/* $Header: /afs/athena.mit.edu/astaff/project/layerdev/src/xlayer/RCS/layer.c,v 1.4 94/07/18 11:36:33 probe Exp $ */

#include <stdio.h>
#ifdef POSIX
#include <unistd.h>
#else
#include <sys/file.h>
#endif
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/mount.h>
#ifdef SOLARIS
#include <sys/statvfs.h>
#endif
#ifdef hpux
#include <sys/vfs.h>
#endif
#ifdef _AIX
#include <sys/statfs.h>
#endif
#ifdef POSIX
#include <dirent.h>
#else
#include <sys/dir.h>
#endif
#include <pwd.h>
#include "layer.h"


/* Routines to find free space on a filesystem */
#ifdef ultrix
#define fsname fd_req.path
#define fsbavail fd_req.bfreen
#define fsbsize fd_req.bsize
#endif
#ifdef SOLARIS
#define fs_data statvfs
#define fsname f_fstr
#define fsbavail f_bavail
#define fsbsize f_bsize
#define statfs statvfs
#define _S_IFMT S_IFMT
#endif
#ifdef hpux
#define fs_data statfs
#define fsname f_spare
#define fsbavail f_bavail
#define fsbsize f_bsize
#endif
#ifdef _AIX
#define fs_data statfs
#define fsname f_fname
#define fsbavail f_bavail
#define fsbsize f_bsize
#endif

#define M_MIN 1
#define M_STD 2
#define M_LCL 3
#define M_OPT 4
#define M_QUIT 5
#define M_SST 6
#define M_UPD 7
#define M_AUD 8
#define M_MON 9
#define M_REG 10
#define M_SAV 11
#define M_CST 12
#define M_OWN 13
#define M_LOC 14
#define M_DSK 15
#define M_HLP 16
#define M_IGN 17
#define M_RMT 18
#define M_DSP 19
#define M_LOD 20
#define M_RMV 21
#define M_QET 22
#define M_PRT 23


struct menu {
    char *keyword;
    char *desc;
    int code;
} mainmenu[] = {
    { "display", "Display current configuration", M_DSP },
    { "options", "Set other Layered Athena options", M_OPT },
    { "minimum", "Minimal subset confirmation", M_MIN },
    { "standard", "Standard subset configuration", M_STD },
    { "local", "Local subset configuration", M_LCL },
    { "remove", "Remove all subsets", M_RMV },
    { "subsets", "Set status of subsets individually", M_SST },
    { "save", "Save configuration", M_SAV },
    { "load", "Load configuration from alternate file", M_LOD },
    { "update", "Perform workstation update", M_UPD },
    { "quit", "Exit the program", M_QUIT },
    { NULL, NULL, 0 }
}, optmenu[] = {
    { "owner", "Workstation owner(s) email address", M_OWN },
    { "location", "Workstation location", M_LOC },
    { "autoupdate", "Automatically update when new versions available", M_AUD },
    { "monitor", "Allow network monitoring of workstation configuration", M_MON },
    { "register", "Register workstation with Information Systems database", M_REG },
    { "save", "Save workstation configuration in a backup file", M_SAV },
    { "custom", "Run customization script at update time", M_CST },
    { "quiet", "Be quiet about availablity of new release", M_QET },
    { "partition", "Select partition to store layered athena files on", M_PRT },
    { "return", "Return to previous menu", M_QUIT },
    { NULL, NULL, 0 }
}, statmenu[] = {
    { "ignore", "Ignore this subset", M_IGN },
    { "remote", "Use this subset remotely", M_RMT },
    { "local", "Copy this subset to the local disk", M_LCL },
    { "help", "Describe the subset", M_HLP },
    { NULL, NULL, 0 }
}, *sstmenu;

char *status[] = { "[illegal]", "ignored", "remote", "local" };
char *status_cmp[] = { "[illegal]", "ignore", "remote", "local" };


#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif

/* Globals */

