summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJim Meyering <jim@meyering.net>1999-04-03 03:27:27 +0000
committerJim Meyering <jim@meyering.net>1999-04-03 03:27:27 +0000
commitbf504d2a46f45f7482edcc425a4a76b56548e9d5 (patch)
tree49234c849faa057e778f11aeab46d298a58d1750
parentce9c687d5aef3cbe5e23ab18a06aa13bdd5bbfcd (diff)
downloadcoreutils-bf504d2a46f45f7482edcc425a4a76b56548e9d5.tar.xz
Add new options -bcCklL and fix some porting problems.
Remove options -dp. Do not read output files. (long_opts, usage, main, wipefile): Adjust to new options. ("human.h", "quotearg.h"): New includes. (struct Options): New members contents, links, n_iterations. Remove allow_devices, remove_file. Change n_iterations to size_t. All uses changed. (output_block_size): New var. (usage): Declare __noreturn__ attribute. (fdatasync): Define to -1 if not present, since we need to invoke both fdatasync and fsync if both are present. All invokers of fdatasync now try fdatasync, then fsync. (MIXIN): New macro. (isaac_seed): Use it to mix in values. Add uid, gid to mix. Don't use gettimeofday, as it has too many porting problems. (isaac_seedfd): Remove, since we no longer read the output files. (sizefd): Remove; we now determine size by writing sequentially. (dopass, wipename, wipefile, main): Clean up error messages. (dopass): Keep track of offset relative to start of file, not end, since we may not know how large the file is. If size is negative, write until we fall off the end of the file. (wipefd): Do not read output file. Return 0 if successful, -1 if not; do not make a special case for non-regular files, since our callers have that info now. (wipename): Now static. Return errno if error. (main): "-" now stands for standard output. Do not shred append-only standard output. (wipefile): Do not grant read permission to file when wiping it. Use symbolic permission (S_IWUSR), not octal. From Paul Eggert.
-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 ();