summaryrefslogtreecommitdiff
path: root/src/shred.c
diff options
context:
space:
mode:
authorJim Meyering <jim@meyering.net>1999-04-04 02:34:49 +0000
committerJim Meyering <jim@meyering.net>1999-04-04 02:34:49 +0000
commit8fe009ed7cad1b3552f06e119c1e6b2a28c41dbc (patch)
tree49e2adfec9db1acc76c7bfa575f9b36524fee6cc /src/shred.c
parentc21cecaea84fb2d86a2873d23eb8e6d92a0bdd65 (diff)
downloadcoreutils-8fe009ed7cad1b3552f06e119c1e6b2a28c41dbc.tar.xz
back out Paul's changes
Diffstat (limited to 'src/shred.c')
-rw-r--r--src/shred.c544
1 files changed, 280 insertions, 264 deletions
diff --git a/src/shred.c b/src/shred.c
index f747a7b7f..05ff72155 100644
--- a/src/shred.c
+++ b/src/shred.c
@@ -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). */
@@ -81,31 +79,22 @@
/* 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 ();