/* $Header: /afs/sipb.mit.edu/project/sipb-athena/repository/src/moira/gen/nfs.dc,v 1.4 1996/06/02 07:42:32 ghudson Exp $
 *
 * This generates the files necessary to load an nfs server.
 *
 *  (c) Copyright 1988, 1990 by the Massachusetts Institute of Technology.
 *  For copying and distribution information, please see the file
 *  <mit-copyright.h>.
 */

#include <mit-copyright.h>
#include <stdio.h>
#include <moira.h>
#include <moira_site.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
EXEC SQL INCLUDE sqlca;


#define min(x,y)	((x) < (y) ? (x) : (y))

char *whoami = "nfs.gen";
char *malloc(), *strsave();
char nfs_dir[64];
struct hash *users, *groups;


main(argc, argv)
int argc;
char **argv;
{
    char cmd[64];
    struct stat sb;
    int changed = 0;

    if (argc > 2) {
	fprintf(stderr, "usage: %s [outfile]\n", argv[0]);
	exit(MR_ARGS);
    }

    initialize_sms_error_table();
    sprintf(nfs_dir, "%s/nfs", DCM_DIR);

#ifsql INGRES
    EXEC SQL CONNECT moira;
    EXEC SQL SET LOCKMODE SESSION WHERE LEVEL=TABLE, READLOCK=SHARED;
#endsql
#ifsql INFORMIX
    EXEC SQL DATABASE moira;
#endsql

    changed = do_nfs();

#ifsql INGRES
    EXEC SQL DISCONNECT;
#endsql
#ifsql INFORMIX
    EXEC SQL CLOSE DATABASE;
#endsql

    if (!changed) {
	fprintf(stderr, "No files updated.\n");
	if (argc == 2 && stat(argv[1], &sb) == 0)
	  exit(MR_NO_CHANGE);
    }

    if (argc == 2) {
	sprintf(cmd, "cd %s; cp %s/nfs/* .; tar cf %s .",
		nfs_dir, SMS_DIR, argv[1]);
	if (system(cmd))
	  exit(MR_TAR_FAIL);
    }

    exit(MR_SUCCESS);
}


/* Generate the files.  Returns zero if nothing changed, non-zero otherwise. */

int do_nfs()
{
    EXEC SQL BEGIN DECLARE SECTION;
    char machname[41], listname[33];
    EXEC SQL END DECLARE SECTION;
    struct save_queue *machs, *lists;
    int changed;

    machs = sq_create();
    lists = sq_create();

    /* The following is declarative, not executed,
     * and so is dependent on where it is in the file,
     * not in the order of execution of statements.
     */
    EXEC SQL WHENEVER SQLERROR GOTO sqlerr;
    EXEC SQL DECLARE s_cursor CURSOR FOR
      SELECT m.name, s.value3
      FROM machine m, serverhosts s
      WHERE m.mach_id = s.mach_id AND s.service = 'NFS' AND s.enable != 0;
    EXEC SQL OPEN s_cursor;
    while (1) {
        EXEC SQL FETCH s_cursor INTO :machname, :listname;
        if (sqlca.sqlcode != 0) break;
        sq_save_unique_string(machs, strsave(strtrim(machname)));
        sq_save_unique_string(lists, strsave(strtrim(listname)));
      }
    EXEC SQL CLOSE s_cursor;

    changed = do_lists(lists);
    EXEC SQL COMMIT;
    changed += do_machs(machs);
    return(changed);
 sqlerr:
    com_err(whoami, MR_INGRES_ERR, " code %d\n", sqlca.sqlcode);
    critical_alert("DCM", "NFS build encountered DATABASE ERROR %d",
		   sqlca.sqlcode);
    exit(MR_INGRES_ERR);
}


/* Make all of the credentials lists that will be needed.  Returns 0 if
 * no files were actually changed */

int do_lists(lists)
struct save_queue *lists;
{
    char file[64], *u;
    struct stat sb;
    FILE *fd;
    EXEC SQL BEGIN DECLARE SECTION;
    char *listname, *lsname, lname[33], uname[9];
    int uid, id, flag1, flag2, flag3, flag4;
    EXEC SQL END DECLARE SECTION;

    sprintf(file, "%s/list-", nfs_dir);
    /*
    if (stat(file, &sb) == 0) {
	if ((ModDiff (&flag1, "users", sb.st_mtime)) ||
	    (ModDiff (&flag2, "list", sb.st_mtime)) ||
	    (ModDiff (&flag3, "imembers", sb.st_mtime)) ||
	    (ModDiff (&flag4, "serverhosts", sb.st_mtime))) exit (MR_DATE);
	if (flag1 < 0 && flag2 < 0 && flag3 < 0 && flag4 < 0) {
	    fprintf(stderr, "The lists do not need to be rebuilt.\n");
	    return(0);
	}
    }
    */

