/* Copyright 1997-2007 Red Hat, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2,
 * as published by the Free Software Foundation.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */
#include <alloca.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <libintl.h>
#include <locale.h>
#include <newt.h>
#include <popt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#define _(String) gettext((String))
#define UNIT_FILE_MAX 256

#include "leveldb.h"

/* return 1 on cancel, 2 on error, 0 on success */
static int servicesWindow(struct service * services, int numServices,
			  int levels, int backButton) {
    newtComponent label, subform, ok, cancel;
    newtComponent * checkboxes, form, curr, blank;
    newtComponent sb = NULL;
    newtGrid grid, subgrid, buttons;
    char * states;
    int i, done = 0, update = 0, j;
    int width, height;
    struct newtExitStruct e;
    int count = 0;
    int last = 0;

    newtPushHelpLine(_("Press <F1> for more information on a service."));

    newtGetScreenSize(&width, &height);
    width = width > 80 ? width - 60 : 20;
    height = height > 25 ? height - 17 : 8;


    sb = newtVerticalScrollbar(-1, -1, height, NEWT_COLORSET_CHECKBOX,
				    NEWT_COLORSET_ACTCHECKBOX);

    subform = newtForm(sb, NULL, 0);
    newtFormSetBackground(subform, NEWT_COLORSET_CHECKBOX);
    newtFormSetHeight(subform, height);

    checkboxes = alloca(sizeof(*checkboxes) * numServices);
    states = alloca(sizeof(*states) * numServices);

    for (i = 0; i < numServices; i++, count++) {
        if (last != services[i].type) {
	        newtFormAddComponent(subform, newtCompactButton(-1, count,
                                        services[i].type == TYPE_INIT_D ? "SysV initscripts":
                                        services[i].type == TYPE_XINETD ? "xinetd services":
                                        services[i].type == TYPE_SYSTEMD ? "systemd services":
                                        "Unknown"));
                count++;
                last = services[i].type;
        }
	if (services[i].type == TYPE_XINETD) {
		checkboxes[i] = newtCheckbox(-1, count, services[i].name,
				     services[i].levels ? '*' : ' ', NULL,
				     states + i);
	} else if (services[i].type == TYPE_SYSTEMD) {
		checkboxes[i] = newtCheckbox(-1, count, services[i].name,
				     services[i].enabled ? '*' : ' ', NULL,
				     states + i);
	} else {
		checkboxes[i] = newtCheckbox(-1, count, services[i].name,
					     (services[i].currentLevels & levels) ? '*' : ' ', NULL,
					     states + i);
	}
	newtFormAddComponent(subform, checkboxes[i]);
    }

    newtFormSetWidth(subform, width);

    buttons = newtButtonBar(_("Ok"), &ok, backButton ? _("Back") : _("Cancel"),
			    &cancel, NULL);

    blank = newtForm(NULL, NULL, 0);
    newtFormSetWidth(blank, 2);
    newtFormSetHeight(blank, height);
    newtFormSetBackground(blank, NEWT_COLORSET_CHECKBOX);

    subgrid = newtGridHCloseStacked(NEWT_GRID_COMPONENT, subform,
			            NEWT_GRID_COMPONENT, blank,
			            NEWT_GRID_COMPONENT, sb, NULL);

    label = newtTextboxReflowed(-1, -1,  _("What services should be "
				"automatically started?"), 30, 0, 20, 0);
    grid = newtGridBasicWindow(label, subgrid, buttons);

    form = newtForm(NULL, NULL, 0);
    newtGridAddComponentsToForm(grid, form, 1);
    newtGridWrappedWindow(grid, _("Services"));
    newtGridFree(grid, 1);

    newtFormAddHotKey(form, NEWT_KEY_F1);

    while (!done) {
	newtFormRun(form, &e);

	if (e.reason == NEWT_EXIT_HOTKEY) {
	    if (e.u.key == NEWT_KEY_F12) {
		done = 1;
		update = 1;
	    } else {
		/* must be F1 */
		curr = newtFormGetCurrent(subform);
		for (i = 0; i < numServices; i++)
		    if (curr == checkboxes[i]) break;

		if (i < numServices && services[i].desc)
		    newtWinMessage(services[i].name, _("Ok"), services[i].desc);
	    }
	} else if (e.u.co == ok || e.u.co == cancel) {
	        done = 1;
	        update = (e.u.co == ok);
        }
    }

    newtPopWindow();
    newtFormDestroy(form);

    if (!update) return 1;

    for (i = 0; i < numServices; i++) {
      if (services[i].type == TYPE_XINETD) {
        if ((services[i].enabled && states[i] != '*') ||
	    (!services[i].enabled && states[i] == '*'))
	      setXinetdService(services[i], states[i] == '*');
      } else if (services[i].type == TYPE_SYSTEMD) {
              char *cmd = NULL;
              int en = 0;
              if (services[i].enabled && states[i] != '*')
                      en = 0;
	      else if (!services[i].enabled && states[i] == '*')
                      en = 1;
              else
                      continue;
              asprintf(&cmd, "/usr/bin/systemctl %s %s >/dev/null 2>&1", en ? "enable" : "disable", services[i].name);
              if (cmd == NULL)
                     return 1;
              system(cmd);
              free(cmd);
      }
      else {
	      for (j = 0; j < 7; j++) {
		      if (levels & (1 << j))
                              doSetService(services[i], j, states[i] == '*');
	      }
      }
    }

    return 0;
}

