summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/shred.c512
1 files changed, 236 insertions, 276 deletions
diff --git a/src/shred.c b/src/shred.c
index 05ff72155..198a80759 100644
--- a/src/shred.c
+++ b/src/shred.c
@@ -8,7 +8,7 @@
/*
* shred.c - by Colin Plumb.
*
- * Do a secure overwrite of given files or devices, so that not even
+ * Do a more-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 deletes it, renaming it to in a
+ * If asked to wipe a file, this also removes 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,6 +64,8 @@
#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). */
@@ -79,22 +81,31 @@
/* FIXME: add comments */
struct Options
{
- int allow_devices;
+ enum { NO_CONTENTS, FREED_CONTENTS, ALL_CONTENTS } contents;
+ enum { NO_LINKS, ORDINARY_LINKS, ALL_LINKS } links;
int force;
- unsigned int n_iterations;
- int remove_file;
+ size_t n_iterations;
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[] =
{
- {"device", no_argument, NULL, 'd'},
+ {"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'},
{"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},
@@ -105,6 +116,8 @@ 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)
{
@@ -115,19 +128,22 @@ usage (int status)
{
printf (_("Usage: %s [OPTIONS] FILE [...]\n"), program_name);
printf (_("\
-Delete a file securely, first overwriting it to hide its contents.\n\
+Overwrite a file to hide its contents.\n\
\n\
- -d, --device allow operation on devices (devices are never deleted)\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\
-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 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\
+ - shred standard output (but don't remove it)\n\
+ --help display this help and exit\n\
+ --version print version information and exit\n\
\n\
FIXME maybe add more discussion here?\n\
"));
@@ -138,7 +154,7 @@ FIXME maybe add more discussion here?\n\
}
#if ! HAVE_FDATASYNC
-# define fdatasync(Fd) fsync (Fd)
+# define fdatasync(fd) (-1)
#endif
/*
@@ -363,28 +379,42 @@ isaac_init (struct isaac_state *s, word32 const *seed, size_t seedsize)
static void
isaac_seed (struct isaac_state *s)
{
- s->mm[0] = getpid ();
- s->mm[1] = getppid ();
+ char *p = (char *) s->mm;
+#define MIXIN(o) (memcpy (p, (char *) &(o), sizeof (o)), p += sizeof (o))
+
+ {
+ pid_t t = getpid ();
+ MIXIN (t);
+ t = getppid ();
+ MIXIN (t);
+ }
+
+ {
+ uid_t t = getuid ();
+ MIXIN (t);
+ }
+
+ {
+ gid_t t = getgid ();
+ MIXIN (t);
+ }
{
#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;
+ struct timespec t;
+ clock_gettime (CLOCK_REALTIME, &t);
#else
- struct timeval tv;
- gettimeofday (&tv, (struct timezone *) 0);
- s->mm[2] = tv.tv_sec;
- s->mm[3] = tv.tv_usec;
+ time_t t = time ((time_t *) 0);
#endif
+ MIXIN (t);
}
{
+ int r = 0;
int fd = open ("/dev/urandom", O_RDONLY);
if (fd >= 0)
{
- read (fd, (char *) (s->mm + 4), 32);
+ r = read (fd, p, 32);
close (fd);
}
else
@@ -393,59 +423,17 @@ isaac_seed (struct isaac_state *s)
if (fd >= 0)
{
/* /dev/random is more precious, so use less */
- read (fd, (char *) (s->mm + 4), 16);
+ r = read (fd, p, 16);
close (fd);
}
}
+ if (0 < r)
+ p += r;
}
isaac_init (s, s->mm, sizeof (s->mm));
}
-/*
- * 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];
-
- 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));
-
- /* Final mix, as in isaac_init */
- isaac_mix (s, s->mm);
- return size - sizeleft;
-}
-
/* Single-word RNG built on top of ISAAC */
struct irand_state
{
@@ -569,60 +557,6 @@ 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
@@ -680,13 +614,14 @@ 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 cursize; /* Amount of file remaining to wipe (counts down) */
- off_t thresh; /* cursize at which next status update is printed */
+ off_t off; /* Offset into file */
+ off_t thresh; /* Offset for next status update */
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() */
@@ -697,9 +632,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) < 0)
+ if (lseek (fd, 0, SEEK_SET) == (off_t) -1)
{
- error (0, 0, _("Error seeking `%s'"), name);
+ error (0, errno, "%s: %s", quotearg_colon (name), _("cannot rewind"));
return -1;
}
@@ -707,7 +642,7 @@ dopass (int fd, char const *name, off_t size, int type,
if (type >= 0)
{
lim = sizeof (r);
- if ((off_t) lim > size)
+ if (0 <= size && (off_t) lim > size)
{
lim = (size_t) size;
}
@@ -723,17 +658,17 @@ dopass (int fd, char const *name, off_t size, int type,
thresh = 0;
if (n)
{
- pfstatus (_("%s: pass %lu/%lu (%s)...)"), name, k, n, pass_string);
- if (size > VERBOSE_UPDATE)
- thresh = size - VERBOSE_UPDATE;
+ pfstatus (_("%s: pass %lu/%lu (%s)...)"), quotearg_colon (name),
+ k, n, pass_string);
+ thresh = VERBOSE_UPDATE;
}
- for (cursize = size; cursize;)
+ for (off = 0; off < size || size < 0; )
{
/* How much to write this time? */
lim = sizeof (r);
- if ((off_t) lim > cursize)
- lim = (size_t) cursize;
+ if (0 <= size && size - off < (off_t) lim)
+ lim = (size_t) (size - off);
if (type < 0)
fillrand (s, r, lim);
/* Loop to retry partial writes. */
@@ -742,38 +677,46 @@ dopass (int fd, char const *name, off_t size, int type,
ssize = write (fd, (char *) r + soff, lim - soff);
if (ssize < 0)
{
- 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);
+ 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));
return -1;
}
}
/* Okay, we have written "lim" bytes. */
- cursize -= lim;
+ off += lim;
/* Time to print progress? */
- if (cursize <= thresh && n)
+ if (thresh <= off && n)
{
- 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;
+ 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;
}
}
/* Force what we just wrote to hit the media. */
- if (fdatasync (fd) < 0)
+ if (fdatasync (fd) < 0 && fsync (fd) < 0)
{
- error (0, 0, _("Error syncing `%s'"), name);
+ error (0, errno, "%s: fsync", quotearg_colon (name));
return -1;
}
return 0;
@@ -977,103 +920,73 @@ 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 with
- * regular files, and 1 on success with non-regular files.
+ * size bytes of the given fd. Returns -1 on error, 0 on success.
*/
static int
wipefd (int fd, char const *name, struct isaac_state *s,
- size_t passes, struct Options const *flags)
+ struct Options const *flags)
{
size_t i;
struct stat st;
- off_t size, seedsize; /* Size to write, size to read */
+ off_t size; /* Size to write; -1 if not known */
+ size_t passes = flags->n_iterations;
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))
- {
- error (0, 0, _("Can't fstat file `%s'"), name);
- return -1;
- }
-
- /* Check for devices */
- if (!S_ISREG (st.st_mode) && !(flags->allow_devices))
+ if (fstat (fd, &st) != 0)
{
- error (0, 0,
- _("`%s' is not a regular file: use -d to enable operations on devices"),
- name);
+ error (0, errno, "%s: fstat", quotearg_colon (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;
- }
+ size = S_ISREG (st.st_mode) ? st.st_size : (off_t) -1;
- seedsize = size = st.st_size;
- if (!size)
- {
- /* Reluctant to talk? Apply thumbscrews. */
- seedsize = size = sizefd (fd);
- }
- else if (st.st_blksize && !(flags->exact))
+ if (0 < size && 0 < 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);
}
- /*
- * 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)
+ if (passes)
{
- seedsize -= 16384;
- seedsize = seedsize / 10 * passes + seedsize % 10 * passes / 10;
- seedsize += 16384;
- }
- (void) isaac_seedfd (s, fd, seedsize);
+ /* Allocate pass array */
+ int *passarray = malloc (passes * sizeof (int));
+ if (!passarray)
+ {
+ error (0, 0, _("virtual memory exhausted"));
+ return -1;
+ }
- /* 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++)
- {
- if (dopass (fd, name, size, passarray[i], s, i + 1, n) < 0)
+ /* Do the work */
+ for (i = 0; i < passes; i++)
{
- memset (passarray, 0, passes * sizeof (int));
- free (passarray);
- return -1;
+ 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 ();
}
- if (flags->verbose > 1)
- flushstatus ();
- }
- memset (passarray, 0, passes * sizeof (int));
- free (passarray);
+ 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 !S_ISREG (st.st_mode);
+ return 0;
}
/* Characters allowed in a file name - a safe universal set. */
@@ -1120,9 +1033,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, delete it.
+ * the same directory slot. Finally, remove it.
* The passed-in filename is modified in place to the new filename.
- * (Which is deleted if this function succeeds, but is still present if
+ * (Which is removed 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
@@ -1130,7 +1043,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 deletes the file.
+ * to try (incname() wraps and returns 1). Finally, it removes the file.
*
* Note that rename() and remove() are both in the ANSI C standard,
* so that part, at least, is NOT Unix-specific.
@@ -1141,7 +1054,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.
*/
-int
+static int
wipename (char *oldname, struct Options const *flags)
{
char *newname, *origname = 0;
@@ -1151,12 +1064,12 @@ wipename (char *oldname, struct Options const *flags)
int dir_fd; /* Try to open directory to sync *it* */
if (flags->verbose)
- pfstatus (_("%s: deleting"), oldname);
+ pfstatus (_("%s: removing"), quotearg_colon (oldname));
newname = strdup (oldname); /* This is a malloc */
if (!newname)
{
- error (0, 0, _("malloc failed"));
+ error (0, 0, _("virtual memory exhausted"));
return -1;
}
if (flags->verbose)
@@ -1164,7 +1077,7 @@ wipename (char *oldname, struct Options const *flags)
origname = strdup (oldname);
if (!origname)
{
- error (0, 0, _("malloc failed"));
+ error (0, 0, _("virtual memory exhausted"));
free (newname);
return -1;
}
@@ -1195,7 +1108,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)
+ if (dir_fd < 0 || (fdatasync (dir_fd) < 0 && fsync (dir_fd) < 0))
sync (); /* Force directory out */
if (origname)
{
@@ -1203,7 +1116,8 @@ 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'"), origname, newname);
+ pfstatus (_("%s: renamed to: %s"),
+ quotearg_colon (origname), newname);
if (flags->verbose > 1)
flushstatus ();
}
@@ -1215,14 +1129,14 @@ wipename (char *oldname, struct Options const *flags)
len--;
}
free (newname);
- err = remove (oldname);
- if (dir_fd < 0 || fdatasync (dir_fd) < 0)
+ err = remove (oldname) ? errno : 0;
+ if (dir_fd < 0 || (fdatasync (dir_fd) < 0 && fsync (dir_fd) < 0))
sync ();
close (dir_fd);
if (origname)
{
if (!err && flags->verbose)
- pfstatus (_("%s: deleted"), origname);
+ pfstatus (_("%s: removed"), quotearg_colon (origname));
free (origname);
}
return err;
@@ -1230,43 +1144,65 @@ wipename (char *oldname, struct Options const *flags)
/*
* Finally, the function that actually takes a filename and grinds
- * 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.
+ * it into hamburger. Returns nonzero if there was an error.
*/
static int
-wipefile (char *name, struct isaac_state *s, size_t passes,
- struct Options const *flags)
+wipefile (char *name, struct isaac_state *s, struct Options const *flags)
{
- int err, fd;
+ int err = 0;
+ struct stat st;
+ int remove_link = flags->links == ALL_LINKS;
- fd = open (name, O_RDWR);
- if (fd < 0 && errno == EACCES && flags->force)
+ if (flags->links == ORDINARY_LINKS
+ || (flags->contents == FREED_CONTENTS && remove_link))
{
- if (chmod (name, 0600) >= 0)
- fd = open (name, O_RDWR);
+ 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 (fd < 0)
+
+ if (flags->contents == ALL_CONTENTS
+ || (flags->contents == FREED_CONTENTS && remove_link
+ && !S_ISLNK (st.st_mode) && st.st_nlink <= 1))
{
- error (0, 0, _("Unable to open `%s'"), name);
- return -1;
+ 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;
+ }
}
- 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)
+ if (err == 0 && remove_link)
{
err = wipename (name, flags);
- if (err < 0)
- error (0, 0, _("Unable to delete file `%s'"), name);
+ if (err != 0)
+ error (0, err < 0 ? 0 : err, "%s: %s", quotearg_colon (name),
+ _("cannot remove"));
}
return err;
}
@@ -1277,7 +1213,6 @@ 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;
@@ -1291,25 +1226,44 @@ main (int argc, char **argv)
isaac_seed (&s);
memset (&flags, 0, sizeof flags);
+ flags.contents = ALL_CONTENTS;
+ flags.n_iterations = DEFAULT_PASSES;
- /* By default, remove each file after sanitization. */
- flags.remove_file = 1;
-
- while ((c = getopt_long (argc, argv, "dfn:pvxz", long_opts, NULL)) != -1)
+ while ((c = getopt_long (argc, argv, "bcCfklLn:pvxz", long_opts, NULL)) != -1)
{
switch (c)
{
case 0:
break;
- case 'd':
- flags.allow_devices = 1;
+ case 'b':
+ flags.contents = NO_CONTENTS;
+ break;
+
+ case 'c':
+ flags.contents = FREED_CONTENTS;
+ break;
+
+ case 'C':
+ flags.contents = ALL_CONTENTS;
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;
@@ -1317,17 +1271,12 @@ main (int argc, char **argv)
|| (word32) tmp_ulong != tmp_ulong
|| ((size_t) (tmp_ulong * sizeof (int)) / sizeof (int)
!= tmp_ulong))
- {
- error (1, 0, _("invalid number of passes: %s"), optarg);
- }
- n_passes = tmp_ulong;
+ error (1, 0, "%s: %s", quotearg_colon (optarg),
+ _("invalid number of passes"));
+ flags.n_iterations = tmp_ulong;
}
break;
- case 'p':
- flags.remove_file = 0;
- break;
-
case 'v':
flags.verbose++;
break;
@@ -1354,7 +1303,7 @@ main (int argc, char **argv)
if (n_files == 0)
{
- error (0, 0, _("missing file argument"));
+ error (0, 0, _("too few arguments"));
usage (1);
}
@@ -1362,13 +1311,24 @@ main (int argc, char **argv)
{
if (STREQ (file[i], "-"))
{
- if (wipefd (0, file[i], &s, (size_t) n_passes, &flags) < 0)
+ 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)
err = 1;
}
else
{
/* Plain filename - Note that this overwrites *argv! */
- if (wipefile (file[i], &s, (size_t) n_passes, &flags) < 0)
+ if (wipefile (file[i], &s, &flags) < 0)
err = 1;
}
flushstatus ();