Make deletion of larger files possible.

Bug: 22502684
Change-Id: I9034af1ce919bff51f836e5bf51cc2585089440e
diff --git a/secdiscard.cpp b/secdiscard.cpp
index 9215993..f7adb0d 100644
--- a/secdiscard.cpp
+++ b/secdiscard.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -39,13 +40,14 @@
     bool unlink{true};
 };
 
-// Deliberately limit ourselves to wiping small files.
-constexpr uint64_t max_wipe_length = 4096;
+constexpr uint32_t max_extents = 32;
 
 bool read_command_line(int argc, const char * const argv[], Options &options);
 void usage(const char *progname);
 int secdiscard_path(const std::string &path);
-int path_device_range(const std::string &path, uint64_t range[2]);
+std::unique_ptr<struct fiemap> path_fiemap(const std::string &path, uint32_t extent_count);
+bool check_fiemap(const struct fiemap &fiemap, const std::string &path);
+std::unique_ptr<struct fiemap> alloc_fiemap(uint32_t extent_count);
 std::string block_device_for_path(const std::string &path);
 
 }
@@ -95,8 +97,8 @@
 
 // BLKSECDISCARD all content in "path", if it's small enough.
 int secdiscard_path(const std::string &path) {
-    uint64_t range[2];
-    if (path_device_range(path, range) == -1) {
+    auto fiemap = path_fiemap(path, max_extents);
+    if (!fiemap || !check_fiemap(*fiemap, path)) {
         return -1;
     }
     auto block_device = block_device_for_path(path);
@@ -108,16 +110,20 @@
         SLOGE("Failed to open device %s: %s", block_device.c_str(), strerror(errno));
         return -1;
     }
-    if (ioctl(fs_fd.get(), BLKSECDISCARD, range) == -1) {
-        SLOGE("Unable to BLKSECDISCARD %s: %s", path.c_str(), strerror(errno));
-        return -1;
+    for (uint32_t i = 0; i < fiemap->fm_mapped_extents; i++) {
+        uint64_t range[2];
+        range[0] = fiemap->fm_extents[i].fe_physical;
+        range[1] = fiemap->fm_extents[i].fe_length;
+        if (ioctl(fs_fd.get(), BLKSECDISCARD, range) == -1) {
+            SLOGE("Unable to BLKSECDISCARD %s: %s", path.c_str(), strerror(errno));
+            return -1;
+        }
     }
     return 0;
 }
 
-// Find a short range that completely covers the file.
-// If there isn't one, return -1, otherwise 0.
-int path_device_range(const std::string &path, uint64_t range[2])
+// Read the file's FIEMAP
+std::unique_ptr<struct fiemap> path_fiemap(const std::string &path, uint32_t extent_count)
 {
     AutoCloseFD fd(path);
     if (!fd) {
@@ -126,41 +132,49 @@
         } else {
             SLOGE("Unable to open %s: %s", path.c_str(), strerror(errno));
         }
-        return -1;
+        return nullptr;
     }
-    alignas(struct fiemap) char fiemap_buffer[offsetof(struct fiemap, fm_extents[1])];
-    memset(fiemap_buffer, 0, sizeof(fiemap_buffer));
-    struct fiemap *fiemap = (struct fiemap *)fiemap_buffer;
-    fiemap->fm_start = 0;
-    fiemap->fm_length = UINT64_MAX;
-    fiemap->fm_flags = 0;
-    fiemap->fm_extent_count = 1;
-    fiemap->fm_mapped_extents = 0;
-    if (ioctl(fd.get(), FS_IOC_FIEMAP, fiemap) != 0) {
+    auto fiemap = alloc_fiemap(extent_count);
+    if (ioctl(fd.get(), FS_IOC_FIEMAP, fiemap.get()) != 0) {
         SLOGE("Unable to FIEMAP %s: %s", path.c_str(), strerror(errno));
-        return -1;
+        return nullptr;
     }
-    if (fiemap->fm_mapped_extents != 1) {
-        SLOGE("Expecting one extent, got %d in %s", fiemap->fm_mapped_extents, path.c_str());
-        return -1;
+    auto mapped = fiemap->fm_mapped_extents;
+    if (mapped < 1 || mapped > extent_count) {
+        SLOGE("Extent count not in bounds 1 <= %u <= %u in %s", mapped, extent_count, path.c_str());
+        return nullptr;
     }
-    struct fiemap_extent *extent = &fiemap->fm_extents[0];
-    if (!(extent->fe_flags & FIEMAP_EXTENT_LAST)) {
-        SLOGE("First extent was not the last in %s", path.c_str());
-        return -1;
+    return fiemap;
+}
+
+// Ensure that the FIEMAP covers the file and is OK to discard
+bool check_fiemap(const struct fiemap &fiemap, const std::string &path) {
+    auto mapped = fiemap.fm_mapped_extents;
+    if (!(fiemap.fm_extents[mapped - 1].fe_flags & FIEMAP_EXTENT_LAST)) {
+        SLOGE("Extent %u was not the last in %s", mapped - 1, path.c_str());
+        return false;
     }
-    if (extent->fe_flags &
-            (FIEMAP_EXTENT_UNKNOWN | FIEMAP_EXTENT_DELALLOC | FIEMAP_EXTENT_NOT_ALIGNED)) {
-        SLOGE("Extent has unexpected flags %ulx: %s", extent->fe_flags, path.c_str());
-        return -1;
+    for (uint32_t i = 0; i < mapped; i++) {
+        auto flags = fiemap.fm_extents[i].fe_flags;
+        if (flags & (FIEMAP_EXTENT_UNKNOWN | FIEMAP_EXTENT_DELALLOC | FIEMAP_EXTENT_NOT_ALIGNED)) {
+            SLOGE("Extent %u has unexpected flags %ulx: %s", i, flags, path.c_str());
+            return false;
+        }
     }
-    if (extent->fe_length > max_wipe_length) {
-        SLOGE("Extent too big, %llu bytes in %s", extent->fe_length, path.c_str());
-        return -1;
-    }
-    range[0] = extent->fe_physical;
-    range[1] = extent->fe_length;
-    return 0;
+    return true;
+}
+
+std::unique_ptr<struct fiemap> alloc_fiemap(uint32_t extent_count)
+{
+    size_t allocsize = offsetof(struct fiemap, fm_extents[extent_count]);
+    std::unique_ptr<struct fiemap> res(new (::operator new (allocsize)) struct fiemap);
+    memset(res.get(), 0, allocsize);
+    res->fm_start = 0;
+    res->fm_length = UINT64_MAX;
+    res->fm_flags = 0;
+    res->fm_extent_count = extent_count;
+    res->fm_mapped_extents = 0;
+    return res;
 }
 
 // Given a file path, look for the corresponding block device in /proc/mount