back out Paul's changes
authorJim Meyering <jim@meyering.net>
Sun, 4 Apr 1999 02:34:49 +0000 (02:34 +0000)
committerJim Meyering <jim@meyering.net>
Sun, 4 Apr 1999 02:34:49 +0000 (02:34 +0000)
src/shred.c

index f747a7b..05ff721 100644 (file)
@@ -8,7 +8,7 @@
 /*
  * shred.c - by Colin Plumb.
  *
- * Do a more-secure overwrite of given files or devices, so that not even
+ * Do a secure overwrite of given files or devices, so that not even
  * very expensive hardware probing can recover the data.
  *
  * Although this process is also known as "wiping", I prefer the longer
@@ -34,7 +34,7 @@
  * assumption out, and the assumption that you want the data processed
  * as fast as the hard drive can spin, you can do better.
  *
- * If asked to wipe a file, this also removes it, renaming it to in a
+ * If asked to wipe a file, this also deletes it, renaming it to in a
  * clever way to try to leave no trace of the original filename.
  *
  * Copyright 1997-1999 Colin Plumb <colin@nyx.net>.  This program may
@@ -64,8 +64,6 @@
 
 #include "system.h"
 #include "error.h"
-#include "human.h"
-#include "quotearg.h"
 #include "xstrtoul.h"
 
 /* The official name of this program (e.g., no `g' prefix).  */
 /* FIXME: add comments */
 struct Options
 {
-  enum { NO_CONTENTS, FREED_CONTENTS, ALL_CONTENTS } contents;
-  enum { NO_LINKS, ORDINARY_LINKS, ALL_LINKS } links;
+  int allow_devices;
   int force;
-  size_t n_iterations;
+  unsigned int n_iterations;
+  int remove_file;
   int verbose;
   int exact;
   int zero_fill;
 };
 