int la_autoupdate;
int la_monitor;
int la_register;
int la_notify;
int la_diskspace;
char la_save[256];
char la_custom[256];
char la_owner[256];
char la_location[256];
char la_partition[256];

char *whoami;
char *conf_dir;
char *srvd_dir;
char *layer_athena;
int debug = 0;

extern char *get_config();


/* Main */

void main(argc, argv)
int argc;
char *argv[];
{
    int i, new;
    char buf[256], hostname[64], *p;

    conf_dir = SRC_DIR;
    if (access("/srvd/.rvdinfo", R_OK))
      srvd_dir = SRVD_DIR;
    else
      srvd_dir = "/srvd";
    sprintf(buf, "%s/bin/layer_athena", conf_dir);
    layer_athena = strsave(buf);
    gethostname(hostname, sizeof(hostname));
    whoami = argv[0];
    quotenewlines = 0;

    for (i = 1; i < argc; i++)
      if (!strcmp(argv[i], "-debug"))
	debug = 1;
      else if (!strcmp(argv[i], "-real"))
	debug = 0;

    if (!debug) {
	FILE *in;

	if (geteuid()) {
	    fprintf(stderr, "Must be root to use %s\n", argv[0]);
	    exit(1);
	}
	if ((in = fopen("/etc/athena/version", "r"))) {
	    while (fgets(buf, sizeof(buf), in));
	    if (!strncmp(buf, "Athena", 6)) {
		fprintf(stderr, "You may only install Layered Athena on a workstation not already\ninstalled with the full Athena release.\n");
		exit(1);
	    }
	    fclose(in);
	}
    }

    /* Load subset config */
    sprintf(buf, "%s/%s", conf_dir, CONF_FILE);
    if (read_syspack(buf)) {
	fprintf(stderr, "Unable to read system configuration.\n");
    }
    for (i = 0; sp_subsets[i]; i++);    /* count subsets */
    sstmenu = (struct menu *) malloc((i+3) * sizeof (struct menu));
    for (i = 0; sp_subsets[i]; i++) {
	sstmenu[i].keyword = sp_subsets[i]->name;
	sprintf(buf, "Change status or get info about subset %s",
		sp_subsets[i]->name);
	sstmenu[i].desc = strsave(buf);
	sstmenu[i].code = 100 + i;
    }
    sstmenu[i].keyword = "summary";
    sstmenu[i].desc = "Display current configuration and disk usage";
    sstmenu[i++].code = M_DSK;
    sstmenu[i].keyword = "return";
    sstmenu[i].desc = "Return to previous menu";
    sstmenu[i++].code = M_QUIT;
    sstmenu[i].code = 0;

    /* Load workstation config */
    set_config("");
    do_load();
    if (strlen(la_owner) == 0 && strlen(la_location) == 0)
      new = 1;

    printf("Welcome to Layered Athena\n");

    /* Init config for new workstations */
    if (new) {
	struct passwd *pwd;
	uid_t uid;

	la_autoupdate = la_monitor = la_register = 1;
	printf("As this program asks questions, just pressing RETURN will enter\n");
	printf("the default value shown in [brackets].  To make one of these\n");
	printf("answers empty, type a space first.\n\n");

	uid = getuid();
	if (!uid) uid = geteuid();
	if (uid && (pwd = getpwuid(uid)))
	  sprintf(la_owner, "%s@mit.edu", pwd->pw_name);
	else
	  sprintf(la_owner, "root@%s", hostname);
	GetString("Workstation owner (email address)", la_owner);
	la_location[0] = 0;
	GetString("Workstation location", la_location);
    }

    /* Enter menu system */
    while (1) {
	switch (getmenu(mainmenu, "Main menu")) {
	case M_MIN:
	    sp_subsets[0]->state = sp_subsets[1]->state = ST_LOCAL;
	    sp_subsets[2]->state = sp_subsets[4]->state = 
	      sp_subsets[6]->state = sp_subsets[10]->state = 
	      sp_subsets[8]->state = sp_subsets[9]->state = 
	      sp_subsets[7]->state = ST_REMOTE;
	    sp_subsets[3]->state = sp_subsets[5]->state =
	      sp_subsets[11]->state = sp_subsets[12]->state = ST_IGNORE;
	    break;
	case M_STD:
	    sp_subsets[0]->state = sp_subsets[1]->state = 
	      sp_subsets[2]->state = sp_subsets[5]->state = ST_LOCAL;
	    sp_subsets[3]->state = sp_subsets[4]->state = 
	      sp_subsets[6]->state = sp_subsets[7]->state = 
	      sp_subsets[8]->state = sp_subsets[9]->state = 
	      sp_subsets[10]->state = sp_subsets[11]->state = ST_REMOTE;
	    sp_subsets[12]->state = ST_IGNORE;
	    break;
	case M_LCL:
	    sp_subsets[0]->state = sp_subsets[1]->state = 
	      sp_subsets[2]->state = sp_subsets[3]->state = 
	      sp_subsets[4]->state = sp_subsets[5]->state = 
	      sp_subsets[6]->state = sp_subsets[7]->state = 
	      sp_subsets[8]->state = sp_subsets[9]->state = 
	      sp_subsets[10]->state = sp_subsets[11]->state = ST_LOCAL;
	    sp_subsets[12]->state = ST_IGNORE;
	    break;
	case M_RMV:
	    sp_subsets[0]->state = sp_subsets[1]->state = 
	      sp_subsets[2]->state = sp_subsets[3]->state = 
	      sp_subsets[4]->state = sp_subsets[5]->state = 
	      sp_subsets[6]->state = sp_subsets[7]->state = 
	      sp_subsets[8]->state = sp_subsets[9]->state = 
	      sp_subsets[10]->state = sp_subsets[11]->state = 
	      sp_subsets[12]->state = ST_IGNORE;
	    break;
	case M_DSP:
	    printf("Workstation %s, owner \"%s\", location \"%s\"\n",
		   hostname, la_owner, la_location);
	    show_subsets();
	    break;
	case M_OPT:
	    do_options();
	    break;
	case M_SST:
	    do_subsets();
	    break;
	case M_SAV:
	    strcpy(buf, DEFAULT_CONF);
	    GetString("Path of file to save configuration in", buf);
	    do_save(buf);
	    break;
	case M_LOD:
	    strcpy(buf, DEFAULT_CONF);
	    GetString("Path of file to load configuration from", buf);
	    clear_config();
	    set_config(buf);
	    do_load();
	    break;
	case M_QUIT:
	    if (!AskQuestion("Save any configuration changes first", TRUE))
	      exit(0);
	    /* fall through to: */
	case M_UPD:
	    do_update();
	    exit(1);
	default:
	    fprintf(stderr, "How did we get here?  Program internal error in main menu parse\n");
	    exit(2);
	}
    }
}


