From af9eb9114c2f8700d4315eaa1e2d637c2aaaf210 Mon Sep 17 00:00:00 2001
From: Robert Morris <rtm@csail.mit.edu>
Date: Thu, 16 Jul 2020 11:38:08 -0400
Subject: [PATCH] make "echo hello > x" truncate file x.

---
 kernel/defs.h    |   1 +
 kernel/fcntl.h   |   1 +
 kernel/fs.c      |  10 ++--
 kernel/sysfile.c |   4 ++
 user/sh.c        |   2 +-
 user/usertests.c | 138 +++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 148 insertions(+), 8 deletions(-)

diff --git a/kernel/defs.h b/kernel/defs.h
index 9c5f643..f33f1f6 100644
--- a/kernel/defs.h
+++ b/kernel/defs.h
@@ -52,6 +52,7 @@ struct inode*   nameiparent(char*, char*);
 int             readi(struct inode*, int, uint64, uint, uint);
 void            stati(struct inode*, struct stat*);
 int             writei(struct inode*, int, uint64, uint, uint);
+void            itrunc(struct inode*);
 
 // ramdisk.c
 void            ramdiskinit(void);
diff --git a/kernel/fcntl.h b/kernel/fcntl.h
index d565483..44861b9 100644
--- a/kernel/fcntl.h
+++ b/kernel/fcntl.h
@@ -2,3 +2,4 @@
 #define O_WRONLY  0x001
 #define O_RDWR    0x002
 #define O_CREATE  0x200
+#define O_TRUNC   0x400
diff --git a/kernel/fs.c b/kernel/fs.c
index 53586d5..e33ec30 100644
--- a/kernel/fs.c
+++ b/kernel/fs.c
@@ -22,7 +22,6 @@
 #include "file.h"
 
 #define min(a, b) ((a) < (b) ? (a) : (b))
-static void itrunc(struct inode*);
 // there should be one superblock per disk device, but we run with
 // only one device
 struct superblock sb; 
@@ -406,11 +405,8 @@ bmap(struct inode *ip, uint bn)
 }
 
 // Truncate inode (discard contents).