    /* get locks */
    EXEC SQL SELECT modtime INTO :lname FROM list WHERE list_id = 0;
    EXEC SQL SELECT modtime INTO :lname FROM users WHERE users_id = 0;

    /* build the list of everyone, and store it in a file whose name
     * corresponds to the empty list.
     */
    do_everyone();

    fprintf(stderr, "Building specific lists\n");
    /* now do each of the lists used by an NFS server */

    while (sq_get_data(lists, &listname)) {
	if (strlen(listname) == 0)
	  continue;
	sprintf(file, "%s/list-%s", nfs_dir, listname);
	fd = fopen(file, "w");
	if (!fd) {
	    fprintf(stderr, "cannot open %s for output\n", file);
	    exit(MR_OCONFIG);
	}

	EXEC SQL DECLARE m_cursor CURSOR FOR
	  SELECT m.member_id
	  FROM imembers m, list l
	  WHERE m.list_id=l.list_id AND l.name = :listname AND
	    m.member_type='USER'
	  ORDER BY member_id;
	EXEC SQL OPEN m_cursor;
	while (1) {
	    EXEC SQL FETCH m_cursor INTO :id;
	    if (sqlca.sqlcode != 0) break;
	    if (u = hash_lookup(users, id))
	      fprintf(fd, "%s\n", u);
	}
	EXEC SQL CLOSE m_cursor;
	if (fclose(fd) == EOF) {
	    fprintf(stderr, "error closing %s\n", file);
	    exit(MR_CCONFIG);
	}
    }
/* don't free here either
    sq_destroy(lists);
 */
    return(1);
 sqlerr:
    com_err(whoami, MR_INGRES_ERR, " code %d\n", sqlca.sqlcode);
    critical_alert("DCM", "NFS build encountered DATABASE ERROR %d",
		   sqlca.sqlcode);
    exit(MR_INGRES_ERR);
}


/*  Build the list of everybody. */
struct grp {
    struct grp *next;
    char *lid;
};
struct user {
    char name[9];
    int uid;
    struct grp *lists;
};


do_everyone()
{
    char buf[BUFSIZ], *l;
    struct user *u;
    struct grp *g;
    struct bucket *b, **p;
    EXEC SQL BEGIN DECLARE SECTION;
    char name[33];
    int gid, id, lid, maxid, uid;
    EXEC SQL END DECLARE SECTION;
    FILE *fd;
    int i;
    struct save_queue *sq;

    fprintf(stderr, "Building the list of everybody\n");
    sprintf(buf, "%s/list-", nfs_dir);
    fd = fopen(buf, "w");
    if (!fd) {
	fprintf(stderr, "cannot open %s for output\n", buf);
	exit(MR_OCONFIG);
    }

    /* make space for group list */
    groups = create_hash(15000);

    /* retrieve simple groups */
    EXEC SQL DECLARE l_cursor CURSOR FOR
     SELECT gid, list_id
     FROM list
     WHERE grouplist != 0 AND active != 0
     ORDER BY list_id;
    EXEC SQL OPEN l_cursor;
    while (1) {
        EXEC SQL FETCH l_cursor INTO :gid, :lid;
        if (sqlca.sqlcode != 0) break;
        sprintf(buf, ":%d", gid);
        hash_store(groups, lid, strsave(buf));
      }
    EXEC SQL CLOSE l_cursor;

    /* now do grplists */
    users = create_hash(10000);
    EXEC SQL DECLARE u_cursor CURSOR FOR
      SELECT users_id, login, uid
      FROM users
      WHERE status = 1
      ORDER BY users_id;
    EXEC SQL OPEN u_cursor;
    while (1) {
        EXEC SQL FETCH u_cursor INTO :id, :name, :uid;
        if (sqlca.sqlcode != 0) break;
        u = (struct user *) malloc(sizeof(struct user));
        strcpy(u->name, strtrim(name));
        u->uid = uid;
        u->lists = NULL;
        hash_store(users, id, u);
      }
    EXEC SQL CLOSE u_cursor;

    EXEC SQL DECLARE m_cursor2 CURSOR FOR
      SELECT list_id, member_id
      FROM imembers
      WHERE member_type = 'USER'
      ORDER BY list_id;
    EXEC SQL OPEN m_cursor2;
    while (1) {
        EXEC SQL FETCH m_cursor2 INTO :lid, :id;
        if (sqlca.sqlcode != 0) break;
        if ((u = (struct user *) hash_lookup(users, id)) &&
	    ((l = hash_lookup(groups, lid)) != NULL)) {
	    g = (struct grp *) malloc(sizeof(struct grp));
	    g->next = u->lists;
	    u->lists = g;
	    g->lid = l;
	  }
      }
    EXEC SQL CLOSE m_cursor2;

    for (p = &(users->data[users->size - 1]); p >= users->data; p--) {
	for (b = *p; b; b = b->next) {
	    u = (struct user *)b->data;
	    sprintf(buf, "%s:%d", u->name, u->uid);
	    for (g = u->lists; g; g = g->next)
	      strcat(buf, g->lid);
	    b->data = strsave(buf);
	    fprintf(fd, "%s\n", buf);
	}
    }

    fclose(fd);
    free(groups);
    return(1);
 sqlerr:
    com_err(whoami, MR_INGRES_ERR, " code %d\n", sqlca.sqlcode);
    critical_alert("DCM", "NFS build encountered DATABASE ERROR %d",
		   sqlca.sqlcode);
    exit(MR_INGRES_ERR);
}


