diff --git a/Makefile b/Makefile
index 943f7191ff2d4db78343fe9c0e58c00cffbf4eec..059434fc1c148f8f0edf6ac05ad8a6569d7bbb25 100644
--- a/Makefile
+++ b/Makefile
@@ -87,11 +87,15 @@ sh : sh.o $(ULIB)
 	$(LD) -N -e main -Ttext 0 -o sh sh.o $(ULIB)
 	$(OBJDUMP) -S sh > sh.asm
 
+ls : ls.o $(ULIB)
+	$(LD) -N -e main -Ttext 0 -o ls ls.o $(ULIB)
+	$(OBJDUMP) -S ls > ls.asm
+
 mkfs : mkfs.c fs.h
 	cc -o mkfs mkfs.c
 
-fs.img : mkfs userfs usertests echo cat README init sh
-	./mkfs fs.img userfs usertests echo cat README init sh
+fs.img : mkfs userfs usertests echo cat README init sh ls
+	./mkfs fs.img userfs usertests echo cat README init sh ls
 
 -include *.d
 
diff --git a/defs.h b/defs.h
index 566380dc07c76a56a0977177ec005c9f1d94a0b8..3674c459be7dc3080d093353206921d4fd8ccfd1 100644
--- a/defs.h
+++ b/defs.h
@@ -85,12 +85,14 @@ int pipe_write(struct pipe *p, char *addr, int n);
 int pipe_read(struct pipe *p, char *addr, int n);
 
 // fd.c
+struct stat;
 void fd_init(void);
 int fd_ualloc(void);
 struct fd * fd_alloc(void);
 void fd_close(struct fd *);
 int fd_read(struct fd *fd, char *addr, int n);
 int fd_write(struct fd *fd, char *addr, int n);
+int fd_stat(struct fd *fd, struct stat *);
 void fd_incref(struct fd *fd);
 
 // ide.c
@@ -115,6 +117,7 @@ void iunlock(struct inode *ip);
 void idecref(struct inode *ip);
 void iput(struct inode *ip);
 struct inode * namei(char *path, uint *);
+void stati(struct inode *ip, struct stat *st);
 int readi(struct inode *ip, char *xdst, uint off, uint n);
 int writei(struct inode *ip, char *addr, uint off, uint n);
 struct inode *mknod(char *, short, short, short);
diff --git a/fd.c b/fd.c
index d162813109ee7db21320504e0c3d06ce021b9816..c5c3e576b287c2c9a7664d1b20dc54db8842d2e7 100644
--- a/fd.c
+++ b/fd.c
@@ -1,4 +1,5 @@
 #include "types.h"
+#include "stat.h"
 #include "param.h"
 #include "x86.h"
 #include "mmu.h"
@@ -121,6 +122,18 @@ fd_close(struct fd *fd)
   release(&fd_table_lock);
 }
 
