/* $Id: members.pc 3956 2010-01-05 20:56:56Z zacheiss $
 *
 */

#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <moira.h>

RCSID("$HeadURL: svn+ssh://svn.mit.edu/moira/trunk/moira/dbck/members.pc $ $Id: members.pc 3956 2010-01-05 20:56:56Z zacheiss $");

#define FIXERRORS

#define max(x, y)	((x) > (y) ? (x) : (y))

EXEC SQL INCLUDE sqlca;

struct member {
    int	list_id;
    int member_id;
    union {
	short all;
	struct {
	    short u_ref_count:12;
	    unsigned short u_direct:1;
	    unsigned short u_baddirect:1;
	    unsigned short u_found:1;
	    unsigned short u_scanned:1;
	} u_flags;
    } flags;
} *find_member(), *allocmember();
#define frefc		flags.u_flags.u_ref_count
#define fdirect		flags.u_flags.u_direct
#define fbaddirect	flags.u_flags.u_baddirect
#define ffound		flags.u_flags.u_found
#define fscanned	flags.u_flags.u_scanned
#define fall		flags.all

#define member2id(c, id)	(((c & 0xff) << 24) | (id & 0xffffff))
#define id2type(id)		((id >> 24) & 0xff)
#define	id2id(id)		(id & 0xffffff)

struct member_list {
    struct member_list *next;
    struct member *member;
};

struct hash *lists, *members;
void fix_member(), display_member();
int debug = 0, records = 0;

char *db = "moira/moira";

main(argc, argv)
int argc;
char **argv;
{
    char buf[256];

#ifdef DEBUG
    if (argc > 1)
      debug = atoi(argv[1]);
#endif /* DEBUG */

/*  ingres sms */
    EXEC SQL CONNECT :db;
/*  begin transaction */
/*  range of m is imembers */
    /* No equivalent */

    lists = create_hash(50000);
    members = create_hash(300000);
    records = 0;

    load_members();
#ifdef DEBUG
    if (debug > 3)
      hash_step(lists, display_member, NULL);
#endif /* DEBUG */
    verify_members();
    fix_members();

#ifdef FIXERRORS
    printf("Commit changes (Y/N)?");
    fflush(stdout);
    fgets(buf, sizeof(buf), stdin);
    if (buf[0] == 'Y' || buf[0] == 'y') {
	printf("Ending transaction\n");
/*	end transaction */
	EXEC SQL COMMIT WORK;
    } else {
#endif /* FIXERRORS */
	printf("Aborting transaction\n");
/*	abort */
	EXEC SQL ROLLBACK WORK;
#ifdef FIXERRORS
    }
#endif /* FIXERRORS */

/*  exit */
    /* No equivalent (?) */
    printf("Done.\n");

    exit(0);
}


load_members()
{
    struct member *m, *m1, *md, *ma;
    struct member_list *descendants, *ancestors, *desc, *ance, la, ld;
    EXEC SQL BEGIN DECLARE SECTION; 
    int list_id, member_id, ref_count, ref;
    char mtype[9];
    EXEC SQL END DECLARE SECTION; 
    struct save_queue *sq;

    printf("Loading members\n");
    sq = sq_create();

/*  retrieve (list_id = m.#list_id, member_id = m.#member_id,
 *	      mtype = m.#member_type, ref_count = m.#ref_count)
 *	where m.direct = 1 {  */
    EXEC SQL DECLARE csrm1 CURSOR FOR
	SELECT list_id, member_id, member_type, ref_count FROM imembers
	    WHERE direct=1;
    EXEC SQL OPEN csrm1;
    while(1) {
	EXEC SQL FETCH csrm1 INTO :list_id, :member_id, :mtype, :ref_count;
	if(sqlca.sqlcode != 0) break; 

#ifdef DEBUG
      if (debug > 5)
	printf("Working on list %d member %s %d refc %d\n",
	       list_id, mtype, member_id, ref_count);
#endif /* DEBUG */
      if ((m = find_member(list_id, member2id(mtype[0], member_id))) == NULL) {
	  m = allocmember();
	  m->list_id = list_id;
	  m->member_id = member2id(mtype[0], member_id);
	  insert_list(m);
	  insert_member(m);
      }
      m->fdirect = 1;
      la.next = (struct member_list *) hash_lookup(members,
						   member2id('L', list_id));
      la.member = m;
      if (isinchain(m, la.next))
	ance = la.next;
      else
	ance = &la;
      if (mtype[0] == 'L')
	ld.next = (struct member_list *) hash_lookup(lists, member_id);
      else
	ld.next = NULL;
      ld.member = m;
      if (isinchain(m, ld.next))
	desc = ld.next;
      else
	desc = &ld;
#ifdef DEBUG
      if (debug > 5)
	printf("%d ancestors, %d descendants\n",
	       chainlen(ance), chainlen(desc));
#endif /* DEBUG */
      for (ancestors = ance; ancestors; ancestors = ancestors->next) {
	  ma = ancestors->member;
	  for (descendants = desc; descendants; descendants=descendants->next) {
	      md = descendants->member;
	      if (member2id('L', ma->list_id) == md->member_id)
		fprintf(stderr, "Loop detected! list %d member %d\n",
			md->list_id, ma->member_id);
	      ref = md->frefc * ma->frefc;
	      if (ref == 0) {
		  ref = max(md->frefc, ma->frefc);
		  if (ref == 0)
		    ref = 1;
	      }
#ifdef DEBUG
	      if (debug > 5)
		printf("Checking list %d member %d, ref = %d\n",
		       ma->list_id, id2id(md->member_id), ref);
#endif /* DEBUG */
	      if (m1 = find_member(ma->list_id, md->member_id)) {
		  m1->frefc += ref;
#ifdef DEBUG
		  if (debug > 5)
		    printf("set refc to %d (%d) on list %d, member %d\n",
			   m1->frefc, ref, m1->list_id, id2id(m1->member_id));
#endif /* DEBUG */
		  if (ma == m && md == m)
		    m1->fdirect = 1;
	      } else {
		  m1 = allocmember();
		  m1->list_id = ma->list_id;
		  m1->member_id = md->member_id;
		  m1->frefc = ref;
#ifdef DEBUG
		  if (debug > 5)
		    printf("set new refc to %d (%d) on list %d, member %d\n",
			   m1->frefc, ref, m1->list_id, id2id(m1->member_id));
#endif /* DEBUG */
		  sq_save_data(sq, m1);
	      }
	  }
      }
      while (sq_get_data(sq, &m)) {
	  insert_list(m);
	  insert_member(m);
      }
      sq_destroy(sq);
      sq = sq_create();
    }
    EXEC SQL CLOSE csrm1; 
    printf("created %d records\n", records);
}

