/*
 *  Simple update daemon for Linux.
 *  Copyright (c) 1996, 1997, 1999 Torsten Poulin.
 *
 *  This program 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, or (at your option)
 *  any later version.
 *
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <stdlib.h>
#include <signal.h>
#include <getopt.h>
#include <syslog.h>

#if defined(__GLIBC__) && __GLIBC__ >= 2
#   include <sys/kdaemon.h>
#else
#   include <asm/unistd.h> /* FIXME! */
    _syscall2(int, bdflush, int, func, int, data);
#endif

#ifndef RESTRICT_TO_ROOT
#   define RESTRICT_TO_ROOT 1
#endif

#define VERSTR "update 2.11\n"

#define HELPSTR \
"Usage: %s [OPTION]\n\n"\
"  -S, --use-sync             force use of sync(2) instead of flushing\n"\
"  -s, --sync-interval SECS   call sync this often (default 30)\n"\
"  -f, --flush-interval SECS  flush some buffers this often (default 5)\n"\
"      --help                 display this text and exit\n"\
"      --version              output version information and exit\n\n"\
"--sync-interval implies --use-sync.\n"


/* OPEN_MAX is POSIX.1 */
#ifndef OPEN_MAX
#   define OPEN_MAX 256		/* anybody's guess */
#endif

#ifndef lint
static const char vcid[] = "$Id: update.c,v 1.10 1999/05/23 22:59:16 tp Exp $";
#endif /* lint */

static unsigned int sync_duration = 30;
static unsigned int flush_duration = 5;
static int use_sync = 0;

static void usage(char *name)
{
    fprintf(stderr, "Try `%s --help\' for more information.\n", name);
    exit(2);
}

/*
 * The update() function never returns.
 *
 * In Linux 2.2.8, user space update is unnecessary and bdflush(1, 0)
 * always returns an error.  If this happens, we just log it and exit.
 *
 * This new behavior in 2.2.8 was actually backed out in 2.2.9, but is
 * expected to go in again later.
 */

static void update(void)
{
    for (;;) {
	if (use_sync) {
	    sleep(sync_duration);
	    sync();
	} else {
	    sleep(flush_duration);
	    if (bdflush(1, 0) < 0) {
		openlog("update", LOG_CONS, LOG_DAEMON);
		syslog(LOG_INFO,
		       "This kernel does not need update(8). Exiting.");
		closelog();
		exit(0);
	    }
	}
    }
}

int main(int argc, char **argv)
{
    int f;

    for (;;) {
	int option_index = 0;
	int c;
	static struct option long_options[] = {
	    { "use-sync", 0, 0, 'S' },
	    { "sync-interval", 1, 0, 's' },
	    { "flush-interval", 1, 0, 'f' },
	    { "help", 0, 0, 'h' },
	    { "version", 0, 0, 'V' },
	    { 0, 0, 0, 0 }
	};

	c = getopt_long(argc, argv, "Ss:f:", long_options, &option_index);
	if (c == -1)
	    break;
	/* Long option? */
        if (c == 0)
	    c = long_options[option_index].val;

	switch (c) {
	case 'S':
	    use_sync = 1;
	    break;
	case 's':
	    if ((sync_duration = atoi(optarg)) <= 0) {
		fprintf(stderr, "%s: argument must be positive integer\n",
			argv[0]);
		exit(2);
	    }
	    use_sync = 1;
	    break;
	case 'f':
	    if ((flush_duration = atoi(optarg)) <= 0) {
		fprintf(stderr, "%s: argument must be a positive integer\n",
			argv[0]);
		exit(2);
	    }
	    break;
	case 'h':
	    printf(HELPSTR, *argv);
            exit(0);
        case 'V':
            printf(VERSTR);
            exit(0);
	case '?':
	    usage(argv[0]);
	default:
	    abort();
	}
    }

    if (optind < argc)
	usage(argv[0]);

#if RESTRICT_TO_ROOT
    /* Keep people from launching more update daemons.
       Might as well call sync().  */
    if (geteuid() != 0) {
	sync();
	fprintf(stderr, "%s: should only be run by root.\n", argv[0]);
	exit(1);
    }
#endif

    /* Ignore a few signals for good measure. */
    signal(SIGTERM, SIG_IGN);
    signal(SIGINT, SIG_IGN);
    
    if (fork() > 0)
	exit(0);
  
    /* Become session leader (get rid of controlling terminal). */
    setsid();

    /* Make sure we are on a file system that stays mounted. */
    chdir("/");
    
    for (f = 0; f < OPEN_MAX; f++)
	close(f);
    
    update();
    
    return 0;
}