do_update()
{
    char buf[256];

    if (!AskQuestion("Are you sure you want to reconfigure the workstation now",
		     0))
      return;
    do_save("");
    sprintf(buf, "%s update syspack=\"%s\"", layer_athena, srvd_dir);
    if (debug)
      fprintf(stderr, "Executing: %s\n", buf);
    else
      system(buf);
    if (AskQuestion("Reboot this workstation now", 1))
      system("sync; reboot");
}


do_save(file)
char *file;
{
    char buf[256], cmd[1024];
    char *p;
    int i;

    sprintf(cmd, "%s set syspack=\"%s\"", layer_athena, srvd_dir);

    if (file && *file) {
	sprintf(buf, " config=\"%s\"", file);
	strcat(cmd, buf);
    }
    if (strcmp(la_owner, get_config("owner"))) {
	sprintf(buf, " owner=\"%s\"", la_owner);
	strcat(cmd, buf);
    }
    if (strcmp(la_location, get_config("location"))) {
	sprintf(buf, " location=\"%s\"", la_location);
	strcat(cmd, buf);
    }
    p = get_config("custom");
    if (!*la_custom && p && *p)
      strcat(cmd, " nocustom");
    else if (*la_custom && strcmp(la_custom, p)) {
	sprintf(buf, " custom=\"%s\"", la_custom);
	strcat(cmd, buf);
    }
    p = get_config("save");
    if (!*la_save && p && *p)
      strcat(cmd, " nosave");
    else if (*la_save && strcmp(la_save, p)) {
	sprintf(buf, " save=\"%s\"", la_save);
	strcat(cmd, buf);
    }
    p = get_config("monitor");
    if (!p || (!la_monitor && atoi(p)) || (la_monitor && !atoi(p)))
      strcat(cmd, la_monitor ? " monitor" : " nomonitor");
    p = get_config("register");
    if (!p || (!la_register && atoi(p)) || (la_register && !atoi(p)))
      strcat(cmd, la_register ? " register" : " noregister");
    p = get_config("autoupdate");
    if (!p || (!la_autoupdate && atoi(p)) || (la_autoupdate && !atoi(p)))
      strcat(cmd, la_autoupdate ? " autoupdate" : " noautoupdate");
    if (la_notify) {
	sprintf(buf, " notify=%s", sp_version);
	strcat(cmd, buf);
    }

    for (i = 0; sp_subsets[i]; i++) {
	sprintf(buf, "subset %s", sp_subsets[i]->name);
	p = get_config(buf);
	if ((!p || strcmp(p, status_cmp[sp_subsets[i]->state])) &&
	    (p && strcmp(p, "ignored") || p && *p)) {
	    sprintf(buf, " %s=%s", sp_subsets[i]->name,
		    status_cmp[sp_subsets[i]->state]);
	    strcat(cmd, buf);
	}
    }

    if (debug)
      fprintf(stderr, "Executing: %s\n", cmd);
    else
      system(cmd);
    clear_config();
}


