/*****************************************************************************\
 *  $Id: scrub.c 78 2006-02-15 01:05:03Z garlick $
 *****************************************************************************
 *  Copyright (C) 2001-2002 The Regents of the University of California.
 *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
 *  Written by Jim Garlick <garlick@llnl.gov>.
 *  UCRL-CODE-2003-006.
 *  
 *  This file is part of Scrub, a program for erasing disks.
 *  For details, see <http://www.llnl.gov/linux/scrub/>.
 *  
 *  Scrub is free software; you can redistribute it and/or modify it under
 *  the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *  
 *  Scrub is distributed in the hope that it will be useful, but WITHOUT ANY
 *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 *  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 *  details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with Scrub; if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
\*****************************************************************************/

/* Scrub a raw disk or plain file.
 */

#if defined(linux) || defined(sun) || defined(UNIXWARE) || defined(__hpux)
#define _LARGEFILE_SOURCE 
#define _FILE_OFFSET_BITS 64
#endif

#if defined(_AIX)
#define _LARGE_FILES
#include <sys/mode.h>
#endif

#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <libgen.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <assert.h>
#include <sys/param.h> /* MAXPATHLEN */

#include "genrand.h"
#include "fillfile.h"
#include "filldentry.h"
#include "getsize.h"
#include "progress.h"
#include "util.h"
#include "sig.h"

#define RANDOM	0x0100
#define VERIFY  0x0200

static const int dirent_pattern[] = { 0x55, 0x22, 0x55, 0x22, 0x55, 0x22 };

static const int old_pattern[] = { 0, 0xff, 0xaa, RANDOM, 0x55, VERIFY };
static const int fastold_pattern[] = { 0, 0xff, 0xaa, 0x55, VERIFY };
static const int nnsa_pattern[] = { RANDOM, RANDOM, 0, VERIFY };
static const int dod_pattern[] = { 0, 0xff, RANDOM, 0, VERIFY };
static const int bsi_pattern[] = { 0xff, 0xfe, 0xfd, 0xfb, 0xf7,
                                   0xef, 0xdf, 0xbf, 0x7f };

typedef enum { false, true } bool;
typedef enum { NOEXIST, REGULAR, SPECIAL, OTHER } filetype_t;

static char *pat2str(int pat);
static void usage(void);
static off_t blkalign(off_t offset, int blocksize);
static filetype_t filetype(char *path);

/* NOTE: default blocksize was 8K in scrub 1.8, however on hpux
 * it was found that raising the default to 1M raised performance from
 * ~1.3 MB/s to 20MB/s. [Graham Smith]
 * Probably it won't hurt on the other OS's.
 */
#define BUFSIZE (1024*1024) /* default blocksize */

char *prog;

static void 
usage(void)
{
    fprintf(stderr, "Usage: %s [-f] [-p dod|nnsa|bsi] [-b blocksize] [-X] [-D newname] [-r] file\n", prog);
    fprintf(stderr, "\t-p select scrub patterns (see scrub(1))\n");
    fprintf(stderr, "\t-b overrides default I/O buffer size of %d bytes\n", BUFSIZE);
    fprintf(stderr, "\t-X create file and keep writing until write fails, then scrub\n");
    fprintf(stderr, "\t-D after scrubbing the file, scrub the directory entry and rename\n");
    fprintf(stderr, "\t-f scrub even if file has signature from previous scrub\n");
    fprintf(stderr, "\t-r remove file after scrub\n");

    exit(1);
}

static char *
pat2str(int pat)
{
    static char str[255];

    if (pat == RANDOM)
        snprintf(str, sizeof(str), "random");
    else if (pat == VERIFY)
        snprintf(str, sizeof(str), "verify");
    else
        snprintf(str, sizeof(str), "0x%x", pat);
    return str;
}

/* Round 'offset' up to an even multiple of 'blocksize'.
 */
static off_t
blkalign(off_t offset, int blocksize)
{
    off_t r = offset % blocksize;

    if (r > 0) 
        offset += (blocksize - r);

    return offset;
}

/* Return the type of file represented by 'path'.
 */
