secdiscard: use F2FS_IOC_SEC_TRIM_FILE for secure discard

A new interface, F2FS_IOC_SEC_TRIM_FILE, has been added to F2FS
filesystem to send secure discard command to a specific file,
not to block device. We can use this interface for better discard
command handling on a file basis.

[how to use F2FS_IOC_SEC_TRIM_FILE]
start: start offset in byte (should be aligned with 4096)
len: length in byte (should be aligned with 4096, except designating EOF)
     -1 also means till EOF.
flags: F2FS_TRIM_FILE_DISCARD - sending secure discard command. If the
                                device doesn't support it, send normal
                                discard.
       F2FS_TRIM_FILE_ZEROOUT - overwrite zero data in the file.

Bug: 140759142
Test: execute "secdiscard -- <file_path>" and check the content of file
Signed-off-by: Daeho Jeong <daehojeong@google.com>
Change-Id: I6ee44a047d291274c9d70ae5e4ccccfc06a52f89
diff --git a/secdiscard.cpp b/secdiscard.cpp
index 4659eed..b91f321 100644
--- a/secdiscard.cpp
+++ b/secdiscard.cpp
@@ -50,6 +50,27 @@
 
 }  // namespace
 
+#ifndef F2FS_IOCTL_MAGIC
+#define F2FS_IOCTL_MAGIC 0xf5
+#endif
+
+// F2FS-specific ioctl
+// It requires the below kernel commit merged in v5.9-rc1.
+//   9af846486d78 ("f2fs: add F2FS_IOC_SEC_TRIM_FILE ioctl")
+// In android12-5.4,
+//   7fc27297c44d ("Merge remote-tracking branch 'aosp/upstream-f2fs-stable-linux-5.4.y'
+//   into android12-5.4")
+#ifndef F2FS_IOC_SEC_TRIM_FILE
+struct f2fs_sectrim_range {
+    __u64 start;
+    __u64 len;
+    __u64 flags;
+};
+#define F2FS_IOC_SEC_TRIM_FILE _IOW(F2FS_IOCTL_MAGIC, 20, struct f2fs_sectrim_range)
+#define F2FS_TRIM_FILE_DISCARD 0x1
+#define F2FS_TRIM_FILE_ZEROOUT 0x2
+#endif
+
 int main(int argc, const char* const argv[]) {
     android::base::InitLogging(const_cast<char**>(argv));
     Options options;
@@ -69,9 +90,6 @@
 // In android-4.14,
 //   ce767d9a55bc ("f2fs: updates on v4.16-rc1")
 #ifndef F2FS_IOC_SET_PIN_FILE
-#ifndef F2FS_IOCTL_MAGIC
-#define F2FS_IOCTL_MAGIC 0xf5
-#endif
 #define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32)
 #define F2FS_IOC_GET_PIN_FILE _IOR(F2FS_IOCTL_MAGIC, 14, __u32)
 #endif
@@ -85,8 +103,31 @@
         ioctl(fd, F2FS_IOC_SET_PIN_FILE, &set);
 
         LOG(DEBUG) << "Securely discarding '" << target << "' unlink=" << options.unlink;
-        if (!secdiscard_path(target)) {
-            LOG(ERROR) << "Secure discard failed for: " << target;
+        struct f2fs_sectrim_range secRange;
+        secRange.start = 0;
+        secRange.len = -1;  // until end of file
+        secRange.flags = F2FS_TRIM_FILE_DISCARD | F2FS_TRIM_FILE_ZEROOUT;
+        /*
+         * F2FS_IOC_SEC_TRIM_FILE is only supported by F2FS.
+         * 1. If device supports secure discard, it sends secure discard command on the file.
+         * 2. Otherwise, it sends discard command on the file.
+         * 3. Lastly, it overwrites zero data on it.
+         */
+        int ret = ioctl(fd, F2FS_IOC_SEC_TRIM_FILE, &secRange);
+        if (ret != 0) {
+            if (errno == EOPNOTSUPP) {
+                // If device doesn't support any type of discard, just overwrite zero data.
+                secRange.flags = F2FS_TRIM_FILE_ZEROOUT;
+                ret = ioctl(fd, F2FS_IOC_SEC_TRIM_FILE, &secRange);
+            }
+            if (ret != 0 && errno != ENOTTY) {
+                PLOG(WARNING) << "F2FS_IOC_SEC_TRIM_FILE failed on " << target;
+            }
+        }
+        if (ret != 0) {
+            if (!secdiscard_path(target)) {
+                LOG(ERROR) << "Secure discard failed for: " << target;
+            }
         }
         if (options.unlink) {
             if (unlink(target.c_str()) != 0 && errno != ENOENT) {