do_load()
{
    int i;
    char *p, buf[256];
    struct fs_data stf;

    p = get_config("autoupdate");
    la_autoupdate = p ? atoi(p) : 0;
    p = get_config("monitor");
    la_monitor = p ? atoi(p) : 0;
    p = get_config("register");
    la_register = p? atoi(p) : 0;
    la_notify = 0;
    if (p = get_config("save"))
      strcpy(la_save, p);
    else
      la_save[0] = 0;
    if (p = get_config("custom"))
      strcpy(la_custom, p);
    else
      la_custom[0] = 0;
    if (p = get_config("owner"))
      strcpy(la_owner, p);
    else
      la_owner[0] = 0;
    if (p = get_config("location"))
      strcpy(la_location, p);
    else
      la_location[0] = 0;
    for (i = 0; sp_subsets[i]; i++) {
	sprintf(buf, "subset %s", sp_subsets[i]->name);
	p = get_config(buf);
	if (!p)
	  sp_subsets[i]->state = ST_IGNORE;
	else if (!strcmp(p, "remote"))
	  sp_subsets[i]->state = ST_REMOTE;
	else if (!strcmp(p, "local"))
	  sp_subsets[i]->state = ST_LOCAL;
	else
	  sp_subsets[i]->state = ST_IGNORE;
    }
    
    /* Now do the free disk space and partition labels */
    sprintf(buf, "%s/.", CONF_DIR);
    strcpy(stf.fsname, "?");
    if (statfs(buf, &stf) != 1) {
	if (debug) {
	    statfs("/usr", &stf);
	} else {
	    mkdir("/usr/layered_athena", 0777);
	    symlink("/usr/layered_athena", CONF_DIR);
	    statfs(buf, &stf);
	}
    }
    strcpy(la_partition, stf.fsname);
    la_diskspace = stf.fsbavail * stf.fsbsize / 1024 + disk_usage(buf);
}


