1
0
mirror of https://git.savannah.gnu.org/git/coreutils.git synced 2026-10-31 18:44:38 +03:00

use getopt_long

(struct Options): Declare.
(usage): New function.
(program_name): Rename from argv0
(FLAG_* macros): Remove.
This commit is contained in:
Jim Meyering
1999-01-23 05:32:58 +00:00
parent ae7d45cdc9
commit 758bbda788

View File

@@ -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 <errno.h> /* 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 <bug-fileutils@gnu.org>."));
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)
#define ISAAC_BYTES (ISAAC_WORDS*sizeof(word32))
#define ISAAC_WORDS (1 << ISAAC_LOG)
#define ISAAC_BYTES (ISAAC_WORDS * sizeof (word32))
/* RNG state variables */
struct isaac_state
@@ -130,7 +198,7 @@ struct isaac_state
};
/* This index operation is more efficient on many processors */
#define ind(mm,x) *(unsigned *)((char *)(mm) + ( (x) & (ISAAC_WORDS-1)<<2 ))
#define ind(mm, x) *(unsigned *)((char *)(mm) + ( (x) & (ISAAC_WORDS-1)<<2 ))
/*
* The central step. This uses two temporaries, x and y. mm is the
@@ -138,16 +206,16 @@ struct isaac_state
* the offset from m to the word ISAAC_WORDS/2 words away in the mm array,
* i.e. +/- ISAAC_WORDS/2.
*/
#define isaac_step(mix,a,b,mm,m,off,r) \
#define isaac_step(mix, a, b, mm, m, off, r) \
( \
a = (a^(mix)) + (m)[off], \
a = ((a) ^ (mix)) + (m)[off], \
x = *(m), \
*(m) = y = ind(mm,x) + a + b, \
*(r) = b = ind(mm,y>>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)
{
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);
}
}
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;
}
else
{
/* Plain filename - Note that this overwrites *argv! */
if (wipefile (*argv, &s, (size_t) passes, flags) < 0)
if (wipefile (file[i], &s, (size_t) n_passes, &flags) < 0)
err = 1;
flushstatus ();
wipes++;
continue;
}
/* Parse option */
if (p[1] == '\0')
{ /* "-": stdin */
if (wipefd (0, *argv, &s, (size_t) 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)
{
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;
}
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);
}