static filetype_t
filetype(char *path)
{
    struct stat sb;
    filetype_t res = NOEXIST;

    if (stat(path, &sb) == 0) {
        if (S_ISREG(sb.st_mode))
            res = REGULAR;
#if defined(linux)
        /* on Linux, char devices were an afterthought so allow block */
        else if (S_ISCHR(sb.st_mode) || S_ISBLK(sb.st_mode))
#else
        else if (S_ISCHR(sb.st_mode))
#endif
            res = SPECIAL;
        else
            res = OTHER;
    }
    return res;
}

/* Scrub 'path', a file/device of size 'size'.
 * Fill with the bytes in 'pat', an array of 'npat' elements.
 * Use 'bufsize' length for I/O buffers.
 */
static void
scrub(char *path, off_t size, const int pat[], int npat, int bufsize, int Sopt)
{
    unsigned char *buf;
    int i;
    prog_t p;
    char sizestr[80];

    if (!(buf = malloc(bufsize))) {
        fprintf(stderr, "%s: out of memory\n", prog);
        exit(1);
    }

    size2str(sizestr, sizeof(sizestr), size);
    printf("%s: scrubbing %s %s\n", prog, path, sizestr);

    initrand();
    for (i = 0; i < npat; i++) {
        printf("%s: %-8s", prog, pat2str(pat[i]));
        progress_create(&p, 50);
        if (pat[i] == RANDOM) {
            churnrand();
            fillfile(path, size, buf, bufsize, 
                    (progress_t)progress_update, p, (refill_t)genrand);
        } else if (pat[i] == VERIFY) {
            assert(i > 0);
            assert(pat[i - 1] != RANDOM);
            assert(pat[i - 1] != VERIFY);
            /* depends on previous pass contents of buf being intact */
            checkfile(path, size, buf, bufsize, 
                    (progress_t)progress_update, p);
        } else {
            assert(pat[i] <= 0xff);
            memset(buf, pat[i], bufsize);
            fillfile(path, size, buf, bufsize, 
                    (progress_t)progress_update, p, NULL);
        }
        progress_destroy(p);
    }
    if (!Sopt)
        writesig(path, bufsize);

    free(buf);
}

/* Scrub free space (-X).
 */
static void
scrub_free(char *path, const int pat[], int npat, int bufsize, int Sopt)
{
    unsigned char *buf;
    off_t size; 

    assert(filetype(path) == NOEXIST || filetype(path) == REGULAR);

    /* special scrub for first pass */
    if (!(buf = malloc(bufsize))) {
        fprintf(stderr, "%s: out of memory\n", prog);
        exit(1);
    }
    assert(npat > 0);
    assert(pat[0] != VERIFY);
    printf("%s: filling file system by expanding %s\n", prog, path);
    printf("%s: %-8s...\n", prog, pat2str(pat[0]));
    fflush(stdout);
    /* XXX add some feedback */
    if (pat[0] == RANDOM) {
        churnrand();
        size = growfile(path, buf, bufsize, (refill_t)genrand);
    } else {
        memset(buf, pat[0], bufsize);
        size = growfile(path, buf, bufsize, NULL);
    }
    free(buf);

    /* remaining passes as usual */
    if (npat > 1)
        scrub(path, size, pat+1, npat-1, bufsize, Sopt);
}

/* Scrub name component of a directory entry through succesive renames.
 */
static void
scrub_dirent(char *path, char *newpath)
{
    const int *pat = dirent_pattern;
    int npat = sizeof(dirent_pattern)/sizeof(dirent_pattern[0]);
    prog_t p;
    int i;

    assert(filetype(path) == REGULAR);

    printf("%s: scrubbing directory entry\n", prog);

    for (i = 0; i < npat; i++) {
        assert(pat[i] != 0);
        assert(pat[i] != RANDOM);
        assert(pat[i] != VERIFY);
        printf("%s: %-8s", prog, pat2str(pat[i]));
        progress_create(&p, 50);
        filldentry(path, pat[i]); /* path: in/out */
        progress_update(p, 1.0);
        progress_destroy(p);
    }
    if (rename(path, newpath) < 0) {
        fprintf(stderr, "%s: error renaming %s to %s\n", prog, path, newpath);
        exit(1);
    }
}

/* Scrub a regular file.
 */