verify_members()
{
    struct member *m;
    struct save_queue *sq;
    EXEC SQL BEGIN DECLARE SECTION; 
    int list_id, member_id, ref_count, dflag;
    char mtype[9];
    EXEC SQL END DECLARE SECTION; 
    int errxtra, errbref, errbdir;
#ifdef DEBUG
    int ref0, ref1, ref2, ref3, refg;
    int db0, db1, db2, db3, dbg;
#endif /* DEBUG */

    /* verify members from database */
    printf("Verifying members\n");
    errxtra = errbref = errbdir = 0;
#ifdef DEBUG
    ref0 = ref1 = ref2 = ref3 = refg = 0;
    db0 = db1 = db2 = db3 = dbg = 0;
#endif /* DEBUG */
    sq = sq_create();
/*  retrieve (list_id = m.#list_id, member_id = m.#member_id,
 *	      mtype = m.member_type, ref_count = m.#ref_count,
 *	      dflag = m.#direct) { */
    EXEC SQL DECLARE csrm2 CURSOR FOR
	SELECT list_id, member_id, member_type, ref_count, direct 
	    FROM imembers;
    EXEC SQL OPEN csrm2;
    while(1) {
	EXEC SQL FETCH csrm2 
	    INTO :list_id, :member_id, :mtype, :ref_count, :dflag;
	if(sqlca.sqlcode != 0) break; 

#ifdef DEBUG
      if (debug > 1)
	switch (ref_count) {
	case 0: db0++; break;
	case 1: db1++; break;
	case 2: db2++; break;
	case 3: db3++; break;
	default: dbg++;
	}
#endif /* DEBUG */
      m = find_member(list_id, member2id(mtype[0], member_id));
      if (m == NULL) {
	  m = allocmember();
	  m->list_id = list_id;
	  m->member_id = member2id(mtype[0], member_id);
	  m->fdirect = dflag;
	  m->frefc = ref_count;
	  sq_save_data(sq, m);
	  errxtra++;
      } else {
	  m->ffound = 1;
#ifdef DEBUG	
	  if (debug > 1)
	    switch (m->frefc) {
	    case 0: ref0++; break;
	    case 1: ref1++; break;
	    case 2: ref2++; break;
	    case 3: ref3++; break;
	    default: refg++;
	    }
#endif /* DEBUG */
	  m->frefc -= ref_count;
	  if (m->frefc != 0)
	    errbref++;
	  if (m->fdirect != dflag) {
	      m->fbaddirect = 1;
	      errbdir++;
	  }
      }
    }
    EXEC SQL CLOSE csrm2; 
    printf("Found %d extra records, %d bad ref counts, %d bad direct flags\n",
	   errxtra, errbref, errbdir);
#ifdef DEBUG
    if (debug > 1) {
	printf("Found in db: %d 0; %d 1; %d 2; %d 3; %d > 3\n",
	       db0, db1, db2, db3, dbg);
	printf("Found  refs: %d 0; %d 1; %d 2; %d 3; %d > 3\n",
	       ref0, ref1, ref2, ref3, refg);
    }
#endif /* DEBUG */
}


