Replace lsdir system call by readdir, isdir system calls,
authorBen Pfaff <blp@cs.stanford.edu>
Fri, 19 May 2006 20:38:50 +0000 (20:38 +0000)
committerBen Pfaff <blp@cs.stanford.edu>
Fri, 19 May 2006 20:38:50 +0000 (20:38 +0000)
which are far less offensive to good taste.
Add dir_get_inode() and dir_readdir() functions.
Remove dir_list(), filesys_list().  The latter was unused.
Update project documentation, solutions, and tests.
Rewrite "ls" example program to use the new interface, and add the
ability to specify a directory name and a "long format" feature.
Add "cd" command to shell.

15 files changed:
doc/filesys.texi
src/examples/ls.c
src/examples/shell.c
src/filesys/directory.c
src/filesys/directory.h
src/filesys/filesys.c
src/filesys/filesys.h
src/filesys/fsutil.c
src/lib/syscall-nr.h
src/lib/user/syscall.c
src/lib/user/syscall.h
src/tests/filesys/extended/dir-lsdir.c
src/tests/filesys/extended/dir-lsdir.ck
src/tests/filesys/extended/dir-open.c
src/tests/filesys/extended/dir-open.ck

index 16d0fad0ed34b90dcd8881ba0f3df0e7ddf8857e..8e4dc4f964b4f352f7ffc30c6b672f2bda8573c5 100644 (file)
@@ -136,7 +136,7 @@ that a file cannot exceed the size of the disk (minus metadata).  This
 also applies to the root directory file, which should now be allowed
 to expand beyond its initial limit of 16 files.
 
-The user is allowed to seek beyond the current end-of-file (EOF).  The
+User programs are allowed to seek beyond the current end-of-file (EOF).  The
 seek itself does not extend the file.  Writing at a position past EOF
 extends the file to the position being written, and any gap between the
 previous EOF and the start of the write must be filled with zeros.  A
@@ -176,11 +176,19 @@ not an external program.)
 Update the existing system calls so that, anywhere a file name is
 provided by the caller, an absolute or relative path name may used.
 The directory separator character is forward slash (@samp{/}).
+You may support @file{.} and @file{..} for a small amount of extra
+credit.
 
 Update the @code{remove} system call so that it can delete empty
 directories in addition to regular files.  Directories can only be
 deleted if they do not contain any files or subdirectories.
 
+Update the @code{open} system call so that it can also open directories.
+Passing @file{.} as the argument to @code{open} must open the current
+directory, regardless of whether @file{.} and @file{..} are fully
+implemented.  Of the existing system calls, only @code{close} needs to
+accept a file descriptor for a directory.
+
 Implement the following new system calls:
 
 @deftypefn {System Call} bool chdir (const char *@var{dir})
@@ -198,22 +206,39 @@ Fails if @var{dir} already exists or if any directory name in
 @file{/a/b/c} does not.
 @end deftypefn
 
-@deftypefn {System Call} void lsdir (void)
-Prints a list of files in the current directory to @code{stdout}, one
-per line, in no particular order.
+@deftypefn {System Call} bool readdir (int @var{fd}, char *@var{name})
+Reads a directory entry from file descriptor @var{fd}, which must
+represent a directory.  If successful, stores the null-terminated file
+name in @var{name}, which must have room for @code{READDIR_MAX_LEN + 1}
+bytes, and returns true.  If no entries are left in the directory,
+returns false.
+
+@file{.} and @file{..} should not be returned by @code{readdir},
+regardless of whether they are implemented.
+
+If the directory changes while it is open, then it is acceptable for
+some entries not to be read at all or to be read multiple times.
+Otherwise, each directory entry should be read once, in any order.
+
+@code{READDIR_MAX_LEN} is defined in @file{lib/user/syscall.h}.  If your
+file system supports longer file names than the basic file system, you
+should increase this value from the default of 14.
+@end deftypefn
+
+@deftypefn {System Call} bool isdir (int @var{fd})
+Returns true if @var{fd} represents a directory,
+false if it represents an ordinary file.
 @end deftypefn
 
 We have provided @command{ls} and @command{mkdir} user programs, which