-/* If positive, the units to use when printing sizes;
-   if negative, the human-readable base.
-   For now, this is a constant.  */
-static int const output_block_size = -1024;
-
 static struct option const long_opts[] =
 {
-  {"no-contents", no_argument, NULL, 'b'},
-  {"freed-contents", no_argument, NULL, 'c'},
-  {"all-contents", no_argument, NULL, 'C'},
-  {"no-links", no_argument, NULL, 'k'},
-  {"ordinary-links", no_argument, NULL, 'l'},
-  {"all-links", no_argument, NULL, 'L'},
+  {"device", no_argument, NULL, 'd'},
   {"exact", required_argument, NULL, 'x'},
   {"force", no_argument, NULL, 'f'},
   {"iterations", required_argument, NULL, 'n'},
+  {"preserve", no_argument, NULL, 'p'},
   {"verbose", no_argument, NULL, 'v'},
   {"zero", required_argument, NULL, 'z'},
   {GETOPT_HELP_OPTION_DECL},
@@ -116,8 +105,6 @@ static struct option const long_opts[] =
 /* Global variable for error printing purposes */
 char const *program_name;
 
-void usage (int status) __attribute__ ((__noreturn__));
-
 void
 usage (int status)
 {
@@ -128,22 +115,19 @@ usage (int status)
     {
       printf (_("Usage: %s [OPTIONS] FILE [...]\n"), program_name);
       printf (_("\
-Overwrite a file to hide its contents.\n\
+Delete a file securely, first overwriting it to hide its contents.\n\
 \n\
-  -b, --no-contents  do not shred contents\n\
-  -c, --freed-contents  shred contents that will be freed\n\
-  -C, --all-contents  shred all contents (default)\n\
+  -d, --device   allow operation on devices (devices are never deleted)\n\
   -f, --force    change permissions to allow writing if necessary\n\
-  -k, --no-links  do not shred links (default)\n\
-  -l, --ordinary-links  shred links to regular files\n\
-  -L, --all-links  shred all links\n\
   -n, --iterations=N  Overwrite N times instead of the default (25)\n\
+  -p, --preserve do not delete file after overwriting\n\
   -v, --verbose  indicate progress (-vv to leave progress on screen)\n\
   -x, --exact    do not round file sizes up to the next full block\n\
   -z, --zero     add a final overwrite with zeros to hide shredding\n\
-  -              shred standard output (but don't remove it)\n\
-  --help         display this help and exit\n\
-  --version      print version information and exit\n\
+  -              shred standard input (but don't delete it);\n\
+                   this will fail unless you use <>file, a safety feature\n\
+      --help     display this help and exit\n\
+      --version  print version information and exit\n\
 \n\
 FIXME maybe add more discussion here?\n\
 "));
@@ -154,7 +138,7 @@ FIXME maybe add more discussion here?\n\
 }
 
 #if ! HAVE_FDATASYNC
-# define fdatasync(fd) (-1)
+# define fdatasync(Fd) fsync (Fd)
 #endif
 
 /*
@@ -379,29 +363,28 @@ isaac_init (struct isaac_state *s, word32 const *seed, size_t seedsize)
 static void
 isaac_seed (struct isaac_state *s)
 {
-  char *p = (char *) s->mm;
-  char *lim = p + sizeof s->mm;
-#define MIXIN_BOUND(s) ((s) < lim - p ? (s) : lim - p)
-#define MIXIN(o) \
-    do \
-      { \
-       size_t s = MIXIN_BOUND (sizeof (o)); \
-        memcpy (p, (char *) &(o), s); \
-       p += s; \
-      } \
-    while (0)
-
-  /* Mix in bits of random information from the environment.
-     Mix the most random items first, in case lim - p is small
-     and we have to truncate.  */
+  s->mm[0] = getpid ();
+  s->mm[1] = getppid ();
+
+  {
+#ifdef HAVE_CLOCK_GETTIME              /* POSIX ns-resolution */
+    struct timespec ts;
+    clock_gettime (CLOCK_REALTIME, &ts);
+    s->mm[2] = ts.tv_sec;
+    s->mm[3] = ts.tv_nsec;
+#else
+    struct timeval tv;
+    gettimeofday (&tv, (struct timezone *) 0);
+    s->mm[2] = tv.tv_sec;
+    s->mm[3] = tv.tv_usec;
+#endif
+  }
 
   {
     int fd = open ("/dev/urandom", O_RDONLY);
     if (fd >= 0)
       {
-       size_t s = MIXIN_BOUND (32);
-       read (fd, p, s);
-       p += s;
+       read (fd, (char *) (s->mm + 4), 32);
        close (fd);
       }
     else
@@ -410,52 +393,57 @@ isaac_seed (struct isaac_state *s)
        if (fd >= 0)
          {
            /* /dev/random is more precious, so use less */
-           size_t s = MIXIN_BOUND (16);
-           read (fd, p, s);
-           p += s;
+           read (fd, (char *) (s->mm + 4), 16);
            close (fd);
          }
       }
   }
 
-#ifdef HAVE_GETHRTIME
-  {
-    hrtime_t t = gethrtime ();
-    MIXIN (t);
-  }
-#endif
-
-#ifdef HAVE_CLOCK_GETTIME
-  {
-    struct timespec t;
-    clock_gettime (CLOCK_REALTIME, &t);
-    MIXIN (t);
-  }
-#endif
-
-  {
-    time_t t = time ((time_t *) 0);
-    MIXIN (t);
-  }
-
-  {
-    pid_t t = getpid ();
-    MIXIN (t);
-    t = getppid ();
-    MIXIN (t);
-  }
+  isaac_init (s, s->mm, sizeof (s->mm));
+}
 
