summaryrefslogtreecommitdiff
path: root/src/shred.c
diff options
context:
space:
mode:
authorJim Meyering <jim@meyering.net>1999-04-04 04:37:39 +0000
committerJim Meyering <jim@meyering.net>1999-04-04 04:37:39 +0000
commitf381610dd5865f69e4ae713eba0d4643d42f2224 (patch)
tree1dafec0a9f1ec52edda860c7c25a40a1bb73cf64 /src/shred.c
parent034df8db205daaa0485f42ece367255f3daffb87 (diff)
downloadcoreutils-f381610dd5865f69e4ae713eba0d4643d42f2224.tar.xz
[!HAVE_CONFIG_H] (xstrtoul, error, close_stdout): Added stubs
to allow standalone compilation. (wipefile): Added support for emulating /dev/fd/# files even if the OS doesn't support them. From Paul Eggert. (main, usage): Changed --device short option to -D. (wipefd, do_wipefd): Renamed function to do_wipefd and added separate wipefd that performs sanity checks on externally-opened file descriptors, such as not append-only. From Paul Eggert. (do_wipefd, isaac_seedfd): Do not read file for any reason. if the file is low-entropy, it's a security hole. (wipefile) Changed to open O_WRONLY and chmod to write-only when forcing. (isaac_seedfd) Function deleted as unnecessary. From Paul Eggert. (dopass): Dynamically fall back to fsync() if fdatasync() fails, since POSIX, in their infinitesimal wisdom, encourage implementations that return constant -1, making compile-time testing useless. From Paul Eggert. (dopass): Changed to support a size of -1 to mean "unknown". This entailed changing to a counting-up offset rather than couting-down cursize for the central state variable. Also changed size argument to be call-by-reference so that it can be passed back once known. (sizer) Function deleted as unnecessary. (wipefd): Changed to match. From Paul Eggert (dopass): Try to skip over bad blocks in destination files. Also added ftruncate() for more complete destruction of metadata. (main, usage): Changed "-" to stand for standard output. (wipefd): Added error message to detect conflict with -v. (dopass): Added periodic fsync() calls to keep the pass progress display in sync with reality. Hopefully they're sufficiently far spaced that throughput isn't affected. It might be a good thing to do even in non-verbose mode, to avoid filling up the kernel caches with dirty data. Also added ftruncate() for more complete destruction of metadata. (quotearg_colon): New function to print pathological filenames properly. [!HAVE_CONFIG_H] (quotearg_colon_buf) New internal helper function that does most of the work. (wipefd, do_wipefd, dopass) Now take a qname (pre-quoted name) argument. (wipename, wipefile, main) Changed diagnostics to use quotearg_colon. Error messages are also in a more uniform format. From Paul Eggert. (struct Options, main, do_wipefd): Added -s/--size=N flag. (xstrtoul): Added support for valid_suffixes to help this. (usage) Documented it. (error): Changed some arguments from N_() to _(), since error() does not translate its argument. I think this is a bug. (struct Options do_wipefd, wipefd, wipefile, main): moved passes argument into the Options structure as n_iterations, which is now a size_t. From Paul Eggert. (isaac_seed_start, isaac_seed_data, isaac_seed_finish): New functions to manage seeding of RNG with arbitrary-sized data. (isaac_init): commented out as dead code. (isaac_seed): changed to use new functions to prevent any possibility of a buffer overflow. (isaac_seed): Added support for Solaris' gethrtime() configure.in: Corresponding feature test. From Paul Eggert. (wipename): Change remove() to unlink() for speed & portability. Use lstat() instead of access() to see if a filename is taken. This works even on dangling symlinks and avoids the suid problems of access(2). From Paul Eggert. (isaac_seed_machdep): New function for reading cycle counters
Diffstat (limited to 'src/shred.c')
-rw-r--r--src/shred.c1066
1 files changed, 764 insertions, 302 deletions
diff --git a/src/shred.c b/src/shred.c
index 05ff72155..e129f47d7 100644
--- a/src/shred.c
+++ b/src/shred.c
@@ -3,6 +3,20 @@
leave it in and see who complains
- use consistent non-capitalization in error messages
- add standard GNU copyleft comment
+
+ - Add -s --size=N option, to force a size. I want this for wiping
+ damaged floppies that would otherwise bobm out at the first I/O
+ error. It should take -k and -M
+ - Add -r/-R/--recursive
+ - Add -i/--interactive
+ - Reserve -d
+ - Add -L
+ - Move the _() into error() and replace it with N() everywhere.
+ Error messages are by definition not performance-critical.
+ - Deal with the amazing variety of gettimeofday() implementation bugs.
+ (Sone systems use a one-arg form; still others insist that the timezone
+ either be NULL or be non-NULL. Whee.)
+ - Add an unlink-all option to emulate rm.
*/
/*
@@ -34,11 +48,11 @@
* 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 unlinks 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
- * be freely distributed under the terms of the GNU GPL, the BSD license,
+ * Copyright 1997, 1998, 1999 Colin Plumb <colin@nyx.net>. This program
+ * may be freely distributed under the terms of the GNU GPL, the BSD license,
* or Larry Wall's "Artistic License" Even if you use the BSD license,
* which does not require it, I'd really like to get improvements back.
*
@@ -50,51 +64,363 @@
* but I've rewritten everything here so completely that no trace of
* the original remains.
*
+ * Thanks to:
+ * Bob Jenkins, for his good RNG work and patience with the FSF copyright
+ * paperwork.
+ * Jim Meyering, for his work merging this into the GNU fileutils while
+ * still letting me feel a sense of ownership and pride. Getting me to
+ * tolerate the GNU brace style was quite a feat of diplomacy.
+ * Paul Eggert, for lots of useful discussion and code. I disagree with
+ * an awful lot of his suggestions, but they're disagreements worth having.
+ *
* Things to think about:
* - Security: Is there any risk to the race
* between overwriting and unlinking a file? Will it do anything
- * drastically bad if told to attack a named pipes or a sockets?
+ * drastically bad if told to attack a named pipe or socket?
*/
+/* The official name of this program (e.g., no `g' prefix). */
+#define PROGRAM_NAME "shred"
+
+#define AUTHORS "Colin Plumb"
-#include <config.h>
#include <getopt.h>
#include <stdio.h>
+#include <string.h> /* For memcpy(), strerror() */
#include <stdarg.h> /* Used by pferror */
+#include <setjmp.h>
+#include <signal.h>
-#include "system.h"
-#include "error.h"
-#include "xstrtoul.h"
+#if HAVE_CONFIG_H
+/* Default fileutils build */
+# include <config.h>
-/* The official name of this program (e.g., no `g' prefix). */
-#define PROGRAM_NAME "shred"
+# include "system.h"
+# include "xstrtoul.h"
+# include "closeout.h"
+# include "error.h"
+# include "quotearg.h" /* For quotearg_colon */
-#define AUTHORS "Colin Plumb"
+/*
+ * error.h #defines __attribute__, but this is safer, because someone else's
+ * preprocessor could decide that it would make a great reserved word.
+ */
+# if __GNUC__ < 2
+# define attribute(x)
+# else
+# define attribute __attribute__
+# endif
+
+#else /* !HAVE_CONFIG_H */
+/*
+ * Standalone build - this file compiles by itself without autoconf and
+ * the like. No i18n, and I still have to write a stub for getopt_long,
+ * but it's a lot less intertwingled than the usual GNU utilities.
+ */
+
+# include <limits.h> /* For ULONG_MAX etc. */
+# include <stdlib.h> /* For strtoul, EXIT_FAILURE */
+# include <errno.h>
+# include <fcntl.h> /* For O_RDONLY etc. */
+# include <unistd.h> /* For getpid(), etc. */
+# include <sys/time.h> /* For struct timeval */
+# include <sys/stat.h> /* For struct stat */
+
+# define GNU_PACKAGE "standalone"
+# define VERSION "2.0" /* Kind of arbitrary... */
+
+# if __GNUC__ < 2 || __GNUC__ == 2 && __GNUC_MINOR__ < 5 || __STRICT_ANSI__
+# define attribute(x)
+# else
+# define attribute __attribute__
+# if __GNUC__ == 2 && __GNUC_MINOR__ < 7
+ /* The __-protected forms were introduced in GCC 2.6.4 */
+# define __format__ format
+# define __printf__ printf
+# endif
+# endif
+
+/* Reasonable default assumptions for time-getting */
+# ifndef HAVE_GETTIMEOFDAY
+# define HAVE_GETTIMEOFDAY 1 /* Most systems have it these days */
+# endif
+
+# ifdef CLOCK_REALTIME
+# ifndef HAVE_CLOCK_GETTIME
+# define HAVE_CLOCK_GETTIME 1
+# endif
+# endif
+
+# ifndef O_NOCTTY
+# define O_NOCTTY 0 /* This is a very optional frill */
+# endif
+# ifndef S_IWUSR
+# ifdef S_IWRITE
+# define S_IWUSR S_IWRITE
+# else
+# define S_IWUSR 0200
+# endif
+# endif
+
+/* Variant convert-to-unsigned-long function that accepts metric suffixes */
+enum strtol_error
+ {
+ LONGINT_OK, LONGINT_INVALID, LONGINT_INVALID_SUFFIX_CHAR, LONGINT_OVERFLOW
+ };
+static int
+xstrtoul(char const *ptr, char const **end, int base, unsigned long *res,
+ char const *valid_suffixes)
+{
+ char *end_ptr;
+ char const *p;
+ static char const metric_suffixes[] = "kMGTPEZY";
+ int decimal_flag;
+ unsigned long n;
+ char c;
+
+ errno = 0;
+ *res = n = strtoul(ptr, &end_ptr, base);
+ if (end)
+ *end = end_ptr;
+ if (errno)
+ return LONGINT_OVERFLOW;
+ if (ptr == end_ptr)
+ return LONGINT_INVALID;
+ c = *end_ptr;
+ if (!c)
+ return LONGINT_OK;
+ /* Now deal with metric-style suffixes */
+ if (!valid_suffixes)
+ valid_suffixes = "";
+ if (!strchr (valid_suffixes, c))
+ return LONGINT_INVALID_SUFFIX_CHAR;
+
+ decimal_flag = 0;
+ switch (c)
+ {
+ case 'b':
+ if (n > ULONG_MAX/512)
+ return LONGINT_OVERFLOW;
+ n *= 512;
+ break;
+
+ case 'B':
+ if (n > ULONG_MAX/102412)
+ return LONGINT_OVERFLOW;
+ n *= 1024;
+ break;
+
+ case 'c':
+ break;
+
+ case 'K':
+ c = 'k';
+ goto def;
+
+ case 'm':
+ c = 'M';
+ /*FALLTHROUGH*/
+def:default:
+ p = strchr (metric_suffixes, c);
+ if (!p)
+ return LONGINT_INVALID_SUFFIX_CHAR;
+ /*
+ * If valid_suffixes contains '0', then xD (decimal) and xB (binary)
+ * are allowed as "supersuffixes". Binary is the default.
+ */
+ if (strchr (valid_suffixes, '0'))
+ {
+ if (end_ptr[1] == 'B')
+ end_ptr++;
+ else if (end_ptr[1] == 'D')
+ {
+ decimal_flag = 1;
+ end_ptr++;
+ }
+ }
+ /* Now do the scaling */
+ p++;
+ if (decimal_flag)
+ do {
+ if (n > ULONG_MAX/1000)
+ return LONGINT_OVERFLOW;
+ n *= 1000;
+ } while (--p > metric_suffixes);
+ else
+ do {
+ if (n > ULONG_MAX/1024)
+ return LONGINT_OVERFLOW;
+ n *= 1024;
+ } while (--p > metric_suffixes);
+ }
+
+ /* Final wrapup */
+ if (end)
+ *end = end_ptr+1; /* Extra suffix is allowed if it's expected */
+ else if (end_ptr[1])
+ return LONGINT_INVALID_SUFFIX_CHAR;
+ *res = n;
+ return LONGINT_OK;
+}
+
+/* Dummy i18n stubs */
+# define _(x) x
+# define N_(x) x
+# define setlocale(x,y) (void)0
+# define bindtextdomain(x,y) (void)0
+# define textdomain(x) (void)0
+
+/*
+ * Print a message with `fprintf (stderr, FORMAT, ...)';
+ * if ERRNUM is nonzero, follow it with ": " and strerror (ERRNUM).
+ * If STATUS is nonzero, terminate the program with `exit (STATUS)'.
+ */
+static void error (int status, int errnum, const char *format, ...)
+ attribute ((__format__ (__printf__, 3, 4)));
+
+static void flushstatus (void);
+extern char const *program_name;
+static void
+error (int status, int errnum, const char *format, ...)
+{
+ va_list ap;
+
+ if (!errnum)
+ errnum = errno;
+
+ flushstatus(); /* Make it look pretty */
+
+ if (program_name)
+ {
+ fputs(program_name, stderr);
+ fputs(": ", stderr);
+ }
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+ if (errnum)
+ {
+ fputs(": ", stderr);
+ fputs(strerror(errnum), stderr);
+ }
+ putc('\n', stderr);
+
+ if (status)
+ exit(status);
+}
+
+/*
+ * GNU programs actually check for failure closing standard output.
+ * This seems unnecessary, until your shell script starts hitting
+ * ENOSPC and doing bizarre things with zero-length files.
+ */
+static void
+close_stdout (void)
+{
+ if (ferror (stdout))
+ error (EXIT_FAILURE, 0, _("write error"));
+ if (fclose (stdout) != 0)
+ error (EXIT_FAILURE, errno, _("write error"));
+}
+
+/*
+ * Quote the argument (including colon characters) into the buffer.
+ * Return the buffer size used (including trailing null byte.)
+ * If this is larger than the bufsize, it is an estimate of the space
+ * needed.
+ */
+static size_t
+quotearg_colon_buf(char const *arg, char *buf, size_t bufsize)
+{
+ /* Some systems don't have \a or \e, so this is ASCII-dependent */
+ static char const escaped[] = "\7\b\33\f\n\r\t\v";
+ static char const escapes[] = "abefnrtv";
+ int c;
+ size_t pos = 0;
+ char const *p;
+
+ while ((c = (unsigned char)*arg++) != 0)
+ {
+ if (isprint(c))
+ {
+ if (strchr("\\:", c)) /* Anything else we should quote? */
+ if (pos++ < bufsize) *buf++ = '\\';
+ }
+ else
+ {
+ if (pos++ < bufsize) *buf++ = '\\';
+ p = strchr(escaped, c); /* c is never 0, so this is okay */
+ if (p)
+ {
+ c = escapes[p-escaped];
+ }
+ else
+ {
+ if (isdigit((unsigned char)*arg))
+ c += 256; /* Force 3-digit form if followed by a digit */
+ if (c > 077)
+ if (pos++ < bufsize) *buf++ = "0123"[c>>6 & 3];
+ if (c > 07)
+ if (pos++ < bufsize) *buf++ = "01234567"[c>>3 & 7];
+ c = "01234567"[c & 7];
+ }
+ }
+ if (pos++ < bufsize) *buf++ = c;
+ }
+ if (pos++ < bufsize) *buf++ = 0;
+ return pos;
+}
+
+/* Quote metacharacters in a filename */
+char const *
+quotearg_colon(char const *arg)
+{
+ static char *buf = 0;
+ size_t bufsize = 0;
+ size_t newsize;
+
+ while ((newsize = quotearg_colon_buf(arg, buf, bufsize)) > bufsize)
+ {
+ buf = realloc(buf, newsize);
+ if (!buf)
+ error(EXIT_FAILURE, 0, _("out of memory"));
+ bufsize = newsize;
+ }
+ return buf;
+}
+
+#endif /* ! HAVE_CONFIG_H */
+
+/* Some systems don't support symbolic links */
+#ifndef S_ISLNK
+# define S_ISLNK(mode) 0
+#endif
#define DEFAULT_PASSES 25 /* Default */
/* How often to update wiping display */
-#define VERBOSE_UPDATE 100*1024
+#define VERBOSE_UPDATE 150*1024
-/* FIXME: add comments */
struct Options
{
- int allow_devices;
- int force;
- unsigned int n_iterations;
- int remove_file;
- int verbose;
- int exact;
- int zero_fill;
+ int allow_devices; /* -d flag: Allow operations on devices */
+ int force; /* -f flag: chmod() files if necessary */
+ size_t n_iterations; /* -n flag: Number of iterations */
+ int remove_file; /* -p flag (inverted): remove file after shredding */
+ off_t size; /* -s flag: size of file */
+ int verbose; /* -v flag: Print progress (goes to 2) */
+ int exact; /* -x flag: Do not round up file size */
+ int zero_fill; /* -z flag: Add a final zero pass */
};
static struct option const long_opts[] =
{
- {"device", no_argument, NULL, 'd'},
+ {"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'},
+ {"size", required_argument, NULL, 's'},
{"verbose", no_argument, NULL, 'v'},
{"zero", required_argument, NULL, 'z'},
{GETOPT_HELP_OPTION_DECL},
@@ -103,7 +429,7 @@ static struct option const long_opts[] =
};
/* Global variable for error printing purposes */
-char const *program_name;
+char const *program_name; /* Initialized before any possible use */
void
usage (int status)
@@ -117,20 +443,19 @@ usage (int status)
printf (_("\
Delete a file securely, first overwriting it to hide its contents.\n\
\n\
- -d, --device allow operation on devices (devices are never deleted)\n\
+ -D, --device allow operation on devices (devices are never removed)\n\
-f, --force change permissions to allow writing if necessary\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\
+ -n, --iterations=N Overwrite N times instead of the default (%d)\n\
+ -p, --preserve do not remove file after overwriting\n\
+ -s, --size=N shred this many bytes (suffixes like k, M accepted)\n\
+ -v, --verbose show 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\
+ - shred standard output (NOTE: conflicts with -v)\n\
--help display this help and exit\n\
--version print version information and exit\n\
\n\
-FIXME maybe add more discussion here?\n\
-"));
+FIXME maybe add more discussion here?"), DEFAULT_PASSES);
puts (_("\nReport bugs to <bug-fileutils@gnu.org>."));
close_stdout ();
}
@@ -138,7 +463,7 @@ FIXME maybe add more discussion here?\n\
}
#if ! HAVE_FDATASYNC
-# define fdatasync(Fd) fsync (Fd)
+# define fdatasync(fd) -1
#endif
/*
@@ -302,6 +627,7 @@ isaac_mix (struct isaac_state *s, word32 const seed[ISAAC_WORDS])
s->iv[7] = h;
}
+#if 0 /* Provided for reference only; not used in this code */
/*
* Initialize the ISAAC RNG with the given seed material.
* Its size MUST be a multiple of ISAAC_BYTES, and may be
@@ -320,13 +646,13 @@ isaac_init (struct isaac_state *s, word32 const *seed, size_t seedsize)
0xd92a4a78, 0xa51a3c49, 0xc4efea1b, 0x30609119};
int i;
-#if 0
+# if 0
/* The initialization of iv is a precomputed form of: */
for (i = 0; i < 7; i++)
iv[i] = 0x9e3779b9; /* the golden ratio */
for (i = 0; i < 4; ++i) /* scramble it */
mix (iv[0], iv[1], iv[2], iv[3], iv[4], iv[5], iv[6], iv[7]);
-#endif
+# endif
s->a = s->b = s->c = 0;
for (i = 0; i < 8; i++)
@@ -355,6 +681,137 @@ isaac_init (struct isaac_state *s, word32 const *seed, size_t seedsize)
/* Final pass */
isaac_mix (s, s->mm);
}
+#endif
+
+/* Start seeding an ISAAC structire */
+static void
+isaac_seed_start(struct isaac_state *s)
+{
+ static word32 const iv[8] =
+ {
+ 0x1367df5a, 0x95d90059, 0xc3163e4b, 0x0f421ad8,
+ 0xd92a4a78, 0xa51a3c49, 0xc4efea1b, 0x30609119
+ };
+ int i;
+
+#if 0
+ /* The initialization of iv is a precomputed form of: */
+ for (i = 0; i < 7; i++)
+ iv[i] = 0x9e3779b9; /* the golden ratio */
+ for (i = 0; i < 4; ++i) /* scramble it */
+ mix (iv[0], iv[1], iv[2], iv[3], iv[4], iv[5], iv[6], iv[7]);
+#endif
+ for (i = 0; i < 8; i++)
+ s->iv[i] = iv[i];
+ /* We cound initialize s->mm to zero, but why bother? */
+
+ /* s->c gets used for a data pointer during the seeding phase */
+ s->a = s->b = s->c = 0;
+}
+
+/* Add a buffer of seed material */
+static void
+isaac_seed_data(struct isaac_state *s, void const *buf, size_t size)
+{
+ unsigned char *p;
+ size_t avail;
+ size_t i;
+
+ avail = sizeof(s->mm) - (size_t)s->c; /* s->c is used as a write pointer */
+
+ /* Do any full buffers that are necessary */
+ while (size > avail)
+ {
+ p = (unsigned char *)s->mm + s->c;
+ for (i = 0; i < avail; i++)
+ p[i] ^= ((unsigned char const *)buf)[i];
+ buf = (char const *)buf + avail;
+ size -= avail;
+ isaac_mix(s, s->mm);
+ s->c = 0;
+ avail = sizeof(s->mm);
+ }
+
+ /* And the final partial block */
+ p = (unsigned char *)s->mm + s->c;
+ for (i = 0; i < size; i++)
+ p[i] ^= ((unsigned char const *)buf)[i];
+ s->c = (word32)size;
+}
+
+
+/* End of seeding phase; get everything ready to prodice output. */
+static void
+isaac_seed_finish(struct isaac_state *s)
+{
+ isaac_mix (s, s->mm);
+ isaac_mix (s, s->mm);
+ /* Now reinitialize c to start things off right */
+ s->c = 0;
+}
+#define ISAAC_SEED(s,x) isaac_seed_data(s, &(x), sizeof(x))
+
+
+#if __GNUC__ >= 2 && (__i386__ || __alpha__ || _ARCH_PPC)
+/*
+ * Many processors have very-high-resolution timer registers,
+ * The timer registers can be made inaccessible, so we have to deal with the
+ * possibility of SIGILL while we're working.
+ */
+static jmp_buf env;
+static void sigill_handler(int signum)
+{
+ (void)signum;
+ longjmp(env, 1); /* Trivial, just return an indication that it happened */
+}
+
+static void
+isaac_seed_machdep(struct isaac_state *s)
+{
+ void (*oldhandler)(int);
+
+ /* This is how one does try/except in C */
+ oldhandler = signal(SIGILL, sigill_handler);
+ if (setjmp(env)) /* ANSI: Must be entire controlling expression */
+ {
+ (void)signal(SIGILL, oldhandler);
+ }
+ else
+ {
+# if __i386__
+ word32 t[2];
+ __asm__ __volatile__ ("rdtsc" : "=a" (t[0]), "=d" (t[1]));
+# endif
+# if __alpha__
+ unsigned long t;
+ __asm__ __volatile__ ("rpcc %0" : "=r" (t));
+# endif
+# if _ARCH_PPC
+ word32 t;
+ __asm__ __volatile__ ("mfspr %0,22" : "=r" (t));
+# endif
+# if __mips
+ /* Code not used because this is not accessible from userland */
+ word32 t;
+ __asm__ __volatile__ ("mfc0\t%0,$9" : "=r" (t));
+# endif
+# if __sparc__
+ /* This doesn't compile on all platforms yet. How to fix? */
+ unsigned long t;
+ __asm__ __volatile__ ("rd %%tick, %0" : "=r" (t));
+# endif
+ (void)signal(SIGILL, oldhandler);
+ isaac_seed_data(s, &t, sizeof(t));
+ }
+}
+
+#else /* !(__i386__ || __alpha__ || _ARCH_PPC) */
+
+/* Do-nothing stub */
+# define isaac_seed_machdep(s) (void)0
+
+#endif /* !(__i386__ || __alpha__ || _ARCH_PPC) */
+
/*
* Get seed material. 16 bytes (128 bits) is plenty, but if we have
@@ -363,87 +820,59 @@ 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 ();
+ isaac_seed_start(s);
+
+ { pid_t t = getpid (); ISAAC_SEED (s, t); }
+ { pid_t t = getppid (); ISAAC_SEED (s, t); }
+ { uid_t t = getuid (); ISAAC_SEED (s, t); }
+ { gid_t t = getgid (); ISAAC_SEED (s, 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;
+#if HAVE_GETHRTIME
+ hrtime_t t = gethrtime ();
+ ISAAC_SEED (s, t);
#else
- struct timeval tv;
- gettimeofday (&tv, (struct timezone *) 0);
- s->mm[2] = tv.tv_sec;
- s->mm[3] = tv.tv_usec;
+# if HAVE_CLOCK_GETTIME /* POSIX ns-resolution */
+ struct timespec t;
+ clock_gettime (CLOCK_REALTIME, &t);
+# else
+# if HAVE_GETTIMEOFDAY
+ struct timeval t;
+ gettimeofday (&t, (struct timezone *) 0);
+# else
+ time_t t;
+ t = time((time_t *)0);
+# endif
+# endif
#endif
+ ISAAC_SEED (s, t);
}
+ isaac_seed_machdep(s);
+
{
- int fd = open ("/dev/urandom", O_RDONLY);
+ char buf[32];
+ int fd = open ("/dev/urandom", O_RDONLY | O_NOCTTY);
if (fd >= 0)
{
- read (fd, (char *) (s->mm + 4), 32);
+ read (fd, buf, 32);
close (fd);
+ isaac_seed_data(s, buf, 32);
}
else
{
- fd = open ("/dev/random", O_RDONLY | O_NONBLOCK);
+ fd = open ("/dev/random", O_RDONLY | O_NONBLOCK | O_NOCTTY);
if (fd >= 0)
{
/* /dev/random is more precious, so use less */
- read (fd, (char *) (s->mm + 4), 16);
+ read (fd, buf, 16);
close (fd);
+ isaac_seed_data(s, buf, 16);
}
}
}
- 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;
+ isaac_seed_finish(s);
}
/* Single-word RNG built on top of ISAAC */
@@ -507,24 +936,21 @@ irand_mod (struct irand_state *r, word32 n)
}
/*
- * Like perror() but fancier. (And fmt is not allowed to be NULL)
- * This apparent use of features specific to GNU C is actually portable;
- * see the definitions in error.h.
- */
-static void pfstatus (char const *,...)
- __attribute__ ((__format__ (__printf__, 1, 2)));
-
-/*
* Maintain a status line on stdout. This is done by using CR and
* overprinting a new line when it changes, padding with trailing blanks
* as needed to hide all of the previous line. (Assuming that the return
* value of printf is an accurate width.)
- FIXME: we can't assume that
+ * FIXME: we can't assume that
*/
static int status_visible = 0; /* Number of visible characters */
static int status_pos = 0; /* Current position, including padding */
-/* Print a new status line, overwriting the previous one. */
+/*
+ * Print a new status line, overwriting the previous one.
+ * attribute() expands to nothing on non-gcc compilers.
+ */
+static void pfstatus (char const *,...)
+ attribute ((__format__ (__printf__, 1, 2)));
static void
pfstatus (char const *fmt,...)
{
@@ -569,60 +995,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
@@ -665,7 +1037,10 @@ fillrand (struct isaac_state *s, word32 *r, size_t size)
}
}
-/* Generate a 6-character (+ nul) pass name string */
+/*
+ * Generate a 6-character (+ nul) pass name string
+ * FIXME: allow translation of "random".
+ */
#define PASS_NAME_SIZE 7
static void
passname (unsigned char const *data, char name[PASS_NAME_SIZE])
@@ -680,13 +1055,17 @@ 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 *sizep == -1, the size is unknown, and it will be filled in as soon
+ * as writing fails.
*/
static int
-dopass (int fd, char const *name, off_t size, int type,
+dopass (int fd, char const *qname, off_t *sizep, 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 size = *sizep;
+ off_t offset; /* Current file posiiton */
+ off_t thresh; /* Offset to print 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 +1076,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: cannot rewind"), qname);
return -1;
}
@@ -707,7 +1086,7 @@ dopass (int fd, char const *name, off_t size, int type,
if (type >= 0)
{
lim = sizeof (r);
- if ((off_t) lim > size)
+ if ((off_t) lim > size && size != (off_t)-1)
{
lim = (size_t) size;
}
@@ -723,57 +1102,97 @@ 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)..."), qname, k, n, pass_string);
+ thresh = VERBOSE_UPDATE;
+ if (thresh > size && size != (off_t)-1)
+ thresh = size;
}
- for (cursize = size; cursize;)
+ offset = 0;
+ for (;;)
{
/* How much to write this time? */
lim = sizeof (r);
- if ((off_t) lim > cursize)
- lim = (size_t) cursize;
+ if ((off_t) lim > size - offset && size != (off_t)-1)
+ {
+ lim = (size_t) (size - offset);
+ if (!lim)
+ break;
+ }
if (type < 0)
fillrand (s, r, lim);
/* Loop to retry partial writes. */
for (soff = 0; soff < lim; soff += ssize)
{
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);
- return -1;
- }
+ if (ssize <= 0)
+ if ((ssize == 0 || errno == EIO || errno == ENOSPC)
+ && size == (off_t)-1)
+ {
+ /* Ah, we have found the end of the file */
+ *sizep = thresh = size = offset + soff;
+ break;
+ }
+ else
+ {
+ error (0, errno, _("%s: error writing at offset %lu"),
+ qname, (unsigned long)(offset+soff));
+ /*
+ * I sometimes use shred on bad media, before throwing it
+ * out. Thus, I don't want it to give up on bad blocks.
+ * This code assumes 512-byte blocks and tries to skip
+ * over them. It works because lim is always a multiple
+ * of 512, except at the end.
+ */
+ if (errno == EIO && soff % 512 == 0 && lim >= soff+512
+ && size != (off_t)-1)
+ {
+ if (lseek (fd, SEEK_CUR, 512) != (off_t)-1)
+ {
+ soff += 512;
+ continue;
+ }
+ error (0, errno, _("%s: lseek"), qname);
+ }
+ return -1;
+ }
}
/* Okay, we have written "lim" bytes. */
- cursize -= lim;
+ offset += soff;
/* Time to print progress? */
- if (cursize <= thresh && n)
+ if (offset >= thresh && 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;
+ if (size != (off_t)-1)
+ pfstatus (_("%s: pass %lu/%lu (%s)...%lu/%lu K"), qname, k, n,
+ pass_string, (unsigned long)((offset+1023) / 1024),
+ (unsigned long)((size+1023) / 1024));
else
- thresh = 0;
+ pfstatus (_("%s: pass %lu/%lu (%s)...%lu K"), qname, k, n,
+ pass_string, (unsigned long)((offset+1023) / 1024));
+ thresh += VERBOSE_UPDATE;
+ if (thresh > size && size != (off_t)-1)
+ thresh = size;
+ /*
+ * Force periodic syncs to keep displayed progress accurate
+ * FIXME: Should these be present even if -v is not enabled,
+ * to keep the buffer cache from filling with dirty pages?
+ * It's a common problem with programs that do lots of writes,
+ * like mkfs.
+ */
+ if (fdatasync (fd) < 0 && fsync (fd) < 0)
+ {
+ error (0, errno, _("%s: fsync" ), qname);
+ return -1;
+ }
}
}
+
/* 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" ), qname);
return -1;
}
return 0;
@@ -789,7 +1208,7 @@ dopass (int fd, char const *name, off_t size, int type,
* First, all possible 1-bit patterns. There are two of them.
* Then, all possible 2-bit patterns. There are four, but the two
* which are also 1-bit patterns can be omitted.
- * Then, all possible 3-bit patterns. Again, 8-2 = 6.
+ * Then, all possible 3-bit patterns. Likewise, 8-2 = 6.
* Then, all possible 4-bit patterns. 16-4 = 12.
*
* The basic passes are:
@@ -927,8 +1346,7 @@ genpattern (int *dest, size_t num, struct isaac_state *s)
}
}
top = num - randpasses; /* Top of initialized data */
-
- /* assert(d == dest+top); */
+ /* assert (d == dest+top); */
/*
* We now have fixed patterns in the dest buffer up to
@@ -950,7 +1368,6 @@ genpattern (int *dest, size_t num, struct isaac_state *s)
* entry at random between here and top-1 (inclusive)
* and swap the current entry with that one.
*/
-
randpasses--; /* To speed up later math */
accum = randpasses; /* Bresenham DDA accumulator */
for (n = 0; n < num; n++)
@@ -970,7 +1387,7 @@ genpattern (int *dest, size_t num, struct isaac_state *s)
}
accum -= randpasses;
}
- /* assert(top == num); */
+ /* assert (top == num); */
memset (&r, 0, sizeof (r)); /* Wipe this on general principles */
}
@@ -981,8 +1398,8 @@ genpattern (int *dest, size_t num, struct isaac_state *s)
* regular files, and 1 on success with non-regular files.
*/
static int
-wipefd (int fd, char const *name, struct isaac_state *s,
- size_t passes, struct Options const *flags)
+do_wipefd (int fd, char const *qname, struct isaac_state *s,
+ struct Options const *flags)
{
size_t i;
struct stat st;
@@ -990,75 +1407,53 @@ wipefd (int fd, char const *name, struct isaac_state *s,
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);
+ n = flags->n_iterations + ((flags->zero_fill) != 0);
if (fstat (fd, &st))
{
- error (0, 0, _("Can't fstat file `%s'"), name);
+ error (0, errno, _("%s: fstat"), qname);
return -1;
}
/* Check for devices */
- if (!S_ISREG (st.st_mode) && !(flags->allow_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;
+ _("%s: not a regular file; use -D to enable operations on devices"),
+ qname);
+ return -1;
}
/* Allocate pass array */
- passarray = malloc (passes * sizeof (int));
+ passarray = malloc (flags->n_iterations * sizeof (int));
if (!passarray)
{
error (0, 0, _("unable to allocate storage for %lu passes"),
- (unsigned long) passes);
+ (unsigned long) flags->n_iterations);
return -1;
}
- 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;
- }
-
- /*
- * 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)
+ size = flags->size;
+ if (size == (off_t)-1)
{
- seedsize -= 16384;
- seedsize = seedsize / 10 * passes + seedsize % 10 * passes / 10;
- seedsize += 16384;
- }
- (void) isaac_seedfd (s, fd, seedsize);
+ size = st.st_size;
+ if (!size && !S_ISREG(st.st_mode))
+ size = (off_t)-1; /* "Unknown size" */
+ else if (st.st_blksize && !(flags->exact))
+ size += st.st_blksize - 1 - (size-1) % st.st_blksize; /* round up */
+ }
/* Schedule the passes in random order. */
- genpattern (passarray, passes, s);
+ genpattern (passarray, flags->n_iterations, s);
/* Do the work */
- for (i = 0; i < passes; i++)
+ for (i = 0; i < flags->n_iterations; i++)
{
- if (dopass (fd, name, size, passarray[i], s, i + 1, n) < 0)
+ if (dopass (fd, qname, &size, passarray[i], s, i + 1, n) < 0)
{
- memset (passarray, 0, passes * sizeof (int));
+ memset (passarray, 0, flags->n_iterations * sizeof (int));
free (passarray);
return -1;
}
@@ -1066,16 +1461,58 @@ wipefd (int fd, char const *name, struct isaac_state *s,
flushstatus ();
}
- memset (passarray, 0, passes * sizeof (int));
+ memset (passarray, 0, flags->n_iterations * sizeof (int));
free (passarray);
if (flags->zero_fill)
- if (dopass (fd, name, size, 0, s, passes + 1, n) < 0)
+ if (dopass (fd, qname, &size, 0, s, flags->n_iterations + 1, n) < 0)
return -1;
+ if (!S_ISREG (st.st_mode))
+ return 1; /* Not a regular flag, maybe don't unlink */
+
+ /* Okay, now deallocate the data */
+ if (flags->remove_file && ftruncate (fd, 0) < 0)
+ {
+ error (0, errno, _("%s: error truncating"), qname);
+ return -1;
+ }
+ return 0; /* Regular file */
+
return !S_ISREG (st.st_mode);
}
+/* A wrapper with a little more checking for fds on the command line */
+static int
+wipefd (int fd, char const *qname, struct isaac_state *s,
+ struct Options const *flags)
+{
+ int fd_flags = fcntl (fd, F_GETFL);
+
+ if (fd_flags < 0)
+ {
+ error (0, errno, _("%s: fcntl"), qname);
+ return -1;
+ }
+ /* Ugly, but I think it's portable... */
+ if (fd_flags & (O_RDONLY | O_WRONLY | O_RDWR) == O_RDONLY)
+ {
+ error (0, 0, _("%s: cannot shred read-only file descriptor"), qname);
+ return -1;
+ }
+ if (fd_flags & O_APPEND)
+ {
+ error (0, 0, _("%s: cannot shred append-only file descriptor"), qname);
+ return -1;
+ }
+ if (fd == 1 && flags->verbose)
+ error (EXIT_FAILURE, 0,
+ _("%s: can't wipe stdout and print verbose messages to it"), qname);
+ return do_wipefd (fd, qname, s, flags);
+}
+
+/* --- Name-wiping code --- */
+
/* Characters allowed in a file name - a safe universal set. */
static char const nameset[] =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_+=%@#.";
@@ -1120,9 +1557,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, unlink 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 unlinked 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,29 +1567,27 @@ 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 unlinks the file.
*
- * Note that rename() and remove() are both in the ANSI C standard,
- * so that part, at least, is NOT Unix-specific.
+ * The unlink() is Unix-specific, as ANSI-standard remove() has more
+ * portability problems with C libraries making it "safe". rename()
+ * is ANSI-standard.
*
- * FIXME: update this comment.
* To force the directory data out, we try to open() the directory and
* invoke fdatasync() on it. This is rather non-standard, so we don't
* insist that it works, just fall back to a global sync() in that case.
- * Unfortunately, this code is Unix-specific.
+ * This is fairly significantly Unix-specific. Of course, on any
+ * filesystem with synchronous metadata updates, this is unnecessary.
*/
-int
+static int
wipename (char *oldname, struct Options const *flags)
{
- char *newname, *origname = 0;
- char *base; /* Pointer to filename component, after directories. */
+ char *newname, *base; /* Base points to filename part of newname */
+ char const *qname = 0;
unsigned len;
int err;
int dir_fd; /* Try to open directory to sync *it* */
- if (flags->verbose)
- pfstatus (_("%s: deleting"), oldname);
-
newname = strdup (oldname); /* This is a malloc */
if (!newname)
{
@@ -1161,13 +1596,8 @@ wipename (char *oldname, struct Options const *flags)
}
if (flags->verbose)
{
- origname = strdup (oldname);
- if (!origname)
- {
- error (0, 0, _("malloc failed"));
- free (newname);
- return -1;
- }
+ qname = quotearg_colon(oldname);
+ pfstatus (_("%s: removing"), qname);
}
/* Find the file name portion */
@@ -1176,12 +1606,12 @@ wipename (char *oldname, struct Options const *flags)
if (base)
{
*base = '\0';
- dir_fd = open (newname, O_RDONLY);
+ dir_fd = open (newname, O_RDONLY | O_NOCTTY);
*base = '/';
}
else
{
- dir_fd = open (".", O_RDONLY);
+ dir_fd = open (".", O_RDONLY | O_NOCTTY);
}
base = base ? base + 1 : newname;
len = strlen (base);
@@ -1192,18 +1622,19 @@ wipename (char *oldname, struct Options const *flags)
base[len] = 0;
do
{
- if (access (newname, F_OK) < 0
- && !rename (oldname, newname))
+ struct stat st;
+ if (lstat (newname, &st) < 0 && rename (oldname, newname) == 0)
{
- 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)
+ if (qname)
{
- /* The use of origname (rather than oldname) here is
- 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);
+ /*
+ * People seem to understand this better than talking
+ * about renaming oldname. newname doesn't need
+ * quoting because we picked it.
+ */
+ pfstatus (_("%s: renamed to `%s'"), qname, newname);
if (flags->verbose > 1)
flushstatus ();
}
@@ -1215,15 +1646,14 @@ wipename (char *oldname, struct Options const *flags)
len--;
}
free (newname);
- err = remove (oldname);
- if (dir_fd < 0 || fdatasync (dir_fd) < 0)
+ err = unlink (oldname);
+ if (dir_fd < 0 || (fdatasync (dir_fd) < 0 && fsync (dir_fd) < 0))
sync ();
close (dir_fd);
- if (origname)
+ if (qname)
{
if (!err && flags->verbose)
- pfstatus (_("%s: deleted"), origname);
- free (origname);
+ pfstatus (_("%s: removed"), qname);
}
return err;
}
@@ -1235,28 +1665,46 @@ wipename (char *oldname, struct Options const *flags)
* 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.
+ * This is actually the error that stopped us from proceeding, so
+ * it's arguably the right one, and in practice it'll be either EACCESS
+ * again or EPERM, which both give similar error messages.
+ * Does anyone disagree?
*/
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;
- fd = open (name, O_RDWR);
- if (fd < 0 && errno == EACCES && flags->force)
+ fd = open (name, O_WRONLY | O_NOCTTY);
+ if (fd < 0)
{
- if (chmod (name, 0600) >= 0)
- fd = open (name, O_RDWR);
+ if (errno == EACCES && flags->force)
+ {
+ if (chmod (name, S_IWUSR) >= 0) /* 0200, user-write-only */
+ fd = open (name, O_WRONLY | O_NOCTTY);
+ }
+ else if (errno == ENOENT && memcmp (name, "/dev/fd/", 8) == 0)
+ {
+ /* We accept /dev/fd/# even if the OS doesn't support it */
+ unsigned long num;
+ char *p;
+ num = strtoul (name+8, &p, 10);
+ /* If it's completely decimal with no leading zeros... */
+ if (!*p && num < INT_MAX && ((num && name[8] != '0') || !name[9]))
+ {
+ return wipefd ((int)num, quotearg_colon (name), s, flags);
+ }
+ }
}
if (fd < 0)
{
- error (0, 0, _("Unable to open `%s'"), name);
+ if (!flags->force)
+ return 0;
+ error (0, errno, _("Unable to open `%s'"), name);
return -1;
}
- err = wipefd (fd, name, s, passes, flags);
+ err = do_wipefd (fd, quotearg_colon (name), s, flags);
close (fd);
/*
* Wipe the name and unlink - regular files only, no devices!
@@ -1277,7 +1725,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;
@@ -1294,15 +1741,17 @@ main (int argc, char **argv)
/* By default, remove each file after sanitization. */
flags.remove_file = 1;
+ flags.n_iterations = DEFAULT_PASSES;
+ flags.size = (off_t)-1;
- while ((c = getopt_long (argc, argv, "dfn:pvxz", long_opts, NULL)) != -1)
+ while ((c = getopt_long (argc, argv, "Dfn:ps:vxz", long_opts, NULL)) != -1)
{
switch (c)
{
case 0:
break;
- case 'd':
+ case 'D':
flags.allow_devices = 1;
break;
@@ -1312,15 +1761,14 @@ main (int argc, char **argv)
case 'n':
{
- unsigned long int tmp_ulong;
- if (xstrtoul (optarg, NULL, 10, &tmp_ulong, NULL) != LONGINT_OK
- || (word32) tmp_ulong != tmp_ulong
- || ((size_t) (tmp_ulong * sizeof (int)) / sizeof (int)
- != tmp_ulong))
+ unsigned long int tmp;
+ if (xstrtoul (optarg, NULL, 10, &tmp, NULL) != LONGINT_OK
+ || (word32) tmp != tmp
+ || ((size_t) (tmp * sizeof (int)) / sizeof (int) != tmp))
{
error (1, 0, _("invalid number of passes: %s"), optarg);
}
- n_passes = tmp_ulong;
+ flags.n_iterations = (size_t)tmp;
}
break;
@@ -1328,6 +1776,17 @@ main (int argc, char **argv)
flags.remove_file = 0;
break;
+ case 's':
+ {
+ unsigned long int tmp;
+ if (xstrtoul (optarg, NULL, 0, &tmp, "cbBkMG0") != LONGINT_OK)
+ {
+ error (1, 0, _("invalid file size: %s"), optarg);
+ }
+ flags.size = tmp;
+ }
+ break;
+
case 'v':
flags.verbose++;
break;
@@ -1360,15 +1819,15 @@ main (int argc, char **argv)
for (i = 0; i < n_files; i++)
{
- if (STREQ (file[i], "-"))
+ if (strcmp (file[i], "-") == 0)
{
- if (wipefd (0, file[i], &s, (size_t) n_passes, &flags) < 0)
+ if (wipefd (1, quotearg_colon (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 ();
@@ -1381,3 +1840,6 @@ main (int argc, char **argv)
exit (err);
}
+/*
+ * vim:sw=2:sts=2:
+ */