-are straightforward once the above syscalls are implemented.
+are straightforward once the above syscalls are implemented.  The
+@command{shell} program implements @command{cd} internally.
 
 The @code{pintos} @option{put} and @option{get} commands should now
 accept full path names, assuming that the directories used in the
 paths have already been created.  This should not require any extra
 effort on your part.
 
-You may support @file{.} and @file{..} for a small amount of extra
-credit.
-
 @node Buffer Cache
 @subsection Buffer Cache
 
@@ -375,6 +400,7 @@ of IDE disk hardware.
 
 @menu
 * Indexed Files FAQ::           
+* Subdirectories FAQ::          
 * Buffer Cache FAQ::            
 @end menu
 
@@ -389,6 +415,22 @@ will have to be smaller than the disk to accommodate the metadata.
 You'll need to consider this when deciding your inode organization.
 @end table
 
+@node Subdirectories FAQ
+@subsection Subdirectories FAQ
+
+@table @b
+@item How should a file name like @samp{//a//b} be interpreted?
+
+Multiple consecutive slashes are equivalent to a single slash, so this
+file name is the same as @samp{/a/b}.
+
+@item How about a file name like @samp{/../x}?
+
+If you don't implement @file{.} and @file{..}, then this is not a
+special case.  If you do, then it is equivalent to @samp{/x}.  That is,
+the root directory is its own parent.
+@end table
+
 @node Buffer Cache FAQ
 @subsection Buffer Cache FAQ
 
index d64d01edd83425fa0233bec9c83851b81796d7e9..5907a6cedb90ab507e6f4feee4502f385c1bcdd7 100644 (file)
@@ -1,12 +1,87 @@
 /* ls.c
   
-   Lists the current directory. */
+   Lists the contents of the directory or directories named on
+   the command line, or of the current directory if none are
+   named.
+
+   By default, only the name of each file is printed.  If "-l" is
+   given as the first argument, the type and size of each file is
+   also printed. */
 
 #include <syscall.h>
+#include <stdio.h>
+#include <string.h>
+
+static void
+list_dir (const char *dir, bool verbose) 
+{
+  int dir_fd = open (dir);
+  if (dir_fd == -1) 
+    {
+      printf ("%s: not found\n", dir);
+      return; 
+    }
+
+  if (isdir (dir_fd))
+    {
+      char name[READDIR_MAX_LEN];
+      printf ("%s:\n", dir);
+      while (readdir (dir_fd, name)) 
+        {
+          printf ("%s", name); 
+          if (verbose) 
+            {
+              char full_name[128];
+              int entry_fd;
+
+              if (strcmp (dir, "."))
+                snprintf (full_name, sizeof full_name, "%s/%s", dir, name);
+              else 
+                {
+                  /* This is a special case for implementations
+                     that don't fully understand . and .. */
+                  strlcpy (full_name, name, sizeof full_name); 
+                }
+              entry_fd = open (full_name);
+
+              printf (": ");
+              if (entry_fd != -1)
+                {
+                  if (isdir (entry_fd))
+                    printf ("directory");
+                  else
+                    printf ("%d-byte file", filesize (entry_fd));
+                }
+              else
+                printf ("open failed");
+              close (entry_fd);
+            }
+          printf ("\n");
+        }
+    }
+  else 
+    printf ("%s: not a directory\n", dir);
+  close (dir_fd); 
+}
 
 int
-main (void
+main (int argc, char *argv[]
 {
-  lsdir ();
+  bool verbose = false;
+  if (argc > 1 && !strcmp (argv[1], "-l")) 
+    {
+      verbose = true;
+      argv++;
+      argc--;
+    }
+  
+  if (argc <= 1)
+    list_dir (".", verbose);
+  else 
+    {
+      int i;
+      for (i = 1; i < argc; i++)
+        list_dir (argv[i], verbose);
+    }
   return 0;
 }
index d31eb3a9a67c534199f0731165295e0a117b1b7e..0aaafbb91c672454d139e498af792f4084e51feb 100644 (file)
@@ -21,6 +21,11 @@ main (void)
       /* Execute command. */
       if (!strcmp (command, "exit"))
         break;
+      else if (!memcmp (command, "cd ", 3)) 
+        {
+          if (!chdir (command + 3))
+            printf ("\"%s\": chdir failed\n", command + 3);
+        }
       else if (command[0] == '\0') 
         {
           /* Empty command. */
index 45bbdaed320b3ff801e72588c8518fa670db0bb2..0d265d5da78b7be2ca705a5a9b7eeacfbcd5dfd2 100644 (file)
@@ -10,6 +10,7 @@
 struct dir 
   {
     struct inode *inode;                /* Backing store. */
+    off_t pos;                          /* Current position. */
   };
 
 /* A single directory entry. */
@@ -37,6 +38,7 @@ dir_open (struct inode *inode)
   if (inode != NULL && dir != NULL)
     {
       dir->inode = inode;
+      dir->pos = 0;
       return dir;
     }
   else
@@ -74,6 +76,13 @@ dir_close (struct dir *dir)
     }
 }
 
+/* Returns the inode encapsulated by DIR. */
+struct inode *
+dir_get_inode (struct dir *dir) 
+{
+  return dir->inode;
+}
+
 /* Searches DIR for a file with the given NAME.
    If successful, returns true, sets *EP to the directory entry
    if EP is non-null, and sets *OFSP to the byte offset of the
@@ -206,15 +215,22 @@ dir_remove (struct dir *dir, const char *name)
   return success;
 }
 
-/* Prints the names of the files in DIR to the system console. */
-void
-dir_list (const struct dir *dir)
+/* Reads the next directory entry in DIR and stores the name in
+   NAME.  Returns true if successful, false if the directory
+   contains no more entries. */
+bool
+dir_readdir (struct dir *dir, char name[NAME_MAX + 1])
 {
   struct dir_entry e;
-  size_t ofs;
-  
-  for (ofs = 0; inode_read_at (dir->inode, &e, sizeof e, ofs) == sizeof e;
-       ofs += sizeof e) 
-    if (e.in_use)
-      printf ("%s\n", e.name);
+
+  while (inode_read_at (dir->inode, &e, sizeof e, dir->pos) == sizeof e) 
+    {
+      dir->pos += sizeof e;
+      if (e.in_use)
+        {
+          strlcpy (name, e.name, NAME_MAX + 1);
+          return true;
+        } 
+    }
+  return false;
 }
index cc8692d50e2174d9194994afe4176d94df748e5d..f49bd3f731c5f8637f02ac0c00a892882df96043 100644 (file)
@@ -17,9 +17,10 @@ struct dir *dir_open (struct inode *);
 struct dir *dir_open_root (void);
 struct dir *dir_reopen (struct dir *);
 void dir_close (struct dir *);
+struct inode *dir_get_inode (struct dir *);
 bool dir_lookup (const struct dir *, const char *name, struct inode **);
 bool dir_add (struct dir *, const char *name, disk_sector_t);
 bool dir_remove (struct dir *, const char *name);
-void dir_list (const struct dir *);
+bool dir_readdir (struct dir *, char name[NAME_MAX + 1]);
 
 #endif /* filesys/directory.h */
index 39bdff91aa99b5c0f37b7dc4bfb9f8e0af64029d..5cf1d974013a008705a91a9b4bd74855416e1967 100644 (file)
@@ -90,24 +90,6 @@ filesys_remove (const char *name)
 
   return success;
 }
-
-/* Prints a list of files in the filesystem to the system
-   console.
-   Returns true if successful, false on failure,
-   which occurs only if an internal memory allocation fails. */
-bool
-filesys_list (void) 
-{
-  struct dir *dir = dir_open_root ();
-  if (dir != NULL) 
-    {
-      dir_list (dir);
-      dir_close (dir);
-      return true;
-    }
-  else
-    return false;
-}
 \f
 static void must_succeed_function (int, bool) NO_INLINE;
 #define MUST_SUCCEED(EXPR) must_succeed_function (__LINE__, EXPR)
index e87cbea5646933256e05b61f679f34d441b46e04..9c30c2539101d8c39a43b4abb34842a1d877706b 100644 (file)
@@ -16,8 +16,6 @@ void filesys_done (void);
 bool filesys_create (const char *name, off_t initial_size);
 struct file *filesys_open (const char *name);
 bool filesys_remove (const char *name);
-bool filesys_chdir (const char *name);
-bool filesys_list (void);
 
 void filesys_self_test (void);
 
index 9c81fb015b605432ee2d60198454d1ce48e21bdf..14e615f8c989b5ad0863fd94d58a316068d2614d 100644 (file)
@@ -3,6 +3,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include "filesys/directory.h"
 #include "filesys/file.h"
 #include "filesys/filesys.h"
 #include "devices/disk.h"
 void
 fsutil_ls (char **argv UNUSED) 
 {
+  struct dir *dir;
+  char name[NAME_MAX + 1];
+  
   printf ("Files in the root directory:\n");
-  filesys_list ();
+  dir = dir_open_root ();
+  if (dir == NULL)
+    PANIC ("root dir open failed");
+  while (dir_readdir (dir, name))
+    printf ("%s\n", name);
   printf ("End of listing.\n");
 }
 
index ef57c7885c17db537d94a9503d38686b314d9696..eb1e0ee907dd62d22265d90e0e9e38db8b51f067 100644 (file)
@@ -26,7 +26,8 @@ enum
     /* Project 4 only. */
     SYS_CHDIR,                  /* Change the current directory. */
     SYS_MKDIR,                  /* Create a directory. */
-    SYS_LSDIR                   /* List the current directory to stdout. */
+    SYS_READDIR,                /* Reads a directory entry. */
+    SYS_ISDIR                   /* Tests if a fd represents a directory. */
   };
 
 #endif /* lib/syscall-nr.h */