-// Only called when the inode has no links
-// to it (no directory entries referring to it)
-// and has no in-memory reference to it (is
-// not an open file or current directory).
-static void
+// Caller must hold ip->lock.
+void
 itrunc(struct inode *ip)
 {
   int i, j;
@@ -463,7 +459,7 @@ readi(struct inode *ip, int user_dst, uint64 dst, uint off, uint n)
   struct buf *bp;
 
   if(off > ip->size || off + n < off)
-    return -1;
+    return 0;
   if(off + n > ip->size)
     n = ip->size - off;
 
diff --git a/kernel/sysfile.c b/kernel/sysfile.c
index 7768d20..015c942 100644
--- a/kernel/sysfile.c
+++ b/kernel/sysfile.c
@@ -341,6 +341,10 @@ sys_open(void)
   f->readable = !(omode & O_WRONLY);
   f->writable = (omode & O_WRONLY) || (omode & O_RDWR);
 
+  if((omode & O_TRUNC) && ip->type == T_FILE){
+    itrunc(ip);
+  }
+
   iunlock(ip);
   end_op();
 
diff --git a/user/sh.c b/user/sh.c
index a593bc0..83dd513 100644
--- a/user/sh.c
+++ b/user/sh.c
@@ -386,7 +386,7 @@ parseredirs(struct cmd *cmd, char **ps, char *es)
       cmd = redircmd(cmd, q, eq, O_RDONLY, 0);
       break;
     case '>':
-      cmd = redircmd(cmd, q, eq, O_WRONLY|O_CREATE, 1);
+      cmd = redircmd(cmd, q, eq, O_WRONLY|O_CREATE|O_TRUNC, 1);
       break;
     case '+':  // >>
       cmd = redircmd(cmd, q, eq, O_WRONLY|O_CREATE, 1);
diff --git a/user/usertests.c b/user/usertests.c
index 9aa0ed4..f83a060 100644
--- a/user/usertests.c
+++ b/user/usertests.c
@@ -22,6 +22,141 @@
 char buf[BUFSZ];
 char name[3];
 
+// test O_TRUNC.
+void
+truncate1(char *s)
+{
+  char buf[32];
+  
+  unlink("truncfile");
+  int fd1 = open("truncfile", O_CREATE|O_WRONLY|O_TRUNC);
+  write(fd1, "abcd", 4);
+  close(fd1);
+
+  int fd2 = open("truncfile", O_RDONLY);
+  int n = read(fd2, buf, sizeof(buf));
+  if(n != 4){
+    printf("%s: read %d bytes, wanted 4\n", s, n);
+    exit(1);
+  }
+
+  fd1 = open("truncfile", O_WRONLY|O_TRUNC);
+
+  int fd3 = open("truncfile", O_RDONLY);
+  n = read(fd3, buf, sizeof(buf));
+  if(n != 0){
+    printf("aaa fd3=%d\n", fd3);
+    printf("%s: read %d bytes, wanted 0\n", s, n);
+    exit(1);
+  }
+
+  n = read(fd2, buf, sizeof(buf));
+  if(n != 0){
+    printf("bbb fd2=%d\n", fd2);
+    printf("%s: read %d bytes, wanted 0\n", s, n);
+    exit(1);
+  }
+  
+  write(fd1, "abcdef", 6);
+
+  n = read(fd3, buf, sizeof(buf));
+  if(n != 6){
+    printf("%s: read %d bytes, wanted 6\n", s, n);
+    exit(1);
+  }
+
+  n = read(fd2, buf, sizeof(buf));
+  if(n != 2){
+    printf("%s: read %d bytes, wanted 2\n", s, n);
+    exit(1);
+  }
+
+  unlink("truncfile");
+
+  close(fd1);
+  close(fd2);
+  close(fd3);
+}
+
+// write to an open FD whose file has just been truncated.
+// this causes a write at an offset beyond the end of the file.
+// such writes fail on xv6 (unlike POSIX) but at least
+// they don't crash.
+void
+truncate2(char *s)
+{
+  unlink("truncfile");
+
+  int fd1 = open("truncfile", O_CREATE|O_TRUNC|O_WRONLY);
+  write(fd1, "abcd", 4);
+
+  int fd2 = open("truncfile", O_TRUNC|O_WRONLY);
+
+  int n = write(fd1, "x", 1);
+  if(n != -1){
+    printf("%s: write returned %d, expected -1\n", s, n);
+    exit(1);
+  }
+
+  unlink("truncfile");
+  close(fd1);
+  close(fd2);
+}
+
+void
+truncate3(char *s)
+{
+  int pid, xstatus;
+
+  close(open("truncfile", O_CREATE|O_TRUNC|O_WRONLY));
+  
+  pid = fork();
+  if(pid < 0){
+    printf("%s: fork failed\n", s);
+    exit(1);
+  }
+
+  if(pid == 0){
+    for(int i = 0; i < 100; i++){
+      char buf[32];
+      int fd = open("truncfile", O_WRONLY);
+      if(fd < 0){
+        printf("%s: open failed\n", s);
+        exit(1);
+      }
+      int n = write(fd, "1234567890", 10);
+      if(n != 10){
+        printf("%s: write got %d, expected 10\n", s, n);
+        exit(1);
+      }
+      close(fd);
+      fd = open("truncfile", O_RDONLY);
+      read(fd, buf, sizeof(buf));
+      close(fd);
+    }
+    exit(0);
+  }
+
+  for(int i = 0; i < 150; i++){
+    int fd = open("truncfile", O_CREATE|O_WRONLY|O_TRUNC);
+    if(fd < 0){
+      printf("%s: open failed\n", s);
+      exit(1);
+    }
+    int n = write(fd, "xxx", 3);
+    if(n != 3){
+      printf("%s: write got %d, expected 3\n", s, n);
+      exit(1);
+    }
+    close(fd);
+  }
+
+  wait(&xstatus);
+  unlink("truncfile");
+  exit(xstatus);
+}
+  
+
 // does chdir() call iput(p->cwd) in a transaction?
 void
 iputtest(char *s)
@@ -2169,6 +2304,9 @@ main(int argc, char *argv[])
     void (*f)(char *);
     char *s;
   } tests[] = {
+    {truncate1, "truncate1"},
+    {truncate2, "truncate2"},
+    {truncate3, "truncate3"},
     {reparent2, "reparent2"},
     {pgbug, "pgbug" },
     {sbrkbugs, "sbrkbugs" },