static int serviceNameCmp(const void * a, const void * b) {
    const struct service * first = a;
    const struct service * second = b;

    if (first->type != second->type) {
            return first->type - second->type;
    }

    return strcmp(first->name, second->name);
}

int getSystemdServices(struct service ** servicesPtr, int * numServicesPtr) {
        FILE *sys = NULL;
        char service[UNIT_FILE_MAX + 1];
        int i;
        int r = 0;
        int c = ' ';
        struct service * services = NULL;
        struct service * p = NULL;
        int numServices = 0;
        int numServicesAlloced = 10;

        numServicesAlloced = 10;
        services = malloc(sizeof(*services) * numServicesAlloced);
        if (services == NULL)
                return -ENOMEM;

        sys = popen("systemctl list-unit-files --no-legend --no-pager", "r");
        if (!sys) {
                r = -1;
                goto finish;
        }

        while (c != EOF) {
                int enabled;
                char *suffix;

                for (i = 0; (c = fgetc(sys)) != EOF && c != ' ' && i < UNIT_FILE_MAX; i++)
                        service[i]=c;

                if (i == UNIT_FILE_MAX) {
                        while ((c = fgetc(sys)) != '\n' && c != EOF);
                        continue;
                }

                service[i]='\0';
                if (c == EOF)
                        break;

                while ((c = fgetc(sys)) == ' ');
                if (c == EOF)
                        break;

                enabled = c == 'e' ? 1 :
                          c == 'd' ? 0 : -1;
                while ((c = fgetc(sys)) != '\n' && c != EOF);

                if (enabled == -1)
                       continue;

                if(strrchr(service, '@'))
                        continue;

                suffix = strrchr(service, '.');
                if (suffix == NULL)
                        continue;
                suffix++;
                if (strcmp(suffix, "service") && strcmp(suffix, "socket"))
                        continue;

                if (numServices == numServicesAlloced) {
                    numServicesAlloced += 10;
                    p = realloc(services, numServicesAlloced * sizeof(*services));
                    if (p == NULL) {
                            r = -ENOMEM;
                            goto finish;
                    }
                    services = p;
                }

                bzero(services + numServices, sizeof(struct service));

                services[numServices].name = strdup(service);
                if (services[numServices].name == NULL) {
                        r = -ENOMEM;
                        goto finish;
                }

                readSystemdUnitProperty(service, "Description", &services[numServices].desc);

                services[numServices].enabled = enabled;
                services[numServices].type = TYPE_SYSTEMD;
                numServices++;
        }

        p = realloc (*servicesPtr, (*numServicesPtr + numServices) * sizeof(struct service));
        if (p == NULL) {
                r = -ENOMEM;
                goto finish;
        }
        *servicesPtr = p;
        memcpy(*servicesPtr+*numServicesPtr, services, numServices * sizeof(struct service));
        *numServicesPtr = *numServicesPtr + numServices;


finish:
        if (services)
                free(services);

        if (sys)
                pclose(sys);

        return r;
}

