diff --git a/Makefile b/Makefile
index 212952f5873cb361f5e53e8f3db4fade58aa4c47..ec2531ad5f3950694c66181bd678ec2729df86c8 100644
--- a/Makefile
+++ b/Makefile
@@ -202,7 +202,8 @@ UPROGS=\
 	$U/_wc\
 	$U/_test\
 	$U/_usertests\
-	$U/_strace
+	$U/_strace\
+	$U/_mv
 
 	# $U/_forktest\
 	# $U/_ln\
@@ -220,7 +221,7 @@ dst=/mnt
 fs: $(UPROGS)
 	@if [ ! -f "fs.img" ]; then \
 		echo "making fs image..."; \
-		dd if=/dev/zero of=fs.img bs=512k count=1024; \
+		dd if=/dev/zero of=fs.img bs=512k count=512; \
 		mkfs.vfat -F 32 fs.img; fi
 	@sudo mount fs.img $(dst)
 	@if [ ! -d "$(dst)/bin" ]; then sudo mkdir $(dst)/bin; fi
diff --git a/kernel/fat32.c b/kernel/fat32.c
index 80a6fe792b30c10a2941a74b32e0fc1d6e4ea955..a75a72f85ce074089bb8818860732a0c76302c59 100644
--- a/kernel/fat32.c
+++ b/kernel/fat32.c
@@ -336,7 +336,8 @@ int eread(struct dirent *entry, int user_dst, uint64 dst, uint off, uint n)
 // Caller must hold entry->lock.
 int ewrite(struct dirent *entry, int user_src, uint64 src, uint off, uint n)
 {
-    if (off > entry->file_size || off + n < off || (entry->attribute & ATTR_READ_ONLY)) {
+    if (off > entry->file_size || off + n < off || (uint64)off + n > 0xffffffff
+        || (entry->attribute & ATTR_READ_ONLY)) {
         return -1;
     }
     if (entry->first_clus == 0) {   // so file_size if 0 too, which requests off == 0
@@ -377,9 +378,11 @@ static struct dirent *eget(struct dirent *parent, char *name)
         for (ep = root.next; ep != &root; ep = ep->next) {          // LRU algo
             if (ep->valid == 1 && ep->parent == parent
                 && strncmp(ep->filename, name, FAT32_MAX_FILENAME) == 0) {
-                ep->ref++;
+                if (ep->ref++ == 0) {
+                    ep->parent->ref++;
+                }
                 release(&ecache.lock);
-                edup(ep->parent);
+                // edup(ep->parent);
                 return ep;
             }
         }
@@ -390,6 +393,7 @@ static struct dirent *eget(struct dirent *parent, char *name)
             ep->dev = parent->dev;
             ep->off = 0;
             ep->valid = 0;
+            ep->dirty = 0;
             release(&ecache.lock);
             return ep;
         }
@@ -399,9 +403,9 @@ static struct dirent *eget(struct dirent *parent, char *name)
 }
 
 // trim ' ' in the head and tail, '.' in head, and test legality
-static char *formatname(char *name)
+char *formatname(char *name)
 {
-    static char illegal[] = { '\"', '*', '/', ':', '<', '>', '?', '\\', '|' };
+    static char illegal[] = { '\"', '*', '/', ':', '<', '>', '?', '\\', '|', 0 };
     char *p;
     while (*name == ' ' || *name == '.') { name++; }
     for (p = name; *p; p++) {
@@ -421,7 +425,7 @@ static char *formatname(char *name)
 
 static void generate_shortname(char *shortname, char *name)
 {
-    static char illegal[] = { '+', ',', ';', '=', '[', ']' };   // these are legal in l-n-e but not s-n-e
+    static char illegal[] = { '+', ',', ';', '=', '[', ']', 0 };   // these are legal in l-n-e but not s-n-e
     int i = 0;
     char c, *p = name;
     for (int j = strlen(name) - 1; j >= 0; j--) {
@@ -470,49 +474,80 @@ uint8 cal_checksum(uchar* shortname)
 }
 
 /**
- * Generate an entry in the raw type and write to the disk.
- * @param   data        for s-n-e it's the first cluster, for l-n-e it's the ordinal.
- * @param   checksum    only for l-n-e, the checksum.
+ * Generate an on disk format entry and write to the disk. Caller must hold dp->lock
+ * @param   dp          the directory
+ * @param   ep          entry to write on disk
+ * @param   off         offset int the dp, should be calculated via dirlookup before calling this
  */
-static void make_entry(struct dirent *dp, uint off, char *name, uint8 attr, uint32 data, uint8 checksum)
+void emake(struct dirent *dp, struct dirent *ep, uint off)
 {
+    if (!(dp->attribute & ATTR_DIRECTORY))
+        panic("emake: not dir");
+    if (off % sizeof(union dentry))
+        panic("emake: not aligned");
+    
     union dentry de;
     memset(&de, 0, sizeof(de));
-    if ((de.sne.attr = attr) == ATTR_LONG_NAME) {
-        de.lne.order = data;
-        de.lne.checksum = checksum;
-        name += ((data & ~LAST_LONG_ENTRY) - 1) * CHAR_LONG_NAME;
-        uint8 *w = (uint8 *)de.lne.name1;
-        int end = 0;
-        for (int i = 1; i <= CHAR_LONG_NAME; i++) {
-            if (end) {
-                *w++ = 0xff;            // on k210, unaligned reading is illegal
-                *w++ = 0xff;
-            } else { 
-                if ((*w++ = *name++) == 0) {
-                    end = 1;
-                }
-                *w++ = 0;
+    if (off <= 32) {
+        if (off == 0) {
+            strncpy(de.sne.name, ".          ", sizeof(de.sne.name));
+        } else {
+            strncpy(de.sne.name, "..         ", sizeof(de.sne.name));
+        }
+        de.sne.attr = ATTR_DIRECTORY;
+        de.sne.fst_clus_hi = (uint16)(ep->first_clus >> 16);        // first clus high 16 bits
+        de.sne.fst_clus_lo = (uint16)(ep->first_clus & 0xffff);       // low 16 bits
+        de.sne.file_size = 0;                                       // filesize is updated in eupdate()
+        off = reloc_clus(dp, off, 1);
+        rw_clus(dp->cur_clus, 1, 0, (uint64)&de, off, sizeof(de));
+    } else {
+        int entcnt = (strlen(ep->filename) + CHAR_LONG_NAME - 1) / CHAR_LONG_NAME;   // count of l-n-entries, rounds up
+        char shortname[CHAR_SHORT_NAME + 1];
+        memset(shortname, 0, sizeof(shortname));
+        generate_shortname(shortname, ep->filename);
+        de.lne.checksum = cal_checksum((uchar *)shortname);
+        de.lne.attr = ATTR_LONG_NAME;
+        for (int i = entcnt; i > 0; i--) {
+            if ((de.lne.order = i) == entcnt) {
+                de.lne.order |= LAST_LONG_ENTRY;
             }
-            switch (i) {
-                case 5:     w = (uint8 *)de.lne.name2; break;
-                case 11:    w = (uint8 *)de.lne.name3; break;
+            char *p = ep->filename + (i - 1) * CHAR_LONG_NAME;
+            uint8 *w = (uint8 *)de.lne.name1;
+            int end = 0;
+            for (int j = 1; j <= CHAR_LONG_NAME; j++) {
+                if (end) {
+                    *w++ = 0xff;            // on k210, unaligned reading is illegal
+                    *w++ = 0xff;
+                } else { 
+                    if ((*w++ = *p++) == 0) {
+                        end = 1;
+                    }
+                    *w++ = 0;
+                }
+                switch (j) {
+                    case 5:     w = (uint8 *)de.lne.name2; break;
+                    case 11:    w = (uint8 *)de.lne.name3; break;
+                }
             }
+            uint off2 = reloc_clus(dp, off, 1);
+            rw_clus(dp->cur_clus, 1, 0, (uint64)&de, off2, sizeof(de));
+            off += sizeof(de);
         }
-    } else {
-        strncpy(de.sne.name, name, sizeof(de.sne.name));
-        de.sne.fst_clus_hi = (uint16)(data >> 16);      // first clus high 16 bits
-        de.sne.fst_clus_lo = (uint16)(data & 0xff);     // low 16 bits
-        de.sne.file_size = 0;                         // filesize is updated in eupdate()
+        memset(&de, 0, sizeof(de));
+        strncpy(de.sne.name, shortname, sizeof(de.sne.name));
+        de.sne.attr = ep->attribute;
+        de.sne.fst_clus_hi = (uint16)(ep->first_clus >> 16);      // first clus high 16 bits
+        de.sne.fst_clus_lo = (uint16)(ep->first_clus & 0xffff);     // low 16 bits
+        de.sne.file_size = ep->file_size;                         // filesize is updated in eupdate()
+        off = reloc_clus(dp, off, 1);
+        rw_clus(dp->cur_clus, 1, 0, (uint64)&de, off, sizeof(de));
     }
-    off = reloc_clus(dp, off, 1);
-    rw_clus(dp->cur_clus, 1, 0, (uint64)&de, off, sizeof(de));
 }
 
 /**
  * Allocate an entry on disk. Caller must hold dp->lock.
  */
-struct dirent *ealloc(struct dirent *dp, char *name, int dir)
+struct dirent *ealloc(struct dirent *dp, char *name, int attr)
 {
     if (!(dp->attribute & ATTR_DIRECTORY)) {
         panic("ealloc not dir");
@@ -527,7 +562,7 @@ struct dirent *ealloc(struct dirent *dp, char *name, int dir)
     }
     ep = eget(dp, name);
     elock(ep);
-    ep->attribute = 0;
+    ep->attribute = attr;
     ep->file_size = 0;
     ep->first_clus = 0;
     ep->parent = edup(dp);
@@ -537,29 +572,15 @@ struct dirent *ealloc(struct dirent *dp, char *name, int dir)
     ep->dirty = 0;
     strncpy(ep->filename, name, FAT32_MAX_FILENAME);
     ep->filename[FAT32_MAX_FILENAME] = '\0';
-
-    if (dir) {    // generate "." and ".." for ep
+    if (attr == ATTR_DIRECTORY) {    // generate "." and ".." for ep
         ep->attribute |= ATTR_DIRECTORY;
         ep->cur_clus = ep->first_clus = alloc_clus(dp->dev);
-        make_entry(ep, 0, ".          ", ATTR_DIRECTORY, ep->first_clus, 0);
-        make_entry(ep, 32, "..         ", ATTR_DIRECTORY, dp->first_clus, 0);
+        emake(ep, ep, 0);
+        emake(ep, dp, 32);
     } else {
         ep->attribute |= ATTR_ARCHIVE;
     }
-    int entcnt = (strlen(name) + CHAR_LONG_NAME - 1) / CHAR_LONG_NAME;   // count of l-n-entries, rounds up
-    char shortname[CHAR_SHORT_NAME + 1];
-    memset(shortname, 0, sizeof(shortname));
-    generate_shortname(shortname, name);
-    uint8 checksum = cal_checksum((uchar *)shortname);
-    for (int i = entcnt; i > 0; i--) {
-        int longnum = i;
-        if (i == entcnt) {
-            longnum |= LAST_LONG_ENTRY;
-        }
-        make_entry(dp, off, ep->filename, ATTR_LONG_NAME, longnum, checksum);
-        off += 32;
-    }
-    make_entry(dp, off, shortname, ep->attribute, ep->first_clus, 0);
+    emake(dp, ep, off);
     ep->valid = 1;
     eunlock(ep);
     return ep;
@@ -576,11 +597,11 @@ struct dirent *edup(struct dirent *entry)
 }
 
 // Only update filesize and first cluster in this case.
+// caller must hold entry->parent->lock
 void eupdate(struct dirent *entry)
 {
     if (!entry->dirty || entry->valid != 1) { return; }
     uint entcnt = 0;
-    elock(entry->parent);
     uint32 off = reloc_clus(entry->parent, entry->off, 0);
     rw_clus(entry->parent->cur_clus, 0, 0, (uint64) &entcnt, off, 1);
     entcnt &= ~LAST_LONG_ENTRY;
@@ -588,19 +609,19 @@ void eupdate(struct dirent *entry)
     union dentry de;
     rw_clus(entry->parent->cur_clus, 0, 0, (uint64)&de, off, sizeof(de));
     de.sne.fst_clus_hi = (uint16)(entry->first_clus >> 16);
-    de.sne.fst_clus_lo = (uint16)(entry->first_clus & 0xff);
+    de.sne.fst_clus_lo = (uint16)(entry->first_clus & 0xffff);
     de.sne.file_size = entry->file_size;
     rw_clus(entry->parent->cur_clus, 1, 0, (uint64)&de, off, sizeof(de));
-    eunlock(entry->parent);
     entry->dirty = 0;
 }
 
 // caller must hold entry->lock
+// caller must hold entry->parent->lock
 // remove the entry in its parent directory
 void eremove(struct dirent *entry)
 {
+    if (entry->valid != 1) { return; }
     uint entcnt = 0;
-    elock(entry->parent);
     uint32 off = entry->off;
     uint32 off2 = reloc_clus(entry->parent, off, 0);
     rw_clus(entry->parent->cur_clus, 0, 0, (uint64) &entcnt, off2, 1);
@@ -611,7 +632,6 @@ void eremove(struct dirent *entry)
         off += 32;
         off2 = reloc_clus(entry->parent, off, 0);
     }
-    eunlock(entry->parent);
     entry->valid = -1;
 }
 
@@ -626,6 +646,7 @@ void etrunc(struct dirent *entry)
     }
     entry->file_size = 0;
     entry->first_clus = 0;
+    entry->dirty = 1;
 }
 
 void elock(struct dirent *entry)
@@ -659,7 +680,9 @@ void eput(struct dirent *entry)
         if (entry->valid == -1) {       // this means some one has called eremove()
             etrunc(entry);
         } else {
+            elock(entry->parent);
             eupdate(entry);
+            eunlock(entry->parent);
         }
         releasesleep(&entry->lock);
 
@@ -669,7 +692,9 @@ void eput(struct dirent *entry)
         acquire(&ecache.lock);
         entry->ref--;
         release(&ecache.lock);
-        eput(eparent);
+        if (entry->ref == 0) {
+            eput(eparent);
+        }
         return;
     }
     entry->ref--;
@@ -851,9 +876,8 @@ static char *skipelem(char *path, char *name)
     int len = path - s;
     if (len > FAT32_MAX_FILENAME) {
         len = FAT32_MAX_FILENAME;
-    } else {
-        name[len] = 0;
     }
+    name[len] = 0;
     memmove(name, s, len);
     while (*path == '/') {
         path++;
diff --git a/kernel/include/fat32.h b/kernel/include/fat32.h
index c82c17ed8899e47d61562c845cfa6651e0844053..83ba428312220a77e1d1249109a2b81bb7fffd57 100644
--- a/kernel/include/fat32.h
+++ b/kernel/include/fat32.h
@@ -52,7 +52,9 @@ struct dirent {
 
 int             fat32_init(void);
 struct dirent*  dirlookup(struct dirent *entry, char *filename, uint *poff);
-struct dirent*  ealloc(struct dirent *dp, char *name, int dir);
+char*           formatname(char *name);
+void            emake(struct dirent *dp, struct dirent *ep, uint off);
+struct dirent*  ealloc(struct dirent *dp, char *name, int attr);
 struct dirent*  edup(struct dirent *entry);
 void            eupdate(struct dirent *entry);
 void            etrunc(struct dirent *entry);
diff --git a/kernel/include/sysnum.h b/kernel/include/sysnum.h
index dc445f081678e5e998dba9f1a2cc7767609c9812..fb43d060ecfaab14a1681f6f712c4e2b5c4d5b60 100644
--- a/kernel/include/sysnum.h
+++ b/kernel/include/sysnum.h
@@ -27,5 +27,6 @@
 #define SYS_dev         23
 #define SYS_readdir     24
 #define SYS_getcwd      25
+#define SYS_rename      26
 
 #endif
\ No newline at end of file
diff --git a/kernel/syscall.c b/kernel/syscall.c
index fda0199d14881225fbf9f8057a9c9f584e6a1f7a..d906609780e943c7983b65930e733a564e975512 100644
--- a/kernel/syscall.c
+++ b/kernel/syscall.c
@@ -115,6 +115,7 @@ extern uint64 sys_getcwd(void);
 extern uint64 sys_remove(void);
 extern uint64 sys_trace(void);
 extern uint64 sys_sysinfo(void);
+extern uint64 sys_rename(void);
 
 static uint64 (*syscalls[])(void) = {
   [SYS_fork]        sys_fork,
@@ -142,6 +143,7 @@ static uint64 (*syscalls[])(void) = {
   [SYS_remove]      sys_remove,
   [SYS_trace]       sys_trace,
   [SYS_sysinfo]     sys_sysinfo,
+  [SYS_rename]      sys_rename,
 };
 
 static char *sysnames[] = {
@@ -170,6 +172,7 @@ static char *sysnames[] = {
   [SYS_remove]      "remove",
   [SYS_trace]       "trace",
   [SYS_sysinfo]     "sysinfo",
+  [SYS_rename]      "rename",
 };
 
 void
@@ -183,7 +186,7 @@ syscall(void)
     p->trapframe->a0 = syscalls[num]();
         // trace
     if ((p->tmask & (1 << num)) != 0) {
-      printf("pid %d: syscall %s -> %d\n", p->pid, sysnames[num], p->trapframe->a0);
+      printf("pid %d: %s -> %d\n", p->pid, sysnames[num], p->trapframe->a0);
     }
   } else {
     printf("pid %d %s: unknown sys call %d\n",
diff --git a/kernel/sysfile.c b/kernel/sysfile.c
index 46d9a0e23d3fab8bf0662cd92ccdc60007131b0d..3fd28fa3c43ee88e4c9408e52b3c9079c7cdec47 100644
--- a/kernel/sysfile.c
+++ b/kernel/sysfile.c
@@ -122,7 +122,7 @@ sys_fstat(void)
 }
 
 static struct dirent*
-create(char *path, short type)
+create(char *path, short type, int mode)
 {
   struct dirent *ep, *dp;
   char name[FAT32_MAX_FILENAME + 1];
@@ -130,8 +130,16 @@ create(char *path, short type)
   if((dp = enameparent(path, name)) == NULL)
     return NULL;
 
+  if (type == T_DIR) {
+    mode = ATTR_DIRECTORY;
+  } else if (mode & O_RDONLY) {
+    mode = ATTR_READ_ONLY;
+  } else {
+    mode = 0;  
+  }
+
   elock(dp);
-  if ((ep = ealloc(dp, name, type == T_DIR)) == NULL) {
+  if ((ep = ealloc(dp, name, mode)) == NULL) {
     eunlock(dp);
     eput(dp);
     return NULL;
@@ -139,8 +147,8 @@ create(char *path, short type)
   
   if ((type == T_DIR && !(ep->attribute & ATTR_DIRECTORY)) ||
       (type == T_FILE && (ep->attribute & ATTR_DIRECTORY))) {
-    eput(ep);
     eunlock(dp);
+    eput(ep);
     eput(dp);
     return NULL;
   }
@@ -164,7 +172,7 @@ sys_open(void)
     return -1;
 
   if(omode & O_CREATE){
-    ep = create(path, T_FILE);
+    ep = create(path, T_FILE, omode);
     if(ep == NULL){
       return -1;
     }
@@ -189,16 +197,16 @@ sys_open(void)
     return -1;
   }
 
+  if(!(ep->attribute & ATTR_DIRECTORY) && (omode & O_TRUNC)){
+    etrunc(ep);
+  }
+
   f->type = FD_ENTRY;
   f->off = (omode & O_APPEND) ? ep->file_size : 0;
   f->ep = ep;
   f->readable = !(omode & O_WRONLY);
   f->writable = (omode & O_WRONLY) || (omode & O_RDWR);
 
-  if(!(ep->attribute & ATTR_DIRECTORY) && (omode & O_TRUNC)){
-    etrunc(ep);
-  }
-
   eunlock(ep);
 
   return fd;
@@ -210,7 +218,7 @@ sys_mkdir(void)
   char path[FAT32_MAX_PATH];
   struct dirent *ep;
 
-  if(argstr(0, path, FAT32_MAX_PATH) < 0 || (ep = create(path, T_DIR)) == 0){
+  if(argstr(0, path, FAT32_MAX_PATH) < 0 || (ep = create(path, T_DIR, 0)) == 0){
     return -1;
   }
   eunlock(ep);
@@ -395,10 +403,94 @@ sys_remove(void)
       eput(ep);
       return -1;
   }
+  elock(ep->parent);      // Will this lead to deadlock?
   eremove(ep);
-
+  eunlock(ep->parent);
   eunlock(ep);
   eput(ep);
 
   return 0;
-}
\ No newline at end of file
+}
+
+// Must hold too many locks at a time! It's possible to raise a deadlock.
+// Because this op takes some steps, we can't promise
+uint64
+sys_rename(void)
+{
+  char old[FAT32_MAX_PATH], new[FAT32_MAX_PATH];
+  if (argstr(0, old, FAT32_MAX_PATH) < 0 || argstr(1, new, FAT32_MAX_PATH) < 0) {
+      return -1;
+  }
+
+  struct dirent *src = NULL, *dst = NULL, *pdst = NULL;
+  int srclock = 0;
+  char *name;
+  if ((src = ename(old)) == NULL || (pdst = enameparent(new, old)) == NULL
+      || (name = formatname(old)) == NULL) {
+    goto fail;          // src doesn't exist || dst parent doesn't exist || illegal new name
+  }
+  for (struct dirent *ep = pdst; ep != NULL; ep = ep->parent) {
+    if (ep == src) {    // In what universe can we move a directory into its child?
+      goto fail;
+    }
+  }
+
+  uint off;
+  elock(src);     // must hold child's lock before acquiring parent's, because we do so in other similar cases
+  srclock = 1;
+  elock(pdst);
+  dst = dirlookup(pdst, name, &off);
+  if (dst != NULL) {
+    eunlock(pdst);
+    if (src == dst) {
+      goto fail;
+    } else if (src->attribute & dst->attribute & ATTR_DIRECTORY) {
+      elock(dst);
+      if (!isdirempty(dst)) {    // it's ok to overwrite an empty dir
+        eunlock(dst);
+        goto fail;
+      }
+      elock(pdst);
+    } else {                    // src is not a dir || dst exists and is not an dir
+      goto fail;
+    }
+  }
+
+  if (dst) {
+    eremove(dst);
+    eunlock(dst);
+  }
+  memmove(src->filename, name, FAT32_MAX_FILENAME);
+  emake(pdst, src, off);
+  if (src->parent != pdst) {
+    eunlock(pdst);
+    elock(src->parent);
+  }
+  eremove(src);
+  eunlock(src->parent);
+  struct dirent *psrc = src->parent;  // src must not be root, or it won't pass the for-loop test
+  src->parent = edup(pdst);
+  src->off = off;
+  src->valid = 1;
+  eunlock(src);
+
+  eput(psrc);
+  if (dst) {
+    eput(dst);
+  }
+  eput(pdst);
+  eput(src);
+
+  return 0;
+
+fail:
+  if (srclock)
+    eunlock(src);
+  if (dst)
+    eput(dst);
+  if (pdst)
+    eput(pdst);
+  if (src)
+    eput(src);
+  return -1;
+}
diff --git a/kernel/trap.c b/kernel/trap.c
index 0b4c54c707a16d41a95c0d3decb7c07de95fe0cb..edf79e1474bf2076f6062617d4cb82b00c58e1ed 100644
--- a/kernel/trap.c
+++ b/kernel/trap.c
@@ -83,7 +83,7 @@ usertrap(void)
   else {
     printf("\nusertrap(): unexpected scause %p pid=%d %s\n", r_scause(), p->pid, p->name);
     printf("            sepc=%p stval=%p\n", r_sepc(), r_stval());
-    trapframedump(p->trapframe);
+    // trapframedump(p->trapframe);
     p->killed = 1;
   }
 
diff --git a/xv6-user/mv.c b/xv6-user/mv.c
new file mode 100644
index 0000000000000000000000000000000000000000..6dc2aa9d15f06e375844876410b15a2011b82a80
--- /dev/null
+++ b/xv6-user/mv.c
@@ -0,0 +1,53 @@
+#include "kernel/include/types.h"
+#include "kernel/include/stat.h"
+#include "kernel/include/fcntl.h"
+#include "kernel/include/param.h"
+#include "xv6-user/user.h"
+
+int main(int argc, char *argv[])
+{
+    if (argc < 3) {
+        fprintf(2, "Usage: mv old_name new_name\n");
+        exit(1);
+    }
+
+    char src[MAXPATH];
+    char dst[MAXPATH];
+    strcpy(src, argv[1]);
+    strcpy(dst, argv[2]);
+    int fd = open(dst, O_RDONLY);
+    if (fd >= 0) {
+        struct stat st;
+        fstat(fd, &st);
+        close(fd);
+        if (st.type == T_DIR) {
+            char *ps, *pd;
+            for (ps = src + strlen(src) - 1; ps >= src; ps--) { // trim '/' in tail
+                if (*ps != '/') {
+                    *(ps + 1) = '\0';
+                    break;
+                }
+            }
+            for (; ps >= src && *ps != '/'; ps--);
+            ps++;
+            pd = dst + strlen(dst);
+            *pd++ = '/';
+            while (*ps) {
+                *pd++ = *ps++;
+                if (pd >= dst + MAXPATH) {
+                    fprintf(2, "mv: fail! final dst path too long (exceed MAX=%d)!\n", MAXPATH);
+                    exit(-1);
+                }
+            }
+        } else {
+            fprintf(2, "mv: fail! %s exists!\n", dst);
+            exit(-1);
+        }
+    }
+    printf("moving [%s] to [%s]\n", src, dst);
+    if (rename(src, dst) < 0) {
+        fprintf(2, "mv: fail to rename %s to %s!\n", src, dst);
+        exit(-1);
+    }
+    exit(0);
+}
\ No newline at end of file
diff --git a/xv6-user/user.h b/xv6-user/user.h
index 291fddf9ccfdb1e902479a87d140cd9d841c005a..cf17f7a5357fa92d089a9b652b146ae0b344eb77 100644
--- a/xv6-user/user.h
+++ b/xv6-user/user.h
@@ -7,27 +7,28 @@ int fork(void);
 int exit(int) __attribute__((noreturn));
 int wait(int*);
 int pipe(int*);
-int write(int, const void*, int);
-int read(int, void*, int);
-int close(int);
-int kill(int);
+int write(int fd, const void *buf, int len);
+int read(int fd, void *buf, int len);
+int close(int fd);
+int kill(int pid);
 int exec(char*, char**);
-int open(const char*, int);
+int open(const char *filename, int mode);
 int fstat(int fd, struct stat*);
-int mkdir(const char*);
-int chdir(const char*);
-int dup(int);
+int mkdir(const char *dirname);
+int chdir(const char *dirname);
+int dup(int fd);
 int getpid(void);
-char* sbrk(int);
-int sleep(int);
+char* sbrk(int size);
+int sleep(int ticks);
 int uptime(void);
 int test_proc(int);
 int dev(int, short, short);
 int readdir(int fd, struct stat*);
-int getcwd(char *);
-int remove(char *);
-int trace(int);
+int getcwd(char *buf);
+int remove(char *filename);
+int trace(int mask);
 int sysinfo(struct sysinfo *);
+int rename(char *old, char *new);
 
 // ulib.c
 int stat(const char*, struct stat*);
diff --git a/xv6-user/usertests.c b/xv6-user/usertests.c
index 17ef17b71c7da5a09aae2aeee47117e79838285b..de67e6b907ea43947a019882ca091cd2cbff3581 100644
--- a/xv6-user/usertests.c
+++ b/xv6-user/usertests.c
@@ -943,6 +943,7 @@ forkforkfork(char *s)
   close(open("stopforking", O_CREATE|O_RDWR));
   wait(0);
   sleep(10); // one second
+  remove("stopforking");
 }
 
 // regression test. does reparent() violate the parent-then-child
@@ -1140,7 +1141,7 @@ createdelete(char *s)
   enum { N = 20, NCHILD=4 };
   int pid, i, fd, pi;
   char name[32];
-  char illegal[] = { '\"', '*', '/', ':', '<', '>', '?', '\\', '|' };
+  char illegal[] = { '\"', '*', '/', ':', '<', '>', '?', '\\', '|', 0 };
   for(pi = 0; pi < NCHILD; pi++){
     pid = fork();
     if(pid < 0){
@@ -1163,9 +1164,11 @@ createdelete(char *s)
         }
         if(i > 0 && (i % 2 ) == 0){
           name[1] = '0' + (i / 2);
-          if(remove(name) < 0){
-            printf("%s: remove failed\n", s);
-            exit(1);
+          if (strchr(illegal, name[1]) == 0) {
+            if(remove(name) < 0){
+              printf("%s: remove failed\n", s);
+              exit(1);
+            }
           }
         }
       }
@@ -1201,7 +1204,7 @@ createdelete(char *s)
 
   for(i = 0; i < N; i++){
     for(pi = 0; pi < NCHILD; pi++){
-      name[0] = 'p' + i;
+      name[0] = 'p' + pi;
       name[1] = '0' + i;
       remove(name);
     }
@@ -2268,6 +2271,7 @@ bigargtest(char *s)
     exit(1);
   }
   close(fd);
+  remove("bigarg-ok");
 }
 
 // what happens when the file system runs out of blocks?
@@ -2665,7 +2669,7 @@ main(int argc, char *argv[])
     {createdelete, "createdelete"},
               // {linkremove, "linkremove"},
               // {linktest, "linktest"},
-              // {removeread, "removeread"},
+    {removeread, "removeread"},
               // {concreate, "concreate"},
     {subdir, "subdir"},
     {fourfiles, "fourfiles"},
diff --git a/xv6-user/usys.pl b/xv6-user/usys.pl
index 69f30450c41c76273eee4ee314b27928be83a627..e48bcde27e1e3859f1a927345afba521d04fe02a 100755
--- a/xv6-user/usys.pl
+++ b/xv6-user/usys.pl
@@ -40,3 +40,4 @@ entry("getcwd");
 entry("remove");
 entry("trace");
 entry("sysinfo");
+entry("rename");