fix_members()
{
    struct member *m;
    struct save_queue *sq;
    int errmis = 0;
    EXEC SQL BEGIN DECLARE SECTION; 
    int list_id, member_id, rowcount;
    char mtype[9];
    EXEC SQL END DECLARE SECTION; 
    char buf[512];

    /* fix any errors */
    printf("Fixing errors\n");
    hash_step(lists, fix_member, &errmis);
    while (sq_get_data(sq, &m)) {
	printf("Extraneous member record, deleting:\n");
	list_id = m->list_id;
	member_id = id2id(m->member_id);
	switch (id2type(m->member_id)) {
	case 'U': strcpy(mtype, "USER"); break;
	case 'L': strcpy(mtype, "LIST"); break;
	case 'S': strcpy(mtype, "STRING"); break;
	case 'K': strcpy(mtype, "KERBEROS"); break;
	default:
	    mtype[0] = id2type(m->member_id);
	    mtype[1] = 0;
	}
    	printf("  List: %d, Member: %s %d, Refc: %d, Direct %d\n",
	       list_id, mtype, member_id, m->frefc, m->fdirect);
#ifdef FIXERRORS
/*	delete m where m.#list_id = list_id and m.#member_id = member_id
 *		and m.member_type = mtype */
	EXEC SQL DELETE FROM imembers WHERE list_id = :list_id AND
	    member_id = :member_id AND member_type = :mtype;
	rowcount = sqlca.sqlerrd[2];
	if (rowcount > 0)
	  printf("%d entr%s deleted\n", rowcount, 
		 rowcount == 1 ? "y" : "ies");
#endif /* FIXERRORS */
    }
    if (errmis > 0)
      printf("Added %d missing records\n", errmis);
}


insert_list(m)
struct member *m;
{
    struct member_list *l, *l1;

    l = (struct member_list *) hash_lookup(lists, m->list_id);
    if (l == NULL) {
	l = (struct member_list *)malloc(sizeof(struct member_list));
	if (l == NULL) {
	    fprintf(stderr, "No memory for insert_list\n");
	    exit(1);
	}
	l->next = NULL;
	l->member = m;
	if( hash_store(lists, m->list_id, l) == -1 ) {
	  fprintf(stderr,"Out of mem while storing lists in hash table\n");
	  exit(1);
	}
	return;
    }
    for (l1 = l; l1; l1 = l1->next)
      if (l1->member->member_id == m->member_id) {
	  fprintf(stderr, "Found 2nd copy of list record for\n");
	  fprintf(stderr, "List: %d, Member: %c %d, Refc: %d, Direct %d\n",
		  m->list_id, id2type(m->member_id), id2id(m->member_id),
		  m->frefc, m->fdirect);
	  kill(getpid(), SIGQUIT);
	  exit(2);
      }

    l1 = (struct member_list *)malloc(sizeof(struct member_list));
    if (l1 == NULL) {
	fprintf(stderr, "No memory for insert_list\n");
	exit(1);
    }
    l1->next = l->next;
    l->next = l1;
    l1->member = m;
}


insert_member(m)
struct member *m;
{
    struct member_list *l, *l1;

    l = (struct member_list *) hash_lookup(members, m->member_id);
    if (l == NULL) {
	l = (struct member_list *)malloc(sizeof(struct member_list));
	if (l == NULL) {
	    fprintf(stderr, "No memory for insert_member\n");
	    exit(1);
	}
	l->next = NULL;
	l->member = m;
	if( hash_store(members, m->member_id, l) == -1 ) {
	  fprintf(stderr,"Out of mem while storing members in hash table\n");
	  exit(1);
	}
	return;
    }

    for (l1 = l; l1; l1 = l1->next)
      if (l1->member->list_id == m->list_id) {
	  fprintf(stderr, "Found 2nd copy of member record for\n");
	  fprintf(stderr, "List: %d, Member: %c %d, Refc: %d, Direct %d\n",
		  m->list_id, id2type(m->member_id), id2id(m->member_id),
		  m->frefc, m->fdirect);
	  kill(getpid(), SIGQUIT);
	  exit(2);
      }

    l1 = (struct member_list *)malloc(sizeof(struct member_list));
    if (l1 == NULL) {
	fprintf(stderr, "No memory for insert_member\n");
	exit(1);
    }
    l1->next = l->next;
    l->next = l1;
    l1->member = m;
}


struct member *find_member(listid, memberid)
int listid;
int memberid;
{
    struct member_list *l;

    for (l = (struct member_list *) hash_lookup(lists, listid); l; l = l->next)
      if (l->member->member_id == memberid)
	return(l->member);
    return(NULL);
}