-  {
-    uid_t t = getuid ();
-    MIXIN (t);
-  }
+/*
+ * Read up to "size" bytes from the given fd and use them as additional
+ * ISAAC seed material.  Returns the number of bytes actually read.
+ */
+static off_t
+isaac_seedfd (struct isaac_state *s, int fd, off_t size)
+{
+  off_t sizeleft = size;
+  size_t lim, soff;
+  ssize_t ssize;
+  int i;
+  word32 seed[ISAAC_WORDS];
 
-  {
-    gid_t t = getgid ();
-    MIXIN (t);
-  }
+  while (sizeleft)
+    {
+      lim = sizeof (seed);
+      if ((off_t) lim > sizeleft)
+       lim = (size_t) sizeleft;
+      soff = 0;
+      do
+       {
+         ssize = read (fd, (char *) seed + soff, lim - soff);
+       }
+      while (ssize > 0 && (soff += (size_t) ssize) < lim);
+      /* Mix in what was read */
+      if (soff)
+       {
+         /* Garbage after the sofff position is harmless */
+         for (i = 0; i < ISAAC_WORDS; i++)
+           s->mm[i] += seed[i];
+         isaac_mix (s, s->mm);
+         sizeleft -= soff;
+       }
+      if (ssize <= 0)
+       break;
+    }
+  /* Wipe the copy of the file in "seed" */
+  memset (seed, 0, sizeof (seed));
 
-  isaac_init (s, s->mm, sizeof (s->mm));
+  /* Final mix, as in isaac_init */
+  isaac_mix (s, s->mm);
+  return size - sizeleft;
 }
 
 /* Single-word RNG built on top of ISAAC */