index 2485530fc08f053bfd15935480b3474cc0528bf6..858b77c7d45d47ef7aca231f921ce88f5a257c6f 100644 (file)
@@ -165,9 +165,14 @@ mkdir (const char *dir)
   return syscall1 (SYS_MKDIR, dir);
 }
 
-void
-lsdir (void)
+bool
+readdir (int fd, char name[READDIR_MAX_LEN + 1]) 
 {
-  syscall0 (SYS_LSDIR);
+  return syscall2 (SYS_READDIR, fd, name);
 }
 
+bool
+isdir (int fd) 
+{
+  return syscall1 (SYS_ISDIR, fd);
+}
index d03eae163ef371956b911277f75575637da18053..340d1b15fea253155bbe30c50eb7bc9cb73ee383 100644 (file)
@@ -10,6 +10,8 @@ typedef int pid_t;
 typedef int mapid_t;
 #define MAP_FAILED ((mapid_t) -1)
 
+#define READDIR_MAX_LEN 14
+
 void halt (void) NO_RETURN;
 void exit (int status) NO_RETURN;
 pid_t exec (const char *file);
@@ -27,6 +29,7 @@ mapid_t mmap (int fd, void *addr);
 void munmap (mapid_t);
 bool chdir (const char *dir);
 bool mkdir (const char *dir);
