// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2000-2003 Silicon Graphics, Inc.
 * All Rights Reserved.
 */


/*
 * fill2:
 *   Derived from fill.c, adds command line options, block boundary marking
 *   and allows me to tweak to suit fill2fs without breaking other qa tests.
 *   This version marks block boundaries (512, 1k, 4k and 16k) within the file
 *   using characters guaranteed not to appear anywhere else, this may allow
 *   simple checks in future which can inspect these offsets and ensure one of
 *   of the four characters is present. Also fixes off-by-one error in fill.c
 *   and is more careful about checking when write operations fail - this is 
 *   needed by fill2fs to ensure that the number of bytes written is accurately
 *   determined.
 */

#include "global.h"

#define constpp char * const *

#define N(x) (sizeof(x)/sizeof(x[0]))

/* function prototypes */
static void illegal(char *, char *);
static void reqval(char, char * [], int);
static void respec(char, char * [], int);
static void unknown(char, char *);
static void usage(void);
char *progname;

char *dopts[] =		{ "nbytes", "linelength", "seed", "file", NULL };
enum			{ D_NBYTES, D_LINELENGTH, D_SEED, D_FILE, D_ISSET, D_NUM };
int dflags[D_NUM] =	{ 0 };
char *bopts[] =		{ "512", "1k", "4k", "16k" };
enum			{ B_512, B_1K, B_4K, B_16K, B_ISSET, B_NUM };
int bflags[B_NUM] =	{ 0 };


/*
 * block boundaries
 *
 */

/* block boundaries which should be flagged in the output file */
/* flag is the character that should be used to indicate each type */
/* of block boundary */


struct s_block_type {
    int mark;
    int size;
    char flag;
};

static struct s_block_type btypes[] = {
    { 0, 512, '!' },	/* 512 */
    { 0, 1024, '"' },	/* 1k */
    { 0, 4096, '#' },	/* 4k */
    { 0, 16384, '$' },	/* 16k */
};

/*
 * main
 *
 */