/* Now do each of the servers, linking the credentials list file and 
 * compiling the quota and dirs files.
 */

int do_machs(machs)
struct save_queue *machs;
{
    EXEC SQL BEGIN DECLARE SECTION;
    char *machname, listname[33], dev[33], dir[81], fstype[9];
    int uid, quota, id, gid, flag1, flag2, flag3, flag4;
    EXEC SQL END DECLARE SECTION;
    char file[64], f1[64], f2[64], *cp, *index();
    int prevuid, quotasum, olddev, oldmach;
    FILE *fd;
    struct hash *machines;

    fprintf(stderr, "Building machine files\n");

    /* acquire locks on machines & filesystems */
    EXEC SQL SELECT modtime INTO :listname FROM users WHERE users_id = 0;
    EXEC SQL SELECT modtime INTO :listname FROM machine WHERE mach_id = 0;
    EXEC SQL SELECT modtime INTO :listname FROM filesys WHERE filsys_id = 0;

    machines = create_hash(100);
    while (sq_get_data(machs, &machname)) {
	EXEC SQL SELECT s.value3, m.mach_id
	  INTO :listname, :id
	  FROM serverhosts s, machine m
	  WHERE s.mach_id = m.mach_id AND m.name = :machname AND 
	    s.service = 'NFS';
	strtrim(machname);
	sprintf(f1, "%s/list-%s", nfs_dir, strtrim(listname));
	sprintf(f2, "%s/%s.cred", nfs_dir, machname);
	unlink(f2); /* ignore errors on this unlink */
	if (link(f1, f2)) {
	    fprintf(stderr, "Cannot link %s to %s\n", f1, f2);
	    exit(MR_OCONFIG);
	}
	hash_store(machines, id, machname);
    }


    olddev = oldmach = -1;
    fd = stdin;

    EXEC SQL DECLARE q_cursor CURSOR FOR
      SELECT DISTINCT q.quota, q.entity_id, q.phys_id, n.device, n.mach_id 
      FROM quota q, nfsphys n
      WHERE n.nfsphys_id = q.phys_id AND q.phys_id != 0 AND
	n.status < 16 AND q.type = 'USER'
      ORDER BY n.mach_id, q.phys_id, q.entity_id;
    EXEC SQL OPEN q_cursor;
    while (1) {
	EXEC SQL FETCH q_cursor INTO :quota, :uid, :flag1, :dev, :flag2;
	if (sqlca.sqlcode != 0) break;
	if (flag1 != olddev || flag2 != oldmach) {
	    if (quotasum)
	      fprintf(fd, "%d %d\n", prevuid, quotasum);
	    if (flag2 == 0 || !hash_lookup(machines, flag2))
	      continue;
	    if (fd != stdin)
	      fclose(fd);
	    olddev = flag1;
	    oldmach = flag2;
	    while (cp = index(dev, '/')) *cp = '@';
	    sprintf(file, "%s/%s.%s.quotas", nfs_dir,
		    hash_lookup(machines, flag2), strtrim(dev));
	    fd = fopen(file, "w");
	    if (!fd) {
		fprintf(stderr, "cannot open %s for output\n", file);
		exit(MR_OCONFIG);
	    }
	    prevuid = -1;
	    quotasum = 0;
	}
	if (uid != prevuid) {
	    if ((cp = hash_lookup(users, prevuid)) &&
		(cp = index(cp, ':')))
	      prevuid = atoi(cp+1);
	    if (quotasum)
	      fprintf(fd, "%d %d\n", prevuid, quotasum);
	    prevuid = uid;
	    quotasum = quota;
	} else {
	    quotasum += quota;
	}
    }
    EXEC SQL CLOSE q_cursor;
    if ((cp = hash_lookup(users, prevuid)) &&
	(cp = index(cp, ':')))
      prevuid = atoi(cp+1);
    if (quotasum)
      fprintf(fd, "%d %d\n", prevuid, quotasum);
    if (fd != stdin && fclose(fd) == EOF) {
	fprintf(stderr, "error closing %s", file);
	exit(MR_CCONFIG);
      }