@@ -581,6 +569,60 @@ flushstatus (void)
 }
 
 /*
+ * Get the size of a file that doesn't want to cooperate (such as a
+ * device) by doing a binary search for the last readable byte.  The size
+ * of the file is the least offset at which it is not possible to read
+ * a byte.
+ *
+ * This is also a nice example of using loop invariants to correctly
+ * implement an algorithm that is potentially full of fencepost errors.
+ * We assume that if it is possible to read a byte at offset x, it is
+ * also possible at all offsets <= x.
+ */
+static off_t
+sizefd (int fd)
+{
+  off_t hi, lo, mid;
+  char c;                      /* One-byte buffer for dummy reads */
+
+  /* Binary doubling upwards to find the right range */
+  lo = 0;
+  hi = 0;              /* Any number, preferably 2^x-1, is okay here. */
+
+  /*
+   * Loop invariant: we have verified that it is possible to read a
+   * byte at all offsets < lo.  Probe at offset hi >= lo until it
+   * is not possible to read a byte at that offset, establishing
+   * the loop invariant for the following loop.
+   */
+  for (;;)
+    {
+      if (lseek (fd, hi, SEEK_SET) == (off_t) -1
+         || read (fd, &c, 1) < 1)
+       break;
+      lo = hi + 1;             /* This preserves the loop invariant. */
+      hi += lo;                        /* Exponential doubling. */
+    }
+  /*
+   * Binary search to find the exact endpoint.
+   * Loop invariant: it is not possible to read a byte at hi,
+   * but it is possible at all offsets < lo.  Thus, the
+   * offset we seek is between lo and hi inclusive.
+   */
+  while (hi > lo)
+    {
+      mid = (hi + lo) / 2;     /* Rounded down, so lo <= mid < hi */
+      if (lseek (fd, mid, SEEK_SET) == (off_t) -1
+         || read (fd, &c, 1) < 1)
+       hi = mid;               /* mid < hi, so this makes progress */
+      else
+       lo = mid + 1;           /* Because mid < hi, lo <= hi */
+    }
+  /* lo == hi, so we have an exact answer */
+  return hi;
+}
+
+/*
  * Fill a buffer with a fixed pattern.
  *
  * The buffer must be at least 3 bytes long, even if
@@ -638,14 +680,13 @@ passname (unsigned char const *data, char name[PASS_NAME_SIZE])
  * Do pass number k of n, writing "size" bytes of the given pattern "type"
  * to the file descriptor fd.   Name, k and n are passed in only for verbose
  * progress message purposes.  If n == 0, no progress messages are printed.
- * If size is negative, write until we fall off the end.
  */
 static int
 dopass (int fd, char const *name, off_t size, int type,
        struct isaac_state *s, unsigned long k, unsigned long n)
 {
-  off_t off;                   /* Offset into file */
-  off_t thresh;                        /* Offset for next status update */
+  off_t cursize;               /* Amount of file remaining to wipe (counts down) */
+  off_t thresh;                        /* cursize at which next status update is printed */
   size_t lim;                  /* Amount of data to try writing */
   size_t soff;                 /* Offset into buffer for next write */
   ssize_t ssize;               /* Return value from write() */
@@ -656,9 +697,9 @@ dopass (int fd, char const *name, off_t size, int type,
 #endif
   char pass_string[PASS_NAME_SIZE];    /* Name of current pass */
 
-  if (lseek (fd, 0, SEEK_SET) == (off_t) -1)
+  if (lseek (fd, 0, SEEK_SET) < 0)
     {
-      error (0, errno, "%s: %s", quotearg_colon (name), _("cannot rewind"));
+      error (0, 0, _("Error seeking `%s'"), name);
       return -1;
     }
 
@@ -666,7 +707,7 @@ dopass (int fd, char const *name, off_t size, int type,
   if (type >= 0)
     {
       lim = sizeof (r);
-      if (0 <= size && (off_t) lim > size)
+      if ((off_t) lim > size)
        {
          lim = (size_t) size;
        }
@@ -682,17 +723,17 @@ dopass (int fd, char const *name, off_t size, int type,
   thresh = 0;
   if (n)
     {
-      pfstatus (_("%s: pass %lu/%lu (%s)...)"), quotearg_colon (name),
-               k, n, pass_string);
-      thresh = VERBOSE_UPDATE;
+      pfstatus (_("%s: pass %lu/%lu (%s)...)"), name, k, n, pass_string);
+      if (size > VERBOSE_UPDATE)
+       thresh = size - VERBOSE_UPDATE;
     }
 
-  for (off = 0; off < size || size < 0; )
+  for (cursize = size; cursize;)
     {
       /* How much to write this time? */
       lim = sizeof (r);
-      if (0 <= size && size - off < (off_t) lim)
-       lim = (size_t) (size - off);
+      if ((off_t) lim > cursize)
+       lim = (size_t) cursize;
       if (type < 0)
        fillrand (s, r, lim);
       /* Loop to retry partial writes. */
@@ -701,46 +742,38 @@ dopass (int fd, char const *name, off_t size, int type,
          ssize = write (fd, (char *) r + soff, lim - soff);
          if (ssize < 0)
            {
-             char buf[LONGEST_HUMAN_READABLE + 1];
-             if (size < 0 && errno == EIO)
-               {
-                 /* Now we know the file size, since we fell off the end.  */
-                 thresh = size = off + lim;
-                 break;
-               }
-             error (0, errno, _("%s: cannot write at offset %s"),
-                    quotearg_colon (name),
-                    human_readable ((uintmax_t) (off + soff),
-                                    buf, 1, 1));
+             int e = errno;
+             error (0, 0, _("Error writing `%s' at %lu"),
+                    name, size - cursize + soff);
+             /* FIXME: this is slightly fragile in that some systems
+                may fail with a different errno.  */
+             /* This error confuses people. */
+             if (e == EBADF && fd == 0)
+               fputs (_("(Did you remember to open stdin read/write with \"<>file\"?)\n"),
+                       stderr);
              return -1;
            }
        }
 
       /* Okay, we have written "lim" bytes. */
-      off += lim;
+      cursize -= lim;
 
       /* Time to print progress? */
-      if (thresh <= off && n)
+      if (cursize <= thresh && n)
        {
-         char offbuf[LONGEST_HUMAN_READABLE + 1];
-         char sizebuf[LONGEST_HUMAN_READABLE + 1];
-         pfstatus (_("%s: pass %lu/%lu (%s)...%s/%s"),
-                   quotearg_colon (name), k, n, pass_string,
-                   human_readable ((uintmax_t) off, offbuf, 1,
-                                   output_block_size),
-                   (size < 0
-                    ? "?"
-                    : human_readable ((uintmax_t) size, sizebuf, 1,
-                                      output_block_size)));
-         thresh += VERBOSE_UPDATE;
-         if (! (0 <= thresh && (thresh < size || size < 0)))
-           thresh = size;
+         pfstatus (_("%s: pass %lu/%lu (%s)...%lu/%lu K"),
+                   name, k, n, pass_string,
+                   (size - cursize + 1023) / 1024, (size + 1023) / 1024);
+         if (thresh > VERBOSE_UPDATE)
+           thresh -= VERBOSE_UPDATE;
+         else
+           thresh = 0;
        }
     }
   /* Force what we just wrote to hit the media. */
-  if (fdatasync (fd) < 0 && fsync (fd) < 0)
+  if (fdatasync (fd) < 0)
     {
-      error (0, errno, "%s: fsync", quotearg_colon (name));
+      error (0, 0, _("Error syncing `%s'"), name);
       return -1;
     }
   return 0;
@@ -944,73 +977,103 @@ genpattern (int *dest, size_t num, struct isaac_state *s)
 
 /*
  * The core routine to actually do the work.  This overwrites the first
- * size bytes of the given fd.  Returns -1 on error, 0 on success.
+ * size bytes of the given fd.  Returns -1 on error, 0 on success with
+ * regular files, and 1 on success with non-regular files.
  */
 static int
 wipefd (int fd, char const *name, struct isaac_state *s,
-       struct Options const *flags)
+       size_t passes, struct Options const *flags)
 {
   size_t i;
   struct stat st;
-  off_t size;                  /* Size to write; -1 if not known */
-  size_t passes = flags->n_iterations;
+  off_t size, seedsize;                /* Size to write, size to read */
   unsigned long n;             /* Number of passes for printing purposes */
+  int *passarray;
+
+  if (!passes)
+    passes = DEFAULT_PASSES;
 
   n = 0;               /* dopass takes n -- 0 to mean "don't print progress" */
   if (flags->verbose)
     n = passes + ((flags->zero_fill) != 0);
 
-  if (fstat (fd, &st) != 0)
+  if (fstat (fd, &st))
     {
-      error (0, errno, "%s: fstat", quotearg_colon (name));
+      error (0, 0, _("Can't fstat file `%s'"), name);
       return -1;
     }
 
-  size = S_ISREG (st.st_mode) ? st.st_size : (off_t) -1;
+  /* Check for devices */
+  if (!S_ISREG (st.st_mode) && !(flags->allow_devices))
+    {
+      error (0, 0,
+            _("`%s' is not a regular file: use -d to enable operations on devices"),
+            name);
+      return -1;
+    }
+
+  /* Allocate pass array */
+  passarray = malloc (passes * sizeof (int));
+  if (!passarray)
+    {
+      error (0, 0, _("unable to allocate storage for %lu passes"),
+            (unsigned long) passes);
+      return -1;
+    }
 
-  if (0 < size && 0 < st.st_blksize && !(flags->exact))
+  seedsize = size = st.st_size;
+  if (!size)
+    {
+      /* Reluctant to talk?  Apply thumbscrews. */
+      seedsize = size = sizefd (fd);
+    }
+  else if (st.st_blksize && !(flags->exact))
     {
       /* Round up to the next st_blksize to include "slack" */
       size += st.st_blksize - 1 - (size - 1) % st.st_blksize;
-      if (size < 0)
-       size = TYPE_MAXIMUM (off_t);
     }
 
-  if (passes)
+  /*
+   * Use the file itself as seed material.  Avoid wasting "lots"
+   * of time (>10% of the write time) reading "large" (>16K)
+   * files for seed material if there aren't many passes.
+   *
+   * Note that "seedsize*passes/10" risks overflow, while
+   * "seedsize/10*passes is slightly inaccurate.  The hack
+   * here manages perfection with no overflow.
+   */
+  if (passes < 10 && seedsize > 16384)
     {
-      /* Allocate pass array */
-      int *passarray = malloc (passes * sizeof (int));
-      if (!passarray)
-       {
-         error (0, 0, _("virtual memory exhausted"));
-         return -1;
-       }
+      seedsize -= 16384;
+      seedsize = seedsize / 10 * passes + seedsize % 10 * passes / 10;
+      seedsize += 16384;
+    }
+  (void) isaac_seedfd (s, fd, seedsize);
 
-      /* Schedule the passes in random order. */
-      genpattern (passarray, passes, s);
+  /* Schedule the passes in random order. */
+  genpattern (passarray, passes, s);
 
-      /* Do the work */
-      for (i = 0; i < passes; i++)
+  /* Do the work */
+  for (i = 0; i < passes; i++)
+    {
+      if (dopass (fd, name, size, passarray[i], s, i + 1, n) < 0)
        {
-         if (dopass (fd, name, size, passarray[i], s, i + 1, n) < 0)
-           {
-             memset (passarray, 0, passes * sizeof (int));
-             free (passarray);
-             return -1;
-           }
-         if (flags->verbose > 1)
-           flushstatus ();
+         memset (passarray, 0, passes * sizeof (int));
+         free (passarray);
+         return -1;
        }
-
-      memset (passarray, 0, passes * sizeof (int));
-      free (passarray);
+      if (flags->verbose > 1)
+       flushstatus ();
     }
 
+  memset (passarray, 0, passes * sizeof (int));
+  free (passarray);
+
   if (flags->zero_fill)
     if (dopass (fd, name, size, 0, s, passes + 1, n) < 0)
       return -1;
 
-  return 0;
+  return !S_ISREG (st.st_mode);
 }
 
 /* Characters allowed in a file name - a safe universal set. */
@@ -1057,9 +1120,9 @@ incname (char *name, unsigned len)
  * Repeatedly rename a file with shorter and shorter names,
  * to obliterate all traces of the file name on any system that
  * adds a trailing delimiter to on-disk file names and reuses
- * the same directory slot.  Finally, remove it.
+ * the same directory slot.  Finally, delete it.
  * The passed-in filename is modified in place to the new filename.
- * (Which is removed if this function succeeds, but is still present if
+ * (Which is deleted if this function succeeds, but is still present if
  * it fails for some reason.)
  *
  * The main loop is written carefully to not get stuck if all possible
@@ -1067,7 +1130,7 @@ incname (char *name, unsigned len)
  * the original to 0.  While the length is non-zero, it tries to find an
  * unused file name of the given length.  It continues until either the
  * name is available and the rename succeeds, or it runs out of names
- * to try (incname() wraps and returns 1).  Finally, it removes the file.
+ * to try (incname() wraps and returns 1).  Finally, it deletes the file.
  *
  * Note that rename() and remove() are both in the ANSI C standard,
  * so that part, at least, is NOT Unix-specific.
@@ -1078,7 +1141,7 @@ incname (char *name, unsigned len)
  * insist that it works, just fall back to a global sync() in that case.
  * Unfortunately, this code is Unix-specific.
  */
-static int
+int
 wipename (char *oldname, struct Options const *flags)
 {
   char *newname, *origname = 0;
@@ -1088,12 +1151,12 @@ wipename (char *oldname, struct Options const *flags)
   int dir_fd;                  /* Try to open directory to sync *it* */
 
   if (flags->verbose)
-    pfstatus (_("%s: removing"), quotearg_colon (oldname));
+    pfstatus (_("%s: deleting"), oldname);
 
   newname = strdup (oldname);  /* This is a malloc */
   if (!newname)
     {
-      error (0, 0, _("virtual memory exhausted"));
+      error (0, 0, _("malloc failed"));
       return -1;
     }
   if (flags->verbose)
@@ -1101,7 +1164,7 @@ wipename (char *oldname, struct Options const *flags)
       origname = strdup (oldname);
       if (!origname)
        {
-         error (0, 0, _("virtual memory exhausted"));
+         error (0, 0, _("malloc failed"));
          free (newname);
          return -1;
        }
@@ -1132,7 +1195,7 @@ wipename (char *oldname, struct Options const *flags)
          if (access (newname, F_OK) < 0
              && !rename (oldname, newname))
            {
-             if (dir_fd < 0 || (fdatasync (dir_fd) < 0 && fsync (dir_fd) < 0))
+             if (dir_fd < 0 || fdatasync (dir_fd) < 0)
                sync ();        /* Force directory out */
              if (origname)
                {
@@ -1140,8 +1203,7 @@ wipename (char *oldname, struct Options const *flags)
                     deliberate.  It makes the -v output more intelligible
                     at the expense of making the `renamed to ...' messages
                     use the logical (original) file name.  */
-                 pfstatus (_("%s: renamed to: %s"),
-                           quotearg_colon (origname), newname);
+                 pfstatus (_("%s: renamed to `%s'"), origname, newname);
                  if (flags->verbose > 1)
                    flushstatus ();
                }
@@ -1153,14 +1215,14 @@ wipename (char *oldname, struct Options const *flags)
       len--;
     }
   free (newname);
-  err = remove (oldname) ? errno : 0;
-  if (dir_fd < 0 || (fdatasync (dir_fd) < 0 && fsync (dir_fd) < 0))
+  err = remove (oldname);
+  if (dir_fd < 0 || fdatasync (dir_fd) < 0)
     sync ();
   close (dir_fd);
   if (origname)
     {
       if (!err && flags->verbose)
-       pfstatus (_("%s: removed"), quotearg_colon (origname));
+       pfstatus (_("%s: deleted"), origname);
       free (origname);
     }
   return err;
@@ -1168,65 +1230,43 @@ wipename (char *oldname, struct Options const *flags)
 
 /*
  * Finally, the function that actually takes a filename and grinds
- * it into hamburger.  Returns nonzero if there was an error.
+ * it into hamburger.  Returns 1 if it was not a regular file.
+ *
+ * FIXME
+ * Detail to note: since we do not restore errno to EACCES after
+ * a failed chmod, we end up printing the error code from the chmod.
+ * This is probably either EACCES again or EPERM, which both give
+ * reasonable error messages.  But it might be better to change that.
  */
 static int
-wipefile (char *name, struct isaac_state *s, struct Options const *flags)
+wipefile (char *name, struct isaac_state *s, size_t passes,
+         struct Options const *flags)
 {
-  int err = 0;
-  struct stat st;
-  int remove_link = flags->links == ALL_LINKS;
+  int err, fd;
 
-  if (flags->links == ORDINARY_LINKS
-      || (flags->contents == FREED_CONTENTS && remove_link))
+  fd = open (name, O_RDWR);
+  if (fd < 0 && errno == EACCES && flags->force)
     {
-      if (lstat (name, &st) != 0)
-       {
-         error (0, errno, "%s", quotearg_colon (name));
-         return -1;
-       }
-      if (flags->links == ORDINARY_LINKS)
-       remove_link = S_ISREG (st.st_mode) || S_ISLNK (st.st_mode);
+      if (chmod (name, 0600) >= 0)
+       fd = open (name, O_RDWR);
     }
-
-  if (flags->contents == ALL_CONTENTS
-      || (flags->contents == FREED_CONTENTS && remove_link
-         && !S_ISLNK (st.st_mode) && st.st_nlink <= 1))
+  if (fd < 0)
     {
-      int fd = open (name, O_WRONLY);
-      if (fd < 0)
-       {
-         if (errno == EACCES && flags->force)
-           {
-             if (chmod (name, S_IWUSR) != 0)
-               {
-                 error (0, errno, "%s: %s", quotearg_colon (name),
-                        _("cannot change permissions"));
-                 return -1;
-               }
-             fd = open (name, O_WRONLY);
-           }
-         if (fd < 0)
-           {
-             error (0, errno, "%s", quotearg_colon (name));
-             return -1;
-           }
-       }
-
-      err = wipefd (fd, name, s, flags);
-      if (close (fd) != 0)
-       {
-         error (0, errno, "%s: close", quotearg_colon (name));
-         return -1;
-       }
+      error (0, 0, _("Unable to open `%s'"), name);
+      return -1;
     }
 
-  if (err == 0 && remove_link)
+  err = wipefd (fd, name, s, passes, flags);
+  close (fd);
+  /*
+   * Wipe the name and unlink - regular files only, no devices!
+   * (wipefd returns 1 for non-regular files.)
+   */
+  if (err == 0 && flags->remove_file)
     {
       err = wipename (name, flags);
-      if (err != 0)
-       error (0, err < 0 ? 0 : err, "%s: %s", quotearg_colon (name),
-              _("cannot remove"));
+      if (err < 0)
+       error (0, 0, _("Unable to delete file `%s'"), name);
     }
   return err;
 }
@@ -1237,6 +1277,7 @@ main (int argc, char **argv)
   struct isaac_state s;
   int err = 0;
   struct Options flags;
+  unsigned long n_passes = 0;
   char **file;
   int n_files;
   int c;
@@ -1250,44 +1291,25 @@ main (int argc, char **argv)
   isaac_seed (&s);
 
   memset (&flags, 0, sizeof flags);
-  flags.contents = ALL_CONTENTS;
-  flags.n_iterations = DEFAULT_PASSES;
 
-  while ((c = getopt_long (argc, argv, "bcCfklLn:pvxz", long_opts, NULL)) != -1)
+  /* By default, remove each file after sanitization.  */
+  flags.remove_file = 1;
+
+  while ((c = getopt_long (argc, argv, "dfn:pvxz", long_opts, NULL)) != -1)
     {
       switch (c)
        {
        case 0:
          break;
 
-       case 'b':
-         flags.contents = NO_CONTENTS;
-         break;
-
-       case 'c':
-         flags.contents = FREED_CONTENTS;
-         break;
-
-       case 'C':
-         flags.contents = ALL_CONTENTS;
+       case 'd':
+         flags.allow_devices = 1;
          break;
 
        case 'f':
          flags.force = 1;
          break;
 
-       case 'k':
-         flags.links = NO_LINKS;
-         break;
-
-       case 'l':
-         flags.links = ORDINARY_LINKS;
-         break;
-
-       case 'L':
-         flags.links = ALL_LINKS;
-         break;
-
        case 'n':
          {
            unsigned long int tmp_ulong;
@@ -1295,12 +1317,17 @@ main (int argc, char **argv)
                || (word32) tmp_ulong != tmp_ulong
                || ((size_t) (tmp_ulong * sizeof (int)) / sizeof (int)
                    != tmp_ulong))
-             error (1, 0, "%s: %s", quotearg_colon (optarg),
-                    _("invalid number of passes"));
-           flags.n_iterations = tmp_ulong;
+             {
+               error (1, 0, _("invalid number of passes: %s"), optarg);
+             }
+           n_passes = tmp_ulong;
          }
          break;
 
+       case 'p':
+         flags.remove_file = 0;
+         break;
+
        case 'v':
          flags.verbose++;
          break;
@@ -1327,7 +1354,7 @@ main (int argc, char **argv)
 
   if (n_files == 0)
     {
-      error (0, 0, _("too few arguments"));
+      error (0, 0, _("missing file argument"));
       usage (1);
     }
 
@@ -1335,24 +1362,13 @@ main (int argc, char **argv)
     {
       if (STREQ (file[i], "-"))
        {
-         int fd_flags = fcntl (STDOUT_FILENO, F_GETFL);
-         if (fd_flags < 0)
-           {
-             error (0, errno, _("standard output"));
-             err = 1;
-           }
-         else if ((fd_flags & O_APPEND) != 0)
-           {
-             error (0, 0, _("cannot shred append-only standard output"));
-             err = 1;
-           }
-         else if (wipefd (STDOUT_FILENO, file[i], &s, &flags) < 0)
+         if (wipefd (0, file[i], &s, (size_t) n_passes, &flags) < 0)
            err = 1;
        }
       else
        {
          /* Plain filename - Note that this overwrites *argv! */
-         if (wipefile (file[i], &s, &flags) < 0)
+         if (wipefile (file[i], &s, (size_t) n_passes, &flags) < 0)
            err = 1;
        }
       flushstatus ();