int
main(int argc, char **argv)
{
    int			status = 0;	/* return status */
    int			c;		/* current option character */
    char		*p;		/* for getsubopt calls */
    long		nbytes;		/* total number of bytes to write */
    int			dlen;		/* length of normal output data line */
    const char		*dseed = NULL;	/* input string for seeding rand */
    unsigned int	seed;		/* seed for output data */    
    char		*dfile = NULL;	/* where to write output */

    FILE		*f;		/* output file */
    char		*dbuf;		/* output line buffer */
    char		bbuf[50];	/* block boundary string */
    char		*active = NULL;	/* active buffer to copy out of */
    size_t		hlen;		/* header length (bytes+key) in output */
					/* lines */
    char		*hptr;		/* pointer to end of header */
    char		*ptr;		/* current position to copy from */
    int			blktype = 0;	/* current block boundary type */
    int			boundary;	/* set if current output char lies on */
					/* block boundary */
    long		i;
    int			j;
    int			l = 0;


    /* defaults */

    progname = basename(argv[0]);
    for (p = progname; *p; p++) {
	    if (*p == '/') {
		    progname = p + 1;
	    }
    }
    nbytes = 1024 * 1024;
    dlen = 73;	/* includes the trailing newline */

    while ((c = getopt(argc, argv, "d:b:")) != EOF) {
	switch (c) {
	case 'd':
	    if (dflags[D_ISSET])
		respec('d', NULL, 0);
	    dflags[D_ISSET] = 1;

	    p = optarg;
	    while (*p != '\0') {
		char *value;
		switch (getsubopt(&p, (constpp)dopts, &value)) {
		case D_NBYTES:
		    if (! value) reqval('d', dopts, D_NBYTES);
		    if (dflags[D_NBYTES]) respec('d', dopts, D_NBYTES);
		    dflags[D_NBYTES] = 1;
		    nbytes = strtol(value, &ptr, 10);
		    if (ptr == value) illegal(value, "d nbytes");
		    break;

		case D_LINELENGTH:
		    if (! value) reqval('d', dopts, D_LINELENGTH);
		    if (dflags[D_LINELENGTH]) respec('d', dopts, D_LINELENGTH);
		    dflags[D_LINELENGTH] = 1;
		    dlen = (int) strtol(value, &ptr, 10) + 1;
		    if (ptr == value) illegal(value, "d linelength");
		    break;

		case D_SEED:
		    if (! value) reqval('d', dopts, D_SEED);
		    if (dflags[D_SEED]) respec('D', dopts, D_SEED);
		    dflags[D_SEED] = 1;
		    dseed = value;
		    break;

		case D_FILE:
		    if (! value) reqval('d', dopts, D_FILE);
		    if (dflags[D_FILE]) respec('d', dopts, D_FILE);
		    dflags[D_FILE] = 1;
		    dfile = value;
		    break;
		    
		default:
		    unknown('d', value);
		}
	    }
	    break;

	case 'b':
	    if (bflags[B_ISSET])
		respec('b', NULL, 0);
	    bflags[B_ISSET] = 1;

	    p = optarg;
	    while (*p != '\0') {
		char *value;
		switch (getsubopt(&p, (constpp)bopts, &value)) {
		case B_512:
		    if (value) illegal(value, "b 512");
		    if (bflags[B_512]) respec('b', bopts, B_512);
		    bflags[B_512] = 1;
		    btypes[0].mark = 1;
		    break;

		case B_1K:
		    if (value) illegal(value, "b 1k");
		    if (bflags[B_1K]) respec('b', bopts, B_1K);
		    bflags[B_1K] = 1;
		    btypes[1].mark = 1;
		    break;

		case B_4K:
		    if (value) illegal(value, "b 4k");
		    if (bflags[B_4K]) respec('b', bopts, B_4K);
		    bflags[B_4K] = 1;
		    btypes[2].mark = 1;
		    break;

		case B_16K:
		    if (value) illegal(value, "b 16k");
		    if (bflags[B_16K]) respec('b', bopts, B_16K);
		    bflags[B_16K] = 1;
		    btypes[3].mark = 1;
		    break;

		default:
		    unknown('b', value);
		    break;
		}
	    }
	    break;

	case '?':
	    usage();
	}
    }

    if (dflags[D_FILE] && optind != argc)
	usage();

    if (! dflags[D_FILE] && argc - optind != 1)
	usage();

    if (! dflags[D_FILE])
	dfile = argv[optind];

    if ((f = fopen(dfile, "w")) == NULL) {
	fprintf(stderr, "fill2: cannot create \"%s\": %s\n",
		dfile, strerror(errno));
	status = 1;
	goto cleanup;
    }

    if (! dflags[D_SEED]) 
	dseed = dfile;

    /* seed is an ascii string */
    seed = 0;
    i = 0;
    while (dseed[i]) {
	seed <<= 8;
	seed |= dseed[i];
	i++;
    }
    srand(seed);


    /* normal data line format: XXXXXXXXXXXX CCCC...CCC CCCCCCCCCCCC...CCC */
    /* block boundary format: CXXXXXXX */

    dbuf = (char *) malloc(dlen + 1);
    hlen = 12+1+strlen(dseed)+1;
    assert(hlen < dlen);
    hptr = dbuf + hlen;

    for (i = 0; i < nbytes; i++) {

	
	/* is this a block boundary? check largest to smallest */
	boundary = 0;
	for (j = N(btypes) - 1; j >= 0; j--) {
	    if (btypes[j].mark) {
		/* first time through or just before a block boundary? */
		if (i == 0 || i % btypes[j].size == btypes[j].size - 1) {
		    boundary = 1;
		    blktype = j;
		    break;
		}
	    }
	}


	/* are there block boundaries to check? */
	/* is this the first time through? */
	/* block boundaries take priority over other output */
	if (bflags[B_ISSET] && (i == 0 || boundary)) {
	    sprintf(bbuf, "%s%c%07ld\n",
		    i ? "\n" : "",
		    btypes[blktype].flag,
		    /* remember i is one less than block boundary */
		    i ? (i+1) / btypes[blktype].size : 0);
	    active = bbuf;
	    ptr = bbuf;
	}
	/* are we at the start of a new line? */
	else if (i == 0
		 || (active == bbuf && *ptr == '\0')
		 || (active == dbuf && l == dlen))
	{
	    sprintf(dbuf, "%012ld %s ", i, dseed);
	    assert(*hptr == '\0');
	    for (ptr = hptr; ptr != dbuf + dlen - 1; ptr++) {
		/* characters upto 126 '~' are used */
		/* do not use !"#$ - leave free for use as block markers */
		*ptr = 37 + (rand() % (127 - 37)); 
	    }
	    *ptr++ = '\n';
	    *ptr = '\0';
	    assert(ptr == dbuf + dlen);
	    active = dbuf;
	    ptr = dbuf;
	    l = 0;
	}
	else {
	    /* continue copying from the active buffer */
	}

	/* output one new character from current buffer */
	if (fputc(*ptr++, f) == EOF) {
	    fprintf(stderr, "fill2: could not write character to \"%s\": %s\n",
		    dfile, strerror(errno));
	    status = 1;
	    goto cleanup;
	}
	if (active == dbuf) l++;

    }

 cleanup:

    /* close file and flush buffers - check if this fails */
    if (fclose(f) != 0) {
	fprintf(stderr, "fill2: fclose() on \"%s\" failed: %s\n",
		dfile, strerror(errno));
	status = 1;
    }
    return status;
}



/*
 * suboption checking functions
 *
 */

static void
illegal(char *value, char *opt)
{
    fprintf(stderr, "%s: Error: Illegal value \"%s\" for -%s option\n",
	    progname, value, opt);
    usage();
}
static void
reqval(char opt, char *tab[], int idx)
{
    fprintf(stderr, "%s: Error: -%c %s option requires a value\n",
	    progname, opt, tab[idx]);
    usage();
}
static void
respec(char opt, char *tab[], int idx)
{
    fprintf(stderr, "%s: Error: ", progname);
    fprintf(stderr, "-%c ", opt);
    if (tab) fprintf(stderr, "%s ", tab[idx]);
    fprintf(stderr, "option respecified\n");
    usage();
}
static void
unknown(char opt, char *s)
{
    fprintf(stderr, "%s: Error: Unknown option -%c %s\n", progname, opt, s);
    usage();
}
static void
usage(void)
{
    fprintf(stderr, "Usage: %s [options] filename\n"
"Options:\n"
"  -d [nbytes=num,linelength=num,   output data properties\n"
"      seed=string,file=name]\n"
"  -b [512,1k,4k,16k]               where to mark block boundaries\n", progname);
    exit(2);
}