int disk_usage(dir)
char *dir;
{
    struct stat stb;
    int total;
    DIR *d;
#ifdef vax
    struct direct *de;
#else
    struct dirent *de;
#endif
    char buf[256];

    if (lstat(dir, &stb))
      return(0);
    total = (stb.st_size + 1023) / 1024;

    if (!S_ISDIR(stb.st_mode))
      return (total);

    if (!(d = opendir(dir)))
      return(total);
    while (de = readdir(d))
      if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) {
	  sprintf(buf, "%s/%s", dir, de->d_name);
	  total += disk_usage(buf);
      }

    closedir(d);
    return(total);
}


do_options()
{
    int quit = 0;
    char buf[256];
    struct stat stb, stb2;
    struct fs_data stf;

    while (!quit) {
	switch (getmenu(optmenu, "Options menu")) {
	case M_AUD:
	    la_autoupdate = AskQuestion("Allow updates to happen automatically when new software\nis available", la_autoupdate);
	    break;
	case M_MON:
	    la_monitor = AskQuestion("Allow network monitoring of the configuration of this workstation", la_monitor);
	    break;
	case M_REG:
	    la_register = AskQuestion("Register this workstation in the central database maintained by Information\nSystems", la_register);
	    break;
	case M_SAV:
	    if (AskQuestion("Save an extra copy of the workstation config",
			    la_save[0]))
	      GetString("Path of file to save configuration in", la_save);
	    else
	      la_save[0] = 0;
	    break;
	case M_CST:
	    if (AskQuestion("Run a customization script at update time",
			    la_custom[0])) 
	      GetString("Path of customization script", la_custom);
	    else
	      la_custom[0] = 0;
	    break;
	case M_OWN:
	    GetString("Email address of workstation owner", la_owner);
	    break;
	case M_LOC:
	    GetString("Campus room number where workstation is", la_location);
	    break;
	case M_QUIT:
	    quit = 1;
	    break;
	case M_QET:
	    la_notify = AskQuestion("Be quiet about the availability of the new release", la_notify);
	    break;
	case M_PRT:
	    GetString("Partition to store layered athena files on",
		      la_partition);
	    /* Now do the free disk space and partition labels */
	    sprintf(buf, "%s/.", CONF_DIR);
	    if (statfs(buf, &stf) < 0) {
		fprintf(stderr, "Unable to find directory %s\n", buf);
		break;
	    }
	    if (!strcmp(stf.fsname, la_partition))
	      break;
	    if (debug) {
		printf("In debug mode, not changing partition by linking /var/athena to %s/layered_athena\n", la_partition);
		break;
	    }

	    lstat(CONF_DIR, &stb);
	    sprintf(buf, "%s/layered_athena", la_partition);
	    if (lstat(buf, &stb2)) {
		lstat(la_partition, &stb2);
	    }
	    if ((stb.st_mode & _S_IFMT) == S_IFLNK) {
		unlink(CONF_DIR);
		if (stb.st_dev == stb2.st_dev) {
		    mkdir(CONF_DIR, 0777);
		} else {
		    mkdir(buf, 0777);
		    symlink(buf, CONF_DIR);
		}
	    } else if (stb.st_dev != stb2.st_dev) {
		sprintf(buf, "%s.old", CONF_DIR);
		link(CONF_DIR, buf);
		unlink(CONF_DIR);
		sprintf(buf, "%s/layered_athena", la_partition);
		mkdir(buf, 0777);
		symlink(buf, CONF_DIR);
	    }
	    statfs(buf, &stf);
	    strcpy(la_partition, stf.fsname);
	    la_diskspace = stf.fsbavail * stf.fsbsize / 1024 + disk_usage(buf);
	    break;
	default:
	    fprintf(stderr, "How did we get here?  Program internal error in option menu parse\n");
	    exit(2);
	}
    }
}