    olddev = oldmach = -1;
    fd = stdin;
    EXEC SQL DECLARE q_cursor2 CURSOR FOR
      SELECT DISTINCT q.quota, q.entity_id, q.phys_id, n.device, n.mach_id,
        n.status 
      FROM quota q, nfsphys n
      WHERE n.nfsphys_id = q.phys_id AND q.phys_id != 0 AND
	n.status > 15 AND q.type = 'GROUP'
      ORDER BY mach_id, phys_id, entity_id;
    EXEC SQL OPEN q_cursor2;
    while (1) {
	EXEC SQL FETCH q_cursor2 INTO :quota, :gid, :flag1, :dev,
		:flag2, :flag3;
	if (sqlca.sqlcode != 0) break;
	if (flag1 != olddev || flag2 != oldmach) {
	    if (quotasum)
	      fprintf(fd, "%d %d\n", prevuid, quotasum);
	    if (flag2 == 0 || !hash_lookup(machines, flag2))
	      continue;
	    if (fd != stdin)
	      fclose(fd);
	    olddev = flag1;
	    oldmach = flag2;
	    while (cp = index(dev, '/')) *cp = '@';
	    sprintf(file, "%s/%s.%s.quotas", nfs_dir,
		    hash_lookup(machines, flag2), strtrim(dev));
	    fd = fopen(file, "w");
	    if (!fd) {
		fprintf(stderr, "cannot open %s for output\n", file);
		exit(MR_OCONFIG);
	    }
	    prevuid = -1;
	    quotasum = 0;
	}
	if (gid != prevuid) {
	    if (cp = hash_lookup(groups, prevuid))
	      prevuid = atoi(cp + 1);
	    if (quotasum)
	      fprintf(fd, "%d %d\n", prevuid, quotasum);
	    prevuid = gid;
	    quotasum = quota;
	} else {
	    quotasum += quota;
	}
    }
    EXEC SQL CLOSE q_cursor2;
    if (cp = hash_lookup(groups, prevuid))
      prevuid = atoi(cp + 1);
    if (quotasum)
      fprintf(fd, "%d %d\n", prevuid, quotasum);
    if (fd != stdin && fclose(fd) == EOF) {
	fprintf(stderr, "error closing %s", file);
	exit(MR_CCONFIG);
      }

    olddev = oldmach = -1;
    fd = stdin;

    EXEC SQL DECLARE q_cursor3 CURSOR FOR
      SELECT DISTINCT f.name, f.lockertype, u.uid, l.gid, f.phys_id, 
		f.mach_id, n.device 
      FROM users u, list l, nfsphys n, filesys f
      WHERE u.users_id = f.owner AND 
              l.list_id = f.owners AND
  	      f.createflg != 0 AND f.phys_id != 0 AND
              f.type = 'NFS' AND 
              f.phys_id = n.nfsphys_id
      ORDER BY mach_id, phys_id;
    EXEC SQL OPEN q_cursor3;
    while (1) {
	EXEC SQL FETCH q_cursor3 INTO :dir, :fstype, :uid, :gid, :flag1,
			:flag2, :dev;
	if (sqlca.sqlcode != 0) break;
	if (flag1 != olddev || flag2 != oldmach) {
	    if (fd != stdin)
	      fclose(fd);
	    olddev = flag1;
	    oldmach = flag2;
	    while (cp = index(dev, '/')) *cp = '@';
	    sprintf(file, "%s/%s.%s.dirs", nfs_dir,
		    hash_lookup(machines, flag2), strtrim(dev));
	    fd = fopen(file, "w");
	    if (!fd) {
		fprintf(stderr, "cannot open %s for output\n", file);
		exit(MR_OCONFIG);
	    }
	}
	fprintf(fd, "%s %d %d %s\n", strtrim(dir), uid, gid, strtrim(fstype));
    }    
    EXEC SQL CLOSE q_cursor3;
    if (fclose(fd) == EOF) {
	fprintf(stderr, "error closing %s", file);
	exit(MR_CCONFIG);
    }
    return(1);
 sqlerr:
    com_err(whoami, MR_INGRES_ERR, " code %d\n", sqlca.sqlcode);
    critical_alert("DCM", "NFS build encountered DATABASE ERROR %d",
		   sqlca.sqlcode);
    exit(MR_INGRES_ERR);
}
