[starnix] Implement O_TRUNC for open

Test: open_test
Bug: 79308
Change-Id: Idb38de3b5c0e80fffc6657b10120aabffe4e77a1
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/559447
Commit-Queue: Adam Barth <abarth@google.com>
Fuchsia-Auto-Submit: Adam Barth <abarth@google.com>
Reviewed-by: Theodore Dubois <tbodt@google.com>
diff --git a/src/proc/bin/starnix/fs/syscalls.rs b/src/proc/bin/starnix/fs/syscalls.rs
index 9ff1b4b..85d3762 100644
--- a/src/proc/bin/starnix/fs/syscalls.rs
+++ b/src/proc/bin/starnix/fs/syscalls.rs
@@ -229,10 +229,10 @@
         return Ok(SUCCESS);
     }
 
-    // TODO: These access checks are not quite correct because they don't
-    // consider the current uid and they don't consider GRO or OTH bits.
-    // Really, these checks should be done by the auth system once that
-    // exists.
+    // TODO(security): These access checks are not quite correct because
+    // they don't consider the current uid and they don't consider GRO or
+    // OTH bits. Really, these checks should be done by the auth system once
+    // that exists.
     let stat = node.stat()?;
     if mode & X_OK != 0 && stat.st_mode & S_IXUSR == 0 {
         return Err(EACCES);
diff --git a/src/proc/bin/starnix/task/task.rs b/src/proc/bin/starnix/task/task.rs
index f9d4ea9..0f86ef4 100644
--- a/src/proc/bin/starnix/task/task.rs
+++ b/src/proc/bin/starnix/task/task.rs
@@ -454,7 +454,32 @@
                 parent.mknod(basename, FileMode::IFREG | access, 0)?
             }
         };
-        node.open(flags)
+
+        let file = node.open(flags)?;
+
+        if flags.contains(OpenFlags::TRUNC) {
+            let node = file.node();
+            let mode = node.info().mode;
+            match mode.fmt() {
+                FileMode::IFREG => {
+                    // You might think we should check file.can_write() at this
+                    // point, which is what the docs suggest, but apparently we
+                    // are supposed to truncate the file if this task can write
+                    // to the underlying node, even if we are opening the file
+                    // as read-only. See OpenTest.CanTruncateReadOnly.
+
+                    // TODO(security): We should really do an access check for whether
+                    // this task can write to this file.
+                    if mode.contains(FileMode::IWUSR) {
+                        node.truncate(0)?;
+                    }
+                }
+                FileMode::IFDIR => return Err(EISDIR),
+                _ => (),
+            }
+        }
+
+        Ok(file)
     }
 
     /// A wrapper for FsContext::lookup_parent_at that resolves the given
diff --git a/src/proc/bin/starnix/types/file_mode.rs b/src/proc/bin/starnix/types/file_mode.rs
index 7e673f6..5c8a59b 100644
--- a/src/proc/bin/starnix/types/file_mode.rs
+++ b/src/proc/bin/starnix/types/file_mode.rs
@@ -20,6 +20,22 @@
     pub const IFIFO: FileMode = FileMode(uapi::S_IFIFO);
     pub const IFSOCK: FileMode = FileMode(uapi::S_IFSOCK);
 
+    pub const ISUID: FileMode = FileMode(uapi::S_ISUID);
+    pub const ISGID: FileMode = FileMode(uapi::S_ISGID);
+    pub const ISVTX: FileMode = FileMode(uapi::S_ISVTX);
+    pub const IRWXU: FileMode = FileMode(uapi::S_IRWXU);
+    pub const IRUSR: FileMode = FileMode(uapi::S_IRUSR);
+    pub const IWUSR: FileMode = FileMode(uapi::S_IWUSR);
+    pub const IXUSR: FileMode = FileMode(uapi::S_IXUSR);
+    pub const IRWXG: FileMode = FileMode(uapi::S_IRWXG);
+    pub const IRGRP: FileMode = FileMode(uapi::S_IRGRP);
+    pub const IWGRP: FileMode = FileMode(uapi::S_IWGRP);
+    pub const IXGRP: FileMode = FileMode(uapi::S_IXGRP);
+    pub const IRWXO: FileMode = FileMode(uapi::S_IRWXO);
+    pub const IROTH: FileMode = FileMode(uapi::S_IROTH);
+    pub const IWOTH: FileMode = FileMode(uapi::S_IWOTH);
+    pub const IXOTH: FileMode = FileMode(uapi::S_IXOTH);
+
     pub const IFMT: FileMode = FileMode(uapi::S_IFMT);
 
     pub const DEFAULT_UMASK: FileMode = FileMode(0o022);
@@ -34,6 +50,10 @@
         self.0
     }
 
+    pub fn contains(&self, other: FileMode) -> bool {
+        *self & other != FileMode::EMPTY
+    }
+
     pub fn fmt(&self) -> FileMode {
         FileMode(self.bits() & uapi::S_IFMT)
     }
diff --git a/src/proc/tests/android/meta/open_test.cml b/src/proc/tests/android/meta/open_test.cml
index 380816a..7e14a85 100644
--- a/src/proc/tests/android/meta/open_test.cml
+++ b/src/proc/tests/android/meta/open_test.cml
@@ -2,7 +2,7 @@
     include: [ "//src/sys/test_runners/starnix/default.shard.cml" ],
     program: {
         binary: "/data/tests/open_test",
-        args: [ "--gunit_filter=*.OTruncAndReadOnlyFile:*.ReadOnly:*.WriteOnly:*.ReadWrite:*.RelPath:*.AbsPath:*.AtRelPath:*.AtAbsPath:*.OpenNoFollowStillFollowsLinksInPath:*.Fault:*.NameTooLong:*.DotsFromRoot:*.SymlinkDirectory" ],
+        args: [ "--gunit_filter=*.OTrunc:*.OTruncAndReadOnlyDir:*.OTruncAndReadOnlyFile:*.ReadOnly:*.WriteOnly:*.ReadWrite:*.RelPath:*.AbsPath:*.AtRelPath:*.AtAbsPath:*.OpenNoFollowStillFollowsLinksInPath:*.Fault:*.Truncate:*.NameTooLong:*.DotsFromRoot:*.SymlinkDirectory:*.CanTruncateReadOnly" ],
         mounts: [
             "/:remotefs:root",
             "/data:remotefs:data",