/*
 *   Copyright (c) 2000 David Parsons. All rights reserved.
 *   
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *  3. All advertising materials mentioning features or use of this
 *     software must display the following acknowledgement:
 *     
 *   This product includes software developed by David Parsons
 *   (orc@pell.chi.il.us)
 *
 *  4. My name may not be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *     
 *  THIS SOFTWARE IS PROVIDED BY DAVID PARSONS ``AS IS'' AND ANY
 *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAVID
 *  PARSONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 *  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 *  IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 *  THE POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * chkconfig: turn services on and off, and report on the ret of
 *            services.
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <ctype.h>

#include <basis/options.h>

#define RC_M	"/etc/rc.d/rc.m"
#define INIT_D	"/etc/rc.d/init.d"

#define LOCAL	0x01
#define SYSTEM	0x02
#define	BASIC	0x04
#define ALL	0x07
int view = ALL;


/*
 * showconfig() either (a) shows the setting of every system and local
 *              service, or (b) returns an appropriate status to check
 *              the setting of a specified service.
 */
void
showconfig(char *token)
{
    DIR *here;
    struct dirent *d;
    int size = (sizeof INIT_D) + 80;
    char *chk = alloca(size);
    int ret = 1;
    int regular = 0;
    struct stat st;

    if (token == 0) {
	printf("\tFlag\t\t\tState\n"
	       "\t====\t\t\t=====\n"
	       "\n");
    }

    /* first check system services */
    if ( (view&(SYSTEM|BASIC)) && (here = opendir(INIT_D)) != 0 ) {
	if (chdir(INIT_D) != 0) {
	    perror(INIT_D);
	    exit(1);
	}
	for (d = readdir(here); d ; d = readdir(here)) {
	    if (!isalnum(d->d_name[0]))
		continue;
	    if (token && strcmp(d->d_name, token) != 0)
		continue;

	    regular = (lstat(d->d_name, &st)==0 && S_ISREG(st.st_mode));

	    if ( ((view == BASIC) ? 1 : 0) ^ (regular ? 1 : 0) == 0)
		continue;

	    snprintf(chk, size, RC_M "/%s", d->d_name);

	    ret = access(chk, R_OK);

	    if (token)
		exit(ret);
	    else if (view == BASIC)
		printf("\t%-24s%s\n", d->d_name, (ret==0) ? "linked" : "implicit");
	    else
		printf("\t%-24s%s\n", d->d_name, (ret==0) ? "on" : "off");
	}
	closedir(here);
    }

    /* then check our own services */
    if ( (view&LOCAL) && (here = opendir(RC_M)) != 0) {
	for (d = readdir(here); d; d = readdir(here)) {
	    if (!isalnum(d->d_name[0]))
		continue;
	    if (token && strcmp(d->d_name, token) != 0)
		continue;

	    snprintf(chk, size, RC_M "/%s", d->d_name);

	    if (lstat(chk, &st) == 0 && S_ISREG(st.st_mode) ) {
		ret = (st.st_mode & S_IEXEC) ? 0 : 1;

		if (token)
		    exit(ret);
		else
		    printf("\t%-24s%s\n", d->d_name, (ret==0) ? "on" : "off");
	    }
	}
	closedir(here);
    }
    exit(token ? 1 : 0);
} /* showconfig */


/*
 * setconfig() attempts to turn a service on or off, by either setting/dropping
 *             a symbolic link (system service) or by setting/dropping the
 *             execute bit (local service)
 */
void
setconfig(char *service, char *value)
{
    int on = (strcasecmp(value, "on") == 0);
    struct stat st;
    int stval;
    int size = 200 + sizeof INIT_D;
    char *chk = alloca(size);

    char *p;

    /* validate the service */
    for (p=service; *p; ++p)
	if (*p == '/') {
	    fprintf(stderr, "%s: unrecognisable\n", service);
	    exit(1);
	}

    if (chdir(RC_M) != 0) {
	perror(RC_M);
	exit(1);
    }

    if (strcasecmp(value, "off") != 0 && !on) {
	fprintf(stderr, "chkconfig: can't set (%s) to (%s)\n", service, value);
	exit(1);
    }

    if (on) {
	/* set execute bit or link */
	/* check rc.m first, then init.d */

	/* if it's a local service, turn the user execute bit on */
	if ((stval = lstat(service, &st)) == 0 && S_ISREG(st.st_mode)) {
	    /* set local file */
	    st.st_mode |= S_IXUSR;
	    if (chmod(service, st.st_mode & ~S_IFMT) != 0) {
		perror(service);
		exit(1);
	    }
	    exit(0);
	}

	/* if it's a system service, link to it */

	snprintf(chk, size, "../init.d/%s", service);
	if (stat(chk, &st) == 0) {
	    if (stval == 0 && unlink(service) != 0) {
		perror(service);
		exit(1);
	    }
	    if (symlink(chk, service) != 0) {
		perror(service);
		exit(1);
	    }
	    exit(0);
	}
    }
    else {
	/* turn the service off */
	if ( (stval = lstat(service, &st)) == 0) {
	    if (S_ISREG(st.st_mode)) {
		/* local file; un-x it. */

		st.st_mode &= ~(S_IXUSR|S_IXGRP|S_IXOTH);

		if (chmod(service, st.st_mode & ~S_IFMT) != 0) {
		    perror(service);
		    exit(1);
		}
		exit(0);
	    }
	    else if (S_ISLNK(st.st_mode)) {
		/* link to system service; unlink it */
		if (unlink(service) != 0) {
		    perror(service);
		    exit(1);
		}
		exit(0);
	    }
	}
	exit(1);
    }

}

struct x_option opts[] = {
    { 'l', 'l', "local",  0, "view local rc settings" },
    { 's', 's', "system", 0, "view system rc settings" },
    { 'b', 'b', "basic",  0, "view basic rc settings" },
    { '?', '?', "help",   0, "show this help message" },
} ;
#define NR_OPTS (sizeof opts / sizeof opts[0])


main(int argc, char **argv)
{

    int opt;

    x_opterr = 1;

    while ((opt = x_getopt(argc, argv, NR_OPTS, opts)) != EOF) {
	switch (opt) {
	case 'l':
		view = LOCAL;
		break;
	case 's':
		view = SYSTEM;
		break;
	case 'b':
		view = BASIC;
		break;
	default:
	case '?':
		showopts(stderr, NR_OPTS, opts);
		exit( (opt == '?') ? 0 : 1);
	}
    }

    if (x_optind == argc)
	showconfig(0);
    else if (x_optind == argc-1)
	showconfig(argv[x_optind]);
    setconfig(argv[x_optind], argv[x_optind+1]);
}
