From 758bbda7882acbbb89556d8fa5d9b83a1e252edb Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Sat, 23 Jan 1999 05:32:58 +0000 Subject: [PATCH] use getopt_long (struct Options): Declare. (usage): New function. (program_name): Rename from argv0 (FLAG_* macros): Remove. --- src/shred.c | 354 ++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 199 insertions(+), 155 deletions(-) diff --git a/src/shred.c b/src/shred.c index a9746b4..55388a4 100644 --- a/src/shred.c +++ b/src/shred.c @@ -1,7 +1,7 @@ /* TODO: - - use getopt_long - - use error - - cvt strings for gettext + x use getopt_long + - use error, not pferror + - bracket strings with _(...) for gettext */ /* @@ -74,14 +74,82 @@ #include /* For errno */ #include "system.h" +#include "xstrtoul.h" +#include "closeout.h" #include "error.h" -static char const version_string[] = -"sterilize 1.02"; #define DEFAULT_PASSES 25 /* Default */ + /* How often to update wiping display */ #define VERBOSE_UPDATE 100*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; +}; + +/* If nonzero, display usage information and exit. */ +static int show_help; + +/* If nonzero, print the version on standard output and exit. */ +static int show_version; + +static struct option const long_opts[] = +{ + {"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'}, + {"help", no_argument, &show_help, 1}, + {"version", no_argument, &show_version, 1}, + {NULL, 0, NULL, 0} +}; + +/* Global variable for error printing purposes */ +char const *program_name; + +void +usage (int status) +{ + if (status != 0) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTIONS] FILE [...]\n"), program_name); + 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\ + -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\ + -x, --exact do not round file sizes up to the next full block\n\ + -z, --zero add a final overwrite with zeros to hide sterilization\n\ + - sterilize 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\ +")); + puts (_("\nReport bugs to .")); + close_stdout (); + } + exit (status); +} + /* * -------------------------------------------------------------------- * Bob Jenkins' cryptographic random number generator, ISAAC. @@ -118,8 +186,8 @@ typedef unsigned char word32; /* Size of the state tables to use. (You may change ISAAC_LOG) */ #define ISAAC_LOG 8 -#define ISAAC_WORDS (1<>ISAAC_LOG) + x \ + *(m) = y = ind ((mm), x) + (a) + (b), \ + *(r) = b = ind ((mm), (y) >> ISAAC_LOG) + x \ ) /* - * Refill the entire r[] array + * Refill the entire R array, and update S. */ static void isaac_refill (struct isaac_state *s, word32 r[ISAAC_WORDS]) @@ -447,9 +515,6 @@ irand_mod (struct irand_state *r, word32 n) return x % n; } -/* Global variable for error printing purposes */ -static char const *argv0 = NULL; - /* * Like perror() but fancier. (And fmt is not allowed to be NULL) */ @@ -466,6 +531,7 @@ __attribute__ ((format (printf, 1, 2))); * 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 */ static int status_visible = 0; /* Number of visible characters */ static int status_pos = 0; /* Current position, including padding */ @@ -523,9 +589,9 @@ pferror (char const *fmt,...) flushstatus (); /* Make it look pretty */ - if (argv0) + if (program_name) { - fputs (argv0, stderr); + fputs (program_name, stderr); fputs (": ", stderr); } va_start (ap, fmt); @@ -555,7 +621,7 @@ sizefd (int fd) /* Binary doubling upwards to find the right range */ lo = 0; - hi = 0; /* Any number, preferably 2^x-1, is okay here. */ + hi = 0; /* Any number, preferably 2^x-1, is okay here. */ /* * Loop invariant: we have verified that it is possible to read a @@ -565,8 +631,8 @@ sizefd (int fd) */ for (;;) { - if (lseek (fd, hi, SEEK_SET) == (off_t) -1 || - read (fd, &c, 1) < 1) + 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. */ @@ -580,8 +646,8 @@ sizefd (int fd) 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) + 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 */ @@ -622,7 +688,7 @@ fillpattern (int type, unsigned char *r, size_t size) * size is rounded UP to a multiple of ISAAC_BYTES. */ static void -fillrand (struct isaac_state *s, word32 * r, size_t size) +fillrand (struct isaac_state *s, word32 *r, size_t size) { size = (size + ISAAC_BYTES - 1) / ISAAC_BYTES; @@ -713,6 +779,8 @@ dopass (int fd, char const *name, off_t size, int type, int e = errno; pferror ("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 ( @@ -942,17 +1010,6 @@ genpattern (int *dest, size_t num, struct isaac_state *s) memset (&r, 0, sizeof (r)); /* Wipe this on general principles */ } -/* Flags definition. Bit numbers here correspond to flag letters below! */ -#define FLAG_DEVICES 1 -#define FLAG_FORCE 2 -#define FLAG_PRESERVE 4 -#define FLAG_VERBOSE 8 -#define FLAG_EXACT 16 -#define FLAG_ZERO 32 -static char const simpleflags[] = "dfpvxz"; /* Same order as above */ - -#define FLAG_EXTRAVERBOSE 256 /* -vv specified */ - /* * 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 @@ -960,7 +1017,7 @@ static char const simpleflags[] = "dfpvxz"; /* Same order as above */ */ static int wipefd (int fd, char const *name, struct isaac_state *s, - size_t passes, unsigned flags) + size_t passes, struct Options const *flags) { size_t i; struct stat st; @@ -972,8 +1029,8 @@ wipefd (int fd, char const *name, struct isaac_state *s, passes = DEFAULT_PASSES; n = 0; /* dopass takes n -- 0 to mean "don't print progress" */ - if (flags & FLAG_VERBOSE) - n = passes + ((flags & FLAG_ZERO) != 0); + if (flags->verbose) + n = passes + ((flags->zero_fill) != 0); if (fstat (fd, &st)) { @@ -982,7 +1039,7 @@ wipefd (int fd, char const *name, struct isaac_state *s, } /* Check for devices */ - if (!S_ISREG (st.st_mode) && !(flags & FLAG_DEVICES)) + if (!S_ISREG (st.st_mode) && !(flags->allow_devices)) { fprintf (stderr, "\"%s\" is not a regular file: use -d to enable operations on devices\n", @@ -1005,7 +1062,7 @@ wipefd (int fd, char const *name, struct isaac_state *s, /* Reluctant to talk? Apply thumbscrews. */ seedsize = size = sizefd (fd); } - else if (st.st_blksize && !(flags & FLAG_EXACT)) + 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; @@ -1040,14 +1097,14 @@ wipefd (int fd, char const *name, struct isaac_state *s, free (passarray); return -1; } - if (flags & FLAG_EXTRAVERBOSE) + if (flags->verbose > 1) flushstatus (); } memset (passarray, 0, passes * sizeof (int)); free (passarray); - if (flags & FLAG_ZERO) + if (flags->zero_fill) if (dopass (fd, name, size, 0, s, passes + 1, n) < 0) return -1; @@ -1115,11 +1172,11 @@ incname (char *name, unsigned len) * * 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 thet case. + * insist that it works, just fall back to a global sync() in that case. * Unfortunately, this code is Unix-specific. */ int -wipename (char *oldname, unsigned flags) +wipename (char *oldname, struct Options const *flags) { char *newname, *origname = 0; char *base; /* Pointer to filename component, after directories. */ @@ -1135,7 +1192,7 @@ wipename (char *oldname, unsigned flags) pferror ("malloc failed"); return -1; } - if (flags & FLAG_VERBOSE) + if (flags->verbose) { origname = strdup (oldname); if (!origname) @@ -1177,7 +1234,7 @@ wipename (char *oldname, unsigned flags) { pfstatus ("%s: renamed to \"%s\"", origname, newname); - if (flags & FLAG_EXTRAVERBOSE) + if (flags->verbose > 1) flushstatus (); } memcpy (oldname + (base - newname), newname, len + 1); @@ -1205,18 +1262,20 @@ wipename (char *oldname, unsigned 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. */ static int -wipefile (char *name, struct isaac_state *s, size_t passes, unsigned flags) +wipefile (char *name, struct isaac_state *s, size_t passes, + struct Options const *flags) { int err, fd; fd = open (name, O_RDWR); - if (fd < 0 && errno == EACCES && flags & FLAG_FORCE) + if (fd < 0 && errno == EACCES && flags->force) { if (chmod (name, 0600) >= 0) fd = open (name, O_RDWR); @@ -1233,7 +1292,7 @@ wipefile (char *name, struct isaac_state *s, size_t passes, unsigned flags) * Wipe the name and unlink - regular files only, no devices! * (wipefd returns 1 for non-regular files.) */ - if (err == 0 && !(flags & FLAG_PRESERVE)) + if (err == 0 && flags->remove_file) { err = wipename (name, flags); if (err < 0) @@ -1242,132 +1301,117 @@ wipefile (char *name, struct isaac_state *s, size_t passes, unsigned flags) return err; } -/* Command-line parsing. I hate global variables, ergo I hate getopt. */ int main (int argc, char **argv) { struct isaac_state s; int err = 0; - int no_more_opts = 0; - unsigned flags = 0; - char const *p; - char *p2; /* Actually a const ptr, but kludged... */ - unsigned long passes = 0; - unsigned wipes = 0; /* How many files have we actually wiped? */ + struct Options flags; + unsigned long n_passes = 0; + char **file; + int n_files; + int c; + int i; - argv0 = argv[0]; /* Ick! A global variable! */ + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); isaac_seed (&s); - while (--argc && !err) + memset (&flags, 0, sizeof flags); + + /* By default, remove each file after sanitization. */ + flags.remove_file = 1; + + while ((c = getopt_long (argc, argv, "dfn:pvxz", long_opts, NULL)) != -1) { - p = *++argv; - if (no_more_opts || *p != '-') + switch (c) { - /* Plain filename - Note that this overwrites *argv! */ - if (wipefile (*argv, &s, (size_t) passes, flags) < 0) - err = 1; - flushstatus (); - wipes++; - continue; + case 0: + break; + + case 'd': + flags.allow_devices = 1; + break; + + case 'f': + flags.force = 1; + break; + + 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)) + { + 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; + + case 'x': + flags.exact = 1; + break; + + case 'z': + flags.zero_fill = 1; + break; + + default: + usage (1); } + } - /* Parse option */ - if (p[1] == '\0') - { /* "-": stdin */ - if (wipefd (0, *argv, &s, (size_t) passes, flags) < 0) + if (show_version) + { + printf ("sterilize (%s) %s\n", GNU_PACKAGE, VERSION); + close_stdout (); + exit (0); + } + + if (show_help) + usage (0); + + file = argv + optind; + n_files = argc - optind; + + if (n_files == 0) + { + error (0, 0, _("missing file argument")); + usage (1); + } + + for (i = 0; i < n_files; i++) + { + if STREQ (file[i], "-") + { + if (wipefd (0, file[i], &s, (size_t) n_passes, &flags) < 0) err = 1; - flushstatus (); - wipes++; - continue; } - if (p[1] == '-') - { /* "--long_option" */ - if (p[2] == '\0') - { - no_more_opts = 1; - } - else if (strcmp (p + 2, "help") == 0) - { - puts ( - "Usage: sterilize [OPTIONS] FILE [...]\n" - "Delete a file securely, first overwriting it to hide its contents.\n" - "\n" - " - Sterilize standard input (but don't delete it)\n" - " This will error unless you use <>file, a safety feature\n" - " -NUM cppi: standard input: line 101: unterminated #if - Overwrite NUM times instead of the default (25)\n" - " -d Allow operation on devices (devices are never deleted)\n" - " -f Force, change permissions to allow writing if necessary\n" - " -p Preserve, do not delete file after overwriting\n" - " -v Verbose, print progress (-vv to leave progress on screen)\n" - " -x Exact, do not round file sizes up to the next full block\n" - " -z Add a final overwrite with zeros to hide sterilization\n" - " -- End of options; following filenames may begin with -\n" - " --help Display this help and exit\n" - " --version Print version information and exit"); - return 0; /* Immediate quit */ - } - else if (strcmp (p + 2, "version") == 0) - { - puts (version_string); - return 0; /* Immediate quit */ - } - else - { - fprintf (stderr, "%s: Unknown option %s\n", - argv0, p); - err = 1; - break; - } - continue; - } - /* Short options - letter options or digits */ - while (*++p) + else { - p2 = strchr (simpleflags, *p); - if (p2) - { - unsigned flag = 1u << (p2 - simpleflags); - if (flag & flags & FLAG_VERBOSE) - flags |= FLAG_EXTRAVERBOSE; - flags |= flag; - continue; - } - if (*p >= '0' && *p <= '9') - { - passes = strtoul (p, &p2, 0); - - if ((word32) passes != passes || - (size_t) (passes * sizeof (int)) / sizeof (int) - != passes) - { - fprintf (stderr, - "%s: Too many passes: -%s\n", - argv0, p); - err = 1; - break; - } - p = p2 - 1; - continue; - } - fprintf (stderr, "%s: Unknown option -%s\n", - argv0, p); - err = 1; - break; + /* Plain filename - Note that this overwrites *argv! */ + if (wipefile (file[i], &s, (size_t) n_passes, &flags) < 0) + err = 1; } + flushstatus (); } /* Just on general principles, wipe s. */ memset (&s, 0, sizeof (s)); - if (!wipes && !err) - { - fprintf (stderr, "%s: no filename specified\n" - "Try \"%s --help\" for more information.\n", - argv0, argv0); - err = 1; - } - - return err; + exit (err); } -- 2.7.4