/* do: "sh -x bdupdate.c" to make the executable */

/*
 * v1.0 - released to Eric Youngdale 17 jan 1994
 * v1.1 - added exit() statements after two loops, DEBUG, argv0 diags
 * v1.2 - added getopt and a buch of flags to set params
 * v1.3 - Call setsid after each fork, print version number.
 */

#undef DEBUG

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <limits.h>
#include <signal.h>
#include <linux/unistd.h>

/*
 * bdupdate: an attempt to unify the update and bdflush programs; this
 * should be a drop-in replacement for "update", but calls bdflush (if
 * it works) every 5 seconds, too.
 *
 * If invoked with an argument, it prints stats as-per bdflush.
 *
 * - alec <Alec.Muffett@UK.Sun.COM>
 */

_syscall2(int, bdflush, int, func, int, data);

static char * version_string = "1.3";

char *bdparam[] =
{
    "Max fraction of LRU list to examine for dirty blocks",
    "Max number of dirty blocks to write each time bdflush activated",
    "Num of clean buffers to be loaded onto free list by refill_freelist",
    "Dirty block threshold for activating bdflush in refill_freelist",
    "Percentage of cache to scan for free clusters",
    "Time for data buffers to age before flushing",
    "Time for non-data (dir, bitmap, etc) buffers to age before flushing",
    "Time buffer cache load average constant",
    "LAV ratio (used to determine threshold for buffer fratricide).",
    (char *) 0
};

void
inststr(char *dst[], int argc, char *src)
{
    if (strlen(src) <= strlen(dst[0]))
    {
        char *ptr;

        for (ptr = dst[0]; *ptr; *(ptr++) = '\0');

        strcpy(dst[0], src);
    } else
    {
        /* stolen from the source to perl 4.036 (assigning to $0) */
        char *ptr, *ptr2;
        int count;
        ptr = dst[0] + strlen(dst[0]);
        for (count = 1; count < argc; count++) {
            if (dst[count] == ptr + 1)
                ptr += strlen(++ptr);
        }
        if (environ[0] == ptr + 1) {
            for (count = 0; environ[count]; count++)
                if (environ[count] == ptr + 1)
                    ptr += strlen(++ptr);
        }
        count = 0;
        for (ptr2 = dst[0]; ptr2 <= ptr; ptr2++) {
            *ptr2 = '\0';
            count++;
        }
        strncpy(dst[0], src, count);
    }
}

void
usage(argv)
     char ** argv;
{
    fprintf(stderr,"Usage:%s [-d][-s sync-delay][-f flush-delay]\n",argv[0]);
    fprintf(stderr,"            [-0 Max fraction of LRU list to examine for dirty blocks] \n");
    fprintf(stderr,"            [-1 Max number of dirty blocks to write each time bdflush activated] \n");
    fprintf(stderr,"            [-2 Num of clean buffers to be loaded onto free list by refill_freelist] \n");
    fprintf(stderr,"            [-3 Dirty block threshold for activating bdflush in refill_freelist] \n");
    fprintf(stderr,"            [-4 Percentage of cache to scan for free clusters] \n");
    fprintf(stderr,"            [-5 Time for data buffers to age before flushing] \n");
    fprintf(stderr,"            [-6 Time for non-data (dir, bitmap, etc) buffers to age before flushing] \n");
    fprintf(stderr,"            [-7 Time buffer cache load average constant] \n");
    fprintf(stderr,"            [-8 LAV ratio (used to determine threshold for buffer fratricide)] \n");
    exit(1);
}


int
main(int argc, char *argv[])
{
    int sync_delay = 30 , flush_delay = 5;
    int i;
    int go = 0;
    int ch;

    /* shame on whomever wrote getopt - global variables */
    extern char * optarg;
    extern int optind, optopt;

    /* Start by parseing command line options */
    while((ch = getopt(argc, argv, "ds:f:1:2:3:4:5:6:7:8:0:")) != EOF)
        switch(ch) {
        case 'd':
            /* print out the current parameters */
	    printf("bdflush version %s\n", version_string);
            for (i = 0; bdparam[i]; i++)
            {
                int j;
                int data;
                int bdflag;
            
                bdflag = 2 + (i << 1);
            
                j = bdflush(bdflag, (int) &data);
            
                printf("%d: %5d %s\n", i, data, bdparam[i]);
            
                if (j)
                {
                    fprintf(stderr, "bdupdate:(%d) bdflush(%08x,&d)=%d %d\n",
                            i, bdflag, j, errno);
                    break;
                }
            }
	   
            go = 1; 

            break;
	case 's':
	    sync_delay = atoi(optarg);
            break;
	case 'f':
            flush_delay = atoi(optarg);
	    break;
	case '1': case '2': case '3': case '4':
	case '5': case '6': case '7': case '8':
	case '0':

       	    if (i = bdflush(1+((ch - '0' + 1) <<1 ), atoi(optarg)))  
            {
                fprintf(stderr, "bdupdate: bdflush(%d,%d)=%d %d\n",1+((ch - '0') <<1 ), atoi(optarg), i, errno);
	        exit(1);
            }

	    break;
        case ':':
            fprintf(stderr, "Option -%c requires an argument!\n",
                    optopt);
	    usage(argv);
            break;
        default:
            usage(argv);
            break;
        }

    if(go) exit(0);

#ifndef DEBUG
    for (i = 0; i < OPEN_MAX; i++)
    {
        close(i);
    }
#endif

    chdir("/");

    signal(SIGTERM, SIG_IGN);
    signal(SIGINT, SIG_IGN);
    signal(SIGCLD, SIG_IGN);

    if (!fork())                /* fork off a daemon */
    {
        inststr(argv, argc, "bdflush (daemon)");
	setsid();  /* Release terminal */

        if (i = bdflush(0, 0))  /* should never return */
        {
#ifdef DEBUG                    /* this should not happen */
            fprintf(stderr, "bdupdate: bdflush(0,0)=%d %d\n", i, errno);
#endif
        }
        exit(1);
    }

    if (!fork())                /* fork off a daemon */
    {
        int has_bdflush;

        inststr(argv, argc, "update (bdflush)");
	setsid();  /* Release terminal */

        has_bdflush = 1;

        for (;;)                /* flush expired buffers */
        {
            if (has_bdflush)
            {
                sleep(5);

                if (i = bdflush(1, 0))
                {
                    has_bdflush = 0;    /* oopsie! */

                    inststr(argv, argc, "update (sync)");
#ifdef DEBUG
                    fprintf(stderr, "bdupdate: bdflush(1,0)=%d %d\n", i, errno);
#endif
                }
            } else
            {
                sleep(30);

                sync();
            }
        }

        exit(1);
    }
    /* parent exits */
    return (0);
}