/*ARGSUSED*/
void fix_member(dummy, l, errmis)
int dummy;
struct member_list *l;
int *errmis;
{
    EXEC SQL BEGIN DECLARE SECTION; 
    int list_id, member_id, ref_count, dflag, rowcount;
    char *mtype;
    EXEC SQL END DECLARE SECTION; 
    char buf[2];
    struct member *m;

    for (; l; l = l->next) {
	m = l->member;

	if (m->fscanned)
	  continue;
	m->fscanned = 1;
	if (m->fbaddirect == 0 && m->frefc == 0 && m->ffound == 1)
	  continue;
	if (m->ffound == 0) {
	    printf("Missing member record, adding:\n");
	    list_id = m->list_id;
	    member_id = id2id(m->member_id);
	    ref_count = m->frefc;
	    dflag = m->fdirect;
	    switch (id2type(m->member_id)) {
	    case 'U': mtype = "USER"; break;
	    case 'L': mtype = "LIST"; break;
	    case 'S': mtype = "STRING"; break;
	    case 'K': mtype = "KERBEROS"; break;
	    default:
		mtype = buf;
		buf[0] = id2type(m->member_id);
		buf[1] = 0;
	    }
	    printf("  List: %d, Member: %s %d, Refc: %d, Direct %d\n",
		   list_id, mtype, member_id, ref_count, dflag);
	    (*errmis)++;
#ifdef FIXERRORS
/*	    append imembers (#list_id = list_id, #member_id = member_id,
 *			     member_type = mtype, #ref_count = ref_count,
 *			     direct = dflag); */
	    EXEC SQL INSERT INTO imembers (list_id, member_id, 
	        member_type, ref_count, direct)
	      VALUES (:list_id, :member_id, :mtype, :ref_count, :dflag);
	    rowcount = sqlca.sqlerrd[2];
	    if (rowcount > 0)
	      printf("%d entr%s added\n", rowcount, 
		     rowcount == 1 ? "y" : "ies");
#endif /* FIXERRORS */
	    continue;
	}
	printf("Member record has bad ref_count and/or direct flag, fixing\n");
	list_id = m->list_id;
	member_id = id2id(m->member_id);
	ref_count = m->frefc;
	dflag = m->fdirect;
	switch (id2type(m->member_id)) {
	case 'U': mtype = "USER"; break;
	case 'L': mtype = "LIST"; break;
	case 'S': mtype = "STRING"; break;
	case 'K': mtype = "KERBEROS"; break;
	default:
	    mtype = buf;
	    buf[0] = id2type(m->member_id);
	    buf[1] = 0;
	}
	printf("  List: %d, Member: %s %d, Refc: %d, Direct %d\n",
	       list_id, mtype, member_id, ref_count, dflag);
#ifdef FIXERRORS
/*	replace m (#ref_count = m.#ref_count + ref_count, direct = dflag)
 *	  where m.#list_id = list_id and m.#member_id = member_id and
 *	    m.member_type = mtype */
	EXEC SQL UPDATE imembers 
	    SET ref_count=ref_count+:ref_count, direct = :dflag 
	    WHERE list_id = :list_id AND member_id = :member_id AND
		member_tpe = :mtype;
	rowcount = sqlca.sqlerrd[2];
	if (rowcount > 0)
	  printf("%d entr%s updated\n", rowcount, 
		 rowcount == 1 ? "y" : "ies");
#endif /* FIXERRORS */
    }
}


struct member *allocmember()
{
    struct member *m;

    m = (struct member *) malloc(sizeof(struct member));
    if (m == NULL) {
	fprintf(stderr, "No memory for new member\n");
	exit(1);
    }
    m->fall = 0;
    records++;
    return(m);
}


int isinchain(m, l)
struct member *m;
struct member_list *l;
{
    for (; l; l = l->next)
      if (l->member == m)
	return(1);
    return(0);
}


int chainlen(l)
struct member_list *l;
{
    int i;
    for (i = 0; l; l = l->next, i++);
    return(i);
}


#ifdef DEBUG

/*ARGSUSED*/
void display_member(key, l, dummy1)
int key, dummy1;
struct member_list *l;
{
    struct member *m;
    char *mtype;

    printf("%d*", key);
    for(; l; l = l->next) {
	m = l->member;
	switch (id2type(m->member_id)) {
	case 'U': mtype = "USER"; break;
	case 'L': mtype = "LIST"; break;
	case 'S': mtype = "STRING"; break;
	case 'K': mtype = "KERBEROS"; break;
	default: mtype = "???"; break;
	}
	printf("List: %d, Member: %s %d, Refc: %d, Direct %d\n",
	       m->list_id, mtype, id2id(m->member_id), m->frefc,
	       m->fdirect);
    }
}

#endif /* DEBUG */