static void 
scrub_file(char *path, const int pat[], int npat, int bufsize, int Sopt)
{
    off_t size; 
    struct stat sb;

    assert(filetype(path) == REGULAR);

    if (stat(path, &sb) < 0) {
        fprintf(stderr, "%s: stat ", prog);
        perror(path);
        exit(1);
    }
    if (sb.st_size == 0) {
        fprintf(stderr, "%s: %s is zero length\n", prog, path);
        exit(1);
    }
    size = blkalign(sb.st_size, sb.st_blksize);
    if (size != sb.st_size) {
        printf("%s: padding %s with %d bytes to fill last fs block\n", 
                prog, path, (int)(size - sb.st_size)); 
    }
    scrub(path, size, pat, npat, bufsize, Sopt);
}

/* Scrub apple resource fork component of file.
 */
#if __APPLE__
static void
scrub_resfork(char *path, const int pat[], int npat, int bufsize)
{
    struct stat rsb;
    char rpath[MAXPATHLEN];
    off_t rsize; 

    assert(filetype(path) == REGULAR);
    (void)snprintf(rpath, sizeof(rpath), "%s/..namedfork/rsrc", path);
    if (stat(rpath, &rsb) < 0)
        return;
    if (rsb.st_size == 0) {
        printf("%s: skipping zero length resource fork: %s\n", prog, rpath);
        return;
    }
    printf("%s: scrubbing resource fork: %s\n", prog, rpath);
    rsize = blkalign(rsb.st_size, rsb.st_blksize);
    if (rsize != rsb.st_size) {
        printf("%s: padding %s with %d bytes to fill last fs block\n", 
                        prog, rpath, (int)(rsize - rsb.st_size)); 
    }
    scrub(rpath, rsize, pat, npat, bufsize, 0);
}
#endif

/* Scrub a special file corresponding to a disk.
 */
static void
scrub_disk(char *path, off_t size, const int pat[], int npat, int bufsize, int Sopt)
{
    assert(filetype(path) == SPECIAL);
    if (size == 0) {
        size = getsize(path);
        if (size == 0) {
            fprintf(stderr, "%s: could not determine size, use -s\n", prog);
            exit(1);
        }
        printf("%s: please verify that device size below is correct!\n", prog);
    }
    scrub(path, size, pat, npat, bufsize, Sopt);
}