static int getServices(struct service ** servicesPtr, int * numServicesPtr,
		       int backButton, int honorHide) {
    DIR * dir;
    struct dirent * ent;
    struct stat sb;
    char fn[1024];
    struct service * services;
    int numServices = 0;
    int numServicesAlloced, rc;
    int err = 0;
    int systemd = systemdActive();

    numServicesAlloced = 10;
    services = malloc(sizeof(*services) * numServicesAlloced);

    if (!(dir = opendir(RUNLEVELS "/init.d"))) {
	fprintf(stderr, "failed to open " RUNLEVELS "/init.d: %s\n",
		strerror(errno));
        return 2;
    }

    while ((ent = readdir(dir))) {
	if (strchr(ent->d_name, '~') || strchr(ent->d_name, ',') ||
	    (ent->d_name[0] == '.')) continue;

        if (systemd && isOverriddenBySystemd(ent->d_name))
                continue;
	sprintf(fn, RUNLEVELS "/init.d/%s", ent->d_name);
	if (stat(fn, &sb))
	{
		fprintf(stderr, _("error reading info for service %s: %s\n"),
			ent->d_name, strerror(errno));
		continue;
	}
	if (!S_ISREG(sb.st_mode)) continue;

	if (numServices == numServicesAlloced) {
	    numServicesAlloced += 10;
	    services = realloc(services,
				numServicesAlloced * sizeof(*services));
	}

	rc = readServiceInfo(ent->d_name, TYPE_INIT_D, services + numServices, honorHide);

	if (!rc) {
		int i;

		rc = -2;
		for (i = 0 ; i < 7 ; i++) {
			if (isConfigured(ent->d_name, i, NULL, NULL)) {
				rc = 0;
				break;
			}
		}
	}

	if (rc == -1) {
	    fprintf(stderr, _("error reading info for service %s: %s\n"),
			ent->d_name, strerror(errno));
	    continue;
	} else if (!rc)
	    numServices++;
    }

    closedir(dir);

    if (!stat("/usr/sbin/xinetd",&sb)) {
    if (!(dir = opendir(XINETDDIR))) {
	fprintf(stderr, "failed to open " XINETDDIR ": %s\n",
		strerror(errno));
        return 2;
    }

    while ((ent = readdir(dir))) {
	if (strchr(ent->d_name, '~') || strchr(ent->d_name, ',') ||
	    strchr(ent->d_name, '.')) continue;

	sprintf(fn, "%s/%s", XINETDDIR, ent->d_name);
	if (stat(fn, &sb))
	{
		err = errno;
		continue;
	}
	if (!S_ISREG(sb.st_mode)) continue;

	if (numServices == numServicesAlloced) {
	    numServicesAlloced += 10;
	    services = realloc(services,
				numServicesAlloced * sizeof(*services));
	}

	rc = readXinetdServiceInfo(ent->d_name, services + numServices);

	if (rc == -1) {
	    fprintf(stderr, _("error reading info for service %s: %s\n"),
			ent->d_name, strerror(errno));
	    closedir(dir);
	    return 2;
	} else if (!rc)
	    numServices++;
    }

    if (err) {
	fprintf(stderr, _("error reading from directory %s: %s\n"),
		XINETDDIR, strerror(err));
        return 1;
    }
    }

    getSystemdServices(&services, &numServices);

    qsort(services, numServices, sizeof(*services), serviceNameCmp);

    *servicesPtr = services;
    *numServicesPtr = numServices;

    return 0;
}

int main(int argc, const char ** argv) {
    struct service * services;
    int numServices;
    int levels = -1;
    char * levelsStr = NULL;
    char *progName;
    poptContext optCon;
    int rc, backButton = 0, hide = 0;
    struct poptOption optionsTable[] = {
	    { "back", '\0', 0, &backButton, 0 },
	    { "level", '\0', POPT_ARG_STRING, &levelsStr, 0 },
	    { "hide", '\0', 0, &hide, 0 },
	    { 0, 0, 0, 0, 0 }
    };

    setlocale(LC_ALL, "");
    bindtextdomain("chkconfig", "/usr/share/locale");
    textdomain("chkconfig");

    progName = (char *)argv[0];

    if (getuid() != 0) {
	fprintf(stderr, _("You must be root to run %s.\n"),progName);
	exit(1);
    }

    optCon = poptGetContext(progName, argc, argv, optionsTable, 0);
    poptReadDefaultConfig(optCon, 1);

    if ((rc = poptGetNextOpt(optCon)) < -1) {
	fprintf(stderr, "%s: %s\n",
		poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
		poptStrerror(rc));
	exit(1);
    }

    if (levelsStr) {
	levels = parseLevels(levelsStr, 0);
	if (levels == -1) {
	    fprintf(stderr, _("bad argument to --levels\n"));
	    exit(2);
	}
    }

    if (getServices(&services, &numServices, backButton, hide)) return 1;
    if (!numServices) {
	fprintf(stderr, _("No services may be managed by ntsysv!\n"));
	return 2;
    }

    newtInit();
    newtCls();

    newtPushHelpLine(NULL);
    newtDrawRootText(0, 0,
		     "ntsysv " VERSION " - (C) 2000-2001 Red Hat, Inc. ");

    if (levels==-1)
     levels = parseLevels("2345", 0);

    rc = servicesWindow(services, numServices, levels, backButton);

    newtFinished();

    return rc;
}