-void lsdir (void);
+bool readdir (int fd, char name[READDIR_MAX_LEN + 1]);
+bool isdir (int fd);
 
 #endif /* lib/user/syscall.h */
index 091fec589c7b628d47dd83deb795d8cf5a1a28a0..254e59501b504f2dfb7c7d08eb1ca67ccf41d7ee 100644 (file)
@@ -1,4 +1,4 @@
-/* Just runs lsdir(). */
+/* Lists the contents of a directory using readdir. */
 
 #include <syscall.h>
 #include "tests/lib.h"
@@ -7,5 +7,15 @@
 void
 test_main (void) 
 {
-  lsdir ();
+  int fd;
+  char name[READDIR_MAX_LEN + 1];
+
+  CHECK ((fd = open (".")) > 1, "open .");
+  CHECK (isdir (fd), "isdir(.)");
+
+  while (readdir (fd, name))
+    msg ("readdir: \"%s\"", name);
+
+  msg ("close .");
+  close (fd);
 }
index 0ad947ce9d3a8d50e20e2334763f94fecdbca4e5..36a4e5213570a7a6122ab2f31a2f7e495ea3b52c 100644 (file)
@@ -9,24 +9,32 @@ my (@output) = read_text_file ("$test.output");
 common_checks (@output);
 @output = get_core_output (@output);
 