do_subsets()
{
    int i, quit = 0;
    char buf[256];

    while (!quit) {
	switch (i = getmenu(sstmenu, "Subset menu")) {
	case M_QUIT:
	    quit = 1;
	    break;
	case M_DSK:
	    printf("Disk requirements of this configuration:\n");
	    show_subsets();
	    break;
	default:
	    i -= 100;
	    sprintf(buf, "Subset %s is currently %s",
		    sp_subsets[i]->name, status[sp_subsets[i]->state]);
	    switch (getmenu(statmenu, buf)) {
	    case M_HLP:
		printf(sp_subsets[i]->desc);
		waitforuser();
		break;
	    case M_IGN:
		sp_subsets[i]->state = ST_IGNORE;
		break;
	    case M_RMT:
		if (sp_subsets[i]->flags & F_LOCALONLY) {
		    printf("Sorry, this subset may not be used remotely.\n");
		    waitforuser();
		    break;
		}
		sp_subsets[i]->state = ST_REMOTE;
		break;
	    case M_LCL:
		sp_subsets[i]->state = ST_LOCAL;
		break;
	    }
	}
    }
}


show_subsets()
{
    int i, usage, total = 0;

    printf("  %-16s %6s  %s\n", "Subset", "Size", "Status");
    for (i = 0; sp_subsets[i]; i++) {
	usage = 0;
	if (sp_subsets[i]->state == ST_REMOTE)
	  usage = sp_subsets[i]->remotesize;
	else if (sp_subsets[i]->state == ST_LOCAL)
	  usage = sp_subsets[i]->localsize;
	printf("  %-16s %6d  %s\n", sp_subsets[i]->name,
	       usage, status[sp_subsets[i]->state]);
	total += usage;
    }
    printf("%-16s   %6d  Kbytes\n", "Total", total);
    printf("  %d Kbytes free on partition %s\n", la_diskspace, la_partition);
    waitforuser();
}


/* Do a menu */

int getmenu(m, title)
struct menu *m;
char *title;
{
    int i, matches, match;
    char buf[256];

    printf("\n%s\n", title);
    for (i = 0; m[i].code; i++)
      printf("  %-12s  %s\n", m[i].keyword, m[i].desc);

    buf[0] = 0;
    GetString("Your choice", buf);

    matches = 0;
    for (i = 0; m[i].code; i++)
      if (!strncmp(m[i].keyword, buf, strlen(buf))) {
	  matches++;
	  match = i;
      }
    if (matches == 1)
      return(m[match].code);
    if (matches == 0)
      printf("That does not match any choice.\n");
    else
      printf("That is ambiguous.  Please type enough of your choice to differentiate it from\nthe other choices.\n");
    waitforuser();
    return(getmenu(m, title));
}


/* Return TRUE or FALSE after asking a Yes/No question */

AskQuestion(prompt, def)
char *prompt;
int def;
{
    char buf[256], val[32];

    sprintf(buf, "%s (Y/N)", prompt);
    if (def)
      strcpy(val, "Y");
    else
      strcpy(val, "N");
    GetString(buf, val);
    if (*val == 'N' || *val == 'n')
      return(FALSE);
    if (*val == 'Y' || *val == 'y')
      return(TRUE);
    printf("Please answer Yes or No.\n");
    return(AskQuestion(prompt, def));
}


/* Wait for a user response */

waitforuser()
{
    char buf[256];

    buf[0] = 0;
    GetString("Press return to continue", buf);
}


/* Ask the user a question, get response with default answer */

GetString(prompt, value)
char *prompt;
char *value;
{
    char buf[256];

    if (*value)
      printf("%s [%s]: ", prompt, value);
    else
      printf("%s: ", prompt);

    fgets(buf, sizeof(buf), stdin);
    if ((*buf == 0 || *buf == '\n') && *value)
      /* take default */
      return;

    if (buf[strlen(buf)-1] == '\n')
      buf[strlen(buf)-1] = 0;
    strcpy(value, buf);
    return;
}