+int
+fd_stat(struct fd *fd, struct stat *st)
+{
+  if(fd->type == FD_FILE){
+    ilock(fd->ip);
+    stati(fd->ip, st);
+    iunlock(fd->ip);
+    return 0;
+  } else
+    return -1;
+}
+
 void
 fd_incref(struct fd *fd)
 {
diff --git a/fs.c b/fs.c
index 0c00c6ffb32006fa5a46a9828431cbd5488a04ac..fa71c6e805baf7d280e5af9e2172593165780245 100644
--- a/fs.c
+++ b/fs.c
@@ -1,4 +1,5 @@
 #include "types.h"
+#include "stat.h"
 #include "param.h"
 #include "x86.h"
 #include "mmu.h"
@@ -270,6 +271,16 @@ bmap(struct inode *ip, uint bn)
 
 #define min(a, b) ((a) < (b) ? (a) : (b))
 
+void
+stati(struct inode *ip, struct stat *st)
+{
+  st->st_dev = ip->dev;
+  st->st_ino = ip->inum;
+  st->st_type = ip->type;
+  st->st_nlink = ip->nlink;
+  st->st_size = ip->size;
+}
+
 int
 readi(struct inode *ip, char *dst, uint off, uint n)
 {
diff --git a/ls.c b/ls.c
new file mode 100644
index 0000000000000000000000000000000000000000..ce088402f37d753a42eeadde0256bcfb79e105a1
--- /dev/null
+++ b/ls.c
@@ -0,0 +1,43 @@
+#include "types.h"
+#include "stat.h"
+#include "user.h"
+#include "fs.h"
+
+char buf[512];
+struct stat stat;
+struct dirent dirent;
+
+int
+main(int argc, char *argv[])
+{
+  int fd;
+  uint off;
+
+  if(argc > 1){
+    puts("Usage: ls\n");
+    exit();
+  }
+
+  fd = open(".", 0);
+  if(fd < 0){
+    printf(2, "ls: cannot open .\n");
+    exit();
+  }
+  if (fstat(fd, &stat) < 0) {
+    printf(2, "ls: cannot open .\n");
+    exit();
+  }
+  if (stat.st_type != T_DIR) {
+    printf(2, "ls: . is not a dir\n");
+  }
+  for(off = 0; off < stat.st_size; off += sizeof(struct dirent)) {
+    if (read(fd, &dirent, sizeof(struct dirent)) != sizeof(struct dirent)) {
+      printf(2, "ls: read error\n");
+      exit();
+    }
+    printf(1, "%s\n", dirent.name);
+  }
+  close(fd);
+
+  exit();
+}
diff --git a/stat.h b/stat.h
new file mode 100644
index 0000000000000000000000000000000000000000..caf0416e1fbb257c3a6f1b7a7ad0f68c9dd50821
--- /dev/null
+++ b/stat.h
@@ -0,0 +1,7 @@
+struct stat {
+  int st_dev;
+  uint st_ino;
+  short st_type;
+  short st_nlink;
+  uint st_size;
+};
diff --git a/syscall.c b/syscall.c
index dfd86c4fea7cb04c560d730a9bd428aeb6a4e083..0f90bd6d65d7fdc7862b106614b06a19fdbae1c5 100644
--- a/syscall.c
+++ b/syscall.c
@@ -1,4 +1,5 @@
 #include "types.h"
+#include "stat.h"
 #include "param.h"
 #include "mmu.h"
 #include "proc.h"
@@ -334,6 +335,28 @@ sys_unlink(void)
   return r;
 }
 
+
+int
+sys_fstat(void)
+{
+  struct proc *cp = curproc[cpu()];
+  uint fd, addr;
+  int r;
+  
+  if(fetcharg(0, &fd) < 0)
+    return -1;
+  if(fetcharg(1, &addr) < 0)
+    return -1;
+  if(fd < 0 || fd >= NOFILE)
+    return -1;
+  if(cp->fds[fd] == 0)
+    return -1;
+  if(addr + sizeof(struct stat) > cp->sz)
+    return -1;
+  r = fd_stat (cp->fds[fd], (struct stat *)(cp->mem + addr));
+  return r;
+}
+
 int
 sys_exec(void)
 {
@@ -570,6 +593,9 @@ syscall(void)
   case SYS_unlink:
     ret = sys_unlink();
     break;
+  case SYS_fstat:
+    ret = sys_fstat();
+    break;
   default:
     cprintf("unknown sys call %d\n", num);
     // XXX fault
diff --git a/syscall.h b/syscall.h
index e6019ad05510c05fb9b3330db4871b5011228f01..9924c7fd9f257a75dd29d28cc0ef0429d2c51b3c 100644
--- a/syscall.h
+++ b/syscall.h
@@ -14,4 +14,5 @@
 #define SYS_open 14
 #define SYS_mknod 15
 #define SYS_unlink 16
+#define SYS_fstat 17
 
diff --git a/user.h b/user.h
index 8154aeb67580b8513056c3bcc0b12d287ca61721..d91b4df5b933fa1f3793d7281e538ab49948defe 100644
--- a/user.h
+++ b/user.h
@@ -14,7 +14,7 @@ int exec(char *, char **);
 int open(char *, int);
 int mknod (char*,short,short,short);
 int unlink (char*);
-
+int fstat (int fd, struct stat *stat);
 int puts(char*);
 char* strcpy(char*, char*);
 void printf(int fd, char *fmt, ...);
diff --git a/userfs.c b/userfs.c
index 046768d58d5e535ea2b6209fa5c3bf3d14e8a633..90aa1fa5330590cef84ce8c4a079dc2303c1b7c9 100644
--- a/userfs.c
+++ b/userfs.c
@@ -1,5 +1,6 @@
-#include "user.h"
 #include "types.h"
+#include "stat.h"
+#include "user.h"
 #include "fs.h"
 #include "fcntl.h"
 
diff --git a/usys.S b/usys.S
index 8cf7581fcd76e63bcc010148fad6195b1336fa90..aa3a22e209f67474b7c69f6ff4d7ff8d8537e2cd 100644
--- a/usys.S
+++ b/usys.S
@@ -23,4 +23,5 @@ STUB(cons_puts)
 STUB(exec)
 STUB(open)
 STUB(mknod)
-STUB(unlink)	
+STUB(unlink)
+STUB(fstat)