-my ($begin);
-for my $i (0...$#output) {
-    $begin = $i, last if $output[$i] eq '(dir-lsdir) begin';
+must_contain_in_order (\@output,
+                      '(dir-lsdir) open .',
+                      '(dir-lsdir) isdir(.)',
+                      '(dir-lsdir) close .');
+
+sub must_contain_in_order {
+    my ($output, @lines) = @_;
+    my (@line_numbers) = map (find_line ($_, @$output), @lines);
+    for my $i (0...$#lines - 1) {
+       fail "\"$lines[$i]\" follows \"$lines[$i + 1]\" in output\n"
+         if $line_numbers[$i] > $line_numbers[$i + 1];
+    }
 }
-fail "\"(dir-lsdir) begin\" does not appear in output\n" if !defined $begin;
 
-my ($end);
-for my $i (0...$#output) {
-    $end = $i, last if $output[$i] eq '(dir-lsdir) end';
+sub find_line {
+    my ($line, @output) = @_;
+    for my $i (0...$#output) {
+       return $i if $line eq $output[$i];
+    }
+    fail "\"$line\" does not appear in output\n";
 }
-fail "\"(dir-lsdir) end\" does not appear in output\n" if !defined $end;
-fail "\"begin\" follows \"end\" in output\n" if $begin > $end;
 
 my (%count);
-for my $fn (@output[$begin + 1...$end - 1]) {
-    $fn =~ s/\s+$//;
+for my $fn (map (/readdir: \"([^"]+)\"/, @output)) {
     fail "Unexpected file \"$fn\" in lsdir output\n"
-      unless grep ($_ eq $fn, qw (. .. dir-lsdir));
+      unless grep ($_ eq $fn, qw (dir-lsdir));
     fail "File \"$fn\" listed twice in lsdir output\n"
       if $count{$fn};
     $count{$fn}++;
index c1f6da5f6a1cb0a1ca010b7f47054812bf9d441c..29d18b8580eb5a48fe59d1a77a1758dfb9a8c363 100644 (file)
@@ -1,6 +1,5 @@
-/* Tries to open a directory.
-   This is allowed to succeed or fail,
-   but if it succeeds then attempts to write to it must fail. */
+/* Opens a directory, then tries to write to it, which must
+   fail. */
 
 #include <syscall.h>
 #include "tests/lib.h"
@@ -10,16 +9,13 @@ void
 test_main (void) 
 {
   int fd;
+  int retval;
   
   CHECK (mkdir ("xyzzy"), "mkdir \"xyzzy\"");
-  msg ("open \"xyzzy\"");
-  fd = open ("xyzzy");
-  if (fd == -1) 
-    msg ("open returned -1 -- ok");
-  else 
-    {
-      int retval = write (fd, "foobar", 6);
-      CHECK (retval == -1, "write \"xyzzy\" (must return -1, actually %d)",
-             retval);
-    }
+  CHECK ((fd = open ("xyzzy")) > 1, "open \"xyzzy\"");
+
+  msg ("write \"xyzzy\"");
+  retval = write (fd, "foobar", 6);
+  CHECK (retval == -1,
+         "write \"xyzzy\" (must return -1, actually %d)", retval);
 }
index 09c0083cd0bfab1afaa15a1df89b738c2e2897bb..e16e9a0285b4398d43366761bf83e73a8234487b 100644 (file)
@@ -2,16 +2,18 @@
 use strict;
 use warnings;
 use tests::tests;
-check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF', <<'EOF']);
+check_expected ([<<'EOF', <<'EOF']);
 (dir-open) begin
 (dir-open) mkdir "xyzzy"
 (dir-open) open "xyzzy"
-(dir-open) open returned -1 -- ok
+(dir-open) write "xyzzy"
+(dir-open) write "xyzzy" (must return -1, actually -1)
 (dir-open) end
+dir-open: exit(0)
 EOF
 (dir-open) begin
 (dir-open) mkdir "xyzzy"
 (dir-open) open "xyzzy"
-(dir-open) write "xyzzy" (must return -1, actually -1)
-(dir-open) end
+(dir-open) write "xyzzy"
+dir-open: exit(-1)
 EOF