int 
main(int argc, char *argv[])
{
    const int *pat = nnsa_pattern;
    int npat = sizeof(nnsa_pattern)/sizeof(nnsa_pattern[0]);
    bool Xopt = false;
    int bopt = BUFSIZE;
    off_t sopt = 0;
    char *Dopt = NULL;
    char *filename = NULL;
    int fopt = 0;
    int Sopt = 0;
    int ropt = 0;

    extern int optind;
    extern char *optarg;
    int c;

    /* Handle arguments.
     */
    prog = basename(argv[0]);
    while ((c = getopt(argc, argv, "p:D:Xb:s:fSr")) != EOF) {
        switch (c) {
        case 'p':   /* Override default pattern with dod|nnsa|old|fastold */
            if (!strcmp(optarg, "dod") || !strcmp(optarg, "DOD")) {
                pat = dod_pattern;
                npat = sizeof(dod_pattern)/sizeof(dod_pattern[0]);
            } else if (!strcmp(optarg, "nnsa") || !strcmp(optarg, "NNSA")) {
                pat = nnsa_pattern;
                npat = sizeof(nnsa_pattern)/sizeof(nnsa_pattern[0]);
            } else if (!strcmp(optarg, "old") || !strcmp(optarg, "OLD")) {
                pat = old_pattern;
                npat = sizeof(old_pattern)/sizeof(old_pattern[0]);
            } else if (!strcmp(optarg, "fastold") || !strcmp(optarg, "FASTOLD")) {
                pat = fastold_pattern;
                npat = sizeof(fastold_pattern)/sizeof(fastold_pattern[0]);
            } else if (!strcmp(optarg, "bsi") || !strcmp(optarg, "BSI")) {
                pat = bsi_pattern;
                npat = sizeof(bsi_pattern)/sizeof(bsi_pattern[0]);
            } else 
                usage();
            break;
        case 'X':   /* fill filesystem */
            Xopt = true;
            break;
        case 'D':   /* scrub name in dirent through successive renames */
            Dopt = optarg;
            break;
        case 'r':   /* remove file when done */
            ropt = 1;
            break;
        case 'b':   /* override blocksize */
            bopt = str2int(optarg);
            if (bopt == 0) {
                fprintf(stderr, "%s: error parsing blocksize string\n", prog);
                exit(1);
            }
            break;
        case 's':   /* override size of special file */
            sopt = str2size(optarg);
            if (sopt == 0) {
                fprintf(stderr, "%s: error parsing size string\n", prog);
                exit(1);
            }
            break;
        case 'f':   /* force scrub even if already done */
            fopt = 1;
            break;
        case 'S':   /* do not write scrub signature */
            Sopt = 1;
            break;
        default:
            usage();
        }
    }
    if (argc - optind != 1)
        usage();
    filename = argv[optind++];

    /* Verify arguments.
     */
    if (filename == NULL)
        usage();
    if (Xopt) {
        if (filetype(filename) == SPECIAL) {
            fprintf(stderr, "%s: -X cannot be used on special files\n", prog);
            exit(1);
        }
        if (sopt > 0) {
            fprintf(stderr, "%s: -s and -X cannot be used together\n", prog);
            exit(1);
        }
        if (Dopt) {
            fprintf(stderr, "%s: -D and -X cannot be used together\n", prog);
            exit(1);
        }
    } else {
        switch (filetype(filename)) {
            case NOEXIST:
                fprintf(stderr, "%s: %s does not exist\n", prog, filename);
                exit(1);
                break;
            case OTHER:
                fprintf(stderr, "%s: %s is not a reg file or %sdisk device\n", 
                    prog, filename,
#if defined(linux)
                    ""
#else
                    "raw "
#endif
                    );
                exit(1);
                break;
            case SPECIAL:
                if (Dopt) {
                    fprintf(stderr, "%s: cannot use -D with special file\n", prog);
                    exit(1);
                }
                if (ropt) {
                    fprintf(stderr, "%s: cannot use -r with special file\n", prog);
                    exit(1);
                }
                break;
            case REGULAR:
                if (sopt > 0) {
                    fprintf(stderr, "%s: cannot use -s with regular file\n", prog);
                    exit(1);
                }
                if (Dopt && *Dopt != '/' && *filename == '/') {
                    fprintf(stderr, "%s: %s should be a full path like %s\n",
                            prog, Dopt, filename);
                    exit(1);
                }
                break;
        }
        if (access(filename, R_OK|W_OK) < 0) {
            fprintf(stderr, "%s: no permission to scrub %s\n", prog, filename);
            exit(1);
        }
        if (checksig(filename, bopt) && !fopt) {
            fprintf(stderr, "%s: %s has already been scrubbed? (use -f to force)\n",
                        prog, filename);
            exit(1);
        }
    }


    if (sizeof(off_t) < 8) {
        fprintf(stderr, "%s: warning: not using 64 bit file offsets\n", prog);
    }

    /* Scrub.
     */
    printf("%s: using %s patterns\n", prog, 
            (pat == dod_pattern)        ? "DoD 5220.22-M" 
            : (pat == nnsa_pattern)     ? "NNSA NAP-14.x" 
            : (pat == old_pattern)      ? "pre v1.7 scrub" 
            : (pat == fastold_pattern)  ? "pre v1.7 scrub (skip random)" 
            : (pat == bsi_pattern)      ? "BSI pattern" 
            : "unknown pattern");

    if (Xopt) {                                     /* scrub free */
        scrub_free(filename, pat, npat, bopt, Sopt);

    } else if (filetype(filename) == REGULAR) {     /* scrub file */
        scrub_file(filename, pat, npat, bopt, Sopt);
#if __APPLE__
        scrub_resfork(filename, pat, npat, bopt);
#endif
        if (Dopt) { /* XXX destroys 'filename' */
            scrub_dirent(filename, Dopt);
            filename = Dopt; /* -r needs this below */
        }

    } else if (filetype(filename) == SPECIAL) {     /* scrub disk */  
        scrub_disk(filename, sopt, pat, npat, bopt, Sopt);
    }

    /* unlink file at the end */
    if (ropt && filetype(filename) == REGULAR) {
        printf("%s: unlinking %s\n", prog, filename);
        if (unlink(filename) != 0) {
            perror(filename);
            exit(1);
        }
    }

    exit(0);
}

/*
 * vi:tabstop=4 shiftwidth=4 expandtab
 */


syntax highlighted by Code2HTML, v. 0.9.1