]> git.neil.brown.name Git - TABS.git/commitdiff
Terminal Alocation and Booking System - June 2006 master origin
authorNeil F. Brown <neilb@dulcimer.orchestra.cse.unsw.EDU.AU>
Sun, 4 Jun 2006 12:46:19 +0000 (22:46 +1000)
committerNeil F. Brown <neilb@dulcimer.orchestra.cse.unsw.EDU.AU>
Sun, 4 Jun 2006 12:46:19 +0000 (22:46 +1000)
Initial Git checking

146 files changed:
MakeCommon [new file with mode: 0644]
MakeRules [new file with mode: 0644]
Makefile [new file with mode: 0644]
book/Makefile [new file with mode: 0644]
book/bcomm.c [new file with mode: 0644]
book/bcomm.h [new file with mode: 0644]
book/bkopen.c [new file with mode: 0644]
book/blex.c [new file with mode: 0644]
book/blex.h [new file with mode: 0644]
book/bogin.c [new file with mode: 0644]
book/boo.c [new file with mode: 0644]
book/boo.h [new file with mode: 0644]
book/bparse.c [new file with mode: 0644]
book/bperiod.c [new file with mode: 0644]
book/bversion.c [new file with mode: 0644]
book/help [new file with mode: 0644]
book/help.man [new file with mode: 0644]
book/periods [new file with mode: 0644]
book/periods.spec [new file with mode: 0644]
book/tkbook.tcl [new file with mode: 0755]
database/Makefile [new file with mode: 0644]
database/book_copydb.c [new file with mode: 0644]
database/bookings_db.c [new file with mode: 0644]
database/classes_db.c [new file with mode: 0644]
database/common.h [new file with mode: 0644]
database/config_db.c [new file with mode: 0644]
database/database.x [new file with mode: 0644]
database/db_change.c [new file with mode: 0644]
database/db_decode.c [new file with mode: 0644]
database/db_extern.h [new file with mode: 0644]
database/db_globals.h [new file with mode: 0644]
database/db_header.h [new file with mode: 0644]
database/db_helper.c [new file with mode: 0644]
database/db_transfer.c [new file with mode: 0644]
database/db_utils.c [new file with mode: 0644]
database/desc_utils.c [new file with mode: 0644]
database/descpairlist.c [new file with mode: 0644]
database/freeslots.c [new file with mode: 0644]
database/hosts_db.c [new file with mode: 0644]
database/intpairlist.c [new file with mode: 0644]
database/main.c [new file with mode: 0644]
database/make_keys.c [new file with mode: 0644]
database/names_db.c [new file with mode: 0644]
database/replica_db.c [new file with mode: 0644]
database/rpc_cmsg.c [new file with mode: 0644]
database/tokens_db.c [new file with mode: 0644]
database/users_db.c [new file with mode: 0644]
database/utils.c [new file with mode: 0644]
database/xdr_mem.c [new file with mode: 0644]
db_client/Makefile [new file with mode: 0644]
db_client/choose_id.c [new file with mode: 0644]
db_client/db_access.c [new file with mode: 0644]
db_client/db_client.h [new file with mode: 0644]
db_client/db_modify.c [new file with mode: 0644]
db_client/full_bookings.c [new file with mode: 0644]
db_client/get_tokens.c [new file with mode: 0644]
db_client/isdefaulter.c [new file with mode: 0644]
db_client/mapping_utils.c [new file with mode: 0644]
db_client/parse_desc.c [new file with mode: 0644]
db_client/process_exclusions.c [new file with mode: 0644]
db_client/user_utils.c [new file with mode: 0644]
doc/Makefile [new file with mode: 0644]
doc/book.texinfo [new file with mode: 0644]
interfaces/Makefile [new file with mode: 0644]
interfaces/bookinterface.c [new file with mode: 0644]
interfaces/bookinterface.h [new file with mode: 0644]
interfaces/charpairlist.c [new file with mode: 0644]
interfaces/kill_user.c [new file with mode: 0644]
interfaces/loglogin.c [new file with mode: 0644]
interfaces/valinterface.c [new file with mode: 0644]
interfaces/valinterface.h [new file with mode: 0644]
interfaces/wsdisplay.c [new file with mode: 0644]
lablist/Makefile [new file with mode: 0644]
lablist/bookterm.c [new file with mode: 0644]
lablist/lab.c [new file with mode: 0644]
lablist/lablist.c [new file with mode: 0644]
lablist/lablist.h [new file with mode: 0644]
lablist/labmon.c [new file with mode: 0644]
lablist/loadlab.c [new file with mode: 0644]
lib/Makefile [new file with mode: 0644]
lib/debug.c [new file with mode: 0644]
lib/dlink.c [new file with mode: 0644]
lib/dlink.h [new file with mode: 0644]
lib/innetgr.c [new file with mode: 0644]
lib/intarray.c [new file with mode: 0644]
lib/misc.h [new file with mode: 0644]
lib/pmap_getport.c [new file with mode: 0644]
lib/skip.c [new file with mode: 0644]
lib/skip.h [new file with mode: 0644]
lib/strccmp.c [new file with mode: 0644]
lib/strdup.c [new file with mode: 0644]
lib/timeutils.c [new file with mode: 0644]
lib/usage.c [new file with mode: 0644]
lib/utils.c [new file with mode: 0644]
login/Makefile [new file with mode: 0644]
login/book_login.c [new file with mode: 0644]
login/book_logout.c [new file with mode: 0644]
manager/Makefile [new file with mode: 0644]
manager/alloc.c [new file with mode: 0644]
manager/allocate.c [new file with mode: 0644]
manager/book_manager.c [new file with mode: 0644]
manager/book_status.c [new file with mode: 0644]
manager/cache_names.c [new file with mode: 0644]
manager/check_default.c [new file with mode: 0644]
manager/check_servers.c [new file with mode: 0644]
manager/check_status.c [new file with mode: 0644]
manager/choose_db.c [new file with mode: 0644]
manager/clnt_create.c [new file with mode: 0644]
manager/collect_table.c [new file with mode: 0644]
manager/create_table.c [new file with mode: 0644]
manager/db_loc.c [new file with mode: 0644]
manager/evictions.c [new file with mode: 0644]
manager/helper.c [new file with mode: 0644]
manager/helper.h [new file with mode: 0644]
manager/lookup_table.c [new file with mode: 0644]
manager/manager_access.c [new file with mode: 0644]
manager/pass_table.c [new file with mode: 0644]
manager/print_tab.c [new file with mode: 0644]
manager/status.x [new file with mode: 0644]
manager/status_client.h [new file with mode: 0644]
manager/statush.h [new file with mode: 0644]
manager/sysdep_windows.c [new file with mode: 0644]
manager/sysdep_xdm.c [new file with mode: 0644]
manager/table.c [new file with mode: 0644]
manager/up_host.c [new file with mode: 0644]
manager/whoison.c [new file with mode: 0644]
messaged/Makefile [new file with mode: 0644]
messaged/messaged.c [new file with mode: 0644]
messaged/multimessaged.c [new file with mode: 0644]
messaged/normalise.c [new file with mode: 0644]
messaged/ok.xbm [new file with mode: 0644]
messaged/ok1.xbm [new file with mode: 0644]
messaged/saveauth.c [new file with mode: 0644]
messaged/sendmess.c [new file with mode: 0644]
tools/Makefile [new file with mode: 0644]
tools/attr.c [new file with mode: 0644]
tools/booking.c [new file with mode: 0644]
tools/conf.c [new file with mode: 0644]
tools/dbadmin.c [new file with mode: 0644]
tools/host.c [new file with mode: 0644]
tools/labvis [new file with mode: 0755]
tools/res.c [new file with mode: 0644]
tools/show_booking.c [new file with mode: 0644]
tools/smadmin.c [new file with mode: 0644]
tools/token.c [new file with mode: 0644]
tools/user.c [new file with mode: 0644]

diff --git a/MakeCommon b/MakeCommon
new file mode 100644 (file)
index 0000000..3d7bb87
--- /dev/null
@@ -0,0 +1,48 @@
+
+# Common stuff to be included only once.
+HaveCommon = yes
+
+clean : cleandirs
+       rm -f $(toclean)
+
+cleandirs :
+       rm -f $(addsuffix *.[od],$(tocleandirs))
+
+%_svc.c: %.x %.h
+       rpcgen -m $< | sed 's,include ".*/,include ",' > $@
+%_xdr.c: %.x %.h
+       rpcgen -c $< | sed -e 's,include ".*/,include ",' -e '/int32_t .buf/s/$$/ (void)buf;/'>  $@
+%_clnt.c: %.x %.h
+       rpcgen -l $< | sed 's,include ".*/,include ",' >  $@
+%.h : %.x
+       rpcgen -h $< > $@
+
+%_svc.o: %_svc.c %.h
+%_xdr.o: %_svc.c %.h
+%_clnt.o: %_svc.c %.h
+
+%.o : %.c %.c-dep
+
+%.d: %
+       @mkdir -p $(dir $@)
+       @{ \
+         echo -n $<-dep' : ' ; \
+         sed -n -e 's,^#include.*"\(.*\)".*,$(dir $@)\1 $(dir $@)\1-dep,p' $< | tr '\012' ' ' ; \
+         echo ; \
+         echo -n '-include ' ; \
+         sed -n -e 's,^#include.*"\(.*\)".*,$(dir $@)\1.d,p' $< | tr '\012' ' ' ; \
+         echo ; \
+       } | sed -e 's,[^ /]*/\.\./,,g' > $@
+
+
+CFLAGS=-Wall -Werror -ggdb
+CPPFLAGS= -DUSE_SIGACTION -DSIGRTN=void -I$(subst ../,,$(dir $<)) -D_GNU_SOURCE
+LDLIBS = -lgdbm -lcrypt -lnsl -lcurses -ltermcap
+
+%/ : FORCE
+       mkdir -p $@
+
+
+.PHONY: FORCE
+FORCE:
+.SECONDARY:
diff --git a/MakeRules b/MakeRules
new file mode 100644 (file)
index 0000000..df46697
--- /dev/null
+++ b/MakeRules
@@ -0,0 +1,56 @@
+
+ThisMake := $(lastword $(MAKEFILE_LIST))
+TOP := $(dir $(ThisMake))
+ifeq ($(S),)
+%:
+       mkdir -p $(TOP)$(ObjDir) ; $(MAKE) -C $(TOP)$(ObjDir) VPATH=.. S=../ -f ../Makefile $*
+
+all:
+
+else
+
+# This is included at the end of each subdir makefile
+# We need to make each target from its objs
+all :
+
+
+$(if $(HaveCommon),,$(eval include $(D)../MakeCommon))
+
+D2 := $(if $(D),$(D),nothing)
+define TargetTemplate
+ all : targets/$(1)
+ targets/$(1) : $(D)$(1)
+       mkdir -p targets ; cp $(D)$(1) targets/$(1)
+ toclean += $(D)$(1)
+ $(D)$(1) : $(patsubst $(D2)../%,%, $(addprefix $(D),$(obj-$(1))))
+       $$(CC) -o $$@ $$^ $$(LDLIBS) $$(lib-$(1))
+ -include  $(patsubst %.o,$(D)%.c.d,$(filter %.o,$(obj-$(1))))
+
+endef
+
+$(foreach t,$(target),$(eval $(call TargetTemplate,$(t))))
+
+
+define LibraryTemplate
+ all : $(D)$(1)
+ toclean += $(D)$(1)
+ $(D)$(1) : $(patsubst $(D2)../%,%,$(addprefix $(D),$(obj-$(1))))
+       $$(AR) cr $$@ $$^
+endef
+
+$(foreach l,$(lib),$(eval $(call LibraryTemplate,$(l))))
+
+
+define DirTemplate
+ D := $(1)
+ include $$(S)$$(D)Makefile
+ tocleandirs += $(1)
+endef
+
+dtmp := $(dirs-y)
+dirs-y := 
+$(foreach dir,$(dtmp),$(eval $(call DirTemplate,$(dir))))
+
+target :=
+lib :=
+endif
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..866d4ce
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,18 @@
+
+ifeq ($(WINDOWS),)
+ unix=y
+ windows=n
+ ObjDir=obj.$(ARCH)
+else
+ unix=n
+ windows=y
+ ObjDir=obj.windows
+endif
+
+dirs-$(unix) += messaged/
+dirs-$(windows) += 
+
+dirs-y += lib/ database/ db_client/ manager/ lablist/ tools/ book/ interfaces/ login/ 
+#doc/ 
+
+include $(S)MakeRules
diff --git a/book/Makefile b/book/Makefile
new file mode 100644 (file)
index 0000000..9d047eb
--- /dev/null
@@ -0,0 +1,13 @@
+
+target += bogin
+obj-bogin = bogin.o
+
+target += book
+obj-book = boo.o bversion.o blex.o bparse.o bperiod.o bcomm.o ../interfaces/bookinterface.a   ../db_client/libdbclient.a   ../database/libdbclnt.a   ../manager/status_client.a ../lib/lib.a
+
+
+man += book.1
+
+# FIXME book.help tkbook
+
+include $(S)$(D)../MakeRules
diff --git a/book/bcomm.c b/book/bcomm.c
new file mode 100644 (file)
index 0000000..a68346a
--- /dev/null
@@ -0,0 +1,2243 @@
+/*
+ * Book interface module:
+ *     Routines that perform the book commands:
+ */
+
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <sys/wait.h>
+#include       <signal.h>
+#include       <stdio.h>
+#include       <time.h>
+#include       <rpc/rpc.h>
+#include       <errno.h>
+#include       <grp.h>
+
+#ifdef NOTYET
+#include       <udb.h>
+#endif
+#include       "../interfaces/bookinterface.h"
+#define BOOK_INTERFACE
+#include       <string.h>
+#include       <ctype.h>
+#include       <pwd.h>
+#include       "boo.h"
+#include       "bcomm.h"
+
+static void removebooking(bookuid_t user, booked *bp);
+
+booked *currbooked = NULL;     /* list of current bookings */
+booked *pastbooked = NULL;     /* list of past bookings */
+currtokentype *currtokens;     /* list of current tokens */
+
+char unknown[] = "Unknown";
+
+/***** time and slot conversion routines *****/
+
+time_t dp_2_time(int dayofyear, int periodofday)
+/* return the zulu time corresponding to parameters */
+{
+       int     min, hour;
+       min = periodofday * (SECINPERIOD / 60);
+       hour = min / 60;
+       min -= hour * 60;
+       return construct(1900 + tnow.tm_year, 0, dayofyear, hour, min);
+}
+
+period *t_within_period(time_t start, period *periods)
+/* A simple way of determining whether a booking period is within a series
+ * of periods (more efficient than using intersect()).
+ * We assume that the periods are in increasing time order.
+ * Return the simple period containing start
+ */
+{
+       period  *pp;
+       time_t  end = start + SECINPERIOD - 1;
+       for (pp = periods; (pp != NULLP && pp->end < end); pp = pp->next);
+       /* pp == NULL || pp->end >= end */
+       return ((pp != NULLP && pp->start <= start) ? pp : NULLP);
+}
+
+int t_lap_period(time_t start, period *periods)
+/* A simple way of determining whether a booking period overlaps one of a
+ * series of periods (more efficient than using intersect()).
+ * We assume that the periods are in increasing time order.
+ */
+{
+       period  *pp;
+       time_t  end = start + SECINPERIOD - 1;
+       for (pp = periods; (pp != NULLP && pp->end < start); pp = pp->next);
+       /* pp == NULL || pp->end >= start */
+       return (pp != NULLP && pp->start <= end);
+}
+
+time_t endofperiods(period *periods)
+/* return the end time of the last period in periods */
+{
+       period *pp;
+       if (periods != NULLP)
+       {
+               for (pp = periods; (pp->next != NULLP); pp = pp->next);
+               return pp->end;
+       }
+       else return 0;
+}
+
+/************ input routines ************/
+
+int getresponse(char *prompt, int *choice, int min)
+/* Interactively prompt for a response from the user.
+ * Responses are Yes: 'y'; No: ('n', <return>); Quit: ('q', <EOF>, <interrupt>).
+ * User may respond with an integer min <= i <= *choice in which case return Yes,
+ * and the number in choice.
+ */
+{
+       char    ch;
+       char    *badresp = "Invalid response\nRespond with 'y', 'n', 'q', <Return>";
+       int             neg, max, val, ret;
+
+       max = *choice;  /* the maximum choice */
+       ret = R_BAD;
+       val = 0;
+       do {
+               neg = 0;
+               printf("%s", prompt);
+               if (max > min) printf(" [%d..%d]", min, max);
+               printf (" ?");
+               if (!isatty(0)) printf("\n");
+               fflush(stdout);
+               if ((ch = getchar()) == EOF || interrupt)
+               {
+                       puts("");
+                       ret = R_QUIT;
+               }
+               else
+                       switch (ch)
+                       {
+                       case 'y':
+                       case 'Y':       ret = R_YES;    break;
+                       case '\n':
+                       case 'n':
+                       case 'N':       ret = R_NO;     break;
+                       case 'q':
+                       case 'Q':       ret = R_QUIT;   break;
+                       case '+':
+                               neg = 0;
+                               goto digit;
+                       case '-':
+                               neg++;
+                       digit:
+                               ch = getchar(); /* fall into next condition */
+                       default:
+                               if (isdigit(ch))
+                               {
+                                       val = 0;
+                                       while (isdigit(ch))
+                                       {
+                                               val = val * 10 + ch - '0';
+                                               ch = getchar();
+                                       }
+                                       if (neg) val *= -1;
+                                       if (min <= val && val <= max) ret = R_YES;
+                               }
+                               if (ret == R_BAD)
+                               {
+                                       fputs(badresp, stderr);
+                                       if (max > min)
+                                               fprintf(stderr, ", or integer: [%d..%d]\n", min, max);
+                                       else fputs(".", stderr);
+                               }
+                               break;
+                       }
+               while (ch != '\n' && ch != EOF) ch = getchar();
+       } while (ret == R_BAD);
+       *choice = val;
+       return ret;
+} /* getresponse */
+
+/********* output routines *********/
+
+void printslot(time_t start, int nperiods)
+/* print the period starting start, lasting nperiods periods */
+{
+       char *s;
+       struct tm       *tp;
+
+       tp = gmtime(&start);
+       s = asctime(tp);
+       printf("%5.5s", s+11);
+       if (nperiods > 0)
+       {
+               printf(" - ");
+               start += nperiods*SECINPERIOD - 1;
+               tp = gmtime(&start);
+               s = asctime(tp);
+               printf("%5.5s ", s+11);
+       }
+       printf(" %3.3s %2.2s %3.3s", s, s+8, s+4);
+}
+
+#ifdef NOTYET
+void update_tokens(udb_uidt user);     /* defined later */
+#else
+void update_tokens(bookuid_t user);    /* defined later */
+#endif
+void printtokens(int ntokpassed, int include_expired)
+/* print the details of the tokens passed, or if none passed, all tokens
+ * available */
+{
+       currtokentype   *cp;
+       update_tokens(user);
+       if (currtokens != NULL)
+       {
+               if (ntokpassed == 0)
+                       printf("\tAll tokens (%scluding expired tokens):\n",
+                              (include_expired ? "in" : "ex"));
+               puts("\tToken\t\t\tNumber\tPeriod Available");
+               for (cp = currtokens; (cp != NULL); cp = cp->next)
+                       if (cp->active == command_no)
+                       {
+                               printf("\t%-16s\t%d\t", cp->name, cp->number);
+                               printperiod(cp->period, "\t\t\t\t\t");
+                       }
+       }
+       else puts("\tNo tokens allocated");
+}
+
+void printcommand(command_type *cp)
+/* a debug routine to print out commands, tokens, periods */
+{
+       strlist *sp;
+       optlist *opt;
+       if (cp != NULLC)
+       {
+               printf("Command:\n\t%s\n", typevaltostr(COMND, cp->command));
+
+               puts("Options:");
+               for (opt = cp->options; (opt != NULL); opt = opt->next)
+                       printf("\t%d = %d\n", opt->option, opt->value);
+
+               puts("Strings:");
+               if (cp->strlist != NULL)
+                       for (sp = cp->strlist; (sp != NULL); sp = sp->next)
+                               printf("\t%s\n", sp->str_val);
+
+               printf("Tokens passed: (%d)\n", cp->ntokpassed);
+               if (cp->ntokpassed > 0)
+                       printtokens(cp->ntokpassed, 0);
+
+               puts("Periods:");
+               if (cp->period != NULLP)
+               {
+                       putchar('\t');
+                       printperiod(cp->period, "\t");
+               }
+               else puts("\tNo period passed");
+       }
+       else puts("Null command");
+}
+
+/***** help output routines ******/
+
+int findhelp(FILE *fp, char *topic, int toprint)
+/*
+ * Help info is found in the file pointed to by fp.
+ * Help sections generally start with a "#topic" line and end with a "#" line.
+ * This routine prints or skips (depending on toprint), all lines
+ * up to the line matching "#topic". topic may be the null string.
+ */
+{
+       char ch, *s;
+       int     found = 0;
+       char *term = getenv("TERM");
+       int linecnt = 0;
+       while (! found && (ch = fgetc(fp)) != EOF)
+       {
+               if (ch == '#')
+               {
+                       s = topic;
+                       while ((ch = fgetc(fp)) != EOF && ch != '\n' && *s && ch == *s++);
+                       if (ch == '\n')
+                               found = (*s == '\0');
+                       else while ((ch = fgetc(fp)) != EOF && ch != '\n');
+               }
+               else
+               {
+                       while (ch != '\n' && ch != EOF)
+                       {
+                               if (toprint) putchar(ch);
+                               ch = fgetc(fp);
+                       }
+                       if (toprint)
+                       {
+                               putchar('\n');
+                               if (++linecnt >= 23 && (term == NULL || strcmp(term, "xterm") != 0))
+                               {
+                                       int c, more;
+                                       fputs("Press return for more: ", stdout);
+                                       fflush(stdout);
+                                       more = ((c = fgetc(stdin)) == '\n');
+                                       while (c != '\n' && c != EOF) c = fgetc(stdin);
+                                       if (more)
+                                               linecnt = 0;
+                                       else return found;
+                               }
+                       }
+               }
+       }
+       return found;
+}
+
+void printhelp(char *topic)
+/* print the help section indexed by topic (see findhelp() for more details) */
+{
+       FILE    *fp;
+       if ((fp = fopen(helpfile, "r")) != NULL)
+       {
+               if (findhelp(fp, topic, 0))
+                       findhelp(fp, "", 1);
+               fclose(fp);
+       }
+       else perror(helpfile);
+}
+
+/******* token cache routines ***********/
+
+currtokentype *currtokenentry(char *name)
+/* find and return the token in currtokens matching name */
+{
+       currtokentype *cp;
+       for (cp = currtokens; (cp != NULL && strcmp(cp->name, name)); cp = cp->next);
+       return cp;
+}
+
+int token_active(char *name)
+{
+       currtokentype *cp;
+       if ((cp = currtokenentry(name)) != NULL)
+               return (cp->active == command_no);
+       else return 0;
+}
+
+int tokens_book_lab(char *labname)
+{
+       /* check to see if any of the currently active tokens
+        * can book the lab labname
+        */
+       /* FIXME why is this here -= neilb 20may96 */
+       return 0;
+}
+
+period *token_periods(char *tname)
+/* return the periods during which the token called tname is available.
+ */
+{
+       period *pp, *pfinal;
+       period_spec *pspecp;
+
+       pfinal = NULLP;
+
+#ifdef RET_PERIOD_NOT_VALID
+       /*
+        * token_period returns an array of periods when the token
+        * named tname is *NOT* valid.
+        * The array is NULL if the token is invalid.
+        * The array is terminated by a sentinal period (with start == -1).
+        * The array contains only the sentinal if the token is always
+        * valid.
+        */
+       if ((pspecp = token_period(tname)) != NULL)
+       {
+               while (pspecp->start != -1)
+               {
+                       pp = newperiod(ABSOLUTE,
+                                      construct(tnow.tm_year+1900,0,pspecp->start, 0, 0),
+                                      construct(tnow.tm_year+1900,0,pspecp->end+1, 0, 0) - 1,
+                                      NULLP);
+                       pfinal = mergeperiods(pfinal, pp);
+                       pspecp++;
+               }
+               if (pfinal == NULLP)
+               {
+                       /* By default, the token is valid for the current year only.
+                        */
+                       pfinal = newperiod(ABSOLUTE,
+                                          construct(1900+tnow.tm_year  ,0,0,0,0),
+                                          construct(1900+tnow.tm_year+1,0,0,0,0)-1,
+                                          NULLP);
+               }
+               else
+                       pfinal = notperiod(pfinal);
+
+       }
+#else
+       /* token_period returns an array of periods when the token
+        * named tname is valid.
+        * The array is NULL if the token is invalid.
+        * The array is terminated by a sentinal period (with start == -1).
+        */
+       for (pspecp = token_period(tname); (pspecp != NULL && pspecp->start != -1); pspecp++)
+       {
+               pp = newperiod(ABSOLUTE,
+                              construct(tnow.tm_year+1900,0,pspecp->start, 0, 0),
+                              construct(tnow.tm_year+1900,0,pspecp->end+1, 0, 0) - 1,
+                              NULLP);
+               pfinal = mergeperiods(pfinal, pp);
+       }
+#endif
+       return pfinal;
+}
+
+#ifdef NOTYET
+void update_tokens(udb_uidt user)
+#else
+void update_tokens(bookuid_t user)
+#endif
+/* get the current list of tokens from the db.
+ * update the current tokenlist "currtokens".
+ * do not deactivate any active tokens.
+ */
+{
+       charpairlist    charpair;
+       currtokentype   *currtok;
+
+       /* reset the number of all tokens in currtok */
+       for (currtok = currtokens; (currtok != NULL); currtok = currtok->next)
+               currtok->number = 0;
+
+       /* get the user's current tokens from the db */
+       charpair = gettokenlist(user);
+#ifdef DEBUG
+       printf("gettokenlist(%d) returns %snull\n", user, (charpair != NULL) ? "non-" : "");
+#endif
+
+       /* update the tokens in currtok; add new ones if not there */
+       while (charpair != NULL)
+       {
+               if ((currtok = currtokenentry(charpair->data)) == NULL)
+               {
+                       if ((currtok = MALLOC(currtokentype)) != NULL)
+                       {
+                               currtok->name = charpair->data;
+                               currtok->period = token_periods(currtok->name);  /* period of token */
+                               currtok->next = currtokens;
+                               currtok->active = 0;
+                               currtokens = currtok;
+                       }
+               }
+               if (currtok != NULL) currtok->number = charpair->num;
+               charpair = charpair->next;
+       }
+} /* update_tokens */
+
+int count_active_tokens()
+/* return the total number of positive active tokens */
+{
+       currtokentype *currtok = currtokens;
+       int     total = 0;
+       while (currtok != NULL)
+       {
+               if (currtok->active == command_no && currtok->number > 0)
+                       total += currtok->number;
+               currtok = currtok->next;
+       }
+       return total;
+}
+
+int mark_token(char *tokname)
+/* if the token tokname is in currtokens, mark the entry and return true */
+{
+       currtokentype *cp;
+       if ((cp = currtokenentry(tokname)) != NULL)
+               cp->active = command_no;
+       return (cp != NULL);
+}
+
+void mark_all_tokens(int include_expired)
+/* mark all current, unexpired tokens as active.
+ * if (include_expired) then mark the expired tokens as well
+ */
+{
+       currtokentype   *cp;
+       period          *pp, *ptemp;
+    
+       if (! include_expired)  /* mark only those tokens that are not yet expired */
+               pp = newperiod(ABSOLUTE, now, construct(tnow.tm_year+1900+1,0,0,0,0) - 1, NULLP);
+       else pp = ptemp = NULLP;
+
+       for (cp = currtokens; (cp != NULL); cp = cp->next)
+       {
+               if (include_expired || (ptemp = intersect(pp, cp->period)) != NULL)
+                       cp->active = command_no;
+               rfree(ptemp);
+       }
+       rfree(pp);
+}
+
+period *union_tokenperiods()
+/* return the union of the periods of all the active tokens */
+{
+       period  *pp = NULL;
+       currtokentype   *cp;
+       for (cp = currtokens; (cp != NULL); cp = cp->next)  {
+               if (cp->active == command_no) {
+                       if (pp != NULL)
+                               pp = periodunion(pp, copyperiods(cp->period));
+                       else pp = copyperiods(cp->period);
+               }
+       }
+       return pp;
+} /* union_tokenperiods */
+
+/********** booking cache routines **********/
+
+int cmpbooked(const void *b1v, const void *b2v)
+/* function used by qsort to compare two bookings according to start time  */
+{
+       const booked *b1=b1v, *b2=b2v;
+       time_t diff;
+       diff = b1->start - b2->start;
+       return ((diff < 0) ? -1 : (diff > 0) ? 1 : 0);
+}
+
+booked *alreadybooked(time_t start, bookuid_t user, char *lab)
+/* return the booked period starting start, owned by user, in lab
+ * ignore user == -1 and lab == NULL
+ */
+{
+       booked  *bp;
+       for (bp = currbooked;
+            (bp != NULL &&
+             (bp->start != start || (user != -1 && bp->owner != user)
+              || (lab != NULL && strcmp(lab, bp->labname) != 0)));
+            bp = bp->next);
+       return(bp);
+}
+
+void insertbookelement(booked **head, booked *new)
+/* insert into place the new booked element into the list pointed to be head */
+{
+       booked  *bp, *prev;
+       for (prev = NULL, bp = *head;
+            (bp != NULL && bp->start < new->start); bp = bp->next)
+               prev = bp;
+       new->next = bp;
+       if (prev != NULL)
+               prev->next = new;
+       else *head = new;
+}
+
+void addbooking(slotgroup *sp, int whenmade,
+               char *tokname, time_t start, char *labname, int nbooked)
+/* add new (PENDING) booked record to list of current bookings */
+{
+       booked  *newbooked;
+       book_key        slot;
+    
+       slot = make_bookkey(sp->doy, sp->pod, desc_2_labind(&sp->what)); 
+       if ((newbooked = MALLOC(booked)) != NULL)
+       {
+               newbooked->mdesc = sp->what;
+               newbooked->slot = slot;
+               newbooked->whenmade = whenmade;
+               newbooked->tokname = tokname;
+               newbooked->start = start;
+               newbooked->labname = labname;
+               newbooked->owner = user;
+               newbooked->ownername = username;
+               newbooked->nbooked = nbooked;
+               newbooked->status = B_PENDING;          /* can only add pending bookings */
+       
+               /* insert the newbooked element into the current (PENDING) booked list */
+               insertbookelement(&currbooked, newbooked);
+       }
+}
+
+void delbooking(booked *todelete)
+/* delete unbooked record from list of current bookings
+ * and add it to list of past bookings
+ */
+{
+       booked  *prev, *bp;
+       for (prev = NULL, bp = currbooked;
+            (bp != NULL && bp != todelete); bp = bp->next) prev = bp;
+       if (bp == todelete)
+       {
+               if (prev != NULL)
+                       prev->next = bp->next;
+               else currbooked = bp->next;
+               todelete->status = B_CANCELLED;
+               insertbookelement(&pastbooked, todelete);
+       }
+       else
+       {
+               puts("Cannot find slot to delete: ");
+               printslot(todelete->start, 1);
+               puts("");
+       }
+}
+
+void addbookings(bookuid_t uid, booked **currbookings, int *nbookings, int pastbookings)
+{
+       userbookinglist userbooklist, ublp;
+       booklist                blist, bp;
+       booked          *bookedp;
+       int                     n, doy, pod, labind;
+       char            *uidname;
+       if (uid != user)
+       {
+               if ((uidname = find_username(uid)) ==  NULL)
+                       uidname = unknown;      
+
+       }
+       else uidname = username;
+       /* get the bookings for the user/class nominated */
+#ifdef DEBUG
+       printf("adding %s bookings for %d (%s)\n",
+              pastbookings ? "past" : "current", uid, uidname);
+#endif
+       n = 0;
+       if (pastbookings)
+               userbooklist = getpastbookings(uid);    /* bookings with status != PENDING */
+       else userbooklist = getbookings(uid);   /* bookings with status == PENDING */
+       if (userbooklist != NULL)
+       {
+               for (ublp = userbooklist; (ublp != NULL); ublp = ublp->next) n++;
+               if (n > 0)
+               {
+#ifdef DEBUG
+                       printf("\tfound %d bookings\n", n);
+#endif
+                       n += *nbookings;
+                       if (*currbookings == NULL)
+                               *currbookings = (booked *) malloc(n*sizeof(booked));
+                       else *currbookings = (booked *) realloc(*currbookings, n*sizeof(booked));
+                       if (*currbookings != NULL)
+                       {
+                               blist = getfullbookings(userbooklist, uid);
+                               for (bp=blist, ublp=userbooklist, bookedp = *currbookings + *nbookings;
+                                    (ublp != NULL);
+                                    (bp = bp==NULL?NULL:bp->next), ublp = ublp->next, bookedp++)
+                               {
+                                       if (bp) bookedp->mdesc = bp->charged;
+                                       bookedp->slot = ublp->slot;
+                                       bookedp->whenmade = ublp->when;
+                                       bookedp->tokname = bp?get_mappingchar(bp->tnum, M_TOKEN): unknown;
+                                       book_key_bits(&(ublp->slot), &doy, &pod, &labind);
+                                       bookedp->start = dp_2_time(doy, pod);
+                                       bookedp->labname = get_mappingchar(labind, M_ATTRIBUTE);
+                                       bookedp->owner = uid;
+                                       bookedp->ownername = uidname;
+                                       bookedp->nbooked = bp?bp->number:1;
+                                       bookedp->status = bp?bp->status:B_PENDING;
+                                       if (bookedp->status != B_ALLOCATED
+                                           && bookedp->status != B_RETURNED
+                                           && bookedp->status != B_FREEBIE)
+                                               bookedp->defaulted = 0;
+                                       else
+                                       {
+                                               if ((bp->claimed & DID_DEFAULT)
+                                                   && !(
+                                                           (bp->claimed&DID_LOGIN)
+                                                           && (bp->claimed&LOGIN_ON_ALLOC)
+                                                           && (bp->claimed>>4)-30*60 < 7*60) /* FIXME */
+                                                       )
+                                                       bookedp->defaulted = 1;
+                                               else
+                                                       bookedp->defaulted = 0;
+                                       }
+                                       bookedp->printed = 0;
+                                       bookedp->next = NULL;   /* corrected in initbookings() */
+                               }
+                               *nbookings = n;
+                       }
+                       else *nbookings = 0;
+               }
+       }
+}
+
+void initbookings()
+/*
+ * Set the list of the user's current bookings and tokens.
+ * Bookings are sorted in order of start time,
+ * tokens left in the order that they were returned from database.
+ */
+{
+       int                     n, i;
+       booked          *bookedp, *nextbp;
+       bookuid_t               *class_udb_ptr;
+    
+       currbooked = pastbooked = NULL; n = 0;
+       addbookings(user, &currbooked, &n, 0);
+    
+       /* add the bookings for classes that user belongs to */
+       if ((class_udb_ptr = (bookuid_t*)user_classes(user)) != NULL)
+       {
+               /* class_udb_ptr-> (nentries, uid {, classid}) */
+               for (i = *class_udb_ptr++; (i>1); i--)
+                       addbookings(*++class_udb_ptr, &currbooked, &n, 0);
+       }
+    
+       /* sort the bookings in date order, and create a linked list of bookings.
+        * (linked list is used so that deletions and insertions of bookings are easy)
+        */
+       if (n > 0)
+       {
+               qsort(currbooked, n, sizeof(booked), cmpbooked);
+               for (bookedp = currbooked, i = 1; (i < n); bookedp++, i++) bookedp->next = bookedp + 1;
+               bookedp->next = NULL;
+       }
+    
+       /* now get the past bookings for this user (but not their classes) */
+       n = 0;
+       addbookings(user, &pastbooked, &n, 1);
+       if (n > 0)
+       {
+               qsort(pastbooked, n, sizeof(booked), cmpbooked);
+               for (bookedp = pastbooked, i = 1; (i < n); bookedp++, i++) bookedp->next = bookedp + 1;
+               bookedp->next = NULL;
+       }
+
+       /* cancel pending bookings owned by the user that are for the past */
+       for (bookedp = currbooked; (bookedp != NULL); bookedp = nextbp)
+       {
+               nextbp = bookedp->next;
+               if (bookedp->status == B_PENDING
+                   && bookedp->start < now
+                   && bookedp->owner == user)
+                       removebooking(user, bookedp);
+       }
+    
+       currtokens = NULL;
+       update_tokens(user);
+} /* initbookings */
+
+/******** main booking functions **********/
+
+int minconsecfree(consecfree *cfp);
+int maxconsectotal(consecfree *cfp);
+
+int printtokavail(int nconsec, time_t start, tokused *tokavail, int makebooking, int counts)
+{
+       /* if 'counts' in set, we provide extra information about have much space in
+        * available in each lab.
+        * if counts==2, print labname(freeworkstations)
+        * if counts==1, print:
+        *      labname  if > warn_percent free
+        *      labname? if <= warn_percent free, but not full
+        *      labname! if full, but still included in list (i.e. no overbook_percent)
+        */
+       tokused *tp;
+       labavail        *lp;
+       int             ntoken, nlabs;
+       int             warn_percent;
+
+       warn_percent = get_configint("warn_percent");
+       if (warn_percent < 0) warn_percent = RESERVE_PC;
+    
+       ntoken = 0;
+       for (tp = tokavail; (tp != NULL); tp = tp->next)
+       {
+               if (tp->nlabs > 0)
+               {
+                       nlabs = 0;
+                       for (lp = tp->labs; (lp != NULL); lp = lp->next)
+                       {
+                               if (lp->laststart == start && lp->nperiods >= nconsec)
+                               {
+                                       int free = minconsecfree(lp->consecfree);
+                                       int size = maxconsectotal(lp->consecfree);
+                                       if (nlabs++ == 0)
+                                       {
+                                               if (ntoken++ == 0)
+                                               {
+                                                       if (makebooking)
+                                                               fputs("Choice", stdout);
+                                                       puts("\tToken\t\t\tLabs");
+                                               }
+                                               if (makebooking)
+                                                       fprintf(stdout, "(%d)", ntoken);
+                                               fprintf(stdout, "\t%-16s", tp->tokname);
+                                       }
+                                       if (counts==2) fprintf(stdout, "\t%s(%d)", lp->labname, free);
+                                       else if (counts==1 && free > 0 && free < (size*warn_percent/100+1))
+                                               fprintf(stdout,"\t[%s]", lp->labname);
+                                       else if (free <= 0)
+                                               fprintf(stdout,"\t(%s)", lp->labname);
+                                       else
+                                               fprintf(stdout, "\t%s", lp->labname);
+                               }
+                       }
+                       if (nlabs) putchar('\n');
+               }
+       }
+       return ntoken;
+} /* printtokavail */
+
+static int choosetoken(int nconsec, time_t start, tokused *tokavail,
+               tokused **tokchoice, int ntoken)
+/*
+ * return an indicator of what the user wants to do,
+ * and (via parameter) a pointer to the available token chosen (if any).
+ */
+{
+       int             i, n, response;
+       tokused *tp;
+    
+       do {
+               printtokavail(nconsec,start,tokavail,TRUE, FALSE);
+               n = ntoken;     /* the number of tokens available in tokavail */
+               response = getresponse("Choose Token", &n, 1);
+               if ((i = (response == R_YES && n == 0)))
+                       puts("Cannot respond with 'y'\n");
+       } while (i);
+    
+       if (response == R_YES)
+       {
+               for (tp = tokavail, i = 0;
+                    (tp != NULL && (tp->nlabs <= 0 || ++i < n));
+                    tp = tp->next);
+               if (tp == NULL)
+               {
+                       fprintf(stderr, "Cannot find the token choice %d !\n", n);
+                       response = R_NO;
+               }
+               else *tokchoice = tp;
+       }
+    
+       return response;
+} /* choosetoken */
+
+void printlab(labavail *lab)
+/* print the number of available terminals in this lab for all
+ * consecutive free slots
+ */
+{
+       consecfree      *cp = lab->consecfree;  /* cp is a circular linked list */
+       printf("\t%-8s", lab->labname);
+       do {
+               printf("\t%d", cp->nfree);
+       } while ((cp = cp->next) != lab->consecfree);
+       putchar('\n');
+}
+
+int minconsecfree(consecfree *cfp)
+/* Return the minimum no of free machines in the consecutive slots in cfp */
+{
+       consecfree      *cp = cfp;      /* cfp is a circular linked list */
+       int             min = cp->nfree;
+       while ((cp = cp->next) != cfp) if (cp->nfree < min) min = cp->nfree;
+       return min;
+}
+
+int maxconsectotal(consecfree *cfp)
+/* Return the maximum total machines over the consecutive slots in cfp
+ * This should not change between the consecutive slots, but we shouldn't
+ * assume anything.
+ */
+{
+       consecfree      *cp = cfp;      /* cfp is a circular linked list */
+       int             max = cp->ntotal;
+       while ((cp = cp->next) != cfp) if (cp->ntotal < max) max = cp->ntotal;
+       return max;
+}
+
+static int chooselab(int nconsec, time_t start, tokused *tokavail,
+labavail **labchoice, bookuid_t user, int labgiven)
+/*
+ * return an indicator of what the user wants to do,
+ * and (via parameter) a pointer to the machine description chosen (if any).
+ * Do not allow the user to choose a lab that has already been booked
+ * (this applies particulary to class bookings which are allowed to book more
+ * than one lab at the same time; normal users do not even get to choose another
+ * booking if they already have one for the same time).
+ */
+{
+       labavail        *maxminlp, *lp;
+       int             nlabs, i, n, minfree, maxmin, maxminindex;
+       int             response;
+
+#ifdef OVERBOOK
+       int     obindex;
+       labavail        *oblp;
+       int             obpercent, obavail = 0;
+
+       obpercent = get_configint("over_book_percent");
+       if (obpercent <= 0) obpercent = 0;
+#endif    
+
+       /* print the labs to be chosen from */
+       puts("Choice\tLab\t\tNo of machines free per slot");
+       nlabs = maxmin = 0;
+       for (lp = tokavail->labs; (lp != NULL); lp = lp->next)
+       {
+               if (lp->laststart == start && lp->nperiods >= nconsec
+                   && alreadybooked(start, user, lp->labname) == NULL)
+               {
+                       printf("(%d)", ++nlabs);
+                       printlab(lp);
+                       minfree = minconsecfree(lp->consecfree);
+                       if (maxmin < minfree)
+                       {
+                               maxmin = minfree;
+                               maxminlp = lp;
+                               maxminindex = nlabs;
+                       }
+#ifdef OVERBOOK
+                       /* see if this lab is a candidate for over-booking */
+                       if (minfree < 0 && obpercent) {
+                               int t = maxconsectotal(lp->consecfree);
+                               t = (t * obpercent) / 100;
+                               if (t+minfree > obavail) {
+                                       obavail = t+minfree;
+                                       oblp = lp;
+                                       obindex = nlabs;
+                               }
+                       }
+#endif
+               }
+       }
+       n = nlabs;
+       if ((n = nlabs) == 1 && user_is_class)
+       {
+               /* there is only one choice of lab, pick it if the user is a class
+                * as class users are asked to choose a number of machines to book
+                * later anyway.
+                */
+               *labchoice = maxminlp;
+               return R_YES;
+       }
+       if (n == 1 && labgiven) response = R_YES;
+       else
+               response = getresponse((n == 1) ? "Book Lab" : "Choose Lab", &n, 1);
+    
+       /* find the lab chosen */
+       if (response == R_YES)
+       {
+               if (n > 0)
+               {
+                       if (n != maxminindex)
+                       {
+                               for (lp = tokavail->labs, i = 0;
+                                    (lp != NULL &&
+                                     (lp->laststart != start || lp->nperiods < nconsec ||
+                                      alreadybooked(start, user, lp->labname) != NULL ||
+                                      ++i < n));
+                                    lp = lp->next);
+                               if (lp == NULL)
+                               {
+                                       /* should not be possible! */
+                                       printf("Cannot find the lab choice %d !\n", n);
+                                       response = R_NO;
+                               }
+                               else maxmin = minconsecfree(lp->consecfree);
+                       }
+                       else lp = maxminlp;
+               }
+               else
+               {
+                       lp = maxminlp;
+                       printf("Picked choice (%d) - %s\n", maxminindex, lp->labname);
+               }
+       }
+    
+       if (response == R_YES)
+       {
+               int warn_percent = get_configint("warn_percent");
+               if (warn_percent < 0) warn_percent = RESERVE_PC;
+               *labchoice = lp;
+               /* issue warning if needed */
+               i = maxconsectotal(lp->consecfree);
+               if (maxmin < (i * warn_percent / 100) + WARNING)
+               {
+                       printf("Warning: You have %s one of the last %d terminals available in %s\n",
+                              ((n<=0 && nlabs>1) ? "been given" : "chosen"),
+                              (i * warn_percent / 100) +WARNING, lp->labname);
+                       puts("\tDepending on the state of the lab at allocation time,");
+                       puts("\tyour booking MIGHT NOT be able to be honoured,");
+                       puts("\tin which case your token will be refunded.");
+               }
+       }
+    
+       return response;
+} /* chooselab */
+
+void utillist_add(utilizationlist *head, utilizationlist item)
+/* insert a copy of the new item into the list.
+ * list is in decreasing order of free machines
+ */
+{
+       utilizationlist new, prev, curr;
+       if ((new = MALLOC(utilizationnode)) != NULL)
+       {
+               *new = *item;           /* copy data */
+               curr = *head;
+               prev = NULL;
+               while (curr != NULL && curr->free > new->free)
+               {
+                       prev = curr;
+                       curr = curr->next;
+               }
+               if (prev == NULL)
+                       *head = new;
+               else prev->next = new;
+               new->next = curr;
+       }
+}
+
+void utillist_free(utilizationlist *head)
+/* free all entries in the list head */
+{
+       utilizationlist up1, up2;
+       for (up1 = *head; (up1 != NULL); up1 = up2)
+       {
+               up2 = up1->next;
+               free(up1);
+       }
+       *head = NULL;
+}
+
+int mergebooking(tokused **tokavail, time_t start, char *tok,
+                int nmach, int nconsec, int minpcfree, description labs)
+/*
+ * tokavail:   The list of available (tokens * labs * free) collected so far.
+ * start:      Period to be booked.
+ * tok:                The name of the token to be used.
+ * nmach:      The number of machines required to be booked at once.
+ * nconsec:    The number of consecutive free slots required.
+ * minpcfree:  A lab is not free unless % free > minpcfree
+ * labs:       a bitset indicating allowable labs
+ *
+ * Get the list of free labs at start using tokname, and add them to tokavail.
+ */
+{
+       utilizationlist freelist, flp;
+       tokused         *tp;
+       labavail                *lp;
+       consecfree              *cp;
+       char            *labname;
+       int                     labindex;
+       int                     i, dayofyear, periodofday;
+    
+#ifdef DEBUG
+       printf("Mergebooking(%d, '%s', ", nconsec, tok);
+       printslot(start, 1);
+       puts(")");
+#endif
+       /* get the token entry */
+       for (tp= *tokavail; (tp != NULL && strcmp(tp->tokname, tok)); tp=tp->next);
+       if (tp == NULL)
+       {
+               if ((tp = MALLOC(tokused)) != NULL)
+               {
+                       tp->tokname = tok;
+                       tp->labs = NULL;
+                       tp->next = *tokavail;
+                       *tokavail = tp;
+               }
+               else
+               {
+                       fputs("Malloc error\n", stderr);
+                       return 0;
+               }
+       }
+
+       /* get the list of free slots using token tok */
+       time_2_dp(start, &dayofyear, &periodofday);
+       freelist = freeslots(tok, dayofyear, periodofday);
+#ifdef DEBUG
+       printf("freeslots(%s, %d, %d) returns %snull\n",
+              tok, dayofyear, periodofday, (free == NULL) ? "" : "non-");
+#endif
+    
+       /* collate the labs returned */
+       for (flp = freelist; (flp != NULL); flp = flp->next)
+       {
+               labindex =  desc_2_labind(&(flp->machine_set));
+               if (labs.item_len == 0 ||
+                   query_bit(&labs, labindex))
+               {
+                       labname = get_mappingchar(labindex, M_ATTRIBUTE);
+#ifdef DEBUG
+                       printf("add to lab: '%s' free: %d\n", labname, flp->free);
+#endif
+                       for (lp=tp->labs;
+                            (lp != NULL && strcmp(lp->labname,labname));
+                            lp=lp->next);
+       
+                       if (lp == NULL)
+                       {
+                               /* add a new lab to this token's availablility */
+                               if ((lp = MALLOC(labavail)) != NULL)
+                               {
+                                       lp->labname = labname;
+                                       lp->nperiods = 0;
+               
+                                       /* add a circular list of nconsec free periods */
+                                       lp->consecfree = NULL;
+                                       for (i=0; (i < nconsec); i++)
+                                       {
+                                               if ((cp = MALLOC(consecfree)) != NULL)
+                                               {
+                                                       cp->nfree = cp->ntotal = 0;
+                                                       /* cp->periodofday = cp->dayofyear = 0; not necessary */
+                                                       cp->speclist = NULL;
+                                                       if (i == 0)
+                                                       {
+                                                               cp->next = cp;
+                                                               lp->consecfree = cp;
+                                                       }
+                                                       else
+                                                       {
+                                                               /* consecfree is a circular linked list */
+                                                               cp->next = lp->consecfree->next;
+                                                               lp->consecfree->next = cp;
+                                                       }
+                                               }
+                                               else
+                                               {
+                                                       fputs("Malloc error\n", stderr);
+                                                       return 0;
+                                               }
+                                       }
+               
+                                       lp->next = tp->labs;
+                                       tp->labs = lp;
+                               }
+                               else
+                               {
+                                       fputs("Malloc error\n", stderr);
+                                       return 0;
+                               }
+                       }
+                       else if (start - lp->laststart > SECINPERIOD) lp->nperiods = 0;
+       
+                       cp = lp->consecfree;
+                       if (lp->laststart != start)
+                       {
+                               /* Have not marked this lab off yet.
+                                * Note: it is possible to have more than one machine type in the
+                                * same lab. Each machine type may be returned in free */
+                               lp->laststart = start;
+                               cp->periodofday = periodofday;
+                               cp->dayofyear = dayofyear;
+                               cp->start = start; /* a bit redundant when nconsec == 1 */
+                               cp->ntotal = cp->nfree = 0;
+                               utillist_free(&(cp->speclist));
+                       }
+                       cp->nfree += flp->free;
+                       cp->ntotal += flp->total;
+       
+                       /* If there is more than one machine type free in the same lab,
+                        * keep track of the machine type with the most free so that this machine
+                        * type will be booked if this lab is selected.
+                        * Note: Users are given choices between labs, but not between machine
+                        * types within the same lab. We always book the machine type that is most free.
+                        */
+                       utillist_add(&(cp->speclist), flp);
+               }
+       }
+
+       /* neilb 16feb95
+        * if the token is available in privileged form atleast consecfree
+        * times, then set minpcfree to 0
+        */
+       if (minpcfree>0 && (nconsec * nmach) <= priv_tok_avail(tok))
+               minpcfree = 0;
+
+    
+       /* We have collated the number of all available terminals in each lab.
+        * Now check whether each lab meets the minimum free requirements.
+        */
+       tp->nlabs = 0;          /* the number of labs available */
+       for (lp = tp->labs; (lp != NULL); lp = lp->next)
+       {
+#ifdef DEBUG
+               printslot(lp->laststart, 1);
+               printf(": lab(%s) avail = %d", lp->labname, lp->consecfree->nfree);
+#endif
+               if (lp->laststart == start)
+               {
+                       cp = lp->consecfree;
+                       if (cp->nfree >= nmach && cp->ntotal * minpcfree < cp->nfree * 100)
+                       {
+                               lp->consecfree = cp->next;      /* next available consecfree */
+                               if (lp->nperiods < nconsec)
+                                       lp->nperiods++;
+                               if (lp->nperiods >= nconsec) tp->nlabs++;
+                       }
+                       else lp->nperiods = 0;
+               }
+               else lp->nperiods = 0;
+#ifdef DEBUG
+               printf(", nconsec = %d\n", lp->nperiods);
+#endif
+       }
+    
+       /* active labs are those with laststart == start && nperiods > 0
+        * available labs are those with laststart == start && nperiods == nconsec
+        */
+       return tp->nlabs;
+} /* mergebooking */
+
+void resetconsecperiods(tokused *tokavail)
+/* simply resets number of consecutive free periods available for
+ * all tokens, and all labs under all tokens. Called after sucessfully
+ * making a booking of required number of consecutive periods.
+ */
+{
+       tokused *tp;
+       labavail        *lp;
+       for (tp = tokavail; (tp != NULL); tp = tp->next)
+               for (lp = tp->labs; (lp != NULL); lp = lp->next) lp->nperiods = 0;
+}
+
+static void removebooking(bookuid_t user, booked *bp)
+{
+       if (unbook(user, bp->whenmade, bp->slot))
+       {
+               delbooking(bp);
+               puts("Unbook successfull");
+       }
+       else fprintf(stderr, "Unbook failed!\n");
+}
+
+int makechange(booked *bkp, int nmachines, char *newtokname, int *nbkp)
+/*
+ * Change the current booking:
+ *     from (bkp->tokname, bkp->nbooked)
+ *     to (newtokname, nmachines).
+ * Can only be used for class bookings.
+ * if (nmachines == 0) prompt user for new number of machines.
+ * return the number of booked machines in nbkp.
+ */
+{
+       slotgroup       slottobook;
+       int             maxfreeinlab, resp, nchange;
+       utilizationlist free, fp;
+
+       *nbkp = 0;
+    
+       time_2_dp(bkp->start, &(slottobook.doy), &(slottobook.pod));
+       slottobook.what = bkp->mdesc;
+
+       /* find the maximum additional bookings that can be made */
+       maxfreeinlab = 0;
+       free = freeslots(bkp->tokname, slottobook.doy, slottobook.pod);
+       for (fp = free; (fp != NULL); fp = fp->next)
+       {
+               /* compare free lab with lab booked */
+               if (strcmp(bkp->labname,
+                          get_mappingchar(desc_2_labind(&(fp->machine_set)),M_ATTRIBUTE))
+                   == 0)
+                       maxfreeinlab += fp->free;
+       }
+
+       if (nmachines == 0)
+       {
+               nmachines = maxfreeinlab + bkp->nbooked;
+               resp = getresponse("New number of bookings", &nmachines, 0);
+               if (resp != R_YES)
+                       return resp;
+               else if (nmachines == 0 &&
+                        (resp = getresponse("This will delete the current booking, are you sure", &nmachines, 1)) != R_YES)
+                       return resp;
+       }
+
+       if (nmachines == 0)
+       {
+               /* remove the class booking */
+               removebooking(user, bkp);
+               return R_YES;
+       }
+       else nchange = nmachines - bkp->nbooked;
+
+       if (nchange != 0 || strcmp(bkp->tokname, newtokname) != 0)
+               /* actually make the change */
+               if (makebook2(user, &slottobook, newtokname, bkp->whenmade, nchange, user, -1))
+               {
+                       printf("Changed booking (%s = %d) to (%s = %d)\n",
+                              bkp->tokname, bkp->nbooked,
+                              newtokname, bkp->nbooked + nchange);
+                       bkp->nbooked = nmachines;
+                       bkp->tokname = newtokname;
+                       *nbkp = nmachines;
+               }
+               else
+               {
+                       puts("*** Failed to change booking ***");
+                       resp = R_NO;
+               }
+       else
+       {
+               resp = R_YES;
+               *nbkp = nmachines;
+       }
+       return resp;
+}
+
+
+int try_change_token(booked *bkp, int nmachines, int *nbookedp)
+{
+       /*
+        * If there exists an active token that books the same lab as
+        * is already booked by bkp->tokname, then change the booking bkp
+        * to use the first such token.
+        */
+       currtokentype *tokenp;
+       int     *labindex;
+       int     ret, found;
+
+       for (tokenp = currtokens; (tokenp != NULL); tokenp = tokenp->next)
+       {
+               if (tokenp->active == command_no)
+               {
+                       found = 0;
+                       labindex = tokname2labs(tokenp->name);
+                       while (*labindex != -1 &&
+                              ! (found = (strcmp(get_mappingchar(*labindex, M_ATTRIBUTE), bkp->labname) == 0)))
+                               labindex++;
+                       if (found) break;
+               }
+       }
+       if (found)
+               ret = makechange(bkp, nmachines, tokenp->name, nbookedp);
+       else
+       {
+               *nbookedp = 0;
+               ret = R_NO;
+       }
+       return ret;
+}
+
+int check_class_changes(booked *bkp, int nmachines, int *nbookedp)
+{
+       /* Try to change bkp into booking nmachines using one of
+        * the currently active tokens that books the same lab as
+        * is already booked by bkp->tokname.
+        * If possible, use the same token as is already used.
+        */
+       int ret;
+       if (token_active(bkp->tokname))
+               ret = makechange(bkp, nmachines, bkp->tokname, nbookedp);
+       else ret = try_change_token(bkp, nmachines, nbookedp);
+       return ret;
+}
+
+void b_showfree(command_type *comm)
+{
+       /* Called for commands: "book" and "available"
+        * Using the token(s) passed, and over the period(s) specified,
+        * find those labs that are still available, print them out,
+        * and if required allow the user to choose to make bookings.
+        */
+       booked  *bookedp, *bkp;
+       int             nconsec;        /* number of consecutive periods being booked */
+       int             nmachreq;       /* number of machines requested at a time */
+       int             minmachreq;     /* min no of machines to be booked at a time */
+       int             nbooked;        /* the number of machines already booked */
+       int             makebooking, totaltokens;
+       time_t  start, end, reserve_end, delay;
+       int             tofirst, ntoken, nfound, minpcfree;
+       int             response, i, j;
+       int             ignore_tokcnt=0; /* if "all" option is given and this is "Avail" command, we ignore token limitations */
+       tokused *tokavail;
+       slotgroup       slottobook;
+       currtokentype   *tokenp;
+    
+       makebooking = (comm->command == BOOK);
+       if (!makebooking && option_value(comm->options, O_ALL_TOKENS)>0) ignore_tokcnt = 1;
+       if ((nconsec = option_value(comm->options, O_CONSEC)) == 0) nconsec = 1;
+       if (user_is_class)
+               /* note: (nmachreq == 0) ==> (prompt user for nmachreq) */
+               nmachreq = option_value(comm->options, O_MACHINES);
+       else nmachreq = 1;
+    
+       /* get the set of tokens to be used for bookings */
+/*    update_tokens(user); */
+
+       /* find out the delay time that people can book in, ie the time
+          limit before a period that they can book for the next period */
+       delay=0;
+       delay = get_configint("book_delay");
+       if (delay <= 0 ) 
+               delay = 600; /* 10 minutes by default */
+    
+       /* can only book periods from now onwards */
+       start = time_2_dp(currtime() + delay, &i, &j);
+       /* ordinary user cannot book the current, or next period */
+       if (! privilaged) start += MINPERIODAHEAD * SECINPERIOD;
+    
+       /* default period is all non-excluded periods */
+       if ((tofirst = (comm->period == NULL)))
+               comm->period = absperiod(notperiod(copyperiods(empty_abs)));
+    
+       /* can only book next period onwards - exclude past periods */
+       if ((comm->period = exclude_past(comm->period)) == NULL)
+       {
+               fputs("\tCan only book or check future periods\n", stdout);
+               return;         /**** return ****/
+       }
+#ifdef DEBUG
+       puts("command period :");
+       printperiod(comm->period, "");
+       puts("token periods :");
+       printperiod(union_tokenperiods(), "");
+#endif
+    
+       /* intersect the requested period(s) with the token period(s) */
+       if ((comm->period = fintersect(union_tokenperiods(), comm->period)) != NULL)
+       {
+               if (comm->period->start > start)
+                       start = time_2_dp(comm->period->start, &i, &j); /* ignore i,j */
+               end = endofperiods(comm->period);
+       }
+#ifdef DEBUG
+       puts("intersect(command, token) :");
+       printperiod(comm->period, "");
+#endif
+    
+       if (comm->period == NULL || start > end)
+       {
+               fputs("\tTokens expired", stdout);
+               if (! tofirst) fputs(" or unavailable in specified period", stdout);
+               puts(".");
+               return;         /**** return ****/
+       }
+    
+       if ((totaltokens = count_active_tokens()) <= 0 && !ignore_tokcnt)
+       {
+               fputs("\tNo tokens available", stdout);
+               if (comm->ntokpassed != 0) fputs(" from tokens specified", stdout);
+               puts(".");
+               return;         /**** return ****/
+       }
+    
+       nfound = 0;
+       reserve_end = construct(1900+tnow.tm_year,0,tnow.tm_yday+MINDAYRESERVE,0,0);
+       tokavail = NULL;
+       for (; (start < end && ( totaltokens > 0 || ignore_tokcnt) && ! interrupt);
+            start += SECINPERIOD)
+       {
+               if (t_within_period(start, comm->period) != NULLP)
+               {
+                       nbooked = 0;
+                       if ((bookedp = alreadybooked(start, -1, NULL)) != NULL)
+                       {
+                               booked  *nextbkp;
+                               /* there may be more than one booking for this time */
+                               for (bkp = bookedp; (bkp != NULL && bkp->start == start); bkp = nextbkp)
+                               {
+                                       /* check_class_changes() might delete bkp from list */
+                                       nextbkp = bkp->next;
+                   
+                                       fputs("Period: ", stdout);
+                                       printslot(bkp->start, 1);
+                                       printf(" - already booked '%s'", bkp->labname);
+                   
+                                       if (bkp->owner == user)
+                                       {
+                                               if (user_is_class) printf(" (%d)", bkp->nbooked);
+                                               printf(" with token '%s'\n", bkp->tokname);
+
+                                               if (makebooking && user_is_class
+                                                   && ((nmachreq == 0 && nbooked == 0)
+                                                       || (nmachreq > nbooked)))
+                                               {
+                                                       int nbkd;
+                                                       if (check_class_changes(bkp, nmachreq-nbooked, &nbkd) == R_QUIT)
+                                                       {
+                                                               totaltokens = 0;
+                                                               break;
+                                                       }
+                                                       nbooked += nbkd;
+                                               }
+                                       }
+                                       else printf(" for class <%s>\n", bkp->ownername);
+                               }
+                       }
+                       /*
+                        * if (more tokens
+                        *    && (want to book more)
+                        *    && (not already booked or user is class)
+                        */ 
+                       if ((totaltokens != 0 || ignore_tokcnt)
+                           && ((nmachreq > nbooked) || (nmachreq == 0 && nbooked == 0))
+                           && (user_is_class || alreadybooked(start, user, NULL) == NULL))
+                       {
+                               if (start < reserve_end)
+                                       minpcfree = 0;
+                               else {
+                                       minpcfree = get_configint("warn_percent");
+                                       if (minpcfree < 0)
+                                               minpcfree = RESERVE_PC;
+                               }
+
+                               /* the minimum number of machines required at a time */
+                               if (user_is_class && nmachreq != 0)
+                                       minmachreq = (nmachreq - nbooked);
+                               else minmachreq = 1;
+
+                               /* collate the freeslots for all specified tokens */
+                               ntoken = 0;
+                               for (tokenp = currtokens;
+                                    (tokenp != NULL);
+                                    tokenp = tokenp->next)
+                               {
+                                       if (tokenp->active == command_no
+                                           && t_within_period(start, tokenp->period) != NULL
+                                           && (tokenp->number >= (nconsec * minmachreq) || ignore_tokcnt))
+                                       {
+                                               if (mergebooking(&tokavail, start, tokenp->name,
+                                                                minmachreq, nconsec, minpcfree,
+                                                                comm->labs))
+                                                       ntoken++;
+                                       }
+                               }
+               
+                               if (ntoken)
+                               {
+                                       /* we have specified at least one token that can be used
+                                        * to book nconsec periods in a lab.
+                                        */
+                                       fputs("Period: ", stdout);
+                                       printslot(start-((nconsec-1)*SECINPERIOD), nconsec);
+                                       if (nconsec > 1)
+                                               printf(" (%d consecutive slots", nconsec);
+                                       else printf(" (1 slot");
+                                       if (minmachreq > 1)
+                                               printf(", %d machines", minmachreq);
+                                       puts(")");
+                   
+                                       if (! makebooking)
+                                               printtokavail(nconsec, start, tokavail, makebooking, option_value(comm->options, O_COUNTS));
+                                       else
+                                       {
+                                               tokused *tp;
+                       
+                                               if (ntoken == 1)
+                                               {
+                                                       /* find and print the one token */
+                                                       for (tp = tokavail;
+                                                            (tp != NULL && tp->nlabs == 0);
+                                                            tp = tp->next);
+                                                       if (tp != NULL)
+                                                       {
+                                                               response = R_YES;
+                                                               fprintf(stdout,"Token: %s\n", tp->tokname);
+                                                       }
+                                                       else response = R_NO;   /* should not happen */
+                           
+                                               }
+                                               else response = choosetoken(nconsec, start, tokavail, &tp, ntoken);
+                       
+                                               switch (response)
+                                               {
+                                                       labavail        *labs;
+                                                       consecfree      *cp;
+                           
+                                               case R_YES:
+                                                       switch (chooselab(nconsec, start, tp, &labs, user, (comm->labs.item_len != 0)))
+                                                       {
+                                                       case R_YES:
+                                                               for (tokenp = currtokens;
+                                                                    (tokenp != NULL && strcmp(tokenp->name ,tp->tokname));
+                                                                    tokenp = tokenp->next);
+                                                               if (tokenp != NULL)
+                                                               {
+                                                                       utilizationlist up;
+                                                                       if (user_is_class)
+                                                                       {
+                                                                               int max;
+                                                                               /* classes can book more than one machine */
+                                                                               cp = labs->consecfree;
+                                                                               max = cp->nfree;
+                                                                               while ((cp=cp->next) != labs->consecfree)
+                                                                                       if (cp->nfree < max) max = cp->nfree;
+                                                                               if (nmachreq == 0)
+                                                                               {
+                                                                                       minmachreq = max;
+                                                                                       switch (response = getresponse("How many machines", &minmachreq, 1))
+                                                                                       {
+                                                                                       case R_YES:
+                                                                                               if (minmachreq == 0)
+                                                                                                       minmachreq = max;
+                                                                                               break;
+                                                                                       case R_QUIT:
+                                                                                               minmachreq = totaltokens = 0;
+                                                                                               break;
+                                                                                       case R_NO:
+                                                                                               minmachreq = 0;
+                                                                                               break;
+                                                                                       }
+                                                                               }
+                                                                               /* else minmachreq == (nmachreq-nbooked)) */
+                                                                       }
+                                   
+                                                                       /* for every consecutive slot chosen,
+                                                                        * book the number of machines required,
+                                                                        * from those groups of machines available.
+                                                                        */
+                                                                       cp = labs->consecfree;
+                                                                       /* for every consecutive slot: */
+                                                                       do {
+                                                                               int     tobook, nbook;
+                                                                               slottobook.doy = cp->dayofyear;
+                                                                               slottobook.pod = cp->periodofday;
+                                                                               currtime();     /* change value of now */
+                                                                               tobook = minmachreq;
+                                                                               /* Note: speclist is the list of
+                                                                                * machine groups that make up the lab,
+                                                                                * ordered from most free to least free.
+                                                                                */
+                                                                               for (up = cp->speclist;
+                                                                                    (up != NULL && tobook > 0);
+                                                                                    up = up->next)
+                                                                               {
+                                                                                       if (tobook > up->free)
+                                                                                               nbook = up->free;
+                                                                                       else nbook = tobook;
+                                                                                       slottobook.what = up->machine_set;
+                                                                                       if (makebook2(user,&slottobook,tokenp->name,enow,nbook, user, tokenp->number))
+                                                                                       {
+                                                                                               addbooking(&slottobook, enow, tokenp->name,
+                                                                                                          cp->start, labs->labname, nbook);
+                                                                                               update_tokens(user);
+                                                                                               totaltokens = (tofirst) ? 0 : count_active_tokens();
+                                                                                               tobook -= nbook;
+                                                                                               printf("Booked");
+                                                                                       }
+                                                                                       else printf("Failed to book");
+                                                                                       if (nbook > 1)
+                                                                                               printf(" %d machines,", nbook);
+                                                                                       printf(" period: ");
+                                                                                       printslot(cp->start, 1);
+                                                                                       putchar('\n');
+                                                                               }
+                                                                               if (tobook > 0 && minmachreq > 1)
+                                                                                       printf("Could not book %d out of %d machines\n", tobook, minmachreq);
+                                                                       } while ((cp=cp->next) != labs->consecfree);
+                                                                       resetconsecperiods(tokavail);
+                                                               }
+                                                               break;
+                                                       case R_QUIT:    totaltokens = 0; break;
+                                                       case R_NO:              break;
+                                                       }
+                                                       break;
+                                               case R_QUIT:    totaltokens = 0; break;
+                                               case R_NO:      break;
+                                               }
+                                       }
+                                       nfound += ntoken;
+                               }
+                       } /* period unbooked */
+               } /* start in selected period */
+       } /* for periods */
+    
+       if (nfound == 0)
+       {
+               fputs("\tNo future free slots found", stdout);
+               if (comm->ntokpassed) fputs(" using tokens specified", stdout);
+               if (interrupt || tofirst)
+               {
+                       fputs(" up to ", stdout);
+                       printslot(start, 1);
+               }
+               else fputs(" in period specified", stdout);
+               puts("");
+       }
+       if (interrupt || tofirst)
+       {
+               fputs("\tSearched up to ", stdout);
+               printslot(start, 0);
+               puts("");
+       }
+} /* b_showfree */
+
+char *status2str(int status)
+{
+       char *s;
+       switch (status)
+       {
+       case B_PENDING: s = "Pending";  break;
+       case B_TENTATIVE:       s = "Tentative"; break;
+       case B_ALLOCATED:       s = "Alloc'd";  break;
+       case B_RETURNED:        s = "Returned"; break;
+       case B_FREEBIE: s = "Free";     break;
+       case B_REFUNDED:        s = "Refund";   break;
+       case B_CANCELLED:       s = "Cancel";   break;
+       default:                s = "Unknown";  break;
+       }
+       return s;
+}
+
+void printperiodheader(int showstatus)
+{
+       printf("Lab\t\tToken or <Class>\t%s%sPeriod\n",
+              (showstatus ? "Status\t" : ""),
+              (user_is_class ? " N   " : ""));
+}
+
+void printbookperiod(booked *bookedp, int nconsec, int showstatus)
+{
+       int fieldlen;
+       printf("%-8s\t", bookedp->labname);
+       fieldlen = 24;
+       if (bookedp->owner == user)
+       {
+               printf("%s", bookedp->tokname);
+               fieldlen -= strlen(bookedp->tokname);
+       }
+       else
+       {
+               printf("<%s>", bookedp->ownername);
+               fieldlen -= (strlen(bookedp->ownername) + 2);
+       }
+       if (fieldlen > 0) printf("%*s", fieldlen, " ");
+       if (showstatus) printf("%s%s\t", status2str(bookedp->status), bookedp->defaulted?"(D)":"");
+       if (user_is_class) printf("%-4d ", bookedp->nbooked);
+       printslot(bookedp->start, nconsec);
+       fputs("\n", stdout);
+}
+
+void listbooking(booked *start, int showstatus)
+{
+       /* Print the booking start if it has not already been printed.
+        * Include as many consecutive bookings matching (and following) start
+        * as possible.
+        */
+       booked *nxt, *curr;
+       int nconsec;
+       if (start->printed != command_no)
+       {
+               nxt = curr = start;
+               nconsec = 1;
+               while (curr == nxt)
+               {
+                       curr->printed = command_no;
+                       /* skip same times */
+                       for (nxt = curr->next;
+                            (nxt != NULL && nxt->start == curr->start);
+                            nxt = nxt->next);
+                       /* get matching next period */
+                       while (nxt != NULL
+                              && nxt->start == curr->start + SECINPERIOD
+                              && (nxt->owner != curr->owner
+                                  || nxt->nbooked != curr->nbooked
+                                  || nxt->status != curr->status
+                                  || strcmp(nxt->tokname, curr->tokname)
+                                  || strcmp(nxt->labname, curr->labname)
+                                      )) nxt = nxt->next;
+                       if (nxt != NULL && nxt->start == curr->start + SECINPERIOD)
+                       {
+                               nconsec++;
+                               curr = nxt;
+                       }
+               }
+               printbookperiod(start, nconsec, showstatus);
+       }
+}
+
+void b_showbook(command_type *comm)
+/*
+ * This routine deals with bookings made within the periods specified using the
+ * tokens specified.
+ * Called with commands: "list" , "unbook", and "change".
+ * Restrictions:
+ * List:   None.
+ * Change: Only classes can invoke this.
+ * Unbook:
+ * The user will not be allowed to cancel a slot less than MINDAYPREUNBOOK
+ * days before the slot is to be taken up unless the booking was made less
+ * than UNBOOKGRACE seconds before the cancellation and the booking was
+ * more than MINPERIODAHEAD periods ahead (ie: not the current or next slot).
+ */
+{
+       booked  *bp, *next;
+       int             command, n, resp, nfound, nconsec, nmachreq;
+       time_t  start, minstart;
+
+       command = comm->command;
+       if (command == CHANGE)
+       {
+               if (user_is_class)
+                       nmachreq = option_value(comm->options, O_MACHINES);
+               else
+               {
+                       puts("Only classes may use the 'change' command - Other users must use 'book' or 'unbook'");
+                       return;
+               }
+       }
+
+       if (currbooked != NULL)
+       {
+               if (command == UNBOOK)
+               {
+                       /* set up unbook restrictions */
+                       start = currtime();
+                       start = time_2_dp(start, &n, &resp); /* n, resp ignored */
+                       start += MINPERIODAHEAD * SECINPERIOD;
+                       minstart = construct(1900+tnow.tm_year, 0, tnow.tm_yday+MINDAYPREUNBOOK,
+                                            0, 0) - 1;
+               }
+               nfound = nconsec = 0;
+               printperiodheader(0);
+       
+               for (bp = currbooked; (bp != NULL); bp = next)
+               {
+                       int pod,doy,lab;
+                       book_key_bits(&bp->slot, &doy, &pod, &lab);
+                       /* check_class_changes() or removebooking() might delete bp from list */
+                       next = bp->next;
+           
+                       if ((comm->period == NULLP || t_lap_period(bp->start, comm->period))
+                           && (comm->labs.item_len == 0 || query_bit(&comm->labs, lab))
+                               )
+                       {
+                               if (command == CHANGE)
+                               {
+                                       if (bp->owner == user)
+                                       {
+                                               int n;          /* we ignore the number booked */
+                                               nfound++;
+                                               printbookperiod(bp, 1, 0);
+                                               if (check_class_changes(bp, nmachreq, &n) == R_QUIT)
+                                                       break;
+                                       }
+                               }
+                               else if (comm->ntokpassed == 0 || token_active(bp->tokname))
+                               {
+                                       if (command == LIST)
+                                       {
+                                               nfound++;
+                                               listbooking(bp, 0);
+                                       }
+                                       else /* command == UNBOOK */
+                                               if (bp->owner == user &&
+                                                   (privilaged || bp->start > minstart ||
+                                                    (bp->start >= start &&
+                                                     (bp->whenmade >= enow - UNBOOKGRACE || token_is_reusable(bp->tokname))
+                                                            )))
+                                               {
+                                                       nfound++;
+                                                       if (option_value(comm->options, O_CONFIRM))
+                                                       {
+                                                               printbookperiod(bp, 1, 0);
+                                                               n = 0;
+                                                               resp = getresponse("Unbook", &n, 1);
+                                                       }
+                                                       else resp = R_YES;
+                                                       if (resp == R_QUIT)
+                                                               break;
+                                                       else if (resp == R_YES)
+                                                       {
+                                                               removebooking(user, bp);
+                                                               /* unbook only the first if no period specified */
+                                                               if (comm->period == NULLP) break;
+                                                       }
+                                               }
+                               }
+                       }
+               }
+               if (nfound == 0)
+               {
+                       fputs("\tNo bookings", stdout);
+                       if (comm->ntokpassed != 0) fputs(" using tokens specified",stdout);
+                       if (comm->period != NULLP) fputs(" in period specified", stdout);
+                       switch (command)
+                       {
+                       case LIST:              puts(" were found"); break;
+                       case UNBOOK:    puts(" could be unbooked"); break;
+                       case CHANGE:    puts(" could be changed"); break;
+                       }
+               }
+       }
+       else puts("\tNo bookings recorded");
+} /* b_showbook */
+
+void b_showpastbook(command_type *comm)
+/* This routine shows past bookings made within the periods specified using the
+ * tokens specified, and if refunding is true, will allow the user to
+ * interactively have these bookings refunded, depending on the status of the
+ * booking.
+ * There are a couple of restrictions on refunding:
+ *     Only privilaged people may make refunds.
+ *     Only bookings that have been ALLOCATED may be refunded.
+ */
+{
+       booked  *bp;
+       int             command, nconsec, resp, n, nfound, doy, pod;
+       time_t  start;
+       reclaim_result  recres;
+
+       switch (command = comm->command)
+       {
+       case REFUND:
+               if (! privilaged)
+               {
+                       puts("\tOnly privilaged users may attempt refunds");
+                       return;
+               }
+               else break;
+       case RECLAIM:
+               /* create a dummy current period */
+               start = currtime();
+               start = time_2_dp(start, &doy, &pod);   /* ignore doy, pod */
+               comm->period = newperiod(ABSOLUTE, start , start + SECINPERIOD , NULLP);
+               comm->ntokpassed = 0;   /* ignore any tokens passed */
+               break;
+       }
+    
+       if (pastbooked != NULL)
+       {
+               nfound = nconsec = 0;
+               if (command != RECLAIM) printperiodheader(command == PAST);
+               for (bp = pastbooked; (bp != NULL); bp = bp->next)
+               {
+                       if ((comm->period == NULLP || t_lap_period(bp->start, comm->period))
+                           && (comm->ntokpassed == 0 || token_active(bp->tokname)))
+                       {
+                               char *cp;
+                               switch (command)
+                               {
+                               case PAST:
+                                       nfound++;
+                                       listbooking(bp, 1);
+                                       break;
+                               case REFUND:
+                                       /* can only refund Allocated, returned and freebie bookings */
+                                       if (bp->status == B_ALLOCATED || B_FREEBIE || B_RETURNED)
+                                       {
+                                               nfound++;
+                                               printbookperiod(bp, 1, 0);
+                                               n = 0;
+                                               resp = getresponse("Refund", &n, 1);
+                                               if (resp == R_YES)
+                                               {
+                                                       if (refund(user, bp->whenmade, bp->slot))
+                                                       {
+                                                               bp->status = B_REFUNDED;
+                                                               puts("Refund successfull");
+                                                       }
+                                                       else fprintf(stderr, "Refund failed!\n");
+                                               }
+                                               else if (resp == R_QUIT)
+                                               {
+                                                       bp = NULL ; /* fall out for for loop as well as switch */
+                                                       break;
+                                               }
+                                       }
+                                       break;
+                               case RECLAIM:
+                                       /* would be faster to simply reclaim the most recent past booking */
+                                       recres = reclaim(bp->labname, user);
+                                       switch (recres.state)
+                                       {
+                                       case RECOK:
+                                               printf("Machine '%s' has been reclaimed\n", recres.host);
+                                               cp = ctime(&recres.when);
+                                               if (cp)
+                                                       printf("You should be able to log in at %5.5s\n",cp+11);
+                                               nfound++;
+                                               break;
+                                       case RECNO:
+                                               fprintf(stderr, "Reclaim failed - Network Problem.\n");
+                                               break;
+                                       case RECUNASSIGNED:
+                                               fprintf(stderr, "No current booking for lab '%s'\n", bp->labname);
+                                               break;
+                                       case RECDOWN:
+                                               fputs("Allocated to a terminal that is down - cannot reclaim it\n", stderr);
+                                               break;
+                                       case RECUNUSED:
+                                               printf("Your allocated machine '%s' is not being used. Go get it\n",
+                                                      recres.host);
+                                               break;
+                                       case RECSELF:
+                                               fputs("You cannot reclaim a booking you are already using.\n",
+                                                     stderr);
+                                               break;
+                                       }
+                                       break;
+                               }
+                       }
+               }
+               if (nfound == 0)
+               {
+                       fputs("\tNo bookings", stdout);
+                       if (comm->ntokpassed != 0) fputs(" using tokens specified",stdout);
+                       if (comm->period != NULLP) fputs(" in period specified", stdout);
+                       fputs(" could be ", stdout);
+                       switch (command)
+                       {
+                       case REFUND: puts("refunded"); break;
+                       case PAST:      puts("found"); break;
+                       case RECLAIM: puts("reclaimed"); break;
+                       }
+               }
+       }
+       else puts("\tNo past bookings recorded");
+} /* b_showpastbook */
+
+/************ the main booking commmand routines **************/
+/*** note: b_define and b_undefine are defined in bperiod.c ***/
+
+void b_token(command_type *comm)
+{
+       printtokens(comm->ntokpassed, option_value(comm->options, O_ALL_TOKENS));
+       /* ignore period if passed to token command */
+}
+
+void b_labs(command_type *comm)
+{
+       /* for each lab, see if it is bookable at any time
+        * in the given range
+        */
+       int *lablist;
+       int *openlist;
+       int l;
+       time_t aperiod;
+       lablist = get_all_labs();
+       for (l=0; lablist[l]!= -1 ; l++);
+       openlist=(int*)malloc(sizeof(int)*(l+1));
+
+       if (comm->period == NULL)
+               comm->period = absperiod(range(book_mktime(0,0), book_mktime(23,59)));
+       comm->period = exclude_past(comm->period);
+       if (comm->period != NULL)
+       {
+               for (l=0; lablist[l]!= -1 ; l++)
+                       if (!lab_excluded(lablist[l]))
+                       {
+                               openlist[l]=0;
+                               for (aperiod=comm->period->start;
+                                    aperiod <= comm->period->end && openlist[l]==0;
+                                    aperiod+= SECINPERIOD)
+                               {
+                                       int doy,pod;
+                                       time_2_dp(aperiod, &doy, &pod);
+
+                                       if (check_bookable(doy, pod, lablist[l]))
+                                               openlist[l]=1;
+                               }
+                       }
+                       else openlist[l]=0;
+               for (l=0 ; lablist[l]!= -1; l++)
+                       if (openlist[l])
+                       {
+                               char *n = get_mappingchar(lablist[l], M_ATTRIBUTE);
+                               if (n) printf(" %s", n);
+                               if (n) free(n);
+                       }
+       }
+       printf("\n");
+       free(openlist);
+       free(lablist);
+}
+
+void b_times(command_type *comm)
+{
+       /* for each bookperiod in the time given, check
+        * to see if it might be bookable in any lab with any token
+        */
+       int *lablist;
+       int periodlist[SECINDAY/SECINPERIOD];
+       time_t aperiod;
+       int p;
+       int l;
+       int inrange;
+
+       lablist = get_all_labs();
+       if (comm->period == NULL)
+               comm->period = absperiod(range(book_mktime(0,0), book_mktime(23,59)));
+       comm->period = exclude_past(comm->period);
+       memset(periodlist, 0, sizeof(periodlist));
+       for (l=0 ; lablist[l] != -1 ; l++)
+               if (lab_excluded(lablist[l])) lablist[l] = -2;
+       for (aperiod = comm->period->start;
+            aperiod <= comm->period->end;
+            aperiod += SECINPERIOD)
+       {
+               int dperiod = (aperiod % SECINDAY)/SECINPERIOD;
+               int l;
+               for (l=0; lablist[l]!= -1 && periodlist[dperiod]==0;
+                    l++)
+                       if (lablist[l]>=0)
+                       {
+                               int doy,pod;
+                               time_2_dp(aperiod, &doy, &pod);
+
+                               if (check_bookable(doy, pod, lablist[l]))
+                                       periodlist[dperiod]=1;
+                       }
+       }
+       free(lablist);
+       inrange=0;
+       for (p=0; p<=SECINDAY/SECINPERIOD ; p++)
+       {
+               if (p<SECINDAY/SECINPERIOD && periodlist[p])
+               {
+                       if (!inrange)
+                               printf(" %02ld:%02ld", p*SECINPERIOD/3600, ((p*SECINPERIOD)%3600)/60);
+                       inrange++;
+               }
+               else
+               {
+                       if (inrange > 1)
+                               printf("-%02ld:%02ld", (p*SECINPERIOD-60)/3600, (((p*SECINPERIOD-60))%3600/60));
+                       inrange=0;
+               }
+       }
+       printf("\n");
+    
+}
+
+void b_help(command_type *comm)
+{
+       int help_type, help_value;
+
+       help_type = option_value(comm->options, O_HELP_TYPE);
+       help_value = option_value(comm->options, O_HELP_VALUE);
+
+       if (help_type != 0 || (comm->period == NULLP && comm->ntokpassed == 0))
+               printhelp(typevaltostr(help_type, help_value));
+
+       if (help_type == TOPIC && help_value == PREDEF)
+               printpredefs();
+
+       if (comm->ntokpassed > 0 || (help_value == TOKEN 
+                                    && (help_type == COMND || help_type == TOPIC)))
+               printtokens(comm->ntokpassed, 0);
+
+       if (comm->period != NULLP)
+       {
+               putchar('\t');
+               printperiod(comm->period, "\t");
+       }
+}
+
+void b_echo(command_type *comm)
+{
+       strlist *sp;
+       for (sp = comm->strlist; (sp != NULL); sp = sp->next)
+               fputs(sp->str_val, stdout);
+       puts("");
+}
+
+
+void b_shell(command_type *comm)
+{
+       /* fork, become user, and execute users shell as a login shell
+        * parent waits for 4 minutes and then (warning at 3 minutes)
+        * kills the child and continues
+        */
+       extern int ntimeoutwarnings;
+       int childid = 0, pid;
+       struct passwd *pwe;
+       char *sh;
+       char shellbase[100];
+       char *sl;
+       int sig;
+
+       if (realuser != 0 && realuser != user)
+       {
+               fputs("book: cannot run a shell unless you are booking for your self\n", stderr);
+               return;
+       }
+    
+       if (user_is_class)
+       {
+               fputs("book: cannot run shell for a class!!\n", stderr);
+               return;
+       }
+#if 0
+#ifdef TIOCGPGRP
+       ioctl(0, TIOCGPGRP, &termpgrp);
+#else
+       termpgrp = tcgetpgrp(0);
+#endif
+#endif
+       switch(childid = fork())
+       {
+       default: /* parent */
+               break;
+       case -1:
+               perror("fork");
+               fputs("book: Cannot fork a new shell, sorry!\n", stderr);
+               return ;
+       case 0: /* child */
+               setuid(0);
+               pwe = getpwuid(user);
+               if (pwe == NULL)
+               {
+                       fputs("book: cannot find user database entry, sorry.\n", stderr);
+                       exit(1);
+               }
+               initgroups(pwe->pw_name, pwe->pw_gid);
+               setgid(pwe->pw_gid);
+               setuid(user);
+               if (chdir(pwe->pw_dir)!= 0)
+               {
+                       perror("book: chdir");
+                       fprintf(stderr, "book: cannot change to HOME directory %s\n", pwe->pw_dir);
+                       exit(1);
+               }
+               sh = pwe->pw_shell;
+               if (sh == NULL || sh[0] == '\0')
+                       sh = "/bin/sh";
+#if 0
+               if (strncmp(sh, "/usr/local/etc/logins/", 22)==0)
+               {
+                       strcpy(shellname, "/usr/local/bin/");
+                       strcat(shellname, sh+22);
+                       sh=  shellname;
+               }
+#endif
+               sl = strrchr(sh, '/');
+               shellbase[0] = '-';
+               if (sl)
+                       strcpy(shellbase+1, sl+1);
+               else
+                       strcpy(shellbase+1, sh);
+               fputs(" ....... Running a Shell. You have five minutes........\n", stderr);
+               putenv("LNAME=root");
+               execl(sh, shellbase, NULL);
+               perror(shellbase);
+               fputs("Failed to run your shell.\n", stderr);
+               exit(120);
+       }
+       ntimeoutwarnings = -1;
+       alarm(4*60);
+       pid = wait(0);
+       sig = 0;
+       while (pid != childid && (pid >=0 || errno != ECHILD))
+       {
+               switch(sig)
+               {
+               case 0:
+                       sig = 15;
+                       fputs("\n------- Shell will be killed in one minute, please finish up ------ \n" , stderr);
+                       alarm(60);
+                       break;
+               case 15:
+                       fputs("\n======= Time expired, killing shell and exitting ======\n", stderr);
+                       kill(childid, sig);
+                       sig = 9;
+                       alarm(3);
+                       break;
+               case 9:
+                       kill(childid, sig);
+                       alarm(3);
+               }
+               pid = wait(0);
+       }
+       if (sig == 9) exit(0);
+       alarm(0);
+#if 0
+#ifdef TIOCSPGRP
+       ioctl(0, TIOCSPGRP, &termpgrp);
+#else
+       tcsetpgrp(0, termpgrp);
+#endif
+#endif
+}
diff --git a/book/bcomm.h b/book/bcomm.h
new file mode 100644 (file)
index 0000000..237f5fe
--- /dev/null
@@ -0,0 +1,72 @@
+/*** basic booking interface constants ***/
+
+#define UNBOOKGRACE    3600    /* seconds grace to unbook */
+#define        MINDAYPREUNBOOK 1       /* days before slot before cannot unbook */
+#define        MINPERIODAHEAD  1       /* min periods from current before can book */
+#define        MINDAYRESERVE   4       /* min days before reserves are considered */
+#define        RESERVE_PC      10      /* percent terminals reserved per lab */
+#define        WARNING         1       /* non-allocation warning issued */
+#define MAXBOOK                200     /* maximum number of class bookings at a time */
+
+/*** user interactive response types ***/
+
+#define        R_BAD   0
+#define        R_NO    1
+#define        R_YES   2
+#define        R_QUIT  3
+
+/*** structures used to keep track of currently available tokens ***/
+
+typedef struct currtokentype {
+    char       *name;
+    int                number;
+    period     *period;
+    int                active;         /* == command_no if token was specified in command */
+    struct currtokentype *next;
+} currtokentype;
+
+/*** structures used to keep track of current bookings ***/
+
+typedef struct bookstruct booked;
+struct bookstruct {
+       description     mdesc;  /* the machine type booked - used to change booking */
+       book_key        slot;   /* the slot that was booked -  used by unbook() */
+       int     whenmade;       /* when the booking was made - used by unbook() */
+       char    *tokname;       /* name of token */
+       time_t  start;          /* start of period */
+       char    *labname;       /* the lab that the booking was made for */
+       bookuid_t       owner;  /* the user (or class) owning the booking */
+       char    *ownername;     /* the name of the user owning the booking */
+       int     nbooked;        /* the number of bookings; >=1 for class bookings */
+       booking_status  status; /* the status of the booking */
+       int     defaulted;      /* non-zero if user defaulted on this booking */
+       int     printed;        /* == command_no if this booking has been printed */
+       booked  *next;
+};
+
+/*** structures used to keep track of free labs and machines ***/
+
+typedef struct consecfree {
+       int     nfree;          /* avail machines in lab using token */
+       int     ntotal;         /* total machines in lab using token */
+       int     periodofday;    /* passed to makebook() to make bookings */
+       int     dayofyear;      /* passed to makebook() to make bookings */
+       time_t  start;          /* start of period; stored when period booked */
+       utilizationlist speclist;       /* all machine specs for this lab & time */
+       struct consecfree       *next;
+} consecfree;
+
+typedef struct labavail {
+    char       *labname;       /* the name of the lab */
+    time_t     laststart;      /* start time of last period in this series */
+    int                nperiods;       /* consecutive periods in series */
+    consecfree *consecfree;    /* list of consecutive free specs */
+    struct labavail    *next;
+} labavail;
+
+typedef struct tokused {
+    char               *tokname;       /* the name of the token */
+    labavail           *labs;          /* labs available using this token */
+    int                        nlabs;          /* the number of labs available */
+    struct tokused     *next;
+} tokused;
diff --git a/book/bkopen.c b/book/bkopen.c
new file mode 100644 (file)
index 0000000..7fa5fb4
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * Read the system book period spec file, and print out the system opening
+ *     and closing times for the next 7 days.
+ * This program was based on boo.c - the book interface main program, and
+ *     includes many of the modules used in that program.
+ * Note:       If compiled with DEBUG defined,
+ *             will allow user to interactively define, exclude, and inspect periods
+ *             before printing opening and closing times.
+ */
+
+#include       <stdio.h>
+#include       <ctype.h>
+#include       <time.h>
+#include       "boo.h"
+
+extern struct tm       tnow;   /* current time - tm format */
+extern time_t          now;    /* current time - seconds */
+extern int     nexttoken(FILE *fptr);                  /* blex.c */
+extern command_type    *getcommand(FILE *fptr);        /* bparse.c */
+extern void    rfree(period *pp);                      /* bparse.c */
+extern void    b_define(command_type *comm);           /* bperiod.c */
+extern void    b_undefine(command_type *comm);         /* bperiod.c */
+extern period  empty_abs[];                            /* bperiod.c */
+extern time_t  currtime();                             /* bperiod.c */
+extern void    initperiods();                          /* bperiod.c */
+extern period  *newperiod(int type, time_t start, time_t end, period *next);
+extern time_t  construct(int year, int month, int day, int hour, int minute);
+extern period  *intersect(period *p1, period *p2);     /* bparse.c */
+extern period          *valid_abs, *valid_hour, *valid_day;
+
+/********** global variables **************/
+
+char   *infile;                /* the name of the file currently being read */
+#ifdef DEBUG
+    char       *sys_cfile =    "periods";
+#else
+    char       *sys_cfile =    "/usr/local/book/periods";
+#endif
+int    interrupt = 0;
+int    parse_err = 0;          /* set of [ FATAL, RECOVER ]  - NOT CURRENTLY USED */
+int    c_inline = 1;           /* the current unprocessed character */
+
+/**** dummy routines to replace the real token routines in bcomm.c */
+
+int mark_token(char *tokname)
+{
+    return 0;
+}
+
+int mark_all_tokens(int include_expired)
+{
+}
+
+strlist *intokenlist(char *tokname, strlist *strings)
+/* return the entry in the strings list matching tokname */
+{
+    while (strings != NULL && strcmp(strings->str_val, tokname))
+       strings = strings->next;
+    return (strings);
+}
+
+void printcommand(command_type *cp)
+/* a debug routine to print out commands, tokens, periods */
+{
+#ifdef DEBUG
+    strlist    *sp;
+    if (cp != NULLC)
+    {
+       printf("Command:\n\t%s\n", typevaltostr(COMND, cp->command));
+
+       puts("Options:");
+       for (opt = cp->options; (opt != NULL); opt = opt->next)
+           printf("\t%d = %d\n", opt->option, opt->value);
+
+       puts("Strings:");
+       if (cp->strlist != NULL)
+           for (sp = cp->strlist; (sp != NULL); sp = sp->next)
+               printf("\t%s\n", sp->str_val);
+
+       puts("Periods:");
+       if (cp->period != NULLP)
+       {
+           putchar('\t');
+           printperiod(cp->period, "\t");
+       }
+       else puts("\tNo period passed");
+    }
+    else puts("Null command");
+#else
+    fprintf(stderr, "Ignore command: %s\n", typevaltostr(COMND, cp->command));
+#endif
+}
+
+int readcommands(char *fname)
+/* The main loop to read book commands.
+ * Used to read setup files and interactive commands (hence fname).
+ */
+{
+    FILE *fptr;
+    char *prompt = "BOPEN > ";
+    command_type       *cp;
+    int quit = 0;
+
+    if (fname != NULL)
+    {
+       if ((fptr = fopen(fname, "r")) == NULL) return 0;
+    }
+    else fptr = stdin;
+
+    infile = fname;
+    c_inline = 1;
+    if (fptr == stdin) printf(prompt); /* only prompt interactive input */
+    while (! quit && nexttoken(fptr) != END)
+    {
+       if (! interrupt)
+       {
+           parse_err = 0;
+           cp = getcommand(fptr);
+           if (cp != NULLC)
+           {
+               if (cp->period == empty_abs)
+                   puts("\tPeriod spec maps to an empty or invalid period");
+               else
+               {
+                   switch (cp->command)
+                   {
+                   case DEFINE: b_define(cp); break;
+                   case UNDEF: b_undefine(cp); break;
+                   default:    printcommand(cp); break;
+                   }
+                   if (cp->command != DEFINE) rfree(cp->period);
+               }
+           }
+       }
+       if (fptr == stdin && ! quit) printf(prompt); /* only prompt interactive input */
+       if (interrupt) interrupt = 0;
+    }
+    if (fptr != stdin) fclose(fptr);
+    return 1;
+}
+
+char   *dayofweek[] = { "su", "mo", "tu", "we", "th", "fr", "sa" };
+
+void printdaytime(time_t timeofday, time_t startofday)
+{
+    time_t     secofday = timeofday - startofday;
+    printf("%02d%02d", secofday/SECINHOUR, (secofday%SECINHOUR)/SECINMIN);
+}
+
+void printstartend(period *periods, time_t startofday)
+{
+    period *pp;
+    if (periods != NULL)
+    {
+       printdaytime(periods->start, startofday);
+       for (pp = periods; (pp->next != NULL); pp = pp->next);
+       putchar('-');
+       printdaytime(pp->end, startofday);
+    }
+    else puts("0000-0000");
+}
+
+void print_opening_times()
+{
+    int                day;
+    time_t     start;
+    period     *dayperiod, *pp;
+    struct tm  *tp;
+    
+    start = construct(1900+tnow.tm_year, 0, tnow.tm_yday, 0, 0);
+    dayperiod = newperiod(ABSOLUTE, start, start+SECINDAY, NULL);
+    for (day = 1; (day <= 7); day++)
+    {
+       if (day != 1) putchar(',');
+       tp = gmtime(&start);
+       printf("%s", dayofweek[tp->tm_wday]);
+       pp = intersect(valid_abs,
+                      intersect(valid_day,
+                                intersect(valid_hour, dayperiod)));
+       printstartend(pp, start);
+       rfree(pp);
+       start += SECINDAY;
+       dayperiod->start = start;
+       dayperiod->end = start + SECINDAY;
+    }
+    putchar('\n');
+}
+
+main(int argc, char **argv)
+{
+    currtime();
+    initperiods();
+
+    /* read in the period specification files */
+    if (! readcommands(sys_cfile)) perror(sys_cfile);
+
+#ifdef DEBUG
+    readcommands((char *) NULL);
+    puts("");
+#endif
+    
+    /* now print out the next seven days */
+    print_opening_times();
+}
diff --git a/book/blex.c b/book/blex.c
new file mode 100644 (file)
index 0000000..7f543a8
--- /dev/null
@@ -0,0 +1,371 @@
+/*
+ * Lexical analyser for book interface
+ */
+
+#include       <sys/types.h>
+#include       <stdio.h>
+#include       <rpc/rpc.h>
+#include       "../interfaces/bookinterface.h"
+#define BOOK_INTERFACE
+#include       "boo.h"
+#include       "blex.h"
+#include       <ctype.h>
+#include       <string.h>
+#include       <sys/errno.h>
+
+
+char   strbuff[MAXTOKSTR];     /* input buffer */
+char   *inchar = " ";          /* input buffer */
+int    instring = 0;           /* indicates that we are in middle if string */
+
+#ifdef DEBUG
+extern int     debug;
+#endif
+
+/* Used by nexttoken() to identify strings.
+ * Minmatch is used to choose between ambiguous string prefixes.
+ * viz:
+ *     if S(n): set of strings with minmatch <= n;
+ *     then for all i,j members of S(n): prefix(i, n) <> prefix(j, n)
+ * If not set up like this then nexttoken() will choose first of 
+ * conflicting alternatives.
+ */
+struct slist slist[] =
+    {
+
+       {       "not",          NOT,    0,      3       },
+
+       {       "help",         COMND,  HELP,   1       },
+       {       "book",         COMND,  BOOK,   1       },
+       {       "unbook",       COMND,  UNBOOK, 1       },
+        {      "change",       COMND,  CHANGE, 1       },
+       {       "reclaim",      COMND,  RECLAIM, 3      },
+       {       "claim",        COMND,  RECLAIM, 2      },
+       {       "available",    COMND,  AVAIL,  1       },
+       {       "test",         COMND,  AVAIL,  2       },
+       {       "show",         COMND,  LIST,   1       },
+       {       "list",         COMND,  LIST,   1       },
+       {       "token",        COMND,  TOKEN,  1       },
+       {       "define",       COMND,  DEFINE, 1       },
+       {       "undefine",     COMND,  UNDEF,  3       },
+       {       "echo",         COMND,  ECHO,   1       },
+       {       "refund",       COMND,  REFUND, 3       },
+       {       "past",         COMND,  PAST,   1       },
+       {       "showpast",     COMND,  PAST,   5       },
+       {       "times",        COMND,  TIMES,  3       },
+       {       "labs",         COMND,  LABS,   3       },
+       {       "quit",         COMND,  QUIT,   1       },
+       {       "exit",         COMND,  QUIT,   2       },
+       {       "shell",        COMND,  SHELL,  3       },
+
+       {       "period",       TOPIC,  PERIOD, 2       },
+       {       "predef",       TOPIC,  PREDEF, 2       },
+       {       "syntax",       TOPIC,  SYNTAX, 2       },
+       {       "command",      TOPIC,  COMND,  2       },
+       {       "cfile",        TOPIC,  CFILE,  2       },
+        {      "tokens",       TOPIC,  TOKEN,  6       },
+       {       "defaulter",    TOPIC,  DEFAULTER, 4    },
+       {       "summary",      TOPIC,  SUMMARY, 4      },
+
+        {      "all",          OPTION, O_ALL_TOKENS,   3       },
+        {      "consecutive",  OPTION, O_CONSEC,       6       },
+        {      "machines",     OPTION, O_MACHINES,     4       },
+       {       "confirm",      OPTION, O_CONFIRM,      7       },
+       {       "counts",       OPTION, O_COUNTS,       6       },
+       
+       {       "am",           APM,    0,      2       },
+       {       "pm",           APM,    12,     2       },
+
+       {       "midday",       HOUR,   12,     4       },
+       {       "midnight",     HOUR,   0,      4       },
+
+       {       "monday",       DAY,    MON,    1       },
+       {       "tuesday",      DAY,    TUE,    2       },
+       {       "wednesday",    DAY,    WED,    1       },
+       {       "thursday",     DAY,    THU,    2       },
+       {       "friday",       DAY,    FRI,    1       },
+       {       "saturday",     DAY,    SAT,    2       },
+       {       "sunday",       DAY,    SUN,    2       },
+       {       "today",        DAY,    TODAY,  3       },
+       {       "tomorrow",     DAY,    TOMOR,  3       },
+
+       {       "january",      MONTH,  0,      2       },
+       {       "february",     MONTH,  1,      2       },
+       {       "march",        MONTH,  2,      3       },
+       {       "april",        MONTH,  3,      2       },
+       {       "may",          MONTH,  4,      3       },
+       {       "june",         MONTH,  5,      3       },
+       {       "july",         MONTH,  6,      3       },
+       {       "august",       MONTH,  7,      2       },
+       {       "september",    MONTH,  8,      2       },
+       {       "october",      MONTH,  9,      1       },
+       {       "november",     MONTH,  10,     1       },
+       {       "december",     MONTH,  11,     3       },
+
+       {       "",     0,      0       }
+    };
+
+tok    tok0, tok1;             /* token buffers */
+tok    *in_token = &tok0;      /* current token */
+tok    *lookahead = &tok1;     /* current lookahead */
+
+char *typevaltostr(int typ, int val)
+{
+    struct slist *sp;
+    for (sp = slist;
+        (*sp->string != '\0' && (sp->tok_type != typ || sp->tok_val != val));
+        sp++);
+    return(sp->string);
+}
+
+/******** debug / error routines ********/
+
+#ifdef DEBUG
+void printtoken()
+{
+    if (debug & DB_TOKEN)
+    {
+       printf("%d\t", c_inline);
+       switch (in_token->tok_type)
+       {
+           case COMMA: puts("COMMA");  break;
+           case DASH:  puts("DASH");   break;
+           case LPR:   puts("LPR");    break;
+           case RPR:   puts("RPR");    break;
+           case COLON: puts("COLON");  break;
+           case FSTOP: puts("FSTOP");  break;
+           case SLASH: puts("SLASH");  break;
+           case PID:   puts("PID");    break;
+           case EOLN:  puts("EOLN");   break;
+           case END:   puts("END");    break;
+           case ERROR: puts("ERROR");  break;
+           case NOT:   puts("NOT");    break;
+           case EQUAL: puts("EQUAL");  break;
+           case APM:   printf("APM %d\n", in_token->tok_val);  break;
+           case DAY:   printf("DAY %d\n", in_token->tok_val);  break;
+           case MONTH: printf("MONTH %d\n", in_token->tok_val);        break;
+           case COMND: printf("COMMAND %d\n", in_token->tok_val);      break;
+           case TOPIC: printf("TOPIC %d\n", in_token->tok_val);        break;
+           case TOKNAME: printf("TOKNAME '%s'\n", in_token->tok_str);  break;
+           case STRING: printf("STRING '%s'\n", in_token->tok_str);    break;
+           case NUMBER: printf("NUMBER %d\n", in_token->tok_val);      break;
+           default:    printf("Unknown token %d\n", in_token->tok_type); break;
+       }
+    }
+}
+#endif
+
+/********** basic lexical routines **********/
+
+void swaptok()
+{
+tok    *tmp;
+       tmp = in_token;
+       in_token = lookahead;
+       lookahead = tmp;
+}
+
+char *getstring(FILE *fptr)
+{
+    int                ch, len;
+    char       *s;
+
+    len = 0;
+    s = strbuff;
+    while (((ch = getc(fptr)) != EOF && ch != '"') && len++ < MAXTOKSTR-1)
+       *s++ = ch;
+    *s = '\0';
+    if (ch == '"')
+       instring = 0;
+    else ungetc(ch, fptr);
+    return strbuff;
+}
+                           
+
+int nexttoken(FILE *fptr)
+/*
+ * Read tokens from input file.
+ * Never returns current token with type ERROR.
+ */
+{
+    int                ch;
+    int                len, type, val;
+    char       *s;
+    struct slist       *slistp, *sfound;
+    struct periodlist  *plistp;
+
+    if (instring)
+    {
+       type = STRING;
+       in_token->tok_type = type;
+       in_token->tok_val = 0;
+       in_token->tok_str = getstring(fptr);
+    }
+    else
+    {
+       int eofcnt = 0;
+       ch = getc(fptr);        /* initialisation */
+       while (ch == EOF && !feof(fptr) && !interrupt)
+       {
+           /* presumably an alarm */
+           eofcnt++;
+           if (eofcnt > 10) break;
+           clearerr(fptr);
+           ch = getc(fptr);
+       }
+       do {
+           while (isspace(ch) && ch != '\n')
+               ch = getc(fptr);
+           if (isalpha(ch))
+           {
+               /* get the alphanumeric string */
+               s = strbuff;
+               len = 0;
+               while ((isalpha(ch) || isdigit(ch) || ch == '_')
+               && len++ < MAXTOKSTR-1)
+               {
+                   if (isalpha(ch) && isupper(ch)) ch = tolower(ch);
+                   *s++ = ch;
+                   ch = getc(fptr);
+               }
+               ungetc(ch, fptr);
+               *s = '\0';
+               in_token->tok_str = strbuff;
+
+               /* look for the string in predefined list */
+               for (slistp = slist, sfound = '\0'; (*slistp->string != '\0');
+                    slistp++)
+               {
+                   if (strncmp(slistp->string, strbuff,
+                       (slistp->minmatch > len) ? slistp->minmatch : len) == 0)
+                   {
+                       if (sfound)
+                       {
+                           /* this should not happen if minmatch has been
+                            * set properly for each string
+                            */
+                           fprintf(stderr, "'%s' ambiguous: chose %s not %s\n",
+                           strbuff, slistp->string, sfound->string);
+                       }
+                       else sfound = slistp;
+                   }
+               }
+               if (sfound)
+               {
+                   /* type == APM or DAY or MONTH */
+                   type = sfound->tok_type;
+                   in_token->tok_val = sfound->tok_val;
+               }
+               else if ((in_token->tok_val = get_mappingint(strbuff, M_ATTRIBUTE)) >= 0
+                        && query_bit(&attr_types.lab, in_token->tok_val)
+                        )
+               {
+                   type = LABNAME;
+               }
+               else
+               {
+                   /* look for the string in (system+user) defined strings */
+                   /* these strings must match exactly */
+                   for (plistp = predefined;
+                       (plistp != NULL && strcmp(plistp->id, strbuff) != 0);
+                       plistp = plistp->next);
+                   if (plistp != NULL)
+                   {
+                       type = PID;
+                       in_token->tok_period = plistp->period;
+                   }
+                   else if (mark_token(strbuff))
+                       /* this is the only way we can identify tokens */
+                       type = TOKNAME;
+                   else type = STRING;
+               }
+           }
+           else
+           {
+               if (isdigit(ch))
+               {
+                   s = strbuff;
+                   type = NUMBER;
+                   len = val = 0;
+                   while (isdigit(ch) && len++ < MAXTOKSTR-1)
+                   {
+                       *s++ = ch;
+                       val = val * 10 + ch - '0';
+                       ch = getc(fptr);
+                   }
+                   ungetc(ch, fptr);
+                   *s = '\0';
+                   in_token->tok_str = strbuff;
+               }
+               else
+               {
+                   val = 0;
+                   if (ch == '\\')
+                   {
+                       if ((ch = getc(fptr)) == '\n')
+                       {
+                           ch = getc(fptr);
+                           c_inline++;
+                       }
+                       type = SKIP;
+                   }
+                   else
+                   {
+                       s = NULL;
+                       switch (ch)
+                       {
+                       case '~': type = NOT;   break;
+                       case ',': type = COMMA; break;
+                       case '-': type = DASH;  break;
+                       case '(': type = LPR;   break;
+                       case ')': type = RPR;   break;
+                       case ':': type = COLON; break;
+                       case '.': type = FSTOP; break;
+                       case '/': type = SLASH; break;
+                       case '=': type = EQUAL; break;
+                       case '?': type = COMND; val = HELP; break;
+                       case '"':
+                           type = STRING;
+                           instring++;
+                           s = getstring(fptr);
+                           break;
+                       case '#': /* handle comments */
+                           while ((ch = getc(fptr)) != EOF && ch != '\n');
+                       case '\n': /* flow on from last case */
+                           c_inline++;
+                           s = "End of Line";
+                           type = EOLN;        break;
+                       case EOF:
+                           if (interrupt)
+                           {
+                               s = "interrupt";
+                               type = EOLN;
+                               clearerr(stdin);
+                           }
+                           else
+                           {
+                               s = "End of File";
+                               type = END;
+                           }
+                           break;
+                       default:
+                           type = ERROR;
+                           s = "ERROR";
+                           fprintf(stderr, "Line %d - Skipping bad char: '%c'\n",
+                           c_inline, ch);
+                           ch = fgetc(fptr);
+                           break;
+                       }
+                       in_token->tok_str = s;
+                   }
+               }
+               in_token->tok_val = val;
+           }
+       } while ((type == ERROR || type == SKIP) && ! interrupt);
+       in_token->tok_type = type;
+    }
+#ifdef DEBUG
+    printtoken();
+#endif
+    return type;
+} /* nexttoken */
diff --git a/book/blex.h b/book/blex.h
new file mode 100644 (file)
index 0000000..68ab96d
--- /dev/null
@@ -0,0 +1,17 @@
+#define MAXTOKSTR      40      /* length of string token */
+
+/* the following structure is used for predefined strings in period syntax */
+struct slist {
+       char    *string;        /* should be lower case */
+       char    tok_type;       /* assigned to token.tok_type */
+       int     tok_val;        /* assigned to token.tok_val */
+       int     minmatch;       /* minimum length to match */
+};
+
+/* the lexical tokens are defined as follows */
+typedef struct {
+    char *tok_str;     /* the input string producing this lexical token */
+    int        tok_type;       /* one of the types of lexical tokens */
+    int        tok_val;        /* the value for one of these types of tokens */
+    period     *tok_period;    /* only used if tok_type == PID */
+} tok;
diff --git a/book/bogin.c b/book/bogin.c
new file mode 100644 (file)
index 0000000..930116c
--- /dev/null
@@ -0,0 +1,307 @@
+/* bogin: Booking login for booking terminals
+ * Run book for those accounts with passwords
+ * For those accounts without passwords, run their shell program.
+ */
+
+#include       <stdio.h>
+#include       <pwd.h>
+#include       <sys/param.h>           /* for getgroups() */
+#include       <sys/wait.h>
+#include       <grp.h>         /* for getgrnam() */
+#if defined (SYS5) && SYS5 >= 4
+#include       <shadow.h>
+#endif
+#include       <string.h>
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <limits.h>
+#ifndef NGROUPS
+#define NGROUPS NGROUPS_MAX
+#endif
+
+#include       <sys/types.h>
+#include       <sys/stat.h>
+#include       <fcntl.h>
+#include       <crypt.h>
+
+#define MAXLOGIN       20
+
+#ifndef DEC
+    /* no getusershell on DEC */
+extern char *getusershell();
+#endif
+
+char   *login_prompt = "\nFor a shell, type 'shell' at the BOOK> prompt\nBook login: "; /* \07 not liked by aidan */
+char   *pass_prompt =  "Password: ";
+
+char   *E_toolong =    "Login too long";
+char   *E_badpass =    "Password incorrect";
+char   *E_nouser =     "No such user";
+char   *E_baduser =    "Invalid user";
+
+char   *book =         "/usr/local/bin/book";
+char   *rootshell =    "/bin/sh";
+
+char   *systype =      "SYSTYPE=bsd4.3";
+char   *homevar =      "HOME=";
+char   *shellvar =     "SHELL=";
+
+struct passwd *findlogin(char *name)
+{
+       int             ch;
+       char    *s, login[MAXLOGIN];
+       char    *password, *guess;
+       int             i, badpass;
+       struct passwd   *pwe;
+
+       if (name != NULL) {
+               if (strlen(name) >= MAXLOGIN)
+               {
+                       *login = '\0';
+                       fputs(E_toolong, stderr);
+               }
+               else strcpy(login, name);
+       }
+       else
+               *login = '\0';
+       do {
+               while (*login == '\0')
+               {
+                       fputs(login_prompt, stdout);
+                       s = login;
+                       i = 0;
+                       while ((ch = getchar()) != EOF && ch != '\n')
+                               if (++i < MAXLOGIN) *s++ = ch;
+                       if (i >= MAXLOGIN)
+                       {
+                               puts(E_toolong);
+                               *login = '\0';
+                       }
+                       else *s = '\0'; 
+               }
+
+               if ((pwe = getpwnam(login)) != NULL)
+               {
+                       if (*(pwe->pw_passwd) != '\0')
+                       {
+                               password = getpass(pass_prompt);
+                               guess = crypt(password, pwe->pw_passwd);
+                               if ((badpass = strcmp(guess, pwe->pw_passwd)))
+                               {
+#if defined (SYS5) && SYS5 >= 4
+                                       struct spwd *pwe2;
+                                       pwe2 = getspnam(login);
+                                       if (pwe2 != NULL)
+                                               badpass = strcmp(crypt(password, pwe2->sp_pwdp), pwe2->sp_pwdp);
+                                       if (badpass)
+#endif
+                                               puts(E_badpass);
+                               }
+                       }
+                       else badpass = 0;
+               }
+               else puts(E_nouser);
+               *login = '\0';
+       } while (pwe == NULL || badpass);
+       setgid(pwe->pw_gid);
+       initgroups(pwe->pw_name, pwe->pw_gid);
+       setuid(pwe->pw_uid);
+       return pwe;
+}
+
+char *isshell(char *usershell)
+{
+       char    *shell;
+       /* no getusershell on DEC */
+#ifndef DEC
+       while ((shell = getusershell()) != NULL && strcmp(shell, usershell));
+       return shell;
+#else
+       return(usershell);
+    
+#endif    
+}
+
+int ingroup(int gid)
+/* return true if the current user is in the group wheel (group 0) */
+{
+       int     n;
+#ifdef ultrix
+       int
+#else
+               gid_t
+#endif
+               *ip, groups[NGROUPS];
+
+       n = getgroups(NGROUPS, groups);
+       for (ip = groups; (n>0 && *ip++ != gid); n--);
+       return (n>0);
+}
+
+void usage(void)
+{
+       fprintf( stderr, "Usage: bogin [ -d terminal_dev ] [-c stty-command] [username]\n" );
+       exit( 2 );
+}
+
+int new_sid( void )
+{
+       pid_t pid;
+
+       /* the case where we don't currently have a ctty */
+       if( setsid() != -1 )
+               return( 1 );
+
+       /*
+        *  first, fork in the case where we already have a pgrp/ctty
+        */
+       pid = fork();
+       if( pid == 0 ) /* child */
+       {
+               /* Lose our controlling terminal */
+               if( setsid() < 0 )
+               {
+                       perror( "bogin: setsid() failed: " );
+                       return( 0 );
+               }
+               else
+               {
+                       return( 1 );
+               }
+       }
+       else  /* parent */
+       {
+               wait(0);
+               exit(0);
+       }
+}
+
+void grab_terminal( char *terminal_dev )
+{
+       int   fd, maxfds=64;
+       char  errbuff[100];
+
+       /* get a new controlling terminal */
+       new_sid();
+
+       /* Lose any open file descriptors */
+       for( fd=0; fd<maxfds; fd++ )
+               close( fd );
+
+       /* point 0,1,2 at terminal_dev */
+       if( open( terminal_dev, O_RDWR) == 0 ) 
+       {
+               /* open() has returned stdin (0) */
+               dup(0);    /* stdout */
+               dup(0);    /* stderr */
+       }
+       else
+       {
+               sprintf( errbuff, "bogin: cannot open %s: ", terminal_dev );
+               perror(errbuff);
+               exit(1);
+       }
+}
+
+int main(int argc, char **argv)
+{
+       struct passwd   *pwe;
+       char            *arg0, *command, *name;
+       char            *newargv[2], *newenvp[4];
+       int                     i;
+       struct group    * supr_grp;
+       char                * terminal_dev = NULL;
+       char            * init_cmd = NULL;
+    
+       newenvp[0] = arg0 = NULL;
+
+       i = 1;
+       if( argv[i] && strcmp(argv[i], "-d")==0 )
+       {
+               i++;
+               if( argv[i] )
+                       terminal_dev = argv[i];
+               else
+                       usage();
+               i++;
+       }
+
+       if (argv[i] && strcmp(argv[i], "-c")==0)
+       {
+               i++;
+               if (argv[i])
+                       init_cmd = argv[i];
+               else
+                       usage();
+               i++;
+       }
+
+/* aidan
+   if (1 <= --argc && argc <= 2)
+   name = argv[argc];
+*/
+       /*   else name = ""; */
+       /* changed by adam 05/05/94 */
+
+       if (argv[i] && argv[i][0] == '-')
+               name = NULL;
+       else
+               name = argv[i] ;
+
+       if (terminal_dev)
+               grab_terminal( terminal_dev );
+
+       if (init_cmd) system(init_cmd);
+       pwe = findlogin(name);
+
+       newenvp[0] = systype;
+       if ((newenvp[1] = (char *) malloc(strlen(homevar)+strlen(pwe->pw_dir)+1)) != NULL)
+       {
+               strcat(strcpy(newenvp[1], homevar), pwe->pw_dir);
+               if ((newenvp[2] = (char *) malloc(strlen(shellvar)+strlen(pwe->pw_shell)+1)) != NULL)
+               {
+                       strcat(strcpy(newenvp[2], shellvar), pwe->pw_shell);
+                       newenvp[3] = NULL;
+               }
+       }
+
+       if (pwe->pw_uid == 0)
+               command = rootshell;            /* root gets its shell */
+       else if (*(pwe->pw_passwd) == '\0')
+               if (isshell(pwe->pw_shell))
+               {
+                       puts(E_baduser);
+                       exit(1);
+               }
+               else command = pwe->pw_shell;   /* newacc, etc */
+       else if (! isshell(pwe->pw_shell))
+       {
+               puts(E_baduser);
+               exit(1);
+       }
+       else if (  ingroup(0) || (((supr_grp = getgrnam("supervisor")) != 
+                                  NULL) && ingroup(supr_grp->gr_gid)) )
+       {
+               /* wheels and supervisors get their shell.
+                * set up SYSTYPE, SHELL, and HOME in their environment
+                */
+               arg0 = "-";
+               command = pwe->pw_shell;
+       }
+       else command = book;            /* ordinary user gets book */
+
+       if (arg0 == NULL) arg0 = command;
+       newargv[0] = arg0;
+       newargv[1] = NULL;
+#ifdef DEBUG
+       printf("execve(%s, (%s, NULL), (", command, arg0);
+       for (i=0; (newenvp[i] != NULL); i++) printf("%s%s", (i==0 ? "" : ", "), newenvp[i]);
+       printf("%sNULL))\n", (i==0 ? "" : ", "));
+#else
+       execve(command, newargv, newenvp);
+#endif
+       perror(command);
+       sleep(2);               /* give error message time to be seen */
+       exit(0);
+}
+
diff --git a/book/boo.c b/book/boo.c
new file mode 100644 (file)
index 0000000..9a92ba6
--- /dev/null
@@ -0,0 +1,334 @@
+/*
+ * The book user interface.
+ */
+
+#include       <sys/types.h>
+#include       <sys/param.h>           /* for NGROUPS used by getgroups */
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <grp.h>         /* for getgrnam() */
+#include       <stdio.h>
+#include       <getopt.h>
+#include       <ctype.h>
+#include       <signal.h>
+#include       <pwd.h>
+#include       <rpc/rpc.h>
+#include       "../interfaces/bookinterface.h"
+#define BOOK_INTERFACE
+#include       "boo.h"
+#ifdef sun
+#include       <unistd.h>
+#include       <limits.h>
+#define NGROUPS NGROUPS_MAX
+#endif
+
+#define TIMEOUT                120 
+#define WARNTIMEOUT    30
+#define        NWARNINGS       1
+
+/********* externals **********/
+/* system calls and subroutines */
+#ifdef ultrix
+extern int     getgroups(int len, int *array);
+#endif
+extern int optind;
+
+
+
+/********** global variables **************/
+
+char   *infile;                /* the name of the file currently being read */
+char   *cfile = "/.periods";   /* appended to home directory */
+#ifdef DEBUG
+    char       *helpfile =     "help";
+    char       *sys_cfile =    "periods";
+    char       *motd_file =    "motd";
+#else
+#ifdef apollo
+    char       *helpfile =     "/usr/local/book/help";
+    char       *sys_cfile =    "/usr/local/book/periods";
+    char       *motd_file =    "/usr/local/book/motd";
+#else
+    char       *helpfile =     "/var/book/help";
+    char       *sys_cfile =    "/var/book/periods";
+    char       *motd_file =    "/var/book/motd";
+#endif
+#endif
+
+#ifdef NOTYET
+UDB    udb_handle;             /* used to make udb requests */
+#endif
+int    interrupt = 0;
+int    ntimeoutwarnings = -1;
+int    privilaged = 0;
+int    user_is_class = 0;
+char   *username;
+bookuid_t      realuser = 0;
+bookuid_t      user = 0;
+int    parse_err = 0;          /* set of [ FATAL, RECOVER ]  - NOT CURRENTLY USED */
+int    c_inline = 1;           /* the current unprocessed character */
+
+/********** debug / error routines **********/
+
+#ifdef DEBUG
+int    debug;                  /* set to DB_TOKEN | DB_PERIOD */
+#endif
+
+int readcommands(char *fname)
+/* The main loop to read book commands.
+ * Used to read setup files and interactive commands (hence fname).
+ */
+{
+       FILE *fptr;
+       char *prompt = "BOOK > ";
+       command_type    *cp;
+       int quit = 0;
+
+       if (fname != NULL)
+       {
+               if ((fptr = fopen(fname, "r")) == NULL) return 0;
+       }
+       else fptr = stdin;
+
+       infile = fname;
+       c_inline = 1;
+       if (fptr == stdin)
+       {
+               /* if stdin, prompt.
+                * but if it isn't a tty, a dd anewline and don't timeout
+                */
+               printf(prompt);         /* only prompt interactive input */
+               if (isatty(0))
+               {
+                       alarm(TIMEOUT);
+                       ntimeoutwarnings = NWARNINGS;
+               }
+               else printf("\n");
+               fflush(stdout);
+       }
+       while (! quit && nexttoken(fptr) != END)
+       {
+               if (fptr == stdin) alarm(0);    /* cancel the alarm request */
+               if (! interrupt)
+               {
+                       parse_err = 0;
+                       cp = getcommand(fptr);
+                       if (cp != NULLC)
+                       {
+                               if (cp->period == empty_abs)
+                                       puts("\tPeriod spec maps to an empty or invalid period");
+                               else
+                               {
+                                       switch (cp->command)
+                                       {
+                                       case HELP:      b_help(cp); break;
+                                       case AVAIL:
+                                       case BOOK:      b_showfree(cp); break;
+                                       case CHANGE:
+                                       case UNBOOK:
+                                       case LIST:      b_showbook(cp); break;
+                                       case RECLAIM:
+                                       case REFUND:
+                                       case PAST:      b_showpastbook(cp); break;
+                                       case LABS:      b_labs(cp); break;
+                                       case TIMES: b_times(cp); break;
+                                       case TOKEN:     b_token(cp); break;
+                                       case UNDEF:     b_undefine(cp); break;
+                                       case DEFINE: b_define(cp); break;
+                                       case ECHO:      b_echo(cp); break;
+                                       case SHELL: b_shell(cp); break;
+                                       case QUIT:      quit++; break;
+                                       default:        printcommand(cp); break;
+                                       }
+                                       if (cp->command != DEFINE) rfree(cp->period);
+                               }
+                       }
+               }
+               if (fptr == stdin && ! quit)
+               {
+                       printf(prompt); /* only prompt interactive input */
+                       if (isatty(0))
+                       {
+                               alarm(TIMEOUT);
+                               ntimeoutwarnings = NWARNINGS;
+                       }
+                       else printf("\n");
+                       fflush(stdout);
+               }
+               if (interrupt) interrupt = 0;
+       }
+       if (fptr != stdin) fclose(fptr);
+       return 1;
+}
+
+void timeout()
+{
+       if (ntimeoutwarnings < 0)
+               return; /* someone else is monitoring timeouts */
+       if (ntimeoutwarnings <= 0)
+       {
+               puts("\07TIME OUT\07");
+               exit(0);
+       }
+       else
+       {
+               printf("\07Timeout in %d seconds\07\n", ntimeoutwarnings-- * WARNTIMEOUT);
+               alarm(WARNTIMEOUT);
+       }
+}
+
+void setinterrupt()
+{
+#ifdef USE_SIGACTION
+       signal(SIGINT,setinterrupt);
+#endif
+    
+       puts("Interrupt\07");
+       interrupt++;
+}
+
+int ingroup(int gid)
+/* return true if the current user is in the group wheel (group 0) */
+{
+       int     n;
+#ifdef ultrix
+       int *ip, groups[NGROUPS];
+#else
+       gid_t *ip, groups[NGROUPS];
+#endif
+    
+       n = getgroups(NGROUPS, groups);
+       for (ip = groups; (n>0 && *ip++ != gid); n--);
+       return (n>0);
+}
+
+int main(int argc, char **argv)
+{
+       FILE    *f;
+       struct passwd   *pwe;
+
+       char    ch, *command;
+       int             err = 0;
+       char    *home_cfile;
+#ifdef USE_SIGACTION
+       static struct sigaction sigalrmact;
+#else
+       static struct sigvec sigintp[] = { setinterrupt, NULL, SV_INTERRUPT };
+       static struct sigvec sigalrmp[] = { timeout, NULL, 0 };
+#endif
+       char    *options = "hvD:";
+#ifdef DEBUG
+       char    *usage = "Usage: %s [-v] [-D debug_level] [uid | login_name | class_name]\n";
+#else
+       char    *usage = "Usage: %s [-v] [uid | login_name | class_name]\n";
+#endif
+       struct group    * supr_grp;
+#ifdef  USE_SIGACTION
+       sigalrmact.sa_handler = timeout;
+       sigalrmact.sa_flags = 0/*SA_RESTART*/;
+#endif    
+       command = *argv;
+       while ((ch = getopt(argc, argv, options)) != EOF)
+               switch (ch)
+               {
+               case 'h': fprintf(stderr, usage, command); exit(0);
+               case 'v': puts(book_version); exit(0);
+               case 'D':
+#ifdef DEBUG
+                       if (sscanf(optarg, "%d", &debug) != 1) err++;
+#else
+                       fputs("Debug not compiled in this version\n", stderr);
+#endif
+                       break;
+               default: err++; break;
+               }
+
+       /** sort out our identity **/
+
+       realuser = user = getuid();             /* set up user */
+       username = NULL;
+       privilaged = (realuser == 0
+                     || ingroup(0) ||  (((supr_grp = getgrnam("supervisor")) != 
+                                         NULL) && ingroup(supr_grp->gr_gid)));
+       if (optind < argc)
+       {
+               if (privilaged)
+                       if (! isdigit(*argv[optind]))
+                       {
+                               username = argv[optind];
+                               user = find_userid(username);
+                               if (user < 0)
+                                       fprintf(stderr, "No such user or class: '%s'\n", username);
+                       }
+                       else user = atoi(argv[optind]);
+               else
+               {
+                       fprintf(stderr, "%s: only group wheel or supervisor may change user\n", command);
+                       exit(1);
+               }
+       }
+       /* sort out our name if we haven't got it yet */
+       if (username == NULL)
+               username = find_username(user);
+       if (username == NULL)
+       {
+               fprintf(stderr, "No such uid or classid: '%d'\n", (int)user);
+               username = "Who are you?";
+               err++;
+       }
+       if (user >= get_class_min())
+               user_is_class = 1;
+
+       if (err)
+       {
+               fprintf(stderr, usage, command);
+               exit(2);
+       }
+
+       currtime();
+       init_interface();
+       initperiods();
+       initbookings();
+#ifdef USE_SIGACTION
+       signal(SIGINT,setinterrupt);
+/*    sigset(SIGALRM,timeout); */
+       sigaction(SIGALRM, &sigalrmact, NULL);
+#else
+       sigvec(SIGINT, sigintp, NULL);
+       sigvec(SIGALRM, sigalrmp, NULL); 
+#endif
+       /***** print message of the day file */
+       if ((f = fopen(motd_file, "r")) != NULL)
+       {
+               while ((ch = fgetc(f)) != EOF) putchar(ch);
+               fclose(f);
+       }
+
+       /***** read in the period specification files *****/
+       if (! readcommands(sys_cfile))
+       {
+               /* the system periods file has to exist */
+               perror(sys_cfile);
+               exit(1);
+       }
+       if (! user_is_class)
+       {
+               pwe = getpwuid(user);
+               if ((home_cfile = (char *) malloc(strlen(pwe->pw_dir)+strlen(cfile)+1)) != NULL)
+               {
+                       strcat(strcpy(home_cfile, pwe->pw_dir), cfile);
+                       readcommands(home_cfile); /* ignore failure */
+               }
+       }
+
+       if (!user_is_class && is_defaulter(user))
+       {
+               printf("WARNING: you are currently deemed to be a defaulter.\n");
+               printf("   see \"help defaulter\" for more details.\n");
+       }
+       /***** the interactive bit *****/
+       readcommands((char *) NULL);
+       puts("");
+       exit(0);
+} /* main */
+
diff --git a/book/boo.h b/book/boo.h
new file mode 100644 (file)
index 0000000..a014672
--- /dev/null
@@ -0,0 +1,209 @@
+#define        MALLOC(X)       (X *) malloc(sizeof(X))
+
+#define        NULLP   (period *) NULL
+#define NULLPL (plist *) NULL
+#define NULLC  (command_type *) NULL
+
+/* fundemental time constants (all time is measured in seconds) */
+#define SECINWEEK      604800L
+#define SECINDAY        86400L
+#define SECINHOUR        3600L
+#define SECINMIN           60L
+
+#define        SECINPERIOD       1800L
+#define MINPERIOD        1800L         /* half hour minimum booking period */
+
+/* error states */
+#define FATAL  1
+#define        RECOVER 2
+
+/* debug states */
+#define DB_TOKEN       1
+#define DB_PERIOD      2
+#define        DB_CALL         4
+
+/* define day ordinals */
+#define        MON     0
+#define        TUE     1
+#define        WED     2
+#define        THU     3
+#define        FRI     4
+#define        SAT     5
+#define        SUN     6
+#define        TODAY   7
+#define        TOMOR   8
+
+/* defines the token type - assigned to token.tok_type */
+/* following token types used in period definitions */
+/* there should not be a token type == 0 */
+#define COMMA  1
+#define        DASH    2
+#define LPR    3
+#define RPR    4
+#define COLON  5
+#define FSTOP  6
+#define SLASH  7
+#define APM    8
+#define PID    9
+#define        NUMBER  10
+#define        HOUR    11
+#define DAY    12
+#define MONTH  13
+#define NOT    14
+#define        EQUAL   15
+
+/* following token types used for command parsing */
+#define        COMND   50
+#define        TOPIC   51
+#define        STRING  52
+#define TOKNAME        53
+#define        OPTION  54
+#define        LABNAME 55      /* tok_val is lab number */
+
+/* following token types used for general lexical parsing */
+#define        SKIP    97
+#define EOLN   98
+#define END    99
+#define ERROR  999
+
+/* the types of commands */
+#define        HELP    1
+#define        BOOK    2
+#define        UNBOOK  3
+#define        AVAIL   4
+#define        LIST    5
+#define        TOKEN   6
+#define        DEFINE  7
+#define        UNDEF   8
+#define ECHO   9
+#define        REFUND  10
+#define        PAST    11
+#define CHANGE 12
+#define        RECLAIM 13
+#define        SHELL   14
+#define        TIMES   15
+#define        LABS    16
+#define        QUIT    99
+
+/* the types of topics */
+#define        PERIOD  1
+#define        PREDEF  2
+#define        SYNTAX  3
+#define        CFILE   4
+#define        DEFAULTER 5
+#define        SUMMARY 7
+/* also uses COMND(50) and TOKEN(6) */
+
+/* the types of options */
+#define        O_ALL_TOKENS    1
+#define        O_HELP_TYPE     2
+#define        O_HELP_VALUE    3
+#define        O_CONSEC        4
+#define        O_MACHINES      5
+#define        O_CONFIRM       6
+#define        O_COUNTS        7
+
+/* values assigned to period.repetition - also use HOUR & DAY defined above */
+#define ABSOLUTE       0
+
+typedef struct periodrec period;
+struct periodrec {
+       int     type;           /* ABSOLUTE, HOUR, DAY */
+       time_t  start, end;     /* seconds from start of type of period */
+       period  *next;
+};
+
+typedef struct periodlist {
+    char       *id;            /* the name of this period list */
+    period     *period;        /* the period list */
+    struct periodlist  *next;
+} plist;
+
+typedef struct str_list strlist;       /* stores a list of arbitrary strings */
+struct str_list {
+    char       *str_val;
+    strlist    *next;
+};
+
+typedef struct option_list {
+    int        option;                 /* the option type being passed (see O_.. above) */
+    int        value;                  /* the value of the option */
+    struct option_list *next;
+} optlist;
+
+typedef struct {
+    int                command;        /* one of HELP..QUIT */
+    int                ntokpassed;     /* number of tokens explicitly passed to command */
+    description labs;          /* labs (an other attributes) passed on command line */
+    optlist    *options;       /* the options passed to the command */
+    period     *period;        /* the period applying to the command */
+    strlist    *strlist;       /* list of strings passed to the command */
+} command_type;
+
+#ifndef BOOK_INTERFACE
+typedef int bookuid_t;
+#endif
+
+/* from bcomm.c */
+void b_echo(command_type *comm);
+void b_shell(command_type *comm);
+void b_help(command_type *comm);
+
+/* book routines and variables from other modules */
+
+
+extern plist   *predefined;
+extern int     c_inline;
+extern int     mark_token(char *tokname);      
+
+extern bookuid_t       realuser;
+extern bookuid_t       user;
+extern char            *helpfile;
+extern char            *username;
+extern char    *book_version;                          /* bversion.c */
+extern char    *typevaltostr(int typ, int val);        /* blex.c */
+extern command_type    *getcommand(FILE *fptr);        /* bparse.c */
+extern int             command_no;             /* used to mark tokens and bookings */
+extern int             interrupt;
+extern int             privilaged;
+extern int             user_is_class;
+extern int     nexttoken(FILE *fptr);                  /* blex.c */
+extern time_t  construct(int year, int month, int day, int hour, int minute);
+extern time_t  currtime();                             /* bperiod.c */
+extern time_t  time_2_dp(time_t t, int *doy, int *pod);        /* bparse.c */
+extern period          empty_abs[];            /* bparse.c */
+extern period  *absperiod(period *p);                  /* bparse.c */
+extern period  *copyperiods(period *p);                /* bparse.c */
+extern period  *exclude_past(period *p);               /* bparse.c */
+extern period  *fintersect(period *p1, period *p2);    /* bparse.c */
+extern period  *intersect(period *p1, period *p2);     /* bparse.c */
+extern period  *mergeperiods(period *p1, period *p2);  /* bparse.c */
+extern period  *newperiod(int type, time_t start, time_t end, period *next); /* bparse.c */
+extern period  *notperiod(period *p);                  /* bparse.c */
+extern period  *periodunion(period *p1, period *p2);   /* bparse.c */
+extern period  empty_abs[];                            /* bperiod.c */
+extern struct tm       tnow;
+extern time_t          enow;
+extern time_t          now;
+extern void    b_avail(command_type *comm);            /* bcomm.c */
+extern void    b_book(command_type *comm);             /* bcomm.c */
+extern void    b_define(command_type *comm);           /* bperiod.c */
+extern void    b_help(command_type *comm);             /* bcomm.c */
+extern void    b_showbook(command_type *comm);         /* bcomm.c */
+extern void    b_showfree(command_type *comm);         /* bcomm.c */
+extern void    b_showpastbook(command_type *comm);     /* bcomm.c */
+extern void    b_token(command_type *comm);            /* bcomm.c */
+extern void    b_labs(command_type *comm);             /* bcomm.c */
+extern void    b_times(command_type *comm);            /* bcomm.c */
+extern void    b_undefine(command_type *comm);         /* bperiod.c */
+extern void    initbookings();                         /* bcomm.c */
+extern void    initperiods();                          /* bperiod.c */
+extern void    printcommand(command_type *cp);         /* bcomm.c */
+extern void    printperiod(period *pp, char *sep);     /* bperiod.c */
+extern void    printtokens(int n, int inc_expired);    /* bcomm.c */
+extern void    rfree(period *pp);                      /* bparse.c */
+extern int     option_value(optlist *head, int option); /* bparse.c */
+extern void    printpredefs();                 /* bperiod.c */
+extern void swaptok(); /* blex.c */
+extern period *book_mktime(int hour,int minute);
+extern period *range(period *p1, period *p2);
diff --git a/book/bparse.c b/book/bparse.c
new file mode 100644 (file)
index 0000000..0ba1da6
--- /dev/null
@@ -0,0 +1,1175 @@
+
+/*
+ * Book interface module:
+ *     Basic Time related routines
+ *     Period manipulating routines
+ *     Parsing routines (recursive descent)
+ */
+
+#include       <stdio.h>
+#include       <time.h>
+#include       <stdlib.h>
+#include       <rpc/rpc.h>
+#include       "../interfaces/bookinterface.h"
+#define BOOK_INTERFACE
+#include       "boo.h"
+
+/* from boo.c - main */
+extern struct tm       tnow;   /* current time - tm format */
+extern time_t          now;    /* current time - seconds */
+extern int             parse_err;      /* set of [ FATAL | RECOVER ] */
+extern int             c_inline;       /* input line number */
+extern char            *infile;        /* input file name */
+extern plist           *predefined;
+extern period          *valid_abs, *valid_hour, *valid_day;
+extern period          empty_abs[];
+
+#ifdef DEBUG
+extern int             debug;
+#endif
+
+#include       "blex.h"
+extern tok     *in_token, *lookahead;                  /* blex.c */
+extern void    printperiod(period *pp, char *sep);     /* bperiod.c */
+extern void    mark_all_tokens(int alltokens);         /* bcomm.c */
+
+/******* Globals ********/
+
+int    level = 0;              /* parenthetical nest level */
+int    command_no = 0;         /* current command no. - used to mark tokens */
+static int daysinmonth[12] =
+{
+       31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+
+/********* debug / error routines ********/
+
+void printerror(int err, char *msg)
+{
+       if (infile != NULL)
+               fprintf(stderr, "%s -  Line %d: ",
+                       infile, (in_token->tok_type == EOLN ? c_inline-1 : c_inline));
+       fprintf(stderr, "%s '%s'\n", msg, in_token->tok_str?in_token->tok_str:"???"); /* FIXME zain ! this can violate segnments */
+       
+       parse_err |= err;
+}
+
+/********** basic time related routines **********/
+
+int leapyear(int y)
+{
+       return (((y % 4) == 0 && (y % 100) != 0) || (y % 400 == 0)) ? 1 : 0;
+}
+
+int checkdate(int year, int month, int day)
+/* check that day month year are legal */
+{
+       int             dim, ret;
+
+       if ((ret = (year >= 1970 || year <= 2038)))
+       {
+               dim = daysinmonth[month] + (month == 1 ? leapyear(year) : 0);
+               ret = (day <= dim);
+               if (! ret)
+                       fprintf(stderr, "Month day out of range. (> %d)\n", dim);
+       }
+       else fprintf(stderr,"Year out of range: %d\n", year);
+       return ret;
+}
+
+int checktime(int hour, int minute)
+/* check that hour minute are legal */
+{
+       int     ret;
+       if (! (ret = ((hour <= 23 && minute <= 59) || (hour == 24 && minute == 0))))
+               fprintf(stderr,"Illegal time: %02d:%02d\n", hour, minute);
+       return ret;
+}
+
+time_t construct(int year, int month, int day, int hour, int minute)
+/* On UNIX, by convention, time = seconds since 00:00GMT Thursday, 1 Jan 1970 */
+{
+       register int            i, days;
+
+       days = 0;
+       for(i = 1970; i < year; i++)
+               days += 365 + leapyear(i);
+       for(i = 0; i < month; i++)
+               days += daysinmonth[i] + (i == 1 ? leapyear(year) : 0);
+       days += day;            /* days since 1 Jan 1970 */
+       return (time_t) days * SECINDAY + hour * SECINHOUR + minute * SECINMIN;
+}
+
+time_t weekstart(time_t t)
+/* Get the start of the week containing time t.
+ * The week starts Monday, and we depend on 1.1.70 being a Thursday
+ */
+{
+       return  t - (t + (THU-MON)*SECINDAY) % SECINWEEK;
+} /* weekstart */
+
+time_t time_2_dp(time_t t, int *dayofyear, int *periodofday)
+/* return start of period that contains (gm)time t,
+ * also return (thro param) day of year and period of that day of this period.
+ * Note: Period must be in current year.
+ */
+{
+       struct tm       *tp;
+       time_t  ret, secofday;
+       tp = gmtime(&t);
+       if (tp->tm_year == tnow.tm_year)
+       {
+               secofday = tp->tm_hour * SECINHOUR + tp->tm_min * SECINMIN + tp->tm_sec;
+               *periodofday = secofday / SECINPERIOD;
+               *dayofyear = tp->tm_yday;
+               ret = t - (secofday - *periodofday * SECINPERIOD);
+       }
+       else ret = 0;
+       return ret;
+}
+
+/************ basic period operations **************/
+
+void rfree(period *pp)
+/* recursively free period list pp */
+{
+       period  *tp;
+       while (pp != NULLP)
+       {
+               tp = pp;
+               pp = pp->next;
+               free(tp);
+       }
+}
+
+period *newperiod(int type, time_t start, time_t end, period *next)
+/* malloc and return a new period with values assigned */
+{
+       period *pp;
+       if ((pp = MALLOC(period)) != NULL)
+       {
+               pp->type = type;
+               pp->start = start;
+               pp->end = end;
+               pp->next = next;
+       }
+       return pp;
+} /* newperiod */
+
+period *copyperiods(period *periods)
+/* return a copy of the period list passed in periods */
+{
+       period *pp, *start, *end, *newp;
+
+       start = NULLP;
+       for (pp = periods; (pp != NULLP); pp = pp->next)
+       {
+               if ((newp = newperiod(pp->type, pp->start, pp->end, NULL)) != NULL)
+               {
+                       if (start == NULLP)
+                               start = newp;
+                       else end->next = newp;
+                       end = newp;
+               }
+       }
+       return start;
+}
+
+period *mergeperiods(period *p1, period *p2)
+/*
+ * merge both ordered period lists p1 and p2 into one ordered list
+ * lists are ordered in increasing time order
+ * destroys p1 and p2 in the process.
+ */
+{
+       period  *pp, *lo, *mid, *hi;
+
+       if (p1 == NULLP)
+               pp = p2;
+       else if (p2 == NULLP)
+               pp = p1;
+       else
+       {
+
+               if (p1->start < p2->start)
+               {
+                       pp = lo = p1;
+                       hi = p2;
+               }
+               else
+               {
+                       pp = lo = p2;
+                       hi = p1;
+               }
+               mid = lo->next;
+
+               /* invariant lo(s,e) <= mid(s,e) && lo(s) <= hi(s) */
+               while (mid != NULLP && hi != NULLP)
+               {
+                       if (mid->start <= hi->start)
+                               lo = mid;
+                       else
+                       {
+                               if (hi->start-1 <= lo->end)
+                               {
+                                       if (lo->end < hi->end)
+                                       {
+                                               lo->end = hi->end;
+                                               lo->next = hi->next;
+                                       }
+                                       else mid = hi->next;
+                                       free(hi);
+                               }
+                               else
+                               {
+                                       lo->next = hi;
+                                       lo = hi;
+                               }
+                               hi = mid;
+                       }
+                       mid = lo->next;
+               }
+               if (mid == NULLP && hi != NULLP) {
+                       if (hi->start-1 <= lo->end)
+                       {
+                               if (lo->end < hi->end)
+                                       lo->end = hi->end;
+                               lo->next = hi->next;
+                               free(hi);
+                       }
+                       else lo->next = hi;
+               }
+       }
+
+       return pp;
+} /* mergeperiods */
+
+period *range(period *p1, period *p2)
+/* A period which is the range of two other periods is the period starting
+ * when the first period starts, and ending either before the second period
+ * starts, or when the second period ends (depending on the type of period).
+ * We must ensure that both periods are singular, and of the same type.
+ * destroys p1 and p2.
+ */
+{
+       period  *pp;
+
+       if (p1 == NULLP || p2 == NULLP)
+               pp = NULLP;
+       else if (p1->next != NULLP || p2->next != NULLP)
+       {
+               fputs("Range must be from only one time to only one other\n", stderr);
+               pp = NULLP;
+       }
+       else if (p1->type != p2->type)
+       {
+               fputs("Range must be between equivalent types\n", stderr);
+               pp = NULLP;
+       }
+       else
+       {
+               pp = p1;
+#if 0
+               if (pp->type == HOUR)
+                       pp->end = p2->start-1;  /* one second before start of p2 */
+               else
+#endif
+                       pp->end = p2->end;
+               if (pp->start > pp->end)
+                       switch (pp->type)
+                       {
+                       case HOUR: pp->end += SECINDAY; break;
+                       case DAY: pp->end += SECINWEEK; break;
+                       case ABSOLUTE:
+                               fputs("End of period is before Start of period\n", stderr);
+                               pp = NULLP; break;
+                       }
+       }
+       if (p1 != NULLP && pp != p1) rfree(p1);
+       if (p2 != NULLP) rfree(p2);
+
+#ifdef DEBUG
+       if (debug & DB_CALL) puts("range(p1,p2)\n");
+       if (debug & DB_PERIOD) printperiod(pp, "");
+#endif
+       return pp;
+} /* range */
+
+period *periodunion(period *p1, period *p2)
+/* Periods must be of the same type */
+{
+       period  *pp;
+
+       if (p1 != NULLP && p2 != NULLP)
+       {
+               if (p1->type != p2->type)
+               {
+                       printerror(FATAL, "Union must be between equivalent types");
+                       fputs("Union must be between equivalent types\n", stderr);
+                       rfree(p1); rfree(p2);
+                       p1 = p2 = NULLP;
+               }
+       }
+
+       pp = mergeperiods(p1, p2);
+       /*
+         if (p1 != NULLP)
+         {
+         for (pp = p1; (p1->next != NULLP); p1 = p1->next);
+         p1->next = p2;
+         }
+         else pp = p2;
+       */
+#ifdef DEBUG
+       if (debug & DB_CALL) printf("periodunion(p1,p2)\n");
+       if (debug & DB_PERIOD) printperiod(pp, "");
+#endif
+       return pp;
+} /* periodunion */
+
+period *common(period *p1, period *p2)
+/*
+ * Precondition: p1->type == p2->type;
+ * return the common period (intersection) of p1 with p2.
+ */
+{
+       period  *pp = NULLP;
+       if (p1->start < p2->start)
+       {
+               if (p2->start < p1->end)
+                       pp = newperiod(p1->type, p2->start,
+                                      (p1->end < p2->end) ?  p1->end : p2->end, NULLP);
+       }
+       else /* p2->start <= p1->start */
+               if (p1->start < p2->end)
+                       pp = newperiod(p1->type, p1->start,
+                                      (p1->end < p2->end) ?  p1->end : p2->end, NULLP);
+       return pp;
+} /* common */
+
+period *rcommon(period *rp, period *p)
+/*
+ * Precondition: rp is a relative period to be applied to the period p
+ */
+{
+       int             rtype;
+       time_t  tstart, tend, periodicity;
+       period  *pp;
+
+       tstart = rp->start;     /* save values of rp */
+       tend = rp->end;
+       rtype = rp->type;
+
+       /* convert rp into period of type: p->type */
+       if (rtype == HOUR)
+       {
+               rp->start = (p->start / SECINDAY) * SECINDAY;
+               periodicity = SECINDAY;
+       }
+       else if (rtype == DAY)
+       {
+               rp->start = weekstart(p->start);
+               periodicity = SECINWEEK;
+       }
+       else
+       {
+               fputs("Snark: bad relative period type\n", stderr);
+               return NULLP;
+       }
+
+       rp->end = rp->start + tend;
+       rp->start += tstart;
+       rp->type = p->type;
+
+       pp = NULLP;
+       while (rp->start < p->end)
+       {
+               pp = mergeperiods(pp, common(rp, p));
+               rp->start += periodicity;
+               rp->end += periodicity;
+       }
+
+       rp->type = rtype;       /* restore rp */
+       rp->start = tstart;
+       rp->end = tend;
+
+       return pp;
+} /* rcommon */
+
+period *intersection(period *p1, period *p2)
+/*
+ * Precondition: lists p1 and p2 are not empty.
+ * Only intersect the first element of p1 with the first of p2.
+ * This routine deals with intersecting elements of different types
+ * by converting the lesser type to the greater one (HOUR << DAY << ABSOLUTE)
+ * Try to maintain list (time) ordering (add new elements to end - not front
+ * which would be easier).
+ * This is done so that times remain in increasing order.
+ * (But will they, and is this important?)
+ */
+{
+       period  *pp = NULLP;
+
+       if (p1->type == ABSOLUTE && p2->type == ABSOLUTE)
+               pp = common(p1,p2);
+       else if (p1->type != ABSOLUTE && p2->type != ABSOLUTE)
+       {
+               if (p1->type == p2->type)
+                       pp = common(p1,p2);
+               else
+               {
+                       /* Make p1->type <= p2->type (ie: smaller periodicity).
+                        * Currently, with only two relative period types, this implies:
+                        * p1->type == HOUR && p2->type == DAY
+                        */
+                       if (p1->type > p2->type) { pp = p1; p1 = p2; p2 = pp; }
+
+                       if (p1->type == HOUR && p2->type == DAY)
+                               pp = rcommon(p1,p2);
+                       else fputs("Snark!: Relative period types >= 3\n", stderr);
+               }
+       }
+       else /* (p1->type != ABSOLUTE exor p2->type != ABSOLUTE) */
+       {
+               if (p1->type == ABSOLUTE)
+                       pp = rcommon(p2, p1);
+               else pp = rcommon(p1, p2);
+       }
+       return pp;
+} /* intersection */
+
+period *intersect(period *p1, period *p2)
+/*
+ * intersects list of periods p1 and p2 and returns resultant period list
+ * does not change lists p1 or p2
+ */
+{
+       period  *pp, *pp1, *pp2, *newp, *start, *end;
+
+       pp = NULLP;
+       pp1 = p1;
+       while (pp1 != NULLP)
+       {
+               pp2 = p2;
+               start = NULLP;
+               while (pp2 != NULLP)
+               {
+                       newp = intersection(pp1, pp2);
+                       /* add the new period list to the end of the current list */
+                       if (start == NULLP)
+                               start = end = newp;
+                       else end->next = newp;
+                       if (end != NULLP) while (end->next != NULLP) end = end->next;
+                       pp2 = pp2->next;
+               }
+               pp = mergeperiods(pp, start);
+               pp1 = pp1->next;
+       }
+#ifdef DEBUG
+       if (debug & DB_CALL) printf("intersect(p1,p2)\n");
+       if (debug & DB_PERIOD) printperiod(pp, "");
+#endif
+       return pp;
+} /* intersect */
+
+period *fintersect(period *p1, period *p2)
+/*
+ * intersects p1 and p2 and returns resultant period list
+ * destroys p1 and p2
+ */
+{
+       period  *pp;
+       pp = intersect(p1,p2);
+       rfree(p1); rfree(p2);
+       return pp;
+}
+
+period *notperiod(period *p1)
+/*
+ * returns the inverse of period p1
+ * destroys p1 in the process
+ */
+{
+       period  *pp, *start, *prev;
+       time_t  t, min, max;
+       int             ptype;
+
+       if (p1 == NULL) return NULLP;
+       switch (ptype = p1->type)
+       {
+       case ABSOLUTE:
+               min = construct(1900+tnow.tm_year, 0, 0, 0, 0);
+               max = construct(1900+tnow.tm_year+1, 0, 0, 0, 0) - 1;
+               break;
+       case HOUR:
+               min = 0;
+               max = SECINDAY - 1;
+               break;
+       case DAY:
+               min = 0;
+               max = SECINWEEK - 1;
+               break;
+       }
+
+       prev = NULL;
+       start = pp = p1;
+       while (min < max && pp != NULL)
+       {
+               if (pp->end < pp->start) /* when negating a moment ... */
+                       pp->end = pp->start; /* ... netgate the next second */
+               t = pp->end + 1;
+               if (min < pp->start - 1)
+               {
+                       pp->end = pp->start - 1;
+                       pp->start = min;
+                       min = t;
+                       prev = pp;
+               }
+               else
+               {
+                       /* skip this irrelevant period */
+                       if (min < t) min = t;
+                       if (prev == NULL)
+                               start = pp->next;
+                       else prev->next = pp->next;
+                       /* free(pp); not always passed a malloc'ed period */
+               }
+               pp = ((prev != NULL) ? prev->next : start);
+       }
+
+       if (min < max)
+               if ((pp = newperiod(ptype, min, max, NULL)) != NULLP) {
+                       if (prev == NULL)
+                               if (start == NULL)
+                                       start = pp;
+                               else start->next = pp;
+                       else prev->next = pp;
+               }
+       return start;
+}
+
+period *exclude_past(period *pp)
+/* Return the period pp excluding any periods from the past */
+{
+       int doy, pod;   /* for passing to time_2_dp() only */
+       return fintersect(newperiod(ABSOLUTE, time_2_dp(now, &doy, &pod) + SECINPERIOD,
+                                   construct(tnow.tm_year+1900+1,0,0,0,0)-1, NULLP), pp);
+}
+
+period *absperiod(period *pp)
+/*
+ * Return a valid, instanciated period corresponding to period pp.
+ * An instanciated period is a period mapped to real dates.
+ * A valid period does not contain any excluded periods.
+ */
+{
+       int pod, doy;
+       time_t start;
+       period *newp;
+
+       /* first, convert any moments to extend to the end of the day */
+       if (pp != NULLP)
+       {
+       
+               if (pp->type != ABSOLUTE)
+               {
+                       /* intersect the period with the relevant day/week absolute period
+                        * Note: relative periods start from next absolute period
+                        */
+                       start = time_2_dp(now, &doy, &pod) + SECINPERIOD;
+                       newp = newperiod(ABSOLUTE, start, (pp->type == HOUR) ?
+                                        start+SECINDAY-1 : start+SECINWEEK-1, NULLP);
+                       pp = fintersect(pp, newp);
+               }
+
+#if 0
+               /* extend all moments to the end of the day */
+               for (newp = pp ; newp ; newp = newp->next)
+                       if (newp->start > newp->end)
+                       {
+                               start = time_2_dp(newp->start, &doy, &pod);
+                               newp->end = start + (SECINDAY - pod*SECINPERIOD) - 1;
+                       }
+#else
+               /* extend all moments to last for one period */
+               for (newp = pp ; newp ; newp = newp->next)
+                       if (newp->start > newp->end)
+                       {
+                               newp->end = newp->start + SECINPERIOD -1;
+                       }
+#endif
+
+               /* exclude invalid periods by intersecting with valid periods */
+               pp = intersect(valid_abs, intersect(valid_day, intersect(valid_hour, pp)));
+       }
+       return pp;
+} /* absperiod */
+
+/*********** recursive descent routines ************/
+
+period *finddate(FILE *fptr)
+/*
+ * date = num '/' num ['/' num]
+ *     | num month    neilb removed [num]
+ */
+{
+       int     dayofmonth, month, year;
+       period  *pp;
+       time_t  start;
+    
+       month = 0;
+       year = tnow.tm_year + 1900;
+       dayofmonth = in_token->tok_val;
+       if (lookahead->tok_type == SLASH)
+       {
+               if (nexttoken(fptr) != NUMBER)
+                       printerror(FATAL, "Missing Month before");
+               else
+               {
+                       month = in_token->tok_val-1;
+                       if (nexttoken(fptr) == SLASH)
+                       {
+                               if (nexttoken(fptr) != NUMBER)
+                               {
+                                       year = 1900 + tnow.tm_year;
+                                       printerror(RECOVER, "Missing Year before");
+                               }
+                               else
+                               {
+                                       year = in_token->tok_val;
+                                       if (year < 1900) year += 1900;
+                                       nexttoken(fptr);
+                               }
+                       }
+                       else year = 1900 + tnow.tm_year;
+               }
+       }
+       else if (lookahead->tok_type == MONTH)
+       {
+               month = lookahead->tok_val;
+               if (nexttoken(fptr) == NUMBER)
+               {
+#if 0
+                       neilb removed thing because  23 may 10:00 didn't' work...
+                               year = in_token->tok_val;
+                       nexttoken(fptr);
+#endif
+               }
+       }
+       else printerror(FATAL, "Missing Month before"); /* should not happen! */
+    
+       if (checkdate(year,month,dayofmonth))
+       {
+               start = construct(year,month,dayofmonth-1,0,0);
+               pp = newperiod(ABSOLUTE, start, start+SECINDAY-1, NULLP);
+       }
+       else pp = NULLP;
+#ifdef DEBUG
+       if (debug & DB_CALL)
+               printf("finddate() -> %d/%d/%d\n", dayofmonth, month, year);
+#endif
+       return pp;
+} /* finddate */
+
+period *book_mktime(int hour,int minute)
+{
+       period *pp;
+       time_t start;
+       if (checktime(hour, minute))
+       {
+               start = hour * SECINHOUR + minute * SECINMIN;
+               pp = newperiod(HOUR, start, start-1, NULLP);
+       }
+       else pp = NULLP;
+       return pp;
+}
+
+period *gettime(FILE *fptr)
+/*
+ * time        = num [':' num] ["am" | "pm"]
+ */
+{
+       int     minute, hour;
+       period  *pp;
+    
+       hour = in_token->tok_val;
+       minute = 0;
+       if (lookahead->tok_type == COLON)
+       {
+               if (nexttoken(fptr) != NUMBER)
+                       printerror(RECOVER, "Missing minute before");
+               else
+               {
+                       minute = in_token->tok_val;
+                       nexttoken(fptr);
+               }
+       }
+       else swaptok();
+       if (in_token->tok_type == APM)
+       {
+               if (hour == 12) hour = 0;       /* 12am --> 0:00; 12pm --> 12:00 */
+               hour += in_token->tok_val;
+               nexttoken(fptr);
+       }
+       pp = book_mktime(hour, minute);
+    
+#ifdef DEBUG
+       if (debug & DB_CALL)
+               printf("gettime() -> %d:%d\n", hour, minute);
+#endif
+       return pp;
+} /* gettime */
+
+period *getday(FILE *fptr)
+/*
+ * day = "monday" | ... | "sunday" | "today" | "tomorrow"
+ */
+{
+       int             day;
+       period  *pp;
+       int             type;
+       time_t  start;
+    
+       if ((day = in_token->tok_val) <= SUN)
+       {
+               type = DAY;
+               start = day * SECINDAY;
+       }
+       else
+       {
+               type = ABSOLUTE;
+               start = construct(1900+tnow.tm_year, 0, tnow.tm_yday, 0, 0);
+               if (day == TOMOR) start += SECINDAY;
+       }
+       pp = newperiod(type, start, start + SECINDAY - 1, NULLP);
+       nexttoken(fptr);
+#ifdef DEBUG
+       if (debug & DB_CALL)
+               printf("getday() -> Day: %d\n", day);
+#endif
+       return pp;
+} /* getday */
+
+period *getpredef(FILE *fptr)
+/*
+ * predef      = periodid ['.' num]
+ */
+{
+       int             week;
+       period  *defp, *newp, *pp;
+       time_t  tstart, tend;
+       char    *s;
+    
+       defp = in_token->tok_period;
+       s = in_token->tok_str;
+#ifdef DEBUG
+       if (debug & DB_CALL)
+               printf("getpredef() -> %s\n", s);
+#endif
+       if (nexttoken(fptr) == FSTOP)
+       {
+               if (nexttoken(fptr) != NUMBER)
+               {
+                       pp = NULLP;
+                       printerror(FATAL, "Expected week number before");
+               }
+               else
+               {
+                       if (defp->type != ABSOLUTE)
+                       {
+                               fprintf(stderr,"Ignored '.%d' after relative period '%s'\n",
+                                       in_token->tok_val, s);
+                               pp = copyperiods(defp);
+                       }
+                       else
+                       {
+                               if (defp != NULLP)
+                               {
+                                       pp = copyperiods(defp);
+                                       tstart = weekstart(defp->start);
+                                       tend = tstart + SECINWEEK - 1;
+                                       week = in_token->tok_val;       /* week 0 == week 1 */
+                                       while (week-- > 1 && pp != NULLP)
+                                       {
+                                               tstart += SECINWEEK;
+                                               tend += SECINWEEK;
+                                               while (pp != NULLP && pp->end < tstart)
+                                                       pp = pp->next;
+                                               if (pp != NULLP)
+                                                       while (tend < pp->start)
+                                                       {
+                                                               tstart += SECINWEEK;
+                                                               tend += SECINWEEK;
+                                                       }
+                                       }
+                                       if (pp != NULLP &&
+                                           (newp = newperiod(ABSOLUTE, tstart, tend, NULLP)) != NULL)
+                                               pp = fintersect(pp, newp);
+                                       else
+                                       {
+                                               rfree(pp);
+                                               pp = NULLP;
+                                       }
+                               }
+                               else
+                               {
+                                       fprintf(stderr, "Predefined period '%s' undefined\n", s);
+                                       pp = NULLP;
+                               }
+                       }
+                       nexttoken(fptr);
+               }
+       }
+       else pp = copyperiods(defp);
+       return pp;
+} /* getpredef */
+
+period *getmonth(FILE *fptr)
+/*
+ * month       = "january" | ... | "december"
+ */
+{
+       period  *pp;
+       int             month, year;
+       time_t  start;
+    
+       month = in_token->tok_val;
+       nexttoken(fptr);
+       start = construct(tnow.tm_year+1900, month, 0, 0, 0);
+       pp = newperiod(ABSOLUTE, start, start + SECINDAY*(daysinmonth[month] + (month == 1 ? leapyear(1900+year) : 0)) - 1, NULLP);
+#ifdef DEBUG
+       if (debug & DB_CALL)
+               printf("getmonth() -> %d\n", month);
+#endif
+       return pp;
+} /* getmonth */
+
+static period *getintersect();
+
+static period *getperiod(FILE *fptr)
+/*
+ * period      = '(' expr ')'
+ *             | time | day | week | date | month
+ */
+{
+       int     type;
+       period  *pp;
+    
+       switch (in_token->tok_type)
+       {
+       case LPR:
+               level++;
+               nexttoken(fptr);
+               pp = getintersect(fptr);
+               if (in_token->tok_type != RPR)
+               {
+                       printerror(FATAL, "Inserted Missing ')' before");
+               }
+               else nexttoken(fptr);
+               level--;
+               break;
+       case NUMBER:
+               swaptok();
+               nexttoken(fptr);
+               swaptok();
+               if ((type = lookahead->tok_type) == SLASH || type == MONTH)
+                       pp = finddate(fptr);
+               else pp = gettime(fptr);
+               break;
+       case HOUR:       pp = book_mktime(in_token->tok_val, 0); nexttoken(fptr); break;
+       case DAY:        pp = getday(fptr); break;
+       case PID:        pp = getpredef(fptr); break;
+       case MONTH:      pp = getmonth(fptr); break;
+       default:
+               printerror(FATAL, "Expected time, day, week, or date before");
+               pp = NULLP; break;
+       }
+#ifdef DEBUG
+       if (debug & DB_PERIOD) printperiod(pp, "");
+#endif
+       return pp;
+} /* getperiod */
+
+static period *getrange(FILE *fptr)
+/*
+ * range = ['not' | '~'] period ['-' period]
+ */
+{
+       period  *pp;
+       int             not = 0;
+    
+       if ((not = (in_token->tok_type == NOT))) nexttoken(fptr);
+       pp = getperiod(fptr);
+       if (in_token->tok_type == DASH)
+       {
+               nexttoken(fptr);
+               pp = range(pp, getperiod(fptr));
+       }
+       if (not) pp = notperiod(pp);
+       return pp;
+} /* getrange */
+
+static period *getunion(FILE *fptr)
+/*
+ * union = range {',' range}
+ */
+{
+       period  *pp;
+    
+       pp = getrange(fptr);
+       while (in_token->tok_type == COMMA)
+       {
+               nexttoken(fptr);
+               pp = periodunion(pp, getrange(fptr));
+       }
+       return pp;
+} /* getunion */
+
+static period *getintersect(FILE *fptr)
+/*
+ * intersect = union {union}
+ */
+{
+       int     type;
+       period  *pp;
+    
+       pp = getunion(fptr);
+       while ((type = in_token->tok_type) == LPR || type == NUMBER || type == DAY ||
+              type == PID || type == MONTH || type == RPR || type == NOT)
+       {
+               if (type == RPR) {
+                       if (level == 0)
+                       {
+                               nexttoken(fptr);
+                               printerror(RECOVER, "Deleted unmatched ')' before");
+                       }
+                       else break;
+               }
+               pp = fintersect(pp, getunion(fptr));
+       }
+       return pp;
+} /* getintersect */
+
+void skiptoeoln(FILE *fptr)
+{
+       char    ch;
+#ifdef DEBUG
+       if (infile != NULL) fprintf(stderr, "%s -  Line %d: ", infile, c_inline);
+       fprintf(stderr, "Skipping :");
+       while (fputc((ch = getc(fptr)), stderr) != EOF && ch != '\n');
+#else
+       while ((ch = getc(fptr)) != EOF && ch != '\n');
+#endif    
+       c_inline++;
+}
+
+void addstrlist(strlist        **head, char *value)
+/* add the new string value to the strlist ponted to by *head,
+ * being carefull to maintain string order in list
+ */
+{
+       static strlist  *end;   /* used to insert at end of list */
+       strlist *sp;
+       if ((sp = MALLOC(strlist)) != NULL &&
+           (sp->str_val = (char *) malloc(strlen(value)+1)) != NULL)
+       {
+               sp->next = NULL;
+               strcpy(sp->str_val, value);
+               if (*head == NULL)
+                       *head = sp;
+               else end->next = sp;
+               end = sp;
+       }
+}
+
+void addoption(optlist **head, int option, int value)
+/* add an option and its value to the list of options */
+{
+       optlist *newopt;
+       if ((newopt = MALLOC(optlist)) != NULL)
+       {
+               newopt->option = option;
+               newopt->value = value;
+               newopt->next = *head;
+               *head = newopt;
+       }
+}
+
+void getoptval(FILE *fptr, optlist **head, int option)
+/* [option] "=" . value */
+{
+       nexttoken(fptr);
+       if (in_token->tok_type == NUMBER)
+       {
+               addoption(head, option, in_token->tok_val);
+               nexttoken(fptr);
+       }
+       else printerror(FATAL, "Expected a number between '=', and");
+}
+
+void getoption(FILE *fptr, optlist **head, int option)
+/* option . "=" value */
+{
+       nexttoken(fptr);
+       if (in_token->tok_type == EQUAL)
+               getoptval(fptr, head, option);
+       else printerror(FATAL, "Expected '= number' between option, and");
+}
+
+int option_value(optlist *head, int option)
+{
+       while (head != NULL && head->option != option) head = head->next;
+       return (head != NULL ? head->value : 0);
+}
+
+command_type *getcommand(FILE *fptr)
+/*
+ * command = [command] [period] {token_string} <eoln>
+ */
+{
+       static command_type     command;
+       int             tokens_parsed, gotperiod, goodoption;
+       int             strcommand, conseccommand, optioncommand;
+    
+       /* prepare to parse a new command */
+       command_no++;
+       command.period = empty_abs;
+       command.strlist = NULL;
+       command.options = NULL;
+       command.ntokpassed = 0;
+       command.labs.item_len = 0;
+       tokens_parsed = gotperiod = parse_err = 0;
+
+       /* default for O_CONFIRM is 1 ... */
+       addoption(&command.options, O_CONFIRM, 1);
+    
+       /* default command is help */
+       if (in_token->tok_type == COMND)
+       {
+               tokens_parsed++;
+               command.command = in_token->tok_val;
+               nexttoken(fptr);
+       }
+       else command.command = HELP;
+    
+       /* check the type of command we have (this affects command syntax/semantics) */
+       conseccommand = (command.command == AVAIL || command.command == BOOK);
+       optioncommand = (command.command == CHANGE || conseccommand);
+       strcommand = (command.command == DEFINE || command.command == UNDEF ||
+                     command.command == ECHO);
+
+       /* parse the command (which ends with an EOLN or END) */
+       while (in_token->tok_type != EOLN &&
+              in_token->tok_type != END &&
+              (parse_err & FATAL) == 0)
+       {
+               tokens_parsed++;
+               switch (in_token->tok_type)
+               {
+               case TOKNAME:
+                       if (! strcommand)
+                               command.ntokpassed++;
+                       else printerror(RECOVER, "Ignoring token name");
+                       nexttoken(fptr);
+                       break;
+               case LABNAME:
+                       if (! strcommand)
+                       {
+                               if (command.labs.item_len == 0)
+                                       command.labs = new_desc();
+                               set_a_bit(&command.labs, in_token->tok_val);
+                       }
+                       else printerror(RECOVER, "Ignoring lab name");
+                       nexttoken(fptr);
+                       break;
+               case STRING:
+                       if (strcommand)
+                               addstrlist(&(command.strlist), in_token->tok_str);
+                       else printerror(FATAL, "Undefined string");
+                       nexttoken(fptr);
+                       break;
+               case COMND:
+               case TOPIC:
+                       if (command.command == HELP)
+                       {
+                               addoption(&command.options, O_HELP_TYPE, in_token->tok_type);
+                               addoption(&command.options, O_HELP_VALUE, in_token->tok_val);
+                               nexttoken(fptr);
+                       }
+                       else printerror(FATAL, "Unexpected second command/topic");
+                       break;
+               case EQUAL:
+                       if (optioncommand)
+                               if (conseccommand)
+                                       getoptval(fptr, &command.options, O_CONSEC);
+                               else getoptval(fptr, &command.options, O_MACHINES);
+                       else printerror(FATAL, "Invalid option");
+                       break;
+               case OPTION:
+                       switch (in_token->tok_val)
+                       {
+                       case O_ALL_TOKENS:
+                               if ((goodoption = (command.command == TOKEN || command.command == AVAIL)))
+                               {
+                                       addoption(&command.options, O_ALL_TOKENS, 1);
+                                       nexttoken(fptr);
+                               }
+                               break;
+                       case O_CONSEC:
+                               if ((goodoption = conseccommand))
+                                       getoption(fptr, &command.options, in_token->tok_val);
+                               break;
+                       case O_MACHINES:
+                               if ((goodoption = optioncommand))
+                                       getoption(fptr, &command.options, in_token->tok_val);
+                               break;
+                       case O_CONFIRM:
+                               goodoption=1;
+                               getoption(fptr, &command.options, in_token->tok_val);
+                               break;
+                       case O_COUNTS:  
+                               goodoption=1;
+                               getoption(fptr, &command.options, in_token->tok_val);
+                               break;
+                       }
+                       if (! goodoption) printerror(FATAL, "Invalid option");
+                       break;
+               default:
+                       if (! gotperiod++)
+                       {
+                               command.period = getintersect(fptr);
+                               if (command.command != DEFINE) command.period = absperiod(command.period);
+                       }
+                       else printerror(FATAL, "Unexpected second period specification starting at");
+                       break;
+               }
+       }
+       if (in_token->tok_type != EOLN) skiptoeoln(fptr);
+
+       if ((parse_err & FATAL) == 0 && tokens_parsed)
+       {
+               /* A fudge to allow us to detect the difference between not giving
+                * a period specification, and having a period specification reduce to
+                * an empty (NULL) period.
+                * We should have all period routines return one of the empty periods
+                * and not NULLP, but NULLP is easier, was developed first, and I am not
+                * bothered to change all neccessary places.
+                */
+               if (command.period == empty_abs)
+                       command.period = NULLP;
+               else if (command.period == NULLP)
+                       command.period = empty_abs;
+       
+               if (! strcommand)
+               {
+                       int alltokens;
+                       /* mark all tokens if the command uses them, but has none */
+                       if ((alltokens = option_value(command.options, O_ALL_TOKENS)))
+                               command.ntokpassed = 0; /* ignore any tokens explicitly passed */
+                       if (command.ntokpassed == 0) mark_all_tokens(alltokens);
+               }
+       
+               return &command;
+       }
+       else return NULLC;
+} /* getcommand */
diff --git a/book/bperiod.c b/book/bperiod.c
new file mode 100644 (file)
index 0000000..3d6a70a
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+ * Book interface module:
+ *     Routines that deal with defining and undefining periods only.
+ *     This subset of the full book interface routines may be used to
+ *     parse the system period definition file.
+ */
+
+#include       <stdio.h>
+#include       <string.h>
+#include       <stdlib.h>
+#include       <time.h>
+#include       <rpc/rpc.h>
+#include       "../interfaces/bookinterface.h"
+#define BOOK_INTERFACE
+#include       "boo.h"
+
+
+/****** global variables used for periods and predefs *****/
+
+struct tm      tnow;   /* current time - tm format */
+time_t         now;    /* current time - seconds */
+time_t         enow;   /* current time - seconds , not adjusted for timezone*/
+plist  *predefined;    /* predefined periods */
+period *valid_abs;     /* non-excluded periods of type ABSOLUTE */
+period *valid_day;     /* non-excluded periods of type DAY */
+period *valid_hour;    /* non-excluded periods of type HOUR */
+period empty_abs[] =   { { ABSOLUTE, 0, -1, NULLP } };
+period empty_day[] =   { { DAY, 0, -1, NULLP } };
+period empty_hour[] =  { { HOUR, 0, -1, NULLP } };
+
+time_t currtime()
+/* set the global time variables now and tnow to current time */
+{
+    time(&enow);
+    tnow = *localtime(&enow);
+    now = construct(1900+tnow.tm_year,0,tnow.tm_yday,tnow.tm_hour,tnow.tm_min);
+    return now;
+}
+
+void initperiods()
+/* initialise global valid periods and predefs */
+{
+    predefined = NULLPL;
+    valid_abs = notperiod(empty_abs);
+    valid_day = notperiod(empty_day);
+    valid_hour = notperiod(empty_hour);
+    currtime();
+}
+/********* period printing routines ******/
+
+void printtime(int type, time_t t, int showyear)
+/* showyear: include year when printing ABSOLUTE type */
+{
+    char       *s;
+    struct tm  *tp;
+
+    tp = gmtime(&t);
+    s = asctime(tp);
+    switch (type)
+    {
+    case ABSOLUTE:
+       printf("%5.5s %3.3s %2.2s %3.3s", s+11, s, s+8, s+4);   /* HH:MM Day Date Mnth */
+       if (showyear)
+           printf(" %d", tp->tm_year+1900);
+       break;
+    case HOUR:
+       printf("%5.5s", s+11);  /* Hour:Min */
+       break;
+    case DAY:
+       t += 4 * SECINDAY;      /* 1 Jan 1970 was a Thursday */
+       tp = gmtime(&t);
+       s = asctime(tp);
+       printf("%5.5s %3.3s", s+11, s); /* Hour:Min Day */
+       break;
+    }
+}
+
+void print_one_period(period *pp)
+{
+    printtime(pp->type, pp->start, 0);
+    fputs(" - ", stdout);
+    printtime(pp->type, pp->end, 1);
+}
+
+void printperiod(period *pp, char *separator)
+/* print each period in the list of periods pp */
+{
+    if (pp != NULLP)
+    {
+       print_one_period(pp);
+       while ((pp = pp->next) != NULLP)
+       {
+           printf("\n%s", separator);
+           print_one_period(pp);
+       }
+       fputs("\n", stdout);
+    }
+    else fputs("NULL period\n", stdout);
+}
+
+/********* predef routines **********/
+
+void printpredefs()
+/* print out a list of all the current predefined periods.
+ * list items are columnated over output lines of 80 charactes, using tabs.
+ */
+{
+    plist *pl;
+    int        col;
+
+    puts("    Predefs currently defined:");
+    col = 0;
+    for (pl = predefined; (pl != NULL); pl = pl->next)
+    {
+       col = ((col+8) / 8) * 8 + strlen(pl->id);
+       if (col > 80)
+       {
+           puts("");
+           col = strlen(pl->id) + 8;
+       }
+       printf("\t%s", pl->id);
+    }
+    puts("");
+}
+
+void addpredefs(strlist *strings, period *periods)
+/*
+ * if the period is not to be excluded,
+ * add it to the predefined period list "predefined", otherwise
+ * add the excluded period to one of: valid_abs, valid_hour, or valid_day.
+ */
+{
+    strlist    *sp;
+    plist      *new;
+    period     **predeflist;
+
+    for (sp = strings; (sp != NULL); sp = sp->next)
+    {
+       if (strcmp(sp->str_val, "exclude"))
+       {
+           if ((new = MALLOC(plist)) != NULLPL)
+           {
+               new->id = sp->str_val;
+               new->period = periods;
+               new->next = predefined;
+               predefined = new;
+           }
+       }
+       else
+       {
+           switch(periods->type)
+           {
+           case HOUR:  predeflist = &valid_hour;       break;
+           case DAY:   predeflist = &valid_day;        break;
+           case ABSOLUTE: predeflist = &valid_abs;     break;
+           }
+           *predeflist = fintersect(*predeflist, notperiod(copyperiods(periods)));
+       }
+    }
+}
+
+int delpredef(char *name)
+/* remove the predefined period called name.
+ * free the period list iff the list is not shared with another predef.
+ */
+{
+    struct periodlist  *pdp, *prev;
+
+    for (pdp = predefined, prev = NULL;
+       (pdp != NULL && strcmp(name, pdp->id));
+       prev = pdp, pdp = pdp->next);
+    if (pdp != NULL)
+    {
+       if (prev == NULL)
+           predefined = pdp->next;
+       else prev->next = pdp->next;
+
+       for (prev = predefined; (prev != NULL && prev->period != pdp->period);
+           prev = prev->next);
+       if (prev != NULL) rfree(pdp->period);   /* no predef shares periods */
+       free(pdp->id);
+       free(pdp);
+    }
+    return (pdp != NULL);
+}
+
+/************ miscellaneous routines ****************/
+
+strlist *instrlist(char *name, strlist *strings)
+/* return the entry in the strings list matching tokname */
+{
+    while (strings != NULL && strcmp(strings->str_val, name))
+       strings = strings->next;
+    return (strings);
+}
+
+/****** main period/predef commands *****/
+
+void b_define(command_type *comm)
+{
+    if (comm->strlist != NULL)
+    {
+       if (comm->period == NULL)
+       {
+           if (instrlist("exclude", comm->strlist))
+           {
+               /* The only way we can print excluded predefs.
+                * Ignore the other (undefined) predef names
+                */
+               puts("The following periods are excluded:");
+               fputs("Absolute Periods:\n\t", stdout);
+               printperiod(notperiod(copyperiods(valid_abs)), "\t");
+               fputs("Weekly periods:\n\t", stdout);
+               printperiod(notperiod(copyperiods(valid_day)), "\t");
+               fputs("Daily periods:\n\t", stdout);
+               printperiod(notperiod(copyperiods(valid_hour)), "\t");
+           }
+           else fputs("Cannot define an empty predef\n", stderr);
+       }
+       else addpredefs(comm->strlist, comm->period);
+    }
+    else if (comm->period != NULLP)
+    {
+       putchar('\t');
+       printperiod(comm->period, "\t");
+    }
+    else printpredefs();
+}
+
+void b_undefine(command_type *comm)
+{
+    strlist *sp;
+    if (comm->strlist != NULL)
+    {
+       for (sp = comm->strlist; (sp != NULL); sp = sp->next)
+           if (! delpredef(sp->str_val))
+           fprintf(stderr, "Predef '%s' does not exist\n", sp->str_val);
+    }
+    else fputs("Nothing to undefine\n", stderr);
+}
diff --git a/book/bversion.c b/book/bversion.c
new file mode 100644 (file)
index 0000000..dfe3ee5
--- /dev/null
@@ -0,0 +1,33 @@
+/* Defines the current version of book (the user interface) */
+
+char *book_version = "Book Version 2.1.4 (16 Apr 1996)";
+
+/*
+ v.2.1.4 16 Apr 1996:
+       . Change token_periods [bcomm.c] to allow for change in
+         token_period(token_name) [bookinterface.c] now returning
+         periods when the token is *not* valid.
+ v.2.1.3 13 Apr 1995:
+       . Fix minor bug when non-privilaged user uses "book" or
+         "available" without specifying a period (defaulting to
+         current time).
+ v.2.1.2 7 Apr 1995:
+       . Fix error in cancelling past or pending bookings.
+       . Not printing status of past bookings properly.
+ v.2.1.1 24 March 1995:
+       . Fix bug when specifying a period not starting on the
+         hour/half hour.
+ v.2.1 22 March 1995:
+       . Include version (this module and associated flags).
+       . Cancell bookings (status==B_PENDING) that start in the past.
+       . Allow classes to change the token used in bookings.
+       . Improve the listing of current bookings - particularly when
+         printing out consecutive class bookings for the same time but
+         for different labs.
+       . Help is now paged for all TERM setting except xterm.
+ v.2.0 Date Unknown (1992?)
+       . First developed this interface to the booking system (2.0).
+       . Booking system (1.*) had a completely different user
+         interface (along with everything else!)
+
+ */
diff --git a/book/help b/book/help
new file mode 100644 (file)
index 0000000..611a654
--- /dev/null
+++ b/book/help
@@ -0,0 +1,793 @@
+* This file contains stanzas of help information used by book.
+* Each stanza is started by a line of the form: "#stanza_name",
+* where stanza_name is the name used by book to index the information;
+* and each stanza is ended by a line containing only a "#".
+* Any lines between the "#stanza_name" line and the "#" line that also
+* start with a "#" will be skipped.
+
+* The following stanza must be the first in this file - It gives
+* the default help stanza (produced by the help command with no arguments).
+* It is also a good idea to put the stanzas into the file in order of descreasing
+* access.
+#
+    Synopsis:
+       Book is used to make, inspect, and cancel terminal bookings.
+       At the prompt ("BOOK > ") enter a command specification with the
+       syntax shown below.
+#command
+    Syntax:
+       [command] {tokens} [period]
+    where:
+       command = "book"   | "unbook"
+               | "show"   | "available"
+               | "refund" | "reclaim"   | "past"
+               | "define" | "undefine"
+               | "token"  | "echo"
+               | "labs"   | "times"
+               | "quit"   | "exit"
+               | "change" | "help"
+               | "shell" .
+       tokens  = booking token name.
+       period  = time specification.
+    Note:
+       The user may abbreviate the commands to the first character.
+       Exceptions are:
+           "exit" (abbrev:"ex") to distinguish it from "echo" (abbr:"e").
+           "undefine" (abbr:"und") to distinguish it from "unbook" (abbr:"u").
+           "refund" (abbr:"ref") to distinguish it from "reclaim" (abbr:"rec").
+    Defaults:
+       If no command is given, but a period or tokens are specified,
+           then the help command is assumed.
+       If no period is given, then the next appropriate period from now
+           is usually assumed. However, this will depend upon the actual
+           command invoked.
+       If no tokens are given, then all available or appropriate tokens will
+           usually be assumed. However, this will depend upon the actual
+           command invoked.
+    See Also:
+        "help summary" For a summary of the commands available.
+       "help" command  For detailed information on the specified command.
+                       (Where command is any one of the commands defined above).
+       "help period"   For information on period syntax.
+       "help token"    For information on the token command and on
+                         tokens in general.
+       "help syntax"   For help on the notation used in syntax definitions.
+       "help defaulter" For information about defaulters.
+#
+
+#summary
+   Summary of commands:
+       available       Find available periods that may be booked.
+       book            Make a terminal booking.
+       change          Change a class booking.
+       claim           Reclaim a booking that was not taken up at allocation time.
+       define          Give a name to a period - create a predefined period.
+       echo            Print a message to the screen.
+       exit            Exit from the program.
+       help            Print help on a command, topic, period, or token.
+       labs            List all labs that might be bookable during a given time range.
+       past            Show past bookings and cancellations.
+       quit            Exit from the program.
+       reclaim         Reclaim a booking that was not taken up at allocation time.
+       refund          Refund a token spent on a past booking.
+       show            List the pending bookings.
+       times           List all timeslot which may be bookable on a given day.
+       token           Show the number of tokens and their periods. 
+       unbook          Cancell a pending booking.
+       undefine        Remove a predefined period.
+       shell           Run a shell for 5 minutes.
+                       This is for printing and giving from booking terminal.
+
+   Summary of Topics:
+       cfile           Information on book command files.
+       command         General information on book commands.
+       period          Info on specifying periods to commands.
+       predef          Info on predefined periods and their use.
+       summary         This summary page.
+       syntax          Info on the syntax specification used in the help documents.
+       tokens          General information about tokens.
+       defaulter       Explaination of when a user is considered a "defaulter"
+
+    See Also:
+        "help" command         For detailed information on any command listed above.
+       "help" topic            For detailed information on any topic listed above.
+#
+
+#book
+    Syntax:
+       "book" {tokens} [period] {options}
+    where:
+       options = ["consecutive"] "=" C
+               | "machines" "=" M
+       C | M   = "0".."9" {"0".."9"}
+    Synopsis:
+       Interactively make a booking
+           for the current user;
+           within the period(s) specified;
+           using one or more of the tokens specified;
+           for a sequence of C consecutive periods at a time;
+           booking M machines at a time (Class bookings only).
+    Defaults:
+       If no period is specified,
+           then stop after the first successful booking.
+       If no tokens are specified,
+           then any token available to the current user may be used.
+       If not specified, C and M are both 1.
+    Operation:
+       Basically, for every sequence of available slots that meets the
+       criteria, the user will be asked to choose from a list of available
+       tokens, and then from a list of available labs.
+       If there is not a choice of tokens or labs to choose from, then the
+       user will be shown the one token or lab available, and asked if they
+       want to book that.
+       In general, when prompted, the user may respond with:
+           "y"         Make the booking.
+           "n"         Don't book this period - try the next one.
+           "q"         Quit - Return to the BOOK prompt.
+       When prompted with a choice of tokens or labs, the user may also
+       respond with:
+           number      The corresponding item from the list will be used;
+           "y"         Pick the lab with the most free machines.
+                       (This response is only valid when choosing labs)
+
+       After making a booking, the program will search for the next sequence
+       of slots to book within the period specified, and the user is prompted
+       once again. If no period was specified, book returns to the command
+       prompt.
+    Restrictions:
+       1) The user is not allowed to book the next slot, the current slot, or
+          any slot in the past.
+       2) Consecutive slots will be booked using tokens of the same type.
+          To book consecutive slots using different token types, the user must
+          resort to using the book command more than once, specifying (or
+          choosing) a different token type for each successive slot.
+       3) The user will not be allowed to book a slot that has already been
+          booked by that user. If there already exists a class booking for
+          for that time which is available to the user, the user will be warned
+          about it, but will still be allowed to make the booking.
+       4) Only classes may specify the number of machines to be booked (M).
+    Note:
+       A terminal booking does not imply a terminal allocation.  This is due
+       to the fact that bookings are based on the number of physical terminals
+       in a lab, while allocations are based on the number of these terminals
+       which are up (working) and available to the network at the time of
+       allocation.
+
+       Thus, if the number of bookings exceeds the number of terminals
+       actually available at allocation, then these excess bookings cannot be
+       honoured. As bookings are allocated in the chronological order in which
+       they were made, it is the last few bookings made for a particular lab
+       and slot that may not be honoured.
+
+       Consequently, if a user books one of the last available slots in a lab,
+       then the user will be warned that the booking may not be honoured.
+
+   Class Bookings:
+       If the user is a class, the user is required to specify the number of
+       machines to be booked for each period satisfying the specs.
+       If this number has not been passed as an option, the user will be
+       prompted for it for every available period found.
+
+       In general, a class booking will be always be made without prompting
+       if there are no unknown parameters and no choices to be made.
+       Ordinary users however, have to at least confirm that they want to make
+       the booking.
+
+       If a class booking exists within the specified period, that:
+       a) Uses one of the specified tokens, or
+       b) Is made for a lab that one of the specified tokens could book,
+       then the existing booking is changed to be for the new specified
+       number of machines, using the alternate token.
+       In this way, the "book" command subsumes the functionality of the
+       "change" command.
+   See Also:
+       "unbook":       For information on unbooking terminal bookings.
+       "change":       For information on changing terminal bookings.
+#
+
+#unbook
+    Syntax:
+       "unbook" {tokens} [period]
+    Synopsis:
+       Interactively cancel all bookings made
+           by the current user;
+           using any of the tokens specified;
+           for any booking slot within the periods specified;
+    Defaults:
+       If no period is specified,
+           then the next booked period made with any of the specified
+           tokens will be cancelled.
+       If no tokens are specified,
+           then all bookings made by the user in the period(s) specified
+           will be cancelled, regardless of what token was used to make
+           the booking.
+    Operation:
+       When a booking is found matching the criteria,
+       the user is prompted with:
+           The booking slot taken;
+           The token used to make the booking.
+       The user may respond with:
+           "y" The booking is cancelled, and the token credited to the user.
+           "n" The booking is not cancelled, and the program searches for
+                 the next booking meeting the criteria.
+           "q" Quit - The program returns to the command prompt "BOOK >"
+    Restrictions:
+       The user will not be allowed to cancel a slot on or after the day
+       of the slot unless the booking was made less than an hour before the
+       cancellation and the booking was not for the current or next slot.
+#
+
+#change
+    Syntax:
+       "change" [period] {tokens} [["machines"] "=" N]
+    Where:
+       N       =       "0".."9" { "0".."9" }
+    Synopsis:
+       Interactively change any class bookings made within the periods specified,
+       such that:
+           (1) If the booking was made with one of the specified tokens,
+               then change the number of bookings to N (if possible).
+           (2) If the booking was made for a lab which could also be booked by one
+               of the specified tokens, then change the token used to
+               the first such token specified, and the number of bookings to N
+               (if possible).
+    Restrictions:
+       Only users who are classes may use this command.
+    Defaults:
+       If N is not specified (or zero), the user will be asked to specify
+           the change to be applied to each matching booking.
+       If no period is specified,
+           then the next class booking made with any of the specified
+           tokens will be cancelled.
+       If no tokens are specified,
+           then all bookings made by the class in the period(s) specified
+           will be changed, regardless of the token used.
+    Operation:
+       When a booking is found matching the criteria, the user is shown:
+           The matching booking slot;
+           The token used to make the booking;
+           The number of machines booked.
+       If the number of machines (N) is already specified,
+       then the booking is changed to that number of machines,
+       otherwise the user is prompted for the change to be made and the
+       user may respond with:
+           A positive or negative number (the change to be made);
+          "n"  The booking is not changed, and the program searches for
+               the next booking meeting the criteria.
+          "q"  Quit - The program returns to the command prompt "BOOK >"
+    Notes:
+       If the change removes more machine bookings than were made, the
+           booking is removed altogether (same as "unbook").
+       The number of machines can only be increased by as many machines
+           as currently remain unbooked.
+       The option "machines" may be shortened to "mach".
+#
+
+#available
+    Syntax:
+       "available" {tokens} [period] [["consecutive"] "=" N]
+    where:
+       N = "0".."9" {"0".."9"}
+    Synopsis:
+       List those periods which may be booked:
+           by the current user;
+           within the period(s) specified;
+           using one or more of the tokens specified;
+           for a sequence of N consecutive slots at a time.
+    Defaults:
+       If no period is specified, then the next available period is shown.
+       If no tokens are specified,
+           then any of the tokens available to the current user may be used.
+       If not specified, N is 1.
+    Restrictions:
+       1) Neither the next slot, the current slot, nor any slot in the past,
+          is available to the user.
+       2) Consecutive slots are only available using tokens of the same type.
+    Note:
+       This command will not make or cancel any bookings.
+       The option "consecutive" may be shortened to "consec", or left out
+           altogether.
+#
+
+#show
+    Syntax:
+       "show" {tokens} [period]
+    Synopsis:
+       List the pending bookings that have been made:
+           for the current user;
+           within the periods specified;
+           using any of the tokens specifed.
+    Output:
+       Two kinds of pending bookings may be shown:
+           1) Bookings made for the user by the user.
+           2) Bookings made by the administrator of a class of which the user
+              is a member.
+       Each booking is identified by:
+           the lab containing the booked terminal;
+           the period of the booking.
+       If the booking is one made by the user,
+           then the token used to make the booking is shown;
+       otherwise the class booked is shown in angle brackets instead.
+    Defaults:
+       If no period is specified, then all bookings are shown.
+       If no tokens are specified, then bookings made with any token are shown.
+    Note:
+       This command will not make or cancel any bookings.
+#
+
+#past
+    Syntax:
+       "past" {tokens} [period]
+    Synopsis:
+       List the expired bookings:
+           for the current user;
+           that were booked within the periods specified;
+           that used (or tried to use) any of the tokens specifed.
+       Expired bookings are those that have been cancelled, or those that
+       have already been allocated a terminal, ie: any booking that
+       is not pending.
+    Output:
+       Each expired booking is identified by:
+           the lab containing the booked terminal;
+           the token used to make the booking;
+           the period of the booking;
+           the status of the booking.
+       The booking status will be one of the following:
+            Status      Meaning
+           Alloc'd     The booking was successfully allocated a terminal.
+           Refund      The booking was not successfully allocated a terminal,
+                       and the token used to make the booking was refunded.
+           Cancel      The booking was cancelled by the user before it could
+                       be allocated a terminal.
+           Free        The booking was successfully allocated a terminal,
+                       but as the lab was underutilised at the time, the
+                       user was refunded the token that he/she had used to
+                       make the booking.
+           Tentative   The booking was not claimed at allocation time, but
+                       tentatively retained in case the user reclaimed it.
+    Defaults:
+       If no period is specified, then all expired bookings are shown.
+       If no tokens are specified, then expired bookings made with any
+       token are shown.
+    See Also:
+       "refund"        for refunding expired bookings.
+       "reclaim"       for reclaiming the current tentative booking.
+#
+
+#refund
+    Syntax:
+       "refund" {tokens} [period]
+    Synopsis:
+       Refund the tokens spent in making allocated bookings:
+           for the current user;
+           that were booked within the periods specified;
+           that used any of the tokens specifed.
+       Allocated bookings are those that have been successfully allocated
+       a terminal in the past, and for which one token has been spent.
+    Output:
+       When an allocated booking is found meeting the criteria, the user
+       is prompted with:
+           the lab containing the booked terminal;
+           the token used to make the booking;
+           the period of the booking;
+           the status of the booking (which will always be Alloc'd);
+           the prompt string: "refund ?".
+       The user may respond with:
+           "y" The token used to make the booking is refunded to the user,
+                 and the booking status turned into "Refund".
+           "n" The token is not refunded, and the program searches for
+                 the next expired booking meeting the criteria.
+           "q" Quit - The program returns to the command prompt "BOOK >"
+    Restrictions:
+       This command may only be used by a privilaged user.
+    See Also:
+       "past"  for more details on expired bookings and their statuses.
+#
+
+#reclaim
+#claim
+    Syntax:
+       "reclaim" | "claim"
+    Synopsis:
+       Reclaim a booking that was not taken up at allocation time.
+    Notes:
+       Once a booking has been allocated to a terminal, the user making the
+       booking has 7 minutes to log in. If he/she does not log in within this
+       time, the terminal is made available to other users.
+       The only way the user can subsequently take up the booking once the
+       allocated terminal has been taken by another, is by using this
+       command.
+       Also, if a user has repeatedly not turned up to claim bookings,
+       the booking system will allow anyone to use a terminal that has been
+       allocated to that user. In this case the user will only be able to
+       claim their booking by using this command.
+
+       If the command is successful, the other (non-booked) user is given
+       5 minutes and a series of warnings, to log off.
+#
+
+#define
+    Syntax:
+       "define" {name} [period]
+    Synopsis:
+       Add a new predefined period (predef) to the existing list of predefs.
+       The new period may subsequently be refered to in a period specification
+       by any of the names given.
+    Defaults:
+       If no period or names are given,
+           then the name of all predefs are printed.
+       If no new predef names are given, then the period (or predef) is
+           displayed as defined (not as instanciated).
+           eg: compare "define ah" with "help ah".
+    Note:
+       To change an existing predef definition, the user must first undefine
+           the predef, and then redefine it.
+       If the name given to a period is the same as a token name, the user
+           will not be able to explicitly refer to that token after the define.
+    See Also:
+       "help predef"   for more information on predefs.
+       "help undefine" for how to undefine predefs interactively.
+       "help token"    for more information on tokens.
+#
+
+#undefine
+    Syntax:
+       "undefine" quotename {quotename}
+    where:
+       quotename = '"' name '"'
+       ie: the predef names to be undefined are enclosed in double quotes.
+    Synopsis:
+       Remove from the current set of predefined periods (predefs), the
+       predefs with the names listed.
+    Restrictions:
+       Cannot remove excluded predefs ("exclude").
+    See Also:
+       "help predef"   for more information on predefs.
+       "help define"   for how to define predefs interactively.
+#
+
+#labs
+    Syntax:
+       labs [period]
+    Synopsis:
+       Finds and displays a list of all labs which are potentially bookable
+       with any current tokens during the given period.
+       Actualy availability in these labs in not checked.
+    Output:
+       One line containing a list of space separated labnames.
+    Note:
+       This command is provided to support the "tkbook" interface to "book".
+
+#
+
+#times
+    Syntax:
+       times [period]
+    Synopsis:
+       Finds a displays a list of all bookable periods during the given period
+       for which booking might possibly be made.
+       This essentially determines lab opening hours.
+    Output:
+       One line containing an ordered list of periods or period ranges. e.g.
+               10:00   would mean the booking period which starts at 10am
+               9:30-17:30 would mean the periods from the one which starts at
+                       9:30am to the one which start at 5:30pm
+    Note:
+       This command is provided to support the "tkbook" interface to "book".
+#
+
+#echo
+    Syntax:
+       "echo" { '"' string '"' }
+    Synopsis:
+       Print the string given within double quotes on standard output.
+    Notes:
+       The string MUST start with and be terminated by a double quote.
+       The double quotes are not printed.
+       The string may extend over more than one line.
+       Consecutive quoted strings are concatenated.
+       This command is particularly useful in command files.
+    See Also:
+       "help cfile"    For help on command files.
+#
+
+#shell
+    Syntax:
+       "shell"
+    Synopsis:
+       Runs a login shell for at most five minutes
+    Notes:
+       If the shell does not exit within four minutes of starting,
+       a warning message is printed. If it still does not exit
+       after a further minute, it is killed and book will exit.
+#
+#help
+#?
+    Syntax:
+       "help" [command | topic] {tokens} [period]
+    where:
+       command = "book"   | "unbook"
+               | "show"   | "available"
+               | "define" | "undefine"
+               | "token"  | "echo"
+               | "exit"   | "quit"
+               | "help" .
+       topic   = "period" | "predef"
+               | "syntax" | "cfile"
+               | "topics" | "command" .
+    Synopsis:
+       Displays help information on the command, topic, period, or tokens
+       specified.
+    Defaults:
+       If none of the above are specified,
+           then an overall introduction to the book interface is displayed.
+       If a period specification is given,
+           then the time periods mapped by the specification are explicitly
+           instanciated.
+       If the name of a token is given,
+           then the number of these tokens assigned to the user is displayed.
+    Note:
+       Help command strings are always presented in the "See Also" section.
+       Since the help command is the default command, the user will usually
+       not have to type "help" (or "h") at all, just the arguements to the
+       help command on their own. The only exception to this is when the help
+       arguement is itself the name of a command (obviously!).
+#
+
+#exit
+#quit
+    Syntax:
+       "quit" | "exit" | "<CTRL-D>"
+    Synopsis:
+       Exit from the book program.
+#
+
+* the token command includes the tokens concept
+#token
+    Syntax:
+       "token" ( ["all"] | {token_name} )
+    Synopsis:
+       For each token named, show:
+           the number of such tokens currently assigned to the user;
+           and the periods that may be booked using the token.
+    Defaults:
+       If no tokens are named,
+           then list all unexpired tokens assigned to the user.
+       If "all" is named, show all tokens regardless of expiry date.
+#tokens
+    Description:
+       Tokens are assigned to users of the booking system, and used by them to
+           make terminal bookings. Users refer to tokens by name, and may pass
+           these token names to various book interface commands.
+       Tokens can be used to book a restricted set of periods only.
+           Tokens of the same name have the same set of restricted periods.
+       Token names cannot be abbreviated (unlike commands, which can be).
+    See Also:
+       "help" command_name     For more details on what each command does
+                               with the list of token names passed to it.
+
+#
+
+#period
+    Synopsis:
+       Most book commands will accept a period specification which allow the
+       users to specify the periods that they would like to book, inspect, or
+       cancel.
+    Syntax:
+       period  = intersection .
+       intersection = union {union} .
+       union   = range {"," range} .
+       range   = ["not" | "~"] starttime {"-" endtime} .
+       endtime = periodtime .
+       starttime = periodtime .
+       periodtime = "(" intersection ")"
+               | time | day | date | month | predef .
+       time    = hour [":" min] ["am" | "pm"] .
+       date    = dayofmonth "/" monthofyear ["/" year] .
+       day     = "monday"  | ... | "sunday" | "today" | "tomorrow" .
+       month   = "january" | ... | "december" .
+       predef  = predef_id ["." week]
+       predef_id = the name of one of the predefined periods.
+       hour = min = week = dayofmonth = monthofyear = year = "0".."9"{"0".."9"}
+    Examples:
+       10am            next (10:00 to 10:29) period.
+                           Today if the current time is before 10am, otherwise
+                           Tommorrow.
+       10-12           next (10:00 to 11:59) period.
+                           Today and/or tommorrow depending on current time.
+       10-12,2pm Tue   next ((10:00 to 11:59, and 14:00 to 14:29) of Tuesday)
+                           If today is Tuesday, then times will refer to next
+                           Tuesday's periods if current time exceeds specified
+                           time(s).
+       10 tue-thur     next 10:00 to 10:29 on Tuesday, Wednesday, and Thursday
+       2pm tue october 14:00 to 14:29 on all Tuesdays in October of this year
+       Thu,Tue s1.4    Tuesday and Thursday of the fourth week of the
+                       predefined period s1 (s1 will usually be defined to be
+                       session 1 of the current year - see "help predef").
+       (11,2pm-3pm wed),(10:30 thur) s1.2-s1.4
+                       The various periods on Wednesdays and Thursdays
+                           of the second week of session 1
+                           to the fourth week (inclusive) of session 1
+    Notes:
+       A booking period has a minimum duration of half an hour.
+       If a user specifies a start time for the period, but not an end time
+       then the end time will depend on the type of start time specified.
+           Start time          Default end time
+           --------------------------------------------
+           Time of day         time of day + 30 minutes
+           Day of Week         end of that day of week
+           Predef.week         end of that predefined week.
+       eg:
+           Start time          Default end time
+           --------------------------------------------
+           10am                10:29:59
+           12:30 Tue           12:59:59 Tue
+           Tue                 23:59:59 Tue
+           s1.2                Sunday of second week of session 1
+    See Also:
+       "help predef"   For help on predefined periods.
+       "help" period   To list the periods matched by a period specification
+                        eg: "help 10-12 Tue" will list the times and dates
+                        matched by the period specification "10-12 Tue"
+       "help syntax"   For help on the notation used in syntax definitions.
+#
+
+#predef
+    Synopsis:
+       A predef is a predefined period that has been given a name.
+       The period may be used in other period expressions by refering to its
+       name in the period expression.
+
+       A particular week of the predefined period may be refered to by
+           "name.week"
+       where "name" is the name of the predefined period, and "week" is the
+       number of the week desired.
+
+       A new period is added to the list of existing predefs by using the
+       command "define", and removed from the list using "undefine".
+
+    Special Predefs:
+       The predef named "excluded" is special in a couple of ways:
+           1) This predef defines those periods which can never be booked.
+               The periods usually refer to holidays when students will not
+               generally be given access to the workstations.
+           2) There may be more than one period named "excluded". These
+              periods are merged into the one predef.
+    Notes:
+       Periods start from week 1 (week 0 and week 1 are synonamous).
+       To see what a predef has been defined as, type: "define" predef_name.
+       To see what predefs have been defined, type: "define"
+       To see what periods have been excluded, type: "define excluded"
+
+       While predefs may be defined interactively, they are more usually
+       defined in command files.
+
+    See Also:
+       "help define"   For the syntax of the define command
+       "help undefine" For the syntax of the undefine command
+       "help period"   For help on period specifications in general.
+       "help cfile"    For help on command files.
+
+#
+
+#cfile
+    Synopsis:
+       A book interface command file contains booking commands that the book
+       interface reads and executes before accepting interactive commands from
+       the user.
+       These command files will usually contain non-interactive booking
+       commands - most commonly the "define" command.
+    Files:
+       There are two command files that the book interface will read:
+
+       /usr/local/lib/book/periods     System command file which define
+                               general periods and predefs that are useful
+                               for the current year.
+
+       $HOME/.periods          User's command file.
+                               This file is read after the system file.
+    File format:
+       Commands are entered as they would be interactively.
+       Commands may be extended onto the next line if the line does not
+           contain a comment, and the line ends with a "\".
+       Comments start with a "#" and extend to the end of line.
+       Blank lines are ignored.
+
+    Notes:
+       The user's command file is primarily intended to contain those defines
+       of predefs that the user will find personally useful.
+       For example, the user may wish to add a define command to their
+       ".period" file which defines a predef that maps all the user's
+       free timetable periods for the week.
+       eg:
+           define myfree (10-12,13-14 Mon), \
+                         (3pm-5pm Tue-Wed), (thur afternoon)
+
+       This predef could later be used (for instance) to find all available
+       free periods on Tuesday of the fourth week of session 1 using:
+           available myfree tue s1.4
+
+       The user may also wish to print a list of tokens, bookings, and
+       available free periods just before getting the interactive prompt from
+       the book interface, by putting the following few lines at the end
+       of their ".period" file.
+       eg:
+           token               # this lists all available tokens
+           show                # this shows all current bookings
+           echo "Periods free today:"
+           avail myfree today  # this lists all my free periods today
+                               # (assuming predef "myfree" has been defined)
+    See Also:
+       "help command"  For a description of the command syntax.
+       "help" command  For a description of the specific command.
+       "help predef"   For a description of predefined periods.
+#
+
+#syntax
+    Command and Period syntax are described using the following (EBNF)
+    conventions:
+
+    Convention Meaning
+    ---------------------------------------------------------------
+    "acb"      the literal string "abc" (entered without the quotes)
+    abc                the construct abc, whose value is given by the assignment
+               of literals and/or constructs to it by the assignment statement
+
+    In what follows, X,Y,Z refer to expressions of literals and/or constructs.
+
+    Expression Meaning
+    ( X )      The same as X.
+    [ X ]      X may occur once or not at all. (X is optional).
+    { X }      X may occur zero or more times.
+    X | Y      An occurence of X or Y (but not both).
+    X Y                X is followed by Y, optionally separated by blanks or tabs.
+
+    abc = X .  The assignment statement.
+               The construct abc has the value of the expression X.
+
+    eg: The syntax of the book command is: "book" {tokens} [period].
+    This means that you type the literal string "book" optionally followed
+    by zero or more token constructs (defined in "help token"), and followed 
+    by the period construct (whose syntax is defined in "help period").
+#
+
+#defaulter
+    A "defaulter" is a user of the booking system who has demonstated
+    a pattern of making bookings, but not claiming them.
+
+    When a user is considered to be a defautler, bookings made by that
+    user will be allocated to a workstation, but the workstation will
+    not be freed or reserved for them. This is because it is expected
+    that the user will not turn up, so someone else should be allowed
+    to use the workstation.
+
+    If the user does turn up to claim their booking, they will have to
+    log in to a booking terminal and issue the "claim" command. This
+    command will cause the allocated workstation to be freed (if
+    necessary) and reserved for the user.
+
+    A user is considered to be a defaulter if they have failed to turn
+    up to at least two bookings in the past fortnight including one of
+    their two most recent bookings.
+    NOTE: this heuristic may change if it doesn't appear to catch the
+    right set of people.
+
+    Once a defaulter demonstates that they take bookings seriously by
+    turning up to several consecutive bookings, the defaulter status
+    will be dropped and bookings will one more cause workstation to be
+    freed and reserved.
+
+    "claiming" a booking means logging on to, or already being logged
+    on to, the allocated workstation before the grace period (of seven
+    minutes) passes. This only applies to the first of a consecutive
+    sequence of booked periods.
+
+    So, if you log in after the seven minute mark, or log into a
+    workstation other than the one you booked, then you will be
+    considered to have defaulted on that booking.
+
+    To find out which bookings the booking system thinks you have 
+    defaulted on, use the "past" command. The defaulted bookings are
+    marked with a (D).
+
+#
diff --git a/book/help.man b/book/help.man
new file mode 100644 (file)
index 0000000..2be0a43
--- /dev/null
@@ -0,0 +1,922 @@
+.TH BOOK 1 "18 September 1992" "UNSW"
+.SH NAME
+book \- terminal booking interface.
+.SH SYNOPSIS
+.B book
+.RB [ -h ]
+.RI [ uid | login ]
+.SH DESCRIPTION
+.I Book
+is used to make, inspect, or cancel terminal bookings interactively.
+Terminal bookings are reservations of terminals in particular labs at
+particular times for particular users or classes of users.
+The
+.I book
+command provides users with an interface to the booking management system
+which forms one half of the Terminal Allocation and Booking
+System (TABS) at UNSW. The terminal allocation system
+is not dealt with here, except where allocations affect bookings.
+.PP
+There are a number of simple book commands which
+may be entered interactively in response to the prompt: \fB"BOOK >"\fP.
+Book commands may also be stored in a command file which is read at
+invocation (see Command Files below).
+.SH OPTIONS
+.TP
+.RI [ uid | login ]
+Changes the current user to the new user with the given uid or login name.
+This flag can only be used by root or members of the group wheel,
+and is provided as a means of simulating and fixing simple user problems.
+.PP
+.SH COMMANDS
+.SS General Syntax
+Most book commands follow the general form defined below.
+.nf
+.ta 1i 2i 3i
+\fIcommand =\fB        [\fIcommand_name\fB] {\fItokens\fB} [\fIperiod\fB]\fP
+\fIcommand_name =\fR
+       "book" \fB|\fP "unbook" \fB|\fP "available" \fB|\fP "show" \fB|\fP
+       "refund" \fB|\fP "past" \fB|\fP "change" \fB|\fP
+       "define" \fB|\fP "undefine" \fB|\fP "token" \fB|\fP "echo" \fB|\fP
+       "help" \fB|\fP "quit" \fB|\fP "exit" .
+\fItokens =\fP booking token name.
+\fIperiod =\fP time specification.
+.fi
+Each command is described in more detail under the section
+"COMMAND DESCRIPTIONS".
+Tokens and periods are described in more detail under the section
+"CONCEPTS".
+.SS General Defaults
+If no command is given, but a period or tokens are specified,
+then the help command is assumed.
+.br
+If no period is given, then the next appropriate period from now is
+usually assumed. However, this will depend upon the actual
+command invoked.
+.br
+If no tokens are given, then all available or appropriate tokens
+will usually be assumed. However, this will depend upon the actual
+command invoked.
+.SS Abbreviations
+Most commands may be abbreviated to their first character with the following
+exceptions:
+.br
+       "undefine" - abbreviation: "und" (to distinguish it from "unbook"
+- abbrev: "u").
+.br
+       "exit" - abbreviation: "ex" (to distinguish it from "echo" -
+abbrev: "e").
+.br
+In fact any predefined string may generally be abbreviated to at least
+its unique prefix (see Periods in CONCEPTS below for more predefined strings).
+.SS Case Sensitivity
+No predefined string is case sensitive, including command names. (ie:
+"Book", "book", "BOOK" are equivalent).
+.bp
+.SH COMMAND DESCRIPTIONS
+.TP
+\fBBook { \fItokens\fB } [ \fIperiod\fB ] { \fIoptions\fB }\fR
+.RS
+.TP .6i
+where:
+.nf
+\fIoptions\fP =        [ "consecutive" ] "=" \fIC\fP
+       | "machines" "=" \fIM\fP .
+\fIC\fP | \fIM\fP =    "0".."9" { "0".."9" } .
+.fi
+.TP .6i
+Synopsis:
+Interactively make a booking:
+for the current user;
+within the period(s) specified;
+using one or more of the tokens specified;
+for a sequence of C consecutive periods at a time;
+booking M machines at a time (Class bookings only).
+.TP .6i
+Defaults:
+If no period is specified,
+then stop after the first successful booking.
+.br
+If no tokens are specified,
+then all tokens available to the current user will be used.
+.br
+If not specified, C and M are both 1.
+.TP .6i
+Operation:
+Basically, for every sequence of available slots that meets the
+criteria, the user will be asked to choose from a list of tokens
+that can be used, and then from a list of labs that are available.
+If there is not a number of tokens or labs to choose from, then the
+user will be shown the one token or lab available and asked if they
+want to book that.
+.RS
+.PP
+In general, when prompted, the user may respond with:
+.TP 5
+.I "y"
+Make the booking.
+.TP 5
+.I "n"
+Don't book this period - try the next one.
+.TP 5
+.I "q"
+Quit - Return to the BOOK prompt.
+.PP
+When prompted with a choice of tokens or labs, the user may also
+respond with:
+.TP 5
+.I number
+The corresponding item from the list will be used;
+.TP 5
+.I "y"
+Pick the lab with the most free machines.
+(This response is only valid when choosing labs)
+.PP
+After making a booking, the program will search for the next sequence
+of slots to book within the period specified, and the user is prompted
+once again. If no period was specified, book returns to the command
+prompt.
+.RE
+.TP .6i
+Restrictions:
+.RS
+.TP 5
+\(bu
+The user is not allowed to book
+any slot in the past, the current slot, or the next slot.
+.TP 5
+\(bu
+Consecutive slots will be booked using tokens of the same type.
+To book consecutive slots using different token types, the user must resort
+to using the book command more than once, specifying (or choosing) a different
+token type for each successive slot.
+.TP 5
+\(bu
+The user will not be allowed to book a slot that has already been
+booked for that user, regardless of whether the booking was made by the user
+himself/herself, or booked by the administrator of a class of which
+the user is a member.
+.RE
+.TP .6i
+Note:
+A terminal booking does not imply a terminal allocation.
+This is due to the fact that bookings are based on the number
+of physical terminals in a lab, while allocations are based on the number of
+these terminals which are up (working) and available to the network
+at the time of allocation.
+
+Thus, if the number of bookings exceeds the number of terminals
+actually available at allocation, then these excess bookings cannot be
+honoured. As bookings are allocated in the chronological order in which
+they were made, it is the last few bookings made for a particular lab and slot
+that may not be honoured.
+
+Consequently, if a user books one of the last available slots in a lab, then
+the user will be warned that the booking may not be honoured.
+.TP .6i
+Class Bookings:
+.RS
+If the user is a class, the user is required to specify the number of
+machines to be booked for each period satisfying the specs.
+If this number has not been passed as an option, the user will be
+prompted for it for every period found.
+.PP
+In general, a class booking will be always be made without prompting
+if there are no unknown parameters and no choices to be made.
+Ordinary users however, have to at least confirm that they want to make
+the booking.
+.RE
+.RE
+
+.TP
+\fBUnbook { \fItokens\fB } [ \fIperiod\fB ]\fR
+.RS
+.TP .6i
+Synopsis:
+Interactively cancel all bookings made:
+by the current user;
+using any of the tokens specified;
+for any booking slot within the periods specified;
+.TP .6i
+Defaults:
+If no period is specified,
+then the next booked period made with any of the specified
+tokens will be cancelled.
+.br
+If no tokens are specified,
+then all bookings made by the user in the period(s) specified
+will be cancelled, regardless of what token was used to make
+the booking.
+.TP .6i
+Operation:
+When a booking is found matching the criteria,
+the user is prompted with:
+.RS
+.TP 5
+\(bu
+The booking slot taken;
+.TP 5
+\(bu
+The token used to make the booking.
+.PP
+The user may respond with:
+.TP 5
+.I y
+The booking is cancelled, and the token credited to the user.
+.TP 5
+.I n
+The booking is not cancelled, and the program searches for
+  the next booking meeting the criteria.
+.TP 5
+.I q
+Quit - The program returns to the command prompt "BOOK >"
+.RE
+.TP .6i
+Restrictions:
+The user will not be allowed to cancel a slot on or after the day
+of the slot unless the booking was made less than an hour before the
+cancellation and the booking was not for the current or next slot.
+.RE
+
+.TP
+\fBChange { \fIname\fB } [ \fIperiod\fB ] [[\fR"machine"\fB]\fR "=" \fIN\fB ]\fR
+.RS
+.TP .6i
+where:
+\fIN\fP =  "0".."9" { "0".."9" } .
+.TP .6i
+Synopsis:
+Can only be used by a user who is a class.
+Interactively increase or decrease the number of machines booked
+by the current class;
+using any of the tokens specified;
+for any booking slot within the periods specified;
+to the number of machines specified;
+.TP .6i
+Defaults:
+.RS
+If the total number of machines booked per period is not specified,
+then the user will be prompted for the machine booking changes
+for each booking matching the criteria.
+.PP
+If no period is specified,
+then the next class booking made with any of the specified
+tokens will be cancelled.
+.PP
+If no tokens are specified,
+then all bookings made by the class in the period(s) specified
+will be cancelled, regardless of what token was used to make
+the booking.
+.RE
+.TP .6i
+Operation:
+.RS
+When a booking is found matching the criteria,
+the user is shown:
+.TP 5
+\(bu
+The booking slot taken;
+.TP 5
+\(bu
+The token used to make the booking;
+.TP 5
+\(bu
+The number of machines booked.
+.PP
+If the number of machines is already specified with the "mach ="
+option, then the booking is changed to that number of machines,
+otherwise the user is prompted for the change to be made and the
+user may respond with:
+.TP 5
+\(bu
+A positive or negative number (the change to be made);
+.TP 5
+"n"
+The booking is not changed, and the program searches for
+the next booking meeting the criteria.
+.TP 5
+"q"
+Quit - The program returns to the command prompt "BOOK >"
+.RE
+.TP .6i
+Notes:
+.RS
+.PP
+If the change removes more machine bookings than were made, the
+booking is removed altogether (same as "unbook").
+.PP
+The number of machines may only be increased by as many machines
+as are currently unbooked.
+.PP
+The option "machines" may be shortened to "mach" or left out altogether.
+.RE
+.TP .6i
+Restrictions:
+Only users who are classes may use this command.
+.RE
+
+.TP
+\fBAvailable { \fItokens\fB } [ \fIperiod\fB ] [[\fR"consecutive"\fB]\fR "=" \fIN\fB ]\fR
+.RS
+.TP .6i
+where:
+\fIN\fP =  "0".."9" { "0".."9" } .
+.TP .6i
+Synopsis:
+List those periods which may be booked:
+by the current user;
+within the period(s) specified;
+using one or more of the tokens specified;
+for a sequence of N consecutive slots at a time.
+.TP .6i
+Defaults:
+If no period is specified, then the next available period is found.
+.br
+If no tokens are specified,
+then any of the tokens available to the current user may be used.
+.br
+If not specified, N is 1.
+.TP .6i
+Restrictions:
+.RS
+.TP 5
+\(bu
+Neither the next slot, the current slot, nor any slot in the past,
+is available to the user.
+.TP 5
+\(bu
+Consecutive slots are only available using tokens of the same type.
+.RE
+.TP .6i
+Note:
+This command will not make or cancel any bookings.
+.br
+The option "consecutive" may be shortened to "consec", or left out altogether.
+.RE
+
+.TP
+\fBShow { \fItokens\fB } [ \fIperiod\fB ]\fR
+.RS
+.TP .6i
+Synopsis:
+List the bookings that have been made:
+for the current user;
+within the periods specified;
+using any of the tokens specifed.
+.TP .6i
+Output:
+.RS
+Two kinds of bookings may be shown:
+.TP .6i
+1)
+Bookings made for the user by the user.
+.TP .6i
+2)
+Bookings made for a class that the user is a member of, by the class
+administrator.
+.PP
+Each booking is identified by:
+the lab containing the booked terminal;
+the period of the booking.
+.PP
+If the booking is one made by the user,
+then the token used to make the booking is shown,
+otherwise the class booked is shown in angle brackets instead.
+.RE
+.TP .6i
+Defaults:
+If no period is specified, then all bookings are shown.
+.br
+If no tokens are specified, then bookings made with any token are shown.
+.TP .6i
+Note:
+This command will not make or cancel any bookings.
+.RE
+
+.TP
+\fBPast { \fItokens\fB } [ \fIperiod\fB ]\fR
+.RS
+.TP .6i
+Synopsis:
+.RS
+List the expired bookings:
+for the current user;
+that were booked within the periods specified;
+that used (or tried to use) any of the tokens specifed.
+.PP
+Expired bookings are those that have been cancelled, or those that
+have already been allocated a terminal, ie: any booking that
+is not pending.
+.RE
+.TP .6i
+Output:
+.RS
+Each expired booking is identified by:
+.TP 5
+\(bu
+the lab containing the booked terminal;
+.TP 5
+\(bu
+the token used to make the booking;
+.TP 5
+\(bu
+the period of the booking;
+.TP 5
+\(bu
+the status of the booking.
+.PP
+The booking status will be one of the following:
+.PP
+.nf
+.ta 1i 2i 3i
+Status  Meaning
+Alloc'd        The booking was successfully allocated a terminal.
+Refund The booking was not successfully allocated a terminal, and the token
+       used to make the booking was refunded.
+Cancel The booking was cancelled by the user before it could be allocated
+       a terminal.
+Free   The booking was successfully allocated a terminal, but as the lab was
+       underutilised at the time, the user was refunded the token that he/she
+       had used to make the booking.
+.fi
+.RE
+.TP .6i
+Defaults:
+If no period is specified, then all expired bookings are shown.
+.br
+If no tokens are specified, then expired bookings made with any
+token are shown.
+.RE
+
+.TP
+\fBRefund { \fItokens\fB } [ \fIperiod\fB ]\fR
+.RS
+.TP .6i
+Synopsis:
+.RS
+Refund the tokens spent in making allocated bookings:
+for the current user;
+that were booked within the periods specified;
+that used any of the tokens specifed.
+.PP
+Allocated bookings are those that have been successfully allocated
+a terminal in the past, and for which one token has been spent.
+.RE
+.TP .6i
+Output:
+.RS
+When an allocated booking is found meeting the criteria, the user
+is prompted with:
+.TP 5
+\(bu
+the lab containing the booked terminal;
+.TP 5
+\(bu
+the token used to make the booking;
+.TP 5
+\(bu
+the period of the booking;
+.TP 5
+\(bu
+the status of the booking (which will always be Alloc'd);
+.TP 5
+\(bu
+the prompt string: "refund ?".
+.PP
+The user may respond with:
+.TP 5
+"y"
+The token used to make the booking is refunded to the user,
+and the booking status turned into "Refund".
+.TP 5
+"n"
+The token is not refunded, and the program searches for
+the next expired booking meeting the criteria.
+.TP 5
+"q"
+Quit - The program returns to the command prompt "BOOK >"
+.RE
+.TP .6i
+Restrictions:
+This command may only be used by a privilaged user.
+.RE
+
+.TP
+\fBDefine { \fIname\fB } [ \fIperiod\fB ]\fR
+.RS
+.TP .6i
+Synopsis:
+Add a new predefined period (predef) to the existing list of predefs.
+The new period may subsequently be refered to in a period specification
+by any of the names given.
+.TP .6i
+Defaults:
+If no period or names are given, then the name of all predefs are printed.
+.br
+If no new predef names are given, then the period (or predef) is
+displayed as defined, not as instanciated.
+eg: compare "define ah" with "help ah".
+.br
+(for the difference between period definition and instanciation,
+see Periods in CONCEPTS below).
+.TP .6i
+Notes:
+The period expression passed to define may of course include any predefs
+already defined. (eg: ah (after hours) is defined by reference to bh (business
+hours) by the command: "define ah not bh")
+.RS
+.PP
+To change an existing predef definition, the user must first undefine
+the predef, and then redefine it.
+.PP
+If the name given to a period is the same as a token name, the user
+will not be able to explicitly refer to that token after the define.
+.RE
+.TP .6i
+See Also:
+For more information on predefs, see Predefined Periods in CONCEPTS below.
+.RE
+
+.TP
+\fBUndefine \fIquotename\fB { \fIquotename\fB } \fR
+.RS
+.TP .6i
+where:
+\fIquotename\fP = `" name "'
+.br
+ie: predef name to be undefined is in lower case and enclosed in double quotes.
+.TP .6i
+Synopsis:
+Remove from the current set of predefined periods (predefs), the
+predefs with the names listed.
+.TP .6i
+Restrictions:
+Cannot remove excluded predefs ("exclude").
+.TP .6i
+See Also:
+For more information on predefs, see Predefined Periods in CONCEPTS below.
+.RE
+
+.TP
+\fBEcho { `" \fIstring\fB "' }
+.RS
+.TP .6i
+Synopsis:
+Print the string given within double quotes on standard output.
+.TP .6i
+Notes:
+The string MUST start with and be terminated by a double quote.
+.RS
+.PP
+The double quotes are not printed.
+.PP
+The string may extend over more than one line in which case the
+intervening newlines are included as part of the string.
+.PP
+Consecutive quoted strings are concatenated.
+.PP
+This command is particularly useful in command files
+.RE
+.TP .6i
+See Also:
+For information on command files, see Command Files in CONCEPTS below.
+.RE
+
+.TP
+\fBHelp [ \fIcommand\fP  |  \fItopic\fP ] { \fItokens\fP } [ \fIperiod\fP ]
+.RS
+.TP .6i
+where:
+.nf
+\fIcommand\fP =        "book" | "unbook" | "available" | "show" | "define"
+       | "undefine" | "token"  | "echo" | "help" | "quit" | "exit" .
+\fPtopic\fP =  "period" | "predef" | "syntax" | "cfile" | "command" .
+.fi
+.TP .6i
+Synopsis:
+Displays help information on the command, topic, period, or tokens
+specified.
+.TP .6i
+Defaults:
+If none of the above are specified,
+then an overall introduction to the book interface is displayed.
+.br
+If a period specification is given,
+then the actual time periods mapped by the specification are displayed as
+instanciated. (For the definition of period instanciation,
+see Periods in CONCEPTS below).
+.br
+If the name of a token is given,
+then the number of these tokens assigned to the user is displayed.
+.TP .6i
+Note:
+Since the help command is the default command, the user will usually
+not have to type "help" (or "h") at all, just the arguements to the
+help command on their own. The only exception to this is when the
+arguement to the help command is itself the name of a command (obviously!).
+.RE
+
+.TP
+\fBToken ( [\fP "all" \fB ] | { \fItokens\fB } )
+.RS
+.TP .6i
+Synopsis:
+Show the number of tokens currently assigned to the user.
+.TP .6i
+Defaults:
+If no tokens are named,
+then list all unexpired tokens currently assigned to the user.
+If "all" is named, show all tokens regardless of expiry date
+.TP .6i
+Notes:
+.RS
+Tokens are assigned to users of the booking system, and used by them to
+make terminal bookings. Users refer to tokens by name, and may pass
+these token names to various book interface commands.
+.PP
+Tokens can be used to book a restricted set of periods only.
+Tokens of the same name have the same set of restricted periods.
+.PP
+Token names cannot be abbreviated (unlike commands, which can be).
+.RE
+.TP .6i
+See Also:
+For more details on what each command does
+with the list of token names passed to it,
+refer to the specific command entry.
+.RE
+
+.TP
+\fBQuit | Exit | <Control-D>\fP
+.RS
+.TP .6i
+Synopsis:
+Exit from the book program.
+.RE
+
+.SH CONCEPTS
+.TP
+.B Periods
+The simplest period is that defined by a start time and an end time.
+(The end time has to be later than the start time, or the period does not
+exist). A period expression involves the union and/or intersection of
+such simple periods. Such expressions usually lead to a series of
+non-intersecting periods, each starting after the previous has ended.
+Most book commands will accept a period expression which allows the
+users to specify the specific periods that they would like to book, inspect, or
+cancel. The Syntax of such an expression is as follows:
+.RS
+.TP .6i
+Syntax:
+.nf
+\fIperiod\fP = \fIintersection\fP .
+\fIintersection\fP =   \fIunion\fP {\fIunion\fP} .
+\fIunion\fP =  \fIrange\fP {"," \fIrange\fP} .
+\fIrange\fP =  ["not" | "~"] \fIstarttime\fP {"-" \fIendtime\fP} .
+\fIendtime\fP =        \fIperiodtime\fP .
+\fIstarttime\fP =      \fIperiodtime\fP .
+\fIperiodtime\fP =     "(" \fIintersection\fP ")"
+       | \fItime\fP | \fIday\fP | \fIdate\fP | \fImonth\fP | \fIpredef\fP .
+\fItime\fP =   \fIhour\fP [":" \fImin\fP] ["am" | "pm"] .
+\fIdate\fP =   \fIdayofmonth\fP "/" \fImonthofyear\fP ["/" \fIyear\fP] .
+\fIday\fP =    "monday"  | ... | "sunday" | "today" | "tomorrow" .
+\fImonth\fP =  "january" | ... | "december" .
+\fIpredef\fP = \fIpredef_id\fP ["." \fIweek\fP]
+\fIpredef_id\fP =      The name of one of the predefined periods (see Predefs below).
+\fIhour\fP = \fImin\fP = \fIweek\fP = \fIdayofmonth\fP = \fImonthofyear\fP = \fIyear\fP = "0".."9"{"0".."9"}
+.fi
+.TP .6i
+Notes:
+.RS
+.TP 5
+\(bu
+All of the predefined strings (eg: "monday", "february", ...), may be
+abbreviated to their shortest unique prefix (eg: "mo", "fe", .).
+.TP 5
+\(bu
+Predefined strings are not case sensitive (eg: "Mon", "mon", "MON" are
+equivalent).
+.TP 5
+\(bu
+Users may experiment with period specifications using the \fBhelp\fP
+and \fBdefine\fP commands.
+.RS
+.PP
+The \fBhelp\fP command will list the actual
+(instanciated) periods matched by a period expression. These periods are
+the ones mapped to an actual date, and are the periods that are passed
+to all commands except \fBdefine\fP.
+.PP
+The \fBdefine\fP command
+will list the evaluation of the period expression before instanciation
+takes place. (eg: compare: "define 10-12 Tue" with "help 10-12 Tue" or
+"define evening" with "help evening").
+.RE
+.TP 5
+\(bu
+The special predefined period \fI"excluded"\fP, defines those periods that
+will never be instanciated (see Predefined Periods below).
+.RE
+.TP .6i
+Defaults:
+A booking period currently has a minimum duration of half an hour.
+If a user specifies a start time for the period, but not an end time
+then the end time will depend on the type of start time specified.
+.nf
+Start time     Default end time
+--------------------------------------------
+Time of day    time of day + 30 minutes
+Day of Week    end of that day of week
+Predef.week    end of that predefined week.
+
+eg:
+Start time     Default end time
+--------------------------------------------
+10am   10:29:59
+12:30 tue      12:59:59 Tue
+Tue    23:59:59 Tue
+s1.2   Sunday of second week of session 1
+.fi
+.TP .6i
+Period Examples:
+.nf
+10am   The next (10:00 to 10:29) period.
+       Today if the current time is before 10am, otherwise Tommorrow.
+10-12  The next (10:00 to 11:59) period.
+       Today or tommorrow depending on current time.
+10-12,2pm Tue  The next ((10:00 to 11:59, and 14:00 to 14:29) of Tuesday)
+       If today is Tuesday
+       and the current time is later than the specified period,
+       then the times will refer to next Tuesday's periods,
+10 tue-thur    next 10:00 to 10:29 on Tuesday, Wednesday, and Thursday
+2pm tue october        14:00 to 14:29 on all Tuesdays in October of this year
+Thur,Tue s1.4  Tuesday and Thursday of the fourth week of the predefined
+       period s1 (s1 will normally be defined to be session 1 of the current
+       year (see Predefined Periods below).
+(11,2pm-3pm wed),(10:30 thur) s1.2-s1.4
+       The various periods on Wednesdays and Thursdays
+       of the second week of session 1
+       to the fourth week (inclusive) of session 1
+.fi
+.RE
+
+.TP
+.B Predefined Periods (Predef)
+.RS
+.TP .6i
+Synopsis:
+A predefined period or predef is a period that has been given a name.
+This period may be used in other period expressions by refering to its
+name (in full) in the period expression.
+.RS
+.PP
+A particular week of the predefined period may be refered to by
+.br
+       "name.week"
+.br
+where "name" is the name of the predefined period, and "week" is the number
+of the week desired.
+.PP
+A new period is added to the list of existing predefs by using the
+command "define", and removed from the list using "undefine".
+.RE
+.TP .6i
+Special Predefs:
+The predef named "excluded" is special in a number of ways:
+.RS
+.TP 5
+(1)
+This predef defines those periods which will never be instanciated.
+These periods usually refer to public holidays when students will not
+generally be given access to the workstations.
+.TP 5
+(2)
+There may be more than one period named "excluded". These
+periods are merged into the one predef.
+.TP 5
+(3)
+Once a period has been excluded, it cannot be unexcluded.
+ie: excluded periods cannot be undefined.
+.RE
+.TP .6i
+Notes:
+.RS
+Periods start from week 1 (week 0 and week 1 are synonamous).
+.br
+To see what predefs have been defined, type: "define"
+.br
+To see what a predef has been defined as, type: "define" predef_name.
+.br
+To see what periods have been excluded, type: "define excluded"
+.PP
+While predefs may be defined interactively, they are more usually
+defined in command files (see Command Files below).
+.RE
+.RE
+
+.TP
+.B Command Files
+.RS
+.TP .6i
+Synopsis:
+A book interface command file contains booking commands that the book
+interface reads and executes before accepting interactive commands from
+the user.
+These command files will usually contain non-interactive booking
+commands - most commonly the "define" command.
+.TP .6i
+Files:
+There are two command files that the book interface will read:
+.RS
+.TP
+.B /usr/local/lib/book/periods
+System command file which define
+general periods and predefs that are useful
+for the current year.
+.TP
+.B $HOME/.periods
+User's command file. This file is read after the system file.
+.RE
+.TP .6i
+File format:
+Commands are entered as they would be interactively.
+.br
+Commands may be extended onto the next line if the line does not
+contain a comment, and the line ends with a "\\".
+.br
+Comments start with a "#" and extend to the end of line.
+.br
+Blank lines are ignored.
+.TP .6i
+Notes:
+The user's command file is primarily intended to contain those defines
+of predefs that the user will find personally useful.
+For example, the user may wish to add a define command to their
+".period" file which defines a predef that maps all the user's
+weekly free timetable periods.
+.RS
+.TP 5
+eg:
+define myfree (10-12,13-14 Mon), \\
+.br
+         (3pm-5pm Tue-Wed), (thur afternoon)
+.PP
+This predef could later be used (for instance) to find all available
+free periods on Tuesday of the fourth week of session 1 using:
+.br
+       available myfree tue s1.4
+.PP
+The user may also wish to print a list of tokens, bookings, and
+available free periods just before getting the interactive prompt from
+the book interface, by putting the following few lines at the end
+of their ".period" file.
+.TP 5
+eg:
+.nf
+token  # this lists all available tokens
+echo "    Current Bookings"
+show   # this shows all current bookings
+echo "    Free periods available today:"
+avail myfree today     # this lists all my free periods available today
+       # (assuming predef "myfree" has already been defined)
+.fi
+.RE
+.RE
+
+.SH SYNTAX CONVENTIONS
+Command and Period syntax are described in this document using the following (
+.I EBNF
+) conventions:
+.PP
+.nf
+Convention     Meaning
+____________________________________________________________
+"abc"  the literal string "abc" (entered without the quotes)
+\fIabc\fP      the construct \fIabc\fP, whose value is given by the assignment of
+       literals and/or constructs to it by the assignment statement.
+.fi
+.PP
+In what follows, \fBX\fP,\fBY\fP,\fBZ\fP refer to expressions of literals and/or constructs.
+.nf
+Expression     Meaning
+( \fBX\fP )    The same as \fBX\fP.
+[ \fBX\fP ]    \fBX\fP may occur once or not at all. (\fBX\fP is optional).
+{ \fBX\fP }    \fBX\fP may occur zero or more times.
+\fBX\fP | \fBY\fP      An occurence of \fBX\fP or \fBY\fP (but not both).
+\fBX\fP \fBY\fP        \fBX\fP is followed by \fBY\fP, optionally separated by blanks or tabs.
+
+\fIabc\fP = \fBX\fP .  The assignment statement.
+       The construct \fIabc\fP has the value of the expression \fBX\fP.
+.fi
+.TP
+eg:
+The syntax of the book command is: "book" { \fItokens\fP } [ \fIperiod\fP ] .
+This means that you type the literal string "book" followed
+by zero or more \fItoken\fP constructs, and followed optionally
+by the \fIperiod\fP construct.
+.SH FILES
+/usr/local/lib/book/periods    System command file.
+.br
+$HOME/.periods         User's command file.
+.SH BUGS
+Probably
diff --git a/book/periods b/book/periods
new file mode 100644 (file)
index 0000000..07c7d35
--- /dev/null
@@ -0,0 +1,52 @@
+# Book interface system initialisation file.
+# Used to define generally useful periods.
+
+# General (relative) periods:
+define lunch           13:00 - 14:00           # Lunchtime
+define morning         8:00 - 12:00            # BH (am)
+define afternoon       12:00 - 18:00           # BH (pm)
+define evening         18:00 - 20:00           # Evening Hours
+define night           20:00 - 22:00           # Night hours
+define wend            Sat, Sun                # Week Ends
+define wdays           not wend                # Week Days
+define bh              8:00 - 18:00 Mon - Fri  # Business Hours
+define ah              not bh                  # Not Business hours
+
+# Absolute Periods defined for 1992:
+# First Session:
+define wk s1            1 Mar - 11 Apr, \
+                       19 Apr - 13 Jun         # Session 1
+define br s1_br        12 Apr - 18 Apr         # Break - Sess 1
+define myb             14 Jun - 25 Jul         # Mid Year Break
+
+# Second Session:
+define s2              26 Jul - 26 Sep, \
+                        4 Oct -  7 Nov         # Session 2
+define s2_br           27 Sep -  3 Oct         # Break - Sess 2
+define end yrend       8  Nov - 24 Dec         # End of Year
+
+define normal_close    0-8:30, 21:30-24        # Times labs are closed normally
+define wend_close      0-9:30, 16:30-24        # Times labs are closed on weekends
+define holiday_close   0-8:30, 16:30-24        # Times labs are closed on holidays
+
+# Excluded periods for 1992.
+# These periods cannot be booked using the book interface.
+# The second predef name is not necessary, and given for reference only.
+
+# specific dates to be excluded:
+# define       exclude excl_anzac      25 Apr                  # Anzac Day
+# define       exclude excl_labour_day 5 Oct                   # Labour Day
+define exclude excl_xmas       25 Dec - 31 Dec         # Christmas Weekend
+define exclude excl_qbday      holiday_close 14 Jun    # Queen's Birthday
+define exclude excl_eastmon    holiday_close 12 Apr    # Easter Monday
+define exclude excl_goodfri    (6pm 8 Apr) - (11:30 10 Apr)    # Good Friday power down
+
+# general exclusion times and dates
+define exclude normal_close            # labs generally not bookable
+define exclude wend_close wend         # weekend exclusion
+define exclude holiday_close myb       # restricted opening hours in myb
+# define       exclude normal_close wdays
+# define       exclude wend_close wend (not wk.12)
+# define       exclude 0-9:30,22:00-24 wend wk.12      # special closing this weekend
+# define       exclude holiday_close wk.1,wk.2 # restricted opening for first few weeks
+
diff --git a/book/periods.spec b/book/periods.spec
new file mode 100644 (file)
index 0000000..af45f39
--- /dev/null
@@ -0,0 +1,33 @@
+expr   = isect
+
+isect  = union {union}
+
+union  = range {',' range}
+
+range  = period {'-' period}           # types must be the same
+
+period = '(' expr ')
+       | time
+       | day
+       | week
+       | date
+
+time   = num [':' num] ["am" | "pm"]   # type = repeated;daily
+
+day    = "monday" | ... | "friday"     # type = repeated;weekly
+
+week   = periodid ['.' num]            # type = absolute
+
+date   = num '/' num ['/' num]         # type = absolute
+       | num month [num]
+
+month  = "january" | ... | "december"
+
+num    = '0'..'9' {'0'..'9'}
+
+
+neilb thoughts 23may96
+
+make "time" become a moment - 10:00 -> 10:00-9:59 (!)
+ranges are then always a-b == first of a to last of b
+not 10:00 ??? want to exclude that second, and (anything which contains it)
\ No newline at end of file
diff --git a/book/tkbook.tcl b/book/tkbook.tcl
new file mode 100755 (executable)
index 0000000..9876ee1
--- /dev/null
@@ -0,0 +1,1385 @@
+#!/usr/local/bin/wish -file
+
+#
+# Things to do:
+#
+# o read list of pending bookings and allow access to it  DONE
+# o allow bookings with multiple tokens and choice of how many of which DONE
+# o make periods page resizable - trace window too.
+# o behave sensibly if {un,}book command is entered
+# o handle prompting in user-entered commands
+# o allow query of a particular slot DONE
+
+# tcl/tk front end to "book" program
+#
+# we present a matrix of days, at least one week,
+# mon to sun. each as a button with a date in it
+# e.g.
+#    mon  tue  wed  thu  fri  sat  sun
+#              22   23   24   25   26
+#    27   28   29   30   31    1    2
+#
+# any day which has bookings might get highlighted
+# Below this is a matrix of periods by labs for the selected
+# day. There is always one selected day, today at first.
+# e.g.
+#         ball     club    ring    oboe
+#  8:30
+#  9:00
+#  9:30
+# 10:00
+# ....
+# 21:00
+# 21:30
+#
+# each timeslot will display number of bookable workstations, plus
+# a 'C' if there is a class booking in the lab
+# Booked slots show "Booked"
+# slots can be selected with the mouse. any column can be selected
+# selected slots may be "booked" or "unbooked" with a button.
+#
+# there is a pane which lists current bookings in the same format
+# that book lists them:
+#    lab   token   fr:om - to:.. day month
+# double clicking on a booking with cause that booking to be selected in the
+# calendar window.
+#
+# There is a window which traces the output of "book"
+# there is also (for testing at least) an input window for typing commands to book.
+
+option add *Dialog.msg.font fixed 
+frame .book
+pack .book
+
+frame .book.title
+label .book.title.name -text ""
+pack .book.title -fill x
+button .book.title.dismiss -command exit -text "Exit"
+button .book.title.help -command DisplayHelp -text "Help"
+pack .book.title.dismiss -side right
+pack .book.title.help -side right
+label .book.title.tlabel -text "Token:"
+pack .book.title.tlabel -side left
+place .book.title.name -relx 0.5 -rely 0.5 -anchor cent
+#pack .book.title.name -side top
+
+frame .book.cal -borderwidth 5 -relief groove
+frame .book.periods -borderwidth 5 -relief groove
+frame .book.bookings -borderwidth 5 -relief groove
+frame .book.io
+
+pack .book.cal -side top
+pack .book.periods -side top 
+
+label .book.bookings.label -text "Bookings"
+pack .book.bookings.label -side top
+listbox .book.bookings.list -relief sunken -yscrollcommand ".book.bookings.scroll set" -height 5 -font fixed -selectmode none -width 60
+bind .book.bookings.list <1>  "findbooking  \[.book.bookings.list nearest \[expr \[winfo pointery .book.bookings.list] -  \[winfo rooty .book.bookings.list]]] "
+bind .book.bookings.list <2>  "findbooking  \[.book.bookings.list nearest \[expr \[winfo pointery .book.bookings.list] -  \[winfo rooty .book.bookings.list]]] "
+scrollbar .book.bookings.scroll -relief sunken -command ".book.bookings.list yview" -width 10
+pack .book.bookings.list -side left 
+pack .book.bookings.scroll -side right -fill y
+pack .book.bookings -side top 
+
+
+entry .book.io.cmd 
+bind .book.io.cmd <Return> { send_book_command [ .book.io.cmd get ] }
+frame .book.io.trace 
+text .book.io.trace.text -wrap none -width 66 -height 5 -state disabled \
+       -yscrollcommand ".book.io.trace.scroll set"
+scrollbar .book.io.trace.scroll -relief sunken -command ".book.io.trace.text yview" -width 10
+pack .book.io.trace.text -side left
+pack .book.io.trace.scroll -side right -fill y
+
+label .book.io.prompt -text "Book> "
+pack .book.io.trace -side bottom
+pack .book.io.cmd -side right -fill x -expand yes
+pack .book.io.prompt -side left
+pack .book.io  -side top
+
+
+proc send_book_command { text } {
+    global file
+    .book.io.trace.text configure -state normal
+    .book.io.trace.text insert  end "BOOK > "
+    .book.io.trace.text insert  end [ .book.io.cmd get ]
+    .book.io.trace.text insert end "\n"
+    .book.io.cmd delete 0 end
+    .book.io.trace.text yview -pickplace end
+    .book.io.trace.text configure -state disabled
+    update
+    puts $file $text
+    flush $file
+    find_prompt ignore 1
+}
+
+proc start_book { { who {}} } {
+    global file
+    .book.title.name configure -text "$who"
+    set file [ open "|/usr/local/bin/book $who 2>@stdout" r+ ]
+}
+
+proc take_line { proc trace line } {
+ if { $trace } {
+  .book.io.trace.text configure -state normal
+  .book.io.trace.text insert  end $line
+  .book.io.trace.text insert end "\n"
+  .book.io.cmd delete 0 end
+  .book.io.trace.text yview -pickplace end
+  .book.io.trace.text configure -state disabled
+ }
+ if { $proc != "ignore" } {
+   $proc $line
+ }
+}
+
+proc find_prompt_orig { {proc ignore} {trace 0 } } {
+ # read lines from  "file" until BOOK > appears
+ global file
+ set extra ""
+ while { "$extra" != "BOOK > "  } {
+   set more [ read $file 10 ] 
+   while { [ string match "*\n*" $more ]  } {
+      regexp "(\[^\n]*)\n(.*)" $more all head tail
+      set extra "$extra$head"
+      set more $tail
+      take_line $proc $trace $extra
+      set extra ""
+   }
+   set extra "$extra$more"
+ }
+}
+
+proc find_prompt { {proc ignore} {trace 0 } } {
+    # read lines from  "file" until BOOK > appears
+    global file
+    set extra ""
+    set found 0
+    while { ! $found  } {
+       if { [gets $file more] < 0 } {
+           book_error "Book process has died. tkbook will now terminate"
+           exit
+       }
+#      puts "line is $more"
+       if { $more == "BOOK > " } { set found 1 } else {
+           take_line $proc $trace $more
+       }
+       set extra ""
+    }
+    #puts "done"
+}
+
+
+
+proc cal_init {} {
+global monthlen months days to_day to_month cal_day cal_month today
+# code for the mini-calendar
+# the calendar shows two weeks and can step forward or backward
+# a week
+# We keep to_day and to_month to know when we are
+# We keep cal_day and cal_month to show start of calendar
+# cal_day/month must never be a week before today
+# we display month as e.g. may or may/june
+
+# find start of this week
+set cal_day $to_day
+set cal_month $to_month
+set diff 0
+while { [ lindex $days $diff ] != $today } {
+  set diff [ expr $diff+1 ]
+  set  cal_day [ expr $cal_day - 1 ]
+  if { $cal_day < 0 } {
+    set cal_day [ expr $cal_day + [ lindex $monthlen $cal_month ] ]
+    set cal_month [ expr $cal_month - 1 ]
+  }
+}
+}
+
+proc cal_create {} {
+global monthlen months days to_day to_month cal_day cal_month 
+ # create the calendar widgits - leave them blank
+ # .book.cal is a frame to hold it
+ # make .book.cal.month fo month name
+ # make .book.cal.{mon..sun}{days,week1,week2} labels buttons, buttons
+ # make .book.cal.back and .fore for moving back and fore
+
+ set monthlen { 31 31 29 31 30 31 30 31 31 30 31 30 31 }
+ set months { January February March April May June July August September October November December }
+ set days { Mon Tue Wed Thu Fri Sat Sun }
+
+ label .book.cal.monthname
+ pack .book.cal.monthname -side top
+ frame .book.cal.back
+ label .book.cal.back.days -text "   " -width 3 -padx 0 -pady 0
+ button .book.cal.back.week1 -state disabled -width 2 -padx 0 -pady 0 -relief flat
+ button .book.cal.back.week2 -state disabled -width 2 -padx 0 -pady 0 -relief flat
+ pack .book.cal.back -side left
+ pack .book.cal.back.days .book.cal.back.week1 .book.cal.back.week2 -side top
+ foreach day $days {
+   frame .book.cal.d$day
+   label .book.cal.d$day.days -text $day -width 3 -padx 0 -pady 0
+   button .book.cal.d$day.week1 -state disabled -width 2 -justify right -padx 0 -pady 0
+   button .book.cal.d$day.week2 -state disabled -width 2 -justify right -padx 0 -pady 0
+   pack .book.cal.d$day.days -side top
+   pack .book.cal.d$day.week1 -side top
+   pack .book.cal.d$day.week2 -side top
+   pack .book.cal.d$day -side left
+ }
+ frame .book.cal.fore
+ label .book.cal.fore.days -text "   " -width 3 -padx 0 -pady 0
+ button .book.cal.fore.week1 -state disabled -width 2 -padx 0 -pady 0 -relief flat
+ button .book.cal.fore.week2 -state disabled -width 2 -padx 0 -pady 0 -relief flat
+ pack .book.cal.fore -side top
+ pack .book.cal.fore.days .book.cal.fore.week1 .book.cal.fore.week2 -side top
+}
+
+proc cal_fill {} {
+    global monthlen months days to_day to_month cal_day cal_month  max_month max_day
+    global the_day the_month the_date
+    # set the dates and month name in the calendar
+    # any button before today is inactive, others are normal
+    set aday $cal_day
+    set amonth $cal_month
+    if  { $amonth < $to_month || ( $amonth == $to_month && $aday <= $to_day) } {
+       .book.cal.back.week1 configure -state disabled -relief flat -text ""
+    } else {
+       .book.cal.back.week1 configure -state normal -relief raised -text "<<" -command backweek
+    }
+    set month1  [ lindex $months $amonth ]
+    foreach week { week1 week2 } {
+       foreach day $days {
+           if { $amonth < $to_month || ( $amonth == $to_month && $aday < $to_day)  ||  $amonth > $max_month || ($amonth == $max_month && $aday > $max_day) } {
+               # before now or after end
+               .book.cal.d$day.$week configure -text "" -state disabled -relief flat
+           } else {
+               # now or later
+               .book.cal.d$day.$week configure -text $aday -state normal -relief raised -command "book_select $day $aday $amonth "
+               if { $the_date == $aday && $the_month == $amonth } {
+                   .book.cal.d$day.$week configure -relief ridge
+               }
+           }
+           if { $week == "week2" && $day == "Sun" } {
+               set month2 [ lindex $months $amonth ]
+           }
+           
+           set aday [ expr $aday + 1 ]
+           if { $aday > [ lindex $monthlen [ expr $amonth + 1]] } {
+               set amonth [ expr $amonth + 1 ]
+               set aday 1
+           }
+       }
+    }
+    if { $month1 == $month2 } {
+       .book.cal.monthname configure -text $month1
+    } else {
+       .book.cal.monthname configure -text $month1/$month2
+    }
+    
+    if { $amonth > $max_month || ($amonth == $max_month && $aday > $max_day) } {
+       .book.cal.fore.week2 configure -state disabled -relief flat -text ""
+    } else {
+       .book.cal.fore.week2 configure -state normal -relief raised -text ">>" -command foreweek
+    }
+}
+
+proc backweek {} {
+   global cal_day cal_month monthlen
+  set  cal_day [ expr $cal_day - 7 ]
+  if { $cal_day < 0 } {
+    set cal_day [ expr $cal_day + [ lindex $monthlen $cal_month ] ]
+    set cal_month [ expr $cal_month - 1 ]
+  }
+  cal_fill
+}
+
+proc foreweek {} {
+   global cal_day cal_month monthlen
+   set cal_day [ expr $cal_day + 7 ]
+   if { $cal_day > [ lindex $monthlen [ expr $cal_month + 1]] } {
+      set cal_month [ expr $cal_month + 1 ]
+      set cal_day [ expr $cal_day - [ lindex $monthlen [ expr $cal_month ]]]
+    }
+   cal_fill
+}
+set getid NONE
+proc book_select { day date month } {
+    global the_day the_date the_month
+    global firstperiod lastperiod 
+    global getid
+    global nextperiod finalperiod
+    global labs
+    global av avtype
+
+    if { $the_date != $date || $the_month != $month } {
+       cursorBusy 0
+       set the_day $day
+       set the_date $date
+       set the_month $month
+
+        if { [array exists av] } {
+            unset av
+        }
+        if { [array exists avtype] } {
+            unset avtype
+        }
+       
+       cal_fill
+       
+       if { $getid != "NONE" } { after cancel $getid }
+       set oldlabs $labs
+       get_labs
+       get_times
+#      update # only re-enable this is events (e.g. ymoveall) can cope that not all in $labs have frames created.
+       foreach lab $oldlabs { destroy .book.periods.l$lab }
+       if [winfo exists .book.periods.message] { destroy .book.periods.message}
+       
+       make_periods redo
+       
+       set nextperiod $firstperiod
+       set finalperiod $lastperiod
+       set getid [after idle getnext]
+       # cursorUnset
+    }
+}
+
+proc getnext {} {
+    global getid finalperiod nextperiod usecounts
+    set getid NONE
+    set oldusecounts $usecounts
+    get1available $nextperiod
+    if { "$oldusecounts" == "$usecounts"  } {
+       incr nextperiod
+    }
+    if { $nextperiod <= $finalperiod } {
+       set getid [ after idle getnext ]
+    } else {
+       cursorUnset
+    }
+    
+}
+
+
+# now for the period table
+# we make this out of listboxen - one listbox for each lab
+# there is also a scroll bar.
+# the scroll bar will effect ALL list boxes. possibly do this via a cycle of control
+# scroll bar modifies first which modifies second ... which modifies scrollbar??
+# no, that creates multiple loops which are hard to break.
+# instead get scrollbar to control time.list
+# time.list then sets all others, and other when moved, reset to match time.list
+# There is also a list box for period times - 08:30 09:00 ...
+# each lab listbox will contain a number of bookable workstations for that time
+# I want the updating of these to be a background task. Initialy fill with
+# blanks.
+proc make_periods { redo } {
+ # expect to have a list of lab names in "labs"
+ # and a range of periods in "firstperiod" and "lastperiod" (0..47)
+ # need a frame for each lab to hold lab name and listbox
+    global firstperiod lastperiod labs
+    global the_day the_date the_month months
+    global av
+    if { $redo != "redo" } {
+       frame .book.periods.title
+       pack .book.periods.title -side top -fill x
+       if { $the_day == "unknown" } {
+           label .book.periods.title.date -text ""
+       } else {
+           label .book.periods.title.date -text "$the_day $the_date [lindex $months $the_month]"
+       }
+       button .book.periods.title.book -text " Book " -command dobook
+       button .book.periods.title.unbook -text "Unbook" -command dounbook
+       pack .book.periods.title.book -side left
+       pack .book.periods.title.unbook -side right
+       pack .book.periods.title.date -side bottom
+       
+       frame .book.periods.time
+       label .book.periods.time.label -text "Time"
+       listbox .book.periods.time.list -width 5  -selectmode none -font fixed
+       pack .book.periods.time.label -side top
+       pack .book.periods.time.list -side right
+       pack .book.periods.time -side left
+    } else {
+       .book.periods.time.list delete 0 end
+    }
+
+    if { $the_day == "unknown" } {
+       .book.periods.title.date configure -text ""
+    } else {
+       .book.periods.title.date configure  -text "$the_day $the_date [lindex $months $the_month]"
+    }
+    if { $firstperiod == "unknown" } { set firstp 0 ; set lastp 47 ; } else {
+       set firstp $firstperiod ; set lastp $lastperiod  
+    }
+
+
+    for { set p $firstp } {$p <= $lastp } { set p [ expr $p+1 ] } {
+       .book.periods.time.list insert end [ format "%02d:%02d" [ expr $p/2] [expr 30*($p%2)] ]
+    }
+
+
+    if { $redo != "redo" } {
+       frame .book.periods.scroll
+       label .book.periods.scroll.label -text ""
+       scrollbar .book.periods.scroll.bar -orient vertical 
+       pack .book.periods.scroll.label -side top
+       pack .book.periods.scroll.bar -side right -fill y
+       pack .book.periods.scroll -side right -fill y
+       
+    }
+    # now fill in the labs...
+
+    if { $firstp > $lastp } {
+       message .book.periods.message -text "You have no tokens which can be used to book on this day." -aspect 250
+       pack .book.periods.message -side left -expand yes
+    } else {
+       foreach lab $labs {
+           frame .book.periods.l$lab
+           pack .book.periods.l$lab -side left
+           label .book.periods.l$lab.label -text "$lab"
+           listbox .book.periods.l$lab.list -width 8 -selectmode extended -font fixed
+           bind .book.periods.l$lab.list <1> "explain $lab  \[.book.periods.l$lab.list nearest \[expr \[winfo pointery .book.periods.l$lab.list] -  \[winfo rooty .book.periods.l$lab.list]]] "
+           bind .book.periods.l$lab.list <2> "explain $lab  \[.book.periods.l$lab.list nearest \[expr \[winfo pointery .book.periods.l$lab.list] -  \[winfo rooty .book.periods.l$lab.list]]] "
+           pack .book.periods.l$lab.label -side top
+           pack .book.periods.l$lab.list
+           pack .book.periods.l$lab -side left
+           for { set p $firstp} {$p <= $lastp } { incr p } {
+               .book.periods.l$lab.list insert end " ???"
+           }
+           .book.periods.l$lab.list configure -yscrollcommand "ynomove .book.periods.l$lab.list"
+       }
+    }
+    .book.periods.time.list configure -yscrollcommand "ymoveall"
+    .book.periods.time.list configure -xscrollcommand "xnomove .book.periods.time.list"
+    .book.periods.scroll.bar  configure -command " .book.periods.time.list yview "
+    
+
+}
+
+proc explain { lab row } {
+    global firstperiod  av labs
+    set period [expr $row + $firstperiod ]
+    set time [format "%2d:%02d" [expr $period / 2] [expr ($period % 2)*30 ] ]
+    set hdr "Explanation of period $time in lab $lab"
+    set state [ array get av $period.$lab ]
+
+    set ambooked NO
+    foreach lb $labs {
+       if { [lindex $av($period.$lb) 1] != {} } {
+           set ambooked $lb
+       }   
+    }
+    if [llength $state] {
+       set state [lindex $state 1]
+       if {[lindex $state 1] != {} } {
+           #booked with the given token
+           set msg "You are booked with token \"[lindex $state 1]\""
+       } elseif { $ambooked != "NO"} {
+           set msg "You are already booked at this time in lab \"$ambooked\""
+       } elseif { [lindex $state 0] != {}} {
+           # bookable, maybe there is a class booking
+           set msg ""
+           set ch [choose_tokens 1 0 [lindex $state 0]]
+           if { $ch == "insufficient" } {
+               set msg "You do not have any tokens left which can book this period"
+           } else {
+               if { [llength [lindex $state 0]] == 1 } {
+                   set msg "You can book this slot with the token \"[lindex $state 0]\""
+               } else {
+                   set msg "You can book this slot with any of the tokens \"[lindex $state 0]\""
+               }
+               global avtype
+               if { [ llength [ array get avtype $period.$lab ] ] } {
+                   if { "$avtype($period.$lab)" == "near" } {
+                       append msg "\n   but the lab is nearly full"
+                   }
+                   if { "$avtype($period.$lab)" == "full" } {
+                       append msg "\n   but the lab is full and you will be over-booking"
+                   }
+               }
+           }
+           # fixme - count tokens
+           if {[lindex $state 2]  != {} } {
+               # class booking...
+               set msg "$msg\nThere is a class booking for \"[lindex $state 2]\""
+           }
+       } else {
+           if {[lindex $state 2] == {} } {
+               set msg "The lab is fully booked"
+           } else {
+               set msg "The lab is fully booked for class \"[lindex $state 2]\""
+           }
+       }
+    } else { 
+       set msg "The lab state is unknown..."
+    }
+       
+
+    #    tk_dialog .dialog1 "Explanation" "$hdr\n\n$msg" info 0 OK
+    .book.io.trace.text configure -state normal
+    .book.io.trace.text insert end "\n$hdr:\n  $msg\n"
+    .book.io.trace.text yview -pickplace end
+    .book.io.trace.text configure -state disabled
+}
+
+proc ynomove { list a b } {
+    # ignore a and b and move to match time.list
+    set timepos [ .book.periods.time.list yview ]
+    set a [lindex $timepos 0]
+    $list yview moveto [ expr $a + ($b-$a)/60 ] 
+}
+proc ymoveall { a b } {
+    global labs
+    .book.periods.scroll.bar set $a $b
+    foreach lab $labs {
+       .book.periods.l$lab.list yview moveto  [ expr $a + ($b-$a)/60 ] 
+    }
+}
+
+proc ymove { list a b } {
+ set current [ $list yview ]
+ if { [ lindex $current 0 ] != $a } {
+   $list yview moveto [ expr $a + ($b-$a)/100 ]
+ }
+}
+
+proc symove { sb list a b } {
+ $sb set $a $b
+ set current [ $list yview ]
+ if { [ lindex $current 0 ] != $a } {
+   $list yview moveto  [ expr $a + ($b-$a)/100 ] 
+ }
+}
+
+proc xnomove { list a b } {
+ if { $a > 0 } {
+  $list xview 0 
+ }
+}
+
+proc periodstr {p} {
+    return [format "%d:%02d" [expr $p/2] [expr ($p%2)*30]]
+}
+
+# booking and unbooking 
+proc dobook {} {
+    global ch_start ch_end ch_firstperiod ch_periods ch_lab ch_tokens ch_booked ch_free
+    if [get_selection] {
+       if { $ch_booked > 0 } {
+           book_error "You are already booked during that time"
+       } elseif { $ch_free != $ch_periods } {
+           book_error "Your selection includes a period which is not bookable"
+       } elseif { [llength $ch_tokens] < 1 } {
+           book_error {You have no token type which can book all of the selected periods.
+           Please select as shorter range of periods to book}
+       } else {
+           # ok, we might be able to book..
+           set ch [choose_tokens $ch_periods 1 $ch_tokens]
+           
+           if { $ch == "insufficient"} {
+               tk_dialog .dialog1 "Insufficient Tokens" "You do no have enough tokens to book the selected periods" error 0 OK
+           } elseif { $ch != "abort" } {
+               
+               global tokens tokencount
+               global nextperiod finalperiod getid the_date the_month months
+               cursorBusy 0
+               set nextp $ch_firstperiod
+               foreach pair $ch {
+                   set tk [lindex $pair 0]
+                   set n [lindex $pair 1]
+                   global mesg usecounts ; set mesg ""
+                   sendcommand "book $the_date [lindex $months $the_month] [periodstr $nextp]-[periodstr [expr $nextp + $n]] =$n $ch_lab $tk confirm=0" takemesg trace
+                   if { [ string length $mesg ] && [ string length $usecounts ] } {
+                       tk_messageBox -message $mesg  -type ok
+                   }
+                   incr nextp $n
+               }
+               get_tok
+               update_current
+               do_update
+               if { $getid != "NONE"} {
+                   if { $nextperiod > $ch_firstperiod } { set $nextperiod $ch_firstperiod}
+               } else {
+                   set nextperiod $ch_firstperiod
+                   set finalperiod [expr $ch_firstperiod + $ch_periods-1]]
+                   set getid [after idle getnext]
+               }
+               cursorUnset
+           }
+       }
+    } else { 
+       book_error "Please select a period to book"
+    }
+}
+
+proc dounbook {} {
+    global ch_start ch_periods ch_lab ch_tokens ch_booked ch_free ch_end
+    global the_date the_month months
+    if [get_selection] {
+       if { $ch_booked != $ch_periods } {
+           book_error "Your selection includes some periods for which you are not booked"
+       } else {
+           global getid nextperiod ch_firstperiod finalperiod
+           cursorBusy 0
+           sendcommand "unbook $the_date [lindex $months $the_month] $ch_start-$ch_end $ch_lab confirm=0" takeline trace
+           get_tok
+           update_current
+           do_update
+           if { $getid != "NONE"} {
+               if { $nextperiod > $ch_firstperiod } { set $nextperiod $ch_firstperiod}
+           } else {
+               set nextperiod $ch_firstperiod
+               set finalperiod [expr $ch_firstperiod + $ch_periods-1]]
+               set getid [after idle getnext]
+           }
+           cursorUnset
+       }
+    } else { 
+       book_error "Please select a period to unbook"
+    }
+}
+
+proc book_error {m} { 
+    after idle {.dialog.msg configure -wraplength 4i}
+    set i [tk_dialog .dialog "Error" "$m" error 0 "OK"]
+}
+proc get_selection {} {
+ # find the start, length and lab of current selection
+ # determine number of bookings at that time and 
+ # list of tokens which may be used on ALL unbooked times
+ global ch_start ch_firstperiod ch_end ch_periods ch_lab ch_tokens ch_booked ch_free
+ global labs av firstperiod seltoken tokens
+ set ch_lab ""
+ foreach lab  $labs {
+   set sel [ .book.periods.l$lab.list curselection ]
+     if { $sel != "" } {
+        set ch_lab $lab
+        set ch_sel $sel
+     }
+ }
+ if { $ch_lab == "" } { return 0 }
+ set start [lindex $ch_sel 0]
+ set ch_start [.book.periods.time.list get $start ]
+ set ch_firstperiod [expr $start + $firstperiod]
+ set ch_periods [llength $ch_sel]
+
+ set hr [ string range $ch_start 0 1 ]
+ set mn [ string range $ch_start 3 4 ]
+ set tm [expr ("1$hr"-100)*2 + $mn/30 ]
+ set ch_end [format "%02d:%02d" [expr ($tm+$ch_periods)/2] [expr (($tm+$ch_periods)%2)*30]]
+ set ch_booked 0
+ set ch_free 0
+ if { $seltoken == "All-Tokens" } {
+     set ch_tokens [ lsort $tokens ]
+ } else {
+     set ch_tokens $seltoken
+ }
+ foreach p $ch_sel {
+     set p [expr $p + $firstperiod ]
+     set e $av($p.$ch_lab)
+
+     if { [lindex $e 1] != {} } { 
+        incr ch_booked 
+     } elseif { [lindex $e 0] != {} } {
+        incr ch_free
+        # find common tokens
+        set ch_tokens [lcomm $ch_tokens [lsort [lindex $e 0] ]]
+     }
+ }
+ return 1
+}
+
+proc lcomm { a b } {
+    # get list of word in both a and b
+    set r {}
+    while { $a != {} && $b != {} } {
+       if { [lindex $a 0] == [lindex $b 0] } {
+           lappend r [lindex $a 0]
+           set a [ lrange $a 1 end ]
+           set b [ lrange $b 1 end ]
+       } elseif { [lindex $a 0] < [lindex $b 0] } {
+           set a [lrange $a 1 end ]
+       } else {
+           set b [lrange $b 1 end ]
+       }
+    }
+    return $r
+}
+
+       
+proc takemesg { aline } {
+    # If a line starts with "Warning:", take it and subsequent lines
+    # that start with a tab, and store them in "mesg"
+    global mesg
+    if { [ string length $mesg ] } {
+       set reg "^\t\(.*\)"
+    } else {
+       set reg "^Warning: \(.*\)" 
+    }
+    if { [ regexp $reg $aline all tail ] } {
+       append mesg "\n" $tail
+    }
+}
+
+# talk to book program...
+# get todays date
+proc takeline { aline } {
+    global line
+    set line $aline
+}
+proc sendcommand { command proc args } {
+    global file
+    if { [llength $args] > 0 } {
+       set  trace 1
+    } else { set trace 0 }
+    if {$trace} {
+       .book.io.trace.text configure -state normal
+       .book.io.trace.text insert  end "BOOK > "
+       .book.io.trace.text insert  end $command
+       .book.io.trace.text insert end "\n"
+       .book.io.cmd delete 0 end
+       .book.io.trace.text yview -pickplace end
+       .book.io.trace.text configure -state disabled
+       update
+    }
+#    puts "sending command $command"
+    puts $file $command
+    flush $file
+    find_prompt $proc $trace
+}
+
+proc get_today {} {
+    # give command "today" to book and extract date from reply
+    # e.g. reply:         00:00 Mon 27 May - 23:59 Mon 27 May 1996
+    global today to_day to_month line
+    global months
+    sendcommand "today" takeline trace
+    regexp {00:00 (...) ([0-9 ][0-9]) (...) - 23:59.*} $line all today to_day monthname
+    for {set i 0 } { $i < 12 } { incr i } {
+       if { [ string range [lindex $months $i ] 0 2 ] == $monthname } { set to_month $i }
+    }
+}
+
+proc get_labs {} {
+    # give command "labs date" and read a list of labs..
+    global the_date the_month months to_day  labs line
+    sendcommand "labs $the_date [lindex $months $the_month]" takeline 
+    set labs [ lsort $line ]
+}
+
+proc get_times {} {
+    global the_date the_month months line firstperiod lastperiod
+    sendcommand "times $the_date [lindex $months $the_month]" takeline 
+    if [ regexp {^ *0?([1-9]?[0-9]*):0?([1-9]?[0-9]).*[- ]0?([1-9]?[0-9]*):0?([1-9]?[0-9]) *$} $line all sh sm eh em ] {
+       set firstperiod [ expr $sh * 2 + ( $sm/30) ]
+       set lastperiod [ expr $eh *2 + ( ($em-29)/30) ]
+    } else {
+       set firstperiod 1
+       set lastperiod 0
+    }
+}
+
+# uncomment this, and you will get a count of the number of free
+# workstations in each free slot, if you have a recent version of
+# book
+set usecounts "counts=1"
+#set usecounts ""
+proc get_available {} {
+    # get availablility for the_day
+    global the_date the_month months av labs
+    global firstperiod lastperiod
+    global usecounts
+    set firstperiod 48
+    set lastperiod 0
+    foreach a [array names av] { array set av [list $a {} ]}
+    sendcommand "av all $the_date [lindex $months $the_month] $usecounts" take_avail
+    set labs [ lsort $labs ]
+}
+
+proc get1available { period } {
+    # get availablility for the_day
+    global the_date the_month months av labs
+    global firstperiod lastperiod currentperiod
+    global usecounts
+
+    foreach a $labs { set av($period.$a) {} }
+    set sh [ expr $period / 2 ]
+    set sm [ expr ($period % 2)*30 ]
+    set eh [ expr ($period+1)/2]
+    set em [ expr (($period+1)%2)*30]
+    set time [ format "%d:%02d-%d:%02d" $sh $sm $eh $em]
+    set currentperiod none
+    sendcommand "av all $the_date [lindex $months $the_month] $time $usecounts" take_avail
+
+    update_avail $period $period
+} 
+
+# av(period.lab) == { {tokens-can-book-with} {tokens-booked-with} {classes-booked-for} }
+proc add_class { where which } {
+    global av
+    set a [lindex $av($where) 0] ; set b [lindex $av($where) 1] ; set c [lindex $av($where) 2];
+    if { [ lsearch -exact $c $which] < 0}  { lappend c $which }
+    set av($where) [ list $a $b $c ]
+}
+proc add_booked { where which } {
+    global av
+    set a [lindex $av($where) 0] ; set b [lindex $av($where) 1] ; set c [lindex $av($where) 2];
+    if { [ lsearch -exact $b $which] < 0} { lappend b $which }
+    set av($where) [ list $a $b $c ]
+}
+proc add_bookable { where which } {
+    global av
+    set a [lindex $av($where) 0] ; set b [lindex $av($where) 1] ; set c [lindex $av($where) 2];
+    if { [ lsearch -exact $a $which ] < 0} { lappend a $which }
+    set av($where) [ list $a $b $c ]
+}
+proc take_avail { line } {
+    # extract availability info and store:
+    # labs is a list of all available labs
+    # firstperiod and lastperiod are period numbers: 0..47
+    # av is an array(period.lab)->{free,booked,class,full}
+    #
+    # Period: time - time  day date month (1 slot)  --- adjust periods, remember period
+    # Period: time - time  day date month - already booked 'lab' for class < > --- periods/lab/av
+    # ....                                                       with token ' ' --- periods/lab/av
+    #   Token   Labs    --- skip
+    #     tokenname    lab lab lab     --- labs/av
+    global labs firstperiod lastperiod av currentperiod
+    if [ regexp {^Period: 0?([1-9]?.):(..) - ..:..  ... .. ... (.*)} $line head hour minute rest ] {
+       set currentperiod [ expr $hour*2+($minute/30) ]
+       if [ regexp -- {- already booked '([^']*)' (.*)} $rest head lab rest2 ] {
+           # now check booking type
+           if { [ lsearch -exact $labs $lab ] >= 0  } {
+               if [ regexp {for class <(.*)>} $rest2 rest3 theclass] {
+                   add_class $currentperiod.$lab $theclass
+               } else {
+                   regexp {with token '(.*)'} $rest2 rest3 thetoken
+                   add_booked $currentperiod.$lab $thetoken
+               }
+           }
+       }
+    } elseif { [ regexp {^Undefined string} $line ] } {
+       global usecounts
+       set usecounts ""
+    } else {
+       if { ! [ regexp {No future free} $line ] && $currentperiod != "none" && ! [ regexp {Token.*Labs} $line ] } {
+           # must be "tokenname   lab lab..." (I hope )
+           # or "token  lab [lab] (lab)"
+           # [lab] are nearly fully booked.  (lab) are fully booked
+           set token [lindex $line 0 ]
+           foreach lab [ lrange $line 1 end ] {
+#              puts "lab is <$lab>"
+               if { [ regexp {^\((.*)\)$} $lab all thelab ] } {
+                   set lab $thelab
+                   global avtype
+                   set avtype($currentperiod.$lab) full
+               } elseif { [ regexp {^\[(.*)\]$} $lab all thelab ] } {
+                   set lab $thelab
+                   global avtype
+                   set avtype($currentperiod.$lab) near
+               }
+               add_bookable $currentperiod.$lab $token
+           }
+       }
+    }
+}
+
+proc do_update { args } { 
+    global firstperiod lastperiod
+    update_avail $firstperiod $lastperiod
+}
+
+proc update_avail { first last } {
+    # update the period display bases\d on possibly changed token choice
+    global firstperiod lastperiod labs av seltoken
+    for { set p $first  } { $p <= $last } { incr p } {
+       set ambooked NO
+       foreach lab $labs {
+           if { [lindex $av($p.$lab) 1] != {} } {
+               set ambooked $lab
+           }   
+       }
+       foreach lab $labs {
+           set state [ array get av $p.$lab ]
+           #       puts "$p.$lab state is $state (sel is $seltoken)"
+           if [llength $state] {
+               set state [ lindex $state 1]
+               if {[lindex $state 1] != {} }  {
+                   # booked...
+                   set mesg "BOOKED"
+               } elseif {$ambooked != "NO" } {
+                   if { $lab < $ambooked } {
+                       set mesg " -->"
+                   } else {
+                       set mesg " <--"
+                   }
+               } elseif { [lindex $state 0] != {}} { 
+                   if {  $seltoken == "All-Tokens" || [ lsearch -exact [lindex $state 0] $seltoken ] >= 0 } {
+                       # count possible tokens
+                       global tokencount
+                       set atok 0
+                       if { $seltoken == "All-Tokens" } { set tokset [lindex $state 0]} else { set tokset $seltoken}
+                       foreach tok $tokset { incr atok $tokencount($tok) }
+                       # bookable, maybe with class booking...
+                       if {[lindex $state 2] == {} } {
+                           if { $atok > 0} {
+                               global avtype
+                               set mesg "  ."
+                               if { [ llength [array get avtype $p.$lab] ] } {
+                                   set mesg "  -"
+                                   if { "$avtype($p.$lab)" ==  "full" } {
+                                       set mesg " full"
+                                   }
+                               }
+                           } else {
+                               set mesg " N/T"
+                           }
+                       } else {
+                           if { $atok > 0 } {
+                               set mesg "  C"
+                           } else {
+                               set mesg "Class"
+                           }
+                       }
+                   } else {
+                       set mesg "N/A"
+                   }
+               } else {
+                   # not bookable..
+                   if {[lindex $state 2] == {} } {
+# this should be FULL
+                       set mesg " full"
+                   } else {
+                       set mesg "Class"
+                   }
+               } 
+           } else { set mesg "  ---" }
+           .book.periods.l$lab.list delete [expr $p - $firstperiod ]
+           .book.periods.l$lab.list insert [expr $p - $firstperiod ] " $mesg"
+       }
+    }
+}
+# find out what tokens are available....
+proc take_tok {line} {
+    global tokens tokencount
+    if [ regexp "^\tAll tokens" $line ] {
+       # skip it
+    } elseif [ regexp "^\tToken" $line ] {
+       # skip it 
+    } elseif [  regexp "^\t(\[^ \t]*) *\t\t*(-?\[0-9]\[0-9]*)\t.* - ..:.. ...  ?(\[0-9]*) (...) \[0-9]\[0-9]\[0-9]\[0-9]$" $line head tok count day mon ] {
+       lappend tokens $tok
+        if { $count < 0 } { set count 0 }
+       set tokencount($tok) $count
+       set nmon [monthnumber $mon]
+       global max_month max_day
+       if { $nmon > $max_month } { set max_month $nmon ; set max_day $day }
+       if { $nmon == $max_month && $day > $max_day } { set max_day $day }
+    } elseif [  regexp "^\t\t\t.* - ..:.. ...  ?(\[0-9]*) (...) \[0-9]\[0-9]\[0-9]\[0-9]$" $line head  day mon ] {
+       set nmon [monthnumber $mon]
+       global max_month max_day
+       if { $nmon > $max_month } { set max_month $nmon ; set max_day $day }
+       if { $nmon == $max_month && $day > $max_day } { set max_day $day }
+    }
+}
+
+proc monthnumber {mon} {
+    global months
+    set nmonth 0
+    for {set i 0 } { $i < 12 } { incr i } {
+       if { [ string range [lindex $months $i ] 0 2 ] == $mon } { set nmonth $i }
+    }
+    return $nmonth
+}
+proc get_tok {} {
+ global tokens tokencount global seltoken
+ global max_month max_day to_month to_day
+ set oldsel $seltoken
+ set tokens {}
+ set max_month $to_month
+ set max_day $to_day
+ sendcommand "tok" take_tok
+    if [winfo exists .book.title.token] {
+       destroy .book.title.token
+    }
+
+ set tmenu [eval [concat {tk_optionMenu .book.title.token seltoken "All-Tokens" } $tokens ] ]
+ set sum 0
+ for {set i 0 } { $i < [llength $tokens]} { incr i} {
+   $tmenu entryconfigure [expr 1+ $i] -accelerator $tokencount([lindex $tokens $i])
+   set sum [expr $sum+$tokencount([lindex $tokens $i])]
+ }
+ $tmenu entryconfigure 0 -accelerator $sum
+ pack .book.title.token -after .book.title.tlabel -side left
+ trace variable seltoken w do_update
+ if [lsearch -exact $tokens $oldsel] { set seltoken "$oldsel" }
+}
+
+# get current bookings.
+# these are stored in a simple list of lists
+# {lab token start end mon date} - start and end are period numbers
+proc get_current {} {
+    global current_bookings
+    set current_bookings {}
+    sendcommand "show" take_show 
+}
+proc take_show {line} {
+    global current_bookings
+    if [regexp {^Lab[  ]*Token or <Class>} $line match ] {
+       # header line, skip it
+    } else {
+       if [regexp {^([a-zA-Z0-9]*)[    ]*([^   ]*)[    ]*(..):(..) - (..):(..)  (...) (..) (...)} $line all lab token starth startm  endh endm day date mon] {
+           if [regexp {<.*>} $token ] {
+               # it is a class booking, skip it
+           } else {
+               # got a good booking
+               # get rid of space
+               set date [ expr $date + 0]
+               set start [ expr ("1$starth" -100)*2 + ( "1$startm" -100)/30]
+               set end [ expr ("1$endh" -100)*2 + ( "1$endm" -100)/30]
+               set booking [ list $lab $token $start $end $mon $date $day ]
+               lappend current_bookings $booking
+           }
+       }
+    }
+}
+
+# update the currnet bookings pane
+proc update_current {} {
+    global current_bookings
+    get_current
+    .book.bookings.list delete 0 end
+    foreach b $current_bookings {
+       foreach pair { {lab 0} {token 1} {start 2} {end 3} {mon 4} {date 5} {day 6}} {
+           set [lindex $pair 0] [lindex $b [lindex $pair 1]]
+       }
+       set line [format "%-16s%-16s%2d:%02d - %2d:%02d  %3s %2d %3s" $lab $token [expr $start / 2] [expr ($start%2)*30 ] [expr $end/2] [expr ($end%2)*30+29] $day $date $mon ]
+       .book.bookings.list insert end $line
+    }
+}
+    
+proc findbooking {row} {
+    global current_bookings firstperiod
+    set booking [ lindex $current_bookings $row ]
+
+    book_select [lindex $booking 6] [lindex $booking 5] [monthnumber [lindex $booking 4]]
+    .book.periods.time.list yview [expr [lindex $booking 2] - $firstperiod ]
+}
+
+# a help window...
+
+proc DisplayHelp {} {
+
+    set w ".bookhelp"
+    if [winfo exists $w] {
+       destroy $w
+    }
+    toplevel $w
+
+    wm title $w "Help for tkbook"
+    wm minsize $w 1 1
+    frame $w.frame -borderwidth 5
+
+    scrollbar $w.frame.yscroll -relief sunken -command "$w.frame.page yview"
+    text $w.frame.page -yscrollcommand "$w.frame.yscroll set " \
+           -width 80 -height 20 -relief sunken -wrap word
+    pack $w.frame.yscroll -side right -fill y
+    pack $w.frame.page -side top -expand 1 -fill both
+
+    set contents {
+
+tkbook - a visual interface to book
+
+If you have any problems with this program that this help does not
+sort out for you, please send mail to Neil Brown <neilb@cse.unsw.edu.au>
+and I will try to fix the problem.
+
+The tkbook window contains the following buttons and subwindows:
+
+Tokens:
+   select the token to use, or All-Tokens
+   If a particular token is selected, then bookings will only
+   be made with that token. Otherwise bookings will be made
+   with whatever token is available. If a booking could be made with
+   one of several tokens, you will be asked to choose.
+
+Help:
+   display this help message.
+
+Exit:
+   Exit from tkbook.
+
+Calendar:
+   select day to view in periods window.
+   ">>" will move forward one week
+   "<<" will move backward one week
+   The currently displayed day is highlighted
+
+Periods window:
+   A range of times in any lab column may be selected with the left
+   mouse button. Once selected, this range may be booked with the "book"
+   button (if all the selected periods are bookable) or unbooked with the
+   "unbook" button (if all the selected periods are booked).
+
+   The text in the various periods have the following meanings:
+
+   .       This period is bookable.
+   C       This period is bookable, but there is also a class booking
+           for a class that you are a member off.
+   N/A     This period is bookable, but not with selected token.
+   N/T     You have used up all your tokens that would be able to book this
+           period.
+
+   full    This period is fully booked.
+   Class   This period is fully booked, and there is a class booking.
+           at this time for a class that you are a member of.
+
+   BOOKED  You have a booking for this period
+    -->
+    <--    You have a booking at this time in another lab
+
+
+   Clicking with the left mouse button in any particular period will
+   provide an explanation of the state of that period in the output window
+   at the bottom.
+
+   If there is a choice of tokens to be used when making a booking, then
+   a dislog window will be displayed wherein your choice can be made.
+   For each possible token, there will be a slider widget which can be
+   manipulated to select the desired number of that token to use.
+   Other sliders will be automatically update so that the sum remains
+   constant.
+
+Bookings window:
+   A list of all current bookings is displayed giving the lab that the book
+   is in, the token that was used to book, and the time and date of the booking.
+   Selecting a booking with the mouse causes that date to be selected for the
+   periods window, which will be scrolled to make the selected booking
+   appear.
+
+Book> window:
+    Commands for the book program may be entered here.
+    If the command asks a question, tkbook will currently hang.
+
+Output window at bottom:
+    tkbook works by running "book" and sending commands to it. Some of these
+    interactions with book are displayed in this window.
+    }
+    $w.frame.page insert 0.0 $contents
+    $w.frame.page configure -state disabled
+
+    button $w.dismiss -text Dismiss -command "destroy $w"
+    pack $w.dismiss -side bottom -fill x
+    pack $w.frame -side top -fill both -expand 1
+}
+
+# mouse pointer routines, shameless stolen from tkrestore
+
+proc cursorBusy {{up 1}} {
+    if {[. cget -cursor]!="watch"} {
+        cursorSet watch
+        if $up {update idletasks}
+    }
+}
+
+proc cursorSet {c {w .}} {
+    global cursor
+    set cursor($w) [lindex [$w configure -cursor] 4]
+    $w configure -cursor $c
+    foreach child [winfo children $w] {cursorSet $c $child}
+}
+
+proc cursorUnset {{w .}} {
+    global cursor
+    catch {$w configure -cursor $cursor($w)}
+    foreach child [winfo children $w] {cursorUnset $child}
+}
+
+
+#
+# choose some tokens:
+#  If a booking is requested and multiple tokens may apply,
+#  a decision needs to be made as to which tokens to use
+#  This is done with
+#     choose_tokens n ask{yes/no} tokenlist
+#  which will return a list of token/count pairs, or nothing
+#  if there are not enough tokens
+#  if ask is yes, the user is asked to make a discision if there are choices.
+#  if ask is no, the first possibility found is given
+
+proc choose_tokens { num ask tlist } {
+    global tokencount
+    global gnum gtlist
+    set gnum $num
+    set gtlist {}
+    set avail 0
+    foreach t $tlist {
+       if [ expr $tokencount($t) > 0 ] {
+           incr avail $tokencount($t)
+           lappend gtlist $t
+       }
+    }
+    set tlist $gtlist
+    if { $avail < $num } {
+       return {insufficient}
+    } elseif { $avail == $num || [llength $tlist] == 1 || ! $ask } {
+       set need $num
+       set ans {}
+       foreach t $tlist {
+           if { $need >= $tokencount($t) } {
+               lappend ans [ list $t $tokencount($t) ]
+               set need [ expr $need - $tokencount($t) ]
+           } elseif { $need > 0 } {
+               lappend ans [ list $t $need ]
+               set need 0
+           }
+       }
+       return $ans
+    } else {
+       global choice
+       # need to pop up a window
+       # it has one vertical scale for each token
+       set w .ch
+       toplevel .ch
+       wm transient $w [winfo toplevel [winfo parent $w]]
+       # fill window ...
+
+       if {$num == 1} {
+           label $w.l -text "Choose $num token"
+       } else {
+           label $w.l -text "Choose $num tokens"
+       }
+       pack $w.l -side top
+       
+       frame $w.b
+       button $w.b.ok -text "ok" -command { done 1 }
+       button $w.b.quit -text "abort" -command { done 0 }
+       pack $w.b.ok -side left
+       pack $w.b.quit -side right
+       pack $w.b -side bottom
+       
+       set need $num
+       foreach t $tlist {
+           frame $w.t$t
+           pack $w.t$t -side left -fill y
+           label $w.t$t.l -text $t
+           pack $w.t$t.l -side bottom
+           scale $w.t$t.s -from $tokencount($t) -to 0 -showvalue yes -command "slide_update $t" \
+                   -label $tokencount($t) -length [ expr $tokencount($t)*20+40]
+           if { $need > $tokencount($t) } {
+               $w.t$t.s set $tokencount($t)
+               set need [ expr $need - $tokencount($t) ]
+           } else {
+               $w.t$t.s set $need
+               set need 0
+           }
+           pack $w.t$t.s -side bottom -fill y
+       }
+       
+       # centre new window
+       wm withdraw $w
+       update idletasks
+       set x  [expr [winfo screenwidth $w]/2 - [winfo reqwidth $w]/2 \
+               - [winfo vrootx [winfo parent $w]]]
+       set y [expr [winfo screenheight $w]/2 - [winfo reqheight $w]/2 \
+               - [winfo vrooty [winfo parent $w]]]
+       wm geom $w +$x+$y
+       wm deiconify $w
+       
+       grab $w
+       tkwait visibility $w
+       
+       tkwait variable choice
+       destroy $w
+       
+       return $choice
+    }
+}
+
+proc slide_update { tok newnum } {
+    # count how many we now have, calc how many needed,
+    # try to update other sliders to make total the same
+    global gnum tokencount gtlist
+
+    set have 0
+    foreach t $gtlist {
+       incr have [ .ch.t$t.s get]
+    }
+    set need [expr $gnum - $have]
+
+    set found 0
+    foreach t [concat $gtlist $gtlist ] {
+       if $found {
+           while { $need > 0 && [.ch.t$t.s get] < $tokencount($t) } {
+               incr need -1
+               .ch.t$t.s set [expr [.ch.t$t.s get] + 1]
+           }
+           while { $need < 0 && [.ch.t$t.s get] > 0 } {
+               incr need 
+               .ch.t$t.s set [expr [.ch.t$t.s get] - 1]
+           }
+       } else {
+           if { $t == $tok } {
+               set found 1
+           }
+       }
+    }
+}
+
+proc done { ok } {
+    global choice gtlist
+    if { ! $ok } {
+       set choice "abort"
+    } else {
+       set ch {}
+       foreach t $gtlist {
+           set n [.ch.t$t.s get ]
+           if $n {
+               lappend ch [ list $t $n ]
+           }
+       }
+       set choice $ch
+    }
+}
+
+
+
+cursorBusy 0
+
+set firstperiod 1
+set lastperiod 0
+# who for...
+
+if { $argc >= 1 } {
+    start_book [lindex $argv 0]
+} else {
+    start_book
+}
+
+cal_create
+
+set the_day unknown
+set firstperiod unknown
+set labs {}
+make_periods first
+update
+find_prompt ignore 1
+
+get_today
+
+set seltoken "All-Tokens"
+get_tok
+update
+cal_init
+set the_month NOMONTH
+set the_date NODATE
+update
+
+book_select $today $to_day $to_month
+
+update_current
+
+#cursorUnset
+
diff --git a/database/Makefile b/database/Makefile
new file mode 100644 (file)
index 0000000..5fc0b68
--- /dev/null
@@ -0,0 +1,25 @@
+
+target += book_database
+
+obj-book_database := main.o
+obj-book_database +=  hosts_db.o replica_db.o bookings_db.o classes_db.o  tokens_db.o users_db.o config_db.o names_db.o
+obj-book_database +=  make_keys.o freeslots.o db_transfer.o db_utils.o db_change.o utils.o db_decode.o 
+obj-book_database += database_svc.o database_xdr.o
+obj-book_database +=  descpairlist.o desc_utils.o  intpairlist.o
+obj-book_database += ../lib/lib.a
+
+
+target += book_maint
+
+obj-book_maint :=  db_helper.o libdbclnt.a ../lib/lib.a
+
+target += book_copydb
+
+obj-book_copydb +=  book_copydb.o libdbclnt.a  ../lib/lib.a
+
+lib += libdbclnt.a
+
+obj-libdbclnt.a := database_clnt.o database_xdr.o intpairlist.o db_decode.o  utils.o make_keys.o desc_utils.o
+
+include $(S)$(D)../MakeRules
+
diff --git a/database/book_copydb.c b/database/book_copydb.c
new file mode 100644 (file)
index 0000000..d8008e6
--- /dev/null
@@ -0,0 +1,255 @@
+/* Copies a number of databases from the server supplied on the */
+ /* command line */
+
+#include       "db_header.h"
+#include       <stdio.h>
+#include       <unistd.h>
+#ifndef apollo
+#include       <netinet/in.h>
+#endif
+#include       <sys/socket.h>
+#include       <netdb.h>
+#include       <sys/stat.h>
+
+char errbuff[128];
+
+int rread(int fd, void *buf, int len)
+{
+       int have = 0;
+
+       while (have < len)
+       {
+               int n = read(fd, buf+have, len-have);
+               if (n > 0)
+                       have += n;
+               else if (n == 0)
+                       len = have;
+               else
+                       return n;
+       }
+       return have;
+}
+
+/* given the name of a master server and a port number that the master */
+ /* is listening on to send a copy  of the database */
+int receive_db(char * supplier,short port)
+{
+       int sock;
+       int datasize;
+       struct sockaddr_in sin;
+       char *buf;
+       struct hostent *he;
+       int buflen = 1024;
+       char keybuf[1024];
+       GDBM_FILE newdb= NULL;
+       datum key, content;
+       char * pathname;
+
+
+       char *files[20];
+       int filecnt = 0;
+    
+       /* later check that port is privileged FIXME */
+       if (port == 0)
+               return 1;
+
+       sock =  socket(AF_INET, SOCK_STREAM, 0);
+    
+       if (sock == -1)
+               return 1;
+
+       he = gethostbyname(supplier);
+       if (he == NULL) {
+               sprintf(errbuff,"Could not get hostname for %s\n",supplier);
+               close(sock);
+               bailout(errbuff, 10);
+       }
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_port = htons(port);
+       bcopy(he->h_addr_list[0], &sin.sin_addr, he->h_length);
+       if (connect (sock, (struct sockaddr*)&sin, sizeof(sin)) == -1) {
+               sprintf(errbuff,"Could not connect to %s, reason....\n",supplier);
+               close(sock);
+               bailout(errbuff,11);
+       }
+
+
+       /* set up initial buffer */
+       buf = (char *)calloc(buflen, sizeof(char));
+
+       while (1)
+       {
+               if (rread(sock, &datasize, sizeof(datasize)) != sizeof(datasize))
+               {
+                       datasize = 1;
+                       printf("datasize read error\n");
+                       break;
+               }
+       
+               datasize = ntohl(datasize);
+               if (datasize == 0)      /* finish signal */
+                       break;
+       
+               if (datasize < 0)
+               {                       /* new database */
+                       int opt;
+                       if (newdb != NULL)
+                               gdbm_close(newdb);
+           
+                       datasize = -datasize;
+       
+                       if (rread(sock, keybuf, datasize) != datasize)
+                               break;
+                       keybuf[datasize] = '\0';
+                       files[filecnt++] = strdup(keybuf);
+                       strcat(keybuf, ".new");
+                       pathname = make_pathname(keybuf);
+                       newdb = gdbm_open(pathname, 1024, GDBM_NEWDB, 0600,0);
+                       if (newdb == NULL ) {
+                               printf("error opening file %s\n",pathname);
+                               break;
+                       }
+                       /* no need to sync */
+                       opt = 1;
+                       gdbm_setopt(newdb, GDBM_FASTMODE, &opt, sizeof(opt));
+               }
+               else
+               {
+                       key.dsize = datasize;
+                       if (rread (sock, keybuf, datasize) != datasize)
+                       {
+                               printf("Error reading key\n");
+                               break;
+                       }
+       
+                       key.dptr = keybuf;
+                       /*
+                         printf("got :");
+                         print_key(&key);
+                       */
+                       /* now for the data */
+       
+                       if (rread(sock, &datasize, sizeof(datasize)) != sizeof(datasize)) {
+                               printf("datasize read error\n");
+                               datasize = 1;
+                               break;
+                       }
+                       /*
+                         printf("datasize = %d\n",datasize);
+                       */
+                       datasize = ntohl(datasize);
+       
+                       if (datasize == 0)  /* finish signal */
+                               break;
+                       content.dsize = datasize;
+       
+                       if (datasize > buflen) {
+                               free(buf);
+                               buflen = datasize + 100;
+                               buf = (char *)calloc(buflen, sizeof(char));
+                       }
+                       datasize = 0;
+                       while (datasize  < content.dsize ) {
+                               int n = rread(sock, buf+ datasize, content.dsize - datasize);
+                               if (n <= 0)
+                               {
+                                       datasize = 1;
+                                       break;
+                               }
+                               datasize += n;
+                       }
+
+                       if (datasize < content.dsize) {
+                               printf("Data read bad, only %d of %d\n", datasize, content.dsize);
+                               datasize = 1;
+                               break;
+                       }
+                       content.dptr = buf;
+       
+                       if (newdb == NULL) {
+                               printf("Protocol error... no file opened\n");
+                               break;
+                       }
+                       /* store it away, remember key must be unique*/
+                       if (gdbm_store(newdb,key,content, GDBM_INSERT) != 0) 
+                       {
+                               printf("store failed: %d\n",gdbm_errno);
+                               break;
+                       }
+               }
+       }
+       close (sock);
+       if (newdb != NULL)
+               gdbm_close(newdb);
+
+       while (filecnt--)
+       {
+               char *preal = make_pathname(files[filecnt]);
+               char *pnew;
+
+               strcpy(keybuf, files[filecnt]);
+               strcat(keybuf, ".new");
+               pnew = make_pathname(keybuf);
+       
+           
+               if (datasize != 0)
+                       unlink(pnew);
+               else
+               {
+                       struct stat stbuf;
+                       if (stat(preal, &stbuf)==0)
+                       {
+                               printf("book_copydb: %s already exists!!\n", preal);
+                               datasize = 1;
+                               unlink(pnew);
+                       }
+                       else
+                               rename(pnew, preal);
+               }
+       }
+       if (datasize == 0)
+               return 0;
+       else
+       {
+               printf("book_copydb: copying of database failed\n");
+               return 1;
+       }
+}
+
+int main (int argc, char *argv[])
+{
+       CLIENT * supplier_cl;
+       transfer_reply * result;
+       char *supplier ;
+       int arg;
+
+
+       if (argc != 2) {
+               printf("usage: %s server\n",argv[0]);
+               exit(3);
+       }
+
+       supplier = argv[1];
+
+       supplier_cl = clnt_create(supplier,BOOK_DATABASE,BOOKVERS,"udp");
+       if (supplier_cl == NULL) {
+               /* no connection to server */
+               clnt_pcreateerror(supplier);
+               exit(1);
+       }
+
+       result = transfer_db_2(&arg,supplier_cl);
+       if (result == NULL) {
+               printf("error contacting server %s\n",supplier);
+               exit(1);
+       }
+       if (result -> error != R_RESOK) {
+               printf("error with server: %d\n",result-> error);
+               exit(1);
+       }
+       /* now get the copy */
+       exit(receive_db(supplier,result->port));
+}
+
diff --git a/database/bookings_db.c b/database/bookings_db.c
new file mode 100644 (file)
index 0000000..6bb3d5b
--- /dev/null
@@ -0,0 +1,397 @@
+/* these routines handle the bookings database */
+/* the bookings database is of the following form:
+
+   key                                 value
+   doy.pod.lab                         [userid,count,token,timemake,status,request]
+   doy.pod.desc                                total-count
+
+   */
+
+#include       "db_header.h"
+#include       <stdio.h>
+
+book_changeres add_booking_to_list(bookhead * blist, book_req *req)
+{
+    booklist new;
+    
+    new = (booklist)malloc(sizeof(bookslot));
+    new -> who_for = req->who_for;
+    new -> who_by  = req->who_by;
+    desc_cpy(&new->mreq,&req->mreq);
+    desc_cpy(&new->charged, &req->slot.what);
+    new -> number = req->number;
+    new -> tnum = req->tnum;
+    new -> time = req->when;
+    new -> status = B_PENDING;
+    new -> claimed = 0;
+    new -> allocations.entitylist_len = 0;
+    new -> allocations.entitylist_val = NULL;
+    new -> next = blist->data;
+    blist -> data = new;
+
+    return C_OK;
+}
+
+static booklist * finduser(bookhead * bh, bookuid_t user, int when)
+{
+       booklist *ptr = &bh->data;
+
+       while ((*ptr != NULL) &&
+              ((user != (*ptr)->who_for)
+               || when != (*ptr)->time
+                      )
+               )
+               ptr = &(*ptr)-> next;
+
+       return(ptr);
+    
+}
+
+static booklist finduser_alloc(bookhead * bh, bookuid_t user)
+{
+       booklist ptr;
+       booklist found = NULL;
+
+       for (ptr = bh->data ; ptr ; ptr=ptr->next)
+               if (user == ptr->who_for
+                   && (ptr->status == B_ALLOCATED || ptr->status == B_RETURNED || ptr->status == B_FREEBIE))
+               {
+                       if (found)
+                               return NULL;
+                       found = ptr;
+               }
+
+       /* HACK alert - if not found, try for cancelled or refunded.. */
+       if (! found)
+               for (ptr = bh->data ; ptr ; ptr=ptr->next)
+                       if (user == ptr->who_for
+                           && (ptr->status == B_CANCELLED || ptr->status == B_REFUNDED))
+                       {
+/*         if (found)
+           return NULL;*/
+                               found = ptr;
+                       }
+
+       /* OK, go for a pending one ... */
+       if (! found)
+               for (ptr = bh->data ; ptr ; ptr=ptr->next)
+                       if (user == ptr->who_for
+                           && (ptr->status == B_PENDING))
+                       {
+                               if (found)
+                                       return NULL;
+                               found = ptr;
+                       }
+       
+       return found;
+}
+
+book_changeres incuser(bookhead *bh, book_req *req)
+{
+       booklist val;
+
+       if ((val = *finduser(bh, req->who_for, req->when)) == NULL)
+               return add_booking_to_list(bh, req);
+
+       /* Do We really want the following. Infact, should it  return an error FIXME */
+       /* or should we just update all the fields, assuming this was created by
+        * a change request
+        *.. We do want this, for class booking that need to be made in several passes.
+        */
+       val->number += req->number;
+       val->tnum = req->tnum;
+       /* FIXME should I free val->mreq.???? */
+       desc_cpy(&val->mreq, &req->mreq);
+       return(C_OK);
+}
+
+book_changeres read_bookhead(bookhead *bh, book_key *key)
+{
+       char ky[20];
+       book_changeres rv;
+       rv = read_db(bookkey2key(ky,*key), bh, sizeof(bookhead),
+                    (xdrproc_t)xdr_bookhead, BOOKINGS);
+       return rv;
+}
+
+book_changeres write_bookhead(bookhead *bh, book_key *key)
+{
+       char ky[20];
+       book_changeres rv = write_db(bookkey2key(ky, *key), bh,
+                                    (xdrproc_t)xdr_bookhead, BOOKINGS);
+       return rv;
+}
+
+book_changeres add_booking(book_req * booking)
+{
+       book_key keyv;
+       static bookhead blist;
+       static book_changeres result;
+    
+       /* read the book slot */
+       slot2bookkey(&booking->slot, &keyv);
+    
+       result = read_bookhead(&blist, &keyv);
+
+       if ((result != C_OK) && (result != C_NOMATCH))
+               return result;
+
+       /*  now add new user to this list */
+       result = incuser(&blist,booking);
+       if (result != C_OK)
+               return result;
+
+       /* now write result out */
+       result = write_bookhead(&blist, &keyv);
+
+       inc_usage_count(&booking->slot, booking->number);
+    
+       return result;
+}
+
+int inc_usage_count(slotgroup *slot, int number)
+{
+       item key;
+       int cnt;
+       book_changeres result;
+
+       key = slot2key(slot);
+       cnt = 0;
+       result = read_db(key, &cnt, sizeof(cnt), (xdrproc_t)xdr_int, BOOKINGS);
+       if (result != C_OK && result != C_NOMATCH)
+       {
+               free(key.item_val);
+               return -1;
+       }
+       cnt += number;
+       if (cnt)
+               result = write_db(key, &cnt, (xdrproc_t)xdr_int, BOOKINGS);
+       else    /* remove the key.  This should save heaps of space in the */
+               /* database adam 03/11/93 */
+               delete_from_db(key,BOOKINGS);
+    
+       free(key.item_val);
+       return cnt;
+}
+
+book_changeres claim_booking(book_set_claim *claim)
+{
+       book_changeres result;
+       static bookhead blist;
+       booklist thebooking;
+    
+       result = read_bookhead(&blist, &claim->slot);
+       if (result != C_OK)
+               return result;
+
+       if (claim->when)
+               thebooking = *finduser(&blist, claim->user, claim->when);
+       else
+               thebooking = finduser_alloc(&blist, claim->user);
+       if (thebooking == NULL)
+               result = C_NOMATCH;
+       else
+       {
+               /* update thebooking->claimed */
+               int  bits = claim->claimtime & 0xf;
+               int when = claim->claimtime >> 4;
+               int oldwhen = thebooking->claimed >>4;
+
+               /* make sure propagated claims have the when field set. */
+               claim->when = thebooking->time;
+
+               if ((bits&DID_LOGIN) &&
+                   (!(thebooking->claimed&DID_LOGIN)|| (oldwhen > when)))
+                       oldwhen = when;
+               thebooking->claimed = ((thebooking->claimed | bits)&0xf) | (oldwhen<<4);
+               if (claim->where >= 0 && thebooking->allocations.entitylist_len>0
+                   && thebooking->allocations.entitylist_val[0] == claim->where)
+                       thebooking->claimed |= LOGIN_ON_ALLOC;
+               result = write_bookhead(&blist, &claim->slot);
+       }
+       return result;
+}
+    
+book_changeres change_booking(book_chng *bchanges, int *refund, int *tokens, int *who_by)
+{
+       static bookhead blist;
+       static book_changeres result;
+       int i;
+
+       /* collect mods to usage count in an array and process all at end */
+       int *usage_mods;
+
+       /* break the key open and use the description to */
+       /* form a slotgroup */
+       slotgroup theslot;
+       int doy,pod,lab;
+      
+       /* read the book slot */
+       result = read_bookhead(&blist, &bchanges->slot);
+    
+       if (result != C_OK)
+               return result;
+
+       usage_mods = (int*)malloc(sizeof(int)*bchanges->chlist.book_ch_list_len);
+
+       for (i=0;i < bchanges->chlist.book_ch_list_len;i++)
+       {
+               booklist thebooking;
+               book_ch_ent *usr;
+
+               usage_mods[i]=0;
+               usr = &bchanges->chlist.book_ch_list_val[i];
+       
+               thebooking = *finduser(&blist, usr->user, usr->when);
+
+               if (thebooking == NULL)
+               {
+                       char errbuf[128];
+           
+                       /* changing a booking that has not yet been made!!
+                        * we should add the booking here
+                        */
+                       sprintf(errbuf, "Couldn't find booking for user %ld in the list, time %d\n",
+                               (long)usr->user, usr->when);
+                       shout(errbuf);
+                       /* looks like out of sequence changes. Could make booking now, anyway, or
+                        * fail and let the create come first before we proceed.
+                        * the latter is easier
+                        */
+                       free(usage_mods);
+                       return C_NOMATCH;
+               }
+               else
+               {
+                       /* if the change has already been done, then ignore */
+                       if (usr->status <= thebooking->status)
+                       {
+                               /* already been done, just accept */
+                       }
+                       else
+                       {
+                               /* change the status and possibly the lab usage */
+                               if (thebooking->status < B_ALLOCATED &&
+                                   usr->status >= B_ALLOCATED)
+                               {
+                                       usage_mods[i] -= thebooking->number;
+                               }
+               
+                               if (thebooking->status <= B_ALLOCATED
+                                   && usr->status > B_ALLOCATED)
+                               {
+                                       refund[i]+= thebooking->number;
+                                       tokens[i] = thebooking->tnum;
+                                       if (usr->priv_refund)
+                                               tokens[i] |= PRIV_REFUND_FLAG;
+                                       who_by[i] = thebooking->who_by;
+                               }
+                               thebooking->status = usr->status;
+                       }
+                       if (thebooking->allocations.entitylist_len < usr->allocations.entitylist_len)
+                       {
+                               int l = usr->allocations.entitylist_len;
+                               if (thebooking->allocations.entitylist_val)
+                                       free(thebooking->allocations.entitylist_val);
+                               thebooking->allocations.entitylist_val = memdup(usr->allocations.entitylist_val, l*sizeof(entityid));
+                               thebooking->allocations.entitylist_len = l; 
+                       }
+                       if (usr->tentative_alloc & 1)
+                               thebooking->claimed |= WAS_TENTATIVE;
+                       if (usr->tentative_alloc & 2)
+                       {
+                               thebooking->claimed = (thebooking->claimed&0xf)|
+                                       (((30-10)*60)<<4)|DID_LOGIN|LOGIN_ON_ALLOC;
+                       }
+               }
+       }
+       /* now apply the usage changes */
+       book_key_bits(&bchanges->slot,&doy,&pod,&lab);
+       theslot.pod = pod;
+       theslot.doy = doy;
+       for (i=0;i < bchanges->chlist.book_ch_list_len;i++)
+               if (usage_mods[i])
+               {
+                       booklist thebooking;
+                       book_ch_ent *usr;
+
+                       usr = &bchanges->chlist.book_ch_list_val[i];
+       
+                       thebooking = *finduser(&blist, usr->user, usr->when);
+                       theslot.what = thebooking->charged;
+                       inc_usage_count(&theslot, usage_mods[i]);
+               }
+       free(usage_mods);
+       /* and write result out */
+       result = write_bookhead(&blist, &bchanges->slot);
+       return result;
+}
+
+booklist map_bookings(bookuid_t user, userbookinglist list)
+{
+       static booklist result = NULL;
+       booklist *lp = &result;
+
+       if (result)
+               xdr_free((xdrproc_t)xdr_booklist, (char*) &result);
+
+       while (list)
+       {
+               static bookhead bh;
+               booklist *ent;
+               book_changeres st;
+
+               st = read_bookhead(&bh, &list->slot);
+       
+               if (st != C_OK)
+                       return NULL;
+               ent = finduser(&bh, user, list->when);
+               if (ent == NULL&& *ent == NULL)
+                       return NULL;
+               *lp = *ent;
+               *ent = (*lp)->next;
+               lp = & (*lp)->next;
+               *lp = NULL;
+               list = list->next;
+       }
+       return result;
+}
+
+book_changeres remove_booking(book_key * removal)
+{
+       static bookhead blist;
+       booklist bptr;
+       static book_changeres result;
+       extern book_changeres del_user_booking();
+       char ky[20];
+
+    
+       /* get the list of bookings */
+       result = read_bookhead(&blist, removal);
+       if (result != C_OK)
+               return result;
+
+       /* remove the booking from the user lists */
+       bptr = blist.data;
+       while (bptr != NULL) {
+#if 0
+               slotgroup slot;
+               int lab;
+               book_key_bits(&removal, &slot.doy, &slot.pod, &lab);
+               slot.what = bptr->charged;
+               inc_usage_count(&slot, -bptr->number);
+#endif
+               result = del_user_booking(bptr->who_for, bptr->time, removal);
+               if (result != C_OK && result != C_NOMATCH)
+                       return result;
+               bptr = bptr->next;
+       }
+
+       
+       /* delete the booking slot from the database */
+       /* CHECK THIS FIRST */
+       result = delete_from_db(bookkey2key(ky, *removal), BOOKINGS);
+       return result;
+}
+
+
diff --git a/database/classes_db.c b/database/classes_db.c
new file mode 100644 (file)
index 0000000..5c09089
--- /dev/null
@@ -0,0 +1,56 @@
+#include       "db_header.h"
+
+/* classes database
+   key: class_id, value: [tokens, count] 
+   */
+book_changeres add_class(class_req * cl_req)
+{
+    static intpairlist list_of_tokens;
+    book_changeres result;
+    char key[20];
+    item k;
+
+    /* read in record for this user */
+    result = read_db(k=key_int(key, C_ALLOTMENTS, cl_req->user),
+                    &list_of_tokens,
+                    sizeof(intpairlist), (xdrproc_t)xdr_intpairlist, CONFIG);
+
+    if ((result != C_OK) && (result != C_NOMATCH))
+       return result;
+
+
+    /* add token to list */
+    incintkey(&list_of_tokens, cl_req->tnum, cl_req->count);
+    
+    /* now save the resultant record */
+    result = write_db(k, &list_of_tokens, (xdrproc_t)xdr_intpairlist, CONFIG);
+    return result;
+}
+
+book_changeres set_allotment(int uid, int tok, int cnt)
+{
+    char key[20];
+    item k;
+    book_changeres result;
+    static intpairlist toks;
+    intpairlist t;
+    result = read_db(k=key_int(key, C_ALLOTMENTS, uid),
+                    &toks,
+                    sizeof(intpairlist), (xdrproc_t)xdr_intpairlist, CONFIG);
+
+    if ((result != C_OK) && (result != C_NOMATCH))
+       return result;
+
+    t = findintkey(toks, tok);
+    if (t) cnt -= t->num;
+    if (cnt)
+    {
+       incintkey(&toks, tok, cnt);
+       result = write_db(k, &toks, (xdrproc_t)xdr_intpairlist, CONFIG);
+    }
+    else
+       result = C_OK;
+    return C_OK;
+}
+
+                         
diff --git a/database/common.h b/database/common.h
new file mode 100644 (file)
index 0000000..d139833
--- /dev/null
@@ -0,0 +1,54 @@
+
+/* from intpairlist.c */
+void add_to_intpairlist(intpairlist * ch, int new, int num);
+int del_from_intpairlist(intpairlist * ch, int name);
+intpairlist findintkey(intpairlist ptr, int key);
+void incintkey(intpairlist *ch, int  key, int diff);
+void decintkey(intpairlist *ch, int  key, int diff);
+void decintkey_nodel(intpairlist *ch, int  key, int diff);
+void print_intpairlist(intpairlist *ch);
+void merge_intlists(intpairlist * tot, intpairlist * others);
+void del_intlist(intpairlist * tot, intpairlist * subs);
+void del_intlist_nodel(intpairlist * tot, intpairlist * subs);
+
+
+/* from utils.c */
+item slotbits2key(int doy, int pod, description *desc);
+item slot2key(slotgroup *slot);
+item bookkey2key(char *ky, book_key k);
+void book_key_bits(book_key *bkey, int *doy, int *pod, int *lab);
+book_key make_bookkey(int doy, int pod, int lab);
+
+/* from desc_utils.c */
+void free_slot(description * slot);
+description new_desc();
+void set_a_bit(description * desc, int i);
+bool_t del_a_bit(description * desc, int i);
+bool_t query_bit(description * desc, int num);
+void full_desc(description * desc);
+void zero_desc(description * desc);
+void desc_add(description d1, description d2);
+void desc_and(description d1, description d2);
+void desc_sub(description d1, description d2);
+bool_t null_desc(description * desc);
+description invert_desc(description * desc);
+bool_t required_bits(description * template, description * testcase);
+bool_t desc_member(description * master, description * slave);
+void desc_cpy(description *dest, description *src);
+bool_t mandatory_attrs(item *mand, item *d1, item *d2);
+bool_t desc_eql(item d1, item d2);
+description * desc_dup(description * src);
+int desc_2_labind(description *d);
+void slot2bookkey(slotgroup *slot, book_key *key);
+
+/* from make_keys.c */
+item user_key(char *key, int value);
+item key_int(char *key, config_type type, int num);
+item key_string(config_type type, char *str);
+item str_key(char * str);
+
+/* from db_decode.c */
+bool_t datum_decode (datum * content, void * data, xdrproc_t xdr_fn );
+bool_t item_decode (item * val, void *data, xdrproc_t xdr_fn);
+bool_t item_encode(item * val, void * data, xdrproc_t xdr_fn);
+bool_t datum_encode(datum * val, void * data, xdrproc_t xdr_fn);
diff --git a/database/config_db.c b/database/config_db.c
new file mode 100644 (file)
index 0000000..0309dcc
--- /dev/null
@@ -0,0 +1,141 @@
+#include        "db_header.h"
+
+/* config database, parameters for the booking system */
+/* these are of three categories
+   * General
+   * Bitspec
+   * Exclusion
+
+*/
+
+#if 0
+/* UPDATE_HEADER: fixes up the header for the bitspec and exclusions */
+ /* stuff */
+book_changeres *update_header(int bit, bittype type, int maxexclusion, int exclusions_per_block)
+{
+    static book_changeres result;
+    item key;
+    static bitstring_exclusion_header header;
+
+
+    key = str_key(BIT_EXCL_HEADER);
+
+    
+    result = read_db(key, &header,
+                    sizeof(bitstring_exclusion_header),
+                    xdr_bitstring_exclusion_header, CONFIG);
+
+    if (result == C_NOMATCH) {
+       header.lab = new_desc();
+       header.mandatory = new_desc();
+       header.all = new_desc();
+       header.weeks = new_desc();
+       header.normallab = header.closed = header.open = header.bookable = 0;
+    }
+    /* if not present.. no matter */
+
+    /* if it was a max exclusion */
+    if (maxexclusion)
+    {
+       header.maxexclusion = maxexclusion;
+       header.exclusions_per_block = exclusions_per_block;
+    }
+    else /* this means it was a bittype */
+    {
+        if (type & BIT_LAB)
+           set_a_bit(&header.lab, bit);
+       else
+           clr_a_bit(&header.lab, bit);
+
+       if (type & BIT_MAND)
+           set_a_bit(&header.mandatory, bit);
+       else
+           clr_a_bit(&header.mandatory, bit);
+
+       if (type & BIT_WEEK)
+           set_a_bit(&header.weeks, bit);
+       else
+           clr_a_bit(&header.weeks, bit);
+
+       if (bit == header.normallab) header.normallab = 0;
+       if (bit == header.closed) header.closed = 0;
+       if (bit == header.open) header.open = 0;
+       if (bit == header.bookable) header.bookable = 0;
+       if (type & BIT_NORMALLAB)
+           header.normallab = bit;
+       if (type & BIT_CLOSED)
+           header.closed = bit;
+       if (type & BIT_OPEN)
+           header.open = bit;
+       if (type & BIT_BOOKABLE)
+           header.bookable = bit;
+       /* set all no matter what */
+       if (type & BIT_FORGET)
+           clr_a_bit(&header.all, bit);
+       else
+           set_a_bit(&header.all, bit);
+       
+    }
+    /* write out the result */
+    result = write_db(key, &header,
+                     xdr_bitstring_exclusion_header, CONFIG);
+    
+    return(&result);
+    
+}
+#endif
+
+
+book_changeres change_implication(impls * impl)
+{
+    char key[20];
+    item k;
+    book_changeres result;
+    
+    /*    impl->ex, impl->number are the two fields */
+    /* how to remove them???? */
+
+    /* make a unique key */
+    k = key_int(key, C_ATTR_DEF, impl->first);
+
+    if (impl->first >= impl->total
+       || impl->exlist.exlist_len == 0)
+       result = delete_from_db(k, CONFIG);
+    else
+       result = write_db(k, impl, (xdrproc_t)xdr_impls, CONFIG);
+
+    return result;
+
+}
+
+
+void init_attr_types()
+{
+    read_db(str_key(ATTR_TYPE_KEY), &attr_types, sizeof(attr_types),
+           (xdrproc_t)xdr_attr_type_desc, CONFIG);
+}
+
+book_changeres change_attr_type(attr_type_desc *attrs)
+{
+    book_changeres result;
+    
+    result = write_db(str_key(ATTR_TYPE_KEY), attrs,
+                     (xdrproc_t)xdr_attr_type_desc, CONFIG);
+    init_attr_types();
+    return result;
+}
+
+book_changeres change_configdata(configdata * configent)
+{
+    book_changeres result;
+    item key;
+
+    key = key_string(C_GENERAL, configent->key);
+
+    
+    result = write_db(key, &configent->value,
+                     (xdrproc_t)xdr_cfgstr, CONFIG);
+
+    free(key.item_val);
+    return result;
+}
diff --git a/database/database.x b/database/database.x
new file mode 100644 (file)
index 0000000..1d93d9c
--- /dev/null
@@ -0,0 +1,571 @@
+/* This is the protocol for client database server interation */
+
+% /* register in arglist may be broken with solaris/pc */
+%#ifndef register
+%#define register
+%#endif
+typedef string machine_name<>;
+/* typedef string tokenname<>;*/
+typedef int tokenid;
+typedef int btime_t;
+typedef int bookuid_t;
+typedef string bitstringname<>;
+typedef string map_value<>;
+typedef int entityid;
+typedef entityid entitylist<>;
+typedef int hostid;
+typedef int hostgroup;
+typedef string reason<>;
+typedef string cfgstr<>;
+
+typedef opaque item<>;
+
+typedef item description;
+
+struct db_updaterec {
+       int update_time;
+       int reorganise_time;
+};
+
+struct slotgroup {
+       int             doy;
+       int             pod;
+       description     what;
+};
+
+typedef int book_key; /* (8bits pod, 9bits doy, 8bits lab) */
+
+
+/* there are two components to a token.  The first is the definition
+of bookings that can be made in the future, the second is bookings
+that can be made within four days */
+struct token {
+       description advance;
+       description late;
+};
+
+/* a request for a booking will contain a description of the slot */
+/* (pod.dow.desc) and the actual request that was made. */
+/* The request  will be stored in the user database  */
+/* the description is a request without wildcards! */
+struct book_req {
+       btime_t         when;           /* when booking was made */
+       slotgroup       slot;           /* machine group to charge */
+       description     mreq;           /* actual request */
+       tokenid         tnum;           /* token used   */
+       int             tok_cnt;        /* how many we think the user has, to avoid races */
+        int            number;         /* how many hosts to book */
+       bookuid_t       who_for;        /* who gets allocated */
+       bookuid_t       who_by;         /* who gets charged */
+};
+
+/* for adding tokens */
+struct tok_req {
+       tokenid tnum;
+       token tok;
+};
+
+struct class_req{
+       bookuid_t user;
+       tokenid tnum;
+       int count;
+};
+
+enum booking_status {
+       B_PENDING   = 1,
+       B_TENTATIVE = 2,
+       B_ALLOCATED = 3,  /* allocated, token kept */
+       B_RETURNED  = 4,  /* allocated, reuable token returned */
+       B_FREEBIE   = 5,  /* allocated, free period, token returned */
+       B_REFUNDED  = 6,  /* not allocated, token returned */
+       B_CANCELLED = 7   /* cancelled before allocation */
+};
+
+struct book_ch_ent {
+       bookuid_t       user;
+       int             when;
+       booking_status  status;
+       entitylist      allocations;
+       int             priv_refund;
+       int             tentative_alloc;
+};
+/* tentative alloc is a bit or of
+ *  1 tentative alloc
+ *  2 already logged on
+ */
+
+typedef book_ch_ent book_ch_list<>;
+
+struct book_chng {
+       book_key slot;
+       book_ch_list chlist;
+};
+
+struct book_set_claim {
+    book_key   slot;
+    bookuid_t  user;
+    int                when;   /* if zero, find a booking which is pending or allocated */
+    int                where;  /* if != -1, set bit3 if wsid matches */
+    int                claimtime;
+};
+
+enum machine_status {
+       BOOKINSERVICE = 1,
+       BOOKOUTOFSERVICE = 2
+};
+struct mach_chng {
+       machine_name machine;
+       machine_status newst;
+};
+
+struct workstation_state
+{
+       description state;
+       reason why;
+       btime_t time;
+       hostid host;
+};
+
+
+struct workstation_ch {
+       entityid ws;
+       workstation_state state;
+};
+
+struct host_ch {
+       hostid entity;
+       hostgroup clump;
+};
+/* stuff for the implications file */
+struct implication {
+       int startday;
+       int endday;
+       int startdow;
+       int enddow;
+       int starttime;
+       int endtime;
+       description included;
+       description excluded;
+       int attribute;
+};
+
+struct impls {
+    int first;
+    int total;
+    implication exlist<>;
+};
+
+enum bittype {
+       BIT_LAB = 1,
+       BIT_MAND = 2,
+       BIT_WEEK = 4,
+       BIT_OPTIONAL = 8,
+       BIT_BOOKABLE = 16,
+       BIT_OPEN = 32,
+       BIT_CLOSED = 64,
+       BIT_NORMALLAB = 128,
+       BIT_REUSABLE = 256,
+       BIT_FORGET = 512
+};
+
+
+struct bitentry {
+       bitstringname name;
+       int number;
+       bittype type;
+};
+
+enum nmapping_type {
+       M_HOST = 1,
+       M_WORKSTATION =2,
+       M_HOSTGROUP = 3,
+       M_TOKEN = 4,
+       M_ATTRIBUTE = 5, /* this includes labs */
+       M_CLASS = 6
+};
+
+enum config_type {
+    C_NUMBERS = 100,           /* string + int*/
+    C_NAMES = 200,             /* int + int*/
+    C_WORKSTATIONS = 3,                /* int */
+    C_WS_COUNT = 4,            /* desc */
+    C_HOSTGROUPS = 5,          /* int */
+    C_HOSTS = 6,               /* int */
+    C_LABS = 7,                        /* int */
+    C_TOKENS = 8,              /* int */
+    C_ALLOTMENTS = 9,          /* int */
+    C_GENERAL = 10,            /* string */
+    C_ATTR_TYPE = 11,          /* int */
+    C_ATTR_DEF = 12            /* int */
+};
+
+struct configdata {
+       cfgstr key;
+       cfgstr value;
+};
+struct name_mapping {
+       nmapping_type type;
+       int key;
+       map_value value;
+};
+
+
+/* stuff for change requests */
+enum book_changetype {
+       NO_CHANGE = 0,
+       CHANGE_WS = 1,
+       CHANGE_HOST = 2,
+       CHANGE_LAB = 3,
+       ADD_SERVER = 10,
+       DEL_SERVER = 12,
+       ADD_BOOKING = 20,
+/*     DEL_BOOKING = 22, */
+       CHANGE_BOOKING = 23,
+       CLAIM_BOOKING = 24,     /* record when a booking was claimed */
+       REMOVE_BOOKING = 25,
+       REMOVE_USER = 26,
+       ADD_TOKEN = 30,
+       ADD_CLASS = 40,
+/*     DEL_CLASS = 42, */
+       IMPLICATIONS = 50,  /* for the implications */          
+       ATTR_TYPE = 52,  /* for the bittable entries */
+       CONFIGDATA = 54,
+       SET_NAMENUM = 60
+};
+/* a header for the bitstrings and exclusions in the config file */
+struct attr_type_desc
+{
+       description mandatory;
+       description lab;
+       description all;
+       int open;
+       int closed;
+       int available;
+       int bookable;
+       int reusable;
+       int firmalloc;
+};
+
+enum db_number {
+       REPLICAS = 0,
+       BOOKINGS = 1,
+       CONFIG = 2,
+       RESERVATIONS = 3
+};
+
+const MAXDB = RESERVATIONS;
+
+union book_change switch (book_changetype chng)
+    {
+    case NO_CHANGE:
+       void;
+    case CHANGE_WS:
+       workstation_ch newws;
+    case CHANGE_HOST:
+       host_ch newhost;
+    case CHANGE_LAB:
+       host_ch newlab;
+    case ADD_SERVER:
+       machine_name server;
+    case DEL_SERVER:
+       machine_name delserver;
+    case ADD_BOOKING:
+       book_req booking;
+    case REMOVE_BOOKING:
+       book_key removal;
+    case REMOVE_USER:
+       bookuid_t user_togo;
+    case CHANGE_BOOKING:
+       book_chng changes;
+    case CLAIM_BOOKING:
+       book_set_claim aclaim;
+    case ADD_TOKEN:
+       tok_req addtok;
+    case ADD_CLASS:
+       class_req addcl;
+    case IMPLICATIONS:
+       impls implch;
+    case ATTR_TYPE:
+       attr_type_desc attrs;
+    case CONFIGDATA:
+       configdata configent;
+    case SET_NAMENUM:
+       name_mapping map;
+    };
+
+struct book_changeent {
+       btime_t time;  /*  the time the change was made to the db*/
+       machine_name machine; /* the machine the change came from */
+       int changenum; /* used when the change is stored in */
+               /* the log file so it can be given to another machine */ 
+       book_change thechange;
+};
+
+enum book_changeres {
+       C_OK            = 1,
+       C_NOMATCH       = 2,
+       C_SYSERR        = 3,
+       C_XDR           = 4,
+        C_LOG          = 5,
+       C_MISMATCH      = 6,
+       C_BAD           = 7,
+       C_FILE          = 8,
+       C_DUP           = 10,
+       C_NOPERM        = 12,
+       C_UNKNOWNHOST   = 14,
+       C_NOP           = 20,
+       C_NOUP          = 24,
+       C_NOMEM         = 25,
+       C_GDBM          = 30,
+       C_NOSERVER      = 35,
+       C_RPCERR        = 40
+};
+
+/* data structures to store in gdbm files. */
+/* machines */
+
+typedef struct descnode * desclist;
+struct descnode {
+       description data;
+       desclist next;
+};
+
+typedef struct slotnode *slotlist;
+struct slotnode {
+       slotgroup *data;
+       slotlist next;
+};
+
+
+typedef struct  intpairnode * intpairlist;
+struct intpairnode {
+       int data;
+       int num;
+       struct intpairnode * next;
+};
+
+struct deschead {
+       desclist data;
+       int num_els;
+};
+
+typedef struct  descpairnode * descpairlist;
+struct descpairnode {
+       description data;
+       int num;
+       descpairlist next;
+};
+typedef struct utilizationnode * utilizationlist;
+struct utilizationnode{
+          description machine_set;
+          int free;
+          int total;
+          utilizationlist next;
+};
+
+
+struct hostinfo
+{
+       entitylist workstations;
+       hostgroup clump;
+};
+
+
+struct hostgroup_info
+{
+    entitylist hosts;
+    entitylist labs;
+};
+
+/* bookings */
+
+/* Note on "claimed".
+ * "claimed" records when, and whether, booking was claimed.
+ * The least significant four bits determine whether they logged on.
+ * The remainder determine the number of seconds after allocation time
+ * that they logged on -  plus 30*60
+ * The whether field is 
+ *   bit 0 - have logged on (time is set)
+ *   bit 1 - were not logged on when grace expired
+ *   bit 2 - alloc was tentative
+ *   bit 3 - logged on to allocated ws
+ * 
+ * bits can be set but not cleared.
+ * once set (when bit 0 set) number can only be made smaller.
+ */
+
+enum claimflag {
+       DID_LOGIN = 1,
+       DID_DEFAULT= 2,
+       WAS_TENTATIVE = 4,
+       LOGIN_ON_ALLOC = 8
+};
+typedef struct bookslot *booklist;
+struct bookslot {
+       bookuid_t       who_for;
+       bookuid_t       who_by;
+       description     mreq;
+       description     charged;        /* which group was "charged" for this booking */
+       int             number;
+       tokenid         tnum;
+       btime_t         time;
+       booking_status  status;
+       entitylist      allocations;
+       int             claimed;        /* when, and whether, booking was claimed */
+       booklist        next;
+};
+
+struct bookhead {
+       booklist data;
+};
+       
+/* class db */
+
+/* user db */
+typedef struct  userbookingnode * userbookinglist;
+struct userbookingnode {
+       book_key        slot;
+       int             when;   /* when booking was made */
+       struct userbookingnode * next;
+};
+
+struct users
+{
+       userbookinglist bookings;       /* the bookings the user has made                       */
+       userbookinglist pastbookings;   /* bookings that are no longer pending                  */
+       intpairlist tokens;             /* the tokens the user has consumed                     */
+       intpairlist priv_tokens;        /* the tokens which have been refunded with privilege   */
+};
+
+struct usermap_req {
+       bookuid_t                       user;
+       userbookinglist         blist;
+};
+struct usermap_res {
+       book_changeres          res;    
+       booklist                blist;
+};
+       
+
+/* stuff for query requests */
+
+enum query_type {
+       M_FIRST = 0,
+       M_NEXT = 1,
+       M_MATCH = 2,
+       M_NEXT_PREFIX = 3 /* Next entry with same prefix (upto '_') */
+
+/* should domain be an optional query????? */
+/* this would be useful for the machines and replica */
+/* databases which are quite small */
+};
+
+struct query_req{
+       query_type type;
+       db_number database;
+       item key;
+};
+enum reply_res {
+       R_RESOK = 0,
+       R_INTERN  =1,
+       R_BADCASE = 3,
+       R_NOMATCH = 5,
+       R_NOMEM = 6,
+       R_NOFIRST = 10,
+       R_NONEXT = 12,
+       R_NODB = 14,
+       R_SYS = 16,
+       R_SERVADD = 18,
+       R_NOSERVER = 20
+};
+
+/* a generic reply */
+struct query_reply_data {
+       item key;
+       item value;
+};
+union query_reply switch ( reply_res error) {
+       case R_RESOK:
+               query_reply_data data;
+       default:
+               void;
+};
+
+
+/* stuff for the changes database */
+
+struct log_header {
+    int lastchangenum;
+    int lowestchangenum;
+    btime_t createstamp;
+    btime_t lastchangestamp;
+    
+};
+
+/* to send requests for changes to other places */
+enum change_reqres {
+       CR_RESOK = 0,
+       CR_NONE = 1
+};
+typedef        book_changeent changelist<>;
+
+union change_reply switch( change_reqres error)
+{
+case CR_RESOK:
+       changelist data;
+default:
+       void;
+};
+typedef  int replica_no;
+
+struct update_request {
+       replica_no minchange;
+       string replica<>;
+};
+
+/* 
+struct replica_ent {
+       replica_no heighest;
+       machine_id server;
+};
+*/
+struct transfer_reply{
+       reply_res error;
+       short port;
+};
+
+enum update_status {
+       NONE = 1,
+       COPY = 2,
+       FULL = 3 };
+
+program BOOK_DATABASE{
+       version BOOKVERS {
+               /* adds a machine to the description set */
+               book_changeres
+                       CHANGE_DB(book_changeent) = 1;
+               query_reply
+                       QUERY_DB(query_req) = 15;
+               utilizationlist
+                       FREE_SLOTS(slotlist) = 16;
+               usermap_res
+                       MAP_BOOKINGS(usermap_req) = 17;
+               void 
+                       SET_UPDATEST(update_status) = 40;
+               update_status
+                       GET_UPDATEST(void) = 42;
+
+               change_reply
+                       LAST_REPLICA_UPDATES(update_request) = 51;
+
+               transfer_reply
+                       TRANSFER_DB(void) = 60;
+               void
+                       TERMINATE(int) = 100;
+               bool
+                       DELETE_CHANGES(update_request) = 110;
+               void
+                       DB_REORGANIZE(void) = 112;
+       } =2;
+} = 0x20304052;
+
diff --git a/database/db_change.c b/database/db_change.c
new file mode 100644 (file)
index 0000000..2c5a113
--- /dev/null
@@ -0,0 +1,311 @@
+/* this module is for the changes that are made to the database */
+
+#include       <sys/types.h>
+#include       "db_header.h"
+#include       <time.h>
+#include       <stdio.h>
+
+char log_head_key[] = "__CHANGE_HEADER__";
+
+log_header replica_state;
+char *replica_machine = NULL;
+
+GDBM_FILE log_file = NULL;
+extern update_status update_st;
+
+static datum mk_host_chkey(char *host, int num)
+{
+       datum key;
+       key.dptr = (char*)malloc(strlen(host)+ 20);
+       sprintf(key.dptr, "%s_%d_", host, num);
+       key.dsize = strlen(key.dptr);
+       return key;
+}
+
+static datum mk_log_header_key(char *host)
+{
+       datum key;
+       key.dptr = (char*)malloc(strlen(host)+30);
+       strcpy(key.dptr, host);
+       strcat(key.dptr, log_head_key);
+       key.dsize = strlen(key.dptr);
+       return key;
+}
+
+void get_replica_header(char *host)
+{
+       datum key, value;
+       if (replica_machine && strcmp(replica_machine, host)==0)
+               return;
+       free(replica_machine); replica_machine = NULL;
+       key = mk_log_header_key(host);
+       replica_machine = strdup(host);
+    
+       value  = gdbm_fetch(log_file, key);
+       if (value.dptr == NULL)
+       {
+               replica_state.lastchangenum = 0;
+               replica_state.lowestchangenum = 1;
+       }
+       else
+       {
+               bcopy(value.dptr, &replica_state, sizeof(replica_state));
+               free(value.dptr);
+       }
+       free(key.dptr);
+}
+
+void sync_replica_header()
+{
+       datum key, content;
+    
+       if (replica_machine)
+       {
+               key = mk_log_header_key(replica_machine);
+               content.dptr = (char*)&replica_state;
+               content.dsize = sizeof(replica_state);
+               gdbm_store(log_file, key, content, GDBM_REPLACE);
+               free(key.dptr);
+       }
+}
+
+/* open the change database.  We have to read the header to ensure */
+ /* that it is there.. Perhaps this should be CREATE rather than INIT */
+int change_init(void)
+{
+       char buf[256];
+    
+       log_file = gdbm_open(make_pathname(LogFile), 1024, GDBM_WRCREAT, 0600, 0);
+
+       if (log_file == NULL) {
+               sprintf(buf, "Cannot open Log database: %s, error number %d\n",LogFile,gdbm_errno);
+               bailout(buf, 4);
+               return 0;
+       }
+       return 1;
+}
+
+void close_change(void)
+{
+       if (log_file  == NULL) return;
+       sync_replica_header();
+       gdbm_close(log_file);
+       log_file = NULL;
+}
+
+
+/* commit takes a change and writes it to the change file, updating */
+ /* the header so that the update number and lastchangestamp are */
+ /* correct.  It then writes the change, and finally the header.  The */
+ /* key is the change number preceded by some string so we will never */
+ /* guess it*/
+book_changeres commit(book_changeent *ch)
+{
+       datum key, content;
+
+
+       get_replica_header(ch->machine);
+
+       if (ch->changenum == 0)
+               ch->changenum = replica_state.lastchangenum+1;
+
+       if (replica_state.lastchangenum < replica_state.lowestchangenum)
+               replica_state.lowestchangenum = ch->changenum;
+       else
+               if (ch->changenum != replica_state.lastchangenum+1)
+               {
+                       char line[512];
+                       sprintf(line,"change number out of synch: low=%d last=%d this=%d\n", replica_state.lowestchangenum, replica_state.lastchangenum, ch->changenum);
+                       shout(line);
+                       /* failed e.g.
+                          Jun 12 11:45:48 change number out of synch: low=27488 last=34906 this=35031
+                       */
+/*         return C_SYSERR;*/
+               }
+    
+       replica_state.lastchangenum = ch->changenum;
+
+       key = mk_host_chkey(ch->machine, ch->changenum);
+
+       if (datum_encode(&content, ch, (xdrproc_t)xdr_book_changeent)== FALSE)
+               return C_XDR;
+
+       if (gdbm_store(log_file, key, content,GDBM_REPLACE) != 0 )
+       {
+               update_st = NONE;
+               /* should restore previous change state FIXME */
+               return C_DUP ;
+       }
+
+       free(key.dptr);
+    
+       /* now write out the change header */
+       sync_replica_header();
+       return C_OK;
+}
+
+void free_changes(change_reply reply)
+{
+       int i;
+
+    
+       if (reply.error != CR_RESOK)
+               return;
+       if (reply.change_reply_u.data.changelist_len == 0)
+               return;
+       for (i=0;i < reply.change_reply_u.data.changelist_len;i++)
+               xdr_free((xdrproc_t)xdr_book_changeent,
+                        (char*) &reply.change_reply_u.data.changelist_val[i]);
+       free(reply.change_reply_u.data.changelist_val);
+}
+
+
+/* returns the changes starting from number LASTNUM */
+/* the changes are unXDR'd as they have to be passed over the network */
+change_reply *SVC(last_replica_updates_2)(update_request *upr, struct svc_req *rq)
+{
+    static change_reply result;
+    int count;
+    int NumberOfChanges;
+
+    get_replica_header(upr->replica);
+    NumberOfChanges = replica_state.lastchangenum - upr->minchange;
+    
+    /* free the last result */
+    free_changes(result);
+
+    /* have gotten rid of this change number!!*/
+    if (upr->minchange+1 < replica_state.lowestchangenum)
+    {
+       result.error = CR_NONE;
+       return(&result);
+    }
+
+    /* make sure getting SOME changes */
+    if (NumberOfChanges <= 0)
+    {
+       result.error = CR_NONE;
+       return(&result);
+    }
+    
+    /* this is the most changes we transfer at a time */
+    if (NumberOfChanges > MAXCHANGES )
+       NumberOfChanges = MAXCHANGES;
+if (NumberOfChanges > 10) NumberOfChanges = 10;
+    result.error = CR_RESOK;
+    
+    /* allocate space for the result */    
+    result.change_reply_u.data.changelist_val = (book_changeent *) calloc(NumberOfChanges, sizeof(book_changeent));
+    result.change_reply_u.data.changelist_len = 0;
+
+
+    /* remember LASTNUM is the  last change received, want the next!*/
+    for (count = (upr->minchange) + 1; count <= (upr->minchange)+ NumberOfChanges; count++)
+    {
+       datum key, contents;
+       key = mk_host_chkey(upr->replica, count);
+       
+       contents = gdbm_fetch(log_file,key);
+       if (contents.dptr == NULL)
+       {
+           result.error = CR_NONE;
+           return &result;
+       }
+
+       datum_decode(&contents,
+                    &result.change_reply_u.data.changelist_val
+                     [result.change_reply_u.data.changelist_len++ ],
+                    (xdrproc_t)xdr_book_changeent);
+       
+       /* free the last lot of data fetched */
+       free(contents.dptr);
+       free(key.dptr);
+    }
+    
+    return(&result);
+}
+
+
+/* this is useless without a reorg at the end to free up the space */
+void change_del_changes(char *replica, int highest)
+{
+    /* delete all changes from replica < highest
+     * if highest == -1, delete all
+     * if highest > 1, delete at most 100 changes and none younger than 1 day...
+     */
+
+    int chnum;
+    int last = highest;
+    get_replica_header(replica);
+
+    if (tracing)
+       printf("database: del changes %d for %s - have %d-%d\n",
+              highest, replica, replica_state.lowestchangenum, replica_state.lastchangenum);
+    if (highest == -1)
+       last = replica_state.lastchangenum+1;
+
+    chnum = replica_state.lowestchangenum;
+    if (chnum <= 0)
+       chnum = 1;
+
+    if (highest > 1)
+    {
+       time_t now, then;
+       int nextlast;
+       now = then = time(0);
+       if (chnum + 100 < last)
+           last = chnum+100;
+       if (last > replica_state.lastchangenum+1)
+           last = replica_state.lastchangenum+1;
+       nextlast = last;
+       while (last > chnum && then > now-24*60*60)
+       {
+           datum key, contents;
+           last = nextlast;
+           nextlast = (chnum + last)/2;
+           key = mk_host_chkey(replica, last);
+           contents = gdbm_fetch(log_file, key);
+           if (contents.dptr) {
+               book_changeent thech;
+               memset(&thech, 0, sizeof(thech));
+               if (datum_decode(&contents,
+                            &thech, (xdrproc_t)xdr_book_changeent)) {
+                   then = thech.time;
+                   xdr_free((xdrproc_t)xdr_book_changeent, (char*) &thech);
+               }
+           }
+       }
+    }
+    if (tracing)
+       printf("database:   will remove %d-%d\n", chnum, last-1);
+    for (; chnum < last ; chnum++)
+    {
+       datum key;
+       key = mk_host_chkey(replica, chnum);
+       gdbm_delete(log_file, key);
+       free(key.dptr);
+    }
+    if (highest == -1)
+    {
+       replica_state.lowestchangenum = 1;
+       replica_state.lastchangenum = 0;
+    }
+    else
+       replica_state.lowestchangenum = chnum;
+    sync_replica_header();
+}
+
+
+bool_t *SVC(delete_changes_2)(update_request *req, struct svc_req *rpstp)
+{
+    static  bool_t result;
+    result = TRUE;
+
+    svc_sendreply(rpstp->rq_xprt, (xdrproc_t) xdr_bool, (caddr_t)&result);
+
+    change_del_changes(req->replica, req->minchange);
+    
+    return(NULL);
+    
+}
+
diff --git a/database/db_decode.c b/database/db_decode.c
new file mode 100644 (file)
index 0000000..d853774
--- /dev/null
@@ -0,0 +1,63 @@
+#include       "db_header.h"
+
+/* these routines are to un XDR structures.  There are two forms */
+ /* provided, a plain for a gdbm data structure, and an opaque for */
+ /* data delivered across the network */
+
+bool_t datum_decode (datum * content, void * data, xdrproc_t xdr_fn )
+{
+       XDR xdrs;
+       xdrmem_create(&xdrs, content->dptr,content->dsize, XDR_DECODE);
+       return (xdr_fn(&xdrs, data)) ;
+}
+
+bool_t item_decode (item * val, void *data, xdrproc_t xdr_fn)
+{
+       XDR xdrs;
+    
+       xdrmem_create(&xdrs, val->item_val,val->item_len, XDR_DECODE);
+       return (xdr_fn(&xdrs, data));
+}
+
+static char *buf = NULL;
+static int bufsize = 0;
+
+bool_t item_encode(item * val, void * data, xdrproc_t xdr_fn)
+{
+       XDR xdrs;
+       if (buf == NULL)
+       {
+               buf = malloc(bufsize = 16384);
+       }
+       xdrmem_create(&xdrs, buf, bufsize-1024, XDR_ENCODE);
+       while (!xdr_fn(&xdrs, data))
+       {
+               free(buf);
+               buf = malloc(bufsize += 8192);
+               xdrmem_create(&xdrs, buf, bufsize-1024, XDR_ENCODE);
+       }
+    
+       val->item_val = buf;
+       val->item_len = XDR_GETPOS(&xdrs);
+       return TRUE;
+}
+
+bool_t datum_encode(datum * val, void * data, xdrproc_t xdr_fn)
+{
+       XDR xdrs;
+       if (buf == NULL)
+       {
+               buf = malloc(bufsize = 16384);
+       }
+       xdrmem_create(&xdrs, buf, bufsize, XDR_ENCODE);
+       while (!xdr_fn(&xdrs, data))
+       {
+               free(buf);
+               buf = malloc(bufsize += 8192);
+               xdrmem_create(&xdrs, buf, bufsize, XDR_ENCODE);
+       }
+    
+       val->dptr = buf;
+       val->dsize = XDR_GETPOS(&xdrs);
+       return TRUE;
+}
diff --git a/database/db_extern.h b/database/db_extern.h
new file mode 100644 (file)
index 0000000..6a48cb3
--- /dev/null
@@ -0,0 +1,6 @@
+
+extern GDBM_FILE db_file[];
+extern char *DatabaseName[];
+
+extern int tracing;
+extern attr_type_desc attr_types;
diff --git a/database/db_globals.h b/database/db_globals.h
new file mode 100644 (file)
index 0000000..4196816
--- /dev/null
@@ -0,0 +1,15 @@
+/* some global variables.  These are only defined in the main module */
+
+/* MAXDB is defined in "common.x" */
+GDBM_FILE db_file[MAXDB +1 ] ;
+char *DatabaseName[MAXDB +1 ] = {"db_replicas",
+                                    "db_bookings", 
+                                    "db_config",
+                                    "db_reservation",
+                                     };
+
+
+
+
+int tracing = 0;
+attr_type_desc attr_types;
diff --git a/database/db_header.h b/database/db_header.h
new file mode 100644 (file)
index 0000000..1238a6d
--- /dev/null
@@ -0,0 +1,131 @@
+/* standard include files for all modules associated with the database */
+ /* manager  */
+#include       <rpc/rpc.h>
+#include       <string.h>
+#include       <gdbm.h>
+#include       "../database/database.h"
+
+#include       "../lib/misc.h"
+#ifdef linux
+#define SVC(x) x ## _svc
+#else
+#define SVC(x) x
+#endif
+
+/*  this is  the directory containing the database files */
+
+
+/* some special keys for the database */
+#define DOMAIN_KEY "_DOMAIN_"  /* the domain of the database.. not */
+                              /* always used */
+#define STATUS_KEY "_STATUS_" /* the status of the database.. the */
+                             /* update time, reorg time */
+#define ATTR_TYPE_KEY "_ATTR_TYPE_1_"
+#define HOST_SETS "_HOSTS_SETS_" /* key of the sets in the hosts */
+                                 /* database */
+#define LogFile "db_log"
+
+/* some stuff for bitstrings */
+#define BITSTRLEN 16  /* size in characters */
+#define BITINDMAX BITSTRLEN * sizeof(char)  *8  /* size in bits */
+
+#define MAXCHANGES 20  /* the maximum number of changes to transfer in */
+                      /* one go */
+
+#define PRIV_REFUND_FLAG 0x8000
+
+#ifdef _AM_MAIN_
+#include       "db_globals.h"
+#else
+#include       "db_extern.h"
+#endif
+
+#include       <stdio.h>
+
+typedef struct CLIENTHOST {
+    CLIENT * client;
+    char * host;
+} CLIENTHOST;
+
+#define Malloc(ptr,type) ptr=(type *)malloc(sizeof(type));if (ptr == NULL) {printf("no memory\n"); exit(33); }
+
+
+
+#include       "common.h"
+
+/* from descpairlist.c */
+void add_to_descpairlist(descpairlist * ch, description * new, int num);
+book_changeres del_from_descpairlist(descpairlist * ch, description *name);
+descpairlist finddesc(descpairlist *ch, description * key);
+void incdesc(descpairlist *ch, description * key, int diff);
+
+/* from db_utils.c */
+query_reply * generic_query(query_req * query);
+int db_open(db_number db, int gdbm_mode);
+void close_db(db_number db);
+book_changeres read_db(item key, void * data, int data_size, xdrproc_t xdr_fn,  db_number db );
+book_changeres update_status_key(db_number db, int update_time, int reorganise_time);
+void get_status_key(db_number db, time_t *update_time, time_t *reorganise_time);
+book_changeres write_db(item key, void * data, xdrproc_t xdr_fn, db_number db);
+book_changeres delete_from_db(item key, db_number db);
+
+/* from config_db.c */
+book_changeres change_implication(impls * impl);
+void init_attr_types();
+book_changeres change_attr_type(attr_type_desc *attrs);
+book_changeres change_configdata(configdata * configent);
+
+
+/* from db_change.c */
+void get_replica_header(char *host);
+void sync_replica_header();
+int change_init(void);
+void close_change(void);
+book_changeres commit(book_changeent *ch);
+void free_changes(change_reply reply);
+void change_del_changes(char *replica, int highest);
+
+/* from replica_db.c */
+int replica_grow(machine_name machine, replica_no height);
+book_changeres replica_add(machine_name server);
+book_changeres replica_del(machine_name server);
+int replica_init();
+
+/* from db_transfer */
+void collect_xfer(void);
+void send_database(int sock, db_number db);
+
+/* from users_db.h */
+book_changeres add_user_booking(book_req * booking, int clientreq);
+book_changeres change_user_booking(book_chng * bchanges, int *refund, int *tokens, int *who_by);
+book_changeres del_user_booking(bookuid_t user, int when, book_key *bk);
+book_changeres del_user(bookuid_t user);
+
+/* from bookings_db.c */
+book_changeres add_booking_to_list(bookhead * blist, book_req *req);
+book_changeres incuser(bookhead *bh, book_req *req);
+book_changeres read_bookhead(bookhead *bh, book_key *key);
+book_changeres write_bookhead(bookhead *bh, book_key *key);
+book_changeres add_booking(book_req * booking);
+int inc_usage_count(slotgroup *slot, int number);
+book_changeres change_booking(book_chng *bchanges, int *refund, int *tokens, int *who_by);
+booklist map_bookings(bookuid_t user, userbookinglist list);
+book_changeres remove_booking(book_key * removal);
+
+/* from classes_db.c */
+book_changeres add_class(class_req * cl_req);
+book_changeres set_allotment(int uid, int tok, int cnt);
+
+
+/* from freeslots.c */
+utilizationlist *free_slots(slotlist sl);
+
+/* from main.c */
+bool_t open_dbs(int gdbm_stat);
+void close_dbs(void);
+void init_db(void);
+void close_all(void);
+void start_helper(void);
+void kill_helper(void);
+SIGRTN die();
+void check_update_status(void);
diff --git a/database/db_helper.c b/database/db_helper.c
new file mode 100644 (file)
index 0000000..0a0bdb6
--- /dev/null
@@ -0,0 +1,442 @@
+/* db_helper: a program to fetch updates from other servers in the network and */
+ /* apply them to this manager */
+/* This program is usually  forked (and execed) when the manager starts up. */
+ /* There is  a good reason for it not being a routine inside the manager that */
+ /* is just forked.  See the comment inside the manager that forks this process */
+
+#include       "db_header.h"
+#include       <stdio.h>
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <string.h>
+#ifndef apollo
+#include       <sys/types.h>
+#include       <netinet/in.h>
+#endif
+#include       <string.h>
+#include       <rpc/pmap_clnt.h>
+
+#include       <netdb.h>
+#include       <sys/socket.h>
+#include       "../lib/skip.h"
+
+GDBM_FILE server_file = NULL; 
+static char *ServerName;
+CLIENT * manager_cl;
+char errbuff[1024];
+
+int tracing = 0;
+
+/* open_hdb opens the server database, and establishes a */
+ /* connection to the manager. */
+void open_hdb()
+{
+
+       /* now for some initialization */
+       /* set up connection to manager */
+       ServerName = get_myhostname();
+    
+       manager_cl = clnt_create("localhost",BOOK_DATABASE,BOOKVERS,"udp");
+       if (manager_cl == NULL) {
+               /* no connection to server */
+               clnt_pcreateerror("localhost");
+               exit(1);
+       }
+}
+
+/* maintain a list of down servers in a skip list... */
+
+typedef struct downserv
+{
+       char *server;
+       int change_num;
+       int up_to_date;
+} * downserv;
+
+int ds_cmp(downserv a, downserv b, char *c)
+{
+       if (b) c = b->server;
+       return strcmp(a->server, c);
+}
+
+void ds_free(downserv a)
+{
+       free(a->server);
+       free(a);
+}
+
+void *down_list;
+void down_init()
+{
+       down_list = skip_new(ds_cmp, ds_free, NULL);
+}
+
+void add_down(char *server, int chnum)
+{
+       downserv * dsp, ds;
+       dsp = skip_search(down_list, server);
+       if (dsp)
+       {
+               (*dsp)->change_num = chnum;
+       }
+       else
+       {
+               ds = (downserv)malloc(sizeof(struct downserv));
+               ds->server = strdup(server);
+               ds->change_num = chnum;
+               ds->up_to_date = 0;
+               skip_insert(down_list, ds);
+       }
+}
+
+void rem_down(char *server)
+{
+       skip_delete(down_list, server);
+}
+downserv *first_down()
+{
+       return skip_first(down_list);
+}
+downserv *next_down(downserv *d)
+{
+       return skip_next(d);
+}
+
+void print_slot(FILE *f, book_key slot)
+{
+       int pod, doy, lab;
+       book_key_bits(&slot, &doy, &pod, &lab);
+       fprintf(f, "d%d p%d l%d", doy, pod, lab);
+}
+
+static int get_latestchanges(char * changesource, char *changeowner, int *changes)
+{
+       CLIENT * replica_cl;
+       change_reply * result;
+       book_changeres *  ch_res;
+       bool_t failed = FALSE;
+       book_changeent * chptr;
+       int index;
+       int rv = 0;
+       struct timeval tv;
+
+       /* set up connection */
+       replica_cl = clnt_create(changesource,BOOK_DATABASE,BOOKVERS,"udp");
+       if (replica_cl == NULL) {
+               /* no connection to server */
+               /* clnt_pcreateerror(changesource);*/
+               return -1;
+       }
+       tv.tv_sec = 5;
+       tv.tv_usec = 0;
+       clnt_control(replica_cl, CLSET_TIMEOUT, (char*)&tv);
+       tv.tv_sec = 2;
+       clnt_control(replica_cl, CLSET_RETRY_TIMEOUT, (char*)&tv);
+    
+       while (failed == FALSE)
+       {
+               update_request ur;
+
+               ur.minchange = *changes;
+               ur.replica = changeowner;
+               result = last_replica_updates_2(&ur, replica_cl);
+
+               if (result == NULL)  /* no contact */
+               {
+                       if (tracing) printf("Call %s for %d of %s got NULL\n", changesource, *changes, changeowner);
+                       rv = -1;
+                       break;
+               }
+               if (tracing) printf("Call %s for %d of %s got %d\n", changesource, *changes, changeowner, result->error);
+               if (result->error == CR_NONE)
+               {
+                       rv = 1;
+                       break;
+               }
+       
+               if (result->error != CR_RESOK) /* no changes */
+                       break;
+
+               /* now process results */
+               chptr = (book_changeent *)result->change_reply_u.data.changelist_val;
+               for (index = 0;!failed && index < result ->change_reply_u.data.changelist_len;index++, chptr++)
+               {
+
+                       if (chptr->changenum == 0)
+                       {
+                               bailout("HELPER, got change with 0 changenumber\n", 9);
+                               break;
+                       }
+                       ch_res = change_db_2(chptr,manager_cl);
+                       /* only bailout if no contact AND parent is 0*/
+                       /* It is possible that manager could have been busy */
+                       if ((ch_res == NULL) && (getppid() == 1))
+                               bailout("HELPER could not contact manager\n",9);
+
+                       /* this is a temporary aberation */
+                       if (ch_res == NULL)  
+                       {
+                               if (tracing) printf("NULL returned for change %d\n", chptr->changenum);
+                               failed = TRUE;
+                               break;
+                       }
+           
+                       /* update the server table */
+                       if (*ch_res == C_OK)
+                               *changes = chptr->changenum;
+                       else
+                       {
+                               if (tracing)
+                               {
+                                       printf("got status %d for change %d\n", *ch_res, chptr->changenum);
+                                       printf(" change type is %d\n", chptr->thechange.chng);
+                                       if (chptr->thechange.chng == CHANGE_BOOKING) {
+                                               printf(" change_booking\n  slot");
+                                               print_slot(stdout, chptr->thechange.book_change_u.changes.slot);
+                                               printf("\n");
+                                       }
+                                       if (chptr->thechange.chng ==  CLAIM_BOOKING) {
+                                               printf(" claim booking\n  slot");
+                                               print_slot(stdout, chptr->thechange.book_change_u.aclaim.slot);
+                                               printf("\n  user %d\n  time %d\n  where %d\n  claim %d\n",
+                                                      chptr->thechange.book_change_u.aclaim.user,
+                                                      chptr->thechange.book_change_u.aclaim.when,
+                                                      chptr->thechange.book_change_u.aclaim.where,
+                                                      chptr->thechange.book_change_u.aclaim.claimtime);
+                                               break;
+                                       }
+                               }
+                               if (*ch_res != C_NOUP)
+                               { 
+                                       update_status stat = COPY;
+
+                                       /* tell the manager to only accept copied updates */
+                                       /* stop it from becoming further out of date */
+                                       set_updatest_2(&stat, manager_cl);
+                               }
+                               failed = TRUE;
+                       }
+               }
+               /* free the result */
+               xdr_free((xdrproc_t)xdr_change_reply, (char*) result);
+       }
+    
+       /* free handle */
+       clnt_destroy(replica_cl);
+
+       /* and the remaining result */
+       if (result != NULL)
+               xdr_free((xdrproc_t)xdr_change_reply, (char*) result);
+    
+       return rv;
+}
+
+
+/* get_updates: poll the other servers in the network for any changes */
+ /* they may have recieved */
+void get_updates()
+{
+    
+       static char dummy[] = "first";
+       static query_req request;
+       query_reply * result;
+       char * replicaserver = NULL;
+       downserv *one_down;
+
+       bool_t up_to_date = TRUE;
+       static char next_server[100] = "" ; /* next server to consider discarding changes from */
+       char this_server[100]; /* server we are considering discarding changes from */
+       int minupdate_sent = -1; /* the minimum update number received by all servers */
+       int anydown = 0; /* if any replicas are down, we cannot discard changes */
+       static int am_a_server = 0;     /* true if my name is found in list of servers */
+       int new_am_a_server = 0; /* true if we find out selves in list of servers */
+    
+    
+       request.key.item_val = dummy;
+       request.key.item_len = 4;
+       request.type = M_FIRST;
+       request.database = REPLICAS;
+
+       /* get the first replica key from the manager (and result!) */
+       result = query_db_2(&request, manager_cl);
+       if (result == NULL) {
+               if (tracing) printf("Could not contact manager \n");
+               if (getppid() == 1)
+                       exit(1);
+               return;
+       }
+       request.type = M_NEXT;
+
+       for (one_down = first_down() ; one_down ; one_down = next_down(one_down))
+               (*one_down)->up_to_date = 1;
+
+       strcpy(this_server, next_server);
+       next_server[0] = '\0';
+       while (result-> error == R_RESOK)
+       {
+               int change_num;
+               /* for each replica:
+                  - If it is me, skip it
+                  - ask for more changes from where we are up to
+                  apply them
+                  - if it is not talking, remember it is broken
+                  else ask for changes from all non-talking hosts
+                  and find out where it is upto wrt me
+               */
+
+               change_num = *(int*)result->query_reply_u.data.value.item_val;
+               change_num = ntohl(change_num);
+       
+               /* set up the next possible request here as the pointer may change if */
+               /* other RPC calls are made in the body of the loop */
+               /* copy the result, so it can be freed */
+               request.key.item_len = result-> query_reply_u.data.key.item_len;
+               request.key.item_val =
+                       memdup(result->query_reply_u.data.key.item_val,
+                              request.key.item_len);  
+
+
+               replicaserver = strndup(result ->query_reply_u.data.key.item_val, result->query_reply_u.data.key.item_len);
+               if (next_server[0] == '\0')
+                       strcpy(next_server, replicaserver);
+               if (this_server[0] == '\0') strcpy(this_server, next_server);
+               if (strcmp(ServerName, replicaserver)==0)
+               {
+                       new_am_a_server = 1;
+               }
+               else
+               {
+                       switch(get_latestchanges(replicaserver, replicaserver, &change_num))
+                       {
+                       case -1:                /* could not contact */
+                               add_down(replicaserver, change_num); anydown = 1;
+                               break;
+                       case 0:         /* got some changes, but not all */
+                               up_to_date = 0;
+                               rem_down(replicaserver);
+                               break;
+                       case 1:         /* got all changes, see what else we can discover */
+                               rem_down(replicaserver);
+                               for (one_down = first_down(); one_down ; one_down = next_down(one_down))
+                               {
+                                       switch(get_latestchanges(replicaserver, (*one_down)->server, &(*one_down)->change_num))
+                                       {
+                                       case -1:        /* golly, now this one is down too */
+                                               break;
+                                       case 0:
+                                               (*one_down)->up_to_date = 0; /* should only set this if replicaserver is in full service */
+                                               break;
+                                       case 1: /* happiness, atleast wrt replicaserver */
+                                               break;
+                                       }
+                               }
+                               /* replica is up, maybe see where they are wrt me */
+                               if (am_a_server && ! anydown)
+                               {
+                                       /* ask for me in their replica list */
+                                       CLIENT *repl_cl = clnt_create(replicaserver, BOOK_DATABASE, BOOKVERS, "udp");
+                                       if (repl_cl == NULL)
+                                               anydown = 1;
+                                       else
+                                       {
+                                               static query_req rq;
+                                               query_reply *rs;
+                                               rq.key.item_val = this_server;
+                                               rq.key.item_len = strlen(rq.key.item_val);
+                                               rq.type = M_MATCH;
+                                               rq.database = REPLICAS;
+                                               rs = query_db_2(&rq, repl_cl);
+                                               if (rs == NULL) anydown = 1;
+                                               else if (rs->error == R_RESOK &&
+                                                        (minupdate_sent == -1 || minupdate_sent > ntohl(*(int*)rs->query_reply_u.data.value.item_val)))
+                                                       minupdate_sent = ntohl(*(int*)rs->query_reply_u.data.value.item_val);
+                                               clnt_destroy(repl_cl);
+                                       }
+                               }
+                               break;
+                       }
+               }
+       
+           
+               /*  free previous result */
+               xdr_free((xdrproc_t)xdr_query_reply, (char*) result);
+       
+               /* get next replica server from our manager */
+               result = query_db_2(&request, manager_cl);
+               if ((result == NULL) && (getppid() == 1))
+                       /* our manager could not be  contacted and has disappeared */
+                       /* i.e. our parent is init */
+                       bailout("HELPER: could not contact my manager",4);
+
+               /* this is a temporary aboration the manager is busy doing */
+               /* other things*/
+               if (result == NULL)
+                       return;
+               if (result->error == R_RESOK && strcmp(this_server, replicaserver)==0)
+                       next_server[0] = 0;
+               free(replicaserver);
+       }
+       /* if up to date, move server into update mode */
+       /* only if not in no update mode */
+       for (one_down = first_down() ; up_to_date && one_down ; one_down = next_down(one_down))
+       {
+               if (!(*one_down)->up_to_date)
+               {
+                       if (tracing)
+                               printf("down host %s is not up-to-date(%d)\n", (*one_down)->server, (*one_down)->change_num);
+                       up_to_date = 0;
+               }
+       }
+
+       if (tracing)
+               printf("uptodate = %d am_a_server = %d\n", up_to_date, new_am_a_server);
+       if (up_to_date && new_am_a_server)
+       {
+               update_status * oldst;
+               static update_status stat = FULL;
+
+               oldst = get_updatest_2((void*)NULL, manager_cl);
+               if ((oldst != NULL) && (*oldst != NONE))
+                       set_updatest_2(&stat, manager_cl);
+       }
+       if (minupdate_sent > 1 && up_to_date && this_server[0] && ! anydown)
+       {
+               /* DISCARD changes < minupdate_sent */
+               update_request rq;
+               if (tracing)
+                       printf("can discard changes up to %d from %s\n", minupdate_sent, this_server);
+               rq.minchange = minupdate_sent;
+               rq.replica = this_server;
+               delete_changes_2(&rq, manager_cl);
+       }
+       am_a_server = new_am_a_server;
+}
+
+
+int main (int argc, char *argv[])
+{
+       static struct timeval tv = {5,0};
+       static struct timeval total = {20,0};
+        
+       /* open our database of servers, and get connection to our */
+       /* manager */
+       if (argc == 2 && strcmp(argv[1], "trace")==0)
+               tracing = 1;
+    
+       open_hdb();
+       down_init();
+
+       /* set faster timeouts for talking to portmaps */
+       pmap_settimeouts(tv, total);
+
+       /* now just look for updates from other servers in the */
+       /* network */
+       while (1) {
+               get_updates();
+               sleep(60);
+       }
+}
+
+
+
+
diff --git a/database/db_transfer.c b/database/db_transfer.c
new file mode 100644 (file)
index 0000000..2f7f9c1
--- /dev/null
@@ -0,0 +1,224 @@
+/* this module contains routines to set up the transfer of the */
+ /* database (from this manager).  The db_receive program will handle */
+ /* the other end */
+
+#include       "db_header.h"
+
+#include       <sys/socket.h>
+#include       <unistd.h>
+#include       <netdb.h>
+#include       <errno.h>
+#include       <sys/wait.h>
+#include       <signal.h>
+#include       <arpa/inet.h>
+extern update_status update_st;
+
+static int xferers[10];
+static int xfercnt = 0;
+
+void collect_xfer(void)
+{
+       int pid;
+
+       while ((pid =
+#ifdef ultrix
+               wait3(NULL, WNOHANG, (char*)NULL)
+#else
+               waitpid((pid_t)-1,(int*)NULL ,WNOHANG)
+#endif
+                      ) > 0)
+       {
+               int i;
+               for (i=0 ; i<xfercnt ; i++)
+                       if (xferers[i] == pid)
+                               xferers[i] = xferers[--xfercnt];
+       }
+       if (xfercnt == 0)
+       {
+               update_status st = COPY;
+               if (update_st == NONE)
+                       SVC(set_updatest_2)(&st, NULL);
+       }
+}
+
+/* given  a socket, sends the contents of the database down */
+void send_database(int sock, db_number db)
+{
+        
+       GDBM_FILE database;
+       char * db_path;
+       datum key, value;
+       int datasize;
+       int sent;
+
+       database = db_file[db];
+       db_path = DatabaseName[db];
+    
+       if (database == NULL) {
+               printf("No file descriptor\n");
+               return;
+       }
+
+       /* send off id of database file, so the other end knows which one */
+       /* it is.  Send off - the length of the database name. The path is */
+       /* setup at the other end */
+       datasize = htonl(-strlen(db_path));
+       sent = write(sock, &datasize, sizeof(datasize));
+       if (sent != sizeof(datasize)) {
+               bailout("TRANSFER error sending... (len) bye\n",6);
+       }
+       sent = write(sock, db_path, strlen(db_path));
+       if (sent != strlen(db_path)) {
+               shout("TRANSFER: error sending... (data) bye\n");
+       }
+    
+       /* now the contents */
+       key = gdbm_firstkey(database);
+
+       while (key.dptr != NULL)
+       {
+               datum newkey;
+               value = gdbm_fetch(database, key);
+
+               /* first send the key (preceeded by it's length) */
+               datasize = htonl(key.dsize);
+               write(sock, &datasize, sizeof(datasize));
+               write(sock, key.dptr, key.dsize);
+
+               /* now send the value */
+               datasize = htonl(value.dsize);
+               write(sock, &datasize, sizeof(datasize));
+               write(sock, value.dptr, value.dsize);
+
+       
+               /* get the next key in the database */
+               /* free the data, and possibly the old key?? */
+               free(value.dptr);
+       
+               newkey = gdbm_nextkey(database, key);
+               free(key.dptr);
+               key = newkey;
+       }
+}
+
+/* Returns the port on which a child is listening for a request to */
+ /* connect.  The child then transfers the contents of the database */
+ /* through this stream.  */
+
+/* Can't just wait for child, as have to return something.   Best we */
+ /* can do is wait for the last child */
+/* change to priv sockets!!! */
+transfer_reply * SVC(transfer_db_2)(void *arg, struct svc_req *q)
+{
+       static transfer_reply result;
+       int pid;
+       int sock;
+       int sk;  /* for the child */
+       int port = 1023;  /* a privileged one */
+       struct sockaddr_in from;
+       unsigned int fromlen;
+       struct sockaddr_in sin;
+       unsigned int sinlen;
+       int datasize;
+       extern book_changeres * change_db();
+       extern char * get_myhostname();
+       char buf[256];
+       int dbnum;
+
+       collect_xfer();
+
+       if (xfercnt >5)
+       {
+               result.error = R_SYS;
+               return &result;
+       }
+    
+       
+       /* pick up the last child */
+       sock = socket(AF_INET, SOCK_STREAM,0);
+       /* now bind */
+       memset(&sin, 0, sizeof(sin));
+
+       sin.sin_family = AF_INET;
+       if (bind (sock, (struct sockaddr *) &sin,sizeof(sin)) != 0)
+       {
+               perror("Bind failed\n");
+               shout("Bind failed");
+               result.error = R_SYS;
+               return(&result);
+       }
+
+       /* now find out what happened with bind!!! */
+       sinlen = sizeof(sin);
+       getsockname(sock, (struct sockaddr *)&sin, &sinlen);
+
+    
+       /* set up port */
+       port = sin.sin_port;
+    
+       if (sock == -1) {
+               shout("Sock == -1");
+               result.error = R_SYS;
+               return(&result);
+       }
+    
+       result.port = ntohs(port);
+       close_dbs();
+    
+       switch (pid=fork()) {
+       case -1:
+               shout("Attempting copy: Fork failed");
+               result.error = R_SYS;
+               close(sock);
+               open_dbs(GDBM_WRCREAT);
+               return(&result);
+               break;
+       case 0:   /* child */
+               break;
+       default:  /* parent */
+               xferers[xfercnt++] = pid;
+               close(sock);
+               result.error = R_RESOK;
+               /* put the database in no update mode */
+               update_st = NONE;
+               open_dbs(GDBM_READER);
+
+               return(&result);
+       }
+
+       /* if no reply in 10 mins then die */
+       /* This should be fixed in other places... why does the connection */
+       /* fail sometimes.... #ifdef apollo perhaps */
+       signal(SIGALRM, SIG_DFL);
+       alarm(600);
+
+       open_dbs(GDBM_READER);
+       /* child waits for connection then sends info down the wire */
+       /* watch for attempt to connect */
+       listen (sock,2);
+
+       /* accept first connection, from any address*/
+       /* FIXME... check this the address of the requesting server!! */
+    
+       fromlen = sizeof(from);
+       sk = accept(sock,(struct sockaddr *)&from,&fromlen);
+       close(sock);
+       alarm(0);
+       /* check it */
+       if (sk == -1)
+               exit(5);
+       sprintf(buf,"connection accpted from %s\n",inet_ntoa(from.sin_addr));
+       shout(buf);
+
+       /* now send stuff down the wire */
+       for (dbnum =0 ; dbnum <= MAXDB ;dbnum++) 
+               send_database(sk, dbnum);
+    
+       /* send end of trasmition signal after all transfered*/
+       datasize = htonl(0);
+       write(sk, &datasize, sizeof(datasize));
+    
+       close_dbs();
+       close(sk);
+       exit(0);
+}
diff --git a/database/db_utils.c b/database/db_utils.c
new file mode 100644 (file)
index 0000000..d78a58b
--- /dev/null
@@ -0,0 +1,355 @@
+/* some utilities for accesing the databases */
+
+#include       <time.h>
+#include       <unistd.h>
+#include       "db_header.h"
+#include       <stdio.h>
+#include       <sys/types.h>
+#include       <sys/stat.h>
+
+extern update_status update_st;
+
+
+/* GENERIC_QUERY: provides the basic query operations (firstkey, */
+ /* nextkey and matchkey for any database.  Because all the databases */
+ /* are stored in XDR format, the result of the query is sent back in */
+ /* this form for the client to interpret.  */
+query_reply * generic_query(query_req * query)
+{
+       static datum key =  {0,0 } ;
+       static datum val =  {0,0 } ;
+       static query_reply result;
+       int plen;
+
+       /* free the gdbm key and data, after the result is returned */
+       /* ie at the start of the next call */
+       if (key.dptr) free(key.dptr);
+       if (val.dptr) free(val.dptr);
+       key.dptr = val.dptr = NULL;
+
+       /* check db actually exists */
+    
+       if  (query->database<0 || query->database >3 || db_file[query->database] == NULL) {
+               result.error = R_NODB;
+               return(&result);
+       }
+    
+       result.error = R_RESOK;
+       switch (query->type  ) {
+       case M_FIRST: /* return the first key in the db and the data as */
+               /* well */
+               key = gdbm_firstkey(db_file[query->database]);
+               if (key.dptr
+                   && strncmp(key.dptr, "_STATUS_", 8)==0)
+               {
+                       datum k2;
+                       k2 = gdbm_nextkey(db_file[query->database], key);
+                       free(key.dptr);
+                       key= k2;
+               }
+               if (key.dptr != NULL) {
+                       result.query_reply_u.data.key.item_len = key.dsize;
+                       result.query_reply_u.data.key.item_val = key.dptr;
+                       val = gdbm_fetch(db_file[query->database],key);
+               }
+               else
+                       result.error = R_NOFIRST;
+               break;
+       case M_NEXT: /* given a key return the next key and the data */
+               /* associated with it */
+               key.dsize = query->key.item_len;
+               key.dptr = query->key.item_val;
+// printf("1key db=%d is %.*s %d\n", query->database, key.dptr?key.dsize:0, key.dptr?key.dptr:"(null)", key.dsize);
+               key = gdbm_nextkey(db_file[query->database],key);
+// printf("2key is %.*s %d\n", key.dptr?key.dsize:0, key.dptr?key.dptr:"(null)", key.dsize);
+               if (key.dptr && strncmp(key.dptr, "_STATUS_",8)==0)
+               {
+                       datum k2 = gdbm_nextkey(db_file[query->database],key);
+// printf("3k2 is %.*s %d\n", k2.dptr?k2.dsize:0, k2.dptr?k2.dptr:"(null)", k2.dsize);
+                       free(key.dptr);
+                       key = k2;
+// printf("4key is %.*s %d\n", key.dptr?key.dsize:0, key.dptr?key.dptr:"(null)", key.dsize);
+               }
+// printf("5key is %.*s %d\n", key.dptr?key.dsize:0, key.dptr?key.dptr:"(null)", key.dsize);
+               if (key.dptr != NULL)
+               {
+                       result.query_reply_u.data.key.item_len = key.dsize;
+                       result.query_reply_u.data.key.item_val = key.dptr;
+                       val = gdbm_fetch(db_file[query->database],key);
+//         printf("5content is %.*s %d\n", val.dptr?val.dsize:0, val.dptr?val.dptr:"(null)", val.dsize);
+               }
+               else
+                       result.error = R_NONEXT;
+               break;
+       case M_MATCH: /* given any key, just return it and its data */
+               /* copy this as the arg to service is freed anyway */
+               key.dptr = query->key.item_val;
+               key.dsize = query->key.item_len;
+               result.query_reply_u.data.key.item_val = key.dptr;
+               result.query_reply_u.data.key.item_len = key.dsize;
+               val = gdbm_fetch(db_file[query->database],key);
+               key.dptr = NULL; /* make sure it doesn't get freed */
+
+               break;
+       case M_NEXT_PREFIX: /* given a key return the next key with the same prefix and the data */
+               /* associated with it.  If first '_' is last char, fint FIRST key*/
+               key.dsize = query->key.item_len;
+               key.dptr = query->key.item_val;
+               if (strchr(key.dptr, '_')== NULL) {
+                       result.error = R_NONEXT;
+                       break;
+               }
+               plen = strchr(key.dptr, '_') - key.dptr +1;
+               if (plen == key.dsize)
+                       key = gdbm_firstkey(db_file[query->database]);
+               else
+                       key = gdbm_nextkey(db_file[query->database],key);
+               while (key.dptr &&
+                      strncmp(key.dptr, query->key.item_val, plen) != 0) {
+                       datum k2 = gdbm_nextkey(db_file[query->database],key);
+                       free(key.dptr);
+                       key = k2;
+               }
+
+               if (key.dptr != NULL)
+               {
+                       result.query_reply_u.data.key.item_len = key.dsize;
+                       result.query_reply_u.data.key.item_val = key.dptr;
+                       val = gdbm_fetch(db_file[query->database],key);
+               }
+               else
+                       result.error = R_NONEXT;
+               break;
+       default:
+               result.error = R_SYS;
+               break;
+       }
+       if ((val.dptr == NULL) && (result.error ==  R_RESOK))
+               result.error = R_NOMATCH;
+
+       result.query_reply_u.data.value.item_val = val.dptr;
+       result.query_reply_u.data.value.item_len = val.dsize;
+
+       return(&result);
+}
+
+/* DB_OPEN: a generic initialization routine.  given a database id, */
+ /* open the database with the appropriate file name. */
+int db_open(db_number db, int gdbm_mode)
+{
+       char buf[1024];
+
+       char * pathname;
+    
+       pathname= make_pathname(DatabaseName[db]);
+
+       if (gdbm_mode == GDBM_NEWDB)
+       {
+               struct stat sb;
+               if (stat(pathname, &sb)== 0)
+                       return 0;
+       }
+       /* open the database file */
+       db_file[db] = gdbm_open(pathname, 1024, gdbm_mode, 0600, 0);
+
+       if (db_file[db] == NULL) {
+               sprintf(buf, "Cannot open database: %s, reason %d\n",pathname, gdbm_errno);
+               shout(buf);
+               fprintf(stderr, buf);
+               exit(3);
+               return 0;
+       }
+       free(pathname);
+       return 1;
+}
+
+/* CLOSE_DB: close a given database */
+void close_db(db_number db)
+{
+       if (db_file[db] == NULL) return;
+       gdbm_close(db_file[db]);
+       db_file[db] = NULL;
+}
+
+/* READ_DB: given a key, a pointer to the data, a function to decode */
+ /* the database record into the data, and the id number of the */
+ /* database, read the key and decode the data into the supplied */
+ /* structure. */
+/* NOTE: the content field should be a static variable o
+   that it can be freed by the next read_db
+   */
+book_changeres read_db(item key, void * data, int data_size, xdrproc_t xdr_fn,  db_number db)
+{
+       datum content;
+       datum dkey;
+       static book_changeres result;
+
+       result = C_SYSERR;
+    
+       xdr_free(xdr_fn, (char*) data);
+       /* make sure all fields are 0 */
+       memset(data, 0, data_size);
+    
+       if (db_file[db] == NULL) {
+               result = C_FILE;
+               return(result);
+       }
+
+       dkey.dptr = key.item_val;
+       dkey.dsize = key.item_len;
+       /* look for the description */
+       content = gdbm_fetch(db_file[db],dkey);
+
+       if (content.dptr != NULL) { /* decode existing list */
+               result = (datum_decode(&content, data, xdr_fn) == TRUE) ? C_OK : C_XDR;
+               free(content.dptr);
+       }
+       else
+               result = C_NOMATCH;
+
+       if (result == C_SYSERR) shout("Strange SYSERR\n");
+       return result;
+}
+
+/* BOTTOMLEVEL_WRITE_DB: given a key, some data in a structure, and */
+ /* xdr function */ 
+ /* and a database id, use the xdr function to encode the data and */
+ /* store it in the required database with the right key. */
+/* This routine is called by write_db.  The reason it is not called */
+ /* directly is the the UPDATE_KEY for the database needs to be */
+ /* changed as well.  The simplest way of doing this involves calls to */
+ /* BOTTOMLEVEL_WRITE_DB */
+static book_changeres bottomlevel_write_db(item key, void * data, xdrproc_t xdr_fn,
+                                          db_number db)
+{
+       static book_changeres result;
+       datum content;
+       datum dkey;
+
+       if (key.item_len == 0) {
+               char buf[256];
+               sprintf(buf, "PANIC: attempt to corrupt database %d by writting NULL key\n", db);
+               bailout(buf,6);
+       }
+
+       if (!datum_encode(&content, data, xdr_fn))
+               return C_XDR;
+       dkey.dptr = key.item_val;
+       dkey.dsize = key.item_len;
+
+       /* round the users and bookings databases up to 512 byte boundary. */
+       /* This will reduce the amount of freed data in the database */
+
+       if (db == BOOKINGS)
+       {
+               /* find out how much space the key and data will take up. */
+               /* They are both stored together.  Round this up to a 512 byte */
+               /* boundary  */
+               int total = (((content.dsize + dkey.dsize) / 512 ) +1 ) * 512;
+
+               /* this is the amount for the data */
+               content.dsize = total - dkey.dsize;
+       }
+    
+       if (gdbm_store(db_file[db],dkey,content,GDBM_REPLACE) == 0)
+               result = C_OK;
+       else
+               result = C_GDBM;
+    
+       return result;
+}
+
+/* UPDATE_STATUS_KEY: this routine updates the status of the database. */
+ /* The fields that can change are the update_time and the */
+ /* reorganise_time */
+book_changeres update_status_key(db_number db, int update_time, int reorganise_time)
+{
+
+       static db_updaterec db_update_status;
+       book_changeres result ;
+       item key;
+       key.item_val = STATUS_KEY;
+       key.item_len = strlen(STATUS_KEY);
+
+       /* it doesn't matter  if the key is here or not */
+       read_db(key, &db_update_status, sizeof(db_updaterec),
+               (xdrproc_t)xdr_db_updaterec, db);
+       if (update_time)
+               db_update_status.update_time = update_time;
+
+       if (reorganise_time)
+               db_update_status.reorganise_time = reorganise_time;
+
+       result = bottomlevel_write_db(key, &db_update_status,
+                                     (xdrproc_t)xdr_db_updaterec, db);
+       return result;
+    
+}
+
+void get_status_key(db_number db, time_t *update_time, time_t *reorganise_time)
+{
+
+       static db_updaterec db_update_status;
+       item key;
+       key.item_val = STATUS_KEY;
+       key.item_len = strlen(STATUS_KEY);
+
+       /* it doesn't matter  if the key is here or not */
+       read_db(key, &db_update_status, sizeof(db_updaterec),
+               (xdrproc_t)xdr_db_updaterec, db);
+       if (update_time)
+               *update_time = db_update_status.update_time;
+
+       if (reorganise_time)
+               *reorganise_time = db_update_status.reorganise_time;
+}
+
+    
+/* WRITE_DB: This is just a wrapper aroud BOTTOMLEVEL_WRITE_DB, to */
+ /* update the status key as well */    
+book_changeres write_db(item key, void * data, xdrproc_t xdr_fn, db_number db)
+{
+       book_changeres result ;
+       if ( (result = bottomlevel_write_db(key, data, xdr_fn, db)) == C_OK)
+               result = update_status_key(db, time(0), 0);
+       return(result);
+   
+}
+
+/* This routine reoganizes the database for us.  It has to return */
+ /* something otherwise the rpc call times out. */
+
+void * SVC(db_reorganize_2)(void * null , struct svc_req *rpstp)
+{
+       static char result;
+       int i;
+       extern GDBM_FILE log_file;
+       char *p;
+
+       /* send a reply here so the client doesn't time out and retry */
+       svc_sendreply(rpstp->rq_xprt, (xdrproc_t)xdr_char, &result);
+
+       for (i=0 ; i<= MAXDB;i++)
+       {
+               unlink(p=make_tmp_pathname(DatabaseName[i]));
+               free(p);
+               gdbm_reorganize(db_file[i]);
+               update_status_key(i, 0, time(0));
+       }
+
+       unlink(p=make_tmp_pathname(LogFile));
+       free(p);
+       gdbm_reorganize(log_file);
+       /* return nothing, we have already sent back a reply as the */
+       /* operation takes some time */
+       return(NULL);
+    
+}
+
+book_changeres delete_from_db(item key, db_number db)
+{
+       datum dkey;
+       dkey.dptr = key.item_val;
+       dkey.dsize = key.item_len;
+       return((gdbm_delete(db_file[db],dkey) ==0) ? C_OK : C_GDBM);
+}
diff --git a/database/desc_utils.c b/database/desc_utils.c
new file mode 100644 (file)
index 0000000..be37f33
--- /dev/null
@@ -0,0 +1,207 @@
+
+#include       "db_header.h"
+
+
+void free_slot(description * slot)
+{
+    /* this will occur if there is no space allocated for the */
+    /* bitstring..    This should not occur too often!! */
+    if ((void *) slot->item_val == (void *) slot)
+       bailout("Memory allocation problem in freeing slot!!!\n",9);
+    
+    free(slot->item_val);
+    free (slot);
+}
+
+description new_desc()
+{
+    description result;
+    result.item_len = BITSTRLEN;
+    result.item_val = (char *) calloc(BITSTRLEN,
+                                     sizeof(char));
+    
+    return(result);
+}
+
+void set_a_bit(description * desc, int i)
+{
+    ((signed char *)desc->item_val)[i / 8] |= (signed char)(0x80 >> (i % 8));
+}
+
+/* DEL_A_BIT: delete a single bit, return status that indicates if was */
+ /* already set */
+bool_t del_a_bit(description * desc, int i)
+{
+    bool_t status = query_bit(desc, i);
+    ((signed char *)desc->item_val)[i / 8] &= ~(signed char)(0x80 >> (i % 8));
+    return(status);
+}
+
+bool_t query_bit(description * desc, int num)
+{
+    return(((signed char)(desc->item_val[num / 8] &
+           (signed char)(0x80 >>( num %8))) !=  (signed char)0)?
+           TRUE  :     FALSE); 
+}
+
+/* FULL_DESC: sets all the bits in the set */
+void full_desc(description * desc)
+{
+    int i;
+    for (i=0;i <desc->item_len;i++)
+       desc->item_val[i] = (signed char)0xff;
+}    
+
+void zero_desc(description * desc)
+{
+    int i;
+    for (i=0;i <desc->item_len;i++)
+       desc->item_val[i] = (signed char)0x00;
+}    
+
+void desc_add(description d1, description d2)
+{
+    int i;
+    for (i=0 ; i<d1.item_len ; i++)
+       d1.item_val[i] |= d2.item_val[i];
+}
+
+void desc_and(description d1, description d2)
+{
+    int i;
+    for (i=0 ; i<d1.item_len ; i++)
+       d1.item_val[i] &= d2.item_val[i];
+}
+
+void desc_sub(description d1, description d2)
+{
+    int i;
+    for (i=0 ; i<d1.item_len ; i++)
+       d1.item_val[i] &= ~d2.item_val[i];
+}
+
+/* NULL_DESC: returns true if no bits in the description are set */
+bool_t null_desc(description * desc)
+{
+    int i;
+    for (i=0;i < desc->item_len;i++)
+       if ((signed char)desc->item_val[i] != (signed char)0x00)
+           return(FALSE);
+    
+    return(TRUE);
+}
+
+description invert_desc(description * desc)
+{
+    description result = new_desc();
+    int i;
+    for (i=0;i < desc->item_len;i++)
+       ((signed char *)result.item_val)[i] = (signed char)
+           ~ desc->item_val[i];
+    return result;
+    
+}
+
+
+/*  REQUIRED_BITS: returns TRUE if only the bits set in the template */
+ /*  are set in the testcase */
+bool_t required_bits(description * template, description * testcase)
+{
+    int i;
+    for (i =0 ; i < BITINDMAX; i++)
+       if (query_bit(template, i) && ! query_bit(testcase, i))
+           return FALSE;
+    return TRUE;
+    
+}
+
+/* DESC_MEMBER: returns TRUE if all the bits in the master appear in */
+ /* the slavlabe*/
+bool_t desc_member(description * master, description * slave)
+{
+    int i;
+    
+    for (i = 0;i < master->item_len;i++)
+    {
+        /* 
+       printf("%d = %x\n",i,(int)((char)~master->item_val[i] &
+              (char) slave->item_val[i]));
+       */
+       if ((char)((char)~master->item_val[i] & (char)slave->item_val[i]) != (char)0)
+           return FALSE;
+    }
+    
+    return TRUE;
+    
+}
+
+void desc_cpy(description *dest, description *src)
+{
+
+    dest->item_len = src->item_len;
+    dest->item_val = memdup(src->item_val, src->item_len);
+    
+}
+
+bool_t mandatory_attrs(item *mand, item *d1, item *d2)
+{
+    /* true if all manadatory bit in d1 are also in d2 */
+    int i;
+    for (i=0; i<BITINDMAX ; i++)
+       if (query_bit(mand,i))
+           if (query_bit(d1,i) && ! query_bit(d2, i))
+               return FALSE;
+    return TRUE;
+}
+
+bool_t desc_eql(item d1, item d2)
+{
+    int i;
+    for (i=0 ; i < BITSTRLEN ; i++)
+       if (d1.item_val[i] != d2.item_val[i])
+           return FALSE;
+    return TRUE;
+}
+
+description * desc_dup(description * src)
+{
+    description * result;
+    
+    result = (description *) malloc(sizeof(description));
+    /* result->item_val = (char *) malloc(src->item_len); */
+    
+    result->item_len = src->item_len;
+    result->item_val = memdup(src->item_val, src->item_len);
+
+    return(result);
+}
+
+
+int desc_2_labind(description *d)
+{
+    int i;
+    for (i=0; i< BITINDMAX ; i++)
+    {
+       if (query_bit(&attr_types.lab, i)
+           && query_bit(d, i))
+           return i;
+    }
+    return -1;
+}
+
+
+void slot2bookkey(slotgroup *slot, book_key *key)
+{
+    /* key is 3 bytes/24 bits.
+     * these are used
+     *  7:pod, 9:doy, 8:lab
+     */
+    int pod, doy, lab;
+    int k;
+    pod = slot->pod;
+    doy = slot->doy;
+    lab = desc_2_labind(&slot->what);
+    
+    k = (pod<<17) | (doy <<8) | lab;
+    *key = k;
+}
diff --git a/database/descpairlist.c b/database/descpairlist.c
new file mode 100644 (file)
index 0000000..5f1872b
--- /dev/null
@@ -0,0 +1,106 @@
+/* some routines for adding and deleting from lists of strings and numbers */
+
+#include       "db_header.h"
+
+void add_to_descpairlist(descpairlist * ch, description * new, int num)
+{
+    descpairlist new_el;
+
+    /* maybe check if present */
+
+    new_el = (descpairlist) malloc (sizeof(descpairnode));
+
+    desc_cpy(&new_el -> data, new);
+    new_el -> num = num;
+    new_el -> next = *ch;
+    *ch  = new_el;
+}
+
+book_changeres del_from_descpairlist(descpairlist * ch, description *name)
+{
+    descpairlist ptr = *ch;
+    descpairlist trailer = ptr;  /* this points to the prev entry in the */
+                            /* list to do deletions, easier than */
+                            /* doubly linked list */
+    book_changeres result = C_NOMATCH;
+
+
+    while (ptr != NULL) {
+       if (( bcmp(ptr -> data.item_val, name->item_val, name->item_len) == 0)) {
+
+           /* delete !! */
+           if (ptr == trailer) {  /* first node */
+               if (ptr -> next == NULL) /* only one node in list*/
+                   *ch = NULL;
+               else
+                   *ch = ptr -> next;
+           }
+           else
+               trailer->next = ptr-> next;
+           /* cut the node off */
+           ptr -> next = NULL;
+           xdr_free((xdrproc_t)xdr_descpairlist, (char*) &ptr);            
+           result = C_OK;
+       }
+       else { /* go to the next element in the list */
+           trailer = ptr;
+           ptr = ptr->next;
+       }
+    }
+    return(result);
+}
+
+descpairlist finddesc(descpairlist *ch, description * key)
+{
+
+    descpairlist ptr = *ch;
+    while ((ptr != NULL) && (bcmp(ptr->data.item_val,key->item_val,key->item_len) != 0) ) {
+       ptr = ptr->next;
+    }
+    return(ptr);
+}
+
+/* if the key is present in the list inc it's value, else add it to */
+ /* the list with a value of 1 operation should always suceed*/
+void incdesc(descpairlist *ch, description * key, int diff)
+{
+    descpairlist val;
+    if ((val = finddesc(ch,key)) == NULL) /* add new element to list */
+       add_to_descpairlist(ch,key,diff);
+    else
+    {
+       val->num += diff;
+       if (val->num == 0)
+           del_from_descpairlist(ch,key);
+    }
+}
+
+
+
+#ifdef WANTED
+merge_lists(descpairlist * tot, descpairlist * others)
+{
+    descpairlist ptr = *others;
+
+    while (ptr != NULL)
+    {
+RUBBISH        inckey(tot, strdup(ptr->data), ptr->num);
+       ptr = ptr ->next;
+    }
+
+}
+
+del_list(descpairlist * tot, descpairlist * subs)
+{
+    descpairlist ptr = *subs;
+    while (ptr != NULL) {
+RUBBISH        deckey(tot, strdup(ptr->data), ptr->num);
+       ptr = ptr->next;
+    }
+}
+#endif
+
+
+
+
+
diff --git a/database/freeslots.c b/database/freeslots.c
new file mode 100644 (file)
index 0000000..a4d7a1c
--- /dev/null
@@ -0,0 +1,97 @@
+/* some routines for adding and deleting from lists of strings and numbers */
+
+#include       "db_header.h"
+
+static void add_to_utilizationlist(utilizationlist * ch, description * new,
+                                  int free, int total)
+{
+    utilizationlist new_el;
+
+    /* maybe check if present */
+
+    new_el = (utilizationlist) malloc (sizeof(utilizationnode));
+
+    desc_cpy(&new_el -> machine_set, new);
+    new_el -> free = free;
+    new_el -> total = total;
+    new_el -> next = *ch;
+    *ch  = new_el;
+}
+
+utilizationlist *free_slots(slotlist sl)
+{
+    
+    book_changeres state;
+    static utilizationlist result;
+    static descpairlist setlist;
+    descpairlist descptr;
+    item key;
+    extern descpairlist finddesc();
+
+    /* clear the result*/
+    xdr_free((xdrproc_t)xdr_utilizationlist, (char*) &result);
+    memset(&result, 0, sizeof(utilizationlist));
+
+    /* get the list of descriptions and counts for each type of */
+    /* machine */
+
+    key = str_key(HOST_SETS);
+    state = read_db(key, &setlist, sizeof(descpairlist),
+                   (xdrproc_t)xdr_descpairlist, CONFIG);
+
+    
+    /* for each element in the list:
+       - get number of machines
+       - subtract number of bookings
+       */
+
+    for ( ; sl ; sl=sl->next)
+    {
+       for (descptr = setlist ; descptr != NULL; descptr = descptr->next)
+       {
+           int bookslots;
+
+           /* make sure the description is for available machines */
+           if (!query_bit(&descptr->data, attr_types.available))
+               continue;
+
+           if (!desc_member(&sl->data->what, &descptr->data))
+               continue;
+       
+           /* read the bookings db */
+           key = slotbits2key(sl->data->doy, sl->data->pod, &descptr->data);
+           state = read_db(key, &bookslots,
+                           sizeof(bookslots), (xdrproc_t)xdr_int, BOOKINGS);
+           if (state != C_OK)
+               bookslots = 0;
+           free(key.item_val);
+       
+/* Read the actual list of bookings to, because we seem to have some
+corruption... neilb 12mar2002
+This is unfinished .. the corruption disappeared...
+*/
+#if 0
+if (0)
+           {   
+                   int lab = desc_2_labind(&descptr->data);
+                   book_key bk = make_bookkey(sl->data->doy, sl->data->pod, lab);
+                   static bookhead bh;
+                   int nbs;
+                   book_changeres res;
+                   res = read_bookhead(&bh, &bk);
+                   if (res != C_OK)
+                           nbs = 0;
+                   else {
+                           booklist bptr = bh.data;
+                           
+                           /* should we count?? */
+                   }
+           }
+#endif
+           
+           add_to_utilizationlist(&result, &descptr->data,
+                                  descptr->num - bookslots, descptr->num);
+       }
+    }
+    return(&result);
+}
diff --git a/database/hosts_db.c b/database/hosts_db.c
new file mode 100644 (file)
index 0000000..fe959db
--- /dev/null
@@ -0,0 +1,335 @@
+#include       "db_header.h"
+
+extern book_changeres read_db();
+
+/* this database maps
+   workstationid -> workstation_state (description, disabled?, host)
+   description -> int (number of workstations with that description)
+   hostgroup ->hostid[], labid[]   (hosts in a hostgroup - table passing set, labs in a hostgroup)
+   hostid -> hostinfo  (workstationlist + hostgroup)
+   labid -> hostgroup, workstationlist (which hostgroup maintains this lab, and which workstations are in it)
+
+ */
+typedef enum change_direction {
+    WS_DEL = 1,
+    WS_ADD =2
+} change_direction;
+
+
+/* HOST_WS: add/deletes a workstation from a host or lab */
+static book_changeres move_ws(config_type whichmap,
+                             entityid ent, entityid wsid,
+                             change_direction dir)
+{
+    book_changeres result;
+    static hostinfo hinfo;
+    char key[20];
+    item k;
+    bool_t bresult;
+
+    if (tracing)
+       printf("move_ws(map %d host %d, ws %d, dir %d)\n", whichmap, ent, wsid, dir);
+    
+    /* read the host entry */
+    if (ent == -1 )
+       return C_OK;
+    
+    result = read_db(k = key_int(key, whichmap, ent),
+                    &hinfo, sizeof(hostinfo), (xdrproc_t)xdr_hostinfo,
+                    CONFIG);
+
+    if (result == C_NOMATCH)
+    {
+       hinfo.clump= -1;
+       hinfo.workstations.entitylist_len=0;
+       hinfo.workstations.entitylist_val=NULL;
+    }
+    else if (result != C_OK)
+       return result;
+
+    /* add/del the workstation */
+    if (dir == WS_ADD)
+       bresult = add_intarray((int_array*)&hinfo.workstations, wsid);
+    else 
+       bresult = del_intarray((int_array*)&hinfo.workstations, wsid);
+
+    if (!bresult)
+       return C_MISMATCH;
+
+    if (tracing)
+    {
+       int i;
+       char *sep;
+       sep = "";
+       printf("workstation array: ");
+       for (i=0; i<hinfo.workstations.entitylist_len; i++)
+       {
+           printf("%s #%d", sep, hinfo.workstations.entitylist_val[i]);
+           sep=", ";
+       }
+       printf("\n");
+    }
+
+    
+    /* write the host entry */
+    result = write_db(k, &hinfo, (xdrproc_t)xdr_hostinfo, CONFIG);
+    return(result);
+    
+}
+
+
+static book_changeres change_hostgroup(config_type where, int oldgroup, int newgroup, hostid host)
+{
+    book_changeres result;
+    static hostgroup_info hg;
+    item k;
+    char key[20];
+
+    if (tracing)
+       printf("old %d, new %d, host %d\n",oldgroup, newgroup, host);
+
+    result = C_OK;
+    if (oldgroup != newgroup) 
+    {
+       if (oldgroup != -1)
+       {
+           /* remove from old hostlist */
+
+           result = read_db(k = key_int(key, C_HOSTGROUPS, oldgroup),
+                            &hg, sizeof(hostgroup_info), (xdrproc_t)xdr_hostgroup_info,
+                            CONFIG);
+           if (result == C_OK)
+           {
+               /* remove from list */
+               if (where == C_HOSTS)
+                   del_intarray((int_array*)&hg.hosts, host);
+               else
+                   del_intarray((int_array*)&hg.labs, host);
+       
+               /* remove this hostgroup if it no longer has any
+                * hosts or labs associated with it
+                */
+               if (hg.hosts.entitylist_len == 0 &&
+                   hg.labs.entitylist_len == 0)
+                   result = delete_from_db(k, CONFIG);
+               else
+                   result =write_db(k, &hg, (xdrproc_t)xdr_hostgroup_info, CONFIG);
+           }
+       }
+
+       if (result == C_OK && newgroup != -1)
+       {
+           result = read_db(k = key_int(key, C_HOSTGROUPS, newgroup),
+                            &hg, sizeof(hostgroup_info), (xdrproc_t)xdr_hostgroup_info,
+                            CONFIG);
+           /* no matter if it not there */
+       
+           /* add to list */
+           if (where == C_HOSTS)
+               add_intarray((int_array*)&hg.hosts, host);
+           else
+               add_intarray((int_array*)&hg.labs, host);
+       
+           result =write_db(k, &hg, (xdrproc_t)xdr_hostgroup_info, CONFIG);
+       }
+    }
+    return result;
+}
+
+static book_changeres change_set(description * set, int diff)
+{
+    /* We keep a list of the types of workstations (as defined by their state
+book_change_u.     * or attributes (available, lab, etc)).
+     * This list is used by the booking system to determine (for example)
+     * the number of available or broken machines in a lab, etc.
+     * If given set doesn't exist, then it is added to the setlist
+     * Note that when a machine becomes broken, it is moved out of its old
+     * set and into the broken set (for the laboratory).
+     */
+    static book_changeres result;
+    static descpairlist setlist;
+    item key;
+
+    if (tracing)
+       printf("Change set %d\n", diff);
+    
+    /* read list of sets */
+    result = read_db(key = str_key(HOST_SETS),
+                    &setlist, sizeof(descpairlist),
+                    (xdrproc_t)xdr_descpairlist, CONFIG);
+    /* no matter if fails!! */
+
+    incdesc(&setlist, set, diff);
+    
+    /* write description */
+    result = write_db(key, &setlist, (xdrproc_t)xdr_descpairlist, CONFIG);
+    return(result);
+}
+
+book_changeres change_ws(workstation_ch * newws)
+{
+    book_changeres result, res1;
+    static workstation_state wstate;
+    workstation_state wstate2;
+    char key[20];
+    item k;
+    entityid newlab, oldlab;
+    int hostchanged, labchanged;
+    
+    /* look for existing workstation record */
+    result = read_db(k = key_int(key, C_WORKSTATIONS, newws->ws),
+                    &wstate, sizeof(wstate),
+                    (xdrproc_t)xdr_workstation_state, CONFIG); 
+
+    if (result == C_OK)
+    {
+       hostchanged = (wstate.host != newws->state.host);
+       oldlab = desc_2_labind(&wstate.state);
+    }
+    else
+    {
+       hostchanged = 0;
+       oldlab = -1;
+    }
+    newlab = desc_2_labind(&newws->state.state);
+    labchanged = (oldlab != newlab);
+    
+    if (newlab == -1)
+    {
+       /* we are removing the workstation record */
+       if (result == C_OK)
+       {
+           /* workstation exists in db */
+
+           /* remove workstation type from set of available workstations */
+           if ((result = change_set(&wstate.state, -1)) == C_OK)
+           {
+               /* remove the workstation from the old host */
+               move_ws(C_HOSTS, wstate.host, newws->ws, WS_DEL);
+               /* remove workstation from the oldlab */
+               move_ws(C_LABS, oldlab, newws->ws, WS_DEL);
+               /* remove the workstation record */
+               result = delete_from_db(k, CONFIG);
+           }
+       }
+    }
+    else
+    {
+       if (newws->state.host != -1 && (hostchanged || labchanged))
+       {
+           static hostinfo hoststate, labstate;
+           char key1[20];
+
+           /* check that hostgroup(lab(ws)) == hostgroup(host(ws) */
+           
+           if ((res1 = read_db(key_int(key1, C_HOSTS, newws->state.host),
+                               &hoststate, sizeof(hoststate),
+                               (xdrproc_t)xdr_hostinfo, CONFIG)) == C_OK)
+           {
+               if ((res1 = read_db(key_int(key1, C_LABS, newlab),
+                                   &labstate, sizeof(labstate),
+                                   (xdrproc_t)xdr_hostinfo, CONFIG)) == C_OK)
+               {
+                   if (hoststate.clump != labstate.clump)
+                   {
+                       if (tracing)
+                           printf("hostgroup(lab(ws:%d)) != hostgroup(host(ws:%d))\n", newws->ws, newws->ws);
+                       res1 = C_BAD;
+                   }
+               }
+               else if (tracing) printf("Could not find lab(ws:%d)\n", newws->ws);
+           }
+           else if (tracing) printf("Could not find host(ws:%d)\n", newws->ws);
+       }
+       else
+       {
+           /* precondition: hostgroup(lab(ws)) == hostgroup(host(ws) */
+           res1 = C_OK;
+       }
+
+       if (res1 != C_OK)
+           result = res1;
+       else
+       {
+           if (result == C_OK)
+               /* remove workstation type from set of available workstations */
+               res1 = change_set(&wstate.state, -1);
+
+           if (hostchanged)
+               /* remove the workstation from the old host */
+               move_ws(C_HOSTS, wstate.host, newws->ws, WS_DEL);
+
+           if (labchanged)
+           {
+               /* remove the workstation from the oldlab */
+               move_ws(C_LABS, oldlab, newws->ws, WS_DEL);
+               /* add the workstation to the new lab */
+               move_ws(C_LABS, newlab, newws->ws, WS_ADD);
+           }
+
+           if (result == C_NOMATCH || hostchanged)
+               /* if the workstation is new or the host has changed
+                * then add the workstation to the new host */
+               move_ws(C_HOSTS, newws->state.host, newws->ws, WS_ADD);
+
+           /* we are changing/rewriting the workstation record */
+           wstate2.why = newws->state.why;
+           wstate2.time = newws->state.time;
+           wstate2.host = newws->state.host;
+           wstate2.state = newws->state.state;
+
+           result = write_db(k, &wstate2, (xdrproc_t)xdr_workstation_state, CONFIG);
+           if (result == C_OK)
+               /* add workstation type to set of available workstations */
+               result = change_set(&newws->state.state, 1);
+       }
+    }
+    return result;
+}
+
+book_changeres change_hostlab(host_ch * newhl, int which)
+{
+    book_changeres result;
+    static hostinfo linfo;
+    char key[20];
+    item k;
+
+    memset(&linfo, 0, sizeof(linfo));
+    result = read_db(k = key_int(key, which, newhl->entity),
+                    &linfo, sizeof(linfo),
+                    (xdrproc_t)xdr_hostinfo, CONFIG);
+
+    if (result == C_NOMATCH)
+       linfo.clump = -1;
+
+    if (linfo.clump != newhl->clump)
+    {
+#if 0 /* must make sure errors are reflected back before enforcingthis  */
+       if (linfo.workstations.entitylist_len != 0)
+           result = C_BAD; /* lab not empty */
+       else
+#endif
+           result = change_hostgroup(which, linfo.clump,
+                                     newhl->clump, newhl->entity);
+    }
+    if (result == C_OK)
+    {
+       if (newhl->clump == -1 && linfo.workstations.entitylist_len == 0)
+           result = delete_from_db(k, CONFIG);
+       else
+       {
+           linfo.clump = newhl->clump;
+           result = write_db(k, &linfo, (xdrproc_t)xdr_hostinfo, CONFIG);
+       }
+    }
+    return result;
+}
+
+book_changeres change_lab(host_ch *newlab)
+{
+    return change_hostlab(newlab, C_LABS);
+}
+book_changeres change_host(host_ch *newhst)
+{
+    return change_hostlab(newhst, C_HOSTS);
+}
diff --git a/database/intpairlist.c b/database/intpairlist.c
new file mode 100644 (file)
index 0000000..1c92fc9
--- /dev/null
@@ -0,0 +1,156 @@
+/* some routines for adding and deleting from lists of strings and numbers */
+
+#include       "db_header.h"
+
+
+void add_to_intpairlist(intpairlist * ch, int new, int num)
+{
+    intpairlist new_el;
+
+    /* maybe check if present */
+
+    new_el = (intpairlist) malloc (sizeof(intpairnode));
+
+    new_el -> data = new;
+    new_el -> num = num;
+    new_el -> next = *ch;
+    *ch  = new_el;
+}
+
+int del_from_intpairlist(intpairlist * ch, int name)
+{
+    intpairlist ptr = *ch;
+    intpairlist trailer = ptr;  /* this points to the prev entry in the */
+                            /* list to do deletions, easier than */
+                            /* doubly linked list */
+    int result = 0;
+
+
+    while (ptr != NULL) {
+       if (ptr->data == name) {
+
+           /* delete !! */
+           if (ptr == trailer) {  /* first node */
+               if (ptr -> next == NULL) /* only one node in list*/
+                   *ch = NULL;
+               else
+                   *ch = ptr -> next;
+           }
+           else
+               trailer->next = ptr-> next;
+           /* cut the node off */
+           ptr -> next = NULL;
+           xdr_free((xdrproc_t)xdr_intpairlist, (char*) &ptr);             
+           result = 1;
+       }
+       else { /* go to the next element in the list */
+           trailer = ptr;
+           ptr = ptr->next;
+       }
+    }
+    return result;
+}
+
+intpairlist findintkey(intpairlist ptr, int key)
+{
+    while ((ptr != NULL) && (ptr->data != key) ) {
+       ptr = ptr->next;
+    }
+    return(ptr);
+}
+
+/* if the key is present in the list inc it's value, else add it to */
+ /* the list with a value of 1 operation should always suceed*/
+void incintkey(intpairlist *ch, int  key, int diff)
+{
+    intpairlist val;
+    if ((val = findintkey(*ch,key)) == NULL) /* add new element to list */
+       add_to_intpairlist(ch,key,diff);
+    else
+    {
+       val->num += diff;
+       if (val->num == 0)      /* if this key is empty remove it */
+           del_from_intpairlist(ch,key);
+    }
+}
+
+/* if the key is in the list decrement it.  If it's value is 0, then */
+ /* delete it from the list */
+/* what should happen with values that are not found... should we just */
+ /* add the negative amount!!!! */
+void decintkey(intpairlist *ch, int  key, int diff)
+{
+    intpairlist val;
+    
+    if ((val = findintkey(*ch,key)) == NULL) /*look for element in list */
+       add_to_intpairlist(ch, key, -diff);
+    else
+    {
+       val->num -= diff;
+
+       if (val->num == 0)      /* if this key is empty remove it */
+           del_from_intpairlist(ch,key);
+    }
+    
+}
+void decintkey_nodel(intpairlist *ch, int  key, int diff)
+{
+    intpairlist val;
+    
+    if ((val = findintkey(*ch,key)) == NULL) /*look for element in list */
+       add_to_intpairlist(ch, key, -diff);
+    else
+    {
+       val->num -= diff;
+    }
+    
+}
+
+void print_intpairlist(intpairlist *ch)
+{
+    int i = 0;
+    intpairlist ptr;
+    if (ch == NULL) {
+       printf("EMPTY INTPAIRLIST\n");
+       return;
+    }
+    
+    ptr = *ch;
+    while (ptr != NULL) {
+       printf("Element %d: (%d, %d)\n",i,ptr->data, ptr->num);
+       i++;
+       ptr = ptr->next;
+    }
+    
+}
+
+
+
+void merge_intlists(intpairlist * tot, intpairlist * others)
+{
+    intpairlist ptr = *others;
+
+    while (ptr != NULL) {
+       incintkey(tot, ptr->data, ptr->num);
+       ptr = ptr ->next;
+    }
+
+}
+
+void del_intlist(intpairlist * tot, intpairlist * subs)
+{
+    intpairlist ptr = *subs;
+    while (ptr != NULL) {
+       decintkey(tot, ptr->data, ptr->num);
+       ptr = ptr->next;
+    }
+}
+
+void del_intlist_nodel(intpairlist * tot, intpairlist * subs)
+{
+    intpairlist ptr = *subs;
+    while (ptr != NULL) {
+       decintkey_nodel(tot, ptr->data, ptr->num);
+       ptr = ptr->next;
+    }
+}
diff --git a/database/main.c b/database/main.c
new file mode 100644 (file)
index 0000000..1926e4c
--- /dev/null
@@ -0,0 +1,701 @@
+
+/* The main module for the booking system database manager */
+#define _AM_MAIN_
+#include       <sys/types.h>
+#include       <unistd.h>
+#include       <time.h>
+#include       "db_header.h"
+#include       <stdio.h>
+#include       <signal.h>
+#include       <sys/wait.h>
+#ifdef apollo
+#include       <sys/statfs.h>
+#elif defined (USE_STATVFS)
+#include       <sys/statvfs.h>
+#else
+#include       <sys/param.h>
+#include       <sys/mount.h>
+#endif
+#include       <ctype.h>
+#include       <arpa/inet.h>
+#include       <rpc/pmap_clnt.h>
+#include       <getopt.h>
+#ifdef SOLARIS2
+#include <sys/time.h>
+#include <sys/resource.h>
+#endif
+
+#ifdef NOTDEF
+#define svc_run my_svc_run
+#endif
+
+#define CHECK_TIME (60 * 10) /* amount of time between checks on free */
+                            /* disc space */ 
+int helper_pid; /* the process id of the helper */
+char * server;
+update_status update_st;
+time_t last_status_set = 0;
+bool_t perform_stats = FALSE;
+
+char *Usage[] = {
+    "Usage: db_manager [-I] [-n] [-M database_maintainer] [-t]",
+    "where:",
+    "  -I      Initialise database",
+    "  -n      Do not fork/exec database helper/maintainer",
+    "  -M db   The database helper/maintainer to fork/exec",
+    "  -t      enable tracing",
+    NULL };
+
+#ifdef NEED_TO_CHECK_DISK
+/* CHECK_DISK: returns is enough disc space ( > 4000) blocks, else */
+ /* returns false */
+bool_t check_disk()
+{
+       static time_t last =0;
+#ifdef apollo    
+       struct statfs buf;
+#elif defined(USE_STATVFS)
+       struct statvfs buf;
+#else    
+       struct fs_data buf;
+#endif    
+       if (last + CHECK_TIME > time(0) )
+               return TRUE;
+       time(&last);
+
+#ifdef apollo
+       statfs(BookBase, &buf, sizeof(struct statfs), 0);
+       printf("Blocks free %d, size %d\n", buf.f_bfree, buf.f_bsize);
+    
+       return ((buf.f_bfree * buf.f_bsize / 1024) < 4000) ? FALSE : TRUE;
+
+#elif defined(USE_STATVFS)
+       statvfs("/var", &buf);
+/*    printf("Blocks free %d\n", buf.f_bfree); */
+       return(buf.f_bfree <4000) ? FALSE : TRUE;
+#else
+       /* on decs check /var */
+       statfs("/var", &buf);
+       if (tracing)
+               printf("Blocks free %d\n", buf.fd_req.bfree);
+    
+       return (buf.fd_req.bfree < 4000) ? FALSE : TRUE;
+    
+    
+#endif
+    
+}
+#endif
+
+bool_t open_dbs(int gdbm_stat)
+{
+       int i;
+       char buf[128];
+       sprintf(buf,"Open stat %d pid %d\n", gdbm_stat, getpid());
+       shout(buf);
+
+       if (tracing) printf("OPEN\n");
+
+       for (i =0; i <= MAXDB; i++)
+               if (!db_open(i, gdbm_stat))
+                       return 0;
+
+       init_attr_types();
+       return 1;
+}
+
+void close_dbs(void)
+{
+       int i;
+       char buf[128];
+       sprintf(buf,"Close  pid %d\n",  getpid());
+       shout(buf);
+
+       if (tracing) printf("CLOSE\n");
+    
+       for (i=0;i <= MAXDB; i++)
+               close_db(i);
+}
+
+void init_db(void)
+{
+       server = get_myhostname();
+       if (server == NULL)
+               bailout("cant get my hostname\n",7);
+    
+       /* set up the change database */
+       if (change_init()
+           && open_dbs(GDBM_WRITER))
+               update_st = COPY;
+       else
+               update_st = NONE;
+       last_status_set = time(0);
+}
+
+void close_all(void)
+{
+       update_st = NONE;
+       close_dbs();
+
+       /* close the change database */
+       close_change();
+    
+}
+
+/* forks a helper process */
+static char *helper_path = "/usr/local/etc/book_maint";
+void start_helper(void)
+{
+       int i;
+       static char *args[2];
+       if ((helper_pid = fork()) == 0) {
+               /* close all sockets opened by parent except standard ones */
+               for (i = 3 ; i <20;i ++)
+                       close(i);
+               /* have to do an exec.  can't just call the helper as another */
+               /* procedure as both the client and server stubs would have to */
+               /* be linked in.  both client and server stubs contain */
+               /* different definitions for remote procedures. */
+               args[0] = helper_path;
+               args[1] = NULL;
+               i = execv(helper_path, args);
+               if (i != 0) {
+                       printf("exec failed!, status %d\n",i);
+                       exit(4);
+               }
+       }
+}
+
+/* send term to helper pid and wait for it to die */
+void kill_helper(void)
+{
+    int pid;
+    int st;
+    
+    if (helper_pid > 0) {
+       kill(helper_pid, SIGTERM);
+       while ((pid=wait(&st)) > 0 && pid != helper_pid)
+           ;
+       helper_pid = 0;
+    }
+}
+
+/* called when sigalrm is received.  ensures that database closed and */
+ /* server dies.  the terminate routine sets timer to send sigalrm and */
+ /* call this routine */
+SIGRTN die()
+{
+    kill_helper();
+    close_all();
+
+    /* remove portmap's knowledge of this manager, so attempts to */
+    /* access it by other manager's timeout quickly */
+    svc_unregister(BOOK_DATABASE, BOOKVERS);
+    
+    exit(0);
+}
+
+void *SVC(terminate_2)(int *dummy, struct svc_req *rqstp)
+{
+    static char  res;
+    if (ntohs(svc_getcaller(rqstp->rq_xprt)->sin_port) > 1024) {
+       /* FIXME check host */
+       return (&res);
+    }
+    update_st = NONE;
+
+    svc_sendreply(rqstp->rq_xprt, (xdrproc_t)xdr_char, (void*)&res);
+    die();
+    return NULL;
+}
+
+void *SVC(set_updatest_2)(update_status * st, struct svc_req *rq)
+{
+    extern GDBM_FILE log_file;
+    static char c;
+    int i;
+    int any=0;
+    time_t now = time(0);
+
+    last_status_set = now;
+
+    /* possibly reorganise some databases */
+    for (i=0 ; i<= MAXDB ; i++)
+    {
+       time_t chng, reorg;
+       get_status_key(i, &chng, &reorg);
+       if (chng > reorg
+           && reorg + 23*60*60 < now
+           && chng + 2*60*60 < now)
+       {
+           any=1;
+           gdbm_reorganize(db_file[i]);
+           update_status_key(i, 0, time(0));
+       }
+    }
+    if (any) gdbm_reorganize(log_file);
+
+    if (*st == FULL && replica_grow(server, -1) < 0)
+       *st = COPY; /* only replicas on list can be FULL */
+    if (update_st == *st)
+       return &c;
+    
+    /* close the db handles */
+    close_dbs();
+    update_st = *st;
+
+    /* open them again with the right mode */
+    if (update_st == NONE)
+       open_dbs(GDBM_READER);
+    else
+       open_dbs(GDBM_WRCREAT);
+    return (&c);
+}
+
+void check_update_status(void)
+{
+    time_t now;
+    now = time(0);
+    if (last_status_set + 5*60 < now && update_st == FULL)
+       update_st = COPY;
+}
+
+update_status * SVC(get_updatest_2)(void *dummy, struct svc_req *rq)
+{
+    static update_status st;
+
+    check_update_status();
+    collect_xfer();
+    st = update_st;
+    return (&st);
+    
+}
+
+/* calls the update routines for various databases */
+book_changeres change_db (book_changeent *ch)
+{
+    static book_changeres result = C_OK;
+    
+    extern book_changeres replica_add();
+    extern book_changeres replica_del();
+    extern book_changeres add_booking();
+    extern book_changeres add_user_booking();
+    extern book_changeres change_booking();
+    extern book_changeres claim_booking();
+    extern book_changeres change_user_booking();
+    extern book_changeres add_token();
+    extern book_changeres add_class();
+    extern book_changeres remove_booking();
+    extern book_changeres change_implication();
+    extern book_changeres change_attr_type();
+    extern book_changeres set_namenum();
+    extern book_changeres change_ws();
+    extern book_changeres change_host();
+    extern book_changeres change_lab();
+    extern book_changeres change_configdata();
+    extern book_changeres commit();
+
+
+    int *refund;
+    int *tokens;
+    int *who_by;
+    int l;
+    
+    /* check the change number from the server */
+    if (ch->changenum != 0)
+    {
+       /* copied from elsewhere,
+        * make sure the right change number
+        * and not this machine
+        */
+       int lastch;
+       if (strcmp(ch->machine, server)==0)
+           return C_BAD;
+
+       lastch = replica_grow(ch->machine, -1);
+       if (lastch < 0 || lastch+1 != ch->changenum)
+           return C_BAD;
+    }
+    switch (ch ->thechange.chng)  {
+
+    case ADD_SERVER:
+        result = replica_add(ch->thechange.book_change_u.server);
+       break;
+    case DEL_SERVER:
+        result = replica_del(ch->thechange.book_change_u.delserver);
+       if (result == C_OK && strcmp(ch->thechange.book_change_u.delserver, server)==0)
+           update_st = COPY;
+       break;
+    case ADD_BOOKING:
+       result = add_user_booking(&ch->thechange.book_change_u.booking,
+                                  ch->changenum==0);
+       /* This should only fail with C_MISMATCH
+          if it fails for other reason...?? should clean up.  FIXME */
+       if (result != C_OK)  
+           break; 
+       result = add_booking(&ch->thechange.book_change_u.booking);
+       break;
+    case CHANGE_BOOKING:
+       refund = (int*)malloc(l=sizeof(int)*
+                   ch->thechange.book_change_u.changes.chlist.book_ch_list_len);
+       memset(refund, 0, l);
+       tokens = (int*)malloc(l);
+       who_by = (int*)malloc(l);
+
+       result = change_booking(&ch->thechange.book_change_u.changes,
+                                refund, tokens, who_by);
+       /* If this fails, then it is probably just out of order changes */
+       if (result != C_OK)
+       {
+           free(refund);
+           free(tokens);
+           free(who_by);
+           break;
+       }
+       if (tracing) printf("before change user booking\n");
+       
+       result = change_user_booking(&ch->thechange.book_change_u.changes,
+                                     refund, tokens, who_by);
+       free(refund);
+       free(tokens);
+       free(who_by);
+       break;
+    case CLAIM_BOOKING:
+       result = claim_booking(&ch->thechange.book_change_u.aclaim);
+       break;
+    case REMOVE_BOOKING:
+       result = remove_booking(&ch->thechange.book_change_u.removal); 
+       break;
+    case REMOVE_USER:
+       result = del_user(ch->thechange.book_change_u.user_togo);
+       break;
+    case ADD_TOKEN:
+       result = add_token(&ch->thechange.book_change_u.addtok);
+       break;
+    case ADD_CLASS:
+       result = add_class(&ch->thechange.book_change_u.addcl);
+       break;
+    case IMPLICATIONS:
+       result = change_implication(&ch->thechange.book_change_u.implch);
+       break;
+    case ATTR_TYPE:
+       result = change_attr_type(&ch->thechange.book_change_u.attrs);
+       break;
+    case CONFIGDATA:
+       result = change_configdata(&ch->thechange.book_change_u.configent);
+       break;
+    case SET_NAMENUM:
+       result = set_namenum(&ch->thechange.book_change_u.map);
+       break;
+    case CHANGE_WS:
+       result = change_ws(&ch->thechange.book_change_u.newws);
+       break;
+    case CHANGE_HOST:
+       result = change_host(&ch->thechange.book_change_u.newhost);
+       break;
+    case CHANGE_LAB:
+       result = change_lab(&ch->thechange.book_change_u.newlab);
+       break;
+    default:
+       if (tracing)
+           printf("server operation (num %d) not inplemented yet\n", ch->thechange.chng);
+       return C_NOP;
+    }
+    
+    /* now check the result to make sure that the operation was successful */
+    if (result != C_OK) {
+       if (tracing)
+           printf("result of change operation = %d\n",result);
+       return result;
+    }
+
+    /* if the change has come from a client then the changenum field */
+    /* will be 0.  in this case the update should be written out to */
+    /* the changes file */
+    if (1/*ch-> changenum == 0*/) {
+/*     printf("about to commit\n"); */
+       result = commit(ch);
+    }
+    /* update the changes recieved in the replicas database.  even do */
+    /* it for our own changes */
+    replica_grow(ch->machine, ch->changenum);
+
+    
+    /* free the request */
+    /* NO this happens automatically!! */
+/*     xdr_free(xdr_book_changeent, (char*) ch); */
+    
+    return result;
+}
+
+/* this is the external interface.  Checks the the request comes from */
+ /* a privileged port and that the database is accepting updates */
+book_changeres *SVC(change_db_2)(book_changeent *ch, struct svc_req *rqstp)
+{
+    
+       struct sockaddr_in *clxprt;
+       static book_changeres result;
+       static int am_priv = -1;
+
+       if (am_priv == -1) {
+               am_priv = (geteuid() == 0);
+       }
+       clxprt = svc_getcaller(rqstp->rq_xprt);
+    
+       /* make sure from priviliged port */
+       if (am_priv && ntohs( clxprt->sin_port) > 1024) {
+               /* FIXME check host */
+               result = C_NOPERM;
+               return(&result);
+       }
+
+       collect_xfer();
+       check_update_status();
+    
+
+       /* check the database is accepting updates */
+       if (update_st == NONE) {
+               result = C_NOUP;
+               shout("no updates acepted\n");
+               return(&result);
+       }
+
+       if (ch->changenum == 0)
+       {
+               if (ch->machine) free(ch->machine);
+               ch->machine = strdup(server);
+       }
+       if (ch->machine == NULL)
+       {
+               result = C_NOUP;
+               shout("change with number but no host\n");
+               return &result;;
+       }
+       if (update_st == COPY && (strcmp(ch->machine,server) == 0)) {
+               result = C_NOUP;
+               shout("no fresh updates acepted\n");
+               return(&result);
+
+       }
+       if (!islower(ch->machine[0])) {
+               char buf[128];
+               sprintf(buf, "No host set in change, source = %s, type = %d, number =%d\n",
+                       inet_ntoa(clxprt->sin_addr), ch->thechange.chng,
+                       ch->changenum) ;
+               shout(buf);
+               result = C_UNKNOWNHOST;
+               return(&result);
+       }
+
+#ifdef NEED_TO_CHECK_DISK
+       /* if no disc space... die */
+       /* eventually this will just be a warning */
+       if (check_disk() == FALSE)
+       {
+               update_st = NONE;
+               shout("MANAGER: out of disc\n");
+               alarm(2);
+       }
+#endif
+    
+       result = change_db(ch);
+       return &result;
+}
+
+query_reply * SVC(query_db_2)(query_req * request, struct svc_req *rq)
+{
+    extern query_reply *generic_query();
+
+    return (generic_query(request));
+}
+
+usermap_res * SVC(map_bookings_2)(usermap_req *req, struct svc_req *rq)
+{
+    /* lookup each booking in the blist for user and
+     * return a list of bookings
+     */
+    static usermap_res res;
+    booklist bl = map_bookings(req->user, req->blist);
+
+    res.blist = bl;
+    if (bl == NULL)
+       res.res = C_NOMATCH;
+    else
+       res.res = C_OK;
+    return &res;
+}
+
+utilizationlist * SVC(free_slots_2)(slotlist * req, struct svc_req *rq)
+{
+    
+#ifdef NEED_TO_CHECK_DISK
+    if (check_disk() == FALSE)
+    {
+       update_st = NONE;
+       shout("MANAGER: out of disk");
+       alarm(2);
+    }
+#endif
+
+    return free_slots(*req);
+}
+
+
+
+/* this main routine is the same as the one generated by rpcgen, */
+ /* except that we need to initialize the book database as well */
+int main (int argc, char *argv[])
+{
+    SVCXPRT *transp;
+    extern void book_database_2();
+    bool_t willstart_helper = TRUE;
+    extern char * optarg;
+    int createnew = 0;
+
+    update_status updtst = COPY; 
+       
+    int c;
+
+    
+#ifdef SOLARIS2
+       struct rlimit rl;
+       rl.rlim_max=1024;
+       rl.rlim_cur=1024;
+       setrlimit(RLIMIT_NOFILE, &rl);
+#endif
+
+
+    while ((c = getopt(argc, argv, "M:H:nrIt")) != EOF) {
+       switch (c) {
+       case 'I':
+           createnew = 1;
+           break;
+       case 'M': /* Maintainer */
+       case 'H': /* Helper */
+           helper_path = optarg;
+           break;
+       case 'n':
+           willstart_helper =  FALSE;
+           break;
+       case 'r':
+           perform_stats = TRUE;
+           break;
+       case 't':
+           tracing = 1;
+           break;
+       default:
+           usage();
+           exit(1);
+       }
+    }
+
+    /* check if manager already running */
+    if (callrpc("localhost", BOOK_DATABASE, BOOKVERS, NULLPROC,
+               (xdrproc_t)xdr_void, (char *)NULL,
+               (xdrproc_t)xdr_void, (char *)NULL) == 0)
+       bailout("Manager already running", 3);
+       
+    /* set the trap for finishing */
+    signal(SIGALRM, die);
+    signal(SIGTERM, die);
+
+
+    /* open local database */
+    server = get_myhostname();
+    if (server == NULL)
+       bailout("cant get my hostname\n",7);
+    
+    /* set up the change database */
+    change_init();
+    update_st = COPY;
+    last_status_set = time(0);
+       
+    if (createnew)
+    {
+       if (open_dbs(GDBM_NEWDB) && replica_init())
+       {
+           printf("database created\n");
+       }
+       else
+       {
+           printf("cannot initialise database, check for existing database\n");
+           exit(1);
+       }
+    }
+    else
+       if (!open_dbs(GDBM_WRITER))
+       {
+           printf("cannot open database\n");
+           exit(1);
+       }
+
+
+    update_st = updtst;
+    last_status_set = time(0);
+       
+    /* the rest of this routine is standard from rpcgen */
+    (void)pmap_unset(BOOK_DATABASE, BOOKVERS);
+
+    transp = svcudp_create(RPC_ANYSOCK);
+    if (transp == NULL) {
+       (void)fprintf(stderr, "cannot create udp service.\n");
+       exit(1);
+    }
+    if (!svc_register(transp, BOOK_DATABASE, BOOKVERS, book_database_2, IPPROTO_UDP)) {
+       (void)fprintf(stderr, "unable to register (BOOK_DATABASE, BOOKVERS, udp).\n");
+       exit(1);
+    }
+
+    transp = svctcp_create(RPC_ANYSOCK, 0, 0);
+    if (transp == NULL) {
+       (void)fprintf(stderr, "cannot create tcp service.\n");
+       exit(1);
+    }
+    if (!svc_register(transp, BOOK_DATABASE, BOOKVERS, book_database_2, IPPROTO_TCP)) {
+       (void)fprintf(stderr, "unable to register (BOOK_DATABASE, BOOKVERS, tcp).\n");
+       exit(1);
+    }
+
+    if (willstart_helper == TRUE)
+       start_helper();
+
+    svc_run();
+    (void)fprintf(stderr, "svc_run returned\n");
+    exit(1);
+}
+
+
+#ifdef NOTDEF
+/*
+ * The heart of the server.  A crib from libc for the most part...
+ */
+void
+my_svc_run(void)
+{
+       fd_set          readfds;
+       int             selret;
+
+       for (;;) {
+
+               readfds = svc_fdset;
+               cache_set_fds(&readfds);
+
+               selret = select(FD_SETSIZE, &readfds,
+                               (void *) 0, (void *) 0, (struct timeval *) 0);
+
+
+               switch (selret) {
+               case -1:
+                       if (errno == EINTR || errno == ECONNREFUSED
+                        || errno == ENETUNREACH || errno == EHOSTUNREACH)
+                               continue;
+                       xlog(L_ERROR, "my_svc_run() - select: %m");
+                       return;
+
+               default:
+                       if (selret)
+                               svc_getreqset(&readfds);
+               }
+       }
+}
+#endif
diff --git a/database/make_keys.c b/database/make_keys.c
new file mode 100644 (file)
index 0000000..b5ee0c9
--- /dev/null
@@ -0,0 +1,42 @@
+#include       "db_header.h"
+
+
+item user_key(char *key, int value)
+{
+    item rv;
+    sprintf(key, "%d", value);
+    rv.item_val = key;
+    rv.item_len = strlen(key);
+    return rv;
+}
+
+item key_int(char *key, config_type type, int num)
+{
+    item rv;
+    sprintf(key, "%d_%d", type, num);
+    rv.item_val = key;
+    rv.item_len = strlen(key);
+    return rv;
+}
+
+item key_string(config_type type, char *str)
+{
+    item rv;
+    char *p;
+    rv.item_val = (char*)malloc(6+strlen(str)+1);
+    sprintf(rv.item_val, "%d_%s", (int)type, str);
+    for (p= rv.item_val ; *p ; p++)
+       if (*p >= 'A' && *p <= 'Z')
+           *p += 'a' - 'A';
+    rv.item_len = strlen(rv.item_val);
+    return rv;
+}
+
+    
+item str_key(char * str)
+{
+    item rv;
+    rv.item_val = str;
+    rv.item_len = strlen(str);
+    return rv;
+}
diff --git a/database/names_db.c b/database/names_db.c
new file mode 100644 (file)
index 0000000..79755d6
--- /dev/null
@@ -0,0 +1,67 @@
+#include       "db_header.h"
+
+    
+/* this database records the mappings from names to numbers
+ * and numbers to names
+ */
+book_changeres set_namenum(name_mapping * mapping)
+{
+    static book_changeres result;
+    char key[20];
+    item k;
+    static map_value oldvalue;
+    static int oldkey;
+    extern char * value_key();
+
+    /* look for map(keyint) => old_string */
+    result = read_db(k = key_int(key, C_NAMES + mapping->type, mapping->key),
+                    &oldvalue, sizeof(oldvalue),
+                    (xdrproc_t)xdr_map_value, CONFIG);
+
+    if (result == C_OK)
+    {
+       /* found  map(keyint) => old_string
+        * remove map(old_string) => keyint
+        */
+       k = key_string(C_NUMBERS + mapping->type, oldvalue);
+       result = delete_from_db(k, CONFIG);
+       free(k.item_val);
+    }
+
+    if (mapping->value && mapping->value[0])
+    {
+       /* look for map(new_string) => otherkey */
+       result = read_db(k= key_string(C_NUMBERS + mapping->type, mapping->value),
+                        &oldkey, sizeof(oldkey),
+                        (xdrproc_t)xdr_int, CONFIG);
+       free(k.item_val);
+       if (result == C_OK)
+       {
+           /* found  map(new_string) => otherkey
+            * remove map(otherkey) => new_string
+            */
+           result = delete_from_db(key_int(key, C_NAMES + mapping->type, oldkey),
+                          CONFIG);
+       }
+
+       /* now write map(keyint) => newstring */
+       result = write_db(key_int(key, C_NAMES + mapping->type, mapping->key),
+                         &mapping->value, 
+                         (xdrproc_t)xdr_map_value, CONFIG);
+
+       if (result == C_OK)
+       {
+           /* write map(newstring) => keyint  */
+           result = write_db(k = key_string(C_NUMBERS + mapping->type, mapping->value),
+                             &mapping->key, (xdrproc_t)xdr_int, CONFIG);
+           free(k.item_val);
+       }
+    }
+    else
+    {
+       /* remove map(keyint) => oldstr */
+       result = delete_from_db(key_int(key, C_NAMES + mapping->type, mapping->key),
+                               CONFIG);
+    }
+    return result;
+}
diff --git a/database/replica_db.c b/database/replica_db.c
new file mode 100644 (file)
index 0000000..d478681
--- /dev/null
@@ -0,0 +1,95 @@
+/* a module for maintaining the replicas database.
+ * servers can be added and deleted
+ * changes to server records are done implicitly
+ * when changes are received from the relevant server.
+ */
+/* replicas database is
+   key        value
+   hostname  replicaheight
+   */
+#include       "db_header.h"
+#include       <stdio.h>
+
+
+/* a new change has arrived from this replica */
+int replica_grow(machine_name machine, replica_no height)
+{
+    int result;
+    replica_no watermark;
+    char buf[256];
+    item key;
+
+    key = str_key(machine);
+
+    watermark = 0;
+    result = read_db(key, &watermark, sizeof(watermark), (xdrproc_t)xdr_replica_no, REPLICAS);
+    if (result != C_OK && (result != C_NOMATCH || height != 0))
+       return -1;
+    if (height < 0)
+       return watermark;
+    
+    if (height < watermark)
+    {
+       sprintf(buf,"MANAGER PROBLEM: changes must be recieved in increasing order (%ld > %ld)\n",
+               (long)height, (long)watermark); 
+       shout(buf);
+    }
+       
+    watermark = height;
+    result = write_db(key,&watermark, (xdrproc_t)xdr_replica_no, REPLICAS);
+    if (result != C_OK)
+       return -1;
+    return watermark;
+}
+
+book_changeres replica_add(machine_name server)
+{
+    book_changeres result;
+    int rep_num;
+    item key;
+    
+    key = str_key(server);
+    
+    result = read_db(key, &rep_num, sizeof(rep_num), (xdrproc_t)xdr_replica_no, REPLICAS);
+    if (result == C_NOMATCH) {
+       result = replica_grow(server,0)>= 0 ? C_OK:C_SYSERR;
+       change_del_changes(server, -1); /* delete all old changes from this replica */
+    }
+    /* if the read was ok, then try not to worry, I think it is probably safer */
+    if (result == C_SYSERR) shout("replica grow failed\n");
+    return result;
+}
+
+/* this change should always (appear to) be made to the exiting server
+ * This discourages confusion if a server is removed and
+ * recreated soon after
+ */
+book_changeres replica_del(machine_name server)
+{
+    book_changeres result;
+    static replica_no rep_num = 0;
+    item key;
+
+    key = str_key(server);
+    
+    result = read_db(key, &rep_num, sizeof(rep_num), (xdrproc_t)xdr_replica_no, REPLICAS);
+    if (result != C_OK)
+       return result;
+    change_del_changes(server, 1); /* make sure change one is gone so that when server gets
+                                   * re-added, these changes wont go
+                                   */
+    return delete_from_db(key, REPLICAS);
+
+}
+
+/* only called when creating a brand new database */
+int replica_init()
+{
+    static char * ServerName;
+    book_changeres res;
+    extern char * get_myhostname();
+
+    ServerName = get_myhostname();
+    res = replica_add(ServerName);
+    return res == C_OK;
+}
diff --git a/database/rpc_cmsg.c b/database/rpc_cmsg.c
new file mode 100644 (file)
index 0000000..8916504
--- /dev/null
@@ -0,0 +1,203 @@
+/* @(#)rpc_callmsg.c   2.1 88/07/29 4.0 RPCSRC */
+/*
+ * Sun RPC is a product of Sun Microsystems, Inc. and is provided for
+ * unrestricted use provided that this legend is included on all tape
+ * media and as a part of the software program in whole or part.  Users
+ * may copy or modify Sun RPC without charge, but are not authorized
+ * to license or distribute it to anyone else except as part of a product or
+ * program developed by the user.
+ *
+ * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
+ * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
+ *
+ * Sun RPC is provided with no support and without any obligation on the
+ * part of Sun Microsystems, Inc. to assist in its use, correction,
+ * modification or enhancement.
+ *
+ * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
+ * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
+ * OR ANY PART THEREOF.
+ *
+ * In no event will Sun Microsystems, Inc. be liable for any lost revenue
+ * or profits or other special, indirect and consequential damages, even if
+ * Sun has been advised of the possibility of such damages.
+ *
+ * Sun Microsystems, Inc.
+ * 2550 Garcia Avenue
+ * Mountain View, California  94043
+ */
+#if !defined(lint) && defined(SCCSIDS)
+static char sccsid[] = "@(#)rpc_callmsg.c 1.4 87/08/11 Copyr 1984 Sun Micro";
+#endif
+
+/*
+ * rpc_callmsg.c
+ *
+ * Copyright (C) 1984, Sun Microsystems, Inc.
+ *
+ */
+
+#include <string.h>
+#include <sys/param.h>
+#include <rpc/rpc.h>
+
+/*
+ * XDR a call message
+ */
+bool_t
+xdr_callmsg (XDR *xdrs, struct rpc_msg *cmsg)
+{
+  int32_t *buf;
+  struct opaque_auth *oa;
+
+  if (xdrs->x_op == XDR_ENCODE)
+    {
+      if (cmsg->rm_call.cb_cred.oa_length > MAX_AUTH_BYTES)
+       {
+         return (FALSE);
+       }
+      if (cmsg->rm_call.cb_verf.oa_length > MAX_AUTH_BYTES)
+       {
+         return (FALSE);
+       }
+      buf = XDR_INLINE (xdrs, 8 * BYTES_PER_XDR_UNIT
+                       + RNDUP (cmsg->rm_call.cb_cred.oa_length)
+                       + 2 * BYTES_PER_XDR_UNIT
+                       + RNDUP (cmsg->rm_call.cb_verf.oa_length));
+      if (buf != NULL)
+       {
+         IXDR_PUT_LONG (buf, cmsg->rm_xid);
+         IXDR_PUT_ENUM (buf, cmsg->rm_direction);
+         if (cmsg->rm_direction != CALL)
+           return FALSE;
+         IXDR_PUT_LONG (buf, cmsg->rm_call.cb_rpcvers);
+         if (cmsg->rm_call.cb_rpcvers != RPC_MSG_VERSION)
+           return FALSE;
+         IXDR_PUT_LONG (buf, cmsg->rm_call.cb_prog);
+         IXDR_PUT_LONG (buf, cmsg->rm_call.cb_vers);
+         IXDR_PUT_LONG (buf, cmsg->rm_call.cb_proc);
+         oa = &cmsg->rm_call.cb_cred;
+         IXDR_PUT_ENUM (buf, oa->oa_flavor);
+         IXDR_PUT_INT32 (buf, oa->oa_length);
+         if (oa->oa_length)
+           {
+             memcpy ((caddr_t) buf, oa->oa_base, oa->oa_length);
+             buf = (int32_t *) ((char *) buf + RNDUP (oa->oa_length));
+           }
+         oa = &cmsg->rm_call.cb_verf;
+         IXDR_PUT_ENUM (buf, oa->oa_flavor);
+         IXDR_PUT_INT32 (buf, oa->oa_length);
+         if (oa->oa_length)
+           {
+             memcpy ((caddr_t) buf, oa->oa_base, oa->oa_length);
+             /* no real need....
+                buf = (long *) ((char *) buf + RNDUP(oa->oa_length));
+              */
+           }
+         return TRUE;
+       }
+    }
+  if (xdrs->x_op == XDR_DECODE)
+    {
+      buf = XDR_INLINE (xdrs, 8 * BYTES_PER_XDR_UNIT);
+      if (buf != NULL)
+       {
+         cmsg->rm_xid = IXDR_GET_LONG (buf);
+         cmsg->rm_direction = IXDR_GET_ENUM (buf, enum msg_type);
+         if (cmsg->rm_direction != CALL)
+           {
+             return FALSE;
+           }
+         cmsg->rm_call.cb_rpcvers = IXDR_GET_LONG (buf);
+         if (cmsg->rm_call.cb_rpcvers != RPC_MSG_VERSION)
+           {
+             return FALSE;
+           }
+         cmsg->rm_call.cb_prog = IXDR_GET_LONG (buf);
+         cmsg->rm_call.cb_vers = IXDR_GET_LONG (buf);
+         cmsg->rm_call.cb_proc = IXDR_GET_LONG (buf);
+         oa = &cmsg->rm_call.cb_cred;
+         oa->oa_flavor = IXDR_GET_ENUM (buf, enum_t);
+         oa->oa_length = IXDR_GET_INT32 (buf);
+         if (oa->oa_length)
+           {
+             if (oa->oa_length > MAX_AUTH_BYTES)
+               return FALSE;
+             if (oa->oa_base == NULL)
+               {
+                 oa->oa_base = (caddr_t)
+                   mem_alloc (oa->oa_length);
+               }
+             buf = XDR_INLINE (xdrs, RNDUP (oa->oa_length));
+             if (buf == NULL)
+               {
+                 if (xdr_opaque (xdrs, oa->oa_base,
+                                 oa->oa_length) == FALSE)
+                   return FALSE;
+               }
+             else
+               {
+                 memcpy (oa->oa_base, (caddr_t) buf, oa->oa_length);
+                 /* no real need....
+                    buf = (long *) ((char *) buf
+                    + RNDUP(oa->oa_length));
+                  */
+               }
+           }
+         oa = &cmsg->rm_call.cb_verf;
+         buf = XDR_INLINE (xdrs, 2 * BYTES_PER_XDR_UNIT);
+         if (buf == NULL)
+           {
+             if (xdr_enum (xdrs, &oa->oa_flavor) == FALSE ||
+                 xdr_u_int (xdrs, &oa->oa_length) == FALSE)
+               {
+                 return FALSE;
+               }
+           }
+         else
+           {
+             oa->oa_flavor = IXDR_GET_ENUM (buf, enum_t);
+             oa->oa_length = IXDR_GET_INT32 (buf);
+           }
+         if (oa->oa_length)
+           {
+             if (oa->oa_length > MAX_AUTH_BYTES)
+               return FALSE;
+             if (oa->oa_base == NULL)
+               {
+                 oa->oa_base = (caddr_t)
+                   mem_alloc (oa->oa_length);
+               }
+             buf = XDR_INLINE (xdrs, RNDUP (oa->oa_length));
+             if (buf == NULL)
+               {
+                 if (xdr_opaque (xdrs, oa->oa_base,
+                                 oa->oa_length) == FALSE)
+                   return FALSE;
+               }
+             else
+               {
+                 memcpy (oa->oa_base, (caddr_t) buf, oa->oa_length);
+                 /* no real need...
+                    buf = (long *) ((char *) buf
+                    + RNDUP(oa->oa_length));
+                  */
+               }
+           }
+         return TRUE;
+       }
+    }
+  if (
+       xdr_u_long (xdrs, &(cmsg->rm_xid)) &&
+       xdr_enum (xdrs, (enum_t *) & (cmsg->rm_direction)) &&
+       (cmsg->rm_direction == CALL) &&
+       xdr_u_long (xdrs, &(cmsg->rm_call.cb_rpcvers)) &&
+       (cmsg->rm_call.cb_rpcvers == RPC_MSG_VERSION) &&
+       xdr_u_long (xdrs, &(cmsg->rm_call.cb_prog)) &&
+       xdr_u_long (xdrs, &(cmsg->rm_call.cb_vers)) &&
+       xdr_u_long (xdrs, &(cmsg->rm_call.cb_proc)) &&
+       xdr_opaque_auth (xdrs, &(cmsg->rm_call.cb_cred)))
+    return xdr_opaque_auth (xdrs, &(cmsg->rm_call.cb_verf));
+  return FALSE;
+}
diff --git a/database/tokens_db.c b/database/tokens_db.c
new file mode 100644 (file)
index 0000000..659d0ff
--- /dev/null
@@ -0,0 +1,41 @@
+/* the tokens database */
+#include       "db_header.h"
+#include       <stdio.h>
+
+book_changeres add_token(tok_req * addtok)
+{
+    static book_changeres result;
+    static token tok;
+    char key[20];
+    item k;
+    
+    result = read_db(k = key_int(key, C_TOKENS, addtok->tnum),
+                    &tok,sizeof(tok),
+                    (xdrproc_t)xdr_token, CONFIG);
+    if ((result != C_OK) && (result != C_NOMATCH))
+       return result;
+
+    if(tracing)
+       printf(">read ok!!\n");
+
+    if (addtok->tok.advance.item_len == 0
+       && addtok->tok.late.item_len == 0)
+    {
+       /* delete token */
+       if (result == C_OK)
+       {
+           delete_from_db(k, CONFIG);
+           result = set_allotment(0, addtok->tnum, 0);
+       }
+       else result = C_OK;
+    }
+    else
+    {
+       /* now save the result */
+       result = write_db(k, &addtok->tok, (xdrproc_t)xdr_token, CONFIG); 
+
+       if (result == C_OK)
+           result = set_allotment(0, addtok->tnum, 1);
+    }
+    return result;
+}
diff --git a/database/users_db.c b/database/users_db.c
new file mode 100644 (file)
index 0000000..b8badb2
--- /dev/null
@@ -0,0 +1,280 @@
+#include       "db_header.h"
+
+/* the user database.
+   key: user_id, value: [bookings] [tokens] 
+   */
+/* The list of bookings contains all bookings the user has made.
+   The list of tokens contains all tokens the user has consumed.
+   */
+/* the operations required on this structure,
+   add booking
+   del booking
+   add user ?  maybe status instead!!!
+   del user ?
+*/
+
+static book_changeres add_to_userbooking(userbookinglist * ch, book_key *slot, int when)
+{
+       userbookinglist new_el;
+
+       /* maybe check if present */
+
+       new_el = (userbookinglist) malloc (sizeof(userbookingnode));
+
+       new_el->slot = *slot;
+       new_el -> when = when;
+       new_el -> next = *ch;
+       *ch  = new_el;
+       return C_OK;
+}
+
+static book_changeres del_from_userbooking(userbookinglist * ch, book_key *key,
+                                   int when)
+{
+       userbookinglist ptr = *ch;
+       userbookinglist trailer = ptr;  /* this points to the prev entry in the */
+       /* list to do deletions, easier than */
+       /* doubly linked list */
+       book_changeres result = C_NOMATCH;
+
+
+       while (ptr != NULL) {
+               if (ptr->slot == *key
+                   && ptr->when == when)
+               {           
+                       /* delete !! */
+                       if (ptr == trailer) {  /* first node */
+                               if (ptr -> next == NULL) /* only one node in list*/
+                                       *ch = NULL;
+                               else
+                                       *ch = ptr -> next;
+                       }
+                       else
+                               trailer->next = ptr-> next;
+                       /* cut the node off */
+                       ptr -> next = NULL;
+                       xdr_free((xdrproc_t)xdr_userbookinglist, (char*) &ptr);             
+                       result = C_OK;
+               }
+               else { /* go to the next element in the list */
+                       trailer = ptr;
+                       ptr = ptr->next;
+               }
+       }
+       return(result);
+}
+
+static userbookinglist finduserbook(userbookinglist *ch, book_key *key, int when)
+{
+
+       userbookinglist ptr = *ch;
+       while ((ptr != NULL) &&
+              !(ptr->slot == *key
+                && ptr->when == when)
+               )
+               ptr = ptr->next;
+
+       return(ptr);
+}
+
+
+/* if the key is present in the list inc it's value, else add it to */
+ /* the list with a value of 1 operation should always suceed*/
+static book_changeres incuserbook(userbookinglist *ch, slotgroup * key, int when)
+{
+       userbookinglist val;
+       book_key k;
+       slot2bookkey(key, &k);
+       if ((val = finduserbook(ch,&k,when)) == NULL) /* add new element to list */
+               return(add_to_userbooking(ch,&k,when));
+       else
+               return(C_OK); /* FIXME ? fail? */
+}
+
+
+/* ADD_USER_BOOKING: this operation assumes that all validataion has been */
+ /* performed for the user request of a booking.  Only required */
+ /* datastructures to be updated */
+
+book_changeres add_user_booking(book_req * booking, int clientreq)
+{
+       item k;
+       static users u_rec;
+       static book_changeres result;
+       intpairlist ipl;
+       char key[20];
+
+       /* read in record for this user */
+       result = read_db(k = user_key(key, booking->who_by),
+                        &u_rec, sizeof(users), (xdrproc_t)xdr_users, BOOKINGS);
+
+       if ((result != C_OK) && (result != C_NOMATCH))
+               return result;
+
+       /* check that user still has the number of tokens that we expected */
+       if (booking->tok_cnt != -1 && clientreq)
+       {
+               extern intpairlist findintkey();
+       
+               /* actually do the check */
+               intpairlist cp;
+               cp = findintkey(u_rec.tokens, booking->tnum);
+               if ((cp == NULL && booking->tok_cnt != 0)
+                   || (cp != NULL && cp->num != booking->tok_cnt))
+               {
+                       result = C_MISMATCH;
+                       return result;
+               }
+       }
+    
+       incintkey(&u_rec.tokens, booking -> tnum, booking->number);
+       ipl = findintkey(u_rec.priv_tokens, booking->tnum);
+       if (ipl)
+       {
+               ipl->num -= booking->number;
+               if (ipl->num <= 0)
+                       del_from_intpairlist(&u_rec.priv_tokens, ipl->data);
+       }
+       if (booking->who_by != booking->who_for)
+       {
+               result = write_db(k, &u_rec, (xdrproc_t)xdr_users, BOOKINGS);
+               k = user_key(key, booking->who_for);
+    
+               /* read in record for this user */
+               result = read_db(k, &u_rec, sizeof(users), (xdrproc_t)xdr_users, BOOKINGS);
+               if ((result != C_OK) && (result != C_NOMATCH))
+                       return result;
+       }       
+       /* now add booking to list */
+       /* perhaps this should be sorted!!!! */
+       result = incuserbook(&u_rec.bookings, &booking->slot, booking->when);
+
+       if (result != C_OK)
+               return result;
+    
+       /* now save the resultant record */
+       result = write_db(k, &u_rec, (xdrproc_t)xdr_users, BOOKINGS);
+       return result;
+    
+}
+
+/* For each user,
+ * move any bookings that are no-longer pending into pastbookings,
+ * and update the appropriate token
+ */
+
+book_changeres change_user_booking(book_chng * bchanges, int *refund, int *tokens, int *who_by)
+{
+       static users u_rec;
+       static book_changeres result;
+       item k;
+       char key[20];
+       int i;
+
+       result = C_OK;
+       for (i =0 ;i < bchanges->chlist.book_ch_list_len;i++)
+       {
+               /* read in record for this user */
+               k = user_key(key, bchanges->chlist.book_ch_list_val[i].user);
+               result = read_db(k, &u_rec, sizeof(users), (xdrproc_t)xdr_users, BOOKINGS);
+               if (result != C_OK)
+                       return result;
+
+               /* first del booking from list */
+               result = del_from_userbooking(&u_rec.bookings, &bchanges->slot,
+                                             bchanges->chlist.book_ch_list_val[i].when);
+/*     printf("result of del = %d\n",result);  */
+
+               if (result == C_OK)
+               {
+                       result = add_to_userbooking(&u_rec.pastbookings, &bchanges->slot,
+                                                   bchanges->chlist.book_ch_list_val[i].when);
+               }
+    
+               if (refund[i] && bchanges->chlist.book_ch_list_val[i].user != who_by[i])
+               {
+                       result = write_db(k, &u_rec, (xdrproc_t)xdr_users, BOOKINGS);
+                       if (result != C_OK)
+                               return result;
+
+                       k = user_key(key, who_by[i]);
+       
+                       result = read_db(k, &u_rec, sizeof(users), (xdrproc_t)xdr_users, BOOKINGS);
+                       if (result != C_OK)
+                               return result;
+
+               }
+               if (refund[i])
+                       decintkey(&u_rec.tokens,
+                                 tokens[i] & ~PRIV_REFUND_FLAG, refund[i]);
+               if (refund[i] && (tokens[i] & PRIV_REFUND_FLAG))
+                       incintkey(&u_rec.priv_tokens,
+                                 tokens[i] & ~PRIV_REFUND_FLAG, refund[i]);
+                     
+
+/*     printf("result deckey %d... user %s\n",result,key); */
+    
+               /* now save the resultant record */
+               result = write_db(k, &u_rec, (xdrproc_t)xdr_users, BOOKINGS);
+               if (result != C_OK)
+                       return result;
+       }
+    
+       return result;
+    
+}
+
+
+/* DEL_USER_BOOKING: deletes a booking from the user list */
+book_changeres del_user_booking(bookuid_t user, int when, book_key *bk)
+{
+       static users u_rec;
+       static book_changeres result;
+       item k;
+       char key[20];
+
+       k = user_key(key, user);
+
+    
+       /* read in record for this user */
+       result = read_db(k, &u_rec, sizeof(users), (xdrproc_t)xdr_users, BOOKINGS);
+       if (result != C_OK)
+               return result;
+
+    
+       /* first del booking from list,  look at past bookings first */
+       result = del_from_userbooking(&u_rec.pastbookings, bk, when);
+
+       /* is this a good idea?? */
+       if (result != C_OK) 
+               result = del_from_userbooking(&u_rec.bookings, bk, when);
+    
+       if (result != C_OK)
+               return result;
+
+       /* now save the resultant record */
+       result = write_db(k, &u_rec, (xdrproc_t)xdr_users, BOOKINGS);
+       return result;
+}
+
+/* DEL_USER : deletes a user record providing no bookings are attached */
+book_changeres del_user(bookuid_t user)
+{
+       static users u_rec;
+       static book_changeres result;
+       item k;
+       char key[20];
+
+       k = user_key(key, user);
+       result = read_db(k, &u_rec, sizeof(users),
+                        (xdrproc_t)xdr_users, BOOKINGS);
+       if (result != C_OK) return result;
+    
+       if (u_rec.pastbookings || u_rec.bookings)
+       {
+               /* there are bookings... just ignore req */
+               return C_MISMATCH;
+       }
+       result = delete_from_db(k, BOOKINGS);
+       return result;
+}
diff --git a/database/utils.c b/database/utils.c
new file mode 100644 (file)
index 0000000..ef9a36f
--- /dev/null
@@ -0,0 +1,51 @@
+/* some routines shared across modules */
+
+#include       "db_header.h"
+#include       <netdb.h>
+#include       <sys/file.h>
+#include       <sys/stat.h>
+#ifdef sun
+#include       <sys/fcntl.h>
+#endif
+
+item slotbits2key(int doy, int pod, description *desc)
+{
+    item key;
+    key.item_len = 2+desc->item_len;
+    key.item_val = (char*)malloc(key.item_len);
+    key.item_val[0] = doy;
+    key.item_val[1] = pod;
+    memcpy(key.item_val+2, desc->item_val, desc->item_len);
+    return key;
+}
+
+item slot2key(slotgroup *slot)
+{
+    return slotbits2key(slot->doy, slot->pod, &slot->what);
+}
+
+item bookkey2key(char *ky, book_key k)
+{
+    /* convert this to network byte order to keep the database happy */
+    /* maybe not!!! */
+    int key = htonl(k);
+    item retval;
+    retval.item_val = ky;
+    bcopy(&key, retval.item_val, sizeof(book_key));
+    retval.item_len = sizeof(book_key);
+    return retval;
+}
+
+void book_key_bits(book_key *bkey, int *doy, int *pod, int *lab)
+{
+    int key = *bkey;
+    if (pod) *pod = (key >>17) & 255;
+    if (doy) *doy = (key >>8) & 511;
+    if (lab) *lab = (key) & 255;
+}
+
+book_key make_bookkey(int doy, int pod, int lab)
+{
+    return  (pod<<17) | (doy <<8) | lab;
+}
+
diff --git a/database/xdr_mem.c b/database/xdr_mem.c
new file mode 100644 (file)
index 0000000..0cd69d6
--- /dev/null
@@ -0,0 +1,239 @@
+/*
+ * Sun RPC is a product of Sun Microsystems, Inc. and is provided for
+ * unrestricted use provided that this legend is included on all tape
+ * media and as a part of the software program in whole or part.  Users
+ * may copy or modify Sun RPC without charge, but are not authorized
+ * to license or distribute it to anyone else except as part of a product or
+ * program developed by the user.
+ *
+ * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
+ * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
+ *
+ * Sun RPC is provided with no support and without any obligation on the
+ * part of Sun Microsystems, Inc. to assist in its use, correction,
+ * modification or enhancement.
+ *
+ * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
+ * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
+ * OR ANY PART THEREOF.
+ *
+ * In no event will Sun Microsystems, Inc. be liable for any lost revenue
+ * or profits or other special, indirect and consequential damages, even if
+ * Sun has been advised of the possibility of such damages.
+ *
+ * Sun Microsystems, Inc.
+ * 2550 Garcia Avenue
+ * Mountain View, California  94043
+ */
+
+/*
+ * xdr_mem.h, XDR implementation using memory buffers.
+ *
+ * Copyright (C) 1984, Sun Microsystems, Inc.
+ *
+ * If you have some data to be interpreted as external data representation
+ * or to be converted to external data representation in a memory buffer,
+ * then this is the package for you.
+ *
+ */
+
+#include <string.h>
+#include <rpc/rpc.h>
+
+static bool_t xdrmem_getlong (XDR *, long *);
+static bool_t xdrmem_putlong (XDR *, const long *);
+static bool_t xdrmem_getbytes (XDR *, caddr_t, u_int);
+static bool_t xdrmem_putbytes (XDR *, const char *, u_int);
+static u_int xdrmem_getpos (const XDR *);
+static bool_t xdrmem_setpos (XDR *, u_int);
+static int32_t *xdrmem_inline (XDR *, int);
+static void xdrmem_destroy (XDR *);
+static bool_t xdrmem_getint32 (XDR *, int32_t *);
+static bool_t xdrmem_putint32 (XDR *, const int32_t *);
+
+static const struct xdr_ops xdrmem_ops =
+{
+  xdrmem_getlong,
+  xdrmem_putlong,
+  xdrmem_getbytes,
+  xdrmem_putbytes,
+  xdrmem_getpos,
+  xdrmem_setpos,
+  xdrmem_inline,
+  xdrmem_destroy,
+  xdrmem_getint32,
+  xdrmem_putint32
+};
+
+/*
+ * The procedure xdrmem_create initializes a stream descriptor for a
+ * memory buffer.
+ */
+void
+xdrmem_create (XDR *xdrs, const caddr_t addr, u_int size, enum xdr_op op)
+{
+  xdrs->x_op = op;
+  /* We have to add the const since the `struct xdr_ops' in `struct XDR'
+     is not `const'.  */
+  xdrs->x_ops = (struct xdr_ops *) &xdrmem_ops;
+  xdrs->x_private = xdrs->x_base = addr;
+  xdrs->x_handy = size;
+}
+
+/*
+ * Nothing needs to be done for the memory case.  The argument is clearly
+ * const.
+ */
+
+static void
+xdrmem_destroy (XDR *xdrs)
+{
+}
+
+/*
+ * Gets the next word from the memory referenced by xdrs and places it
+ * in the long pointed to by lp.  It then increments the private word to
+ * point at the next element.  Neither object pointed to is const
+ */
+static bool_t
+xdrmem_getlong (XDR *xdrs, long *lp)
+{
+  if ((xdrs->x_handy -= 4) < 0) {
+    xdrs->x_handy += 4;
+    return FALSE;
+  }
+  *lp = (int32_t) ntohl ((*((int32_t *) (xdrs->x_private))));
+  xdrs->x_private += 4;
+  return TRUE;
+}
+
+/*
+ * Puts the long pointed to by lp in the memory referenced by xdrs.  It
+ * then increments the private word to point at the next element.  The
+ * long pointed at is const
+ */
+static bool_t
+xdrmem_putlong (XDR *xdrs, const long *lp)
+{
+  if ((xdrs->x_handy -= 4) < 0) {
+    xdrs->x_handy += 4;
+    return FALSE;
+  }
+  *(int32_t *) xdrs->x_private = htonl (*lp);
+  xdrs->x_private += 4;
+  return TRUE;
+}
+
+/*
+ * Gets an unaligned number of bytes from the xdrs structure and writes them
+ * to the address passed in addr.  Be very careful when calling this routine
+ * as it could leave the xdrs pointing to an unaligned structure which is not
+ * a good idea.  None of the things pointed to are const.
+ */
+static bool_t
+xdrmem_getbytes (XDR *xdrs, caddr_t addr, u_int len)
+{
+  if ((xdrs->x_handy -= len) < 0) {
+    xdrs->x_handy += len;
+    return FALSE;
+  }
+  memcpy (addr, xdrs->x_private, len);
+  xdrs->x_private += len;
+  return TRUE;
+}
+
+/*
+ * The complementary function to the above.  The same warnings apply about
+ * unaligned data.  The source address is const.
+ */
+static bool_t
+xdrmem_putbytes (XDR *xdrs, const char *addr, u_int len)
+{
+  if ((xdrs->x_handy -= len) < 0) {
+    xdrs->x_handy += len;
+    return FALSE;
+  }
+  memcpy (xdrs->x_private, addr, len);
+  xdrs->x_private += len;
+  return TRUE;
+}
+
+/*
+ * Not sure what this one does.  But it clearly doesn't modify the contents
+ * of xdrs.  **FIXME** does this not assume u_int == u_long?
+ */
+static u_int
+xdrmem_getpos (const XDR *xdrs)
+{
+  return (u_long) xdrs->x_private - (u_long) xdrs->x_base;
+}
+
+/*
+ * xdrs modified
+ */
+static bool_t
+xdrmem_setpos (xdrs, pos)
+     XDR *xdrs;
+     u_int pos;
+{
+  caddr_t newaddr = xdrs->x_base + pos;
+  caddr_t lastaddr = xdrs->x_private + xdrs->x_handy;
+
+  if ((long) newaddr > (long) lastaddr)
+    return FALSE;
+  xdrs->x_private = newaddr;
+  xdrs->x_handy = (long) lastaddr - (long) newaddr;
+  return TRUE;
+}
+
+/*
+ * xdrs modified
+ */
+static int32_t *
+xdrmem_inline (XDR *xdrs, int len)
+{
+  int32_t *buf = 0;
+
+  if (xdrs->x_handy >= len)
+    {
+      xdrs->x_handy -= len;
+      buf = (int32_t *) xdrs->x_private;
+      xdrs->x_private += len;
+    }
+  return buf;
+}
+
+/*
+ * Gets the next word from the memory referenced by xdrs and places it
+ * in the int pointed to by ip.  It then increments the private word to
+ * point at the next element.  Neither object pointed to is const
+ */
+static bool_t
+xdrmem_getint32 (XDR *xdrs, int32_t *ip)
+{
+  if ((xdrs->x_handy -= 4) < 0) {
+    xdrs->x_handy += 4;
+    return FALSE;
+  }
+  *ip = ntohl ((*((int32_t *) (xdrs->x_private))));
+  xdrs->x_private += 4;
+  return TRUE;
+}
+
+/*
+ * Puts the long pointed to by lp in the memory referenced by xdrs.  It
+ * then increments the private word to point at the next element.  The
+ * long pointed at is const
+ */
+static bool_t
+xdrmem_putint32 (XDR *xdrs, const int32_t *ip)
+{
+  if ((xdrs->x_handy -= 4) < 0) {
+    xdrs->x_handy += 4;
+    return FALSE;
+  }
+  *(int32_t *) xdrs->x_private = htonl (*ip);
+  xdrs->x_private += 4;
+  return TRUE;
+}
diff --git a/db_client/Makefile b/db_client/Makefile
new file mode 100644 (file)
index 0000000..5850925
--- /dev/null
@@ -0,0 +1,6 @@
+
+lib += libdbclient.a
+
+obj-libdbclient.a +=  user_utils.o choose_id.o db_access.o mapping_utils.o process_exclusions.o db_modify.o full_bookings.o get_tokens.o parse_desc.o isdefaulter.o
+
+include $(S)$(D)../MakeRules
diff --git a/db_client/choose_id.c b/db_client/choose_id.c
new file mode 100644 (file)
index 0000000..7731b0c
--- /dev/null
@@ -0,0 +1,46 @@
+
+/*
+ * choose_id(int min, nmapping_type type)
+ * find a free id number in the mapping min or more.
+ *
+ * starting at min, search upwards in steps of 1,2,4,8 ...
+ * until a free space is found.
+ * then divide and concour until we find two consecutive
+ * indexes, first in use, second not in use
+ *
+ */
+
+#include       "db_client.h"
+
+int choose_id(int min, nmapping_type type)
+{
+    int step = 1;
+    int max;
+    char *n;
+
+    n = get_mappingchar(min, type);
+    if (n == NULL)
+       return min;
+
+    while (n != NULL)
+    {
+       free(n);
+       step *= 2;
+       n = get_mappingchar(min+step, type);
+    }
+    
+    max = min+step;
+    while (min+1 < max)
+    {
+       int mid = (max+min)/2;
+       n = get_mappingchar(mid, type);
+       if (n)
+       {
+           min = mid;
+           free(n);
+       }
+       else
+           max = mid;
+    }
+    return min+1;
+}
diff --git a/db_client/db_access.c b/db_client/db_access.c
new file mode 100644 (file)
index 0000000..f6e9cfa
--- /dev/null
@@ -0,0 +1,348 @@
+
+/*
+ * routines to access the book database from a client
+ * Provides global data (if asked for with get_*):
+ *   attr_types
+ *   impl_list
+ *
+ * Requires db_client handle and refresh_db_client()-> success
+ *
+ * provides db_read(item key, void *data, bool_t xdr_fn, db_number)
+ *  if db_number is CONFIG, replies are cached for quick re-access.
+ *  periodically check if CONFIG has changed and flush cache if it has
+ *
+ *
+ */
+
+#include       "db_client.h"
+#include       <stdlib.h>
+#include       <unistd.h>
+#include       <time.h>
+#include       "../lib/skip.h"
+typedef struct cache_ent
+{
+       item    key;
+       item    content;
+       time_t  db_time;
+} *cache_ent;
+
+CLIENT *db_client = NULL;
+
+static int cache_cmp(cache_ent a, cache_ent b, item *k)
+{
+       int rv;
+
+       if (b != NULL)
+               k = & b->key;
+    
+       rv = memcmp(a->key.item_val, k->item_val,
+                   a->key.item_len<k->item_len ? a->key.item_len:k->item_len);
+       if (rv) return rv;
+       else return k->item_len - a->key.item_len;
+}
+
+static void cache_free(cache_ent a)
+{
+       free(a->key.item_val);
+       free(a->content.item_val);
+       free(a);
+}
+
+struct
+{
+       cache_ent       *data;
+       time_t  db_time;        /* last modify time of db */
+       time_t  check_time;     /* last time we checked the modify time */
+       time_t  attr_time;
+       time_t  impl_time;
+       int     book_level;
+       int             min_class_uid;
+} config_cache = {0, 0, 0, 0, 0};
+
+attr_type_desc attr_types;
+impls  impl_list;
+
+void update_config_time(void)
+{
+       config_cache.db_time++;
+}
+
+
+void check_config_time(void)
+{
+       time_t now = time(0);
+       reply_res result;
+       db_updaterec db_status;
+
+       if (config_cache.check_time + 20*60 > now)
+               return;
+       /* time to get the config info again */
+
+       if (db_client == NULL
+           && refresh_db_client() == 0)
+               return; /* cannot get db client */
+
+       result = db_read_direct(str_key(STATUS_KEY), &db_status,
+                               (xdrproc_t)xdr_db_updaterec, CONFIG);
+       if (result == R_NOMATCH)
+               db_status.update_time = 1;
+       else if (result != R_RESOK)
+               return;
+       if (db_status.update_time > config_cache.db_time)
+       {
+               char *bl;
+               config_cache.db_time = db_status.update_time;
+               bl = get_configchar("book_level");
+               config_cache.book_level = 0;
+               if (bl)
+               {
+                       if (strcmp(bl,"off")==0)
+                               config_cache.book_level = 0;
+                       else if (strcmp(bl, "halfon")==0)
+                               config_cache.book_level = 1;
+                       else if (strcmp(bl, "on")==0)
+                               config_cache.book_level = 1;
+                       else if (strcmp(bl, "fullon")==0)
+                               config_cache.book_level = 2;
+                       free(bl);
+               }
+               config_cache.min_class_uid = get_configint("min_class_uid");
+               if (config_cache.min_class_uid <= 0)
+                       config_cache.min_class_uid = 0x4000000;
+       }
+       config_cache.check_time = now;
+}
+
+int get_book_level(void)
+{
+       check_config_time();
+       return config_cache.book_level;
+}
+
+int get_class_min(void)
+{
+       check_config_time();
+       return config_cache.min_class_uid;
+}
+
+char *get_exempt_group(void)
+{
+       /* find the name of the group in which users who are exempt
+          from the booking system (ie not logged out by the booking
+          system) are placed.
+       */
+  
+       return get_configchar("exempt_group");
+
+}
+
+
+int get_attr_types(void)
+{
+       check_config_time();
+       if (config_cache.attr_time < config_cache.db_time)
+       {
+               /* need to refresh this info */
+               item dbkey;
+
+               dbkey = str_key(ATTR_TYPE_KEY);
+               if (db_read(dbkey, &attr_types, (xdrproc_t)xdr_attr_type_desc, CONFIG)== R_RESOK)
+                       config_cache.attr_time = config_cache.db_time;
+       }
+       if (config_cache.attr_time)
+               return 1;
+       else
+               return 0;
+}
+
+int get_impl_list(void)
+{
+       check_config_time();
+       if (config_cache.impl_time < config_cache.db_time)
+       {
+               /* need to refresh this info */
+               item dbkey;
+               impls new;
+               impls one;
+               char key[20];
+
+               memset(&one, 0, sizeof(one));
+               dbkey = key_int(key, C_ATTR_DEF, 0);
+               if (db_read(dbkey, &one, (xdrproc_t)xdr_impls, CONFIG)== R_RESOK)
+               {
+                       /* keep reading */
+                       new.first = 0;
+                       new.total = one.total;
+                       new.exlist.exlist_len = one.total;
+                       new.exlist.exlist_val = (implication *)malloc(sizeof(implication)*one.total);
+
+                       while (new.first < new.total)
+                       {
+                               int i;
+                               for (i=0 ; i<one.exlist.exlist_len ; i++)
+                               {
+                                       new.exlist.exlist_val[new.first+i] =
+                                               one.exlist.exlist_val[i];
+                               }
+                               new.first += one.exlist.exlist_len;
+                               free(one.exlist.exlist_val);
+                               one.exlist.exlist_val = NULL;
+                               if (new.first < new.total)
+                               {
+                                       dbkey = key_int(key, C_ATTR_DEF, new.first);
+                                       if (db_read(dbkey, &one, (xdrproc_t)xdr_impls, CONFIG)!= R_RESOK)
+                                               new.total = -1;
+                               }
+                       }
+               }
+               else new.total = -1;
+
+               if (new.total >= 0)
+               {
+                       xdr_free((xdrproc_t)xdr_impls, (char*) &impl_list);
+                       impl_list = new;
+                       config_cache.impl_time = config_cache.db_time;
+               }
+       }
+       if (config_cache.impl_time)
+               return 1;
+       else
+               return 0;
+}
+
+static time_t want_tcp = 0;
+
+reply_res db_read(item key, void *data, xdrproc_t xdr_fn, db_number db)
+{
+
+       cache_ent *cep, ce;
+       if (db != CONFIG)
+               return db_read_direct(key, data, xdr_fn, db);
+
+       if (config_cache.data == NULL)
+               config_cache.data = skip_new(cache_cmp, cache_free, NULL);
+
+       check_config_time();
+    
+       cep = skip_search(config_cache.data, &key);
+       if (cep == NULL)
+       {
+               ce = (cache_ent)malloc(sizeof(struct cache_ent));
+               ce->key.item_val = memdup(key.item_val, key.item_len);
+               ce->key.item_len = key.item_len;
+               ce->content.item_len = 0;
+               ce->content.item_val = NULL;
+               ce->db_time = 0;
+               skip_insert(config_cache.data, ce);
+       }
+       else
+               ce = *cep;
+
+       if (ce->db_time < config_cache.db_time && db_client != NULL)
+       {
+               /* must refresh info from database */
+               query_req req;
+               query_reply *res;
+
+               req.key = key;
+               req.type = M_MATCH;
+               req.database = db;
+
+               res = query_db_2(&req, db_client);
+               if (res == NULL)
+               {
+                       /* try for a tcp client FIXME can I tell if reply was toooo big?? */
+                       want_tcp = time(0);
+                       refresh_db_client();
+                       if (db_client != NULL)
+                               res = query_db_2(&req, db_client);
+               }           
+
+               if (res != NULL)
+               {
+                       if (res->error == R_RESOK)
+                       {
+                               if (ce->content.item_val)
+                                       free(ce->content.item_val);
+                               ce->content.item_val = memdup(res->query_reply_u.data.value.item_val,
+                                                             res->query_reply_u.data.value.item_len);
+                               ce->content.item_len = res->query_reply_u.data.value.item_len;
+                               ce->db_time = config_cache.db_time;
+                       }
+                       else if (res->error == R_NOMATCH)
+                       {
+                               if (ce->content.item_val)
+                                       free(ce->content.item_val);
+                               ce->content.item_val = NULL;
+                               ce->content.item_len = 0;
+                               ce->db_time = config_cache.db_time;
+                       }
+                       xdr_free((xdrproc_t)xdr_query_reply, (char*) res);
+               }
+       }
+
+       if (ce->db_time > 0)
+       {
+               /* the data in the cache is valid */
+               if (ce->content.item_val == NULL)
+                       return R_NOMATCH;
+               item_decode(&ce->content, data, xdr_fn);
+               return R_RESOK;
+       }
+       else
+               return R_NOSERVER;
+}
+
+reply_res db_read_direct(item key, void *data, xdrproc_t xdr_fn, db_number db)
+{
+
+       query_req req;
+       query_reply *res;
+
+       req.key = key;
+       req.type = M_MATCH;
+       req.database = db;
+
+       if (db_client == NULL)
+               refresh_db_client();
+       if (db_client == NULL)
+               return R_NOSERVER;
+       
+       res = query_db_2(&req, db_client);
+
+       if (res == NULL)
+       {
+               /* try for a tcp client FIXME can I tell if reply was toooo big?? */
+               want_tcp = time(0);
+               refresh_db_client();
+               if (db_client == NULL)
+                       return R_NOSERVER;
+               res = query_db_2(&req, db_client);
+       }           
+       if (res == NULL)
+               return R_NOSERVER;
+
+       if (res->error == R_RESOK)
+               item_decode(&res->query_reply_u.data.value, data, xdr_fn);
+       xdr_free((xdrproc_t)xdr_query_reply, (char*) res);
+       return res->error;
+}
+
+void open_db_client(char *server, char *pr)
+{
+       char *proto = pr?pr:"udp";
+       if (db_client)
+       {
+               clnt_destroy(db_client);
+               db_client = NULL;
+       }
+       if (want_tcp > 0 && want_tcp > time(0)-10*60)
+               proto = "tcp";
+       if (server)
+               db_client = clnt_create(server, BOOK_DATABASE, BOOKVERS, proto);
+       else {
+               if (pr)
+                       want_tcp = time(0);
+               refresh_db_client();
+       }
+}
diff --git a/db_client/db_client.h b/db_client/db_client.h
new file mode 100644 (file)
index 0000000..ce4f8ab
--- /dev/null
@@ -0,0 +1,64 @@
+
+#include       "../database/db_header.h"
+#include       "../manager/status_client.h"
+
+
+extern CLIENT *db_client;
+extern impls impl_list;
+extern attr_type_desc attr_types;
+
+#include       "../database/common.h"
+
+/* from db_access.c */
+void update_config_time(void);
+void check_config_time(void);
+int get_book_level(void);
+int get_class_min(void);
+int get_attr_types(void);
+int get_impl_list(void);
+reply_res db_read(item key, void *data, xdrproc_t xdr_fn, db_number db);
+reply_res db_read_direct(item key, void *data, xdrproc_t xdr_fn, db_number db);
+void open_db_client(char *server, char *pr);
+
+/* from user_utils.c */
+int find_userid(char * userstring);
+char * find_username(int userid);
+int * user_classes(int uid);
+int my_innetgr(char *class, char *user);
+int in_class(int uid, int clid);
+int in_net_group(char *group, char *user);
+int in_neverclose(int uid, char *labname);
+char *user_surname(char * name);
+char *get_exempt_group(void);
+int in_exempt(int uid);
+
+/* from mapping_utils.c */
+int get_mappingint(char * tname, nmapping_type type);
+char * get_mappingchar(int num, nmapping_type type);
+char *get_configchar(char *name);
+int get_configint(char *name);
+
+/* from db_modify.c */
+book_changeres send_change(book_change *ch);
+book_changeres make_mapping(int key, char *val, nmapping_type type);
+book_changeres make_config(char *key, char *val);
+book_changeres do_removal(book_key key);
+
+/* from full_bookings.c */
+booklist getfullbookings(userbookinglist bl, bookuid_t user);
+
+/* from ../tools/show_booking.c */
+void show_booking(booklist bl, book_key slot);
+int char2bstatus(char *s);
+
+/* from choose_id.c */
+int choose_id(int min, nmapping_type type);
+
+/* from get_tokens.c */
+intpairlist get_alloc_tokenlist(bookuid_t user);
+
+/* from parse_desc.c */
+char *parse_desc(char *attrs, item *desc);
+
+/* from isdefaulter.c */
+int is_defaulter(int who);
diff --git a/db_client/db_modify.c b/db_client/db_modify.c
new file mode 100644 (file)
index 0000000..a7d9080
--- /dev/null
@@ -0,0 +1,64 @@
+
+#include       "db_client.h"
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <time.h>
+
+book_changeres send_change(book_change *ch)
+{
+    book_changeent che;
+    book_changeres *result;
+    book_changeres ret;
+    
+    if (db_client || refresh_db_client())
+    {
+       che.time = time(0);
+       che.machine = "";
+       che.changenum = 0;
+       che.thechange = *ch;
+       result = change_db_2(&che, db_client);
+       update_config_time();
+       if (result == NULL)
+       {
+           if (refresh_db_client())
+               result = change_db_2(&che, db_client);
+       }
+       if (result == NULL) {
+           ret = -1;
+       } else if (*result == C_OK) {
+           ret = 0;
+       } else ret = *result;
+    } else ret = -1;
+    return ret;
+}
+
+book_changeres make_mapping(int key, char *val, nmapping_type type)
+{
+    book_change ch;
+
+    ch.chng = SET_NAMENUM;
+    ch.book_change_u.map.type = type;
+    ch.book_change_u.map.key = key;
+    ch.book_change_u.map.value = val;
+    return send_change(&ch);
+}
+
+book_changeres make_config(char *key, char *val)
+{
+    book_change ch;
+
+    ch.chng = CONFIGDATA;
+    ch.book_change_u.configent.key = key;
+    ch.book_change_u.configent.value = val;
+    return send_change(&ch);
+}
+
+book_changeres do_removal(book_key key)
+{
+    book_change ch;
+    ch.chng = REMOVE_BOOKING;
+    ch.book_change_u.removal = key;
+    return send_change(&ch);
+}
+
+    
diff --git a/db_client/full_bookings.c b/db_client/full_bookings.c
new file mode 100644 (file)
index 0000000..da174f8
--- /dev/null
@@ -0,0 +1,43 @@
+
+#include       "db_client.h"
+
+/* GETFULLBOOKINGS: given a list of slots and times for a user, gets */
+ /* the full booking record (token name, request, count, etc) from the */
+ /* bookings database.  The slot and time the booking was made are the */
+ /* key to uniqly identify a booking.
+    Have to be carefull not to ask for too many bookings at one time,
+    as there is an 8K limit on the size of a UDP packet */
+booklist getfullbookings(userbookinglist bl, bookuid_t user)
+{
+    usermap_req req;
+    usermap_res *res;
+    booklist nbl = NULL;
+    booklist *nblp = &nbl;
+
+    while (bl)
+    {
+       userbookinglist bl_end, bl_rest;
+       int cnt;
+       req.user = user;
+       for (bl_end = bl, cnt=20 ; bl_end && cnt ; bl_end=bl_end->next, cnt--);
+       if (bl_end)
+       {
+           bl_rest=bl_end->next;
+           bl_end->next = NULL;
+       }
+       else
+           bl_rest = NULL;
+       req.blist = bl;
+       res = map_bookings_2(&req, db_client);
+       if (res == NULL || res->res != C_OK)
+           return NULL;
+       *nblp = res->blist;
+       res->blist = NULL;
+       while (*nblp)
+           nblp = & (*nblp)->next;
+       if (bl_end)
+           bl_end->next = bl_rest;
+       bl = bl_rest;
+    }
+    return nbl;
+}
diff --git a/db_client/get_tokens.c b/db_client/get_tokens.c
new file mode 100644 (file)
index 0000000..ece7ef8
--- /dev/null
@@ -0,0 +1,73 @@
+
+#include       "db_client.h"
+
+
+/* GETTOKENLIST: for a normal user returns a list of all the tokens */
+ /* user is allocated.  This is calculated by getting the allocations */
+ /* for all classes the user is a member of and then subtracting the */
+ /* tokens the user has consumed.
+    
+    For a class (uid > min_class_uid) all tokens are valid, so the token list
+    is calculated from the list of all valid tokens
+    */
+intpairlist get_alloc_tokenlist(bookuid_t user)
+{
+       int classsize;
+       int i;
+       int isclass = (user >= get_class_min());
+       int * class_list;
+       int r[3];
+       intpairlist toks = NULL;
+       intpairlist intres = NULL;
+    
+       /* if the user is a class, all tokens are valid, so pretend to be root */
+       if (isclass)
+       {
+               r[0] = 1;
+               r[1] = 0;
+               class_list = r;
+       }
+       else
+               class_list = user_classes(user);
+
+       if (class_list == NULL)
+       {
+               fprintf(stderr,"No classes\n");
+               return NULL;
+       }
+       /* the first element of array is number of users */
+       classsize = *class_list;
+
+
+       /* get allocations for all classes, skip the first */
+       for (i= 1; i <= classsize;i++)
+       {
+               char k[20];
+                int res;
+
+               if ((res = db_read(key_int(k, C_ALLOTMENTS, class_list[i]), &toks, (xdrproc_t)xdr_intpairlist, CONFIG))
+                   != R_RESOK) {
+//                    printf("db_read returned %d\n", res);
+                }
+                else
+               {
+//                    printf("db_read also returned %d\n", res);
+                    
+                           merge_intlists(&intres, &toks);
+                       xdr_free((xdrproc_t)xdr_intpairlist, (char*) &toks);
+               }
+       }
+       if (class_list != r)
+               free(class_list);
+       if (isclass)
+       {
+               intpairlist ip = intres;
+               while (ip)
+               {
+                       ip->num = 1000;
+                       ip  = ip->next;
+               }
+       }
+       return intres;
+    
+}
diff --git a/db_client/isdefaulter.c b/db_client/isdefaulter.c
new file mode 100644 (file)
index 0000000..c2e8b9b
--- /dev/null
@@ -0,0 +1,140 @@
+
+
+#include       "../db_client/db_client.h"
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <time.h>
+
+struct timebl { /* booklist wirg times */
+    int poy;
+    int lab;
+    booklist bl;
+};
+static int blcmp(const void *av, const void *bv)
+{
+       const struct timebl *a = av;
+       const struct timebl *b = bv;
+       return a->poy - b->poy;
+}
+
+int is_defaulter(int who)
+{
+       /* determine if user is a defaulter
+        *  Get the users record
+        *  take the pastbookings list and discard:
+        *              any booking in the future
+        *              any booking more than X in the past
+        *
+        *  Map_bookings to get actual bookings
+        *   Of bookings which were ALLOCATED,RETURNED,FREEBIE
+        *    sort by time (So consecutives may be found)
+        *
+        * heuristic 1:
+        *    for first in a consecutive list
+        *              count good if loggedin before grace and in last 14 days
+        *              count bad if not loggedin before grace and in last 14 days
+        *   if good+bad>=3 && good <= 2*bad,
+        *              is_defaulter
+        * heuristic 2:
+        *   find most recent defaulter booking more than 2 weeks old and only consider bookings since then.
+        *   If most recent two bookings were "good", then not is_defaulter
+        *   otherwise use heuristic 1
+        */
+       char k[20];
+       static users urec;
+       int good,bad;
+       int i;
+       booklist bk, bp;
+       struct timebl *blist;
+       userbookinglist *prev, *next, bn;
+       int memory_length = 14; /* how long we remember defaults */
+       int grace = get_configint("grace");
+       time_t now;
+       int cnt;
+       int consec_good;
+       int ndoy, npod;
+       if (db_read(user_key(k, who), &urec, (xdrproc_t)xdr_users, BOOKINGS) != R_RESOK)
+               return 0;
+
+       now=time(0);
+       get_doypod(&now, &ndoy, &npod);
+       for (prev = &urec.pastbookings; *prev ; prev = next)
+       {
+               int pod, doy;
+               bn = *prev;
+               next = &bn->next;
+               book_key_bits(&bn->slot, &doy, &pod, NULL);
+               if (doy>ndoy
+                   || (doy==ndoy && pod >= npod)
+                       )
+               {
+                       *prev = *next;
+                       *next = NULL;
+                       next = prev;
+                       xdr_free((xdrproc_t)xdr_userbookinglist, (char*)&bn);
+               }
+       }
+       bk = getfullbookings(urec.pastbookings, who);
+
+       for (cnt=0,bp=bk ; bp ; bp=bp->next)
+               if (bp->status == B_ALLOCATED || bp->status == B_RETURNED || bp->status == B_FREEBIE)
+                       cnt++;
+       blist = (struct timebl *)malloc(cnt*sizeof(struct timebl));
+       for (cnt=0,bp=bk, bn=urec.pastbookings ; bp ; bp=bp->next, bn=bn->next)
+               if (bp->status == B_ALLOCATED || bp->status == B_RETURNED || bp->status == B_FREEBIE)
+               {
+                       int doy, pod, lab;
+                       book_key_bits(&bn->slot, &doy, &pod, &lab);
+                       blist[cnt].bl = bp;
+                       blist[cnt].poy = doy*48+pod;
+                       blist[cnt].lab = lab;
+                       cnt++;
+               }
+       qsort(blist, cnt, sizeof(blist[0]), blcmp);
+       /* now list is a sorted list of allocated bookings */
+       good = bad = 0;
+       consec_good=0;
+       for (i=0; i<cnt ; i++)
+               if (i==0 || blist[i-1].poy != blist[i].poy-1 || blist[i-1].lab != blist[i].lab)
+               {
+                       booklist b = blist[i].bl;
+                       int isgood=0, isbad=0;
+                       /* this is a first of consecutive list */
+                       if ((b->claimed&DID_LOGIN) &&
+                           ((b->claimed&LOGIN_ON_ALLOC) || !(b->claimed&DID_DEFAULT))  &&
+                           ((b->claimed>>4)-30*60)< grace)
+                               isgood++;
+                       else if (b->claimed&DID_DEFAULT)
+                               isbad++;
+                       if (isbad && blist[i].poy/48 < ndoy-memory_length) /* old bad booking */
+                               isbad = good = bad = 0;
+                       if (isbad) consec_good=0;
+                       if (isgood) consec_good++;
+                       if (blist[i].poy/48 > ndoy-memory_length)
+                       {
+                               good+= isgood;
+                               bad += isbad;
+                       }
+               }
+       free(blist);
+       xdr_free((xdrproc_t)xdr_booklist, (char*)&bk);
+
+       /*
+        * Now:
+        *   'good' is the number of allocations that were taken up within the grace period,
+        *   'bad' is the number of bookings which defaulted and were not taken up
+        *   'consec_good' is the number of consecutive good allocations since the last bad one.
+        *
+        * The person is a defaulter if there are at least 2 'bad' allocations, and there have
+        * not been 2 consecutive good allocations, and 2*bad > good
+        *
+        */
+       return (bad >= 2 && 2*bad > good && consec_good < 2);
+       /*
+         if (consec_good >= 2 || consec_good >= bad)
+         return 0;
+         else
+         return (good+bad>=3 && good <= 2*bad);
+       */
+}
+
diff --git a/db_client/mapping_utils.c b/db_client/mapping_utils.c
new file mode 100644 (file)
index 0000000..78f9aa0
--- /dev/null
@@ -0,0 +1,74 @@
+
+#include       "db_client.h"
+
+
+int get_mappingint(char * tname, nmapping_type type)
+{
+    int result;
+    item key;
+    extern char * value_key();
+    
+    key = key_string(C_NUMBERS+type, tname);
+    switch (db_read(key, &result, (xdrproc_t)xdr_int, CONFIG))
+    {
+    case R_RESOK:
+       break;
+    case R_NOMATCH:
+       result = -1;
+       break;
+    default: result = -2;
+       break;
+    }
+    free(key.item_val);
+    return result;
+}
+
+char * get_mappingchar(int num, nmapping_type type)
+{
+    reply_res result;
+    item key;
+    char k[20];
+    char * retval = NULL;
+
+    key = key_int(k, C_NAMES+ type, num);
+    
+    result = db_read(key, &retval, (xdrproc_t)xdr_map_value, CONFIG);
+
+    if (result != R_RESOK)
+       return NULL;
+    
+    return (retval);
+}
+
+char *get_configchar(char *name)
+{
+    reply_res result;
+    item key;
+    char * retval= NULL;
+
+    key = key_string(C_GENERAL, name);
+    result = db_read(key, &retval, (xdrproc_t)xdr_map_value, CONFIG);
+    free(key.item_val);
+    if (result != R_RESOK)
+       return NULL;
+
+    return retval;
+}
+
+int get_configint(char *name)
+{
+    reply_res result;
+    item key;
+    char * retval = NULL;
+    int rv;
+
+    key = key_string(C_GENERAL, name);
+    result = db_read(key, &retval, (xdrproc_t)xdr_map_value, CONFIG);
+    free(key.item_val);
+    if (result != R_RESOK)
+       return -1;
+
+    rv = atoi(retval);
+    free(retval);
+    return rv;
+}
diff --git a/db_client/parse_desc.c b/db_client/parse_desc.c
new file mode 100644 (file)
index 0000000..fc09417
--- /dev/null
@@ -0,0 +1,32 @@
+
+/*
+ * parse_desc - convert a dot sep list of attributes in to a description
+ *
+ */
+#include       <string.h>
+#include       "db_client.h"
+
+char *parse_desc(char *attrs, item *desc)
+{
+       item d =  new_desc();
+       char *cp, *ep;
+
+       for (cp=attrs; *cp ; cp = (*ep)?(ep+1):ep)
+       {
+               char *a;
+               int anum;
+               ep = strchr(cp, '.');
+               if (ep == NULL) ep = cp+strlen(cp);
+               a = strndup(cp, ep-cp);
+               anum = get_mappingint(a, M_ATTRIBUTE);
+               if (anum < 0)
+               {
+                       free(d.item_val);
+                       return a;
+               }
+               set_a_bit(&d, anum);
+               free(a);
+       }
+       *desc = d;
+       return NULL;
+}
diff --git a/db_client/process_exclusions.c b/db_client/process_exclusions.c
new file mode 100644 (file)
index 0000000..c141892
--- /dev/null
@@ -0,0 +1,68 @@
+
+#include       "../db_client/db_client.h"
+
+static bool_t match_value(int val, int min, int max)
+{
+/*     printf("match %d (%d-%d)\n", val, min, max);  */
+    
+    if (val == -1)
+       return TRUE;
+    
+    return((val >= min) && (val <= max)) ;
+}
+
+/* CHECK_CLUSIONS: check the included and excluded attributes */
+static bool_t check_clusions(description * d1, description * d2, bool_t included)
+{
+    bool_t result;
+    description inverted;
+
+    
+    if (included)
+       result = required_bits(d1,d2);
+    else {
+       inverted = invert_desc(d2);
+       result = required_bits(d1,&inverted);
+       free(inverted.item_val);
+    }
+/*     printf("RESULT =%d\n",result);   */
+    return result;
+    
+}
+
+/* PROCESS_EXCLUSIONS: takes a pod, doy, machine_id and description */
+/* and uses to the exclusion table to decide which attributes should */
+/* be set */
+
+/* maybe need to decide if the supplied description should be used as */
+/* the output as well.  This could be another flag */
+/* the desc can be null, this means no exclusion.  If any of the */
+/* others are -1, this is the null value!, and these values will not */
+/* be included in the comparisons.  Some means of determining */
+/* defaults has to be devised */
+
+description * process_exclusions(int pod, int doy, description * desc)
+{
+    description *result;
+    int i;
+
+    get_attr_types();
+    get_impl_list();
+    
+    result = desc;
+    
+    for (i=0 ; i < impl_list.total ; i++)
+    {
+       implication *imp = &impl_list.exlist.exlist_val[i];
+       /* first compare the day of the year */
+       if ( match_value(doy, imp->startday, imp->endday)
+           && match_value(doy_2_dow(doy), imp->startdow, imp->enddow)
+           && match_value(pod, imp->starttime, imp->endtime)
+           && check_clusions(&imp->included, result, TRUE)
+           && check_clusions(&imp->excluded, result, FALSE)
+           )
+           set_a_bit(result, imp->attribute);
+    }
+    return (result);
+}
+
diff --git a/db_client/user_utils.c b/db_client/user_utils.c
new file mode 100644 (file)
index 0000000..d0525d9
--- /dev/null
@@ -0,0 +1,498 @@
+
+#include       "../db_client/db_client.h"
+#include       "../lib/skip.h"
+#include        "../manager/status_client.h"
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <time.h>
+#include       <sys/socket.h>
+#include       <string.h>
+#include       <stdio.h>
+#include       <pwd.h>
+#include       <netdb.h>
+#ifndef __CYGWIN__
+#include       <rpcsvc/ypclnt.h>
+#endif
+
+
+static char *domain = NULL;/* be careful with this on suns */
+
+extern char * strtok();
+
+
+char *ypmatch(char * key, char * map)
+{
+       char * result;
+       int resultlen;
+       int nb;
+    
+       if (domain == NULL)
+       {
+               int res;
+               if ((res =yp_get_default_domain(&domain)))
+               {
+                       printf("failed here, reason %d\n",res);
+                       return NULL;
+               }
+       
+       }
+       if (!(nb=yp_match(domain, map, key, strlen(key),&result, &resultlen)))
+       {
+               result[resultlen] = '\0';
+printf("YPMATCH %s %s -> %s\n", key, map, result);
+               return result;
+       }
+/*    else if (nb == 5)
+      return strdup("");
+*/
+       return NULL;
+}
+
+
+static int * add_int(int * tab, int new)
+{
+       int * result ;
+       int i;
+
+       if (tab == NULL)
+       {
+               result = (int *) malloc(2 * sizeof(int));
+               result[0] =1;
+       }
+    
+       else
+       {
+               result = (int *) malloc((tab[0] +2) * sizeof(int));
+               for (i=0;i <=tab[0];i++)
+                       result[i] = tab[i];
+               result[0]++;
+               free(tab);
+       }
+    
+       result[result[0]] = new;
+       return result;
+    
+}
+
+    
+int find_userid(char * userstring)
+{
+       struct passwd * pwent;
+       if ( (pwent = getpwnam(userstring)) != NULL)
+               return (pwent->pw_uid);
+       else
+               return(get_mappingint(userstring, M_CLASS));
+}
+
+typedef struct unc {
+       time_t when;
+       int uid;
+       char *uname;
+} *unc;
+static int unc_cmp(unc a, unc b, int *k)
+{
+       if (b) k= &b->uid;
+       return a->uid - *k;
+}
+static void unc_free(unc a)
+{
+       free(a->uname);
+       free(a);
+}
+static unc uncl = NULL;
+static void unc_add(int uid, char *name)
+{
+       unc a;
+       if (uncl)
+       {
+               unc *ap = skip_search(uncl, &uid);
+               if (ap)
+               {
+                       a = *ap;
+                       free(a->uname);
+                       a->uname = strdup(name);
+                       a->when = time(0);
+                       return;
+               }
+       }
+       a = (unc)malloc(sizeof(struct unc));
+       a->uid = uid;
+       a->uname = strdup(name);
+       a->when = time(0);
+       if (uncl==NULL)
+               uncl = skip_new(unc_cmp, unc_free, NULL);
+       skip_insert(uncl, a);
+}
+
+static char *unc_find(int uid)
+{
+       unc *a;
+       if (uncl == NULL) return NULL;
+       a = skip_search(uncl, &uid);
+       if (a && (*a)->when + 40*60 < time(0)) return (*a)->uname;
+       else return NULL;
+}
+
+char * find_username(int userid)
+{
+       struct passwd * pwent;
+       if (userid < get_class_min())
+       {
+               char *n = unc_find(userid);
+               if (n == NULL)
+               {
+                       if ((pwent = getpwuid(userid)) != NULL)
+                               unc_add(userid, n=pwent->pw_name);
+               }
+               return strdup(n);
+       }
+       else
+               return get_mappingchar(userid, M_CLASS);
+}
+#if 0
+int * user_classes(int uid)
+{
+       char * res;
+       char * username;
+       char * t;
+       int *result = NULL;
+       struct passwd * pwent;
+    
+
+       if ( (pwent = getpwuid(uid)) != NULL)
+               username = strdup(pwent->pw_name);
+       else
+               return NULL;
+
+       /* always the first one */
+       result = add_int(result, uid);
+       /* ypmatch username netgroup.byuser */
+       res = ypmatch(username, "netgroup.byuser");
+
+       /* this is as NIS+ on the suns will not allow "." in map names!!! */
+       if (res == NULL)
+               res = ypmatch(username, "netgroup_byuser");
+    
+       if (res == NULL)
+       {
+               free(username);
+               return(result);
+       }
+    
+       free(username);
+       /* map each classname to classid */
+
+
+       for (t = strtok(res, ",") ; t ; t = strtok(NULL, ","))
+       {
+               int clid;
+               clid = get_mappingint(t, M_CLASS);
+               if (clid > 0)
+                       result = add_int(result,clid);
+       }
+       free(res);
+       return(result);
+}
+#else
+
+/* find all classes that 'uid' is a member of.
+ * We first get a list of valid classes from the CONFIG db, and
+ * then use innetgr to check each of these
+ */
+int * user_classes(int uid)
+{
+       char * username;
+       int *classes = NULL;
+       struct passwd * pwent;
+       query_req request;
+       static time_t listtime = 0; /* age of list of classes */
+       static struct cl {
+               char *name;
+               int id;
+               struct cl *next;
+       } *list = NULL, *t;
+    
+       if ( (pwent = getpwuid(uid)) != NULL)
+               username = strdup(pwent->pw_name);
+       else
+               return NULL;
+       classes = add_int(classes, uid); /* always in their own class */
+
+       if (listtime + 10*60 < time(0L)) {
+
+               listtime = time(0L);
+               while (list) {
+                       t = list;
+                       list = list->next;
+                       free(t->name);
+                       free(t);
+               }
+
+               refresh_db_client();
+               request.key.item_val = strdup("206_");
+               request.key.item_len = 4;
+               request.type = M_NEXT_PREFIX;
+               request.database = CONFIG;
+
+               while (db_client) {
+                       query_reply *result = query_db_2(&request, db_client);
+
+                       free(request.key.item_val);
+
+                       if (result == NULL)
+                               break;
+
+                       if (result->error != R_RESOK ||
+                           result->query_reply_u.data.key.item_len == 0 ||
+                           result->query_reply_u.data.key.item_val == NULL)
+                               break;
+
+                       request.key.item_len = result->query_reply_u.data.key.item_len;
+                       request.key.item_val =
+                               memdup(result->query_reply_u.data.key.item_val,
+                                      request.key.item_len+1);
+                       request.key.item_val[request.key.item_len] = 0;
+
+                       if (strncmp(request.key.item_val, "206_", 4) == 0 &&
+                           strspn(request.key.item_val+4, "0123456789")
+                           == request.key.item_len-4) {
+                               /* Ok, looks like a class number/name */
+                               char *name = NULL;
+                               int clid = atoi(request.key.item_val+4);
+                               item_decode(&result->query_reply_u.data.value,
+                                           &name,
+                                           (xdrproc_t)xdr_map_value);
+                               
+                               t = malloc(sizeof(*t));
+                               t->name = name;
+                               t->id = clid;
+                               t->next = list;
+                               list = t;
+                       }
+
+                       xdr_free((xdrproc_t)xdr_query_reply, (char*)result);
+               }
+       }
+       for (t=list ; t ; t=t->next) {
+               if (innetgr(t->name, NULL, username, NULL))
+                       classes = add_int(classes, t->id);
+       }
+
+       return classes;
+}
+#endif
+
+typedef struct ngc {
+    time_t when;
+    char *user;
+    char *groups;
+} * ngc;
+
+#if 0
+static int ngc_cmp(ngc a, ngc b, char *k)
+{
+       if (b) k=b->user;
+       return strcmp(a->user, k);
+}
+static void ngc_free(ngc a)
+{
+       free(a->user);
+       free(a->groups);
+       free(a);
+}
+static ngc ngcl = NULL;
+static void ngc_add(char *user, char *grps)
+{
+       ngc a, *ap;
+       if (ngcl)
+       {
+               ap = skip_search(ngcl, user);
+               if (ap)
+               {
+                       a=*ap;
+                       free(a->groups);
+                       a->groups = strdup(grps);
+                       time(&a->when);
+                       return;
+               }
+       }
+       a = (ngc)malloc(sizeof(struct ngc));
+       a->user = strdup(user);
+       a->groups = strdup(grps);
+       time(&a->when);
+       if (ngcl == NULL)
+               ngcl = skip_new(ngc_cmp, ngc_free, NULL);
+       skip_insert(ngcl, a);
+}
+
+static char *ngc_find(char *user)
+{
+       ngc *a;
+       if (ngcl == NULL) return NULL;
+       a = skip_search(ngcl, user);
+       if (a && (*a)->when + 40*60 > time(0))
+               return (*a)->groups;
+       else
+               return NULL;
+}
+#endif
+
+#if 0
+#ifdef INNETGR_BY_USER
+int my_innetgr(char *class, char *user)
+{
+       char * res;
+       char * t;
+       int rv = 0;
+    
+
+       /* ypmatch username netgroup.byuser */
+       res = ngc_find(user);
+       if (res)
+               res = strdup(res);
+       else
+       {
+               res = ypmatch(user, "netgroup.byuser");
+
+               /* this is as NIS+ on the suns will not allow "." in map names!!! */
+               if (res == NULL)
+                       res = ypmatch(user, "netgroup_byuser");
+    
+               if (res == NULL)
+               {
+                       return -1;
+               }
+
+               ngc_add(user, res);
+       }
+       /* map each classname to classid */
+
+
+       for (t = strtok(res, ",") ; t ; t = strtok(NULL, ","))
+               if (strccmp(class, t)==0)
+                       rv = 1;
+       free(res);
+       return rv;
+}
+#else
+int my_innetgr(char *class, char *user)
+{
+       /* First lookup innetgr:group,host,user,dom.
+        * If thse finds something, let it say yes or no.
+        * Otherwise fall back to normal innetgr
+        */
+       char *ng = malloc(8+strlen(class)+1+0+1+strlen(user)+1+0+1);
+       if (ng) {
+               sprintf(ng, "innetgr:%s,,%s,", class, user);
+               if (setnetgrent(ng)) {
+                       char *u, *h, *d;
+                       if (getnetgrent(&h, &u, &d)) {
+                               /* Successful lookup */
+                               int rv = 0;
+                               if (strcasecmp(user, u)==0)
+                                       rv = 1;
+                               endnetgrent();
+                               free(ng);
+                               return rv;
+                       }
+                       endnetgrent();
+               }
+               free(ng);
+       }
+       return innetgr(class, NULL, user, NULL);
+}      
+#endif
+#else
+int my_innetgr(char *class, char *user)
+{
+       return innetgr(class, NULL, user, NULL);
+}
+#endif
+int in_class(int uid, int clid)
+{
+       char *uname, *cname;
+       int rv;
+    
+       if (uid == clid) return 1;
+       if (clid < get_class_min()) return 0;
+       if (uid < 0 || clid < 0) return 0;
+
+       uname = find_username(uid);
+       cname = get_mappingchar(clid, M_CLASS);
+
+       if (uname == NULL || cname == NULL)
+               rv = -1;
+       else
+               rv = in_net_group(cname, uname);
+       if (uname) free(uname);
+       if (cname) free(cname);
+       return rv;
+}
+
+
+int in_net_group(char *group, char *user)
+{
+/*    return innetgr(group, NULL, user, NULL); */
+       return my_innetgr(group, user);
+}
+
+int in_exempt(int uid)
+{
+       /* gets the name of the exempt group, checks
+          if user is in that group */
+
+       char *name;
+       char *group;
+       int rv=0;
+
+       name = find_username(uid);
+       group = get_exempt_group(); 
+       if (name && group)
+               rv = in_net_group(group, name);
+       if (name) free(name);
+       if (group) free(group);
+       return rv;
+
+}
+
+
+    
+int in_neverclose(int uid, char *labname)
+{
+       char clname[200];
+       char *name = find_username(uid);
+       int rv;
+    
+       if (name && labname)
+       {
+               strcpy(clname, "book.neverclose.");
+               strcat(clname, labname);
+               rv = in_net_group(clname, name);
+               free(name);
+       }
+       else
+               rv = 0;
+       return rv;
+}
+
+
+char *user_surname(char * name)
+{
+       char * result;
+       char * eos;
+       struct passwd * pwent;
+       if ( (pwent = getpwnam(name)) != NULL)
+       {
+       
+               result = strdup(pwent->pw_gecos);
+               eos = strchr(result, ',');
+               if (eos) *eos = '\0';
+               eos = strrchr(result, ' ');
+               if (eos && eos > result && eos[1]) strcpy(result, eos+1);
+               return result;
+       }
+       else
+               return NULL;
+}
+
+
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644 (file)
index 0000000..960c5aa
--- /dev/null
@@ -0,0 +1,2 @@
+
+target-y +=
diff --git a/doc/book.texinfo b/doc/book.texinfo
new file mode 100644 (file)
index 0000000..29e386b
--- /dev/null
@@ -0,0 +1,4121 @@
+\input texinfo @c -*-texinfo-*-
+@c %**start of header
+@setfilename book.info
+@settitle TABS - The Terminal Allocation and Booking System
+@c @setchapternewpage off
+@headings double
+@afourpaper
+@c %**end of header
+
+@ifinfo
+
+@node TOP, Introduction, (dir), (dir)
+@top TABS - The Terminal Allocation and Booking System
+@comment  node-name,  next,  previous,  up
+This document describes the workstation booking system developed at,
+and used by, the School of Computer Science and Engineering, at the
+University of New South Wales.
+
+@end ifinfo
+
+@titlepage
+@title TABS
+@subtitle The Terminal Allocation and Booking System
+@subtitle from The University of New South Wales
+@author The School of Computer Science and Engineering
+@author Computing Support Group
+@end titlepage
+
+
+
+@menu
+* Introduction::                
+* Concepts::                    
+* Components::                  
+* Bookings Database::           
+* Laboratory Status and Management::  
+* The full scoop on tokens and attributes::  
+* Setting it up::               
+* UNSW Local Lore::            
+* Interaction with other systems::  
+* Policy::                      
+@end menu
+
+@node Introduction, Concepts, TOP, TOP
+@chapter Introduction
+TABS - The Terminal Allocation and Booking System assists in controlling
+access to computing laboratories in the School of Computer Science and
+Engineering at The University Of New South Wales.
+
+It has been under development since 1994 and has a heritage extending
+back to other systems dating back to 1983. Though it has now reached a
+sufficient level of maturity that it can be given to others to use, it
+is still in several ways a @emph{work-in-progress}.
+
+
+
+@menu
+* Whats in a name?::            
+* What the user sees::          
+* The Administrators perspective::  
+* Tools::                       
+@end menu
+
+@node Whats in a name?, What the user sees, Introduction, Introduction
+@section What's in a name?
+The answer to this question is, of course "four words"...
+
+@table @samp
+@item Terminal
+The system deals with @dfn{terminals} or @dfn{workstations} as the basic
+unit which is controlled. i.e. TABS only controls access to the computer
+system. No other resource (such as CPU time etc) is controlled.
+
+@item Allocation
+The system allocates terminals to particular users, or groups of
+users. Having made this allocation is honours the allocation by making
+sure that, at the appropriate time, no one else is using the terminal.
+This involves evicting any current user, and baring access to any
+non-booked user. This is the sole pro-active part of TABS.
+
+@item Booking
+The system accepts bookings by users or classes for workstations.
+A booking identifies the time when the workstation is needed, and the
+lab in which the workstation will be allocated. It does not identify a
+particular workstation - that is what allocation is for.
+The number of bookings which can be made, and the possible time/lab
+choices can be restricted with great flexibility.
+
+@item System
+TABS is not just a single program. It is a collection of daemons, (for
+storing, maintaining and monitoring the state of the system, and for
+evicting user), associated tools (for reporting on and changing the
+state of the system).  It needs to interact fairly closely with the
+login process.
+@end table
+
+As TABS is primarily responsible for monitoring and controlling logins, it
+easily keeps track of both current and past logins, and so is useful for
+keeping @emph{Tabs} on users.
+
+@node What the user sees, The Administrators perspective, Whats in a name?, Introduction
+@section What the user sees
+
+Possibly the best way to give an introductory overview of the booking
+system is to describe some of the interactions that an ordinary user of
+the booking system might have with the system.
+
+
+
+@menu
+* Making a booking::            
+* Claiming a booking::          
+* Being evicted::               
+@end menu
+
+@node Making a booking, Claiming a booking, What the user sees, What the user sees
+@subsection Making a booking
+Probably the first interaction a user will have is when they attempt
+to make a booking.
+This is done by running the program @code{book}, either while logged
+in normally, or by logging in to a @dfn{Booking Terminal}.
+
+@code{book} is an interactive line oriented program with online help.
+It allows the user to examine the bookings they have made, both
+pending and past, and to make new bookings or cancel old bookings. 
+
+A booking is made by using the @code{book} command within the
+@code{book} program.
+This command can be given a range of times to try to find a bookable
+slot in, or can just be allowed to find the first available slot.
+It will offer any available time-slots to the user for confirmation.
+Once the user confirms a time slot, they will be asked to choose a
+token to be used for the booking (if there is more than one option)
+and then choose the lab in which the booking will be made (again, only if
+there is more than one option).
+
+Time slots are half an hour long. A booking can be made for one of more
+consecutive half hour periods.
+
+Once a booking has been made, the user knows @emph{when} it is for, and
+@emph{which lab} it will be in. Note however, that the user does not know
+which of the lab's workstations they will be allocated until the time has
+come to claim the booking.
+
+
+@node Claiming a booking, Being evicted, Making a booking, What the user sees
+@subsection Claiming a booking
+
+To claim a booking, the user must turn up at the appropriate lab at
+the appropriate time, preferably a few minutes early.
+
+In or near the lab will be an Allocation Display Terminal (or
+@dfn{Skyterm}).
+This is a @samp{Dumb Terminal} which continually displays unclaimed
+allocations in one or more labs.
+The user should find an entry on the display for their booking.
+It will have the first three letters of their family name, their login
+name, and the name of the workstation that they have been allocated.
+
+They should then go and find that workstation.
+If someone is currently using the workstation, the booking system will
+be sending them warnings that they should log off, and will force them
+to log off at the start time for the user's booking if they do not log
+off voluntarily.
+
+The booked user should wait for the current user to log off (if
+necessary) and then simply log on.
+
+If the user turns up late to claim their booking, they will have until
+seven minutes past the start of the period (starting on the hour or half
+hour) to log in.  During this time, no-one else will be permitted to log
+in.
+
+If they arrive more than seven minutes late, and someone else has
+already logged in then they will miss out.
+If they had only booked a single half hour period, they are probably
+too late to be able to do any useful work anyway.
+If they had booked several half hour periods, it is NOT sufficient to
+wait for the next period and find the workstation reserved for them
+then.
+This is because the booking system "expects" them not to turn up, and
+so will not evict anyone to make room for them.
+In this situation, the user should log in to a booking terminal and
+use the @code{claim} command.
+This will find out which workstation the user was allocated to, and
+will commence eviction proceeding for whoever is logged on. This will
+give the currently logged on user at least 5 minutes to log off.
+The booked user will then be able to log on normally.
+
+@node Being evicted,  , Claiming a booking, What the user sees
+@subsection Being evicted
+If a user is logged on to a workstation, but is not booked for the next
+half hour period, then there is a fair chance that they will be evicted
+at the end of the current period.  If this is to happen, then 10 minutes
+before the end of the current period, a warning message will appear on
+their screen in a window similar to the windows that @code{xmessage}
+uses.  The window will have a button on it allowing the user to
+acknowledge the message and make it go away. Further warning messages
+will appear at 6, 3, 2, and 1 minute to go. Each message will make it
+clear how much longer the current user has until they will be forced to
+log off.  If they persist in not logging off, then, when the time
+arrives, all their windows will disappear, quite probably all their
+processes will be killed, and a login window will appear so that the
+booked user may log in.  Note that the eviction process does not
+physically remove the user from the workstation as effective, legal
+techniques to achieve this are yet to be developed.
+
+@node The Administrators perspective, Tools, What the user sees, Introduction
+@section The Administrators perspective
+
+The other side of the coin to what the user sees is the system
+administrator's point of view. 
+This section discusses the various components that the administrator
+sees.
+
+There are two important components to the inner workings of the booking
+system. They are the database and the lab management components which
+are introduced here and discussed in depth in subsequent chapters.
+
+
+
+@menu
+* The Database::                
+* Lab Management::              
+@end menu
+
+@node The Database, Lab Management, The Administrators perspective, The Administrators perspective
+@subsection The Database
+The booking system maintains a database of information which controls
+the running of the system.
+This information can broadly be classified into two classes:
+configuration information and booking information.
+
+Configuration information identifies all the workstations and hosts
+which will be managed by the system, as well as the various booking
+privileges which different users have.
+
+Booking information records all bookings that have been made along
+which their current status, such as @code{pending}, @code{cancelled},
+@code{allocated} etc.
+The database also stores some summary information such as how many more
+bookings can be made in each lab in each time-slot.
+
+This database can be replicated on a number of computers to provide high
+availability, and is remotely accessible over the network via ONC-RPC.
+Each copy of the database is managed by a pair of programs, one
+(@code{book_database}) which provides a controlled RPC interface to the
+files which store the information, and one (@code{book_maint}) which
+works to keep the multiple copies up to date with respect to one
+another.
+
+@node Lab Management,  , The Database, The Administrators perspective
+@subsection Lab Management
+
+Lab management involves monitoring the status of hosts and workstations
+within a lab (or group of labs), allocating bookings to workstations as
+appropriate, and evicting users from workstations when required.
+
+The status of a lab, or group of labs, is represented in a table. This
+table lists various information about the lab such as who is logged
+on which workstation, and when they logged in; who is allocated to which
+workstation; and which workstations are currently working.
+
+This table is passed from host to host around all the hosts which manage
+workstations in a lab group. Each host updates any information in the
+table which it is responsible for before passing it on.
+
+One host in the group is designated a @dfn{master} and is responsible
+for retarding the speed of the table passing so that is does not exceed
+once around every 30 seconds, and for extracting booking information
+from the booking database as required, and allocated the bookings to
+particular workstations.
+
+The task of lab management rests on a pair of programs called
+@code{book_status} and @code{book_manager}.
+@code{book_status} holds a current copy of the table and responds to
+ONC-RPC requests to examine and update the table.
+@code{book_manager} periodically retrieves a copy of the table from the
+@code{book_status}, confirms and updates some of the information, and
+passes the table onto the next host in the list.
+@code{book_manager} also notes when some one needs to be warned to log
+off and possibly evicted, and takes the necessary action.
+
+@code{book_status} also maintains (with some help from
+@code{book_manager}) a list of known database locations so that clients
+can quickly find an active, up-to-date database replica.
+For this reason it is often useful to run @code{book_status} on all
+computers in a network rather than just those that are involved in
+running a bookable lab.
+
+@node Tools,  , The Administrators perspective, Introduction
+@section Tools
+
+To maintain the database and to monitor the labs, there are a number of
+tools available.
+These are simple, non-interactive, commands which inspect and modify
+state information.
+Many of the commands are named after the parts of the system that they
+deal with (e.g. @code{token}, @code{booking} @code{lab}).
+These are detailed in @ref{Components}.
+
+@node Concepts, Components, Introduction, TOP
+@chapter Concepts
+There are a number of central concepts in the booking system. The
+following defines them and shows how they inter-relate.
+
+
+
+@menu
+* Workstation::                 
+* Host::                        
+* Lab::                         
+* HostGroup::                   
+* Period::                      
+* Attribute::                   
+* Token::                       
+* Class::                       
+* Full and Free Periods::       
+* Open and Closed Times::       
+* Allocations::                 
+* Evictions and Reservations::  
+* Defaulters::                  
+* Booking::                     
+* Database::                    
+* Lab status::                  
+@end menu
+
+@node Workstation, Host, Concepts, Concepts
+@section Workstation
+A @dfn{workstation} is the I/O station (keyboard, monitor, and mouse)
+that a user uses to (hopefully) do work. It is the thing which the
+client of the booking system actually books and is allocated.  In
+reality, it may be a workstation in the more traditional sense of the
+word, an X terminal, or even a @var{dumb} terminal.
+
+Unfortunately, it is not yet possible to book modems as we would need
+the modem to check the calling telephone number before answering the
+telephone.
+
+@node Host, Lab, Workstation, Concepts
+@section Host
+A @dfn{host} is a thing which supports one or more workstations; ie: the
+host will execute processes which use the workstation(s) for user
+interaction.  In the case of traditional workstation, the @emph{host}
+and the @emph{workstation} are one and the same, but where X terminals
+are used, a host will usually be responsible for supporting multiple
+@emph{workstations}.
+
+Often a @emph{workstation} will be tied to a particular host, but this is not
+necessary.
+
+@node Lab, HostGroup, Host, Concepts
+@section Lab
+A @dfn{Lab} is a collection of physically close workstations, which are
+normally similar in configuration.
+When a booking is made, it is allocated to a particular @emph{Lab}, but not
+to any particular workstation within that @emph{Lab}.
+
+@node HostGroup, Period, Lab, Concepts
+@section HostGroup
+A @dfn{HostGroup} is a group of @emph{hosts} that work together to
+maintain a group of @emph{Labs}.  In the case of a lab of workstations,
+the @emph{HostGroup} is most simply the group of workstation/hosts in
+the lab, while a lab of Xterminals all controlled by the one Xserver
+will have a @emph{HostGroup} made up that single Xserver host.
+
+In general however, the @emph{HostGroup} is chosen such that it is the
+smallest group of hosts needed to control an integral number of labs.
+
+Thus it may be necessary to have 2 Xterminal labs controlled by a
+@emph{HostGroup} of 3 Xserver hosts if both labs had some Xterminals
+controlled by a common Xserver host.
+
+@node Period, Attribute, HostGroup, Concepts
+@section Period
+A period is a half hour beginning on the hour or the half hour. A booking
+can be made for one or more periods.
+
+There are 48 periods in a day, and 365 (or 366) days in a year.
+TABS has no concept of a sequence of years. There is only one year - the
+current year.
+This is not normally a problem as academic years fit within calander
+years (at least in the southern hemisphere) though could be a problem
+with summer session subjects.
+
+
+@node Attribute, Token, Period, Concepts
+@section Attribute
+The differences between different workstations and different periods are
+recorded using a set of boolean @dfn{attributes} which a workstation or period
+may or may not possess.
+
+Every given workstation has a set of attributes
+associated with it for every given period.
+
+There may be attributes which indicate:
+@itemize @bullet
+@item type of host which supports a workstation
+@item type of display which the workstation has
+@item which lab a workstation is in
+@item which week of session a period is in
+@item whether a particular lab is closed during a particular period
+@item whether part time students should be given preferential access to
+a particular period.
+@end itemize
+
+Attributes are primarily used to determine booking privileges as
+described under @emph{Tokens}.
+
+@node Token, Class, Attribute, Concepts
+@section Token
+
+A @dfn{token} conveys a right to book a single workstation for a single
+period. @emph{Tokens} are allocated to classes and (occasionally) to
+users and are used up by making bookings.  When a booking is cancelled
+or allocated, the token that was used to make it may be refund,
+depending on several variables.  It may also be refunded in a special
+form which provides extra power --- see Policy.
+
+A token contains a set of attributes and conveys the right to book any
+workstation/period combination that only has attributes from that set.
+Thus, if a token has the attributes corresponding to weeks 1, 2, and 3
+of session, but no other week attributes set, then it can only be used
+to make bookings during those weeks.
+
+A token contains a second set of attributes which may be broader than
+the first set and is used if a booking is being made less than 4 days in
+advance. The rational for this is described later (I hope).
+
+Tokens may or may not be refundable.
+Non refundable tokens convey the right to a certain amount of terminal
+time. When it has been used up, it is gone.
+Refundable tokens convey a share of the available terminal time. The
+more one has, the greater share of the available time can be booked and
+hence used.
+This is (Will be) discussed in more detail in "The full scoop on tokens
+and attributes".
+
+@node Class, Full and Free Periods, Token, Concepts
+@section Class
+It is useful to group users together into classes for the purposes of
+token allocation and class bookings.
+When a number of tokens are allocated to a class, that number of tokens
+becomes available to each member of the class.
+Similarly if a booking is made for a class then the workstations
+reserved for that booking are made available to any member of the class.
+
+Class membership is recorded using netgroups. The booking system
+software uses YP lookup of the @code{netgroup.byuser} or
+@code{netgroup_byuser} to access class/netgroup information.
+
+Classes are treated internally as very similar to normal users. To keep
+a clear distinction, classes are assigned uid numbers greater than
+some large value, normally 1073741824 (40000000 in base 16). This value
+is configurable
+This means that all users, whose uids are looked up via normal
+mechanisms, must have uids less than this.
+
+@node Full and Free Periods, Open and Closed Times, Class, Concepts
+@section Full and Free Periods
+The distinction between full and free periods is only relevant when
+refundable tokens are not being used.
+
+A @dfn{full} period in a lab is any period in which the total number of
+bookings exceeds 90% of the number of available workstations.
+A @dfn{free} period is any other period.
+
+Bookings which are allocated in a free period fill have the token
+refunded. This is to encourage people to book times which are likely to
+be under used. If refundable tokens are used, this is fairly irrelevant
+as these tokens are refunded even for full periods.
+
+@node Open and Closed Times, Allocations, Full and Free Periods, Concepts
+@section Open and Closed Times
+
+The booking system knows about three different sorts of times: Times
+where a lab is open, times when it is closed, and other times.
+
+@itemize @bullet
+@item
+Bookings can only be made for times when a lab it open. Also, these are
+the only times when the system will attempt to allocate bookings to
+workstations.
+
+@item
+When a lab is closed, anyone who is logged on will be evicted, and no-one
+will be allowed to log in. This helps lab supervision staff to encourage
+people to leave the room so that it may be locked.
+
+@item
+When a lab is neither open nor closed, the booking system simply
+continues monitoring the lab and does not impose on logins at all. This
+state is particularly appropriate for the first half hour after a lab is
+opened in the morning: people should be allowed to login, but bookings
+should not be allowed, in case the lab is opened slightly late by
+mistake.
+@end itemize
+
+@node Allocations, Evictions and Reservations, Open and Closed Times, Concepts
+@section Allocations
+
+@dfn{Allocation} is the process of assigning bookings to workstations.
+This is done 10 minutes before the start of a period.
+
+Allocation is performed by ordering the bookings by the time they were
+made (so that bookings that are made early get preference); ordering the
+workstations in a @samp{most available} order, and then sequentially
+assigning the bookings to either (a) a workstation that the booked user
+is already using or (b) the first workstation that matches the
+requirements of the booking.
+
+The ordering of workstations is done in the following classifications:
+@itemize @bullet
+@item
+Unused workstations in order of id number come first.
+
+@item
+Workstations that are in use by users who are not booked for the coming
+period come next. Within this group, workstations are ordered by the
+amount of un-booked time that the user has used. Thus people who have
+been unbooked for a while are more likely to be evicted.
+
+@item
+Workstations that are in use by users who are booked for the coming
+period. These will only get allocated to other users if there are not
+enough workstations to go around.
+
+@item
+Finally, workstations which are not working. Rather than allocating a
+booking to a broken workstation, the allocation procedure will record
+that the booking could not be allocated and will refund the token.
+@end itemize
+
+Allocations come in two flavours: normal and firm.
+
+Normal allocations will reserve a workstation for a user for only
+a few (seven) minutes. If they do not turn up, then anyone else may log
+in.
+
+Firm allocations will reserve the workstation for the entire period of
+the booking. These are normally used only for supervised class bookings
+when the supervisor wants only people from the booked class to be in the
+room.
+
+TAKE TWO
+
+@itemize @bullet
+@item
+Any bookings for users who were allocated a workstation in the previous
+period, but are not logged on, are marked as @dfn{tentative} meaning
+that no eviction will be performed for them.
+@item
+Any bookings for a user who is determined to be a defaulter (see below)
+is marked as tentative.
+@item
+The bookings for each lab are sorted by the time that they are made,
+except that tentative bookings are sorted last.
+@item
+The workstations are sorted with
+@itemize @bullet
+@item 
+Free workstations first in no particular order
+@item
+Workstations with someone logged in who has not booked for the coming
+period next. These workstations are sorted by how long the person has
+been logged on since their last booking. People who have been on a long
+time are sorted earlier in the list.
+@item
+Workstation with someone who has booked for the coming period.
+@item
+Workstation which don't seem to be working.
+@end itemize
+@item
+These two lists are then processed together, normally allocating the first booking
+to the first workstation, the second booking to the second workstation
+etc.
+If, however, a booking is found for a user who is currently logged in to
+an unallocated workstation, the booking is allocated to that workstation
+instead of the next one.
+@end itemize
+This allocation process achieves the following goals:
+@itemize @bullet
+@item
+Booked users who are currently logged on don't have to change
+workstation.
+@item
+Users who book multiple successive time slots, and don't turn up for the
+first, will have subsequent bookings allocated as @emph{tentative} which
+will not cause an eviction. (If they do turn up late, they can claim
+their booking at a booking terminal).
+@item
+When the lab is not fully booked, current users who have had recent
+bookings will be less likely to be evicted than users who have not had
+recent bookings.
+@item
+If the case so some workstations malfunctioning so that not all bookings
+can be honoured, bookings that were made earlier are more likely to be
+honoured.
+@end itemize
+
+@node Evictions and Reservations, Defaulters, Allocations, Concepts
+@section Evictions and Reservations
+
+Evictions and reservations are the two @emph{weapons} in the booking
+system's arsenal for honouring bookings.
+
+Eviction involves forcing someone who is not booked to leave a
+workstation that has been allocated to someone else. This is done by
+sending warning messages every few minutes for the ten minutes before
+the allocation begins, and then by logging the user out if there
+remained on despite all the warnings.
+Logging the user out is done by signalling the @emph{xdm} process which
+is controlling the login session.
+
+Reservation involves preventing anyone but the allocated user from
+logging in to a particular workstation. This is done by simply denying
+login, which is very easy to do when using @emph{xdm}.
+
+@node Defaulters, Booking, Evictions and Reservations, Concepts
+@section Defaulters
+
+A @dfn{defaulter} is a user of the booking system who has made some
+bookings, but did not turn up to claim them.
+TABS attempts to record when a user does or does not claim their
+booking, and applies a simple heuristic to determine whether they seem
+to be displaying a pattern of not claiming bookings.
+
+If a user is determined to be a @dfn{defaulter}, any allocation made for
+them will be made as a tentative allocation, so it will not cause an
+eviction or a reservation.
+The defaulter can still claim their bookings by using the @code{claim}
+command at a booking terminal.
+They will only need to do this a couple of times in order for TABS to
+determine that they are no longer to be treated as a @dfn{defaulter}.
+
+@node Booking, Database, Defaulters, Concepts
+@section Booking
+
+A @dfn{booking} is a request to be allocated a workstation in a
+particular lab at a particular time.
+
+@node Database, Lab status, Booking, Concepts
+@section Database
+
+The @dfn{Database} hold all of the non-transient state of the booking
+system.
+This includes the hardware (workstations, hosts, labs), the
+configuration (attributes, tokens, implications) and bookings.
+
+@node Lab status,  , Database, Concepts
+@section Lab status
+
+@dfn{Lab status} refers to the transient status of the workstations and
+hosts in a lab. This includes current allocations, current usage or
+workstations, current state of workstations.
+
+
+@node Components, Bookings Database, Concepts, TOP
+@chapter Components
+
+
+@menu
+* dbadmin::                     
+* smadmin::                     
+* booking::                     
+* bkuser::                        
+* token - the program::         
+* bkattr::                        
+* bkconf::                        
+* bkhg::                        
+* lab - the program::           
+* bookterm::                    
+* bogin::                       
+* book::                        
+* tkbook::                      
+* messaged::                    
+* saveauth::                    
+* sendmess::                    
+* book_login::                  
+* book_logout::                 
+* book_database and book_maint::  
+* book_status and book_manager::  
+@end menu
+
+@node dbadmin, smadmin, Components, Components
+@section dbadmin
+
+The @code{dbadmin} command is used to help administer the database
+replica, particular when creating and deleting replicas.
+
+If @code{dbadmin} need to find a database server in order to perform its
+task, it will use the normal procedure of asking the local book_status
+server. It may be necessary or desirable to over ride this, such as when
+there is no status server running yet. In this case a @code{-r replica}
+flag may be given, in which case @code{dbadmin} will consult that
+replica as needed.
+
+The various uses are as follows:
+@table @code
+@item dbadmin [ -r replica ] -s [-A | serverlist]
+This will discover and show the status of various replicas. If a
+@code{serverlist} is given, then each server is queried in turn.
+If @code{-A} is given, then all registered servers will be queried.
+
+@item dbadmin [ -r replica ] -a replica
+This will register the given replica as an active replica.
+@code{dbadmin} will only attempt this if the new replica indeed seems to
+be working and is not currently registered.
+
+@item dbadmin [ -r replica ] -d replica
+This will de-register the given replica. If the @code{-r replica} flag is
+not given, then @code{dbadmin} will send the change request to the
+replica being deleted.
+
+@item dbadmin -x replica
+This command instructs the named replica to exit, which is slightly
+cleaner than sending it a SIGTERM signal.
+
+@item dbadmin -R replica
+This command instructs the replica to re-organise its datafiles.
+Re-organisation is needed as GDBM files will never shrink, even when
+lots of entries are removed.
+This command is no longer necessary as in normal operation, the
+@code{book_database} will automatically re-organise files about once a
+day.
+@end table
+
+The status information reported with @code{dbadmin -s} has one line for
+each server reported on. The line contains the state of the server,
+either @code{UP} for full update mode, @code{CP} for copy only mode, or
+@code{NO} for no update mode. 
+Following this is a list of all registered replicas that the server
+knows about, together with the number of the last change received from
+that replica.
+
+@node smadmin, booking, dbadmin, Components
+@section smadmin
+
+@code{smadmin} is used for monitoring the status manager pair of
+servers.
+It can send commands to, and receive information from, the
+@code{book_status} server.
+
+The various uses are:
+@table @code
+@item smadmin -t @var{host}
+Get the status table from the host and print it.
+The resulting output has one line for each record in the table. See
+section on @ref{Status Table} for detailed information.
+
+@item smadmin -d @var{host}
+Get the list of database replicas from the host and print it.
+There will be one line for each replica showing the name of the host
+and the last known status of that replica.
+The list is ordered with the most preferred replica first.
+
+@item smadmin -D @var{host}
+Update the list of database replicas stored at the host. This collects
+the list, checks each listed replica to discover its status, and to
+discover if there are any changes to the list of registered replicas,
+and then sends changes back to the @code{book_status} server.
+
+This operation is performed regularly by the @code{book_manager}
+program.
+
+@item smadmin -r @var{host}
+
+Tell the @code{book_status} daemon to kill and restart its
+@code{book_manager}.
+This is useful after installing a new version of @code{book_manager},
+and also after updating information which would change the list of
+entries in the status table.
+
+When the @code{book_manager} starts, it extracts relevant host, lab,
+workstation etc. information from the database and builds a new status
+table. This table is merged with the current table stored in the
+@code{book_status} program ensuring the the status table has correct
+form and current information.
+@end table
+
+
+@node booking, bkuser, smadmin, Components
+@section booking
+
+The @code{booking} program is used to inspect, and to a limited extent
+change, bookings by time and lab.
+It does not allow the making of new bookings. This is solely the
+prerogative of the @code{book} program.
+
+@code{booking} does two quite distinct tasks: listing, and possibly
+deleting, bookings; and changing the status of a list of bookings.
+
+
+
+@menu
+* Listing Bookings::            
+* Changing the status of bookings::  
+@end menu
+
+@node Listing Bookings, Changing the status of bookings, booking, booking
+@subsection Listing Bookings
+
+@example
+ booking [-R] dates periods labs
+@end example
+
+To list bookings, @code{booking} should be given a range of dates, a
+range of times, and one or more labs. It will then list all the bookings
+in the database that match those criteria.
+
+The date range and the time range are in the same format as used for
+implications.
+e.g. @code{29feb-05mar} or @code{9:00-13:30}.
+The time  range in optional and defaults to the whole day.
+
+The list of labs is given as one or more attributes.
+Any lab which implies (though the implications) one of the attributes
+ins included. Alternately, the labs can be given as @code{ALL} meaning
+all labs.
+
+The bookings are listed one per line giving: username, date, period,
+lab, status, number-of-workstations-booked, token-used, and the time
+that the booking was made.
+There may also be listed the user who made the booking (if it is
+different from the user who the booking is for) and the list of
+workstations that the booking was allocated to, if the booking has been
+allocated.
+
+For example:
+@example
+fred 29mar 09:30 in club ALLOCATED x1 undercroft 27/03;14:32:23 club07:0
+fred 01apr 10:00 in ring PENDING x1 undercroft 27/03;14:34:43
+COMP1011 02apr 11:00 in ring PENDING x19 ringlabbooking 01/03;09:31:03
+@end example
+
+In this example, @code{club} and @code{ring} are labs, @code{undercroft}
+and @code{ringlabbooking} are tokens, @code{fred} is a user and
+@code{COMP1011} is a class, which booked all 19 workstations in the ring lab.
+
+When listing bookings, @code{booking} can also remove bookings from the
+database.
+This is done to clean out old data, and should not be done until
+bookings are a couple of weeks old.
+
+When given the @code{-R} flag, @code{booking} will attempt to remove the
+bookings for each period/lab that it lists. The removal will only
+succeed if none of the bookings for the time/lab are still PENDING.
+
+@node Changing the status of bookings,  , Listing Bookings, booking
+@subsection Changing the status of bookings
+
+Most changes to bookings are best done with the @code{book} program.
+However, this program can only deal with one user at a time, so it is
+not good at, for instance, cancelling all the bookings that were made
+for a particular time. This is a task that @code{booking} can do.
+
+@example
+ booking -s new-status < list-of-bookings
+@end example
+
+When given a list of bookings, and a new booking status, @code{booking}
+will attempt to set the status of that booking to the given status.
+The attempt can fail as status changes are one way. It is not, for
+instance, possible change change a booking from @code{CANCELLED} to
+@code{PENDING}.
+
+The possible status are PENDING, TENTATIVE, ALLOCATED, RETURNED,
+REFUNDED, CANCELLED, FREEBIE, which are (or should be) described else
+where.
+
+The list of bookings should appear on the standard input and should
+contain, one per line: date, period, lab, user, time-made.
+These fields can be easily extracted from the output of @code{booking}.
+For example, to cancel all the pending bookings in March, the following
+command could be used.
+
+@example
+ booking 01mar-31mar ALL | awk '$6 ~ /PENDING/ @{ print $2, $3, $5, $1, $11 @}' |
+    booking -s CANCELLED
+@end example
+
+
+@node bkuser, token - the program, booking, Components
+@section bkuser
+
+The @code{bkuser} program reports information about users and classes, and
+can allocate tokens to users and classes.
+It can also discard old information about users and classes when tidying
+up between sessions.
+
+The information that can be reported on is most available though the
+@code{book} program as well, however @code{bkuser} makes the information
+more readily available for post processing.
+
+There are three usages from @code{bkuser}: reporting, allocating tokens,
+and renaming classes.
+
+
+
+@menu
+* Reporting on users::          
+* Creating classes and allotting tokens::  
+* Renaming classes::            
+@end menu
+
+@node Reporting on users, Creating classes and allotting tokens, bkuser, bkuser
+@subsection Reporting on users
+@example
+ bkuser -[Dtabp] [-m] [user|ALL]
+@end example
+With this form of usage, @code{bkuser} will report on one or more users
+and will possibly delete token usage information, providing that there
+are no bookings recorded.
+
+The flags have the following meanings.
+@table @code
+@item -D
+Delete token usage information for the user.
+Booking information must already have been deleted via the
+@code{booking} program.
+Allocation (allotment) information must be changed with the next usage
+of @code{bkuser}.
+
+@item -m
+Provide reports in @emph{machine readable} form. This just removes any
+noise and makes the report uniform and easily parsable.
+
+@item -t
+Report on token allocation and usage. For each token that has been
+allocated to the user (or a class which the user is a member of), the
+token name, the number allocated, and the number used (not currently
+available for reuse) are listed.
+
+@item -a
+Report on personal allocations. This is similar to @code{-t} except that
+only tokens allocated to that particular user or class are listed.
+
+@item -b
+Report on pending bookings. All pending bookings for the user are list
+in the same format as used for @code{booking} except that the leading
+username is left off.
+
+@item -p
+Report on past bookings. All bookings that are not pending (e.d,
+allocated or cancelled bookings) are listed in the same format as for
+@code{-b}.
+
+@end table
+If none of @code{-tabp} are given, then @code{-tbp} is assumed.
+
+@node Creating classes and allotting tokens, Renaming classes, Reporting on users, bkuser
+@subsection Creating classes and allotting tokens
+
+@example
+ bkuser [-C [-i id]] [-A token -c count] user ...
+@end example
+
+In this form, @code{bkuser} can register (create) class names and can
+allot tokens to classes or users.
+@table @code
+@item -C
+Register (create) the given names as class names, if they don't already
+exist.
+@item -i idnumber
+When registering a class name, use the id number given as its internal
+id number. Normally an unused id number greater than
+@strong{min_class_id} will be used.
+@item -A token
+Allocate some of the given token to the given users or classes.
+@item -c count
+Indicates how many tokens to be allocated.
+If @code{count} starts with a plus sign, then that many tokens are added
+to each users allocation.
+If @code{count} starts with a minus sign, then the given number of
+tokens is subtracted.
+Otherwise @code{bkuser} ensures that precisely the number given is
+allocated to each user or class given.
+@end table
+
+@node Renaming classes,  , Creating classes and allotting tokens, bkuser
+@subsection Renaming classes
+@example
+ bkuser -R oldname newname
+@end example
+This simply changes the name of a class. The new name should exist as a
+netgroup, though this is not currently checked.
+
+
+@node token - the program, bkattr, bkuser, Components
+@section token
+
+The @code{token} program is used to create, modify, examine, and discard
+tokens.
+
+
+
+@menu
+* Creating a token::            
+* Modifying a token::           
+* Listing tokens::              
+* Discarding Tokens::           
+@end menu
+
+@node Creating a token, Modifying a token, token - the program, token - the program
+@subsection Creating a token
+@example
+ tools/token [-f] -c tokenname [-i idnum] attributes
+@end example
+
+This usage creates a token with the given @code{tokenname}. An
+@code{idnum} can be given but is not normally necessary as an unused id
+number will otherwise be allocated.
+The @code{-f} suppresses checking incase the token already exists.
+
+The attributes listed are assigned to both attribute sets of the token.
+
+@node Modifying a token, Listing tokens, Creating a token, token - the program
+@subsection Modifying a token
+@example
+ tools/token -R oldname newname
+ tools/token -[ads] attributes token[/EL]
+@end example
+
+The first usage given here simply changes the name of the token.
+
+The second usage changes the attributes assigned to a token or tokens.
+The attributes should be a period separated list of attribute names.
+The flags @code{a}, @code{d}, and @code{s} mean to @samp{add},
+@samp{delete}, and @samp{set} the given attributes respectively.
+
+There may  be several token names given.
+If a token name is given unadorned, then both the early, and the late
+attribute sets are changed.
+If the token name is suffixed with @code{/E} then only the early token
+set is changed. If suffixed with @code{/L} then only the late token set
+is changed.
+
+@node Listing tokens, Discarding Tokens, Modifying a token, token - the program
+@subsection Listing tokens
+@example
+ tools/token [-v] -l
+ tools/token tokenname
+@end example
+The first usage lists all tokens, either just as token name or, with
+@code{-v}, with complete information.
+The second usage lists the full detail of the named token or tokens.
+
+The full detail lists the name of the token, the list of attributes used
+for advance bookings, and the list of attributes used for late bookings.
+For example
+@example
+ % tools/token firstyear
+ firstyear
+ Advance:
+   weekdays yellow cyan open available
+ Late:
+   weekend weekdays yellow cyan magenta open available
+@end example
+In this example, students with the @code{firstyear} token can book the
+yellow and cyan tokens on weekdays (provided that they are open and the
+machines are available (not out of service)) in advance.
+Providing that other more deserving students have not already booked the
+labs out, they can also book weekend times and the magenta labs when
+making a late booking.
+
+@node Discarding Tokens,  , Listing tokens, token - the program
+@subsection Discarding Tokens
+@example
+  tools/token -D tokenname
+@end example
+
+This usage simply deletes the token. If this token is currently
+allocated to anyone, that allocation remains, but will be ineffective as
+the token will no longer exist.
+
+@node bkattr, bkconf, token - the program, Components
+@section bkattr
+
+@example
+  tools/bkattr attrfile
+  tools/bkattr
+@end example
+
+The @code{bkattr} program is used to set and examine the attributes and
+implications.
+See @ref{Attributes} and @ref{Implications} for the format of attribute
+and implication descriptions.
+
+The first form reads the given file and makes sure that the recorded
+attributes and implications match what is in the file.
+The second form reports on the current set of attributes and
+implications in a form that is suitable for input.
+
+
+@node bkconf, bkhg, bkattr, Components
+@section bkconf
+
+@example
+  tools/bkconf
+  tools/bkconf name
+  tools/bkconf name value
+@end example
+The  @code{bkconf} program accesses and modifies the miscellaneous
+configuration values.
+The first form lists all values.
+The second form lists the named value.
+The third form sets the named value to the given value.
+
+@node bkhg, lab - the program, bkconf, Components
+@section bkhg, bkhost, bklb, bkws
+
+The @var{hostgroup}s, @var{host}s, @var{lab}s, and @var{workstation}s
+and their relationships can be registered using the commands
+@code{bkhg}, @code{host}, @code{bklb} and @code{bkws} respectively.
+
+These are all actually one program which determines what sort of object
+to manipulate by examining the least few character of the command used
+to invoke it.
+
+As @var{lab}s are registered as attributes via the @code{bkattr} program,
+@code{bklb} will not create or remove labs.
+
+There are two usages for these command that are common across all four
+commands:
+@example
+  tools/bk@var{object} -D @var{objectname}
+  tools/bk@var{object} -R @var{oldobjectname} @var{newobjectname}
+@end example
+The first will delete the named object, while the second will rename
+the named object.
+
+The third usage lists the relevant object(s) after possibly creating
+and/or modifying them.
+As the possible modifications differ between objects, each object type
+will be treated separately. In all cases, the @code{-c} allows the
+object to be created if it doesn't already exist.
+
+
+
+@menu
+* Creating hostgroups::         
+* hosts and labs::              
+* workstations::                
+@end menu
+
+@node Creating hostgroups, hosts and labs, bkhg, bkhg
+@subsection Creating hostgroups
+
+@example
+tools/bkhg [-c] @var{hostgroupname}
+@end example
+
+No modifications are possible on hostgroup, they may only be created and
+listed.
+The listing simply lists the hosts and labs in the hostgroup:
+@example
+HostGroup: spectrum
+  hosts:
+    red, green, blue
+  labs:
+    cyan, magenta, yellow
+@end example
+
+@node hosts and labs, workstations, Creating hostgroups, bkhg
+@subsection hosts and labs
+
+@example
+tools/bkhost [-c] [-g group] @var{hostname}
+tools/bklb [-g group] @var{labname}
+@end example
+
+Hosts and labs are treated much the same by this program as the only
+thing that can be done to them is to put them in a hostgroup.
+This is done with the @code{-g} flag.
+
+The program will then list the lab or host giving the hostgroup that it
+is in, and any workstations attached to it.
+
+@example
+Host: green           in HostGroup: spectrum
+  workstations:
+    yellow01:0, cyan01:0
+@end example
+
+@node workstations,  , hosts and labs, bkhg
+@subsection workstations
+
+@example
+tools/bkws [-c] [-l lab] [-h host] [-d attributes] [ -r reason] @var{workstationname}
+@end example
+
+Each workstation has a set of attributes, possibly a controlling host,
+and possibly a reason why it has been taken out of service.
+This command allows all those to be set.
+
+@table @code
+@item -l lab
+will remove any lab attributes currently set and set the lab attribute
+for the given lab.
+
+@item -h host
+will set the given host to be the controlling host for the workstation.
+If the host is either the empty string or given as @code{none}, then
+the workstation will have no controlling host.
+
+@item -d attributes
+@code{attributes} should be a period separated list of attributes,
+possibly preceded by a plus or minus sign. This attribute list is then
+added to, subtracted from, or assigned to the set of attributes for the
+workstation. This option is not currently very useful as non-lab
+attributes are not supported very well.
+
+@item -r reason
+This will take a workstation out of service by removing the
+@code{available} bit (which is set by default) and recording the
+reason that the workstation is out of service, and the time it was
+taken out of service.  If the reason string is the empty string, the
+workstation is returned to service.
+@end table
+
+The command will then list the workstation(s) giving name, host, reason
+if applicable, and attributes:
+@example
+Workstation: cyan01:0        On Host: green
+   cyan, available
+@end example
+
+@node lab - the program, bookterm, bkhg, Components
+@section lab
+
+@example
+tools/lab [-udosASht] labname ...
+@end example
+
+The @code{lab} program is used to view the current status workstations
+in one or more labs.
+It will normally list all workstations with their current status, login
+and allocation information.
+
+For each lab, @code{lab} will output a status line for the whole lab,
+followed by one line for each workstation.
+
+The lab status line reports the status of the lab (FULL, FREE, or
+CLOSED), which host performed the last allocation, the time when that
+allocation takes effect, and the time that the allocation was made.
+
+The workstation status lines report the workstation name, the
+workstation state (Up or Down), the allocation state (OutOfServuce,
+Broken, NotBookable, UnAllocated, Allocated, Tentative), the users
+currently logged on (if any), and the user or class currently allocated
+(if any).
+A final column will contain either the host which the workstation is
+currently logged on to (or was most recently logged on to), or the time
+of the last login or logoff, depending on a command line option.
+
+The set of workstations actually listed, plus a few other aspects of the
+output are controlable by the command line options.
+
+@table @code
+@item -u
+List only workstation which appear to be UP.
+
+@item -d
+List only workstation which appear to be DOWN.
+
+@item -o
+List only workstation which are OutOfService, and give the reason that
+was given when taking them out of service.
+
+@item -s
+Don't print the lab status lines. This is useful with @code{-d} or
+@code{-o} to get just a list of the offending workstations.
+
+@item -t
+Show the time of last login or logoff in the final column. This is the
+default.
+
+@item -h
+Show the host of the last login in the final column.
+
+@item -A
+List all labs.
+
+@item -S
+List all Student labs. Their are all labs for which the @code{open}
+attribute is set.
+
+@end table
+
+As with other programs which accept lab names, the lab names given on
+the command name can be any attribute. All labs which imply anyone of
+the given attributes will be reported on.
+Thus, for example @code{lab -A} is equivalent to @code{lab open}.
+
+
+@node bookterm, bogin, lab - the program, Components
+@section bookterm
+
+The @code{bookterm} program displays a continually updated list of
+unclaimed allocations, and available and unusable workstations.
+
+@code{bookterm} is normally run on a number of dedicated @emph{dumb}
+terminals which are positioned in or near labs, though it can equally
+well be run in an @code{xterm} or elsewhere.
+
+The display consists of a two line header, and a number of 24 character
+wide columns. On a standard @code{80x24} display, there will be three
+columns each with 20 rows.
+The header lists the labs that are being reported on, the start time of
+the current allocation period, the page number, and the time that the
+display was last updated.
+
+Each row of each column contains some information about the state of one
+workstation, or possibly lists an unallocatable booking.
+The different rows that are normally displayed are the following, listed
+here in the order that they will normally be listed on the display.
+@table @samp
+@item sur-username..workstation
+This indicates that the given workstation has been allocated to the
+given user, and that the user has not yet logged on.
+The @code{sur} refers to the first three letters of the users
+surname. It is the primary sort key for there rows.
+@item sur*username..workstation
+This indicates that the given workstation has been tentatively allocated
+to the given user, but that someone else is using the workstation.
+The user would have to use the @code{claim} command at a booking
+terminal to gain access to the workstation.
+This syntax is a little obscure, and may well be changed in a future
+release.
+@item sur-username.....REFUNDED
+The given user has a booking, but it could not be allocated due to a
+lack of usable workstations. The users token has been refunded, possibly
+with extra privilege.
+@item AVAILABLE.....workstation
+This indicates that the named workstation is available. It is not inuse
+and is not currently allocated.
+@item DOWN..........workstation
+The booking system has detected that the named workstation is not
+working properly.
+@item DEAD..........workstation
+The booking system has been told that the named workstation is
+out-of-service, and should not be used.
+The @code{bookterm} program displays a list of workstations and
+reservations for a lab (or labs).  The display is continually updated,
+similar to an airline departure screen.
+@end table
+
+When multiple labs are being displayed, the rows for the different labs
+are grouped together and separated, one lab from another, by blank rows.
+
+The command line usage for @code{bookterm} is:
+@example
+ lablist/bookterm [-wfn] labname ...
+@end example
+@table @code
+@item -w
+This flag causes the listing to be sorted by workstation name instead of
+by user surname.
+@item -n
+Normally bookterm will clear the display before drawing each page, to
+make sure the whole page is drawn.
+This can be bothersome when it is run in an @code{xterm}, and the
+@code{-n} flag will suppress this clearing, so that only changes will be
+drawn.
+@item -f
+Full status information for the lab will be displayed. This includes a
+row for each workstation and for each allocation. The detail of the
+different rows is described below (or will be one day).
+A particularly useful addition is that the 3 letter surname is replaced
+with a three character time-since-login.
+@item labname
+One or more labs may be given to be display.
+In line with other programs which take a list of lab names, and
+attribute can be given, and all labs which imply that attribute will be
+included.
+@end table
+
+Note: Users surnames are found by examining the @code{GECOS} field. If
+there is a comma, the the surname is the word immediately before the
+comma, otherwise it is the last word in the field.
+
+
+@node bogin, book, bookterm, Components
+@section bogin
+
+@code{bogin} is a @code{/bin/login} replacement to be used on dedicated
+booking terminals. It will normally be run by @code{getty} or
+@code{init}.
+
+Usage is
+@example
+book/bogin [ -d devicename ] [ username ]
+@end example
+
+If a @samp{devicename} is given, @code{bogin} attempts to open that
+device instead of using @code{stdin} and @code{stdout}.
+
+If an @samp{username} is given, @code{bogin} immediately prompts for a
+password, otherwise it will first prompt for a user name.
+
+For normal users, @code{bogin} will then run @file{/usr/local/bin/book}
+as that user.
+There are, however, other possibilies.
+@itemize @bullet
+@item
+If the user's shell is not in @file{/etc/shells} and the user has a
+password, then @code{bogin} will not allow the login.
+@item
+If the user's shell is not in @file{/etc/shells} and the user has no
+password, then @code{bogin} will run the users shell.
+@item
+If the user's shell is in @file{/etc/shells} but the user has no
+password, then @code{bogin} will not allow the login.
+@item
+If the user is root then @code{bogin} will run @file{/bin/shell}
+@item
+If the user is in group @samp{wheel} or in group @samp{supervisor} then
+@code{bogin} will run the users shell.
+@end itemize
+
+@node book, tkbook, bogin, Components
+@section book
+
+MORE TO BE WRITTEN
+
+The book command is used to make all bookings for the system.  It can
+be run either interactively or in batch mode via a script.
+
+
+
+@menu
+* Interactive Use::             
+* Batch Use::                   
+@end menu
+
+@node Interactive Use, Batch Use, book, book
+@subsection Interactive Use
+In order to make bookings that user needs tokens.   They can then use
+these tokens to make bookings in a particular lab at a particular time.
+It is not necessary to specify all attributes, as the program will
+prompt the user when there are ambiguities (e.g. a choice of tokens, a
+choice of labs etc.).
+
+The root user and members of group @code{supervisor} are able to invoke
+the book command as another user.  This is useful in diagnosing
+problems or misunderstandings users may have with their bookings.
+
+@node Batch Use,  , Interactive Use, book
+@subsection Batch Use
+The scripts that make class and lab bookings use the book command in
+batch mode.  In this case all the attributes need to be defined
+uniquely.  The token that is used to make the booking must define only
+one lab that is to be used.  In addition, the number of machines
+required for each slot can be specified.
+
+The book command has extensive online help.
+
+@node tkbook, messaged, book, Components
+@section tkbook
+
+@code{tkbook} is a visual interface to the @code{book} program written
+using @dfn{Tk/Tcl}.
+It allows bookings to be made and cancelled in a reasonably intuitive,
+visual way.
+It provide an online help file which (hopefully) is included as appendix
+X.
+
+@node messaged, saveauth, tkbook, Components
+@section messaged
+
+@example
+messaged/multimessaged [-l] [-L port] [-t timeout] [-g geometry] [-f font] 
+   [-D auth-dir] [-d domain]
+@end example
+
+@code{Messaged} is a daemon for displaying messages on X11 displays. It
+is used by the booking system to display warning messages which
+encourage users to log off.
+
+@code{messaged} will maintain multiple message windows on multiple
+displays concurrently, rather than requiring a separate process for each
+window.
+
+@code{messaged} accepts tcp connections over which messages are sent to
+it.
+Each message should start with a line that contains the display
+name. All subsequent lines, up to 2048 byte limit make up the message.
+Upon receiving a message, @code{messaged} will attempt to display it.
+The window used to display the message will contain an @code{OK} button
+which may be clicked to discard the window.
+
+Incoming connections will only be accepted if they come from a privileged
+port on localhost or, if the @code{-d domain} option is present, on a
+host within the domain given.
+
+@code{Messaged} can be run from some versions of @code{inetd} with
+support @code{stream/wait} services properly, or can be run as a stand
+alone daemon.
+
+The command line options have the following meanings:
+@table @code
+@item -l
+@code{messaged} will listen for tcp connection in @code{stdin} (file
+descriptor 1). This is the normal usage when started as a
+@code{stream/wait} service by @code{inetd}
+
+@item -L port
+Port may be a name given in @file{/etc/services} or a number. In either
+case, @code{messaged} will attempt to bind to and listen on that port
+for tcp connections.
+
+@item -t timeout
+If @code{messaged} has no windows to display for @code{timeout} minutes,
+it will exit. This is most useful with @code{-l}.
+
+@item -g geometry
+Specify the geometry for the displayed windows. The default is
+@code{+100+100}.
+Note that only the position can be specified this way. The size is
+calculated from the size of the message.
+
+@item -f font
+Specify the font to use for messaged. The default is
+@example
+-misc-fixed-*-*-normal-*-20-*-*-*-*-*-iso8859-*
+@end example
+
+@item -D auth-dir
+Specify the directory to find @file{Xauthority} file in. The default of
+@file{/var/X11/xdm/save_auth} works well with @code{saveauth}.
+
+@item -d domain
+Allow tcp connection from other hosts providing that they are in the
+given domain. Normally @code{messaged} only accepts connection from
+@code{localhost} (IP address 127.0.0.1).
+
+@end table
+
+If neither @code{-l} or @code{-L} are given, messaged will read a
+message from standard input and display it.
+This is useful for testing and can be used for a @code{stream/nowait}
+service under @code{inetd}, though that would waste the multi-window/
+multi-display funcitonality.
+
+@node saveauth, sendmess, messaged, Components
+@section saveauth
+
+@code{saveauth} attempts to save X11 authentication information for
+later use by @code{messaged}.
+
+It first verifyies that the authentication information available to it
+is correct by attempting to connect to the default display (which will
+be taken from the @code{DISPLAY} environment variable).
+If this succeeds, then the default @file{Xauthority} file (which will
+have been used to authenticate the connection) will be copied into a
+known place.
+
+To determine this known place, the display name is normalise, and used
+as a file name in the directory @file{/var/X11/xdm/save_auth}.
+
+To normalise the display name, the hostname part if fully qualified and
+converted to lower case, the display number is left unchanged and a
+screen number of @samp{0} is used.
+
+This program can safely be made setuid and then run from a default
+@file{Xsession} script.
+
+@node sendmess, book_login, saveauth, Components
+@section sendmess
+
+@code{sendmess} is a simple client of @code{messaged} which will ask
+message to display a message on some display.
+
+@example
+messaged/sendmess [display [host]] < message
+@end example
+
+@code{sendmess} is normally given a display name as a command line
+argument and a message on standard input. It will connect to the
+messaged on the local host and deliver the message.
+
+If @code{sendmess} is given a second argument, it will connect to the
+@code{messaged} on the given host. This is needed if the display is
+currently logged in to another host, and will only work if
+@code{messaged} on that host was started with an approriate @code{-d
+domain} flag.
+
+With no arguments, @code{sendmess} simply read from standard input and
+sends what it gets to the local @code{messaged}, so the first line of
+the standard input should be a display name.
+
+As @code{messaged} only accepts connections from privileged ports,
+@code{sendmess} can only be run by root (or be setuid, though that is
+not encouraged).
+
+@code{sendmess} plays no particular part in the booking system as
+@code{book_manager} sends eviction messages directly to the
+@code{messaged}.
+It is useful for testing, and can be used in any other circumstance
+where it is useful to display messages on particular workstations, such
+as informing a lab supervisor of a paperout condition on a printer.
+
+
+
+@node book_login, book_logout, sendmess, Components
+@section book_login
+
+@code{book_login} should be run as part of the login process so as to
+allow the booking system to be aware of logins, and to be able to control
+(restrict) logins.
+
+@example
+login/book_login [-w workstation | -d display] -u user [-p pid]
+@end example
+
+@code{book_login} will check with the booking system to see if the
+user is permitted to log in to the given workstation.
+If they are, the booking system is told that they have logged in, the
+login is recorded in @file{/var/book/logins}  and
+@code{book_login} exits with @dfn{success} status.
+If not, the @code{book_login} will output an explanatory message and
+exit with a @dfn{failure} status.
+
+The options are interpreted as follows:
+@table @code
+@item -w workstation
+This indicates which workstation the user is trying to log in to. Either
+it or the @code{-d} argument must be given.
+
+@item -d display
+This indicates that the user is trying to log in to the given X11
+display.
+@code{book_login} trys to convert this name into a workstation name
+known to the booking system. The corresponding workstation name must be
+a valid display name, with the host part being a prefix of the full
+qualified domain name, the display number part being a simple number,
+and the screen part being absent.
+
+@item -u user
+This indicates which user is loging on.
+
+@item -p pid
+This gives the process id of a process which can be signalled in order
+to terminate the login session. This will normally be the @code{xdm}
+which is controlling the login. The process id is stored in
+@file{/var/book/xdm-pid/@var{workstationname}}.
+
+If this option is omitted, then the booking system will not be able to
+evict anyone (unless a process id is stored in the file by some other
+means). 
+@end table
+
+@code{book_login} is normally run from the @code{Startup}  under
+@code{xdm}.
+A possible usage would be:
+@example
+if book_login -d $DISPLAY -u $USER -d $PPID > /tmp/book_msg.$DISPLAY
+then # Ok to login
+  rm /tmp/book_msg.$DISPLAY
+else : NOT ok to login
+  sendmess $DISPLAY < /tmp/book_msg.$DISPLAY
+  rm /tmp/book_msg.$DISPLAY
+  sleep 15
+  exit 1
+fi
+@end example
+This example required the Korn Shell (@code{ksh}) or some other shell
+which understands @code{$PPID} to mean the parent process id.
+
+@node book_logout, book_database and book_maint, book_login, Components
+@section book_logout
+
+@code{book_logout} is the partner to @code{book_login} in that it
+informs the booking system that a user has logged out, and records the
+logout in @file{/var/book/logins}.
+@example
+login/book_logout -u user [-w workstation | -d display] [-K]
+@end example
+
+The @code{-u}, @code{-w} and @code{-d} options have the same meaning as
+for @code{book_login}.
+If @code{-K} is given, then @code{book_logout} attempts to kill all
+processes owned by the user.
+This is quite appropriate on traditional workstations, but it's use
+should be considered carefully on multi-user hosts.
+
+@code{book_logout} is normally run from the @code{xdm} @code{Reset}
+script. A possible usage would be:
+@example
+book_logout -u $USER -d $DISPLAY -K
+@end example
+
+@node book_database and book_maint, book_status and book_manager, book_logout, Components
+@section book_database and book_maint
+
+@code{book_database} and @code{book_maint} are the pair of programs that
+maintain the replicated booking system database.
+Their purpose and functionality is detailed elsewhere in
+@xref{BOOK_DATABASE} and @xref{BOOK_MAINT}.
+
+@example
+database/book_database [-I] [-n] [-M book_maint_path]
+database/book_maint
+@end example
+@code{book_maint} takes no arguments. 
+@code{book_database} takes the following arguments.
+@table @code
+@item -I
+This tells @code{book_database} to create a new database. This will only
+succeed if none of the database files currently exist.
+The newly created database contains only one datum - that fact that @dfn{this}
+host is a registered replica.
+
+@item -n
+This tells @code{book_database} not to run @code{book_maint}.
+If this is done, then it is probably wise to run @code{book_maint}
+separately.
+
+@item -M book_maint_path
+This tells @code{book_database} where to find the @code{book_maint}
+program.
+By default it tries @file{/usr/local/etc/book_maint}.
+@end table
+
+@code{book_database} and @code{book_maint} do not fork, and so should
+normally be run the the background.
+
+
+
+@node book_status and book_manager,  , book_database and book_maint, Components
+@section book_status and book_manager
+
+@code{book_status} and @code{book_manager} are the two programs which
+monitor the status of workstations and labs, and which perform the
+generally running of the system.
+They are described in purpose and functionality in @xref{BOOK_STATUS}
+and @xref{BOOK_MANAGER}.
+
+@example
+manager/book_status [-M book_manager_path] close database replica names
+manager/book_manager
+@end example
+
+@code{book_manager} takes no arguments.
+@code{book_status} takes an optional @code{-M} option which tells it
+where to find the @code{book_manager} program (default is
+@file{/usr/local/etc/book_manager}).
+
+Subsequent arguments are taken as names of database replicas which can
+be considered to be @dfn{close}. If these replicas are active and
+up-to-date, then they will be chosen in preference to other replicas.
+
+@code{book_status} should be run at boot time. It will in turn run
+@code{book_manager}. 
+
+@node Bookings Database, Laboratory Status and Management, Components, TOP
+@chapter Bookings Database
+The booking system data base contains all the configuration information
+describing the various objects in the systems, and also all bookings
+that have been made.
+
+The database is replicated over multiple hosts with each replica able
+to accept updates. This minimised the effect of network or server disruptions.
+
+
+
+@menu
+* Databases::                   
+* Changes::                     
+* BOOK_DATABASE::               
+* BOOK_MAINT::                  
+* Creating new replicas -- BOOK_COPYDB::  
+* Removing replicas::           
+@end menu
+
+@node Databases, Changes, Bookings Database, Bookings Database
+@section Databases
+There are several databases (gdbm files) most of which contain multiple
+tables. The separation into different files is an artifact of part of
+the implementation and not very significant.
+
+Each database file provides lookup of some content given a key.
+Keys are chosen to be unique across all tables in a given database file.
+The content is often stored in machine independent External Data Representation
+(@var{xdr}) format.
+
+The four database files are @code{REPLICA} which records information
+about the active database replicas, @code{CONFIG} which contains fairly
+static configuration information, @code{BOOKINGS} which contains highly
+dynamic booking information, and @code{ALLOCATIONS} which may one day
+record all the allocations that have been made.
+@table @code
+@item REPLICAS
+This contains all known active database replicas.
+There may be database replicas running that are not in this list,
+however these will not accept new changes, they will only copy changes from
+other replica. It contains one table.
+@table @samp
+@item server-name
+An integer representing
+number of changes from that server which have been included into this
+database.
+@end table
+
+@item BOOKINGS
+Contains all the bookings that have been made, including those that have
+been cancelled.
+The bookings are indexed by both when and where the booking is for, and
+who it is for. There is also some summary information.
+
+The tables within the database are:
+@table @samp
+@item ByTime
+ This table contains all the bookings. The key is a triple of
+ day-of-year, period-of-day, and lab-number.
+ The content is a list of bookings, each of which contains:
+ @table @samp
+ @item who_for
+  The user id or class id that workstations will be reserved for.
+ @item who_by
+  The user id who made the booking, and hence to whom the booking is
+  charged and to whom it may be refunded. This is normally the same as
+  @samp{who_for}.
+ @item ws_count
+  The number of workstation to be reserved, normally 1.
+ @item token
+  The id number of the token used for the booking.
+ @item timestamp
+  The time (in second since epoch) that the booking was made.
+ @item status
+  The status of the booking which may be one of PENDING, ALLOCATED,
+  REVOKED, REFUNDED, CANCELLED, FREEBIE
+ @item charged
+  A machine attribute set indicating which class of machines the booking
+  was tentatively allocated to.
+ @item request
+  An attribute set which is a subset of the token attributes, and
+  indicates attributes that are particularly asked for.
+  This is essentially unused at present.
+ @item allocations
+  The list of workstations that booking was allocated to.
+ @item claim_status
+  This records information about how and when the booking was claimed.
+  It has four booleans and an integer:
+  @table @code
+  @item DID_LOGIN
+        This is set to @code{True} when the user logs in during the
+        allocation time, in the lab of the booking.
+  @item DID_DEFAULT
+        This is set to @code{True} if, when the grace period expired,
+        the user was not logged on to their allocated workstation.
+  @item WAS_TENTATIVE
+        This is set to @code{True} if the allocation was made as a
+        @code{Tentative} allocation. This is information for human
+        viewers only and is not used by TABS.
+  @item LOGIN_ON_ALLOC
+        This is set when @code{DID_LOGIN} is set, if the user logged in
+        on the workstation to which they were allocated.
+  @item when_on
+        This is set to the time in the period when @code{DID_LOGIN} was
+        set. This will range from -10 minutes to 20 minutes.
+        If set to -10, it means that the user was logged in when
+        allocation were made.
+  @end table        
+        
+ @end table
+
+@item utilisation
+ This table records how many of which sort of machine has (tentatively)
+ been allocated so far.
+ The key is a triple of day-of-year, period-of-day, and machine
+ description, and the content is a number of workstations of that type
+ allocated at that time.
+@item ByUser
+ This table provide an index to the bookings by user id. It also
+ provides a summary of tokens used.
+ The key is simply a user id.
+ The content contains two lists of booking keys, one for pending
+ bookings, one for all other bookings.
+ The booking key is the same as the key for ByTime, combined with the
+ timestamp of the booking.
+ The summary of tokens used is simply a list of token numbers and usage
+ counts.
+ A token number with a high bit set indicates a privileged refund, and
+ the count will be negative.
+@end table
+
+@item CONFIG
+The CONFIG database contains all the (fairly) static configuration
+information about such things as hosts, workstations, tokens, attributes.
+The tables included in the database are:
+@table @samp
+
+@item Numbers
+Maps Names and types to id numbers. The types are: HostGroup,
+Host, Workstation, Token, Attribute, and Class.
+
+Note that Labs are also Attributes and so do not have separate names. 
+
+@item Names
+This is the inverse of Numbers and maps types and id numbers to names.
+
+@item Workstations
+Maps workstation id to:
+@table @samp
+@item Attribute set
+ which describes the attributes of the workstation
+@item time_disabled
+ Which stores the time that a workstation was marked out-of-service, or 0 if it
+ is in service.
+@item why_disabled
+ A string indicating why and by whom the workstation was disabled.
+@item host
+ The hostid of the host which controls this workstation.
+ If a workstation can select between any of the hosts in the HostGroup,
+ then this field is set to -1.
+@end table
+
+@item Workstation_count
+ Maps an attribute set to the number of (in service) workstations
+ with that attribute set.
+
+@item HostGroups
+Maps a HostGroup id to a list of hosts in that HostGroup, and a list of
+labs served by hosts in the HostGroup.
+
+@item Hosts
+Maps a host id to a list of workstations served by that host, and the id
+of the HostGroup containing the host.
+
+@item Labs
+Maps a lab id to a list of workstation in that lab, and the id of the
+hostgroup which serves that lab.
+
+@item Tokens
+Maps a token id to the token description.
+
+@item Allotments
+Maps a user or class id to a list of token allotments for that
+entity. This list is a list of pairs of token id and count, where the
+count is non-zero.
+User id 0 always maps to a list containing an allotment of 1 of each
+defined token.
+
+The total allocation for a user is the sum of the individual allocations
+to that user and to all of the classes (netgroups) which the user is in.
+
+@item General
+This contains some ad-hoc string to string mappings.
+These include:
+@table @samp
+@item book_level
+Can be @samp{off} to disable evictions and reservations, or @samp{on}
+to fully enable the booking system.
+@item book_advance
+A number which indicate how far in advance the second
+part of a token comes into effect
+@item grace
+A number which is the number of minutes into a period for
+which a workstation will be held reserved for the allocated user.
+@item min_class_uid
+The minimum uid for classes, and hence one more than the maximum uid of
+users. Default value is 4000,0000 in hexadecimal.
+
+@end table
+
+@item Attribute_types
+This contains a single record recording the types of various attributes.
+There are 3 attribute set which contain:
+@itemize @bullet
+@item All defined attributes
+@item All required attributes
+@item All Lab attributes
+@end itemize
+and 5 special attribute numbers:
+@table @samp
+@item open
+which must be set for times when a lab is open. i.e. when allocations
+should be performed.
+@item closed
+which must be set for times when a lab is closed.
+@item available
+which must be set on any workstation which is not out of service
+@item reusable
+which should be set in any token which is always refunded
+@item firmalloc
+which should be set in a token which is use to make firm (supervised)
+class bookings.
+@end table
+Attributes are described in more detail in 'Configuration' --- 'Bitstrings'.
+
+@item Attribute_definitions
+This contains an ordered list of attribute implications.
+Each entry in the table contains a number of implications and also the
+total number of implications all together.
+Each entry is keyed by the index of the first implication that the entry
+contains. e.g if key @emph{0} mapped to the first 4 implications, then key 4
+would map to the next few implications.
+Attributes definitions
+are described in more detail in 'Configuration' --- 'Bitstrings'.
+
+An implication contains:
+@itemize @bullet
+@item a start and end day of the year
+@item a start and end day of the week
+@item a start and end period of the day
+@item a set of requisite attributes
+@item a set of anti-requisite attributes
+@item a single attribute to set if the period and  current attributes
+match the requirements.
+@end itemize
+
+@end table
+@item ALLOCATIONS
+Not implemented at the moment.  It is meant to record all the
+allocations that have been made in the labs.
+@end table
+
+@node Changes, BOOK_DATABASE, Databases, Bookings Database
+@section Changes
+As mentioned before changes can be given to any of the replicas in the
+system.  The changes that have occured at a database manager are stored
+in the @file{db_log} file (/var/book/db_log).  This file contains a header for each
+replica, containing the following fields
+@table @code
+@item lastchangenum
+The number of the last change received from that replica.
+This should match the number in the @code{REPLICAS} database.
+@item lowestchangenum
+The lowest number change still stored in the database.
+@end table
+
+
+
+@menu
+* Types of Changes::            
+@end menu
+
+@node Types of Changes,  , Changes, Changes
+@subsection Types of Changes
+The changes are designed as much as possible to be commutable.  This
+means that it should not really matter which order changes from other
+database managers are applied, they should succeed.  There are a number
+of different types of changes,
+@table @samp
+@item CHANGE_WS
+Change the information about a workstation.
+Giving a workstation an attribute list that does not contain any lab
+attribute will delete the workstation.
+@item CHANGE_HOST
+Change the hostgroup which a host is a member of.
+Setting the hostgroup to -1 removes the host.
+Note that this does not remove the mapping from the NAMES database.
+@item CHANGE_LAB
+Change the hostgroup that a lab is in.
+There must be no workstations in the lab for this to work.
+Setting the hostgroup to -1 deletes the lab.
+@item ADD_SERVER
+Adds a new server to the list of replicas.
+@item DEL_SERVER
+Removes a server from the list of replicas.
+This change is normally sent to the server being deleted.
+This makes sure all changes from that server are propagated properly.
+@item ADD_BOOKING
+Add a booking to the database.
+This can also be used to change the number of workstations reserved by
+the booking.
+@item CHANGE_BOOKING
+Change the state of the booking.  This is done
+when cancelling or allocating bookings.
+When allocation bookings, the @code{CHANGE_BOOKING} request can also
+contain a list of workstations to which the booking was allocated, and
+an indication of whether the allocation was tentative, and whether the
+user was already logged on.
+@item CLAIM_BOOKING
+Set the @code{claim_status} information for a booking.
+This change can set any of the boolean values in @code{claim_status} to
+@code{True}, or can set then @code{when_on} value.
+It is used when a user logs on, and when @code{book_manager} notices
+that the grace period has expired.
+@item REMOVE_BOOKING
+Removed booking information.  This is to save
+space in the booking database.  It should be done very carefully.  The
+operation checks that there are no outstanding bookings, so ensure all
+allocations have been performed before doing it.
+@item REMOVE_USER
+Remove token usage information for a user. This is done at the end of a
+session (or semester or term) to provide a clean slate for the new
+session.
+REMOVE_USER can only remove information for users who have no bookings
+(past or pending) recorded, so REMOVE_BOOKING should be used first.
+@item ADD_TOKEN
+Add a new token definition.
+If the lengths of the description are zero, the token is removed.
+Root is automatically alloted one of every token.
+@item ADD_CLASS
+Change the number of tokens allocated to a class or user.
+The number sent is added to the number already allocated, and may be
+negative.
+@item IMPLICATIONS
+Set the implications information in the CONFIG database.
+@item ATTR_TYPE
+Set the attr_type information.
+@item CONFIGDATA
+Add some generic configuration information
+@item SET_NAMENUM
+Add a mapping between a name and an id number.
+If the name is the empty string, any mapping is removed.
+@end table
+
+Changes are stored with a key of the change number plus the originating
+replica.  The value is just the whole @code{book_changeent} structure.
+This allows requests to be made to fetch changes and give them to other
+database managers.
+
+Old changes can be purged from the log_file once they are at least one
+day old and have been copied to all registered replicas.
+
+
+@node BOOK_DATABASE, BOOK_MAINT, Changes, Bookings Database
+@section BOOK_DATABASE
+The @file{book_database} program is responsible for providing network
+access to the booking system database.
+It will accept changes as described above and impose them on the
+database, and will return any information requested from the database.
+
+It will also, on request, provide a complete copy of the database, so
+that a new replica may be started. While it is doing this, it will not
+accept any changes as this would corrupt the database copy being sent.
+
+@file{book_database} will fork the @file{book_maint} program which
+copies changes from other replicas to this replica, thus keeping all the
+database copies in synchrony.
+
+
+Each database replica may be in one of three state with respect to
+changes which it will accept.
+@table @samp
+@item full update mode
+All changes will be accepted.
+Most clients of the database will only accept information from a
+database in this state.
+@item copy only mode
+Only changes being copied from some other
+replica will be accepted. New changes are rejected.
+This allows a replica which has been out of action for a while to get
+up-to-date with other replicas before entering full service.
+
+The @code{book_database} program will automatically go into this mode if
+it has not been explicitly told that it is in synchrony with other
+replicas for a period of 10 minutes.
+
+@item no update mode
+No updates will be accepted.
+A replica enters this mode while it is transferring a copy of the
+database, or when it notices an internal error.
+@end table
+
+@node BOOK_MAINT, Creating new replicas -- BOOK_COPYDB, BOOK_DATABASE, Bookings Database
+@section BOOK_MAINT
+The @code{book_maint} program is responsible for keeping all the
+database replicas in synchrony with each other by copying changes from
+one to another.
+For each replica, there is one @code{book_maint} process which copies
+changes from other replicas to this replica.
+
+If @code{book_maint} cannot contact some other replica, it will try to
+copy changes that originated at that replica from another replica.
+
+@code{book_maint} checks the other replicas for new changes every
+minute. If it determines that all changes have been copied to the local
+database, it tells the local database that is it in synchrony so that it
+will stay in @code{full update} mode.
+
+@node Creating new replicas -- BOOK_COPYDB, Removing replicas, BOOK_MAINT, Bookings Database
+@section Creating new replicas -- BOOK_COPYDB
+
+The first database replica is created by running @file{book_database}
+with a @code{-I} flag (which requests it to initialise the databases).
+All other new replicas should be created with the @file{book_copydb} program.
+It is given the name of a server to copy from.
+
+Creating a fresh new database should be done with great care as having
+two independent database servers in the one network would be very
+problematic.
+
+If @code{book_copydb} is successful it will leave the database files in
+the book directory (normally @file{/var/book}) as files with names
+starting @file{db_}.
+
+With this done, it is only necessary to start @file{book_database}
+running and the replica will be available and kept in synchrony.
+
+Before the replica will be full functional it must be added to the
+replica list with the @code{dbadmin -a} command.
+This will cause it is start accepting changes and cause the other
+replicas to collect changes from it.
+
+@node Removing replicas,  , Creating new replicas -- BOOK_COPYDB, Bookings Database
+@section Removing replicas
+
+The preferred way to remove a replica is to first delete it from the
+replica list with @code{dbadmin -d}. Once all other replicas have
+removed it from their replica lists, the @file{book_database} process
+can be killed and the files removed.
+
+If this is not possible, because the files or host supporting that
+replica are unavailable (host down, files corrupted), then the @code{-r replica}
+flag should be given with the @code{dbadmin -d} command to tell some
+other replica that the unavailable replica has been removed.
+The replica told should be the one that has received the most changes
+from the replica to be deleted, though hopefully all replicas will be
+equally up-to-date.
+
+Once all replicas have removed knowledge of the replica to be deleted from their
+lists, it is safe to remove the database files,
+and start a new database server to replace the deleted replica.
+
+
+@node Laboratory Status and Management, The full scoop on tokens and attributes, Bookings Database, TOP
+@chapter Laboratory Status and Management
+
+Each host within the booking system runs a pair of programs called
+@file{book_status} and @file{book_manager} which together monitor the
+status of the workstations and hosts, and perform the proactive tasks of
+the booking system such as workstation allocation and eviction.
+
+
+
+
+@menu
+* Table of Status Information::  
+* BOOK_STATUS::                 
+* BOOK_MANAGER::                
+@end menu
+
+@node Table of Status Information, BOOK_STATUS, Laboratory Status and Management, Laboratory Status and Management
+@section Table of Status Information
+
+All the status information that the booking system needs about the
+workstations and hosts in a given hostgroup is stored in a table.
+This table is passed from host to host with each host updating any
+entries which it has new information about.
+Thus every host in a host group knows the status of everything in the
+hostgroup.
+
+The table contains a number of records which hold different types of
+information. Each record has a timestamp which is updated when
+information in the record is modified. When a new table arrives at a
+particular host, it is merged with the current table by taking the most
+recent of each record present.
+
+A record contains:
+@itemize @bullet
+@item type
+@item identifier
+@item timestamp
+@item length of data
+@item data@code{[]}
+@end itemize
+
+Each record has a type and an identifier.
+The five different record types are:
+@table @code
+@item TABLERECORD
+This record refers to the whole table. The identifier is the identifier
+for the hostgroup. The data contains the id of the host which created the
+table.
+When two tables are merged, the table with the most recent
+@code{TABLERECORD} is considered primary and the set of records in
+that table determines the set of records in the resulting merged table.
+
+@item HOSTRECORD
+Contains information about the status of the host identified by the
+identifier.
+There is one of these for each host in the hostgroup.
+The data contains the status of the host, whether up or down.
+
+@item ALLOCRECORD
+This record records information about the last time an allocation was
+performed.
+There is one of these per lab.
+The time stamp is the time the allocation was for (rather than the time
+the allocation was made).
+
+@item WSRESRECORD
+There is one of these for each workstation in the host group.
+It records the result of the most recent allocation (See allocations
+below XREF!!). This includes the status of the allocation and the user
+or class that was allocated. It also records whether the allocation was
+@code{firm} or @code{normal}.
+
+@item WSSTATUSRECORD
+This record records the state of a workstation. It contains the status
+of the workstation (up or down), who is logged on, when they logged on,
+which host they are logged on to, and the last time that they were
+allocated to the workstation.
+@end table
+
+@node BOOK_STATUS, BOOK_MANAGER, Table of Status Information, Laboratory Status and Management
+@section BOOK_STATUS
+The @file{book_status} program is an rpc server which simply stores
+status information about the hostgroup and the booking system.
+If provides this information on request and will change it on request.
+
+The information it stores is the status table described above and a list
+of known database replicas.
+
+
+
+@menu
+* Status Table::                
+* Replica List::                
+@end menu
+
+@node Status Table, Replica List, BOOK_STATUS, BOOK_STATUS
+@subsection Status Table
+The status table can be changed either by sending a whole new table
+which is merged with the current table, or by sending change requests
+which change either the user who is logged on, or the user who is
+allocated to a particular workstation.
+
+As UDP is used for all rpc calls, and because the status table can get
+rather large, it is normally transferred in chunks of 20 records.
+
+Whenever @file{book_status} receives a copy of the table it signals it's
+partner @file{book_manager} process (with @code{SIGALRM}) to tell it
+that a new table is available.
+
+The protocol for sending a copy of the table to a @file{book_status}
+process tags each chunk sent with a time stamp (seconds since Unix
+epoch) so that the receiver can be certain whether or not chunks that
+arrive together are actually related.
+If two tables arrive at the same time, chunks interleaved, then the one
+with the later time stamp is accepted.  If the time stamps are the same,
+then one one which was seen first is accepted.
+If a chunk arrives with a time stamp which is more than 3 minutes
+different to the current time, then the chunk is rejected.  This allows
+for ignoring hosts with incorrect clocks, and guards against the
+possibility of a table with a timestamp in the far futre partially
+arriving and thus blocking all other arrivals.
+
+In normal operation, a table will be collected from a @file{book_status}
+as often as one is sent to it, or slightly more often as tables are
+collected for other purposes such as lab status monitoring. If tables
+stop being collected, then something is wrong, typcially the
+@file{book_maint} process is failing.  If this happens, in particuilar
+if three consecutive tables are received without any being taken, then
+@file{book_status} will stop accepting tables, so that the upstream host
+will treat it as being down, and will exclude it from the system.
+
+@node Replica List,  , Status Table, BOOK_STATUS
+@subsection Replica List
+@code{book_status} maintains a replica list which it keeps sorted in
+order of preference. Replicas that are up and accepting all updates are
+preferred over replicas that are not accepting updates or are down.
+
+Each @code{book_status} program can be told that some replicas are
+@emph{close by}. These replicas will be sorted early if they appear to be up.
+
+The replica list is initialised from a file and changes are stored in
+that file, so any changes to the replica configuration will be
+persistently remembered by every host.
+
+@node BOOK_MANAGER,  , BOOK_STATUS, Laboratory Status and Management
+@section BOOK_MANAGER
+The @code{book_manager} process is responsible for passing the status
+table around the hostgroup, and for performing the proactive tasks of
+the booking system.
+
+
+
+@menu
+* Table Passing::               
+* Eviction::                    
+@end menu
+
+@node Table Passing, Eviction, BOOK_MANAGER, BOOK_MANAGER
+@subsection Table Passing
+When a new table arrives at a @code{book_status} process, it signals the
+@code{book_manager}.
+The @code{book_manager} will then normally collect the table from the
+@code{book_status} and send it on to the next host in the hostgroup that
+is up.
+If that host appears to be down, it is marked as down in the table and
+the new table is sent back to the @code{book_status} that is was
+collected from. This causes another signal to be sent and the
+@code{book_manager} will try to send the table to the next host.
+
+The first host in the table which is up is considered to be the manager
+for the hostgroup. It behaves slightly differently.
+When a table arrives at the hostgroup manager (it will probably have
+been sent by the last host in the hostgroup), the
+@code{book_manager} does not pass it on immediately, by waits until 30
+seconds have passed since it last sent a table before passing it.
+This means that the table goes around the lab at most once every 30
+seconds.
+
+Also, if any host is down and has not had its HOSTRECORD updated for
+more than 5 minutes, the hostgroup manager will mark the oldest such
+record as up, thus attempting to re-include it in the table passing
+(incase it has in fact come up in the mean time).
+
+Another task of the @code{book_manager} is to pole all workstation owned
+by the host or (only for the hostgroup manager) not owned by any host to
+make sure the recorded status is correct.
+
+The @code{book_manager} notes when the grace period for any allocation
+expires, and check to see if the allocated user has logged on. If they
+ahve not, a @code{default} is recorded for that bookings.
+
+@node Allocation, Eviction, Table Passing, BOOK_MANAGER
+
+@subsection Allocation
+Allocations are made every 30 minutes i.e. for each booking period.
+Allocations are made 10 mins before the period starts so that there is
+time to evict the current user (if required).  The hostgroup
+@code{master} is responsible for making the allocations.
+
+
+An entry is placed in @file{/var/book/book_errors} showing the time it
+made the allocations.   There is also a file in
+@file{/var/book/book.dump} into which is written a copy of the
+lab status table, formatted that same way the @code{smadmin -t} formats
+it, immediately after allocations were made. This can be 
+useful in tracing through exactly what happened when a complaint is received.
+
+The allocation process is described in the Concepts chapter.
+
+@node Eviction,  , Table Passing, BOOK_MANAGER
+@subsection Eviction
+After allocations have been made, the book_manager will being the
+process of evicting anyone who is logged on to a workstation to which
+someone else has been allocated.
+
+This process starts by displaying messages on the workstation warning
+the user that they will have to log off soon (and telling them how long
+they have left).
+If the user has not logged of by the time that the new allocation is to
+take effect, book_manager forces the eviction by sending a SIGTERM
+signal to the @emph{xdm} process which is controlling the login session. This
+causes @emph{xdm} to reset the server which in-turn breaks any connection that
+the user had with the Xserver.
+
+@emph{xdm} with, as part of it's cleanup, run the @emph{Xreset} script. This
+script should call the @emph{book_logout} program which informs the booking
+system that the user has logged out, and may kill any remaining
+processes belonging to the user (depending on how it is configured).
+
+@emph{book_manager} determines the process id of the @emph{xdm} by looking in the
+file @file{/var/book/xdm-pid/@var{workstation-name}} which should have
+been setup properly by the Xstartup script.
+
+@emph{book_manager} uses the services of @emph{messaged} to display warning
+messages. For this to work, the file
+@file{/var/X11/xdm/save_auth/@var{workstation-name}} must have been
+setup to contain appropriate Xauthority information.
+
+To help track the eviction process, for use when responding to
+complaints about unfair treatment, book_manager keeps a log of all its
+eviction actions in @file{/var/book/evict_log}.
+This file contains the time, host, workstation, userid-being-evicted,
+time until eviction and time since last warning. A record in written
+whenever a messages is sent, an xdm is signalled, or when the eviction
+process is halted, such as when the user logs off.
+
+
+
+
+
+@node The full scoop on tokens and attributes, Setting it up, Laboratory Status and Management, TOP
+@chapter The full scoop on tokens and attributes
+
+@menu
+* Attributes::                  
+* Implications::                
+* Attributes and subspaces::    
+* Reusable versus non-reusable::  
+* Firm Allocations for class bookings::  
+* Advance and Late bookings::   
+* Privileged Tokens::           
+* Putting it together::         
+@end menu
+
+@node Attributes, Implications, The full scoop on tokens and attributes, The full scoop on tokens and attributes
+@section Attributes
+
+Attributes are used for classifying workstations and time periods, are
+used to control when labs are open or closed, and when assigned to
+tokens, are used to controlling bookings and access to workstations and
+time periods.
+
+Attributes are defined using the @file{attr} program. This program reads
+specifications which define attributes and define their associated
+interrelationships using implications.
+
+Attributes are defined, one per line, by the following specification:
+@display
+@var{attribute-number} @samp{:} @var{attribute-name} [ @var{flags} ]
+@end display
+where flags are optional and may be one or more of the following:
+
+@table @code
+@item optional
+This flag indicates that the attribute is not a required attribute.  The
+concept of required attributes are defined and discussed under
+@ref{Attributes and subspaces}.
+
+@item lab
+This flag indicates that the attribute is actually a lab name. Workstations
+get registered as being in a particular lab by having this attribute set.
+
+@item closed
+This flag marks the distinguished attribute which is used to mark a lab as
+being closed. If that attribute is assigned to a given lab at a given
+time, then that lab is deemed to be closed at that time.
+
+The attribute flagged with @code{closed} should always be required (not
+flagged @code{optional}) and should not be set in any token.
+
+The attribute flagged with @code{closed} is usually also named
+@samp{closed} for clarity, but it need not be.
+
+@item open
+This flag is similar to @code{closed} flag except that the attribute that it
+flags is used to mark a lab/time as open, so that allocations will be made
+as necessary.
+
+The attribute flagged with @code{open} should be included in all tokens,
+or marked @code{optional} (in which case some other mechanism would be
+needed to restrict bookings to times when the lab is open).
+
+The attribute flagged with @code{open} is usually also named
+@samp{open} for clarity, but it need not be.
+
+@item available
+This flags the attribute which is normally assigned to
+all working workstations. If a workstation becomes sufficiently broken
+that it is not expected to be in service for some time, it should have
+the @code{available} attribute removed (with the @code{ws}
+program). This will, among other things, reduce the number of bookings
+which the booking system will accept in that lab.
+
+The attribute flagged with @code{available} is usually also named
+@samp{available} for clarity, but it need not be.
+
+@item reusable
+This flags the attribute which is used to mark tokens as
+being reusable tokens.
+
+@item firmalloc
+The flags the attribute which is used to mark a token as requiring a firm
+allocation. This means that the allocation will be held for the full
+period of the booking, not just the first few minutes.
+This in normally only used for supervised class bookings.
+
+@end table
+
+@node Implications, Attributes and subspaces, Attributes, The full scoop on tokens and attributes
+@section Implications
+Implications are used to define more general attributes from specific
+attributes and/or times. The intention here is to combine specific
+attributes which are usually assigned to particular workstations (and
+which usually include at least one @code{lab} attribute), with specific
+times, days, or dates to produce attributes that are useful for
+defining tokens and/or opening and closing times for labs.
+The implications involving opening and closing times will usually
+(and fairly obviously) involve the attributes that were flagged @code{open} and
+@code{closed} (mentioned in the previous section).
+
+Implications are defined to the booking system using the @file{attr}
+program. This program reads specifications which define attributes and
+which define their associated interrelationships using implications.
+
+Implications are defined, one per line, according to the following
+specifications:
+@example
+@var{implication} = @var{premise} @samp{->} @var{attribute-name}
+
+@var{premise} = @{ @var{dates} | @var{days-of-week} | @var{times-of-day} @} @{ @var{included-attribute} | @var{excluded-attribute} @}
+
+@var{dates} = @var{day-of-month} @var{month} [ @samp{-} @var{day-of-month} @var{month} ]
+        eg: @code{25jul} or @code{01feb-30apr}
+
+@var{day-of-month} = @var{digit} @var{digit}
+
+@var{month} = @samp{jan} @dots{} @samp{dec}
+
+@var{days-of-week} = @var{day-of-week} [ @samp{-} @var{day-of-week} ]
+        Note: The week is from Sunday to Saturday, so @code{sa-su} is
+        not valid.
+
+@var{day-of-week} = @samp{su} @dots{} @samp{sa}
+
+@var{times-of-day} = @var{time} [ @samp{-} @var{time} ]
+        Note: A single time (eg: @code{6:30}) indicates the half hour period
+        starting with that time and so is equivalent to the range from that
+        time to the half hour later (@code{6:30-7:00}).
+
+@var{time} = @var{digit} [ @var{digit} ] @samp{:} @var{digit} @var{digit}
+        Note: this is a 24 hour time with a precision of 30 minutes
+        eg: @code{6:30} or @code{17:00}
+
+@var{included-attribute} = @var{attribute-name}
+        eg: @code{weekend}
+
+@var{excluded-attribute} = @samp{!} @var{attribute-name}
+        eg: @code{!publicholiday}
+
+@end example
+
+To understand implications, it helps to understand how they are
+processed.
+
+The processing starts with some particular date and time, and a set of
+specific attributes. The attributes will be that attributes of some
+workstation, or a single @code{lab} attribute.
+
+Then each implication is tested in turn. If the date and time match all
+the time ranges given, and all the @emph{included} attributes are
+currently in the attribute set, and none of the @emph{excluded} are in
+the attribute set, then the implied attribute is added to the attribute
+set.
+
+The resultant attribute set is then used, either to compare with a
+tokens attribute set, or to test for the @code{open} or @code{closed}
+attribute.
+
+NEED SOME EXAMPLES
+
+
+@node Attributes and subspaces, Reusable versus non-reusable, Implications, The full scoop on tokens and attributes
+@section Attributes and subspaces
+
+In order to understand fully how tokens and attributes work, it is
+helpful to consider the space of all possible workstation time periods.
+
+This is a two dimensional space, one dimension being all the
+workstations known to the booking system. The other being all the half
+hour periods, 48 per day, 365 (or 366) days per year.
+
+Each of the points in this space corresponds to a particular workstation
+during a particular half hour period.
+Each point has associated with it it's own, possibly unique, set of
+attributes.
+Some of these attributes come from the workstation. Others come from the
+implications which will probably include some termporal information.
+
+Each attribute will appear in the attribute set of some subset of the
+points in the space. Each such subset of points (or subspace) can be thought
+of as being defined by the corresponding attribute. Thus each attribute
+defines as subspace of the space of workstation time periods.
+
+For example, the @code{lab} attribute defines the space of all
+workstation time periods where the workstation is in the given lab.
+An attribute called @code{weekend} defined by
+@example
+su -> weekend
+sa -> weekend
+@end example
+would define a space of workstation time periods where the time period
+is on a Saturday or a Sunday.
+
+There are a couple of attributes with predefined meanings such that the
+space that they define has a specific meaning to the booking system.
+The @code{closed} attribute defines a space of workstation time periods
+where nobody may log in.
+The @code{open} attribute defines a space where allocations are made,
+and therefore where bookings can be made for.
+The @code{available} attribute defines a space of all workstations which
+are deemed to be usable. Any workstations outside this space are not
+bookable, and will reject all login attempts.
+
+Some attributes define spaces which are not, in themselves,
+interesting. They are only intermediate spaces used in defining more
+interesting spaces. The attributes @code{normalopen} and
+@code{normalclosed} in @ref{Setting it up} are examples of this. These
+attributes should be marked optional so that they are not involved in
+the evaluation of tokens.
+
+Token have associated with them, a set of attributes (actually two sets
+--- an advanced booking set and a late booking set, but this distinction
+is not relevant to the current discussion).  The attribute set defines
+that subspace of the workstation time period space which can be booked
+using the token. Unfortunately however, the subspace defined by a token
+is not simply a union or intersection of the subspaces defined by the
+attributes in the token.
+
+Rather, a token's subspace is the complement of the union of the
+subspaces defined by the non-optional attributes which are @emph{not} in the
+token's attribute set. That is, a token's subspace is the set of all points
+which may or may not have attributes out of the token's attribute set,
+but which do not have any other interesting, non-optional attributes.
+
+Another way of looking at it, is to look at the situation from the
+workstation/period point of view (rather than the token's perspective):
+An individual workstation/period with a given set of non-optional
+attributes @emph{can only} be booked by a token that also contains at
+least these non-optional attributes. Note that the token may additionaly
+contain other optional and non-optional attributes, but these are
+irrelevant with respect to this individual workstation/period in
+question.
+
+To see how this is effective, it is best to consider the normal (or at
+least, the recommended) way of defining attributes.
+
+@menu
+* Choosing attributes::         
+@end menu
+
+@node Choosing attributes,  , Attributes and subspaces, Attributes and subspaces
+@subsection Choosing attributes
+A good way to choose attributes, or at least the
+@strong{interesting/non-optional} attributes is to choose some groups of
+attributes such that the subspaces defined by the attributes in the
+group are non-intersecting, and span the entire space. To put this
+another way, the attributes in a group serve to classify every
+workstation time period into one of a small number of classes.
+
+Some example of such attribute groups are:
+@itemize @bullet
+@item
+The lab attributes. Each workstation is in precisely one lab.
+@item
+@code{weekend}, @code{weekday}, @code{publicholiday}, could uniquely
+classify every day, if we chose @code{weekday} to only include normal
+week days that are not @code{publicholiday}s.
+@item
+@code{session}, @code{break}, @code{outofsession} could distinguish the
+different periods of the year.
+@item
+@code{wk1}, @code{wk2}, ..., @code{wk15}, @code{midsession},
+@code{endofsession}, @code{outofsession} could provide a finer grained
+classification of the year.
+@item
+@code{daytime} and @code{evening} could be used if there was a need to
+give some priority to evening students.
+@item
+@code{open}, @code{closed}, and @code{free} could classify the labs/times
+which are open for bookings, closed baring access, or free for
+unrestricted use.
+@end itemize
+
+Given a collection of attribute groups chosen in this manner to be
+spanning and non-intersecting, the attributes for a token can be chosen
+to be just the desired attributes out of each group.
+The subspace defined by the token would be the intersection of the
+groupwise unions of the subspaces defined by the attributes.
+
+@node Reusable versus non-reusable, Firm Allocations for class bookings, Attributes and subspaces, The full scoop on tokens and attributes
+@section Reusable versus non-reusable
+
+The previous section showed how the range of times and workstations
+for which a token is valid can be controlled with a great deal of
+precision.  There is, however, a lot more to understanding tokens, as
+this and subsequent sections will detail.
+
+When creating a token, it can be marked as @emph{reusable}. This is
+done simply by setting a particular distinguished attribute (which is
+really overloading the meaning of what an attribute is, but was done
+as an implimentation convenience).
+
+If a token is marked as @emph{reusable} then when a booking is
+allocated that was made with that token, the token is returned to the
+user for further use.
+
+If a token is not marked as @emph{reusable} then after allocation, the
+token is normally not returned to the user for further use, unless the
+lab was underutilised at the time of allocation. This exception was
+introduced to encourage users to make bookings for the less popular
+labs and/or times.
+
+@menu
+* Reusable tokens as shares::   
+* Non-reusable tokens --- for getting the job done.::  
+@end menu
+
+@node Reusable tokens as shares, Non-reusable tokens --- for getting the job done., Reusable versus non-reusable, Reusable versus non-reusable
+@subsection Reusable tokens as shares
+
+Setting a limit on the number of reusable tokens available to a
+particular user limits the total number of bookings that can be
+outstanding, or pending, at any one time.  The macroscopic effect of
+this is not at first obvious as the actual effect depend a lot on how
+other people book.
+
+The effect turns out to be that each token works like a @strong{share},
+and that the avalable workstation time periods are shared out among the
+people who want them in proportion to the number to tokens or
+@dfn{shares} that they each have.
+
+To see how this works, consider a senario in which, desparate for
+workstation time, each user books for the first available time slot as
+soon as any token is returned to them for reuse.
+In this senario, the immediate future will be solidly booked for some
+fixed amount of time which will depend on the total number of tokens
+allocated, and the total number of workstation periods available.
+Lets suppose that this totals three days.
+
+Then, a person with 10 tokens allocated to them will have booked 10
+periods, or 5 hours during that 3 day period.  A different person with
+only 6 tokens will have booked only 3 hours worth of time. Providing
+that these two users book equally aggressively, this proportion of
+bookable time will remain the same, whether other users continue to
+book aggressively or not.
+
+In reality, people will not book as agressively as pictured
+here. Certain times of day will be more popular, and certain weeks of
+the session will be more popular.  The more popular times will be more
+solidly booked out, and will only be able to be booked further in
+advance.  The further in advance a user books, the more time they are
+without their token, and so the fewer booking can be made with that
+token.  This will automatically reward people for booking less popular
+times, as they will get their booking sooner, get their token back
+sooner, and be able to book again sooner.
+
+
+Reusable tokens have other benefits. They are quite easy to manage as
+there is not the problem of users running out of tokens (not
+permanently anyway), and because the actual booking rights conferred
+by such token are dynamically determined, based on load, the number
+allocated to each different class of student need not precisely
+reflect their need.
+
+The one disadvantage of reusable tokens is that they do not allow the
+user to plan a long way in advance how the will book their time. It is
+unusual to book more than a week in advance.  While this is a
+theoretical disadvantage, it may not have significant practical
+applications, as students are not generally known for a tentancy to
+plan ahead.
+
+At the authors site, reusable tokens are now the only tokens
+used. Unfortunately, we found that allocating large numbers of
+reusable tokens had a suprising and undesirable effect: many users
+would use their tokens to make large numbers of consecutive bookings
+most of which were defaulted upon. It appeared as if these users were
+using the booking system to reserve their option to turn up and use a
+terminal effectively whenever they felt like doing so. They obviously
+did not care about the penalty of being declared a defaulter, and did
+not mind having to claim their bookings from a booking terminal. They
+had an endless supply of tokens and would use them to buy the freedom
+to use a terminal whenever they liked, regardles of how many others it
+may have inconvenienced. Note that although it is true that this
+behaviour could only continue as long as everyone did not behave the
+same way, most users did not behave like this either because they
+hadn't thought of it, or because it went against the (implied)
+original principles of the booking system.
+
+@node Non-reusable tokens --- for getting the job done.,  , Reusable tokens as shares, Reusable versus non-reusable
+@subsection Non-reusable tokens --- for getting the job done.
+
+Non-reusable tokens were, in an earlier version, the only sort of
+tokens. While they have fallen out of favour at the author's site,
+they are described here for completeness and historical interest, and
+because someone may find a genuine use for them.
+
+The idea behind a non-reusable token is that booking rights are given
+for a purpose --- to get some specific piece of work done, typically an
+assignment.
+
+The sponsor of the work (the lecturer who set the assignment) would
+determine how much time the assignment required (or should require),
+and would determine when the work should be done (between when the
+assignment was set and when it was due). Also, and specfic needs of
+the assignment, such as a particular lab, would be determined.
+
+Then a token would be created for that assignment, and the appropriate
+number would be allocated to everyone doing that assignment.
+
+The advantage of this approach is that tokens have easily
+understandable meanings. People know from the start of session how
+much time they can book, when, and what for. Also, if students have
+different needs for different assignments, then this is easily
+accomodated into the token allocation scheme.
+
+The main disadvantage of this approach is that it is difficult to
+accurately determine how much time to allocate to each assignment.
+Meaningful feedback on how much time was actually used on each
+assignment is difficult to get (and only useful after the fact), and
+so the time allocated per assignment tends to be based on educated
+guesses.
+Other disadvantages are:
+@itemize @bullet
+@item
+It is far more administratively time consuming to create, allocate,
+and make adjustments to the numbers and types of tokens that are
+needed by this approach.
+@item
+Students can carelessly waste all their tokens and then be out on a
+limb. It is harder to waste a small number of reusable tokens.
+@item
+The tokens allocated will not necessarily reflect the amount of
+workstation time available, and there can be quite severe congestion
+at peak assignment periods with whole weeks being booked solid.
+@item
+The fact that non-reusable tokens are nonetheless returned when the
+lab and time booked are underutilised, makes the meaning of a token
+less precise. Nevertheless, this refunding is a valuable means of
+encouraging people to use less popular labs and/or times.
+@end itemize
+
+Experience has shown that both these schemes for token allocation can
+be made to work, though allocating a few reusable tokens that are
+valid for the full session is the easiest to administer.  There is no
+reason that the two schemes cannot be combined if, for instance, one
+particular assignment has special needs. In this case the normal
+general purpose reusable tokens can be suplimented with a few special
+purpose tokens of limited duration, possibly non-reusable.
+
+@node Firm Allocations for class bookings, Advance and Late bookings, Reusable versus non-reusable, The full scoop on tokens and attributes
+@section Firm Allocations for class bookings
+
+Experience with the booking system has shown a need for two different
+styles of class bookings.
+
+One, which we call a @dfn{lab booking} is a time when students can
+work on assignments, when there is a tutor available to provide
+assitance. This is particularly used for first year students.  In this
+situation there is no requirement to attend.  For @dfn{lab bookings},
+a booking much like a normal student booking is made. If noone claims
+such a booking in the first 7 minutes, then the workstation becomes
+available to anyone who wants it.
+
+The other style of class booking is referred to as a @dfn{tut booking}
+as the time is run more like a tutorial. In this situation there is a
+tutor in attendance who will work with the class as a whole leading
+them through examples which each student can work on at their own
+workstation.  This situation is quite different from @dfn{lab
+bookings} as the whole room should be reserved for the class, and
+people not in the class should not be allowed in, even if there are
+free workstations, as this could disrupt the lesson.
+
+To accomodate @dfn{tut bookings} an extra attribute can be included in
+the token used to make the booking. This attribute is the
+@code{firmalloc} attribute. It indicates that the allocation should be
+maintained for the entire extent of the period booked, rather than
+just the first 7 minutes.
+
+@node Advance and Late bookings, Privileged Tokens, Firm Allocations for class bookings, The full scoop on tokens and attributes
+@section Advance and Late bookings
+
+The distinction between advance and late bookings was designed to meet
+a potential need that has not yet arisen in practice, so the
+distinction has not yet been used.
+
+Lets say that a small number of workstations existed that had special
+or more desirable attributes.  For instance, when the booking system
+was being designed, colour workstations were much less common, and
+consequently more desirable. Although this is no longer true, there
+may still be some extra functionality or attribute of workstations
+(perhaps sound, or availablility of DVD drives) which might still
+create a subclass of special purpose workstations.
+
+In any case, assuming that such workstations exist, and that there is
+a small group of users who have a particular need to use this subclass
+of workstations, then there are two obvious approaches that can be
+made to cater to these booking needs:
+@enumerate
+@item
+Only the users who need the workstations should be able to book. This
+would mean the people with need would always have the access they
+need.  It would also mean that people without a demonstated need would
+never be able to book one of these @strong{better} workstations, even
+if they were under booked, which does not seem fair.
+@item
+All users could be allowed to book the special workstations.
+This would mean that the presumably large number of users without a
+specific need, but with a desire to use these workstations, could swamp
+the @strong{better} workstations, thus excluding the users with a real
+need.
+@end enumerate
+
+Neither of these solutions seems adequate. What is needed is a
+situation where users with a specific need can book whenever they want
+to, while users without a specific need should be able to book, but
+only if the needy users haven't.
+
+To achieve this we make a distinction between advance and late
+bookings.  Advance bookings are those that are made at least 4 days
+before the time of the bookings. Late bookings are those that are made
+during the last 4 days before the time of the bookings.
+
+If users without a need are only allowed to book the special
+workstations when making a late bookings, while users with need can
+make such bookings at any time, then the desired soloution can be
+realised.
+
+It is to enable this, that tokens have two sets of attributes
+associated with them. One is used to determine booking privileges when
+making an early booking. The other is used when making a late booking.
+Normally the first set will be a subset of the second, but this is not
+enforced.
+
+Quite possibly, 4 days is too long a period for this to work well,
+and a number more like two days should be used. Hopefully this number
+will be made configurable soon.
+
+@node Privileged Tokens, Putting it together, Advance and Late bookings, The full scoop on tokens and attributes
+@section Privileged Tokens
+
+An extra complication in understanding tokens is that some tokens can be
+@dfn{Privileged}.
+This privilege is not actually associated with the token, but with the
+allocation of the token to a particular user.
+
+With normal (unprivileged) tokens, the last 10% of a lab can only be
+booked as a late booking. This was done to decrease the discontent if
+some workstations get put out of service.  With a privileged token,
+the last 10% of a lab can also be booked as an advance booking.
+
+Privileged tokens (or more accurately, privileged allocation of tokens)
+result from a booking not being allocated due to a lack of working
+workstations.  It is not possible to create (allocate) privileged
+tokens any other way.
+
+The rational is that if a users booking cannot be allocated due to a
+lack of working workstations, that user should get some
+recompense. That recompense is the ability to make a replacement
+booking in the near future, where @dfn{near} is determined by the
+definition of @code{late} bookings.
+
+The privilege attached to tokens is not very visible. There is no way
+to see if a user has any privileged tokens, or to change the privilege
+level. The only effect they have is to effect what bookings can be made
+when.
+
+@node Putting it together,  , Privileged Tokens, The full scoop on tokens and attributes
+@section Putting it together
+
+As mentioned in @ref{Attributes and subspaces}, it is best to choose
+attributes in groups such that attributes in each group are
+nonintersecting and spanning. Given this, creating a token is simply a
+matter of choosing which attributes from each group are
+required. Some further tips are:
+@itemize @bullet
+@item
+Include at least one attribute from each group.
+@item
+Always include the @code{open} attribute and the @code{available}
+attribute.
+@item
+Remember to include the @code{reusable} attribute if reusable tokens are
+required.
+@item
+Normally there will be a lab attribute for each lab that the token is
+usable in.
+@end itemize
+
+Tokens for use in class booking will be created somewhat differently.
+@itemize @bullet
+@item
+They should only allow booking in one lab. This means that book will not
+prompt for a lab, so bookings can be made by a shell script.
+@item
+They will usually have no other restrictions
+@item
+They do not need to be reusable
+@item
+They may have @code{firmalloc} set of a @code{tut booking} is required.
+@end itemize
+
+@node Setting it up, UNSW Local Lore, The full scoop on tokens and attributes, TOP
+@chapter Setting it up
+
+There are a number of dependencies between different parts of the
+booking system such that certain parts cannot work until other parts are
+working. This can make the process of getting it up and running appear a
+bit daunting.
+To alleviate this, a number of recipes are presented here which provide
+step by step instructions on how to get things going.
+
+The different steps are given in an order that would be a reasonable
+order for doing things. There is some flexibility and so where there are
+dependencies, they are given explicitly.
+
+Paths names of programs are given as they appear in the distribution
+which is almost certainly not where they will be installed.
+
+
+
+@menu
+* Installing files.::           
+* Creating the database::       
+* Creating a database replica --- OPTIONAL::  
+* Configuration::               
+* Starting a status server::    
+* Adding hostgroups and hosts::  
+* Creating attributes and implications::  
+* Adding workstations::         
+* Creating tokens::             
+* Creating classes::            
+* Allocating tokens to classes::  
+* Installing book_login and book_logout::  
+* Enabling the system::         
+@end menu
+
+@node Installing files., Creating the database, Setting it up, Setting it up
+@section Installing files.
+Every host on which any part of the booking system will be run must have
+a directory @file{/var/book}.
+This will contain, at the very least a file called @file{database_loc}
+which will contain a list of know database replicas.
+The things which may be in this directory are:
+@table @file
+
+@item database_loc
+File containing list of known database replicas, one host name per line.
+This file is maintained and used by
+@file{book_status}/@file{book_manager} but must be initialised to
+contain at least one name.
+
+@item db_@var{*}
+Some @code{gdbm} files which contain a replica of the
+database. Naturally these will only appear on hosts which run a database
+server.
+
+@item help
+The help file for @code{book}. It is used to provide online help. It
+should be installed on every host on which @code{book} might be run.
+
+@item periods
+The periods file for @code{book}. This lists some definitions of
+different periods that people might use when specifying bookings. It
+must exist for @code{book} to run.
+
+@item motd
+The contents of this file, if it is present, is displayed whenever
+@code{book} is run.
+
+@item whoison
+This directory will contain one file for each workstation that is
+currently logged on to the host. The files contains the uid, in ASCII,
+of the user who is logged on. It should exist on all hosts which support
+workstations.  
+
+@item logins
+This file lists all logins and logouts that have happened on the host.
+There is one line per login or logout, with a date, a host name, a
+workstation nane, and a user name. The line start with a plus for logins
+and a minus for logouts.
+
+@item xdm-pid
+This directory contains one file for each workstation logged on to the
+host. The file contains an ASCII process id of a process to kill in
+order to force a logout on that workstation. The process id is usually
+that of the controlling @code{xdm}.
+
+@item book_errors
+A log of ad hoc messages from booking system daemons. It is fairly
+uninteresting without the source code.
+
+@item book.dump
+When ever an allocation is performed, a copy of the resulting status
+table is appended to this file. It should be rotated and discarded at
+least daily.
+
+@item evict_log
+A file containing a log of eviction messages and actions.
+
+@end table
+
+In order to use @code{messaged} a new service needs to be added to
+@file{/etc/services} and @file{/etc/inetd.conf}.
+The service should be called @code{message/tcp}. If @file{inetd} is used to run
+messaged, it must be run as a @code{stream wait} services.
+Many versions of @file{inetd} do not work properly for which type of
+service and so @file{messaged} will need to be run with the @code{-L}
+flag so that it binds and listens itself.
+
+Optionally, two new programs can be recorded in @file{/etc/rpc} they are
+@example
+  book_database 540033106
+  book_status   540033105
+@end example
+There are unofficial, unregistered program numbers.
+
+The various programs should be installed where
+needed. @file{book_status} and @file{book_manager} must be on every
+host. Other programs probably will be too.
+
+The only program which need to be setuid to root are @code{book} and
+@code{saveauth}.
+
+
+@itemize @bullet
+@item @file{book_status} should be run at boot time on all hosts
+@item @file{book_database} should be run at boot time on all replica hosts.
+@item @file{bookterm} should be run on all allocation display terminals.
+@item @file{bogin} should be run (by getty) on booking terminals.n
+@end itemize
+
+A final step, which should NOT be done until all else is ready, is
+modifying the xdm @file{startup}, @file{session} and @file{reset}
+scripts to call @file{book_login}, @file{save_auth} and
+@file{book_logout} respectively.
+
+The installation is essentially a pre-requisite for all other steps.
+
+@node Creating the database, Creating a database replica --- OPTIONAL, Installing files., Setting it up
+@section Creating the database
+
+The database is created by running @file{book_database} with the
+@code{-I} (initialise) flag. e.g.
+@example
+   /usr/local/etc/book_database -I -M /usr/local/etc/book_maint &
+@end example
+This will create a database with only one piece of information: that fact
+that the host it is on is has a replica of the database. It will also
+start the database server running.
+
+This database server can then be queried with
+@example
+   /usr/local/etc/dbadmin -r localhost -sA
+@end example
+which ask the replica on @file{localhost} for a status listing off all
+replicas.
+The one replica should be listed with a change count of zero. Its state
+will at first be @code{EXT} but will shortly change to @code{UP}.
+
+@node Creating a database replica --- OPTIONAL, Configuration, Creating the database, Setting it up
+@section Creating a database replica --- OPTIONAL
+
+This can only be done after the database has been created.
+
+The first step is to copy the current database onto a new machine. This
+is done with @file{book_copydb} e.g.
+@example
+   database/book_copydb name-of-source-replica
+@end example
+The start a database server.
+@example
+   database/book_database -M database/book_maint &
+@end example
+Due to shell interpretations, the following is needed
+@example       
+   database/book_database -M /usr/local/etc/book_maint &
+@end example
+Check that this looks OK:
+@example
+   tools/dbadmin -r localhost -s localhost
+   tools/dbadmin -r localhost -sA
+@end example
+The first command with show that status of the new replica, the second
+will show the status of all registered replicas. They should be much the
+same, though the new replica will be in state @code{EXT}.
+
+Finally, register the replica
+@example
+   tools/dbadmin -r name-of-source-replica -a `hostname`
+@end example
+After a minute or so the new replica should discover that it is
+registered and
+@example
+   tools/dbadmin -r localhost -sA
+@end example
+should list all replicas, including the new one.
+
+@node Configuration, Starting a status server, Creating a database replica --- OPTIONAL, Setting it up
+@section Configuration
+
+Configuration of TABS involves describing the various parts of the
+system.
+These include:
+@table @samp
+@item Attributes
+The various attributes which be used to distinguish certain times and
+workstations need to be defined. This will include at least one
+attribute for each lab and the two attributes @emph{open} and @emph{closed}.
+It may also include other concepts such as @emph{weekend} @emph{evening}
+@emph{publicholiday} @emph{outofsession} etc.
+These are defined using the @code{attr} program.
+
+@item Implications
+These indicate how to determine more general attributes from specific
+attributes, and from particular times, days, or dates.
+These are also set using the @code{attr} program.
+
+@item hostgroup, hosts, labs, and workstation.
+These are the basic hardware that TABS need to know about. There is one
+program which is linked to the names @emph{hg}, @emph{host}, @emph{lb}, @emph{ws} for
+creating, examining, and modifying these different objects.
+
+@item tokens
+Each token identifies a set of workstation times which can be
+booked. Tokens need to be created for each set of workstation times that
+some group of users should be able to book.
+The @emph{token} command is used for this.
+
+@item users
+There is no need to explicitly tell the booking system about specific
+users as usernames and user ids are discovered through the normal
+password file access mechanisms.
+It is, however, necessary to inform the booking system of any classes
+(netgroups) which it will need to know about. The names of these classes
+should be chosen so as to be different from any user name.
+The command @code{user} is used to introduce these classes to the
+booking system.
+
+@item allotments
+Allotments record how many of which token has been alloted to each class
+or user. The term @emph{allocation} is used in various parts of this
+document for the handing out of tokens as it a more commonly used
+word. However we use @emph{allocation} to talk about allocating
+workstations to bookings, and so @emph{allotment} was chosen to describe
+allocating tokens to classes.
+
+Normally, individual users are not alloted person tokens, though this is
+certainly possible.
+Classes are the normal unit for allotting tokens to, and this is done
+with the @file{user} command, which can register classes with the
+booking system, can query the current allotment for classes, and can
+change the allotment.
+
+@item classes/netgroups
+
+As described elsewhere, classes memberships are recorded using
+@file{netgroups} and are not explicitly recorded by the booking system.
+Thus setting up of class memberships, while necessary for proper running
+of the system, is not discussed here.
+
+@end table
+
+@node Starting a status server, Adding hostgroups and hosts, Configuration, Setting it up
+@section Starting a status server
+
+The status server, @file{book_status} together with @file{book_manager},
+needs to be run on every host on which booking system commands are to be
+run. This is because it is used to find a database replica.
+
+Before @file{book_status} is run, the name of at least one replica site
+should be written to the @file{/var/book/database_loc} file. Given this
+information, the @file{book_manager} can find the others.
+
+For example, on a replica server:
+@example
+  hostname > /var/book/database_loc
+  manager/book_status -M manager/book_manager &
+@end example
+
+The list of known replicas can be checked with the @file{smadmin}
+command:
+@example
+  smadmin -d localhost 
+@end example
+
+This step is a prerequisite to any further database operations, as
+@file{dbadmin} is the only command that can be explicitly given replica
+to talk to --- all other database management commands find a database
+replica to talk to.
+
+@node Adding hostgroups and hosts, Creating attributes and implications, Starting a status server, Setting it up
+@section Adding hostgroups and hosts
+
+Host groups and hosts can be added any time that the database and
+status servers are up and running, though they are usually just entered
+once when the system is set up.
+
+A host group is a group of hosts that together manager a group of labs,
+as discussed else where.
+A host group is created with the @file{bkhg} command.
+
+@example
+  tools/bkhg -c spectrum
+@end example
+
+Hosts can similarly be created and should be placed in a hostgroup.
+
+@example
+  tools/bkhost -c -g spectrum red blue green
+@end example
+
+Registers three hosts called @code{red}, @code{blue}, and @code{green} in
+a group called @code{spectrum}.
+
+Once this has been done, status/manager servers can should be started on
+each of the hosts, and they will start passing status information
+around and thereby monitoring the status of the host group.
+
+If the servers are already running, they can be asked to re-assess there
+membership in a hostgroup by requesting one of the status servers to
+restart its manager. The manager will create an appropriate status table
+which will be passed around among all hosts in the hostgroup.
+The status server can be told to restart its manager with the
+@file{smadmin} command:
+@example
+    tools/smadmin -r blue
+@end example
+
+The status table that is being passed around can be examined using the
+@file{smadmin} command. At this state it will only have @code{TABLE} and
+@code{HOST} records.
+@example
+   tools/snadmin -t blue
+
+   00: TABLE spectrum     27/03;14:53:58 (1): on blue
+   01: HOST  red          29/03;13:42:27 (1): UP
+   02: HOST  green        29/03;13:42:27 (1): UP
+   03: HOST  blue         29/03;13:42:27 (1): UP
+
+@end example
+
+@node Creating attributes and implications, Adding workstations, Adding hostgroups and hosts, Setting it up
+@section Creating attributes and implications
+
+Creation of attributes and implications can be done as soon as at least
+one database replica is active, and a status manager is running (to
+help locate the replica).
+
+The sections on attributes and implications in the configuration chapter
+should be read and understood before proceeding.
+
+The minimum that should be created at this stage are the @code{open},
+@code{closed}, and @code{available} attributes, an attribute for each
+lab, and implications to determine when the labs are open.
+For following is an example input file for the @code{attr} command which
+provides this minimum.
+
+@example
+  1: open open
+  2: closed closed
+  3: available available
+ 10: yellow lab
+ 11: magenta lab
+ 12: cyan lab
+
+ 100: normalopen optional
+ 101: normalclosed optional
+ sa 9:30-16:30 -> normalopen
+ sa 16:30-17:30 -> normalclosed
+ su 9:30-16:30 -> normalopen
+ su 9:30-16:30 -> normalclosed
+ mo-fr 8:30-19:30 -> normalopen
+ mo-fr 19:30-20:30 -> normalclosed
+
+ yellow normalopen -> open
+ yellow normalclosed -> closed
+ magenta normalopen -> open
+ magenta normalclosed -> closed
+ cyan mo-fr normalopen -> open
+ cyan mo-fr normalclosed -> closed
+@end example
+
+In this example, the @code{cyan} lab is not open on weekends.
+Also, the labs are only @code{closed} for an hour after closing
+time. This is because physical security is sufficient to keep people off
+at other times, and it does not hinder @emph{special} people who might
+have keys.
+
+The labs envisioned in this example are meant to be unlocked at 8am on
+week days and 9am on weekends. During the first half the lab is neither
+open (so no-one will have booked and then complain because the lab was
+opened late) or closed (so people who arrive can actually log on).
+
+Once the file can been created, it can be entered into the database with
+the @code{bkattr} command
+@example
+  tools/bkattr attr-file
+@end example
+
+The current attributes and implications can be extracted from the
+database by running the attr command with no arguments.
+
+
+@node Adding workstations, Creating tokens, Creating attributes and implications, Setting it up
+@section Adding workstations
+
+Once the @code{hostgroups}, @code{hosts}, and @code{attributes} (and
+thereby labs) have been defined, the workstations can be defined.
+These must at least be given a name and a lab. If they are tied to a
+particular host, they should be given a host as well.
+
+Each workstation's name should be a valid displayname for the X server
+running on the workstation.
+
+The following example registers two hosts in each of the previously
+created labs. For larger collections of workstations it can be useful to
+use a small shell script.
+
+@example
+  tools/bkws -c -l yellow -h red     yellow00:0
+  tools/bkws -c -l yellow -h green   yellow01:0
+  tools/bkws -c -l cyan -h blue      cyan00:0
+  tools/bkws -c -l cyan -h green     cyan01:0
+  tools/bkws -c -l magenta -h red    magenta00:0
+  tools/bkws -c -l magenta -h blue   magenta01:0
+@end example
+
+Once the workstations have been recorded, the status manager should be
+told to create a new table which will now contain this extra
+information:
+@example
+  tools/smadmin -r blue
+@end example
+
+@node Creating tokens, Creating classes, Adding workstations, Setting it up
+@section Creating tokens
+
+Class tokens should have only one lab
+
+@node Creating classes, Allocating tokens to classes, Creating tokens, Setting it up
+@section Creating classes
+
+NOTES: include teachers in class for class bookings.
+Possibly have tut classes within subject, possibly not
+
+@node Allocating tokens to classes, Installing book_login and book_logout, Creating classes, Setting it up
+@section Allocating tokens to classes
+
+@node Installing book_login and book_logout, Enabling the system, Allocating tokens to classes, Setting it up
+@section Installing book_login and book_logout
+
+@node Enabling the system,  , Installing book_login and book_logout, Setting it up
+@section Enabling the system
+
+@example
+ tools/conf book_level on
+@end example
+
+This chapter should describe various scripts that are used to
+maintain the booking system...
+
+@node UNSW Local lore, Local setup, Setting it up, TOP
+@chapter UNSW Local lore
+This chapter describes the various formal and informal procedures
+developed at the School of Computer Science and Engineering (CSE) at
+the University of New South Wales to administer the booking system.
+This chapter is also includes some examples of common problems and
+their corresponding solutions.
+
+@menu
+* Local setup::
+* Keeping it going::
+* Skyterms and Booking terminals::
+* Common problems
+@end menu
+
+@node Local setup, File locations, Setting it up, UNSW Local lore
+@section Local setup
+The previous sections of the booking manual describe how to use the
+booking system in a general sense.  What will be described here are
+some of the little quirks of the current (Dec 2000) setup and
+procedures used at the School of CSE.
+
+One thing to note is that most of the current
+workstations in student labs are also hosts (for themselves).  Thus
+the host for the fife01 workstation is fife01.  This is different from
+the days (pre-1998) when some labs (notably the Elec Eng Undercroft
+labs) were full of X-terminals controlled by only three hosts (bribe,
+bonus and boodle). Thus the distinction between @emph{Labs} and
+@emph{Hostgroups} may not seem particularly useful with our current
+configuration of machines in labs.
+
+Many of the booking services can be accessed and controlled using
+@code{metac}, so this is a useful command to become familiar with. In
+fact in some cases, @code{metac} becomes the failsafe command when all
+others have failed.
+
+@node File locations, Initialisation Procedure, Local Setup, UNSW Local lore
+@subsection File locations
+The main booking system files are kept under the SS directory:
+@example
+       /home/ss/book/@emph{current_session}
+@end example
+The term @emph{current_session} is substituted for the corresponding
+session, eg 97s2.  Each session directory will usually have the
+following files:
+
+@itemize @bullet
+@item
+@file{bookings} - lists all the class bookings.  Keep this file
+updated as it is the easiest way to find out what labs have class
+bookings.
+@item
+@file{attr} - sets up the attributes for the labs, and the booking
+periods defined for days of the week, the session, and the current
+year, etc.
+@item
+@file{tokens} - describes the token attributes.
+@item
+@file{allotments} - describes the token allotments that each subject
+has.
+@item
+@file{config} - record of commands run through the @code{bkconf}
+command.
+@end itemize
+
+@node Initialisation Procedure, Keeping it going, File locations, UNSW Local lore
+@subsection Initialisation Procedure
+Until fairly recently, there were no systems in place to clean out old
+bookings, nor was there any way of removing obsolete objects from the
+booking database.  As the booking system only knows about the current
+year (booking periods are only identified relative to the start to the
+current year), it was necessary for the database to be cleaned out and
+reset sometime at the start of each year when there were no active
+bookings being made or allocated. Without this periodic reset, the
+previous year's bookings (and settings) would carry over into the
+current year.
+
+Unfortunately there is precious little time available at the beginning
+of the year when the system is not active, especially with the advent
+of summer session, so systems have now been put into place which mean
+that these yearly system resets are no longer necessary.
+
+Nevertheless, it is still useful to know how to reset the bookings
+system:
+
+Many of the following steps involve using scripts in the
+@file{/home/ss/book/bin/} directory, or files in the
+@file{/home/ss/book/@emph{current_session}} directory.  These scripts
+and files make it much easier to make changes than doing so by
+hand. In addition if there are changes to be made during session, you
+simply update the file(s), and make the changes to the booking system
+via the appropriate script(s).
+
+@enumerate
+@item
+  Disable book and book_db on all database replicas:
+  @enumerate a
+  @item use dbadmin -sA to get the list of replicas.
+  @item metac -D book -K book replica
+  @item metac -D book_db -K book_db replica
+  @end enumerate
+@item
+Remove all the old database stuff on all old replicas:
+@example
+       rm /var/book/db_*
+@end example
+@item
+On one of the database servers, create the master book database server:
+  @enumerate a
+  @item
+  Reinitialise the database:
+  @example
+       /usr/local/etc/book_database -I -M /usr/local/etc/book_maint &
+  @end example
+  @item
+  kill the hand started book_database:
+  @example
+       dbadmin -x localhost
+  @end example
+  @item
+  Reenable and restart book and book_db:
+  @example
+       metac -E book localhost
+       metac -E book_db localhost
+  @end example
+  @end enumerate
+@item
+Create new database replicas, one on each replica machine:
+  @enumerate a
+  @item
+  copy the database over:
+  @example
+       /usr/local/etc/book_copydb source_machine
+  @end example
+  @item
+  start the database up:
+  @example
+       metac -E book_db localhost
+  @end example
+  @item
+  register the replica with the master (and others):
+  @example
+       dbadmin -r master_machine -a `hostname`
+  @end example
+  @item
+  check the replica has been registered (after waiting a while):
+  @example
+       dbadmin -r localhost -sA
+  @end example
+  @end enumerate
+@item
+Initialise the booking database:
+  @enumerate a
+  @item
+  Create a directory for the current session's config files:
+  @example
+       cd /home/ss/book/
+       mkdir yyyyss
+  @end example
+  @item
+  Copy the config files from the previous session to the current,
+  and update the current session:
+  @example
+       cd current
+       cp attr tokens bookings allotments allot_list config ../yyyyss
+       cd ..
+       rm current
+       ln -s yyyyss current
+  @end example
+  @item
+  Edit the config files and @file{/home/ss/book/lab.conf} as necessary.
+  @end enumerate
+@item
+Set up (or update) the booking system and database from the various files
+that are usually kept in @file{/home/ss/book/current/}:
+
+  @enumerate a
+  @item
+  Define the booking attributes:
+  @example
+       bkattr attributes
+  @end example
+  @item
+  Define the labs etc. (uses @file{/home/ss/book/lab.conf}):
+  @example
+       mk_hosts
+  @end example
+  @item
+  Create or redefine the tokens:
+  @example
+       mktokens < tokens
+  @end example
+  @item
+  Allocate tokens to classes:
+  @example
+       /home/ss/book/bin/allot < allotments
+  @end example
+  @item
+  Make any class bookings:
+  @example
+       ./bookings
+  @end example
+  @end enumerate
+@end enumerate
+
+@node Keeping it going, Administration of Course Class bookings, Initialisation Procedure, UNSW Local lore
+@section  Keeping it going
+
+@subsection Flushing the database
+Every week the script:
+@example
+        /home/ss/book/bin/flushdb
+@end example
+is run to flush out old bookings that are more than one month old.
+The script, which uses the @code{booking} command,
+also needs to change any old PENDING bookings into CANCELLED bookings
+first, or they will not be removed.
+
+Logs are kept of the bookings that have been deleted in the files:
+@file{/home/ss/book/log/flush.start-end.gz}.
+
+This script is run on one of the replicated booking database servers
+(2004: maestro), and called by one of the nightly maintenance scripts
+in @file{/usr/local/maint/nightly/}.
+
+@node Administration of Course Class bookings, Skyterms and Booking terminals, Keeping it going, UNSW Local lore
+@subsection  Administration of Course Class bookings
+Most CSE courses require labs of workstations to be booked for their
+students for tutorial and laboratory work.  To enable this, tokens are
+allocated to every course class which are used to make lab bookings
+which can only be taken up by students enrolled in that
+class. Although each course class (or their administrator) could make
+their own regular suite of session bookings for their students, it is
+far better that these many and various course class bookings be
+coordinated through a central point to avoid conflicts, and to balance
+the loads and demands on limited laboratory resources.
+
+Currently (since 2000) all course administrators register their
+booking requirements as early as possible with a course coordinator
+whose job it is to determine the relative merits of potentially
+competing requests for limited (lab) resources. The coordinator is
+usually an academic who is empowered to refuse or change bookings that
+are either unreasonable, impractical, or submitted with too little
+warning. This academic then passes the final booking requests onto the
+@emph{Computing Services Group} (usually via @emph{ss}), and a system
+administrator makes the required bookings on behalf of the course
+classes. This chain of command has helped avoid the problems that the
+System Administrators once had dealing with poorly planned lab
+allocations from overworked subject administrators.
+
+Currently (since 2002), the course coordinator updates the file:
+
+        @file{/home/give/public_html/Timetables/labs/allYYSS}
+
+and after being notified of a change to this file, the system
+administrator (who needs root access to be able to make the bookings
+as the course classes), runs the script:
+
+         @file{/home/ss/book/bin/mkbookings}
+
+This script compares the existing bookings in the booking database,
+against the planned bookings, and
+@enumerate
+@item makes any planned bookings that don't exist yet;
+@item undoes any existing bookings that are no longer planned;
+@item logs all bookings and unbookings in the files:
+@file{/home/ss/book/current/[un]book.date}.
+@end enumerate
+
+There are some occasions when bookings are made by hand (using the
+@code{book} command) for course classes. This especially happens when
+the course class needs to make a once-off booking for a (prac) exam
+set at the terminals. In these cases it is important that
+@code{mkbookings} does not undo any of the once-off bookings.
+The command:
+@example
+       mkbookings -d
+@end example
+will create the files @file{/home/ss/book/current/book.raw} and
+@file{/home/ss/book/current/unbook.raw} which are the differences
+between the existing and planned bookings. These files can be
+inspected and/or edited, and then merged with the list of other
+bookings to be ignored with the command:
+@example
+       mkbookings -i
+@end example
+The command:
+@example
+       mkbookings -h
+@end example
+gives more details.
+
+
+@node Skyterms and Booking terminals, Setup, Local setup, UNSW Local lore
+@section Skyterms and Booking terminals
+One of the important features of the booking system is the operations of the booking terms and skyterms.  For those who don't know (you should, since you're playing with the booking system) the booking terms allow people to log in for a short time, and make a booking, etc.  The skyterms display the bookings that have been made, and are updated every booking period (ie every half hour). One final note is that the program that runs the skyterms is called @code{bookterm} and the program that runs the book terms is call @code{bogin}.
+
+@node Setup
+@subsection Setup
+The usual setup for either type of terminal display is for a dumb
+terminal to be connected to a machine via a serial cable.  That
+machine is then set up to run either the book or sky term.
+Alternatively the terminal can be connected directly to the serial
+connection system (as the terminals in the third floor foyer are).
+This is connected to the patch pannel in 343d.  Then the booking and
+skyterms are connected to the annex system (funnel0).  The following
+steps are needed to get either type of terminal working from a lab
+machine serial port connection (ie the terminal is connected by serial
+cable to some machine, such as @code{fife00}).
+@itemize @bullet
+@item
+Connect the dumb terminal to the machine.  At current the machine serial port is a 9 pin male, and the terminals (VT510-emulating a vt100) use a 25 pine male or female plug.  
+@item
+Look at the file @file{/home/conform/products/unsw/book.v.2.4/config}. Change the file so it can handle the new book and/or sky terms.
+@item
+@code{rlogin} to the machine in question and run 
+@code{conform -i -H /home/conform}
+
+NOTE : make sure you know what you are doing with @file{conform} before you do anything.
+@item
+Send the signal @code{kill -1 1} to the machine to start the process.
+@item
+If there are problems, make sure that the wiring of the serial cable is correct (a common problem).
+@end itemize
+
+To set up a terminal to run off the serial network (like the ones in the third floor foyer), try the following.
+@itemize @bullet
+@item
+Set up the terminal and make sure that the cable is wired correctly
+@item
+Look at @file{/home/conform/products/unsw/metad.v.2/filtered} and search for the phrase @code{bookterm}.  Modify the relevant parts to include the new skyterm.  Find somewhere in the annex (funnel0) to patch the skyterm to.  Be careful with the wiring.
+@item 
+For the booking terms, these can also be connected to the annex.  First the port that the terminal connects to on the funnel must be set up to be a dedicated port to that terminal.  Port 32 is an example. (This is using the annex system, ie @code{rlogin} to haydn, run @code{na}, etc)
+@item
+Next, a server has to have @code{bogin} running on it.  At the moment composer runs the booking terminal in the third floor foyer.  In the @file{/etc/services} file, there is a list of the services.  Here you will find the link to funnel0, port32 from the above example.
+@item
+Update the @file{/home/conform/products/unsw/book.v.2.4/config} file to the new reality.
+@item
+Run conform, just like before.
+@item
+Be annoyed when you realise that the serial network wiring is shot, and doesn't work.  Break out the multimeter.
+@end itemize
+
+@node Common problems
+@section Common problems
+This section not only details some common problems but also some situations that can occur and that the documentation doesn't cover elsewhere.
+@menu
+* Composite classes::
+* Lab command not updating, or showing wrong information::
+* User in certain class, but cannot log on during class booking::
+* Booking/Skyterm dies::
+* How can I tell if someone did log on when they said::
+@end menu
+
+@node Composite classes
+@subsection Composite classes
+A common occurance is for members of two classes to be able to log into the same lab.  This usually is done for third year subjects and their equivalent post-grad subject.  Such a class description is given as
+@example
+       COMP35119511
+@end example
+Ie with the two class numbers concatenated together.  The steps to do this are as follows: 
+@itemize @bullet
+@item
+Create a new class of the desired name:
+@example
+       acc -C .comp35119511
+@end example
+@item
+Set up the UserGroups class, with no expiry date.
+@example
+       acc classes+ UserGroups[forever] .comp35119511
+@end example
+@item
+For each class that is a component of the new dual class, do
+@example
+       acc classes+ comp35119511[forever] .comp3511
+and    acc classes+ comp35119511[forever] .comp9511
+@end example
+Ie each class is a member (child) of the dual class
+@item
+Set the netgroup priority to 8.
+@example
+       acc netgroup=8 .comp3511
+@end example
+@item
+Add the class by using bkuser
+@example
+       bkuser -C comp35119511
+@end example
+@item
+Change the allotments file and run it through the system
+@example
+       /home/ss/book/bin/allot < allotments
+@end example
+@end itemize
+
+@node Lab command not updating, or showing wrong information
+@subsection Lab command not updating, or showing wrong information
+Occasionaly the information displayed by the @code{lab} command is incorrect.  This is usually due to the status table not updating.  The @code{smadmin} command is useful in this case, in order to force an update.  If all else fails, try @code{metac -K book} on the machine in question.  This will pretty much make sure the status table is updated.
+
+@node User in certain class, but cannot log on during class booking
+@subsection User in certain class, but can't log on during class booking
+The first thing to do is determine if this occurs for everyone who is a member of the class in question.  If so, then it is likely that the lab booking was made for the wrong class.  The usual cause of this problem is due to the Netgroup priority not being set correctly.  This isn't the place to describe the details, but make sure the Netgroup priority of the subject is not 1, and in most cases should be 8.
+
+@node Booking/Skyterm dies
+@subsection Booking/Skyterm dies
+These little things can go down for a variety of reasons, but most are to do with either serial cable failure, or the status table not updating on the machine that the terminal is connected to.  The following is a check list of things to look at:
+@itemize @bullet
+@item
+Check the serial cable.  Make sure that it is connected at both end, and the plugs haven't been jerked thus snapping the wires.  In addition, make sure the cable is plugged into the correct ports on both the terminal and the machine.  If the terminal is plugged into the serial network, make sure no-one has done any re-wiring.
+@item
+Skyterms are rather easy, as they only recieve information.  If a Skyterm is blank, check that the status table on that machine is being updated correctly.  If it is not, then the Skyterm will die.  See @ref{Lab command not updating, or showing wrong information,} for information about status tables.
+The Booking terms are a little more tricky, as they both send and recieve data.  If a booking term isn't working, make sure that @file{bogin} is running on the machine that the terminal is connected to.  After that, it much depends on the situation.  If the terminal was working, then something on the system end may have changed.  If you are trying just to get a machine working, getting the connections correct in the serial cable is a bit of a hassle.  The best thing to do is to check an already existing cable, and go from there.
+@item
+Most problems are from the system end, not the hardware end.  Those little dumb terminals are pretty bullet proof.  The workstation machines are not.
+@end itemize
+
+@node How can I tell if someone did log on when they said
+@subsection How can I tell if someone did log on when they said
+Check out the logon logs :) on (Sep 97)
+@example
+       /import/elephant/1/all-logins/[YYMMDD]
+@end example 
+
+
+
+@node Interaction with other systems, Policy, UNSW Local Lore, TOP
+@appendix Interaction with other systems
+This chapter summarises the interactions that the booking system needs
+to have with other systems in order to be able to do its job.  These
+systems are the login system (which normally means @file{xdm}), the
+user interface (which must be via @file{xdm}), the YP/NIS system, and
+the people who use TABS.
+
+@menu
+* Login interaction::           
+* User Interface Interaction::  
+* YP/NIS interaction::          
+* Interaction with users::      
+@end menu
+
+@node Login interaction, User Interface Interaction, Interaction with other systems, Interaction with other systems
+@section Login interaction
+
+For obvious reasons, the booking system needs to be aware of, and have
+some control over, user logins and logouts.
+It maintains this awareness and control through three contact points:
+@table @file
+@item book_login
+@file{book_login} is a program which should be run as part of the login
+process. It should be passed various arguments discussed elsewhere
+including the username and the workstation name.
+
+@file{book_login} checks whether or not the user is allowed to log in to
+this workstation. If not, an explanatory message is sent to
+@file{stdout} and @file{book_login} exits with a failure status. It is
+expected that this failure status will cause the login to be aborted.
+If the user is allowed to login, @file{book_login} informs the booking
+system, logs the login, and exits with a success status.
+
+This program can easily be incorporated into an @file{xdm}
+@file{startup} script.
+
+@item book_logout
+@file{book_logout} is a program which should be run when a user logs
+out.
+It informs the booking system that the user has logged out, logs the
+logout, and optionally kills all processes on the host owned by the
+user.
+
+@file{book_logout} can easily be incorporated into an @file{xdm}
+@file{reset} script.
+
+@item /var/X11/xdm/xdm-pids/@var{displayname}
+This is a file which should hold the process id number of the @file{xdm}
+process which is controlling the named display.
+When the booking system needs to force someone to logout, it does so by
+sending a @code{SIGTERM} signal to the process named in this file.
+
+If the @file{xdm} @file{startup} file is a @file{ksh} script, then this
+can be achieved by echoing @code{$PPID} into the appropriate file.
+@end table
+
+@node User Interface Interaction, YP/NIS interaction, Login interaction, Interaction with other systems
+@section User Interface Interaction
+The only interaction that the booking system needs with the user
+interface occurs when it needs to send a message to the user informing
+that they should consider logging off.
+With the X11 window system, the only complexity in this is determining
+the appropriate authentication information.
+Rather than trusting @file{.Xauthority} in the users @file{HOME}
+directory, the booking system takes a copy of the @code{Xauthority}
+information and stores it in a known, protected place. This place is
+@file{/var/X11/xdm/save_auth/@var{displayname}}.
+The authority info is stored by running @file{saveauth} in the
+@file{xdm} @file{session} script.
+@file{saveauth} is set-uid to @code{root} and is careful only to save
+authentication information that is actually valid.
+@file{saveauth} needed to run in @file{session} with the X11R4
+@file{xdm} because authentication information was not available in
+@file{startup}. Possibly with X11R5 and later releases, @file{saveauth}
+can be run in @file{startup} and need not be set-uid.
+
+The messages are displayed on the workstation screen via a program
+called @file{messaged}. This program accepts requests (from privileged
+sources) to displays messages accordingly, using
+@file{/var/X11/xdm/save_auth/@var{displayname}} to authenticate itself
+to the Xserver. It can manage multiple message windows on multiple
+workstations simultaneously.
+
+@node YP/NIS interaction, Interaction with users, User Interface Interaction, Interaction with other systems
+@section YP/NIS interaction
+
+Apart from username/uid lookup and hostname/ip-address lookup,  which
+may or may not go through YP, the only interaction that the booking
+system has with YP is to determine class membership.
+
+Class membership is needed to validate access to class bookings and to
+determine token allocations.
+
+Class membership should be recorded in the @code{netgroup} table within
+YP. Netgroups provide for hierarchical memberships and this can be quite
+useful for class memberships. For example, there could be a netgroup for
+each first year subject, and a netgroup for first year which contains
+each subject. Then a base level of token allocation could be given to the
+first-year class, and extra allocation to each particular subject.
+
+The booking system does not use the @code{innetgr} library routine for
+accessing netgroups as many implementations are not very
+efficient. Rather, it does direct YP lookups of the
+@file{netgroup.byuser} or @file{netgroup_byuser} maps.
+
+@node Interaction with users,  , YP/NIS interaction, Interaction with other systems
+@section Interaction with users
+
+Apart from the obvious user initiated interactions, such as making,
+querying, and cancelling bookings, the booking system needs to provide
+two special interaction which must not require logging in to a
+workstation.
+
+
+
+@menu
+* Notification of allocations::  
+* Discovery of late arrivals::  
+@end menu
+
+@node Notification of allocations, Discovery of late arrivals, Interaction with users, Interaction with users
+@subsection Notification of allocations
+The booking system needs to be able to inform booked users which
+workstation they have been allocated to. For this the booking system
+needs one or more dedicated displays, which will usually be character
+based terminals.
+The program @file{bookterm} should be run in these terminals. It is
+given a list of labs to report on and will continually display a list of
+users who have been allocated workstation, but have not yet logged on.
+
+@node Discovery of late arrivals,  , Notification of allocations, Interaction with users
+@subsection Discovery of late arrivals
+
+If a user books multiple consecutive periods and does not turn up for
+the first period, the booking system assumes that they couldn't make it
+and does not free up a workstation for subsequent periods.
+If they do eventually arrive, the booking system needs to be told.
+For this purpose, dedicated terminals need to be provided for late
+arrivals to announce their presence.  These terminals should run
+@file{bogin} which works in a similar fashion to @file{/bin/login} except
+that instead of running the user's shell, it runs the @file{book}
+program.
+
+
+@node Policy,  , Interaction with other systems, TOP
+@appendix Policy
+Restrictions:
+@itemize @bullet
+@item
+Users can only book for the future, and not the current or next
+period.
+@item  Non-classes can only book one workstation at a time
+@item  Classes can book any number of workstations at a time
+@item
+Users cannot cancel a booking on the day of allocation, unless it
+was also made on the day of allocation. (This might change)
+@item
+The last 10% of a lab can only be booked in the last 4 days before
+the time of the booking. Exceptions are where classes and privileged
+tokens are concerned.
+@item
+If a booking cannot be honoured, the token is refunded in a
+privileged form and can book from the last 10% of the lab.
+@item
+Users can make bookings for other users - to support group
+work. This is not fully implemented yet.
+@end itemize
+
+@contents
+@bye
+
diff --git a/interfaces/Makefile b/interfaces/Makefile
new file mode 100644 (file)
index 0000000..d5abe78
--- /dev/null
@@ -0,0 +1,12 @@
+
+lib += bookinterface.a
+obj-bookinterface.a =  bookinterface.o charpairlist.o ../manager/up_host.o
+#  ../database/libdbclnt.a   ../db_client/libdbclient.a  ../manager/status_client.a ../lib/lib.a
+
+
+lib += libval.a
+obj-libval.a =  valinterface.o kill_user.o wsdisplay.o
+# ../manager/status_client.a  ../database/libdbclnt.a  ../db_client/libdbclient.a ../lib/lib.a
+
+
+include $(S)$(D)../MakeRules
diff --git a/interfaces/bookinterface.c b/interfaces/bookinterface.c
new file mode 100644 (file)
index 0000000..28ceec8
--- /dev/null
@@ -0,0 +1,697 @@
+#include       "bookinterface.h"
+
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <time.h>
+#include       <stdio.h>
+
+    
+
+static users u_rec;
+static intpairlist u_tokens;
+static int user_id = -1;
+
+void init_interface()
+{
+       if (!get_attr_types()
+           || !get_impl_list())
+       {
+               printf("Cannot communicate with booking system\n");
+               exit(1);
+       }
+}
+
+static reply_res set_user(bookuid_t  user)
+{
+       char k[20];
+
+       user_id = user;
+       xdr_free((xdrproc_t)xdr_users, (char*) &u_rec);
+
+       return db_read(user_key(k, user), &u_rec, (xdrproc_t)xdr_users, BOOKINGS);
+    
+}
+
+userbookinglist getbookings( bookuid_t user)
+{
+       if (user_id != user)
+       {
+               if (set_user(user) != R_RESOK)
+                       return NULL;
+       }
+       return u_rec.bookings;
+}
+
+userbookinglist getpastbookings( bookuid_t user)
+{
+       if (user_id != user)
+       {
+               if (set_user(user) != R_RESOK)
+                       return NULL;
+       }
+       return u_rec.pastbookings;
+}
+
+utilizationlist freeslots(tokenname tname, int doy, int pod)
+{
+       slotlist sl = NULL;
+       slotgroup *s;
+       static token tok;
+       descpairlist all_descriptions;
+       description *tok_desc;
+       utilizationlist *ul;
+       description timebits;
+       descpairlist ptr;
+       time_t now;
+       struct tm *nowtm;
+       char key[20];
+       int adv_time;
+       int attr;
+       int openlabs;
+       int token_id;
+    
+       memset(&tok, 0, sizeof(token));
+       memset(&all_descriptions, 0, sizeof(descpairlist));
+    
+    
+       /* get the token decsription */
+       token_id = get_mappingint(tname, M_TOKEN);
+       if (token_id == -1) {
+               printf("Bad Token id for token %s\n", tname);
+               return NULL;
+       }
+       if (db_read(key_int(key, C_TOKENS, token_id), &tok, (xdrproc_t)xdr_token, CONFIG)
+           != R_RESOK)
+               return NULL;
+
+       time(&now);
+       nowtm = localtime(&now);
+
+       adv_time = get_configint("book_advance");
+
+       if (adv_time <= 0)
+               adv_time = 4;
+       if (nowtm->tm_yday + adv_time < doy)
+               tok_desc = &tok.advance;
+       else
+               /* late booking */
+               tok_desc = &tok.late;
+
+       /* find out whether any of the labs are "open" now */
+       openlabs=0;
+       for (attr=0 ; attr<BITINDMAX ; attr++)
+               if (query_bit(&attr_types.lab, attr) && query_bit(tok_desc, attr))
+               {
+                       timebits = new_desc();
+                       set_a_bit(&timebits, attr);
+                       process_exclusions(pod, doy, &timebits);
+
+                       if (query_bit(&timebits, attr_types.open)
+                           && mandatory_attrs(&attr_types.mandatory,  &timebits, tok_desc) == TRUE)
+                       {
+                               openlabs++;
+                       }
+                       free(timebits.item_val);
+               }
+
+       if (openlabs == 0) return NULL;
+/*
+  printf("Token description %s:",key);
+  show_token(&tab, &tok);
+*/
+
+       if (db_read(str_key(HOST_SETS), &all_descriptions, (xdrproc_t)xdr_descpairlist, CONFIG)
+           != R_RESOK)
+               return(NULL);
+
+       ptr = all_descriptions;
+
+       while (ptr != NULL)
+       {
+               if (desc_member(tok_desc, &ptr->data))
+               {
+                       /* check lab is open */
+                       desc_cpy(&timebits, &ptr->data);
+                       process_exclusions(pod, doy, &timebits);
+
+                       if (mandatory_attrs(&attr_types.mandatory,  &timebits, tok_desc) != FALSE)
+                       {
+                               slotlist sl2;
+                               s = (slotgroup *)malloc(sizeof(slotgroup));
+                               sl2= (slotlist)malloc(sizeof(slotnode));
+                               sl2->data = s;
+                               sl2->next = sl;
+                               sl = sl2;
+           
+                               s->doy = doy;
+                               s->pod = pod;
+                               desc_cpy(&s->what, &ptr->data);
+                       }
+                       free(timebits.item_val);
+               }
+       
+               ptr = ptr -> next;
+       }
+       xdr_free((xdrproc_t)xdr_descpairlist, (char*) &all_descriptions);
+       ul = free_slots_2(&sl, db_client);
+       if (ul)
+               return *ul;
+       else
+               return NULL;
+}
+
+/* for ordinary user assume that their request is the same as the */
+ /* token for the moment.  Also assume that they can only make one */
+ /* booking at a time for any slot */
+
+bool_t makebook2(bookuid_t user, slotgroup * slotname, tokenname tname, time_t
+                when, int count, bookuid_t who_by, int currtok)
+{
+       book_change arg;
+       token tok;
+       book_changeres result;
+       time_t now;
+       struct tm *nowtm;
+       int tnum;
+       char tkey[20];
+
+
+       arg.chng = ADD_BOOKING;
+       memset(&tok, 0, sizeof(tok));
+    
+       if ((tnum = get_mappingint(tname, M_TOKEN)) == -1) {
+               shout("Bad token\n");
+               return(FALSE);
+       
+       }
+       arg.book_change_u.booking.slot = *slotname;
+
+       if (db_read(key_int(tkey, C_TOKENS, tnum), &tok, (xdrproc_t)xdr_token, CONFIG)
+           != R_RESOK)
+               return(FALSE);
+
+       time(&now); nowtm = localtime(&now);
+       if (nowtm->tm_yday + 4 < slotname->doy)
+               arg.book_change_u.booking.mreq = tok.advance;
+       else
+               arg.book_change_u.booking.mreq = tok.late;
+    
+    
+       arg.book_change_u.booking.when = when;
+       arg.book_change_u.booking.who_for = user;
+       arg.book_change_u.booking.who_by = who_by;
+       arg.book_change_u.booking.tnum = tnum;
+       if (currtok == -1)
+               arg.book_change_u.booking.tok_cnt = currtok;
+       else {
+               intpairlist cp;
+               cp = findintkey(u_rec.tokens, tnum);
+               if (cp) currtok = cp->num;
+               else currtok = 0;
+               arg.book_change_u.booking.tok_cnt = currtok;
+       }
+       arg.book_change_u.booking.number = count;
+
+       result = send_change(&arg);
+
+       if (result != 0)
+               printf("Error %d from server\n",result);
+    
+       return (result == 0)? TRUE : FALSE;
+}
+
+bool_t unbook(bookuid_t user, int when, book_key which)
+{
+       book_change arg;
+       book_ch_ent buents[2];
+
+       arg.chng = CHANGE_BOOKING;
+       arg.book_change_u.changes.slot = which;
+       arg.book_change_u.changes.chlist.book_ch_list_len = 1;
+       arg.book_change_u.changes.chlist.book_ch_list_val = buents;
+       buents[0].user = user;
+       buents[0].when = when;
+       buents[0].status = B_CANCELLED;
+       buents[0].priv_refund = 0;
+       buents[0].tentative_alloc = 0;
+       buents[0].allocations.entitylist_len = 0;
+       buents[0].allocations.entitylist_val = NULL;
+    
+
+       if (send_change(&arg)==0)
+               return TRUE;
+       else
+               return FALSE;
+}
+
+bool_t refund(bookuid_t user, int when, book_key which)
+{
+       book_change arg;
+       book_ch_ent buents[2];
+
+       arg.chng = CHANGE_BOOKING;
+       arg.book_change_u.changes.slot = which;
+       arg.book_change_u.changes.chlist.book_ch_list_len = 1;
+       arg.book_change_u.changes.chlist.book_ch_list_val = buents;
+       buents[0].user = user;
+       buents[0].when = when;
+       buents[0].status = B_REFUNDED;
+       buents[0].priv_refund = 1;
+       buents[0].tentative_alloc = 0;
+
+       buents[0].allocations.entitylist_len = 0;
+       buents[0].allocations.entitylist_val = NULL;
+
+       if (send_change(&arg)==0)
+               return TRUE;
+       else
+               return FALSE;
+}
+
+/* TOKEN_PERIOD: returns a list of pairs of days of the year that */
+ /* define the weeks the token is valid for.  Returns NULL if no token */
+ /* found */
+period_spec * token_period(char * tname)
+{
+       token tok;
+       int i;
+       int cnt;
+       period_spec * result;
+       char key[20];
+       int tnum;
+
+       tnum = get_mappingint(tname, M_TOKEN);
+       memset(&tok, 0, sizeof(tok));
+
+       if (db_read(key_int(key, C_TOKENS, tnum), &tok, (xdrproc_t)xdr_token, CONFIG)
+           != R_RESOK)
+               return NULL;
+
+       /* should select between advance spec and late */
+
+       /* make some space for the result, this is overkill */
+       result = (period_spec*)malloc(sizeof(period_spec) * impl_list.exlist.exlist_len);
+
+       /* look at each index definied in the token, get info about it */
+       /* from the exclusions table.  Want to know the day of the year */
+       /* that the week starts and finishes */
+       cnt = 0;
+       for (i = 0; i < impl_list.total ; i++)
+       {
+               int a = impl_list.exlist.exlist_val[i].attribute;
+#ifdef RET_PERIOD_NOT_VALID
+               /* if in mand and NOT in adv and not other restrictions */
+               if (!query_bit(&tok.advance, a) &&
+                   query_bit(&attr_types.mandatory, a))
+               {
+                       implication *im = &impl_list.exlist.exlist_val[i];
+                       if (im->startdow > 0
+                           || im->enddow < 6
+                           || im->starttime>0
+                           || im->endtime < 24*60
+                           || !null_desc(&im->included)
+                           || !null_desc(&im->excluded))
+                               ;
+                       else
+                       {
+                               result[cnt].start = im->startday;
+                               result[cnt].end = im->endday;
+                               cnt++;
+                       }
+               }
+#else
+               if (query_bit(&tok.advance, a) &&
+                   query_bit(&attr_types.mandatory, a))
+               {
+                       int st, en;
+                       st = impl_list.exlist.exlist_val[i].startday;
+                       en = impl_list.exlist.exlist_val[i].endday;
+                       if (st > 0 && en > 0)
+                       {
+                               result[cnt].start = st;
+                               result[cnt].end = en;
+                               cnt++;
+                       }
+               }
+#endif
+       }
+       xdr_free((xdrproc_t)xdr_token, (char*) &tok);
+
+
+
+
+       /* terminate the list */
+       result[cnt].start = -1;
+       return result;
+}
+
+
+/* FIND_USER_RES: find the user's reservation in the table */
+int find_user_res(table * tab, int userid)
+{
+       int i;
+       for (i=0;i < tab->table_len;i++)
+               if ((Cluster_type(tab,i) == WSRESCLUSTER) &&
+                   (Ws_user(tab,i) == userid)) 
+                       return i;
+    
+       return -1;
+}
+
+/* RECLAIM: attempts to reclaim a tentative booking in a lab for the */
+ /* user */
+reclaim_result reclaim(char * labname, int userid)
+{
+       CLIENT * labhandle;
+       table labtab;
+       hostinfo labdata;
+       hostgroup_info hg;
+       entityid lab_num;
+       int index;
+       char key[20];
+       reclaim_result res;
+       int partner_index;
+    
+       res.state = RECNO;
+       res.host = NULL;
+
+       memset(&labdata, 0, sizeof(labdata));
+       memset(&hg, 0, sizeof(hg));
+    
+       /* try to talk to the lab */
+       lab_num = get_mappingint(labname, M_ATTRIBUTE);
+       if (lab_num < 0)
+               return res;
+       if (db_read(key_int(key, C_LABS, lab_num), &labdata, (xdrproc_t)xdr_hostinfo, CONFIG)
+           != R_RESOK)
+               return res;
+       if (db_read(key_int(key, C_HOSTGROUPS, labdata.clump), &hg, (xdrproc_t)xdr_hostgroup_info, CONFIG)
+           != R_RESOK)
+       {
+               xdr_free((xdrproc_t)xdr_hostinfo, (char*) &labdata);
+               return res;
+       }
+       labhandle = up_host(&hg, FALSE);
+       xdr_free((xdrproc_t)xdr_hostinfo, (char*) &labdata);
+       xdr_free((xdrproc_t)xdr_hostgroup_info, (char*) &hg);
+    
+       if (labhandle == NULL)
+               return res;
+
+       /* get the allocation data from the lab */
+       collect_tab(&labtab, labhandle);
+    
+       index = find_user_res(&labtab, userid);
+       if (index == -1)
+       {
+               res.state = RECUNASSIGNED;
+       }
+       else
+       {
+               partner_index = lookup_table(&labtab, WSSTATUSCLUSTER,
+                                            Cluster_id(&labtab,index));
+
+               res.host = get_mappingchar(Cluster_id(&labtab,index), M_WORKSTATION); 
+
+               if (Ws_status(&labtab,partner_index) == NODEBROKEN)
+               {
+                       res.state = RECDOWN;
+               }
+               else if (Ws_user(&labtab,partner_index) == -1)
+               {
+                       res.state = RECUNUSED;
+               }
+               else if (Ws_user(&labtab, partner_index) == userid)
+               {
+                       res.state = RECSELF;
+               }
+               else
+                       res.state = RECOK;
+    
+               if (res.state != RECNO)
+               {
+                       /* contact the node to make the update */
+
+                       CLIENT * reclaim_node;
+                       static resinfo data ;
+                       char * host;
+                       bool_t * result;
+
+       
+                       data.wsid = Cluster_id(&labtab,index);
+                       data.userid = userid;
+                       data.when = time(0)+5*60;
+                       if (Cluster_modtime(&labtab,index) > data.when)
+                               data.when = Cluster_modtime(&labtab,index)+1;
+
+                       res.when = data.when;
+                       result = res_user_time_2(&data, labhandle);
+                       if (result == NULL || (*result == FALSE))
+                               if (res.state == RECOK)
+                                       res.state = RECNO;
+
+                       /* now maybe talk to the actualy host */
+                       host = get_mappingchar(Ws_whereon(&labtab,partner_index), M_HOST);
+                       if (host != NULL)
+                       {
+                               reclaim_node = clnt_create(host, BOOK_STATUS, BOOKVERS, "udp");
+                               if (reclaim_node != NULL)
+                               {
+                                       result = res_user_time_2(&data, reclaim_node);
+                                       if (result != NULL && (*result != FALSE))
+                                               if (res.state == RECNO)
+                                                       res.state = RECOK;
+                                       clnt_destroy(reclaim_node);
+                               }
+                               free(host);
+                       }
+               }
+       }
+       xdr_free((xdrproc_t)xdr_table, (char*) &labtab);
+
+       clnt_destroy(labhandle);
+       return res;
+}
+/* INT_2_CHARPAIRLIST: converts a charpairlist into an int pair list */
+void int_2_charpairlist(charpairlist * cl, intpairlist il, nmapping_type type)
+{
+       while (il  != NULL) {
+               char * charval;
+               charval = get_mappingchar(il->data, type);
+               if (charval != NULL)
+                       inckey(cl, charval, il->num);
+               il = il->next;
+       }
+}
+static void free_tokens();
+charpairlist gettokenlist(bookuid_t user)
+{
+       charpairlist result = NULL;
+       reply_res res;
+
+       /* get the user record always, as the token list may have changed*/
+       res = set_user(user);
+       if ( (res != R_RESOK) && (res != R_NOMATCH))
+       {
+               fprintf(stderr, "Bad status of user\n");
+               return (NULL);
+       }
+       if (u_tokens)
+               free_tokens();
+       u_tokens = get_alloc_tokenlist(user);
+       if (user < get_class_min())
+               del_intlist_nodel(&u_tokens, &u_rec.tokens);
+       int_2_charpairlist(&result, u_tokens, M_TOKEN);
+       return result;
+}
+
+int priv_tok_avail(char *tokname)
+{
+       /* find out how many privileged versions of this token are available */
+       int tnum;
+       intpairlist ip;
+       tnum = get_mappingint(tokname, M_TOKEN);
+       if (tnum < 0)
+               return 0;
+       for (ip= u_rec.priv_tokens ; ip ; ip=ip->next)
+               if (ip->data == tnum)
+                       return ip->num;
+       return 0;
+}
+
+int token_is_reusable(char *tokname)
+{
+       int tnum = get_mappingint(tokname, M_TOKEN);
+       char key[20];
+       static token tok;
+       if (tnum < 0)
+               return 0;
+               
+       memset(&tok, 0, sizeof(token));
+       if (db_read(key_int(key, C_TOKENS, tnum), &tok, (xdrproc_t)xdr_token, CONFIG)
+           != R_RESOK)
+               return 0;
+
+       if (query_bit(&tok.advance, attr_types.reusable))
+               return 1;
+       else
+               return 0;
+}
+
+int *tokname2labs(char *tokname)
+{
+       /* returns a list of lab indexes, terminated by -1 */
+       int token_id;
+       static token tok;
+       char key[20];
+       description *tok_desc;
+       int i, cnt;
+       int *rv;
+            
+       memset(&tok, 0, sizeof(token));
+    
+    
+       /* get the token decsription */
+       token_id = get_mappingint(tokname, M_TOKEN);
+       if (token_id == -1) {
+               printf("Bad Token id for token %s\n", tokname);
+               return(NULL);
+       }
+       if (db_read(key_int(key, C_TOKENS, token_id), &tok, (xdrproc_t)xdr_token, CONFIG)
+           != R_RESOK)
+               return NULL;
+
+       tok_desc = &tok.advance;
+
+       cnt=0;
+       for (i=0; i< BITINDMAX ; i++)
+       {
+               if (query_bit(&attr_types.lab, i)
+                   && query_bit(tok_desc, i))
+                       cnt++;
+       }
+       rv = (int*)malloc((cnt+1)*sizeof(int));
+       cnt=0;
+       for (i=0; i< BITINDMAX ; i++)
+       {
+               if (query_bit(&attr_types.lab, i)
+                   && query_bit(tok_desc, i))
+                       rv[cnt++] = i;
+       }
+       rv[cnt] = -1;
+    
+       return rv;
+}
+
+
+int *get_all_labs()
+{
+       int i, cnt;
+       int *rv;
+            
+       cnt=0;
+       for (i=0; i< BITINDMAX ; i++)
+       {
+               if (query_bit(&attr_types.lab, i))
+                       cnt++;
+       }
+       rv = (int*)malloc((cnt+1)*sizeof(int));
+       cnt=0;
+       for (i=0; i< BITINDMAX ; i++)
+       {
+               if (query_bit(&attr_types.lab, i))
+                       rv[cnt++] = i;
+       }
+       rv[cnt] = -1;
+    
+       return rv;
+    
+}
+
+static token *u_tokeninfo;
+static void get_tokens()
+{
+       /* make a u_tokeninfo array the same size as u_tokens list
+        * and populate it
+        */
+       int c;
+       intpairlist p;
+    
+       if (u_tokeninfo) free_tokens();
+       if (u_tokens == NULL)
+               u_tokens = get_alloc_tokenlist(user_id);
+       for (c=0, p=u_tokens ; p ; c++, p=p->next)
+               ;
+       u_tokeninfo =(token*)malloc(c*sizeof(token));
+       memset(u_tokeninfo, 0, c*sizeof(token));
+       for (c=0, p=u_tokens ; p ; c++, p=p->next)
+       {
+               char key[20];
+               db_read(key_int(key, C_TOKENS, p->data), &u_tokeninfo[c], (xdrproc_t)xdr_token, CONFIG);
+       }
+}
+
+static void free_tokens()
+{
+       int c;
+       intpairlist p;
+
+       if (u_tokeninfo)
+       {
+               for (c=0, p=u_tokens ; p ; c++, p=p->next)
+               {
+                       xdr_free((xdrproc_t)xdr_token, (char*)&u_tokeninfo[c]);
+               }
+               free(u_tokeninfo);
+       }
+       u_tokeninfo = NULL;
+       xdr_free((xdrproc_t)xdr_intpairlist, (char*)&u_tokens);
+       u_tokens = NULL;
+}
+
+
+
+int check_bookable(int doy, int pod, int where)
+{
+       description timebits;
+       int found=0;
+       int c;
+       intpairlist p;
+    
+       if (u_tokeninfo == NULL)
+               get_tokens();
+       timebits = new_desc();
+       set_a_bit(&timebits, where);
+       process_exclusions(pod, doy, &timebits);
+
+       for (c=0, p=u_tokens ; !found && p ; c++, p=p->next)
+       {
+               /* fixme decide on advance or late... */
+               if (mandatory_attrs(&attr_types.mandatory, &timebits, &u_tokeninfo[c].advance))
+                       found++;
+       }
+       return found;
+}
+
+int lab_excluded(int lab)
+{
+       /* returns true if lab is a mandatory attribute
+        * and is not in any token
+        */
+       int c;
+       intpairlist p;
+
+       if (query_bit(&attr_types.mandatory, lab)==0)
+               return 0;
+
+       if (u_tokeninfo == NULL)
+               get_tokens();
+       for (c=0, p=u_tokens ; p ; c++, p=p->next)
+       {
+               /* fixme decide on advance or late... */
+               if (query_bit(&u_tokeninfo[c].advance, lab)
+                   ||query_bit(&u_tokeninfo[c].late, lab))
+                       return 0;
+       }
+       return 1;
+}
diff --git a/interfaces/bookinterface.h b/interfaces/bookinterface.h
new file mode 100644 (file)
index 0000000..6d5f4de
--- /dev/null
@@ -0,0 +1,62 @@
+
+#include       "../db_client/db_client.h"
+
+typedef char *tokenname;
+
+#define RET_PERIOD_NOT_VALID
+
+typedef struct charpairnode
+{
+    char *data;
+    int num;
+    struct charpairnode *next;
+} charpairnode, *charpairlist;
+
+charpairlist findkey(charpairlist, char*);
+
+void init_interface(void);
+userbookinglist getbookings(bookuid_t user);
+userbookinglist getpastbookings(bookuid_t user);
+charpairlist gettokenlist(bookuid_t user);
+utilizationlist freeslots(tokenname tname, int doy, int pod);
+bool_t makebook(bookuid_t user, slotgroup *slotname, tokenname tname, time_t
+               when, int count, bookuid_t whoby);
+bool_t makebook2(bookuid_t user, slotgroup * slotname, tokenname tname, time_t
+                when, int count, bookuid_t who_by, int currtok);
+bool_t unbook(bookuid_t user, int when, book_key which);
+bool_t refund(bookuid_t user, int when, book_key which);
+
+typedef struct period_spec
+{
+    int start;
+    int end;
+} period_spec;
+
+period_spec * token_period(char *);
+
+typedef enum reclaim_state {
+    RECNO= 1,  /* network problem */
+    RECUNASSIGNED=2, /* No reservation for the current period in lab */
+    RECDOWN=3, /* the node is down.. */
+    RECUNUSED =4, /* the node is not being used... go get it */
+    RECSELF =5, /* the node is being used by you YOU CLOT!! */
+    RECOK =10 /* booking status changed from tentative to 'hard' */
+             /* succesfully */
+} reclaim_state;
+
+typedef struct  reclaim_result {
+    reclaim_state state;
+    char * host;
+    time_t when;
+} reclaim_result;
+
+reclaim_result reclaim(char * lab, int userid);
+int* tokname2labs(char* tokname);
+
+book_changeres inckey(charpairlist *ch, char * key, int diff);
+int priv_tok_avail(char *tokname);
+int token_is_reusable(char *tokname);
+
+int *get_all_labs(void);
+int check_bookable(int doy, int pod, int where);
+int lab_excluded(int lab);
diff --git a/interfaces/charpairlist.c b/interfaces/charpairlist.c
new file mode 100644 (file)
index 0000000..d654e50
--- /dev/null
@@ -0,0 +1,107 @@
+/* some routines for adding and deleting from lists of strings and numbers */
+
+#include       "../interfaces/bookinterface.h"
+
+book_changeres add_to_charpairlist(charpairlist * ch, char * new, int num)
+{
+    charpairlist new_el;
+
+    /* maybe check if present */
+
+    new_el = (charpairlist) malloc (sizeof(charpairnode));
+
+    new_el -> data = strdup(new);
+    new_el -> num = num;
+    new_el -> next = *ch;
+    *ch  = new_el;
+    return C_OK;
+}
+
+void free_charpairlist(charpairlist l)
+{
+    if (l->next)
+       free_charpairlist(l->next);
+    free(l->data);
+    free(l);
+}
+
+book_changeres del_from_charpairlist(charpairlist * ch, char *name)
+{
+    charpairlist ptr = *ch;
+    charpairlist trailer = ptr;  /* this points to the prev entry in the */
+                            /* list to do deletions, easier than */
+                            /* doubly linked list */
+    book_changeres result = C_NOMATCH;
+
+
+    while (ptr != NULL) {
+       if (( strcmp(ptr -> data, name) == 0)) {
+
+           /* delete !! */
+           if (ptr == trailer) {  /* first node */
+               if (ptr -> next == NULL) /* only one node in list*/
+                   *ch = NULL;
+               else
+                   *ch = ptr -> next;
+           }
+           else
+               trailer->next = ptr-> next;
+           /* cut the node off */
+           ptr -> next = NULL;
+           free_charpairlist(ptr);
+           result = C_OK;
+       }
+       else { /* go to the next element in the list */
+           trailer = ptr;
+           ptr = ptr->next;
+       }
+    }
+    return(result);
+}
+
+charpairlist findkey(charpairlist ch, char * key)
+{
+    charpairlist ptr = ch;
+    while ((ptr != NULL) && (strcmp(ptr->data,key) != 0) ) {
+       ptr = ptr->next;
+    }
+    return(ptr);
+}
+
+/* if the key is present in the list inc it's value, else add it to */
+ /* the list with a value of 1 operation should always suceed*/
+book_changeres inckey(charpairlist *ch, char * key, int diff)
+{
+    charpairlist val;
+    if ((val = findkey(*ch,key)) == NULL) /* add new element to list */
+       return(add_to_charpairlist(ch,key,diff));
+    else 
+       val->num += diff;
+#if 0
+Dont do this as we are interested in tokens that are all used up    
+    if (val->num == 0) /* if this key is empty remove it */
+       return( del_from_charpairlist(ch,key));
+#endif
+    return(C_OK);
+}
+
+
+void merge_lists(charpairlist * tot, charpairlist * others)
+{
+    charpairlist ptr = *others;
+
+    while (ptr != NULL) {
+       inckey(tot, ptr->data, ptr->num);
+       ptr = ptr ->next;
+    }
+
+}
+
+void del_list(charpairlist * tot, charpairlist * subs)
+{
+    charpairlist ptr = *subs;
+    while (ptr != NULL) {
+       inckey(tot, strdup(ptr->data), -ptr->num);
+       ptr = ptr->next;
+    }
+}
diff --git a/interfaces/kill_user.c b/interfaces/kill_user.c
new file mode 100644 (file)
index 0000000..a50664f
--- /dev/null
@@ -0,0 +1,126 @@
+
+/*
+ * kill off all processes owned by a given user.
+ *
+ * on most (posix) OSes, fork, setuid, and send signal to proc -1
+ * on ultrix, setuid, do a "ps" and signal every process.
+ *
+ */
+
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <sys/wait.h>
+#include       <sys/types.h>
+#include       <signal.h>
+#include       <errno.h>
+#include       <stdio.h>
+
+#ifdef ultrix
+#define KILLALLFAILS
+#endif
+
+void killuser(int uid)
+{
+    pid_t pid;
+    int st;
+    if (uid <= 0) return;
+#ifndef KILLALLFAILS
+    switch(pid = fork())
+    {
+    case 0:
+       setuid(uid);
+       if (kill(-1, SIGHUP) != 0 && errno == ESRCH) exit(0);
+       sleep(5);
+       if (kill(-1, SIGTERM) != 0 && errno == ESRCH) exit(0);
+       sleep(15);
+       if (kill(-1, SIGKILL) != 0 && errno == ESRCH) exit(0);
+       exit(0);
+    case -1:
+       break;
+    default:
+       setuid(0); /* make sure I don't get killed */
+       while(wait(&st) != -1)
+           ;
+       break;
+    }
+#else
+#ifdef ultrix
+    /* fork a ps to find processes to kill */
+{
+    int pfd[2];
+    FILE *psf;
+    char buf[200];
+    int pids[500];
+    int pcnt = 0;
+    int i;
+    pipe(pfd);
+    switch(pid = fork())
+    {
+    case 0:
+       close(1);
+       close(pfd[0]);
+       dup2(pfd[1], 1);
+       close(pfd[1]);
+       execl("/bin/ps", "ps", "axgl", 0);
+       exit(100);
+    case -1:
+       break;
+    default:
+       setreuid(0, uid);
+       close(pfd[1]);
+       psf = fdopen(pfd[0], "r");
+       while (fgets(buf, sizeof(buf), psf)!= NULL)
+       {
+/*
+01234567890123456789   
+      F UID   PID  PPID CP PRI NI ADDR  SZ  RSS WCHAN STAT  TT  TIME COMMAND
+      3   0     0     0  0 -25  0  2fb   0    0 90a1d D     ?   0:01  swapper
+10000001   0     1     0  0   5  0  9b2 252  212 4b3a8 I     ?   0:04 /etc/init
+      3   0     2     0  0 -24  0  6a74096    0 4b4e0 D     ?   0:00  pagedaemo
+      3   0     3     0  0 -25  0  6ab   0    0       R     ?   0:00  idleproc
+1008001   0    45     1  0   1  0  757 100   56 90a5c S     ?   6:23 /etc/route
+1000001   0    72     1  1   1  0  860  96   60 90a5c S     ?   9:31 /etc/portm
+110080011443 27012     1  0  15  0 1550 416  140 fc000 S     p3  0:03 netstat -I
+110080011443 27013     1  0  15  0 1615 416  132 fc000 S     p3  0:03 netstat -I
+110080011443 27014     1  0  15  0  f89 400  140 fc000 S     p3  0:09 vmstat 2
+110080011443 27015     1  1  15  0 1731 388  172 fc000 S     p3  5:03 iostat 2
+01234567890123456789
+Don't bother getting uid, just try to kill everything 
+*/
+           char *st;
+           int p;
+           if (!isdigit(buf[6])) continue; /* proabably the header line */
+           st = buf + 11;
+           while (*st != ' ' && st < buf+17) st++;
+           if (*st != ' ') continue;
+           while (*st == ' ') st++;
+           if (st > buf+17) continue;
+           p = atoi(st);
+           if (p <= 1 || p == getpid() || p == pid)
+               continue;
+           if (kill(p, SIGHUP)== 0)
+           {
+               if (pcnt < 499) pids[pcnt++] = p;
+               else kill(p, SIGKILL);
+           }
+           
+           
+       }
+       fclose(psf);
+       wait(0);
+       for (i=0; i<pcnt ; i++)
+           kill(pids[i], SIGTERM);
+       for (i=0; i<pcnt ; i++)
+           kill(pids[i], SIGKILL);
+       
+       break;
+    }
+    setreuid(0, 0);
+}
+#else
+  */ --- CANNOT KILL ALL PROCESSES!!! 
+#endif /* ultrix */
+
+#endif
+}
+
diff --git a/interfaces/loglogin.c b/interfaces/loglogin.c
new file mode 100644 (file)
index 0000000..aa532f2
--- /dev/null
@@ -0,0 +1,59 @@
+
+#include       <sys/types.h>
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <stdio.h>
+#include       <sys/stat.h>
+#include       <time.h>
+#include       <fcntl.h>
+
+static char LogFile[] = "/var/book/logins";
+
+void log_login(char type, time_t when, char *workstation, char *host, char *user)
+{
+    char *buf;
+    struct tm *tm;
+    static int sfd = -1;
+    int fd = sfd;
+    char hname[256];
+
+    if (user == NULL)
+       user = "(nobody)";
+    if (workstation == NULL)
+       workstation = "(console)";
+    if (fd == -1)
+    {
+       fd = open(LogFile, O_WRONLY|O_CREAT|O_APPEND, 0640);
+       fcntl(fd, F_SETFD, 1);
+    }
+    else fd = dup(fd);
+    if (type == 0)
+    {
+       sfd = fd;
+       return; /* just opening file */
+    }
+
+    if (host == NULL)
+    {
+       host = hname;
+       gethostname(host, 256);
+    }
+    if (when == 0)
+       time(&when);
+    
+    buf = malloc( 1+1+13+1+strlen(workstation)+1+strlen(host)+5+strlen(user)+1+10);
+    tm = localtime(&when);
+    sprintf(buf, "%c %02d%02d%02d.%02d%02d%02d %s %s %s\n",
+           type, tm->tm_year, tm->tm_mon+1, tm->tm_mday,
+           tm->tm_hour, tm->tm_min, tm->tm_sec,
+           workstation, host, user);
+
+    if (fd >= 0)
+    {
+       fchmod(fd, 0640);
+       fchown(fd, 0, 0);
+       write(fd, buf, strlen(buf));
+    }
+    free(buf);
+    close(fd);
+}
diff --git a/interfaces/valinterface.c b/interfaces/valinterface.c
new file mode 100644 (file)
index 0000000..2a5a238
--- /dev/null
@@ -0,0 +1,220 @@
+#include       "valinterface.h"
+#include       <time.h>
+
+/* results from can login:
+    NOBOOKSYSTEM
+    OUTOFSERVICE
+    CLOSED
+    RESERVED username
+    CLASSRESERVED classname
+    NOTBOOKED
+    CANLOGIN
+
+    plus hosttotry
+    plus bookable
+    */
+    
+void canlogin(val_usermrec * rec, bookuid_t user, char * wsname, char * host)
+{
+       int mywsid;
+       int mylabid;
+       int bstatus;
+       int firm_alloc = 0;
+       time_t alloctime;
+       table tab;
+       int i;
+       int whohere;
+
+       rec->bookable = FALSE;
+
+       open_status_client((host != NULL) ? host : "localhost");
+       if (status_client == NULL)
+       {
+               rec->status = NOBOOKSYSTEM;
+               return;
+       }
+
+       if (wsname == NULL)
+       {
+               rec->status = NOBOOKSYSTEM;
+               return;
+       }
+    
+       mywsid = get_mappingint(wsname, M_WORKSTATION);
+       if (mywsid == -2)
+               mywsid = get_mappingint_cache(wsname, M_WORKSTATION);
+       if (mywsid == -1)
+       {
+               rec->status = NOBOOKSYSTEM;
+               return;
+       }
+
+       if ((collect_tab(&tab, status_client) == FALSE) ||
+           ( tab.table_len     == 0))
+       {
+               rec->status = NOBOOKSYSTEM;
+               return;
+       }
+    
+
+       /* assume table sorted alloccluster then all ws clusters in that lab */
+       for (i=0;i < tab.table_len;i++)
+       {
+               if (Cluster_type(&tab,i) == ALLOCCLUSTER)
+               {
+                       mylabid = Cluster_id(&tab, i);
+/*         labstatus = Alloc_status(&tab,i);  */
+                       alloctime = Cluster_modtime(&tab, i);
+               }
+               if (Cluster_type(&tab, i) == WSRESCLUSTER 
+                   && Cluster_id(&tab,i)== mywsid)
+               {
+                       whohere = Res_user(&tab,i);
+                       bstatus = Res_status(&tab,i);
+                       if (tab.table_val[i].data.data_len >= 3)
+                               firm_alloc = Res_status2(&tab, i);
+                       alloctime = Cluster_modtime(&tab,i);
+                       break;
+               }
+       }
+       if (i == tab.table_len)
+       {
+               /* workstation not found!! */
+               rec->status = NOBOOKSYSTEM;
+               return;
+       }
+       rec->whereami = NULL;
+       if (user >= 0)
+               for (i=0 ; i<tab.table_len;i++)
+               {
+                       if (Cluster_type(&tab,i) == WSRESCLUSTER
+                           && Res_status(&tab,i) == NODEALLOCATED
+                           && Res_user(&tab,i) == user)
+                               rec->whereami =
+                                       get_mappingchar_cache(Cluster_id(&tab,i), M_WORKSTATION);
+               }
+       switch(bstatus)
+       {
+       case NODEOUTOFSERVICE:
+               rec->status = OUTOFSERVICE;
+               rec->bookable = TRUE;
+               break;
+       case NODEBROKEN:
+       case NODENOTALLOC:
+       case NODETENTATIVE:
+               /* anyone can log on here */
+               rec->status = CANLOGIN;
+               break;
+       
+       case NODENOTBOOKABLE:
+               /* anyone can log on here too */
+               rec->status = CANLOGIN;
+               rec->bookable = FALSE;
+               break;
+       
+       case NODEALLOCATED:
+               /* can only log in if in class, or if allocation is old */
+               rec->bookable = TRUE;
+               if (whohere == -2)
+               {
+                       /* lab closed, unless in neverclose */
+                       char *labname = get_mappingchar_cache(mylabid, M_ATTRIBUTE);
+                       if (user >= 0 && labname && in_neverclose(user, labname)!= 0)
+                               rec->status = CANLOGIN;
+                       else
+                               rec->status = CLOSED;
+               }
+               else
+               {
+                       long age = time(0) - alloctime;
+                       int grace =  get_configint("grace");
+                       if (grace <= 0)
+                               grace = 7*60;
+                       if (time(0)<alloctime) age=0;
+                       if (age > 40*60 || (!firm_alloc && age > grace))
+                       {
+                               /* an old allocation */
+                               rec->status = CANLOGIN;
+                       }
+                       else if (user >= 0 && in_class(user, whohere) != 0)
+                               rec->status = CANLOGIN;
+                       else
+                       {
+                               if (whohere >= get_class_min())
+                                       rec->status = CLASSRESERVED;
+                               else
+                                       rec->status = RESERVED;
+                               rec->resname = find_username(whohere);
+                       }
+               }
+               break;
+       }
+       if (get_book_level()<=0) rec->bookable = 0;
+       if (user >=0 && (rec->status == CLOSED || rec->status == CLASSRESERVED || rec->status == RESERVED) && in_exempt(user)!= 0)
+               rec->status = CANLOGIN;
+           
+       xdr_free((xdrproc_t)xdr_table, (char*) &tab);
+}
+
+
+int hasloggedin(bookuid_t user, char * wsname, char * host)
+{
+       static ws_user_pair arg;
+       bool_t * result;
+       char *hostname = host;
+
+       if (wsname == NULL) return 0;
+       set_whoison(wsname, user);
+    
+       if (hostname == NULL)
+               hostname = get_myhostname();
+
+       arg.userid = user;
+       arg.hostid = get_mappingint(hostname, M_HOST);
+       if (arg.hostid == -2)
+               arg.hostid = get_mappingint_cache(hostname, M_HOST);
+       arg.wsid = get_mappingint(wsname, M_WORKSTATION);
+       if (arg.wsid == -2)
+               arg.wsid = get_mappingint_cache(wsname, M_WORKSTATION);
+
+       if (arg.wsid < 0 || arg.hostid < 0)
+               return 0;
+    
+       if (status_client == NULL ||
+           (result = new_user_2(&arg, status_client)) == NULL)
+       {
+               open_status_client((host != NULL) ? host : "localhost");
+               if (status_client == NULL ||
+                   (result = new_user_2(&arg, status_client)) == NULL)
+                       return 0;
+       }
+       /* record login in database */
+       if (user != NOONE)
+       {
+               time_t now, then;
+               book_set_claim *cp;
+               book_change ch;
+               int doy, pod;
+               static workstation_state st;
+               int lab;
+               char key[20];
+
+               get_attr_types();
+               ch.chng = CLAIM_BOOKING;
+               cp = & ch.book_change_u.aclaim;
+               now = time(0);
+               then = now + 7*60; /* a time in the "current" period FIXME use #define */
+               get_doypod(&then, &doy, &pod);
+               if (db_read(key_int(key, C_WORKSTATIONS, arg.wsid), &st, (xdrproc_t)xdr_workstation_state, CONFIG)== R_RESOK)
+               {
+                       lab = desc_2_labind(&st.state);
+                       cp->slot = make_bookkey(doy, pod, lab);
+                       cp->user = user;
+                       cp->when = 0;
+                       cp->claimtime = ((now-then+30*60)<<4) | DID_LOGIN;
+                       cp->where = arg.wsid;
+                       send_change(&ch);
+               }
+       }
+       return 1;
+}
diff --git a/interfaces/valinterface.h b/interfaces/valinterface.h
new file mode 100644 (file)
index 0000000..6d58c25
--- /dev/null
@@ -0,0 +1,29 @@
+
+#include       "../db_client/db_client.h"
+
+
+typedef enum val_result {
+    NOBOOKSYSTEM,
+    OUTOFSERVICE,
+    CLOSED,
+    RESERVED,
+    CLASSRESERVED,
+    NOTBOOKED,
+    CANLOGIN
+} val_result;
+
+typedef struct val_umrec{
+    val_result status;
+    bool_t     bookable;               /* is machine bookable? */
+    char       *resname;               /* who reserved for */
+    char       *whereami;              /* workstation requesting user allocated to */
+} val_usermrec;
+
+#define NOONE (-1)
+
+extern void canlogin(val_usermrec * rec, bookuid_t user, char * wsname, char * host);
+extern int hasloggedin(bookuid_t user, char * wsname, char * host);
+extern char *display2ws(char*);
+
+void log_login(char type, time_t when, char *workstation, char *host, char *user);
+void killuser(uid_t uid);
diff --git a/interfaces/wsdisplay.c b/interfaces/wsdisplay.c
new file mode 100644 (file)
index 0000000..1699a86
--- /dev/null
@@ -0,0 +1,52 @@
+
+/* map display name to workstation name.
+ *
+ * split off host part: if it empty or "unix", set to hostname
+ * split off display part: discard screen number
+ * try host:display - is it fails retry with less of the hostname
+ * if host disappears, then giveup
+ *
+ */
+
+#include       <sys/types.h>
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <string.h>
+#include       "../db_client/db_client.h"
+
+char *display2ws(char *display)
+{
+    char host[256];
+    char disp[100];
+    char *colon;
+    int len;
+    int wsnum = -1;
+
+    colon = strchr(display, ':');
+    if (colon==NULL) return NULL;
+
+    strcpy(host, display);
+    host[colon-display] = '\0';
+    strcpy(disp, colon);
+    
+    if (host[0]=='\0' || strcmp(host,"unix")==0)
+    {
+       gethostname(host,256);
+    }
+
+    colon = strchr(disp, '.');
+    if (colon) *colon='\0';
+    
+    len = strlen(host);
+    while (wsnum== -1 && len > 0)
+    {
+       strcpy(host+len, disp);
+       wsnum = get_mappingint(host, M_WORKSTATION);
+       len--;
+       while (len>0 && host[len]!= '.') len--;
+    }
+
+    if (wsnum < 0)
+       return NULL;
+    return get_mappingchar(wsnum, M_WORKSTATION);
+}
diff --git a/lablist/Makefile b/lablist/Makefile
new file mode 100644 (file)
index 0000000..acd03f1
--- /dev/null
@@ -0,0 +1,14 @@
+
+target += lab
+
+obj-lab += lab.o  lablist.o  loadlab.o ../manager/up_host.o  ../db_client/libdbclient.a  ../database/libdbclnt.a  ../manager/status_client.a  ../lib/lib.a
+
+target += bookterm
+
+obj-bookterm += bookterm.o lablist.o loadlab.o ../manager/up_host.o  ../db_client/libdbclient.a ../database/libdbclnt.a   ../manager/status_client.a ../lib/lib.a
+
+target += labmon
+
+obj-labmon += labmon.o lablist.o  loadlab.o ../manager/up_host.o  ../db_client/libdbclient.a  ../database/libdbclnt.a ../manager/status_client.a  ../lib/lib.a
+
+include $(S)$(D)../MakeRules
diff --git a/lablist/bookterm.c b/lablist/bookterm.c
new file mode 100644 (file)
index 0000000..2e9ecc9
--- /dev/null
@@ -0,0 +1,598 @@
+
+#include       <sys/types.h>
+#include       <time.h>
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <sys/ioctl.h>
+#include       "lablist.h"
+#include       "../lib/skip.h"
+#include       <termios.h>
+#include       <curses.h>
+#include       <term.h>
+#include       <getopt.h>
+/*
+ * bookterm - repeatedly display allocation info with curses
+ *
+ * display is a 2 line banner, a blank line, a number of columns,
+ * and a final blank line.
+ * Each column is 24 characters wide with a 3 space column separator
+ *
+ * The banner gives {}{labnames}{Page n of m}
+ *                  {reservations for Period}{}{date-of-display}
+ *
+ * Each row in the columns contains some info about state of a
+ * workstation and the name of the workstation, with a row of dots between
+ * to stretch the column to full width.
+ *
+ * Labs are separated by two blank rows.
+ *
+ * each workstation produces one or possibly two rows.
+ * Also, each unsucessfull allocation may produce a row.
+ *
+ * The different rows that are possible are:
+ * DOWN..........workstation   workstation appears to be down
+ * DEAD..........workstation   workstation is out-of-service
+ * AVAILABLE.....workstation   workstation is free for anyone to use
+ * INUSE.........workstation   workstation is inuse
+ * sur-username..workstation   workstation is reserved for username
+ *                             either alloc still valid, or not inuse
+ * sur!username..workstation   user is logged on, but noone allocated
+ * sur+username..workstation   user is on and allocated
+ * sur?username..workstation   user is on, but allocated to someone else
+ * sur*username..workstation   user is allocated, but will have to claim
+ * sur*username........CLAIM   user is allocated but must reclaim
+ *
+ * sur-username.....REFUNDED   allocation could not be made
+ *
+ * the "sur" means the first three characters of the user's surname
+ *
+ * The rows are sorted either by workstation name or by state.
+ * when by state, the username rows come first, then AVAILABLE INUSE DOWN DEAD
+ *
+ * Each row type can be selectivly ignored.
+ *
+ */
+
+#define        USER_ALLOC      1
+#define        USER_ON         2
+#define        USER_ONALLOC    4
+#define        USER_CONFLICT   8
+#define        USER_CLAIM      16
+#define        USER_REFUND     32
+#define        USER_CLAIM2     64
+#define USER          127
+#define        AVAIL           128
+#define        INUSE           256
+#define        DOWN            512
+#define        DEAD            1024
+#define        TIME_ON         2048
+
+#define        PAGE_SLEEP      5
+#define        COL_SLEEP       3
+
+// whether or not to show the first few chars of the surname
+// on user lines, default to yes.
+static int show_surname = 1;
+
+int row_type(cluster *st, cluster *res, time_t now)
+{
+       static int grace=0;
+       int rv=0;
+       if (ws_status(st) == NODEDOWN)
+               return DOWN;
+
+       if (grace == 0)
+       {
+               grace = get_configint("grace");
+               if (grace<=0) grace=7*60;
+       }
+
+       if (res_status(res) == NODEOUTOFSERVICE)
+       {
+               rv = DEAD;
+               /* there should be no-one on, but check anyway */
+               if (ws_user(st) >= 0)
+                       rv |= USER_ON;
+               return rv;
+       }
+       if (res_status(res) == NODETENTATIVE
+           || (res_status(res) == NODEALLOCATED
+               && now-cluster_modtime(res) > grace))
+       {
+               /* tentatively reserved */
+               if (ws_user(st)< 0)
+                       rv |= USER_ALLOC;
+               else if (in_class(ws_user(st), res_user(res))==0)
+                       rv |= USER_CLAIM|USER_CLAIM2;
+       }
+       else if (res_status(res) == NODEALLOCATED
+                && in_class(ws_user(st), res_user(res))==0)
+               rv |= USER_ALLOC;
+    
+       if (ws_user(st) >= 0)
+       {
+               if (rv & USER_ALLOC)
+                       rv |= USER_CONFLICT;
+               else if (in_class(ws_user(st), res_user(res))==1)
+                       rv |= USER_ONALLOC;
+               else
+                       rv |= USER_ON;
+       }
+       if (rv & (USER_ON|USER_ONALLOC))
+               rv |= INUSE;
+       if (ws_user(st) < 0 && (rv & USER_ALLOC)==0)
+               rv |= AVAIL;
+       return rv;
+}
+
+/* the rows for each lab are built up in an array of the following
+ * structure
+ */
+
+typedef struct row
+{
+       int type;
+       char text[25];
+       int     hoststart;
+} *row;
+
+static int host_cmp(row a, row b)
+{
+       return strcmp(a->text+a->hoststart, a->text+b->hoststart);
+}
+static int user_cmp(row a, row b)
+{
+       if (a->type == b->type)
+               return strcmp(a->text, b->text);
+       else
+               return a->type - b->type;
+}
+
+/* cache usernames and fullnames to reduce lookups */
+typedef struct uscache {
+    int id;
+    char info[30];
+} *uscache;
+static int us_cmp(uscache a, uscache b, int *u)
+{
+       if (b) u = & b->id;
+       return a->id - *u;
+}
+static void us_free(uscache a)
+{
+       free(a);
+}
+
+static void *uslist = NULL;
+void set_user(char *r, int uid, char sep)
+{
+       char *name, *sur;
+       uscache *cp;
+       int i=0;
+
+       if (uslist == NULL) uslist = skip_new(us_cmp, us_free, NULL);
+       cp = skip_search(uslist, &uid);
+       if (cp)
+       {
+               strncpy(r, (*cp)->info, 24);
+               if (show_surname) {
+                       r[3] = sep;
+                       i += 4;
+               }
+               r[20+i] = 0;
+       }
+       else
+       {
+               uscache ce= (uscache)malloc(sizeof(struct uscache));
+               ce->id = uid;
+               name = find_username(uid);
+               if (name == NULL)
+                       name = strdup("????");
+               if (show_surname) {
+                       sur = user_surname(name);
+                       if (sur == NULL) sur = strdup(name);
+                       strncpy(r, sur, 3);
+                       i+=3;
+                       r[i] = 0;
+                       strcat(r, "---");
+                       r[i] = sep;
+                       i++;
+                       free(sur);
+               }
+               strncpy(r+i, name, 20);
+               r[20+i] = 0;
+               strcpy(ce->info, r);
+               skip_insert(uslist, ce);
+               free(name);
+       }
+}
+
+
+static void make_row(row r, cluster *ws, cluster *res, int type, int timeon, char *labname)
+{
+       int uid = -1;
+       int l1, l2;
+       char sep=0;
+       char *str = NULL;
+       char *wsname = NULL;
+    
+       if (type & USER)
+               r->type = USER;
+       else
+               r->type = type;
+    
+       switch(type)
+       {
+       case USER_ALLOC:
+               uid = res_user(res); sep = '-'; break;
+       case USER_ON:
+               uid = ws_user(ws); sep = '!'; break;
+       case USER_ONALLOC:
+               uid = ws_user(ws); sep = '+'; break;
+       case USER_CONFLICT:
+               uid = ws_user(ws); sep = '?'; break;
+       case USER_CLAIM:
+               uid = res_user(res); sep = '*'; break;
+       case USER_CLAIM2:
+               uid = res_user(res); sep = '*';
+               wsname = strndup("CLAIM(", 6+strlen(labname)+2);
+               strcat(wsname, labname);
+               strcat(wsname, ")");
+               break;
+       case AVAIL:
+               str = "AVAILABLE" ; break;
+       case INUSE:
+               str = "INUSE"; break;
+       case DOWN:
+               str = "DOWN"; break;
+       case DEAD:
+               str = "DEAD"; break;
+       }
+
+       if (str)
+               strcpy(r->text, str);
+       else
+       {
+               set_user(r->text, uid, sep);
+               if (timeon)
+               {
+                       time_t length = time(0)-ws_whenon(ws);
+                       char tm[4];
+                       length /= 60;
+                       if (length < 90)
+                               sprintf(tm, "%02dm", (int)length);
+                       else
+                               sprintf(tm, "%02dh", (int)(length/60));
+                       strncpy(r->text, tm, 3);
+               }
+       }
+
+       if (wsname == NULL)
+               wsname = get_mappingchar(cluster_id(ws), M_WORKSTATION);
+       if (wsname == NULL)
+               wsname = strdup("*unknown*");
+
+       l1 = strlen(r->text);
+       l2 = strlen(wsname);
+       strcpy(r->text+24-l2, wsname);
+       while (l1+l2 < 24)
+               r->text[l1++] = '.';
+       r->hoststart = 24 - l2;
+       free(wsname);
+}
+
+void make_refund(row r, int uid)
+{
+       int l1, l2;
+       r->type = USER;
+       set_user(r->text, uid, '-');
+    
+       l1 = strlen(r->text);
+       l2 = 8;
+       strcpy(r->text+24-l2, "REFUNDED");
+       while(l1+l2 < 24)
+               r->text[l1++] = '.';
+       r->hoststart = 24-l2;
+}
+
+struct column
+{
+       row rows;
+       int cnt;
+};
+struct column make_rows(labinfo lab, int types)
+{
+       row rv;
+       int i;
+       struct column r2;
+    
+       int max;
+       int rn;
+       time_t now = time(0);
+       max = lab->wscnt * 3;
+       if (alloc_numfailed(lab->alloc)>0)
+               max += alloc_numfailed(lab->alloc);
+       rv = (row)malloc(sizeof(struct row) * max);
+    
+       rn = 0;
+       if (types & USER_REFUND)
+               for (i=0; i < alloc_numfailed(lab->alloc); i++)
+                       make_refund(&rv[rn++], alloc_failid(lab->alloc,i));
+       for (i=0 ; i < lab->wscnt ; i++)
+       {
+               int t = row_type(lab->wslist[i].status,
+                                lab->wslist[i].alloc,
+                                now);
+               t &= types;
+               if (t)
+               {
+                       int t2;
+                       for (t2 = 1 ; t2 <= DEAD ; t2<<= 1)
+                               if (t&t2)
+                                       make_row(&rv[rn++],
+                                                lab->wslist[i].status,
+                                                lab->wslist[i].alloc,
+                                                t2,
+                                                types& TIME_ON, lab->labname);
+               }
+       }
+       r2.rows = rv;
+       r2.cnt= rn;
+       return r2;
+}
+
+
+void show_banner(char *title, int page, int pages, int pod, int width)
+{
+       time_t now;
+       int pad;
+       char pageof[20];
+       char reshead[60];
+       char *nows;
+    
+       time(&now);
+       sprintf(pageof, "Page %d of %d", page+1, pages);
+
+       pad = width-strlen(title);
+       move(0,0);
+       printw("%*s%s%*s%s\n",pad/2, "", title,
+              (pad+1)/2 - strlen(pageof), "", pageof);
+
+       sprintf(reshead, "Reservations for Period %d:%02d-%d:%02d",
+               pod/2, (pod%2)*30, (pod +1)/2, ((pod+1)%2)*30);
+
+       nows = ctime(&now);
+       pad = width - strlen(reshead) - (strlen(nows)-1);
+       if (pad<0) pad=0;
+       move(1,0);
+       printw("%s%*s%s", reshead, pad, "", nows);
+}
+
+void put_row(char *rw, int *r, int *c, int *p,
+       int rows, int cols, int pages, char *title, int pod)
+{
+       /* if page is full, display it and pause and clear
+        * add new row
+        */
+       if (rw == NULL ||
+           ( *r == 0 && *c == 0 && *p != 0))
+       {
+               show_banner(title, (*r==0 && *c == 0)?(*p-1):(*p), pages, pod, cols*(24+3)-3);
+               refresh();
+               if (rw)
+                       sleep(PAGE_SLEEP+cols*COL_SLEEP);
+               erase();
+       }
+       if (rw)
+       {
+               move(3+ *r, *c *(24+3));
+               printw("%s", rw);
+               if ((++*r)>= rows)
+               {
+                       *r = 0;
+                       if ((++*c) >= cols)
+                       {
+                               *c = 0;
+                               ++*p;
+                       }
+               }
+       }
+
+}
+
+/* returns columns on last page */
+int  show_pages(char *title, int width, int height, int pod, void *collist)
+{
+
+       int cols = (width+3)/(24+3);
+       int rows = height - 3;
+       int pages;
+       int total_rows;
+       int c,r,p;
+
+       struct column *cl;
+       int rw;
+
+       total_rows = -2;
+       for (cl=dl_next(collist) ; cl != collist ; cl =dl_next(cl))
+               total_rows += cl->cnt + 2;
+       pages = (total_rows-1) / (rows*cols) +1;
+
+       erase();
+       c=0; r=0; p=0;
+       for (cl=dl_next(collist) ; cl != collist ; cl=dl_next(cl))
+       {
+               for (rw = 0 ; rw < cl->cnt ; rw++)
+                       put_row(cl->rows[rw].text, &r, &c, &p,
+                               rows, cols, pages, title, pod);
+               if (dl_next(cl) != collist)
+               {
+                       if (r)
+                               put_row("                        ",  &r, &c, &p,
+                                       rows, cols, pages, title, pod);
+                       if (r)
+                               put_row("                        ",  &r, &c, &p,
+                                       rows, cols, pages, title, pod);
+               }
+       }
+       put_row(NULL,  &r, &c, &p,
+               rows, cols, pages, title, pod);
+       return c;
+}
+
+void sort_rows(struct column *c, int (*fn)())
+{
+       qsort(c->rows, c->cnt, sizeof(struct row), fn);
+}
+
+int show_labs(char *title, int width, int height, void *labs, int types, int byws)
+{
+       void *cols = dl_head();
+       labinfo l;
+       int pod, doy;
+       int last_cols;
+
+
+       for (l =lablist_next(labs) ; l != labs ; l = lablist_next(l))
+               if (l->alloc)
+               {
+                       struct column *c;
+                       time_t mt;
+                       c= dl_new(struct column);
+                       *c = make_rows(l, types);
+                       sort_rows(c, byws? host_cmp : user_cmp);
+                       dl_add(cols, c);
+                       mt = cluster_modtime(l->alloc);
+                       get_doypod(&mt, &doy, &pod);
+               }
+
+       last_cols = show_pages(title, width, height, pod, cols);
+
+       while (dl_next(cols) != cols)
+       {
+               struct column *c = dl_next(cols);
+               dl_del(c);
+               free(c->rows);
+               dl_free(c);
+       }
+       dl_free(cols);
+       return last_cols;
+}
+
+int size_term(int *width, int *height) 
+{
+       struct winsize win;
+       int w = *width;
+       int h = *height;
+       if (ioctl(1,TIOCGWINSZ,&win) == -1 || win.ws_col == 0)
+       {
+               *width = 80;
+               *height = 24;
+       }
+       else
+       {
+               *width = win.ws_col;
+               *height = win.ws_row;
+       }
+
+       if (w== *width && h == *height)
+               return 0;
+       else return 1;
+}
+
+char *Usage[] = {
+    "Usage: bookterm -[wfS] labs",
+    "  -w : sort by workstation",
+    "  -f : show all records",
+    "  -S : don't show surname and flag",
+    NULL };
+
+
+int main(int argc, char *argv[])
+{
+       int width, height;
+       char title[200];
+       int autosize = 1;
+       void *lablist = lablist_make();
+       char *msg;
+       int types = USER_ALLOC|USER_CLAIM2|USER_REFUND|AVAIL|DOWN|DEAD;
+       int byws = 0;
+       int opt;
+       time_t new_page = 0;
+       extern char *getenv();
+       char *term = getenv("TERM");
+       int noclear = 0;
+
+       while ((opt = getopt(argc, argv, "wfnS"))!= EOF)
+               switch(opt)
+               {
+               case 'n':
+                       noclear = 1;
+                       break;
+               case 'w':               /* sort by workstation */
+                       byws = 1;
+                       break;
+               case 'f':               /* full information */
+                       types = (USER|AVAIL|DOWN|DEAD|TIME_ON)&~USER_CLAIM2;
+                       break;
+               case 'S':
+                       show_surname = 0;
+                       break;
+               default:
+                       usage();
+                       exit(2);
+               }
+
+       title[0]=0;
+       for (; optind < argc ; optind++)
+       {
+               msg = add_lab(lablist, argv[optind]);
+               if (msg)
+               {
+                       fprintf(stderr, "bookterm: could not add lab %s: %s\n", argv[optind], msg);
+                       exit(1);
+               }
+               if (title[0] == 0)
+                       strcpy(title, argv[optind]);
+               else
+                       if (strlen(title)+1+strlen(argv[optind])+1 < sizeof(title))
+                               strcat(strcat(title, "/"), argv[optind]);
+
+       }
+       if (lablist_empty(lablist))
+       {
+               fprintf(stderr, "bookterm: no labs found\n");
+               exit(0);
+       }
+
+       size_term(&width, &height);
+       initscr();
+       setterm(term);
+       clear();
+       refresh();
+    
+       while(1)
+       {
+               time_t delay;
+               int last_cols;
+               load_labs(lablist);
+               delay = new_page-time(0);
+               if (delay>0) sleep(delay);
+               if (autosize)
+                       if (size_term(&width, &height))
+                       {
+                               initscr();
+                               setterm(term);
+                               clear();
+                               refresh();
+                       }
+               if (!noclear)
+               {
+                       clear();
+                       refresh();
+               }
+               last_cols=show_labs(title, width, height, lablist, types, byws);
+               new_page = time(0)+PAGE_SLEEP+COL_SLEEP*last_cols;
+       }
+
+}
diff --git a/lablist/lab.c b/lablist/lab.c
new file mode 100644 (file)
index 0000000..8ac5116
--- /dev/null
@@ -0,0 +1,237 @@
+
+#include       "lablist.h"
+/*#include <signal.h>*/
+#include <unistd.h>
+
+/*
+ * lab: print out status of some labs.
+ * possibly with heading, possibly only down or outofservice machines
+ */
+
+char *periodname[] = { "<null>", "FREE", "FULL", "CLOSED" };
+char *nstatus[] = { "<null>", "Up", "Down"};
+char *bstatus[] = { "<null>", "OutOfService", "Broken", "NotBookable",
+               "UnAllocated", "Allocated", "Tentative" };
+
+enum { N_UP, N_DOWN, N_OUT, N_ALL } class; /* which workstations to show */
+bool_t show_header;
+bool_t phost;
+
+void print_lab(labinfo li)
+{
+       int i;
+       if (show_header)
+       {
+               char *name, *host;
+               int xdrname = 1, xdrhost = 1;
+               name = get_mappingchar(li->labid, M_ATTRIBUTE);
+               if (name == NULL) {
+                       name= "<unknown>";
+                       xdrname = 0;
+               }
+               host = get_mappingchar(alloc_host(li->alloc), M_HOST);
+               if (host == NULL) {
+                       host = "<unknown>";
+                       xdrhost = 0;
+               }
+               if (li->alloc->data.data_len <3)
+                       printf("\nLab %s is %s: no allocations\n\n",
+                              name, periodname[alloc_status(li->alloc)]);
+               else
+               {
+                       time_t period = cluster_modtime(li->alloc);
+                       int doy, pod;
+                       char doys[10], pods[10];
+                       get_doypod(&period, &doy, &pod);
+                       printf("\nLab %s is %s, last allocation by %s for %s %s (at %s)\n\n",
+                              name, periodname[alloc_status(li->alloc)],
+                              host,
+                              doy2str(doys, doy), pod2str(pods, pod),
+                              myctime(alloc_when(li->alloc))
+                               );
+               }
+               if (xdrname)
+                       free(name);
+               if (xdrhost)
+                       free(host);
+       }
+
+       for (i=0 ; i < li->wscnt ; i++)
+       {
+               workstation w = &li->wslist[i];
+               if (
+                       ((class==N_UP) && ws_status(w->status) == NODEUP)
+                       || ((class==N_DOWN) && ws_status(w->status) == NODEDOWN)
+                       || ((class==N_OUT) && res_status(w->alloc) == NODEOUTOFSERVICE)
+                       || (class== N_ALL)
+                       )
+               {
+                       char *wsname;
+                       char *useron = NULL;
+                       char *userres = NULL;
+                       char c1 = ' ' , c2= ' ' ;
+
+                       wsname = get_mappingchar(w->wsid, M_WORKSTATION);
+                       if (wsname == NULL) wsname = strdup("<unknown>");
+                       if (ws_user(w->status) >= 0)
+                       {
+                               useron = find_username(ws_user(w->status));
+                               if (useron == NULL) useron = strdup("<who?>");
+                       }
+                       if (res_user(w->alloc) >= 0)
+                       {
+                               userres = find_username(res_user(w->alloc));
+                               if (userres == NULL) userres = strdup("<who?>");
+                               c1 = '{' ; c2 = '}';
+                               if (in_class(ws_user(w->status), res_user(w->alloc))==1)
+                                       c1='[', c2=']';
+                       }
+
+                       printf("%-12s:%-8s%12s: %-10s %c %-9s%c",
+                              wsname, nstatus[ws_status(w->status)],
+                              bstatus[res_status(w->alloc)],
+                              useron?useron:"",
+                              c1, userres?userres:"", c2);
+
+                       if (phost)
+                       {
+                               int h = ws_whereon(w->status);
+                               if (h >0)
+                               {
+                                       char *hn = get_mappingchar(h, M_HOST);
+                                       if (hn)
+                                               printf(" on %s", hn);
+                                       else
+                                               printf("on host-%d", h);
+                               }
+                       }
+                       else
+                       {
+                               /* print login time */
+                               if (ws_whenon(w->status))
+                                       printf(" since %s",
+                                              myctime(ws_whenon(w->status)));
+                       }
+                       printf("\n");
+                       if (class == N_OUT)
+                       {
+                               workstation_state ws;
+                               char k[20];
+                               memset(&ws, 0, sizeof(ws));
+                               if (db_read(key_int(k, C_WORKSTATIONS, w->wsid), &ws,
+                                           (xdrproc_t)xdr_workstation_state, CONFIG) == R_RESOK)
+                               {
+                                       if (ws.why)
+                                               printf(" %s (%s)\n", ws.why,
+                                                      myctime(ws.time));
+                                       xdr_free((xdrproc_t)xdr_workstation_state, (char*) &ws);
+                               }
+                       }
+                       if (useron) free(useron);
+                       if (userres) free(userres);
+                       free(wsname);
+               }
+       }
+}
+
+char *Usage[] = {
+    "Usage: lab [-udosASht] labname ...",
+    "  -u : only list UP workstations",
+    "  -d : only list DOWN workstations",
+    "  -o : only list out of service workstations, and give reason",
+    "  -s : don't print header on lab listing",
+    "  -t : show time of login (default)",
+    "  -h : show host that ws is logged on to instead of time",
+    "  -A : list all labs",
+    "  -S : list all bookable labs",
+    "  -R : repeat listing ever 30 seconds",
+    "  labname: the name of any lab of group of labs",
+    NULL
+};
+
+int main(int argc, char *argv[])
+{
+       extern int optind;
+       extern char *optarg;
+       char *msg;
+       int opt;
+       void *lablist = lablist_make();
+       labinfo li;
+       int repeat = FALSE;
+
+       /* quit on receipt of sigusr1 - to help with memory debugging in loops */
+/*
+  struct sigaction sa;
+  sigset_t ss;
+  sa.sa_handler = exit;
+  sa.sa_flags = 0;
+  sigemptyset(&ss);
+  sigaddset(&ss, SIGUSR1);
+  sa.sa_mask = ss;
+  sigaction(SIGUSR1, &sa, NULL);
+*/
+       class = N_ALL;
+       show_header = TRUE;
+       phost = FALSE;
+       while ((opt = getopt(argc, argv, "udosASRht"))!= EOF)
+               switch(opt)
+               {
+               case 'h': phost = TRUE; break;
+               case 't': phost = FALSE; break;
+               case 'u': class = N_UP ; break ;
+               case 'd': class = N_DOWN ; break ;
+               case 'o': class = N_OUT ; break ;
+               case 's': show_header = FALSE ; break ;
+               case 'R': repeat = TRUE; break;
+               case 'A': 
+                       msg = add_lab(lablist, NULL);
+                       if (msg)
+                       {
+                               fprintf(stderr, "lab: could not find all labs: %s\n", msg);
+                               exit(1);
+                       }
+                       break;
+               case 'S':
+                       msg = add_lab(lablist, "bookable");
+                       if (msg)
+                       {
+                               fprintf(stderr, "lab: could not find bookable labs: %s\n", msg);
+                               exit(1);
+                       }
+                       break;
+               default:
+                       usage();
+                       exit(1);
+               }
+       for (; optind < argc ; optind++)
+       {
+               msg = add_lab(lablist, argv[optind]);
+               if (msg)
+               {
+                       fprintf(stderr, "lab: could not add lab %s: %s\n", argv[optind], msg);
+                       exit(1);
+               }
+       }
+       if (lablist_empty(lablist))
+       {
+               fprintf(stderr, "lab: no labs given\n");
+               exit(0);
+       }
+
+
+       do {
+               load_labs(lablist);
+
+               for (li = lablist_next(lablist) ; li != lablist ; li = lablist_next(li))
+                       if (li->alloc)
+                               print_lab(li);
+                       else
+                               printf("Cannot get table for %s\n", li->labname);
+               if (repeat) {
+                       printf("---------------------\n");
+                       fflush(stdout);
+                       sleep(30);
+               }
+       } while (repeat);
+       exit(0);
+}
diff --git a/lablist/lablist.c b/lablist/lablist.c
new file mode 100644 (file)
index 0000000..aa909dd
--- /dev/null
@@ -0,0 +1,128 @@
+
+#include       <time.h>
+#include       "lablist.h"
+#include       "../lib/skip.h"
+
+
+static int hg_cmp(hostg a, hostg b, int *cp)
+{
+    int c;
+    if (b) c = b->hgid;
+    else c = *cp;
+    return a->hgid - c;
+}
+static void hg_free(hostg a)
+{
+    clnt_destroy(a->handle);
+    xdr_free((xdrproc_t)xdr_hostgroup_info, (char*) &a->info);
+    xdr_free((xdrproc_t)xdr_table, (char*) &a->tab);
+}
+
+void *hgl = NULL;
+hostg find_hg(int hgid)
+{
+    hostg *hgp, hg;
+    char k[20];
+
+    if (hgl == NULL)
+       hgl = skip_new(hg_cmp, hg_free, NULL);
+    hgp = skip_search(hgl, &hgid);
+    if (hgp)
+       return *hgp;
+    hg = (hostg)malloc(sizeof(struct hostg));
+    memset(hg, 0, sizeof(struct hostg));
+    hg->hgid = hgid;
+    db_read(key_int(k, C_HOSTGROUPS, hgid), &hg->info, (xdrproc_t)xdr_hostgroup_info, CONFIG);
+    skip_insert(hgl, hg);
+    return hg;
+}
+
+
+char *real_add_lab(void *list, int lab)
+{
+    labinfo li;
+    char k[20];
+
+    for (li = dl_next(list); li != list ; li = dl_next(li))
+       if (li->labid == lab)
+           return NULL;
+    
+    li = dl_new(struct labinfo);
+    memset(li, 0, sizeof(struct labinfo));
+    li->labid = lab;
+    li->labname = get_mappingchar(lab, M_ATTRIBUTE);
+    if (li->labname == NULL)
+    {
+       dl_free(li);
+       return "Cannot get labname";
+    }
+    switch (db_read(key_int(k, C_LABS, lab), &li->info, (xdrproc_t)xdr_hostinfo, CONFIG))
+    {
+    case R_RESOK:
+       break;
+    case R_NOMATCH:
+       /* lab not setup yet, just ignore it */
+       return NULL;
+    default:
+       free(li->labname);
+       dl_free(li);
+       return "Cannot get lab info";
+    }
+    li->hg = find_hg(li->info.clump);
+    dl_add(list, li);
+    return NULL;
+}
+
+char *add_lab(void *list, char *labname)
+{
+    /* labname is actually an attribute name
+     * any lab that has this attribute set now will be added
+     * (if not already in list)
+     * if labname is NULL, then all labs are added
+     */
+    int attr;
+
+    if (get_attr_types() == 0)
+       return "Cannot contact database";
+
+    if (labname == NULL)
+       attr = -1;
+    else
+    {
+       attr = get_mappingint(labname, M_ATTRIBUTE);
+       if (attr < 0)
+           return "unknown lab";
+    }
+    if (attr >=0 && query_bit(&attr_types.lab, attr))
+       return real_add_lab(list, attr);
+    else
+    {
+       /* must check each lab to see if it has this bit */
+       time_t now;
+       int doy, pod;
+       int lab;
+       
+       now = time(0);
+       get_doypod(&now, &doy, &pod);
+       for (lab =0 ; lab < BITINDMAX ; lab++)
+           if (query_bit(&attr_types.lab, lab))
+           {
+               char *rv = NULL;
+               if (attr == -1)
+                   rv = real_add_lab(list, lab);
+               else
+               {
+                   /* this is a lab bit, compute implications */
+                   item d = new_desc();
+                   set_a_bit(&d, lab);
+                   process_exclusions(pod, doy, &d);
+                   if (query_bit(&d, attr))
+                       rv = real_add_lab(list, lab);
+                   free(d.item_val);
+               }
+               if (rv)
+                   return rv;
+           }
+       return NULL;
+    }
+}
diff --git a/lablist/lablist.h b/lablist/lablist.h
new file mode 100644 (file)
index 0000000..7dc6407
--- /dev/null
@@ -0,0 +1,55 @@
+
+
+/*
+ * interface to the lab listing programs "lab" and "skyterm"
+ *
+ * will provide a list of ws or hosts for each lab in a list
+ *
+ * make_lablist() -> empty list
+ * add_lab(lablist, name) -> success if lab is know, this supports lab groups
+ *                             through attributes common to a set of labs
+ * load_labs(lablist)
+ *  fills in info, freeing old info
+ *
+ * the info in each list is an array of workstations - see below
+ * and a labinfo structure for the lab
+ */
+
+#include       "../db_client/db_client.h"
+#include       "../lib/dlink.h"
+
+typedef struct hostg
+{
+    int                hgid;
+    CLIENT     *handle;
+    hostgroup_info info;
+    table      tab;
+    time_t     tabtime;
+} *hostg;
+
+typedef struct workstation
+{
+    entityid   wsid;
+    cluster    *status;
+    cluster    *alloc;
+} *workstation;
+
+typedef struct labinfo
+{
+    entityid   labid;
+    char       *labname;
+    hostinfo   info;
+    hostg      hg;
+
+    cluster    *alloc;
+
+    workstation        wslist;
+    int                wscnt;
+} *labinfo;
+
+#define        lablist_make() (dl_head())
+#define        lablist_empty(l) (l == dl_next(l))
+#define lablist_next(l) ((labinfo)dl_next(l))
+
+char *add_lab(void *, char *);
+void load_labs(void *);
diff --git a/lablist/labmon.c b/lablist/labmon.c
new file mode 100644 (file)
index 0000000..abd8d99
--- /dev/null
@@ -0,0 +1,71 @@
+
+/* labmon - monitor all labs printing messages which can be sent by pager.
+ *   with '-v' produce status messages, not just errors
+ */
+
+#include       "lablist.h"
+#include       <time.h>
+
+int main(int argc, char *argv[])
+{
+       void *lablist = lablist_make();
+       char *msg;
+       labinfo li;
+       int verbose=0;
+       char strange_host[1024];
+       strcpy(strange_host, "");
+
+       if (argc>1) verbose=1;
+    
+       msg = add_lab(lablist, NULL);
+
+       if (msg)
+       {
+               printf("labmon: could not find all labs: %s\n", msg);
+               exit(1);
+       }
+       load_labs(lablist);
+
+       for (li = lablist_next(lablist); li != lablist ; li = lablist_next(li))
+               if (li->alloc)
+               {
+                       /* checks:
+                        *  if allocation date is more than 30 minutes old
+                        *  if any host is up, but not mentioned for > 10 minutes
+                        */
+                       if (li->alloc->data.data_len < 3)
+                               printf("labmon: no allocation in %s\n", li->labname);
+                       else if (cluster_modtime(li->alloc) +30*60 < time(0))
+                               printf("labmon: no allocation in %s for %d minutes\n", li->labname, (int)(time(0)-cluster_modtime(li->alloc))/60);
+                       else
+                       {
+                               table *t = &li->hg->tab;
+                               int r;
+                               if (verbose)
+                                       printf("labmon: lab %s allocate %d minutes ago - OK\n", li->labname,  (int)(time(0)-cluster_modtime(li->alloc))/60);
+                               for (r=0; r <t->table_len ; r++)
+                                       if (Cluster_type(t,r) == HOSTCLUSTER)
+                                       {
+                                               if (Host_status(t,r) == NODEUP && Cluster_modtime(t,r)+5*60 < time(0))
+                                               {
+                                                       strcat(strcat(strange_host, " "), li->labname);
+                                                       break;
+                                               }
+                                               else if (verbose)
+                                                       printf("labmon: host %s %s for %d minutes - OK\n",
+                                                              get_mappingchar(Cluster_id(t,r), M_HOST),
+                                                              (Host_status(t,r) == NODEUP)?"Up":"Down",
+                                                              (int)(time(0)-Cluster_modtime(t,r))/60);
+                                       }
+                       }
+               }
+               else
+               {
+                       printf("labmon: cannot get table for %s\n", li->labname);
+               }
+       if (strange_host[0])
+               printf("labmon: out-of-date hosts in %s\n", strange_host);
+       exit(0);
+}
+
+       
diff --git a/lablist/loadlab.c b/lablist/loadlab.c
new file mode 100644 (file)
index 0000000..5574e7a
--- /dev/null
@@ -0,0 +1,80 @@
+#include       <time.h>
+#include       "lablist.h"
+    
+int load_table(hostg hg)
+{
+    if (hg->tabtime)
+       xdr_free((xdrproc_t)xdr_table, (char*) &hg->tab);
+    hg->tabtime = 0;
+
+    if (hg->handle == NULL
+       || collect_tab(&hg->tab, hg->handle)== 0)
+    {
+       if (hg->handle) clnt_destroy(hg->handle);
+       hg->handle = up_host(&hg->info, TRUE);
+       if (hg->handle == NULL
+           || collect_tab(&hg->tab, hg->handle)== 0)
+           return -1;
+    }
+    hg->tabtime = time(0);
+    return 0;
+}
+
+
+
+int load_lab(labinfo li, time_t start)
+{
+    table *tab;
+    int labstart;
+    int c;
+    int i;
+    if (li->hg->tabtime < start)
+    {
+       li->alloc = NULL;
+       if (li->wslist) free(li->wslist);
+       li->wslist = NULL;
+       if (load_table(li->hg) < 0)
+           return -1;
+    }
+    
+    tab = &li->hg->tab;
+
+    labstart = -1;
+    for (c=0 ; labstart == -1 && c<tab->table_len ; c++)
+       if (Cluster_type(tab, c)== ALLOCCLUSTER
+           && Cluster_id(tab, c) == li->labid)
+           labstart = c+1;
+    if (labstart == -1)
+       return -1;
+
+    li->alloc = &tab->table_val[labstart-1];
+
+    if (li->wslist == NULL)
+    {
+       li->wscnt = li->info.workstations.entitylist_len;
+       li->wslist = (workstation)malloc(sizeof(struct workstation)*li->wscnt);
+    }
+    for (i=0 ; i<li->wscnt ; i++)
+    {
+       int wsid = Cluster_id(tab,labstart+2*i);
+       workstation ws = &li->wslist[i];
+       assert(Cluster_type(tab,labstart+2*i) == WSSTATUSCLUSTER, "not a statuscluter");
+       assert(Cluster_type(tab,labstart+2*i+1) == WSRESCLUSTER, "not a rescluster");
+       assert(Cluster_id(tab,labstart+2*i+1) == wsid, "wrong wsid");
+
+       ws->wsid = wsid;
+       ws->status = & tab->table_val[labstart+2*i];
+       ws->alloc = & tab->table_val[labstart+2*i+1];
+    }
+    return 0;
+}
+
+void load_labs(void *lablist)
+{
+    labinfo li;
+    time_t start = time(0);
+    
+    for (li = dl_next(lablist) ; li != lablist ; li = dl_next(li))
+       load_lab(li, start);
+}
+
diff --git a/lib/Makefile b/lib/Makefile
new file mode 100644 (file)
index 0000000..d9531b9
--- /dev/null
@@ -0,0 +1,6 @@
+
+lib +=  lib.a
+
+obj-lib.a :=  debug.o dlink.o intarray.o skip.o timeutils.o utils.o usage.o pmap_getport.o strdup.o strccmp.o innetgr.o
+
+include $(S)$(D)../MakeRules
diff --git a/lib/debug.c b/lib/debug.c
new file mode 100644 (file)
index 0000000..c1a225c
--- /dev/null
@@ -0,0 +1,59 @@
+/* a module containing some debug routines */
+#include       <sys/file.h>
+#include       <stdio.h>
+#ifdef sun
+#include       <sys/fcntl.h>
+#endif
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       "misc.h"
+
+#define DebugFile "/var/book/book_debug"
+static int debug_level =0 ;
+
+void set_bookdebug_level(void)
+{
+    int fd;
+    char buf[20];
+    char * debug;
+    extern char * getenv();
+    
+    /* read the env variable BOOK_DEBUG and return the  debug_level */
+    /* accordingly.  If This var is not set then look at */
+    /* /var/book/book_debug for a number that is the book debug level */
+    debug = getenv("BOOK_DEBUG");
+
+    if (debug != NULL) {
+       debug_level = atoi(debug);
+/*     printf("DEBUG LEVEL = %d\n",debug_level);*/
+       return;
+    }
+
+    /* try the file */
+    fd = open (DebugFile, O_RDONLY);
+    if (fd == -1)
+    {
+       debug_level = 0;
+       return;
+    }
+    
+    if (read (fd, buf, 20) == 0)
+    {
+       debug_level = 0;
+       close(fd);
+       return;
+    }
+    close(fd);
+    
+    debug_level = atoi(buf);
+    return;
+    
+}
+
+void Debug(char * message, int level)
+{
+    if (level < debug_level) {
+/*     fprintf(stderr,"%s", message);*/
+       shout(message);
+    }
+}
diff --git a/lib/dlink.c b/lib/dlink.c
new file mode 100644 (file)
index 0000000..606e5b5
--- /dev/null
@@ -0,0 +1,70 @@
+
+/* doubly linked lists */
+
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <string.h>
+#include       "dlink.h"
+
+
+void *dl_head(void)
+{
+    void *h;
+    h = dl_alloc(0);
+    dl_next(h) = h;
+    dl_prev(h) = h;
+    return h;
+}
+
+void dl_free(void *v)
+{
+    struct __dl_head *vv = v;
+    free(vv-1);
+}
+
+
+void dl_insert(void *head, void *val)
+{
+    dl_next(val) = dl_next(head);
+    dl_prev(val) = head;
+    dl_next(dl_prev(val)) = val;
+    dl_prev(dl_next(val)) = val;
+}
+
+void dl_add(void *head, void *val)
+{
+    dl_prev(val) = dl_prev(head);
+    dl_next(val) = head;
+    dl_next(dl_prev(val)) = val;
+    dl_prev(dl_next(val)) = val;
+}
+
+void dl_del(void *val)
+{
+    if (dl_prev(val) == 0 || dl_next(val) == 0)
+       return;
+    dl_prev(dl_next(val)) = dl_prev(val);
+    dl_next(dl_prev(val)) = dl_next(val);
+    dl_prev(val) = dl_next(val) = 0;
+}
+
+char *dl_strndup(char *s, int l)
+{
+    char *n;
+    if (s == NULL)
+       return NULL;
+    n = dl_newv(char, l+1);
+    if (n == NULL)
+       return NULL;
+    else
+    {
+       strncpy(n, s, l);
+       n[l] = 0;
+       return n;
+    }
+}
+
+char *dl_strdup(char *s)
+{
+    return dl_strndup(s, strlen(s));
+}
diff --git a/lib/dlink.h b/lib/dlink.h
new file mode 100644 (file)
index 0000000..996b0d8
--- /dev/null
@@ -0,0 +1,23 @@
+
+/* doubley linked lists */
+
+struct __dl_head
+{
+    struct __dl_head * dh_prev;
+    struct __dl_head * dh_next;
+};
+
+#define        dl_alloc(size)  ((void*)(((char*)calloc(1,(size)+sizeof(struct __dl_head)))+sizeof(struct __dl_head)))
+#define        dl_new(t)       ((t*)dl_alloc(sizeof(t)))
+#define        dl_newv(t,n)    ((t*)dl_alloc(sizeof(t)*n))
+
+#define dl_next(p) *((void**)&(((struct __dl_head*)(p))[-1].dh_next))
+#define dl_prev(p) *((void**)&(((struct __dl_head*)(p))[-1].dh_prev))
+
+void *dl_head();
+char *dl_strdup(char *);
+char *dl_strndup(char *, int);
+void dl_insert(void*, void*);
+void dl_add(void*, void*);
+void dl_del(void*);
+void dl_free(void*);
diff --git a/lib/innetgr.c b/lib/innetgr.c
new file mode 100644 (file)
index 0000000..34b6ef3
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Use a local magic map to do innetgr lookups.
+ * The netgroup
+ *    innetgr:CLASS,HOST,USER,DOMAIN
+ * will contain either (host,user,domain) or (-,-,-)
+ * - Neil Brown 24may2006
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <malloc.h>
+
+int innetgr(const char *name, const char *host, const char *user, const char *ypdomain)
+{
+       char *group=NULL;
+       asprintf(&group, "innetgr:%s,%s,%s,%s",
+                name, host?host:"", user?user:"", ypdomain?ypdomain:"");
+       if (setnetgrent(group)) {
+               char *u, *h, *d;
+               if (getnetgrent(&h,&u,&d)) {
+                       /* Successful lookup */
+                       int rv = 0;
+                       if ((h && strcmp(h,"-")) || (u && strcmp(u,"-")))
+                               rv = 1;
+                       endnetgrent();
+                       free(group);
+                       return rv;
+               }
+       }
+       free(group);
+       /* Failed :-( */
+       return 0;
+}
+
diff --git a/lib/intarray.c b/lib/intarray.c
new file mode 100644 (file)
index 0000000..375efff
--- /dev/null
@@ -0,0 +1,49 @@
+
+#include       <sys/types.h>
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       "misc.h"
+
+
+/* ADD_INTARRAY: adds an integer to a array */
+int add_intarray(int_array * array, int newval)
+{
+    int i;
+    /* should check existence */
+    for (i=0;i < array->array_len;i++)
+       if (array->array_val[i] == newval)
+           return 0;
+    
+    array->array_len++;
+    if (array->array_val == (int*)0)
+       array->array_val = (int *)malloc(array->array_len * sizeof (int));
+    else
+       array->array_val = (int *)realloc(array->array_val, array->array_len *
+                                         sizeof (int)); 
+    array->array_val[array->array_len -1] = newval;
+    return 1;
+}
+
+int del_intarray(int_array * array, int oldval)
+{
+    int i;
+    int found = 0;
+    for (i = 0; i < array->array_len;i++) {
+       found = found || (array->array_val[i] == oldval);
+       if (found)
+           array->array_val[i] = array->array_val[i+1];
+    }
+    if (found)
+       array->array_len --;
+
+    return(found);
+}
+
+#if 0
+void print_intarray(int_array * ar)
+{
+    int i;
+    for (i=0; i < ar->array_len;i++)
+       printf("VAl %d = %d\n",i, ar->array_val[i]);
+}
+#endif
diff --git a/lib/misc.h b/lib/misc.h
new file mode 100644 (file)
index 0000000..a6fd387
--- /dev/null
@@ -0,0 +1,62 @@
+
+/* from utils.c */
+extern void shout(char * message);
+extern void bailout(char * message, int n);
+extern int assert(int condition, char * message);
+extern char * get_myhostname(void);
+#ifndef linux_why_not
+extern void *memdup(void *m, int l);
+#endif
+char * make_pathname(char * name);
+char * make_tmp_pathname(char * name);
+
+extern char BookBase[];
+
+/* from debug.c */
+void set_bookdebug_level(void);
+extern void Debug(char *message, int level);
+
+
+/* from strccmp.c */
+
+int strnccmp(char *a, char *b, int n);
+int strccmp(char *a, char *b);
+
+/* from timeutils.c */
+
+int dayofweek(char *str);
+int doy_2_dow(int doy);
+int pod_2_hrs(int pod);
+int podofday(char *str);
+int dayofyear(char *str);
+char *doy2str(char *rv, int doy);
+char *pod2str(char *rv, int pod);
+int minuteofweek(char *str);
+void get_doypod(time_t *time_in_secs, int *doy, int *pod);
+char *myctime(time_t time_in_secs);
+time_t unmyctime(char *tim);
+
+/* from strdup.c */
+#ifndef linux
+char *strdup(const char *s);
+char *strndup(const char *s, size_t a);
+#endif
+
+/* from usage.c */
+void usage(void);
+extern char *Usage[];
+
+/* from intarray.c */
+typedef struct int_array
+{
+    int array_len;
+    int * array_val;
+} int_array;
+int add_intarray(int_array * array, int newval);
+int del_intarray(int_array * array, int oldval);
+
+
+/* from pmap_getport.c */
+
+#include       <sys/time.h>
+void pmap_settimeouts(struct timeval intertry, struct timeval percall);
diff --git a/lib/pmap_getport.c b/lib/pmap_getport.c
new file mode 100644 (file)
index 0000000..37c6159
--- /dev/null
@@ -0,0 +1,100 @@
+/* @(#)pmap_getport.c  2.2 88/08/01 4.0 RPCSRC */
+/*
+ * Sun RPC is a product of Sun Microsystems, Inc. and is provided for
+ * unrestricted use provided that this legend is included on all tape
+ * media and as a part of the software program in whole or part.  Users
+ * may copy or modify Sun RPC without charge, but are not authorized
+ * to license or distribute it to anyone else except as part of a product or
+ * program developed by the user.
+ * 
+ * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
+ * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
+ * 
+ * Sun RPC is provided with no support and without any obligation on the
+ * part of Sun Microsystems, Inc. to assist in its use, correction,
+ * modification or enhancement.
+ * 
+ * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
+ * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
+ * OR ANY PART THEREOF.
+ * 
+ * In no event will Sun Microsystems, Inc. be liable for any lost revenue
+ * or profits or other special, indirect and consequential damages, even if
+ * Sun has been advised of the possibility of such damages.
+ * 
+ * Sun Microsystems, Inc.
+ * 2550 Garcia Avenue
+ * Mountain View, California  94043
+ */
+#if !defined(lint) && defined(SCCSIDS)
+static char sccsid[] = "@(#)pmap_getport.c 1.9 87/08/11 Copyr 1984 Sun Micro";
+#endif
+
+/*
+ * pmap_getport.c
+ * Client interface to pmap rpc service.
+ *
+ * Copyright (C) 1984, Sun Microsystems, Inc.
+ */
+#include <unistd.h>
+#include <rpc/rpc.h>
+#include <rpc/pmap_prot.h>
+#include <rpc/pmap_clnt.h>
+#include <sys/socket.h>
+#include <net/if.h>
+
+static struct timeval timeout = { 5, 0 };
+static struct timeval tottimeout = { 60, 0 };
+
+/* changed by adam unsw 29/04/92 to make booking system work better */
+void
+    pmap_settimeouts(struct timeval intertry, struct timeval percall)
+{
+    timeout.tv_usec = intertry.tv_usec;
+    timeout.tv_sec = intertry.tv_sec;
+    tottimeout.tv_usec = percall.tv_usec;
+    tottimeout.tv_sec = percall.tv_sec;
+}
+
+
+/*
+ * Find the mapped port for program,version.
+ * Calls the pmap service remotely to do the lookup.
+ * Returns 0 if no map exists.
+ */
+u_short
+pmap_getport(address, program, version, protocol)
+       struct sockaddr_in *address;
+       u_long program;
+       u_long version;
+       u_int protocol;
+{
+       u_short port = 0;
+       int socket = -1;
+       register CLIENT *client;
+       struct pmap parms;
+
+       address->sin_port = htons(PMAPPORT);
+       client = clntudp_bufcreate(address, PMAPPROG,
+                                  PMAPVERS, timeout, &socket, RPCSMALLMSGSIZE, RPCSMALLMSGSIZE);
+       if (client != (CLIENT *)NULL) {
+               parms.pm_prog = program;
+               parms.pm_vers = version;
+               parms.pm_prot = protocol;
+               parms.pm_port = 0;  /* not needed or used */
+               if (CLNT_CALL(client, PMAPPROC_GETPORT,
+                             (xdrproc_t)xdr_pmap, (caddr_t)&parms,
+                             (xdrproc_t)xdr_u_short, (caddr_t)&port,
+                             tottimeout) != RPC_SUCCESS){
+                       rpc_createerr.cf_stat = RPC_PMAPFAILURE;
+                       clnt_geterr(client, &rpc_createerr.cf_error);
+               } else if (port == 0) {
+                       rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED;
+               }
+               CLNT_DESTROY(client);
+       }
+       (void)close(socket);
+       address->sin_port = 0;
+       return (port);
+}
diff --git a/lib/skip.c b/lib/skip.c
new file mode 100644 (file)
index 0000000..4817c84
--- /dev/null
@@ -0,0 +1,255 @@
+
+/* Skiplists.
+   Provide three functions to skip_new:
+     cmp(e1, e2, k)
+     This should compare e1 with e2 or (if null) k (a key)
+   free should free an entry
+   errfn should do something with an error message
+   
+   skip_new(cmp,free,errfn) -> list
+   skip_insert(list,entry) -> bool
+   skip_delete(list,key) -> bool
+   skip_search(list,key) -> *entry
+   skip_first(list) -> *entry
+   skip_next(*entry) -> *entry
+
+   */
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <string.h>
+
+#define false 0
+#define true 1
+typedef char boolean;
+#define BitsInRandom 31
+
+#define MaxNumberOfLevels 16
+#define MaxLevel (MaxNumberOfLevels-1)
+#define newNodeOfLevel(l) (node)malloc(sizeof(struct node)+(l)*sizeof(node))
+
+typedef void *keyType;
+typedef void *valueType;
+
+
+typedef struct node
+{
+    valueType value;           /* must be first for skip_next to work */
+    struct node *forward[1];   /* variable sized array of forward pointers */
+} *node;
+
+typedef struct list
+{
+    int level; /* Maximum level of the list
+                  (1 more than the number of levels in the list) */
+    node       header; /* pointer to head of list */
+    int        (*cmpfn)();     /* compares two values or a key an a value */
+    void (*freefn)();  /* free a value */
+    void (*errfn)();   /* when malloc error occurs */
+} *list;
+
+static void default_errfn(char *msg)
+{
+    write(2, msg, strlen(msg));
+    write(2, "\n", 2);
+    abort();
+}
+
+static int randomsLeft = 0;
+static int randomBits;
+
+list skip_new(cmpfn, freefn, errfn)
+int (*cmpfn)();
+void (*freefn)();
+void (*errfn)();
+{
+    list l;
+    int i;
+
+    if (errfn == NULL)
+       errfn = default_errfn;
+    l = (list)malloc(sizeof(struct list));
+    if (l == NULL)
+    {
+       (*errfn)("Malloc failed in skip_new");
+       return NULL;
+    }
+    l->level = 0;
+    l->header = newNodeOfLevel(MaxNumberOfLevels);
+    if (l->header == NULL)
+    {
+       (*errfn)("Malloc failed in skip_new");
+       return NULL;
+    }
+    for (i=0; i<MaxNumberOfLevels; i++)
+       l->header->forward[i] = NULL;
+    l->header->value = NULL;
+    l->cmpfn = cmpfn;
+    l->freefn = freefn;
+    if (errfn)
+       l->errfn = errfn;
+    else
+       l->errfn = default_errfn;
+    return l;
+}
+
+void skip_free(l)
+list l;
+{
+    register node p;
+    p = l->header;
+    while (p != NULL)
+    {
+       register node q;
+       q = p->forward[0];
+       if (p != l->header) /* header has no meaningful value */
+           (*l->freefn)(p->value);
+       free(p);
+       p = q;
+    }
+    free(l);
+}
+
+static int randomLevel()
+{
+    register int level = 0;
+    register int b;
+    do {
+       if (randomsLeft == 0) {
+#ifdef POSIX
+           randomBits = lrand48();
+#else
+           randomBits = random();
+#endif
+           randomsLeft = BitsInRandom/2;
+        }
+       b = randomBits&3;
+       randomBits>>=2;
+       randomsLeft--;
+       if (!b) level++;
+    } while (!b);
+    return(level>MaxLevel ? MaxLevel : level);
+}
+
+boolean skip_insert(l, value)
+list l;
+valueType value;
+{
+    int k;
+    node update[MaxNumberOfLevels];
+    node p,q;
+    int cm;
+
+    p = l->header;
+    for (k=l->level ; k>=0 ; k--)
+    {
+       cm = 1;
+       while ( p->forward[k] != NULL
+              && (cm=(*l->cmpfn)(p->forward[k]->value, value, 0))<0)
+           p = p->forward[k];
+       update[k] = p;
+    }
+
+    if (cm == 0)
+       return false;
+
+    k = randomLevel();
+    if (k> l->level)
+    {
+       k = ++l->level;
+       update[k] = l->header;
+    }
+    q = newNodeOfLevel(k);
+    if (q == NULL)
+    {
+       (*l->errfn)("Malloc failed in skip_insert");
+       return false;
+    }
+    q->value = value;
+    for ( ; k>=0 ; k--)
+    {
+       p = update[k];
+       q->forward[k] = p->forward[k];
+       p->forward[k] = q;
+    }
+    return true;
+}
+
+boolean skip_delete(l, key)
+list l;
+keyType key;
+{
+    int k, m;
+    node update[MaxNumberOfLevels];
+    node p,q;
+    int cm;
+
+    p = l->header;
+
+    for (k=l->level ; k>=0 ; k--)
+    {
+       cm = 1;
+       while ( p->forward[k] != NULL
+              && (cm=(*l->cmpfn)(p->forward[k]->value, NULL, key))<0)
+           p = p->forward[k];
+       update[k] = p;
+    }
+
+    if (cm == 0)
+    {
+       q = update[0]->forward[0];
+       m=l->level;
+       for (k=0; k<=m && (p=update[k])->forward[k] == q ; k++)
+           p->forward[k] = q->forward[k];
+       if (l->freefn)
+           (*l->freefn)(q->value);
+       free(q);
+       while (l->header->forward[m] == NULL && m > 0)
+           m--;
+       l->level = m;
+       return true;
+    }
+    else
+       return false;
+}
+
+valueType *skip_search(l, key)
+list l;
+keyType key;
+{
+    int k;
+    node p;
+    int cm;
+
+    p = l->header;
+    for (k=l->level ; k>=0 ; k--)
+    {
+       cm = 1;
+       while ( p->forward[k] != NULL
+              && (cm=(*l->cmpfn)(p->forward[k]->value, NULL, key))<0)
+           p = p->forward[k];
+    }
+    if (cm != 0)
+       return NULL;
+    return &p->forward[0]->value;
+}
+
+valueType *skip_first(l)
+list l;
+{
+    node p;
+
+    p = l->header;
+    if (p->forward[0] == NULL)
+       return NULL;
+    return & p->forward[0]->value;
+}
+
+valueType *skip_next(c)
+valueType *c;
+{
+    node p;
+    p = (node)c;
+    if (p->forward[0] == NULL)
+       return NULL;
+    return & p->forward[0]->value;
+}
diff --git a/lib/skip.h b/lib/skip.h
new file mode 100644 (file)
index 0000000..afad69b
--- /dev/null
@@ -0,0 +1,19 @@
+
+/* include for skiplists */
+#define _PROTO_
+#ifdef _PROTO_
+void *skip_new(int cmpfn(), void freefn(), void errfn());
+char skip_insert(void *list, void *entry);
+char skip_delete(void *list, void *key);
+void *skip_search(void *list, void *key);
+void *skip_first(void *list);
+void *skip_next(void *last);
+void skip_free(void *);
+#else
+void *skip_new();
+char skip_insert();
+char skip_delete();
+void *skip_search();
+void *skip_first();
+void *skip_next();
+#endif
diff --git a/lib/strccmp.c b/lib/strccmp.c
new file mode 100644 (file)
index 0000000..7f99470
--- /dev/null
@@ -0,0 +1,29 @@
+
+#include       <sys/types.h>
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <string.h>
+#include       "misc.h"
+
+int strnccmp(char *a, char *b, int n)
+{
+    char ac, bc;
+
+    while(n--)
+    {
+       ac = *a++;
+       if (ac>='a' && ac <= 'z') ac -= 'a'-'A';
+       bc = *b++;
+       if (bc>='a' && bc <= 'z') bc -= 'a'-'A';
+       if (ac == 0 || ac != bc) break;
+    }
+    if (ac<bc) return -1;
+    if (ac > bc) return 1;
+    return 0;
+}
+
+int strccmp(char *a, char *b)
+{
+    return strnccmp(a,b,strlen(a)+1);
+}
+
diff --git a/lib/strdup.c b/lib/strdup.c
new file mode 100644 (file)
index 0000000..d9e6b45
--- /dev/null
@@ -0,0 +1,27 @@
+#define NULL 0
+
+#include       <sys/types.h>
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <string.h>
+#include       "misc.h"
+
+
+char *strdup(const char *s)
+{
+    if (s==NULL)
+       return NULL;
+    return (char *)strcpy((char*)malloc(strlen(s)+1), s);
+}
+
+char *strndup(const char *s, size_t a)
+{
+    char *r;
+    if (s == NULL)
+       return NULL;
+
+    r = (char*)malloc(a+1);
+    strncpy(r, s, a);
+    r[a] = 0;
+    return r;
+}
diff --git a/lib/timeutils.c b/lib/timeutils.c
new file mode 100644 (file)
index 0000000..787ef69
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+ * dayofyear is 2 digits and 3 letters, e.g. 13mar
+ * minuteofweek is 2letters and 4 digits, e.g. Mo0800 Mo1800
+ * attribute is a string
+ *
+ */
+
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <stdio.h>
+#include       <time.h>
+#include       <string.h>
+#include       <ctype.h>
+#include       "misc.h"
+
+char *months[12] =
+{ "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" };
+int monsize[12] = {
+    31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+    };
+char *days[7] =
+{ "su", "mo", "tu", "we" ,"th", "fr", "sa" };
+
+char*strchr();
+
+
+int dayofweek(char * str)
+{
+    int i;
+    for (i=0;i < 7;i++)
+       if (strcmp(days[i],str) == 0)
+           return i;
+    return(-1);
+    
+}
+
+int doy_2_dow(int doy)
+{
+    struct tm * tm;
+    time_t now;
+    int dow;
+
+    if (doy == -1)
+       return -1;
+
+    time(&now);
+    tm = localtime(&now);
+
+    /* the difference in numbers of days */
+    dow  = (tm->tm_wday + (doy+(53*7) - tm->tm_yday)) % 7;
+
+    return dow;
+}
+
+int pod_2_hrs(int pod)
+{
+    int result;
+    
+    if (pod == -1)
+       return -1;
+    result = (pod / 2) * 100;
+    if (pod % 2)
+       result += 30;
+
+    return result;
+    
+}
+
+int podofday(char *str)
+{
+    /* str is hh:mm or h:mm
+     * where mm is 00 or 30
+     * return 0 to 47 ( any other number)
+     *
+     */
+    int l = strlen(str);
+    if (l!=4 && l != 5)
+       return -1;
+    if (str[l-3] != ':' || str[l-1] != '0'
+       || (str[l-2] != '0' && str[l-2] != '3')
+       || (str[l-4] < '0' || str[l-4] > '9')
+       || (str[0] < '0' || str[0] > '9')
+       )
+       return -1;
+    return atoi(str)*2 + (str[l-2]&1);
+}
+
+int dayofyear(char *str)
+{
+    int day, mon, m;
+    time_t now;
+    struct tm *tm;
+    int leapyear;
+    if (strlen(str) != 5)
+       return -1;
+    if (!isdigit(str[0]) || !isdigit(str[1]))
+       return -1;
+    for (mon=0 ; mon<12 ; mon++)
+       if (strcmp(months[mon], str+2)==0)
+           break;
+    if (mon == 12)
+       return -1;
+
+    time(&now);
+    tm = localtime(&now);
+    if (tm->tm_year%4 /* || ((1900 + tm->tm_year)%400) == 0*/)
+       leapyear=0;
+    else
+       leapyear=1;
+    
+    day = atoi(str);
+    if (day<1 || day > monsize[mon])
+       return -1;
+    day -= 1;
+    for (m = 0 ; m < mon ; m++)
+       day += monsize[m];
+    if (mon >= 2 && ! leapyear)
+       day -= 1;
+#ifdef DEBUG
+    printf("%s -> %d\n", str, day);
+#endif    
+    return day;
+}
+
+char *doy2str(char *rv, int doy)
+{
+    struct tm *tm;
+    time_t now;
+    int leapyear;
+    int mon;
+    
+    if (doy <= 0)
+       strcpy(rv, "01jan");
+    else if (doy>=365)
+       strcpy(rv, "31dec");
+    else
+    {
+
+       time(&now);
+       tm = localtime(&now);
+       if (tm->tm_year%4 /* || ((1900 + tm->tm_year)%400) == 0*/)
+           leapyear=0;
+       else
+           leapyear=1;
+       if (!leapyear && doy >= 31+28) doy++;
+       mon = 0;
+       while (doy >=  monsize[mon])
+           doy -= monsize[mon++];
+       sprintf(rv, "%02d%3.3s", doy+1, months[mon]);
+    }
+    return rv;
+}
+
+char *pod2str(char *rv, int pod)
+{
+    sprintf(rv, "%02d:%02d", pod/2, (pod%2)*30);
+    return rv;
+}
+
+
+int minuteofweek(char *str)
+{
+
+    int day, hr, min;
+    char h[3];
+    if (strlen(str) != 6)
+       return -1;
+
+    if (str[5] != '0')
+       return -1;
+    if (str[4] != '0' && str[4] != '3')
+       return -1;
+    strncpy(h, str+2, 2);
+    h[2] = '\0';
+    hr = atoi(h);
+    min = str[4] == '3'? 30:0;
+
+    for (day=0 ; day<7 ; day++)
+       if (strncmp(str, days[day], 2)==0)
+           break;
+    if (day == 7)
+       return -1;
+    min = ((day*24)+hr)*60+min;
+#ifdef DEBUG
+    printf("%s -> %d\n", str, min);
+#endif
+    return min;
+    
+}
+
+/* get_doypod: given a time in seconds, produces the day of the year */
+ /* and period of the day (localtime!).  It also rounds the time in */
+ /* seconds down to the start of the booking period!!! */    
+void get_doypod(time_t * time_in_secs, int * doy, int * pod)
+{
+    int min_diff;
+    
+    struct tm * tm = localtime(time_in_secs);
+    *doy = tm->tm_yday;
+    *pod = (2 * tm->tm_hour);
+    if (tm->tm_min >= 30)
+       (*pod)++;
+
+    /* now make the time_in_secs equal to the start of the period */
+    min_diff = tm->tm_min % 30;
+    *time_in_secs -= (min_diff * 60) + tm->tm_sec;
+    
+#ifdef DEBUG
+    printf("pod = %d, doy = %d, min diff = %d, time  = %d\n", *pod,
+          *doy, min_diff, *time_in_secs);
+#endif
+}
+
+
+
+char * myctime(time_t time_in_secs)
+{
+    static char result[30];
+    struct tm * tm = localtime(&time_in_secs);
+    
+    sprintf(result, "%02d/%02d;%02d:%02d:%02d", tm->tm_mday,
+           tm->tm_mon + 1,
+           tm->tm_hour, tm->tm_min, tm->tm_sec);
+    return(result);
+       
+}
+
+time_t unmyctime(char *tim)
+{
+    int dy, mo, hr, mn, sc;
+    int m;
+    int cnt = 5;
+    time_t now, oldnow;
+    struct tm tm;
+    time(&now);
+    do
+    {
+       if (cnt-- < 0) return 0;
+        oldnow= now;
+       tm = *localtime(&now);
+       sscanf(tim, "%02d/%02d;%02d:%02d:%02d", &dy, &mo, &hr, &mn, &sc);
+       mo-= 1;
+       now += sc - tm.tm_sec;
+       now += 60* (mn - tm.tm_min);
+       now += 3600 * (hr - tm.tm_hour);
+       now += 3600*24 * (dy - tm.tm_mday);
+       for (m=0 ; m< 12 ; m++)
+       {
+           if (m < mo && m>= tm.tm_mon) now += 24*3600*monsize[m] ;
+           if (m >=mo && m<  tm.tm_mon) now -= 24*3600*monsize[m] ;
+       }
+       if (tm.tm_year%4 || ((1900 + tm.tm_year)%400) == 0)
+           ;
+       else
+       {
+           /* leapyear=1; */
+           if (mo <= 1 && tm.tm_mon > 1) now -= 24*3600;
+           if (mo >  1 && tm.tm_mon <=1) now += 24*3600;
+       }
+    } while (now != oldnow);
+    return now;
+}
diff --git a/lib/usage.c b/lib/usage.c
new file mode 100644 (file)
index 0000000..72c1653
--- /dev/null
@@ -0,0 +1,16 @@
+
+#include       <sys/types.h>
+#include       <stdio.h>
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       "misc.h"
+
+extern char *Usage[];
+
+
+void usage(void)
+{
+    int i;
+    for (i=0 ; Usage[i] ; i++)
+       fprintf(stderr, "%s\n", Usage[i]);
+}
diff --git a/lib/utils.c b/lib/utils.c
new file mode 100644 (file)
index 0000000..0601ca7
--- /dev/null
@@ -0,0 +1,113 @@
+
+
+#include       <stdio.h>
+#include       <sys/file.h>
+#ifdef POSIX
+#include       <sys/fcntl.h>
+#endif
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <string.h>
+#include       <time.h>
+#include       <memory.h>
+#include       <sys/stat.h>
+#include       "misc.h"
+
+
+char BookBase[] = "/var/book";
+#undef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 256
+
+void *memdup(void *m, int l)
+{
+       char *rv = (char*)malloc(l);
+       memcpy(rv, m, l);
+       return rv;
+}
+
+
+char * make_pathname(char * name)
+{
+       char * result;
+       result = (char *) malloc (strlen(BookBase) + strlen(name) + 6);
+       if (result == NULL)
+               /* should be a syslog entry */
+               fprintf(stderr, "makepathname: malloc failed\n");
+       sprintf(result, "%s/%s",BookBase, name);
+       return result;
+}
+
+char * make_tmp_pathname(char * name)
+{
+       /* FIXME free this and above somewhere */
+       char * result;
+       result = (char *) malloc (strlen(BookBase) + strlen(name) + 6);
+       if (result == NULL)
+               /* should be a syslog entry */
+               fprintf(stderr, "makepathname: malloc failed\n");
+       sprintf(result, "%s/#%s#",BookBase, name);
+       return result;
+}
+
+char * get_myhostname(void)
+{
+       char * retstring;
+       char buf[MAXHOSTNAMELEN];
+       char * ptr = buf;
+       gethostname(buf,MAXHOSTNAMELEN);
+       /* strip off the domain!! */
+
+       while ((*ptr != '.') && (*ptr != '\0'))
+               ptr++;
+    
+       *ptr = '\0';
+    
+       retstring = (char *)calloc(strlen(buf)+1, sizeof(char));
+       strcpy(retstring,buf);
+
+       return(retstring);
+}
+
+int assert(int condition, char * message)
+{
+       if (!condition) {
+               if (message == NULL) {
+                       printf("ASSERTION failed... non fatal\n");
+                       return 0;
+               }
+               else {
+                       printf("ASSERTION failed\n%s\n",message);
+                       exit(43);
+           
+               }
+       }
+       else
+               return 1;
+}
+
+void shout(char * message)
+{
+       int fid;
+       char *ct;
+       time_t now;
+
+       /* there should also be a debug function as well */
+       static char filename[] = "/var/book/book_errors";
+    
+       now = time(0);
+       ct = ctime(&now);
+    
+       fid = open(filename,O_APPEND|O_CREAT| O_RDWR);
+       fchmod(fid, S_IRUSR|S_IWUSR);
+       write(fid, ct+4, 16);
+       write(fid, message, strlen(message));
+       close(fid);
+    
+}
+
+void bailout(char * message, int n)
+{
+       shout(message);
+       exit(n);
+}
+
diff --git a/login/Makefile b/login/Makefile
new file mode 100644 (file)
index 0000000..d49f12a
--- /dev/null
@@ -0,0 +1,11 @@
+
+target += book_login
+
+obj-book_login = book_login.o ../interfaces/libval.a  ../db_client/libdbclient.a ../lib/lib.a ../manager/status_client.a  ../database/libdbclnt.a 
+
+target += book_logout
+
+obj-book_logout = book_logout.o ../interfaces/libval.a  ../db_client/libdbclient.a ../lib/lib.a ../manager/status_client.a  ../database/libdbclnt.a 
+
+include $(S)$(D)../MakeRules
+
diff --git a/login/book_login.c b/login/book_login.c
new file mode 100644 (file)
index 0000000..4b71cf4
--- /dev/null
@@ -0,0 +1,134 @@
+
+/*
+ * book_login: validate and record userlogin with the booking system
+ *
+ * 1/ determine userid and workstation
+ * 2/ check if can login
+ * 2a/ ifnot, report message and exit
+ * 3/ record controling pid
+ * 4/ tell booking system of login
+ * 5/ write to log file
+ *
+ * Usage: book_login -w workstation -d display -u user -p pid
+ */
+
+#include       <stdlib.h>
+#include       <unistd.h>
+#include       "../interfaces/valinterface.h"
+#include        "../database/database.h"
+#include       <pwd.h>
+#ifndef ultrix
+#include       <sys/fcntl.h>
+#else
+#include       <sys/file.h>
+#endif
+#include       <getopt.h>
+
+char control_pid_dir[] = "/var/book/xdm-pid/"; /*  /var/X11/xdm/xdm-pids */
+static void lusage(char *m)
+{
+       if (m)
+               fprintf(stderr, "book_login: %s\n", m);
+       fprintf(stderr, "Usage: book_login -w workstation -d display"
+               " -u user -p pid\n");
+       exit(1);
+}
+
+
+int main(int argc, char *argv[])
+{
+    
+       char *ws = NULL;
+       bookuid_t who = -1;
+       char *username = NULL;
+       pid_t control = 0;
+       int arg;
+       val_usermrec info;
+       struct passwd *pw;
+       while((arg=getopt(argc,argv, "w:d:u:p:"))!= EOF)
+               switch(arg) {
+               case 'w':
+               case 'd':
+                       if (ws != NULL)
+                               lusage("Only one `-w' or `-d' may be given");
+                       if (arg == 'd')
+                               ws = display2ws(optarg);
+                       else
+                               ws = optarg;
+                       break;
+               case 'u':
+                       pw = getpwnam(optarg);
+                       username = optarg;
+                       if (pw == NULL || pw->pw_uid < 0)
+                               fprintf(stderr, "book_login: unknown user %s...\n", optarg);
+                       else
+                               who = pw->pw_uid;
+                       break;
+               case 'p':
+                       control = atoi(optarg);
+                       break;
+               default:
+                       lusage("unknown option");
+                       break;
+               }
+
+       if (who==0) exit(0); /* root, just say yes */
+
+       canlogin(&info, who, ws, NULL);
+
+       if (info.bookable == 0 ||
+           info.status == CANLOGIN) {
+               if (who < 0) {
+                       printf("Ok to login\n");
+                       exit(0);
+               }
+               /* login is OK, try to record things */
+               if (control>1 && ws != NULL) {
+                       char pidfile[200];
+                       int fd;
+                       strcpy(pidfile, control_pid_dir);
+                       strcat(pidfile, ws);
+                       fd = open(pidfile, O_WRONLY|O_CREAT|O_TRUNC, 0600);
+                       if (fd) {
+                               char n[20];
+                               sprintf(n, "%d\n", control);
+                               write(fd, n, strlen(n));
+                               close(fd);
+                       }
+               }
+       
+               hasloggedin(who, ws, NULL);
+       
+#if 0
+               log_login('+', 0, ws, NULL, username);
+#endif
+               exit(0);
+       } else {
+               /* print a message and fail */
+               printf("Login denied - ");
+               switch(info.status) {
+               case OUTOFSERVICE:
+                       printf("this workstation is NOT IN SERVICE");
+                       break;
+               case CLOSED:
+                       printf("this lab is currently CLOSED");
+                       break;
+               case CLASSRESERVED:
+                       if (info.resname== NULL)
+                               printf("this workstation is currently reserved for a class of which you are not a member");
+                       else
+                               printf("this workstation is currently reserved for a member of %s", info.resname);
+                       break;
+               case RESERVED:
+                       printf("this workstation is currently reserved for %s",
+                              info.resname?info.resname:"another user");
+                       break;
+               default:
+                       printf("no reason is given - please see the booking system administrator");
+                       break;
+               }
+               printf("\n");
+               exit(1);
+       }       
+       return 0;
+}
diff --git a/login/book_logout.c b/login/book_logout.c
new file mode 100644 (file)
index 0000000..af0860c
--- /dev/null
@@ -0,0 +1,73 @@
+
+/*
+ * book_logout: tell the booking system that a user has logged out
+ *
+ * 1/ determine username and workstation
+ * 2/ tell booking system the nobody just logged on
+ * 3/ write to log file
+ * 4/ optionally kill all processes owned by the user
+ *
+ * Usage: book_logout -u user -w workstation -d display -K
+ */
+
+#include       <getopt.h>
+#include       "../interfaces/valinterface.h"
+#include       <pwd.h>
+#include        <stdio.h>
+#include        <stdlib.h>
+
+static void lusage(char *m)
+{
+    if (m)
+    fprintf(stderr, "book_logout: %s\n", m);
+    fprintf(stderr, "Usage: book_logout -u user -w workstation -d display -K\n");
+    exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+    char *ws = NULL;
+    bookuid_t who = -1;
+    char *username;
+    int arg;
+    struct passwd *pw;
+    int killoff = 0;
+    extern char *optarg;
+    
+    while ((arg = getopt(argc, argv, "u:w:d:K"))!= EOF)
+       switch(arg)
+       {
+       case 'w':
+       case 'd':
+           if (ws != NULL)
+               lusage("Only one `-w' or `-d' may be given");
+           if (arg == 'd')
+               ws = display2ws(optarg);
+           else
+               ws = optarg;
+           break;
+       case 'u':
+           pw = getpwnam(optarg);
+           username = optarg;
+           if (pw == NULL)
+               fprintf(stderr, "book_logout: unknown user %s...\n", optarg);
+           else
+               who = pw->pw_uid;
+           break;
+       case 'K':
+           killoff = 1;
+           break;
+       default:
+           lusage("unknown option");
+           break;
+       }
+
+    hasloggedin(NOONE, ws, NULL);
+#if 0
+    if (username)
+       log_login('-', 0, ws, NULL, username);
+#endif
+    if (killoff)
+       killuser(who);
+    exit(0);
+}
diff --git a/manager/Makefile b/manager/Makefile
new file mode 100644 (file)
index 0000000..e4814c3
--- /dev/null
@@ -0,0 +1,25 @@
+
+target += book_status
+
+obj-book_status += book_status.o 
+obj-book_status += db_loc.o table.o helper.o lookup_table.o
+obj-book_status += status_svc.o status_xdr.o
+obj-book_status += ../lib/lib.a
+
+lib += status_client.a
+obj-status_client.a += status_clnt.o status_xdr.o manager_access.o choose_db.o cache_names.o collect_table.o lookup_table.o $(whoison_sysdep-y)
+
+target += book_manager
+
+whoison_sysdep-$(unix) =  whoison.o
+
+manager_sysdep-$(unix) = sysdep_xdm.o
+manager_sysdep-$(windows) = sysdep_windows.o
+
+obj-book_manager += book_manager.o
+obj-book_manager += allocate.o print_tab.o create_table.o pass_table.o evictions.o check_default.o check_status.o alloc.o check_servers.o $(manager_sysdep-y)
+obj-book_manager += status_client.a
+obj-book_manager +=  ../db_client/libdbclient.a ../lib/lib.a
+obj-book_manager +=  ../database/libdbclnt.a ../database/intpairlist.o  clnt_create.o
+
+include $(S)$(D)../MakeRules
diff --git a/manager/alloc.c b/manager/alloc.c
new file mode 100644 (file)
index 0000000..48de934
--- /dev/null
@@ -0,0 +1,70 @@
+#include       <time.h>
+#include       "helper.h"
+
+bool_t next_period_due(table * tab, int * doy, int *pod)
+{
+       int oldpod, olddoy;
+       time_t next_period, cutoff;
+       time_t alloctime = time(0);
+       int i;
+
+
+       for (i=0 ; i<tab->table_len ; i++)
+               if (Cluster_type(tab, i) == ALLOCCLUSTER
+                   && Cluster_modtime(tab,i) < alloctime)
+                       alloctime = Cluster_modtime(tab,i);
+    
+       get_doypod(&alloctime, &olddoy, &oldpod);
+    
+       cutoff = 0;
+       cutoff = get_configint("alloctime");
+       if (cutoff <= 0 )
+               cutoff = 7*60; /* 7 minutes by default */
+
+       next_period = time(0) + cutoff; /* want 7 mins before next time */
+       /* starts */
+       get_doypod(&next_period, doy, pod);
+       return ((*doy != olddoy) || (*pod != oldpod));
+}
+
+
+/* LAB_STATUS :find out from the exclusions table what should happen */
+ /* this period.  Maybe this should update the status of the machines */
+ /* in the labs at the same time */
+
+/* should the labindex go into the table?? */
+int lab_status(int doy, int pod, int lab)
+{
+       extern description * process_exclusions();
+       static description res;
+       int st;
+
+
+       if (res.item_len == 0)
+               res = new_desc();
+       zero_desc(&res);
+       set_a_bit(&res, lab);
+
+       if (!get_attr_types()) return PERIODFREE;
+    
+       /* what does the routine that processes the exclusions do?? */
+       /* needs to take lab ind ( the labind can be in the description!!, */
+       /* doy, pod and return if lab is open or  not.  */
+
+       process_exclusions(pod, doy, &res);
+
+       /* now find out which bit is set */
+       /* these should not be numbers */
+       if (query_bit(&res, attr_types.open) == TRUE)
+               st = PERIODFULL;
+       else if (query_bit(&res, attr_types.closed) == TRUE)
+               st = LABCLOSED;
+       else
+               st = PERIODFREE;
+    
+       sprintf(errbuf, "Lab status (pod %d, doy %d) %d\n", pod,
+               doy, st);
+       shout(errbuf);
+
+       return st;
+}
diff --git a/manager/allocate.c b/manager/allocate.c
new file mode 100644 (file)
index 0000000..eb7120e
--- /dev/null
@@ -0,0 +1,691 @@
+
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <time.h>
+#include       "helper.h"
+/*
+ * allocations:
+ *
+ * 1/ For each lab in table that needs allocation
+ *   a/ get all bookings
+ *   b/ determine which bookings should be considered "tentative"
+ *             This is if they are allocated, but not logged in
+ *   c/ sort bookings putting late and tentative bookings last
+ *   d/ find out which workstations have to-be-allocated users on
+ *      and if they fit the requirement, mark them
+ *   e/ sort available workstations putting
+ *             un-used first
+ *             then people who have not been booked for a long time
+ *             then booked users who are not now allocated
+ *             then workstations with to-be-allocated users
+ *   f/ allocate bookings to workstations looking forward for
+ *      a workstation that the user is on.
+ *      Determine what bookings cannot be honoured
+ * 2/ Rebuild table with new workstation reserve records and and
+ *    refund records
+ * 3/ Lodge the new table with the server
+ * 4/ inform the server as to how each booking was dealt with
+ * 5/ Done.
+ *
+ */
+
+/*
+ * for each lab we need
+ *  The original list of bookings
+ *  An array containing pointers plus some extra info per booking
+ *    1/ where allocated
+ *    2/ if tentative (no, change status to match)
+ *  Array containing list of workstation and table indexes && state pointers
+ *    and whether to-be-allocated user is on
+ *  How many workstation are up, how many allocated
+ */
+
+typedef struct labinfo
+{
+       struct labinfo *next;
+       entityid        labid;
+       bookhead        booklist;
+       struct bk
+       {
+               booklist b;
+               entityid *whereto;
+               int      num_alloc;
+               int      already_on;
+       } *bookings;
+       int bookcnt;
+       struct ws
+       {
+               entityid                wsid;
+               int                     status_index;
+               int                     res_index;
+               workstation_state       *wsstate;
+               struct bk               *booking;
+               book_status             newstatus;
+               int                     sorttype; /* unused, used, to_be_alloc, broken */
+               time_t                  atime; /* when booking made, or last alloc */
+               int                     whofor; /* if 2B_ALLOC, who is on or reserved */
+       } *ws;
+       int ws_total;
+       int ws_up;
+       int ws_alloc;
+       int alloc_index;
+       int doy, pod;
+       time_t alloctime;
+       book_status     newstatus;
+} *labinfo;
+
+#define WS_UNUSED      0
+#define        WS_USED         1
+#define        WS_2B_ALLOC     2
+#define        WS_BROKEN       3
+
+
+static void free_labinfo(labinfo lab)
+{
+       if (lab)
+       {
+               free_labinfo(lab->next);
+               xdr_free((xdrproc_t)xdr_bookhead, (char*) &lab->booklist);
+               if (lab->bookings)
+                       free(lab->bookings);
+               if (lab->ws)
+                       free(lab->ws);
+               free(lab);
+       }
+}
+
+static void update_table(table *tab, labinfo lab)
+{
+       int *d;
+       int failed_allocs;
+       int w,b;
+    
+       /* for each workstation in ws,
+        *  set time, whofor, status
+        * for each booking
+        *  if not-allocated, record in lab field
+        * set lab allocate time and status
+        *
+        */
+       for (w=0; w < lab->ws_total ; w++)
+       {
+               /* make sure the data field is long enough */
+               cluster *r = &tab->table_val[lab->ws[w].res_index];
+               if (r->data.data_len < 3)
+               {
+                       r->data.data_val = (int*)realloc(r->data.data_val, 3*sizeof(int));
+                       r->data.data_len = 3;
+                       r->data.data_val[2] = 0;
+               }
+       
+               Cluster_modtime(tab, lab->ws[w].res_index) = lab->alloctime;
+               Res_status2(tab, lab->ws[w].res_index) = 0;
+               if (lab->ws[w].newstatus == NODEOUTOFSERVICE
+                   || lab->ws[w].newstatus == NODEBROKEN
+                   || lab->ws[w].newstatus == NODENOTBOOKABLE
+                       )
+               {
+                       if (lab->newstatus == LABCLOSED)
+                       { /* FIXME maybe should get evictions to check lab status rather than this hack */
+                               Res_user(tab, lab->ws[w].res_index) = -2;
+                               Res_status(tab, lab->ws[w].res_index) = NODEALLOCATED;
+                               Res_status2(tab, lab->ws[w].res_index) = 2;
+                       }
+                       else
+                       {
+                               Res_status(tab, lab->ws[w].res_index) =
+                                       lab->ws[w].newstatus;
+                               Res_user(tab, lab->ws[w].res_index) = -1;
+                       }
+               }
+               else
+                       switch (lab->newstatus)
+                       {
+                       default: break; /* keep -Wall quite */
+                       case LABCLOSED:
+                               Res_user(tab, lab->ws[w].res_index) = -2;
+                               Res_status(tab, lab->ws[w].res_index) = NODEALLOCATED;
+                               Res_status2(tab, lab->ws[w].res_index) = 2;
+                               break;
+                       case PERIODFREE: /* synonym for not closed and not open */
+                               Res_user(tab, lab->ws[w].res_index) = -1;
+                               Res_status(tab, lab->ws[w].res_index) = NODENOTALLOC;
+                               break;
+                       case PERIODFULL: /* really means "OPEN" at this stage */
+                               if (lab->ws[w].booking == NULL)
+                               {
+                                       /* nothing allocated here */
+                                       Res_user(tab, lab->ws[w].res_index) = -1;
+                                       Res_status(tab, lab->ws[w].res_index) = NODENOTALLOC;
+                               }
+                               else
+                               {
+                                       Res_user(tab, lab->ws[w].res_index) =
+                                               lab->ws[w].booking->b->who_for;
+                                       if (query_bit(&lab->ws[w].booking->b->mreq, attr_types.firmalloc))
+                                       {
+                                               Res_status(tab, lab->ws[w].res_index) = NODEALLOCATED;
+                                               Res_status2(tab, lab->ws[w].res_index) = 1; /* Firm alloc */
+                                       }
+                                       else
+                                       {
+                                               Res_status(tab, lab->ws[w].res_index) =
+                                                       lab->ws[w].booking->b->status == B_TENTATIVE?
+                                                       NODETENTATIVE : NODEALLOCATED;
+                                               Res_status2(tab, lab->ws[w].res_index) = 0; /* loose alloc */
+                                       }
+                                       /* if already on, update whenalloc */
+                                       if (lab->ws[w].booking->b->who_for ==
+                                           Ws_user(tab, lab->ws[w].status_index))
+                                       {
+                                               Ws_whenalloc(tab, lab->ws[w].status_index) = lab->alloctime;
+                                               lab->ws[w].booking->already_on= 1;
+                                       }
+                   
+                               }
+               
+                               break;
+                       }
+       }
+
+       failed_allocs = 0;
+       for (b=0 ; b<lab->bookcnt ; b++)
+       {
+               if (lab->bookings[b].num_alloc == 0
+                   && lab->bookings[b].b->who_for < get_class_min())
+                       failed_allocs++;
+       }
+       tab->table_val[lab->alloc_index].timestamp = lab->alloctime;
+       tab->table_val[lab->alloc_index].data.data_len =
+               failed_allocs + 3; /* status, who, when failed */
+       free(tab->table_val[lab->alloc_index].data.data_val);
+       d = tab->table_val[lab->alloc_index].data.data_val =
+               (int*)malloc(sizeof(int)*
+                            tab->table_val[lab->alloc_index].data.data_len);
+
+       if (lab->newstatus == PERIODFULL)
+       {
+               int spare = lab->ws_up - lab->ws_alloc;
+               if (lab->ws_total <=0 || ((spare*100) / lab->ws_total) >= 10)
+                       lab->newstatus = PERIODFREE;
+       }
+       d[0] = lab->newstatus;
+       d[1] = my.hostid;
+       d[2] = time(0);
+       failed_allocs = 0;
+       for (b=0 ; b<lab->bookcnt ; b++)
+       {
+               if (lab->bookings[b].num_alloc == 0
+                   && lab->bookings[b].b->who_for < get_class_min())
+                       d[3+(failed_allocs++)] = lab->bookings[b].b->who_for;
+       }
+
+}
+
+static void make_ws_list(labinfo lab, table *tab)
+{
+       int ind;
+       int ws;
+
+       for (ind = 0 ; ind < my.hgdata.labs.entitylist_len ; ind++)
+               if (my.hgdata.labs.entitylist_val[ind] == lab->labid)
+                       break;
+
+       if (ind == my.hgdata.labs.entitylist_len)
+               bailout("make_ws_list: cannot find lab in hostgroup", 2);
+       lab->ws_total = my.labdata[ind].workstations.entitylist_len;
+       lab->ws = (struct ws*)malloc(sizeof(struct ws) * lab->ws_total);
+
+       for (ws = 0 ; ws < lab->ws_total ; ws++)
+       {
+               int i;
+               lab->ws[ws].wsid = my.labdata[ind].workstations.entitylist_val[ws];
+               for (i=0 ; i<tab->table_len ; i++)
+                       switch(Cluster_type(tab,i))
+                       {
+                       default: break; /* keep -Wall quite */
+                       case WSRESCLUSTER:
+                               if (Cluster_id(tab,i) == lab->ws[ws].wsid)
+                                       lab->ws[ws].res_index = i;
+                               break;
+                       case WSSTATUSCLUSTER:
+                               if (Cluster_id(tab,i) == lab->ws[ws].wsid)
+                                       lab->ws[ws].status_index = i;
+                               break;
+                       }
+               lab->ws[ws].wsstate = get_wsstate(lab->ws[ws].wsid, 1);
+               lab->ws[ws].booking = NULL;
+               lab->ws[ws].newstatus = 0;
+       }
+}
+
+static void calc_ws_state(labinfo lab, table *tab)
+{
+       /* for each workstation
+        *  if out of service, mark OUTOFSERVICE
+        *  if down, mark BROKEN
+        *  if controling host is down, mark BROKEN
+        *  else mark NOTALLOC
+        *
+        *  calculate ws_up on the way
+        */
+       int w;
+       int up = 0;
+
+       for (w=0 ; w<lab->ws_total ; w++)
+       {
+               int st;
+               int i;
+       
+               if (query_bit(&lab->ws[w].wsstate->state, attr_types.available) == 0)
+                       st = NODEOUTOFSERVICE;
+               /*
+                * FIXME
+                * somehow status_index was 8388608.
+                #0  0x804a671 in calc_ws_state (lab=0x80677e8, tab=0x805ba7c) at /tmp_amd/glass/import/1/neilb/Common/work/book/manager/allocate2.c:279
+                279             else if (Ws_status(tab, lab->ws[w].status_index) == NODEDOWN)
+                (gdb) where
+                #0  0x804a671 in calc_ws_state (lab=0x80677e8, tab=0x805ba7c) at /tmp_amd/glass/import/1/neilb/Common/work/book/manager/allocate2.c:279
+                #1  0x804b850 in allocate (tab=0x805ba7c) at /tmp_amd/glass/import/1/neilb/Common/work/book/manager/allocate2.c:643
+                #2  0x804d170 in propagate_table () at /tmp_amd/glass/import/1/neilb/Common/work/book/manager/pass_table.c:292
+                #3  0x80498ca in main (argc=1, argv=0xbffffd5c) at /tmp_amd/glass/import/1/neilb/Common/work/book/manager/book_manager.c:97
+                (gdb) print w
+                $1 = 3
+                (gdb) print lab->ws[w]
+                $2 = {wsid = 264, status_index = 8388608, res_index = 0, wsstate = 0x8062814, booking = 0x0, newstatus = 0, sorttype = 33554432, atime = 1023676987, 
+                whofor = 16777216}
+
+               */
+               else if (Ws_status(tab, lab->ws[w].status_index) == NODEDOWN)
+                       st = NODEBROKEN;
+               else if (lab->ws[w].wsstate->host >= 0 &&
+                        (i = lookup_table(tab, HOSTCLUSTER, lab->ws[w].wsstate->host )) > 0
+                        && Host_status(tab,i) == NODEDOWN)
+                       st = NODEBROKEN;
+               else
+               {
+                       item d;
+                       desc_cpy(&d, &lab->ws[w].wsstate->state);
+                       process_exclusions(lab->pod, lab->doy, &d);
+                       if (!query_bit(&d, attr_types.open))
+                               st = NODENOTBOOKABLE;
+                       else
+                               st = NODENOTALLOC;
+                       free(d.item_val);
+               }
+
+               lab->ws[w].newstatus = st;
+
+               if (st == NODENOTALLOC)
+               {
+                       up++;
+                       if (Ws_user(tab, lab->ws[w].status_index) >= 0)
+                       {
+                               /* atime should reflect how much un-booked time
+                                * has been used
+                                */
+                               lab->ws[w].sorttype = WS_USED;
+                               lab->ws[w].atime = Ws_whenalloc(tab, lab->ws[w].status_index);
+                       }
+                       else
+                               lab->ws[w].sorttype = WS_UNUSED;
+               }
+               else
+                       lab->ws[w].sorttype = WS_BROKEN;
+       }
+       lab->ws_up = up;
+}
+
+static int ws_cmp(const void *av, const void *bv)
+{
+       const struct ws *a=av, *b=bv;
+       /* compare two workstations.
+        * unused come first
+        * then inuse, but not to-be-alloc
+        * then to-be-alloc
+        * then broken or outofservice
+        */
+
+       if (a->sorttype != b->sorttype)
+               return a->sorttype - b->sorttype;
+
+       switch(a->sorttype)
+       {
+       case WS_UNUSED:
+               return a->wsid - b->wsid;
+       case WS_USED:
+               return a->atime - b->atime; /* longer without booking -> earlier in list */
+       case WS_2B_ALLOC: /* used but user booked */
+               return b->atime - a->atime; /* an earlier booking sorts to end */
+       case WS_BROKEN: /* not available */
+               return a->wsid - b->wsid;
+       default: return 0; /* keep -Wal quiet */
+       }
+}
+
+static void sort_ws_list(labinfo lab)
+{
+       qsort(lab->ws, lab->ws_total, sizeof(struct ws), ws_cmp);
+}
+
+static int get_bk_list(labinfo lab)
+{
+       /* get the list of bookings for this lab
+        * and make the bookings list of pending bookings
+        */
+       char k[20];
+       int cnt;
+       booklist bp;
+
+       lab->booklist.data = NULL;
+       switch (db_read(bookkey2key(k, make_bookkey(lab->doy, lab->pod, lab->labid)),
+                       &lab->booklist, (xdrproc_t)xdr_bookhead, BOOKINGS))
+       {
+       case R_RESOK:
+               break;
+
+       case R_NOMATCH:
+               lab->booklist.data = NULL;
+               break;
+       default:
+               return -1;
+       }
+       cnt = 0;
+       for (bp= lab->booklist.data ; bp ; bp=bp->next)
+               if (bp->status == B_PENDING)
+                       cnt++;
+       lab->bookings = (struct bk*)malloc(sizeof(struct bk) * cnt);
+       lab->bookcnt = cnt;
+       cnt = 0;
+       for (bp= lab->booklist.data ; bp ; bp=bp->next)
+               if (bp->status == B_PENDING)
+               {
+                       lab->bookings[cnt].b = bp;
+                       lab->bookings[cnt].whereto = (entityid*)malloc(sizeof(entityid)*bp->number);
+                       lab->bookings[cnt].num_alloc = 0;
+                       lab->bookings[cnt].already_on=0;
+                       cnt++;
+               }
+       return 0;
+}
+
+
+static void pre_allocate(labinfo lab, table *tab)
+{
+       /* for each bookings,
+        *  if user is allocated in lab >5minutes ago, change to tentative
+        *  else if user is a defaulter, change to tentative;
+        *  if user is logged on anywhere, change sorttype, and reset booking type
+        */
+       int b;
+       for (b=0; b<lab->bookcnt ; b++)
+       {
+               int w;
+               for (w=0 ; w<lab->ws_total ; w++)
+               {
+                       if ((Res_status(tab, lab->ws[w].res_index) == NODEALLOCATED ||
+                            Res_status(tab, lab->ws[w].res_index) == NODETENTATIVE)
+                           && Res_user(tab, lab->ws[w].res_index) == lab->bookings[b].b->who_for
+                           && Cluster_modtime(tab, lab->ws[w].res_index) < time(0)-5*60 /*FIXME not time(0) */
+                               )
+                               lab->bookings[b].b->status = B_TENTATIVE;
+               }
+               if (lab->bookings[b].b->status != B_TENTATIVE
+                   && is_defaulter(lab->bookings[b].b->who_for))
+                       lab->bookings[b].b->status = B_TENTATIVE;
+               /*
+                * NOTE:
+                * IF 'a' is allocated to 'w' but doesn't show up,
+                *   and 'b' logs on
+                *   and then 'a' claims the booking at 17minutes past
+                *   and 'a' and 'b' are both booked for there next period
+                *   then who gets 'w'?? Both have a claim.
+                * The following code will pick whichever booking is first in the list,
+                *     which is as arbitrary as anything else.
+                */
+               for (w=0 ; w<lab->ws_total ; w++)
+               {
+                       /* FIXME next test should be for all labs */
+                       if (lab->ws[w].sorttype == WS_USED
+                           && Ws_user(tab, lab->ws[w].status_index) == lab->bookings[b].b->who_for)
+                       {
+                               lab->ws[w].sorttype = WS_2B_ALLOC;
+                               lab->ws[w].whofor = lab->bookings[b].b->who_for;
+                               lab->ws[w].atime = lab->bookings[b].b->time;
+                               lab->bookings[b].b->status = B_PENDING;
+                       }
+                       if ((lab->ws[w].sorttype == WS_USED || lab->ws[w].sorttype == WS_UNUSED)
+                           && Res_status(tab, lab->ws[w].res_index) == NODEALLOCATED
+                           && Res_user(tab, lab->ws[w].res_index) == lab->bookings[b].b->who_for
+                           && Cluster_modtime(tab, lab->ws[w].res_index) > time(0)-5*60)
+                       {
+                               /* a recent allocation, allocate same again */
+                               lab->ws[w].sorttype = WS_2B_ALLOC;
+                               lab->ws[w].whofor = lab->bookings[b].b->who_for;
+                               lab->ws[w].atime = lab->bookings[b].b->time;
+                               lab->bookings[b].b->status = B_PENDING;
+                       }
+               }
+       }
+}
+
+static int bk_cmp(const void *av, const void *bv)
+{
+       const struct bk *a=av, *b=bv;
+       /* sort pending first, then tentative
+        * with in those, early bookings first 
+        */
+
+       if (a->b->status != b->b->status)
+               return a->b->status - b->b->status;
+       return a->b->time - b->b->time;
+}
+
+static void bk_sort(labinfo lab)
+{
+       qsort(lab->bookings, lab->bookcnt, sizeof(struct bk), bk_cmp);
+}
+
+
+
+static void do_allocate(labinfo lab, table *tab)
+{
+       /* for each booking, 
+        * look for workstation already logged on
+        * and allocate if right attributes
+        * else allocate to first free ws with right attributes
+        */
+       int b;
+       for (b=0 ; b<lab->bookcnt ; b++)
+       {
+               int first = 0;
+               while (first != -1
+                      && lab->bookings[b].num_alloc < lab->bookings[b].b->number)
+               {
+                       int w;
+                       first = -1;
+                       for (w=0 ; w < lab->ws_up ; w++)
+                               if (lab->ws[w].booking == NULL
+                                   && desc_member(&lab->bookings[b].b->charged,
+                                                  &lab->ws[w].wsstate->state) == 1
+                                   && desc_member(&lab->ws[w].wsstate->state,
+                                                  &lab->bookings[b].b->charged) == 1
+                                       )
+                               {
+                                       /* potential workstation */
+                                       if (first < 0) first = w;
+                                       if (lab->ws[w].sorttype == WS_2B_ALLOC &&
+                                           lab->ws[w].whofor == lab->bookings[b].b->who_for)
+                                       {
+                                               /* user already logged on here, or recently allocated */
+                                               first = w;
+                                       }
+                               }
+                       if (first >= 0)
+                       {
+                               lab->ws[first].booking = & lab->bookings[b];
+                               lab->bookings[b].whereto[lab->bookings[b].num_alloc] = lab->ws[first].wsid;
+                               lab->bookings[b].num_alloc ++;
+                               lab->ws_alloc++;
+                       }
+               }
+       }
+}
+
+static labinfo get_labs(table *tab, time_t then)
+{
+       /* make linked list of labinfos for each ALLOCCLUSTER in
+        * the table
+        */
+       labinfo rv = NULL;
+       int c;
+
+       for (c=0 ; c < tab->table_len ; c++)
+               if (Cluster_type(tab, c)== ALLOCCLUSTER)
+               {
+                       extern int fake_alloc;
+                       time_t alloctime;
+                       int olddoy, oldpod;
+                       int doy,pod;
+                       alloctime = Cluster_modtime(tab, c);
+                       get_doypod(&alloctime, &olddoy, &oldpod);
+
+                       if (fake_alloc)
+                               alloctime += 30*60;
+                       else
+                               alloctime = then;
+                       get_doypod(&alloctime, &doy, &pod);
+                       if (doy != olddoy || pod != oldpod)
+                       {
+                               /* this lab needs allocation */
+                               labinfo l = (labinfo)malloc(sizeof(struct labinfo));
+                               l->labid = Cluster_id(tab, c);
+                               l->booklist.data = NULL;
+                               l->bookings = NULL;
+                               l->bookcnt = 0;
+                               l->ws = NULL;
+                               l->ws_total = l->ws_up = l->ws_alloc = 0;
+                               l->alloc_index = c;
+                               l->alloctime = alloctime;
+                               l->doy = doy;
+                               l->pod = pod;
+                               l->newstatus = lab_status(doy, pod, l->labid);
+                               l->next = rv;
+                               rv = l;
+                       }
+               }
+       return rv;
+}
+
+static void record_table(table *tab)
+{
+       FILE *f = fopen("/var/book/book.dump", "a");
+       if (f)
+       {
+               print_tab(tab, f);
+               fclose(f);
+       }
+}
+
+static void record_alloc(labinfo lab)
+{
+       /* create a book_chng list and send to server */
+       book_ch_list chlist;
+       book_ch_ent *bp;
+       book_change ch;
+       int i;
+
+       if (lab->bookcnt)
+       {
+               chlist.book_ch_list_len = lab->bookcnt;
+               chlist.book_ch_list_val = bp = (book_ch_ent*)malloc(sizeof(book_ch_ent)*lab->bookcnt);
+
+               for (i=0; i<lab->bookcnt ; i++, bp++)
+               {
+                       bp->user = lab->bookings[i].b->who_for;
+                       bp->when = lab->bookings[i].b->time;
+                       bp->priv_refund = 0;
+                       bp->tentative_alloc = 
+                               lab->bookings[i].b->status == B_TENTATIVE;
+                       if (lab->bookings[i].already_on)
+                               bp->tentative_alloc |= 2;
+                       if (lab->bookings[i].num_alloc == 0)
+                               bp->status = B_REFUNDED, bp->priv_refund = 1;
+                       else if (lab->newstatus == PERIODFREE)
+                               bp->status = B_FREEBIE;
+                       else if (query_bit(&lab->bookings[i].b->mreq, attr_types.reusable))
+                               bp->status = B_RETURNED;
+                       else
+                               bp->status = B_ALLOCATED;
+                       if (lab->bookings[i].num_alloc > 0)
+                       {
+                               bp->allocations.entitylist_len = lab->bookings[i].num_alloc;
+                               bp->allocations.entitylist_val = (entityid*)memdup(lab->bookings[i].whereto, lab->bookings[i].num_alloc * sizeof(entityid));
+                       }
+                       else
+                       {
+                               bp->allocations.entitylist_len = 0;
+                               bp->allocations.entitylist_val = NULL;
+                       }
+               }
+
+               ch.chng = CHANGE_BOOKING;
+    
+               ch.book_change_u.changes.slot = make_bookkey(lab->doy, lab->pod, lab->labid);
+               ch.book_change_u.changes.chlist = chlist;
+               send_change(&ch);
+               free(chlist.book_ch_list_val);
+       }
+}
+
+int allocate(table *tab)
+{
+       labinfo labs;
+       time_t then, cutoff;
+       labinfo l;
+       extern int fake_alloc;
+    
+       cutoff = 0;
+       cutoff = get_configint("alloctime");
+       if (cutoff <= 0 )
+               cutoff = 60*7; /* 7 minutes by default */
+
+       then = time(0) + cutoff;
+    
+       labs = get_labs(tab, then);
+       if (labs == NULL)
+               return 0; /* no allocation needed */
+    
+
+       for (l=labs ; l ; l=l->next)
+               get_bk_list(l);
+       for (l=labs ; l ; l=l->next)
+               make_ws_list(l, tab);
+       for (l=labs ; l ; l=l->next)
+               calc_ws_state(l, tab);
+       for (l=labs ; l ; l=l->next)
+               pre_allocate(l, tab);
+    
+       for (l=labs ; l ; l=l->next)
+               sort_ws_list(l);
+
+       for (l=labs ; l ; l=l->next)
+               bk_sort(l);
+    
+       for (l=labs ; l ; l=l->next)
+               do_allocate(l, tab);
+
+       for (l=labs ; l ; l=l->next)
+               update_table(tab, l);
+
+       record_table(tab);
+
+       if (fake_alloc)
+               abort();
+    
+       for (l=labs ; l ; l=l->next)
+               record_alloc(l);
+    
+       free_labinfo(l);
+       return 1;
+}
diff --git a/manager/book_manager.c b/manager/book_manager.c
new file mode 100644 (file)
index 0000000..d2d5bb6
--- /dev/null
@@ -0,0 +1,110 @@
+ /* this program receives tokens from the network with information */
+ /* about other nodes in the laboratory.  It fills in private */
+ /* information  and passes the token on to the next *available* node */
+ /* in the laboratory */
+
+/* #define TESTING */
+
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <time.h>
+#include       <signal.h>
+#include       <sys/wait.h>
+#include       <sys/time.h>
+#include       <sys/file.h>
+#include       "helper.h"
+#ifdef sun
+#include       <sys/fcntl.h>
+#endif
+
+
+char errbuf[200];
+
+/* static struct timeval SHORTTIMEOUT = {10, 0};*/
+int sendtable; /* the last time the table was sent on to another node */
+
+bool_t am_master;
+int last_send = 0;
+
+
+/* this routine is called when an interupt is received to send the */
+ /* table on somewhere else */
+SIGRTN set_counter()
+{
+       set_bookdebug_level();
+       sendtable++;
+       sprintf(errbuf,"NODE_STATUS Now sendtable %d\n",sendtable);
+       Debug(errbuf,2);
+
+       signal(SIGALRM, set_counter);
+}
+
+
+int fake_alloc = 0;
+int main (int argc, char *argv[])
+{
+       static struct timeval wait = {5,0};
+       static struct timeval totwait = {15,0};
+       int st;
+       time_t last_check = 0;
+    
+       am_master = FALSE;
+
+       set_counter();
+
+       pmap_settimeouts(wait, totwait);
+
+       /* get the rpc handles set up */
+       open_status_client("localhost");
+       if (status_client == NULL)
+       {
+               shout("Node_status cannot init_bind\n");
+               exit(1);
+       }
+
+       while ((st = load_config()) == -2)
+               sleep(30);
+
+
+       check_servers();
+    
+       if (st == -1)           /* not registered in booking system, so nothing to do */
+               exit(0);
+
+       get_attr_types();
+       get_impl_list();
+
+       /* settimeouts for creating client handles*/
+
+       if (argc>1)
+       {
+               fake_alloc = 1;
+       }
+
+       send_new_table();               /* make sure the table structure is recent */
+       sendtable = 1;
+       while (1)
+       {
+               if (time(0) - last_check > 37*60)
+               {
+                       last_check = time(0);
+                       check_servers();
+               }
+               if (sendtable)
+               {
+                       int nexttime;
+                       sprintf(errbuf, "NODESTATUS wakeup %d\n",(int)time(0));
+                       Debug(errbuf,1);
+                       alarm(0);
+                       sendtable = 0; 
+                       nexttime = propagate_table();
+                       if (nexttime > 2) alarm(nexttime);
+                       else sendtable = 1;
+
+                       sprintf(errbuf,"NODESTATUS sleep %d\n", (int)time(0));
+                       Debug(errbuf,1);
+               }
+               if (sendtable==0) pause();
+       }
+       exit(0);
+}
diff --git a/manager/book_status.c b/manager/book_status.c
new file mode 100644 (file)
index 0000000..71c8108
--- /dev/null
@@ -0,0 +1,95 @@
+/* this module caches information from the server and is responsible */
+ /* for maintaining contact with a server. */
+/* this module should be run on every node along with a book_helper */
+/* all information is dynamic.. i.e. it will not be saved */
+
+
+#include       <stdio.h>
+#include       <signal.h>
+#include       <rpc/rpc.h>
+#include       <rpc/pmap_clnt.h>
+#include       <sys/wait.h>
+#include       "statush.h"
+#include       <sys/resource.h>
+
+extern int helper_pid;
+
+static SIGRTN die(int sig)
+{
+       if (helper_pid > 0)
+               kill(helper_pid, SIGKILL);
+       (void)pmap_unset(BOOK_STATUS, BOOKVERS);
+       if (sig)
+               exit(0);
+}
+
+void * SVC(status_die_2)(void * noarg,struct svc_req *rqstp)
+{
+       static char dummy;
+
+       if (ntohs(svc_getcaller(rqstp->rq_xprt)->sin_port) > 1024) {
+               /* FIXME check host */
+               return (&dummy);
+       }
+
+
+       /* reply that the die is about to happen */
+       svc_sendreply(rqstp->rq_xprt, (xdrproc_t)xdr_char, &dummy);
+       die(0);
+       exit(0);
+}
+
+int main (int argc, char *argv[])
+{
+       extern void book_status_2();
+       SVCXPRT *transp;
+       char *maint_prog = "/usr/local/etc/book_manager";
+
+       if (argc > 2 && (strcmp(argv[1], "-H")==0 || strcmp(argv[1], "-M")==0))
+       {
+               /*  Helper or Maint */
+               maint_prog = argv[2];
+               argc -= 2;
+               argv += 2;
+       }
+       /* trap an alarm signal This is the way to kill the bind_process*/
+       signal(SIGTERM, die);
+
+       /* check to see if already running */
+
+       callrpc("localhost", BOOK_STATUS, BOOKVERS, STATUS_DIE,
+               (xdrproc_t)xdr_void, (char *)NULL, 
+               (xdrproc_t)xdr_void, (char *)NULL);
+
+       (void)pmap_unset(BOOK_STATUS, BOOKVERS);
+    
+       transp = svcudp_create(RPC_ANYSOCK);
+       if (transp == NULL) {
+               (void)fprintf(stderr, "cannot create udp service.\n");
+               exit(1);
+       }
+       if (!svc_register(transp, BOOK_STATUS, BOOKVERS, book_status_2, IPPROTO_UDP)) {
+               (void)fprintf(stderr, "unable to register (BOOK_STATUS, BOOKVERS, udp).\n");
+               exit(1);
+       }
+    
+       transp = svctcp_create(RPC_ANYSOCK, 0, 0);
+       if (transp == NULL) {
+               (void)fprintf(stderr, "cannot create tcp service.\n");
+               exit(1);
+       }
+       if (!svc_register(transp, BOOK_STATUS, BOOKVERS, book_status_2, IPPROTO_TCP)) {
+               (void)fprintf(stderr, "unable to register (BOOK_STATUS, BOOKVERS, tcp).\n");
+               exit(1);
+       }
+    
+       init_databases(argc-1, argv+1);
+    
+       /* now fork a manager process to handle the forwarding of tables */
+       start_node_manager(maint_prog); 
+    
+       svc_run();
+       (void)fprintf(stderr, "svc_run returned\n");
+       exit(1);
+    
+}
diff --git a/manager/cache_names.c b/manager/cache_names.c
new file mode 100644 (file)
index 0000000..cd40042
--- /dev/null
@@ -0,0 +1,91 @@
+
+/*
+ * the book_manager caches name/number mapping that it finds in
+ * BOOK/cache so that these mapping are available for logon/logoff even
+ * the database is inaccessable
+ *
+ * the format if the file is simply "typenumber number name\n"
+ *
+ */
+
+#include       <stdio.h>
+#include       <string.h>
+
+static void open_cachef(char *mode);
+
+static FILE *cachef = NULL;
+static int next_ent(int *type, int *num, char **name)
+{
+    char linebuf[1024];
+    static char nm[200];
+    int n, t;
+
+    if (cachef == NULL)
+       open_cachef("r");
+
+    if (cachef == NULL)
+       return 0;
+
+    if (fgets(linebuf, sizeof(linebuf), cachef)== NULL)
+    {
+       fclose(cachef);
+       cachef = NULL;
+       return 0;
+    }
+
+    nm[0]=0;
+    sscanf(linebuf, "%d %d %[^\n]\n", &t, &n, nm);
+    if (nm[0])
+    {
+       *type = t;
+       *num = n;
+       *name = nm;
+       return 1;
+    }
+    else
+    {
+       fclose(cachef);
+       cachef = NULL;
+       return 0;
+    }
+}
+
+static void open_cachef(char *mode)
+{
+    cachef = fopen("/var/book/cache", mode);
+}
+
+void reset_cache()
+{
+    if (cachef)
+       fclose(cachef);
+    cachef = NULL;
+}
+
+int get_mappingint_cache(char *name, int type)
+{
+    int t, n;
+    char *nm;
+    
+    reset_cache();
+    while (next_ent(&t, &n, &nm))
+    {
+       if (t == type && strcmp(name, nm)==0)
+           return n;
+    }
+    return -1;
+}
+
+char *get_mappingchar_cache(int num, int type)
+{
+    int t, n;
+    char *nm;
+    
+    reset_cache();
+    while (next_ent(&t, &n, &nm))
+    {
+       if (t == type && num == n)
+           return strdup(nm);
+    }
+    return NULL;
+}
diff --git a/manager/check_default.c b/manager/check_default.c
new file mode 100644 (file)
index 0000000..6537730
--- /dev/null
@@ -0,0 +1,107 @@
+
+#include       <time.h>
+#include       "helper.h"
+
+
+/*
+ * check if anyone appears to have defaulted on a booking
+ * just now.
+ * for each workstation
+ *   if    it is allocated/tentative
+ *     and it's allocation time is before now, but after last check
+ *     and user allocated is not on
+ *   then
+ *     record default for allocated user in this lab for this period
+ *
+ */
+
+void check_default(table *t)
+{
+    static time_t last_check = 0;
+    time_t now = time(0);
+    int lab;
+    int grace = get_configint("grace");
+    time_t alloctime;
+    int ind;
+
+    if (grace==0) grace=7*60;
+    
+    if (last_check == 0)
+    {
+       last_check = now;
+       return;
+    }
+
+    for (ind=0 ; ind < t->table_len ; ind++)
+    {
+       if (Cluster_type(t,ind)== ALLOCCLUSTER)
+       {
+           lab = Cluster_id(t,ind);
+           alloctime = Cluster_modtime(t,ind);
+       }
+       if (Cluster_type(t,ind) == WSRESCLUSTER
+           && (Res_status(t,ind) == NODEALLOCATED
+               ||Res_status(t,ind) == NODETENTATIVE)
+           && Res_user(t,ind) < get_class_min()
+           && alloctime +grace <= now
+           && alloctime +grace > last_check
+           && Cluster_type(t,ind-1) == WSSTATUSCLUSTER
+           && Cluster_id(t,ind-1) == Cluster_id(t,ind)
+/*         && Ws_user(t,ind-1) != Res_user(t,ind)   */
+           )
+       {
+           /* ok, a rescluster which might be for us.
+            * must check the ws belong to us, and that
+            * who-on is correct
+            */
+           workstation_state *ws = get_wsstate(Cluster_id(t,ind),0);
+           if (ws
+               && (ws->host == my.hostid
+                   || (ws->host <0 && Ws_whereon(t,ind-1)==my.hostid)
+                   || (ws->host <0 && Ws_whereon(t,ind-1)<0 && am_master)
+                   )
+               )
+           {
+               int doy, pod;
+               book_set_claim *cp;
+               book_change ch;
+               time_t whenon;
+               ch.chng = CLAIM_BOOKING;
+               cp = & ch.book_change_u.aclaim;
+
+               get_doypod(&alloctime, &doy, &pod);
+               cp->slot = make_bookkey(doy, pod, lab);
+               cp->user = Res_user(t,ind);
+               cp->when = 0;
+               cp->where = -1;
+               if (check_whoison(Cluster_id(t,ind),  &whenon) ==
+                   Res_user(t,ind))
+               {
+                   int delta_whenon = whenon - alloctime;
+                   if (delta_whenon < -10*60)
+                       delta_whenon = grace;
+                   cp->claimtime = DID_LOGIN | ((delta_whenon+30*60) <<4);
+                   cp->where = Cluster_id(t,ind);
+               }
+               else if (Cluster_modtime(t,ind) > alloctime + 30)
+               {
+                   /* looks like they claimed, but haven't logged on yet, give them the benefit of the doubt */
+                   cp->claimtime = 0;
+               }
+               else
+               {
+                   /* Ok, looks like a defaulter to me!! */
+                   cp->claimtime = DID_DEFAULT;
+               }
+               {
+                   char buf[100];
+                   sprintf(buf, "check_default set %x for user %d on %d (d%d,p%d,l%d)\n",
+                           cp->claimtime, (int)cp->user, cp->where, doy, pod, lab);
+                   shout(buf);
+               }
+               send_change(&ch);
+           }
+       }
+    }
+    last_check = now;
+}
diff --git a/manager/check_servers.c b/manager/check_servers.c
new file mode 100644 (file)
index 0000000..41c7689
--- /dev/null
@@ -0,0 +1,116 @@
+
+/*
+ * check servers:
+ *  make sure the servers list in the status server
+ *  is vaguely correct
+ *
+ * use refresh_db_client to find atleast one database.
+ * walk though the replicas table to get a list of all names
+ * for each database, contact it and inform book_status of state.
+ * call get_database_2 to get local idea
+ * for each in local list but not in replica list,
+ * tell book_status to forget it.
+ * 
+ */
+
+#include       "helper.h"
+#include       "../lib/dlink.h"
+
+void check_servers(void)
+{
+    void *replicas = dl_head();
+    database_list *dblist;
+    query_req request;
+    query_reply *result;
+    char *rep;
+
+    if (refresh_db_client()== 0)
+       return; /* cannot do anything!! */
+
+
+    request.key.item_val = memdup("dummy", 6);
+    request.key.item_len = 5;
+    request.type = M_FIRST;
+    request.database = REPLICAS;
+
+    result = query_db_2(&request, db_client);
+    request.type = M_NEXT;
+    
+    while (result != NULL && result->error == R_RESOK)
+    {
+       char *nm;
+
+       free(request.key.item_val);
+       request.key = result-> query_reply_u.data.key;
+       request.key.item_val = memdup(result-> query_reply_u.data.key.item_val,result-> query_reply_u.data.key.item_len);
+       nm = dl_strndup(result->query_reply_u.data.key.item_val,
+                       result->query_reply_u.data.key.item_len);
+       dl_insert(replicas, nm);
+       xdr_free((xdrproc_t)xdr_query_reply, (char*) result);
+       result = query_db_2(&request, db_client);
+    }
+    free(request.key.item_val);
+    xdr_free((xdrproc_t)xdr_query_reply, (char*) result);
+
+    for (rep = dl_next(replicas) ; rep != replicas ; rep=dl_next(rep))
+    {
+       /* check out this replica */
+       database_state st;
+       open_db_client(rep, NULL);
+       if (db_client)
+       {
+           update_status *upst;
+           upst = get_updatest_2(NULL, db_client);
+           if (upst == NULL)
+               st = DB_DOWN;
+           else if (*upst != FULL)
+               st = DB_READONLY;
+           else
+               st = DB_UP;
+       }
+       else
+           st = DB_DOWN;
+       {
+           /* better try telling the status about the difference */
+           database_info dbi;
+           dbi.name = rep;
+           dbi.state = st;
+           set_database_2(&dbi, status_client);
+       }
+    }
+
+    if (result && result->error == R_NONEXT)
+    {
+       dblist= get_databases_2(NULL, status_client);
+       if (dblist != NULL)
+       {
+           int db;
+           for (db = 0
+                ; db < dblist->database_list_len
+                  && dblist->database_list_val[db].state != DB_UNREGISTERED
+                ; db++)
+           {
+               for (rep = dl_next(replicas) ; rep != replicas &&
+                        strcmp(rep, dblist->database_list_val[db].name)!= 0
+                    ; rep = dl_next(rep));
+               if (rep == replicas)
+               {
+                   /* this database  nolonger exists, trash it */
+                   database_info dbi;
+                   dbi.name = dblist->database_list_val[db].name;
+                   dbi.state = DB_UNREGISTERED;
+                   set_database_2(&dbi, status_client);
+               }
+               
+           }
+           xdr_free((xdrproc_t)xdr_database_list, (char*)dblist);
+       }
+    }
+    while (dl_next(replicas)!= replicas)
+    {
+       rep = dl_next(replicas);
+       dl_del(rep);
+       dl_free(rep);
+    }
+    refresh_db_client();
+}
diff --git a/manager/check_status.c b/manager/check_status.c
new file mode 100644 (file)
index 0000000..f5c2f0d
--- /dev/null
@@ -0,0 +1,164 @@
+
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <time.h>
+#include       "helper.h"
+#include       <sys/socket.h>
+#include       <netinet/in.h>
+#include       <netdb.h>
+#include       <netinet/tcp.h>
+
+
+/* check_status
+ * ping any workstation connected to me
+ * and check whoison file for any workstation
+ * logged onto me, or not logged on and not tied to non-me host
+ *
+ * only check if info > 5 minutes old
+ *
+ */
+
+static int can_ping(entityid wsid)
+{
+    /* attempt to ping by connecting to X11 port */
+    char whost[200];
+    char *colon;
+    int port;
+    struct hostent *he;
+    struct sockaddr_in sin;
+    int sock;
+    char *wsname = get_mappingchar(wsid, M_WORKSTATION);
+    if (wsname == NULL) return FALSE;
+    strcpy(whost, wsname);
+    free(wsname);
+    colon = strchr(whost, ':');
+    if (colon == NULL) return TRUE; /* assume it is there?? */
+    *colon++ = '\0';
+    he = gethostbyname(whost);
+    if (he == NULL)
+    {
+       strcat(whost, ".x"); /* FIXME change out xterminal names in booking system */
+       he = gethostbyname(whost);
+    }
+    if (he == NULL)
+       return FALSE;
+    port = 6000 + atoi(colon);
+    memset(&sin, 0, sizeof(sin));
+    memcpy(&sin.sin_addr, he->h_addr_list[0], he->h_length);
+    sin.sin_port = htons(port);
+    sin.sin_family = AF_INET;
+    sock = socket(AF_INET, SOCK_STREAM, 0);
+    if (sock < 0) return TRUE;
+#ifdef TCP_CONN_ABORT_THRESHOLD
+    {
+       int thresh;
+       thresh = 5*1000;
+       setsockopt(sock, IPPROTO_TCP, TCP_CONN_ABORT_THRESHOLD, (char*)&thresh, sizeof(thresh));
+#ifdef TCP_CONN_NOTIFY_THRESHOLD
+       setsockopt(sock, IPPROTO_TCP, TCP_CONN_NOTIFY_THRESHOLD, (char*)&thresh, sizeof(thresh));
+#endif
+    }
+#endif
+    if (connect(sock, (struct sockaddr *)&sin, sizeof(sin))== 0)
+    {
+       close(sock);
+       return TRUE;
+    }
+    else
+    {
+       close(sock);
+       return FALSE;
+    }
+}
+
+
+#ifdef WIN32
+/* since there are no book_login/book_logout, we are responsible for updating things here,
+   hence need a more frequent update cycle */
+#define CHECK_FREQUENCY        10
+#else
+#define CHECK_FREQUENCY        5*60
+#endif
+
+void check_status(table *t)
+{
+    time_t now = time(0);
+    time_t then = now - CHECK_FREQUENCY;
+    static time_t firsttime = 0;
+    int ind;
+
+    /* make sure all workstations get pings ASAP after reboot */
+    if (firsttime == 0) firsttime = now;
+    if (then < firsttime) then = firsttime;
+    
+    for (ind = 0 ; ind < t->table_len && now < then + CHECK_FREQUENCY+20; ind++)
+       if (Cluster_type(t,ind) == WSSTATUSCLUSTER
+           && Cluster_modtime(t,ind) < then
+           )
+       {
+           cluster *cl = &t->table_val[ind];
+           workstation_state *ws = get_wsstate(cl->identifier,0);
+           if ((ws_user(cl) >= 0 && ws_whereon(cl) == my.hostid)
+               ||
+               (ws &&
+                ws_user(cl) < 0
+                && ( ws->host == my.hostid || ws->host < 0)
+                )
+               )
+           {
+               /* logged onto me, or
+                *  nobody logged in, and not someone elses
+                */
+               int who;
+               time_t when;
+               who = check_whoison(cl->identifier, &when);
+               if (who != cl->data.data_val[0])
+               {
+                   /* this info is WRONG, correct it */
+                   cl->data.data_val[0] = who;
+                   if (who >= 0)
+                   {
+                       ws_whenon(cl) = when;
+                       ws_whereon(cl) = my.hostid;
+                   }
+                   else
+                       ws_whereon(cl) = -1;
+               }
+
+               if (ws)
+               {
+                   if (ws->host == my.hostid
+                       || (am_master && ws->host <= 0))
+                   {
+                       if (can_ping(cl->identifier))
+                           ws_status(cl) = NODEUP;
+                       else
+                           ws_status(cl) = NODEDOWN;
+                   }
+                   
+               }
+               cl->timestamp = now;
+           }
+           else
+           {
+               /* if there is a controling host, which is down, mark ws as down, 5 minutes ago, so when host
+                  comes up it will mark ws as up  */
+               if (ws && ws->host >= 0 && cl->data.data_val[1] == NODEUP && am_master)
+               {
+                   int i;
+                   for (i=0 ; i<t->table_len ; i++)
+                       if (t->table_val[i].cltype == HOSTCLUSTER
+                           && t->table_val[i].identifier == ws->host
+                           && host_status(&t->table_val[i]) == NODEDOWN)
+                       {
+                           cl->data.data_val[1] = NODEDOWN;
+                           if (cl->timestamp >= now-5*60)
+                               cl->timestamp++;
+                           else
+                               cl->timestamp = now - 5*60;
+                       }
+               }
+           }
+           now = time(0);
+       }
+}
diff --git a/manager/choose_db.c b/manager/choose_db.c
new file mode 100644 (file)
index 0000000..4125ae8
--- /dev/null
@@ -0,0 +1,88 @@
+
+/*
+ * chose and refresh database client handle.
+ * client handle is stored in "db_client"
+ * and refresh_db_client is called to chose a new database server
+ */
+
+/*
+ * Call local book_status and get database_list
+ * Iterate through databases calling them until one responds with
+ * accessable status
+ *
+ * If the status we find disagrees with status in table, tell
+ * the book_status about it
+ */
+
+#include       "../db_client/db_client.h"
+
+int refresh_db_client(void)
+{
+       database_list *dblist;
+       int db;
+    
+       if (db_client)
+               clnt_destroy(db_client);
+       db_client = NULL;
+
+       if (status_client == NULL)
+               open_status_client("localhost");
+       if (status_client == NULL)
+               return 0;
+       dblist = get_databases_2(NULL, status_client);
+       if (dblist == NULL)
+       {
+               open_status_client("localhost");
+               if (status_client)
+                       dblist = get_databases_2(NULL, status_client);
+       }
+       if (dblist == NULL)
+               return 0;
+       for (db= 0 ;
+            db < dblist->database_list_len  &&
+                    dblist->database_list_val[db].state != DB_UNREGISTERED ;
+            db++)
+       {
+               database_state st;
+       
+               open_db_client(dblist->database_list_val[db].name, NULL);
+               if (db_client)
+               {
+                       /* its up, lets check writable */
+                       update_status *upst;
+                       upst = get_updatest_2(NULL, db_client);
+                       if (upst == NULL)
+                               st = DB_DOWN;
+                       else if (*upst != FULL)
+                               st = DB_READONLY;
+                       else
+                               st = DB_UP;
+               }
+               else
+                       st = DB_DOWN;
+               if (dblist->database_list_val[db].state == DB_CLOSE)
+                       dblist->database_list_val[db].state = DB_UP;
+               if (st != dblist->database_list_val[db].state)
+               {
+                       /* better try telling the status about the difference */
+                       database_info dbi;
+                       dbi = dblist->database_list_val[db];
+                       dbi.state = st;
+                       set_database_2(&dbi, status_client);
+               }
+               if (st == DB_UP)
+               {
+                       xdr_free((xdrproc_t)xdr_database_list, (char*) dblist);
+                       return 1;
+               }
+               else
+               {
+                       if (db_client)
+                               clnt_destroy(db_client);
+                       db_client = NULL;
+               }
+       }
+       xdr_free((xdrproc_t)xdr_database_list, (char*) dblist);
+       return 0;
+}
+
diff --git a/manager/clnt_create.c b/manager/clnt_create.c
new file mode 100644 (file)
index 0000000..8654a01
--- /dev/null
@@ -0,0 +1,99 @@
+
+/* creates a rpc handle, bypassing the hostname lookup if possible. */
+ /* This is achieved by cacheing the addresses.  This has been made */
+ /* necessary by the lack of a reliable name server, it's not a bad */
+ /* idea anyway.  Perhaps the entries should be timed out after a while??? */
+
+#include       <rpc/rpc.h>
+#include       <netdb.h>
+#include       "../lib/skip.h"
+#include       "../lib/misc.h"
+extern char * strdup();
+
+static void * sl = NULL;
+
+/* the data structure for the skiplist cache */
+typedef struct hname_node
+{
+    char * hostname;
+    struct sockaddr_in data;
+} hname_node;
+
+/* a comparison function for the skiplist */
+static int node_cmp(hname_node * h1, hname_node * h2, char * name)
+{
+    return ((h2 != NULL)? strcmp(h1->hostname, h2->hostname) :
+       strcmp(h1->hostname, name));
+    
+}
+
+/* a function to free a skiplist node */
+static void node_free(hname_node * n1)
+{
+    free(n1->hostname);
+}
+
+/* set up the skiplist initially */
+void hostcache_init()
+{
+    sl = skip_new(node_cmp,node_free, NULL);
+}
+
+
+CLIENT * my_clntudp_create(char * hostname, int prog, int vers )
+{
+    char errbuf[128];
+    struct sockaddr_in sin;
+    static struct timeval tv = {5,0 } ;
+    int sock = RPC_ANYSOCK;
+    hname_node **result;
+
+    if (sl == NULL)
+       hostcache_init();
+    result = skip_search(sl, hostname);
+    
+    
+    /* if not present look it up and add it */
+    if (result == NULL) {
+       struct hostent * he;
+
+/*     printf("Not in cache %s\n",hostname); */
+       he = gethostbyname(hostname);
+       if (he == NULL) {
+           sprintf(errbuf, "Hostname lookup for %s failed\n",hostname);
+           shout(errbuf);
+           return  NULL;
+       }
+       else {
+           hname_node * new_el = (hname_node*)malloc(sizeof(hname_node));
+           sin.sin_family = he -> h_addrtype;
+           sin.sin_port =0;
+           bzero(sin.sin_zero, sizeof(sin.sin_zero));
+           bcopy(he->h_addr, &sin.sin_addr, he->h_length);
+           /*printf("Adding %s as %s\n", hostname,
+                  inet_ntoa(sin.sin_addr));
+                  */
+           /* fix up node to add to cache */
+           new_el->hostname = strdup(hostname);
+           bcopy(&sin, &new_el->data, sizeof(struct sockaddr_in));
+           skip_insert(sl, new_el); 
+           
+       }
+    }
+    else
+    {
+/*     printf("Found in cache %s\n",hostname); */
+       bcopy(&(*result)->data, &sin, sizeof(struct sockaddr_in));
+    }
+/*    printf("Address to contact %s\n",inet_ntoa(sin.sin_addr)); */
+    
+    return(clntudp_create(&sin, prog, vers, tv, &sock));
+}
+
+
+
+
+
+
+
+
diff --git a/manager/collect_table.c b/manager/collect_table.c
new file mode 100644 (file)
index 0000000..8348f02
--- /dev/null
@@ -0,0 +1,48 @@
+
+#include       "statush.h"
+
+bool_t collect_tab(table * newtab, CLIENT *cl)
+{
+    static int index;
+    char * xdrbuf;
+    XDR xdrs;
+    table_chunk * result;
+    int timestamp;
+    bool_t status;
+    int max;
+    
+    index =0;
+    result = get_chunk_2(&index, cl);
+    if (result == NULL) {
+       return FALSE;
+    }
+    max = result->chnk.max;
+    
+    xdrbuf = (char*)malloc(max);
+    timestamp = result->chnk.timestamp;
+    
+    while (index < max) {
+       bcopy(result->data.data_val, xdrbuf + result->chnk.index,
+             result->data.data_len);
+       index += result->data.data_len;
+       
+       xdr_free((xdrproc_t)xdr_table_chunk, (char*) result);
+       /* check for finish */
+       if (index == max)
+           break;
+       
+       result = get_chunk_2(&index, cl);
+       if (result == NULL)
+       {
+           return FALSE;
+       }
+       
+    }
+    xdrmem_create(&xdrs, xdrbuf, max, XDR_DECODE);
+    bzero(newtab, sizeof(table));
+    status = xdr_table(&xdrs, newtab);
+    
+    free(xdrbuf); 
+    return(status);
+       
+}
diff --git a/manager/create_table.c b/manager/create_table.c
new file mode 100644 (file)
index 0000000..4083b04
--- /dev/null
@@ -0,0 +1,226 @@
+
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <time.h>
+#include       "helper.h"
+#include       <sys/stat.h>
+#include       <sys/file.h>
+#ifdef sun
+#include       <sys/fcntl.h>
+#endif
+#include       "../lib/skip.h"
+
+int check_whoison(int wsid, time_t *when)
+{
+
+       char *wsname;
+       int r;
+       wsname = get_mappingchar(wsid, M_WORKSTATION);
+       if (wsname == NULL)
+               return -1;
+       r = get_whoison(wsname, when);
+       free(wsname);
+       return r;
+}
+
+
+
+static void init_cluster(cluster * cl, cluster_type ct, int id, int cltime, int dcount,
+            int a, int b)
+{
+       cl->cltype = ct;
+       cl->identifier = id;
+       cl->timestamp = cltime;
+       cl->data.data_len = dcount;
+       cl->data.data_val = (int*) malloc (dcount * sizeof(int));
+       if (dcount >= 1)
+               cl->data.data_val[0] = a;
+       if (dcount >= 2)
+               cl->data.data_val[1] = b;
+
+       while (--dcount >= 2)
+               cl->data.data_val[dcount] = 0;
+}
+
+struct mine  my = { 0 };
+
+struct ws_state
+{
+       entityid wsid;
+       workstation_state state;
+};
+static int ws_cmp(struct ws_state *a, struct ws_state *b, int *idp)
+{
+       int id;
+       if (b) id = b->wsid;
+       else id = *idp;
+       return a->wsid - id;
+}
+static void ws_free(struct ws_state *a)
+{
+       xdr_free((xdrproc_t)xdr_workstation_state, (char*) &a->state);
+       free(a);
+}
+
+workstation_state *get_wsstate(entityid wsid, int no_cache)
+{
+    
+       struct ws_state **p, *ws;
+       workstation_state state;
+       char key[20];
+    
+
+       if (my.wslist == NULL)
+               my.wslist = skip_new(ws_cmp, ws_free, NULL);
+       
+       p = skip_search(my.wslist, &wsid);
+       if (p) {
+               if (no_cache)
+                       skip_delete(my.wslist, &wsid);
+               else
+                       return &(*p)->state;
+       }
+
+       memset(&state, 0, sizeof(state));
+       if (db_read(key_int(key, C_WORKSTATIONS, wsid), &state, (xdrproc_t)xdr_workstation_state, CONFIG) != R_RESOK)
+               return NULL;
+       ws = (struct ws_state*)malloc(sizeof(struct ws_state));
+       ws->wsid = wsid;
+       ws->state = state;
+       skip_insert(my.wslist, ws);
+       return &ws->state;
+}
+
+
+int  load_config(void)
+{
+       item key;
+       char k[20];
+       int i;
+
+       if (my.hostname == NULL)
+               my.hostname = get_myhostname();
+       if (my.wslist == NULL)
+               my.wslist = skip_new(ws_cmp, ws_free, NULL);
+
+       memset(&my.hostdata, 0, sizeof(my.hostdata));
+       memset(&my.hgdata, 0, sizeof(my.hgdata));
+    
+       my.hostid = get_mappingint(my.hostname, M_HOST);
+       if (my.hostid < 0)
+               return my.hostid;
+
+       key = key_int(k, C_HOSTS, my.hostid);
+       if (db_read(key, &my.hostdata, (xdrproc_t)xdr_hostinfo, CONFIG) != R_RESOK)
+               return -1;
+
+       my.hgid = my.hostdata.clump;
+
+       key = key_int(k, C_HOSTGROUPS, my.hgid);
+       if (db_read(key, &my.hgdata, (xdrproc_t)xdr_hostgroup_info, CONFIG) != R_RESOK)
+       {
+               xdr_free((xdrproc_t)xdr_hostinfo, (char*) &my.hostdata);
+               return -1;
+       }
+
+       my.labcnt = my.hgdata.labs.entitylist_len;
+
+       my.labdata = (hostinfo *)malloc(sizeof(hostinfo)*my.labcnt);
+       memset(my.labdata, 0, sizeof(hostinfo)*my.labcnt);
+       my.wscnt = 0;
+       for (i=0 ; i<my.labcnt ; i++)
+       {
+               key = key_int(k, C_LABS, my.hgdata.labs.entitylist_val[i]);
+               if (db_read(key, &my.labdata[i], (xdrproc_t)xdr_hostinfo, CONFIG) != R_RESOK)
+               {
+                       xdr_free((xdrproc_t)xdr_hostinfo, (char*) &my.hostdata);
+                       xdr_free((xdrproc_t)xdr_hostgroup_info, (char*) &my.hgdata);
+                       while (--i >= 0)
+                               xdr_free((xdrproc_t)xdr_hostinfo, (char*) &my.labdata[i]);
+                       free(my.labdata);
+                       return -1;
+               }
+               my.wscnt += my.labdata[i].workstations.entitylist_len;
+       }
+       return 0;
+}
+
+static int ecmp(const void *av, const void *bv)
+{
+       const entityid *a=av, *b=bv;
+       return *a - *b;
+}
+static void sort_entitylist(entitylist *l)
+{
+       qsort(l->entitylist_val, l->entitylist_len, sizeof(entityid), ecmp);
+}
+
+void new_table(table *newtab)
+{
+       int i,j;
+       time_t now;
+       int cnum;
+    
+       now = time(0);
+
+       /*
+        * Need
+        * 1 for the hostgroup
+        * 1 cluster per lab
+        * 1 per host
+        * 2 per workstation
+        */
+       newtab->table_len = 1 + my.labcnt + my.hgdata.hosts.entitylist_len + my.wscnt*2;
+       newtab->table_val = (cluster*)malloc(newtab->table_len * sizeof(cluster));
+
+       cnum = 0;
+
+       /* table cluster, data is creator */
+       init_cluster(&newtab->table_val[cnum++],
+                    TABLECLUSTER, my.hgid, now,
+                    1,  my.hostid, 0);
+
+       /* host clusters. Data is NODE{UP,DOWN} */
+       sort_entitylist(&my.hgdata.hosts);
+       for (i=0 ; i<my.hgdata.hosts.entitylist_len ; i++)
+               init_cluster(&newtab->table_val[cnum++],
+                            HOSTCLUSTER,  my.hgdata.hosts.entitylist_val[i],
+                            my.hgdata.hosts.entitylist_val[i] == my.hostid? now:0,
+                            1, NODEUP, 0);
+
+       /* lab clusters. Data is alloc status and host */
+       for (i=0 ; i<my.labcnt ; i++)
+       {
+               init_cluster(&newtab->table_val[cnum++],
+                            ALLOCCLUSTER, my.hgdata.labs.entitylist_val[i], 0,
+                            2, PERIODFREE, -1);
+
+               /* workstation and reservation clusters
+                * workstation: who, NODE{UP,DOWN}, when-on, hoston, when alloc
+                * reservation: whofor, alloc status
+                */
+               sort_entitylist(&my.labdata[i].workstations);
+               for (j = 0 ; j< my.labdata[i].workstations.entitylist_len ; j++)
+               {
+                       int who;
+                       time_t when;
+                       int wsid = my.labdata[i].workstations.entitylist_val[j];
+                       who = check_whoison(wsid, &when);
+
+                       init_cluster(&newtab->table_val[cnum],
+                                    WSSTATUSCLUSTER, wsid, who>=0? now:0,
+                                    5, who, NODEUP);
+                       if (who>=0)
+                       {
+                               newtab->table_val[cnum].data.data_val[2] = when;
+                               newtab->table_val[cnum].data.data_val[3] = my.hostid;
+                               newtab->table_val[cnum].data.data_val[4] = now;
+                       }
+                       cnum++;
+
+                       init_cluster(&newtab->table_val[cnum++],
+                                    WSRESCLUSTER, wsid, 0,
+                                    3, -1, NODENOTALLOC);
+               }
+       }
+}
diff --git a/manager/db_loc.c b/manager/db_loc.c
new file mode 100644 (file)
index 0000000..b8ceb6c
--- /dev/null
@@ -0,0 +1,178 @@
+
+#include       <time.h>
+#include       <unistd.h>
+#include       "statush.h"
+
+/* manage list of databases
+ * this is initialised from a file
+ * and periodically maintained by the helper processes
+ */
+
+static database_list databases;
+
+/* keep a list of "close" databases
+ * this is initialised from comm line args
+ * names removed when told UNREGISTERED
+ * names added to top when told CLOSE
+ *
+ * names are stored so that a bigger index number is closer 
+ */
+char **close_dbs;
+int close_db_cnt = 0;
+
+void add_close(char *name)
+{
+       char **new = (char**)malloc((close_db_cnt+1)*sizeof(char*));
+       int i;
+       for (i=0 ; i<close_db_cnt ; i++)
+               new[i] = close_dbs[i];
+       new[close_db_cnt] = strdup(name);
+       if (close_db_cnt) free(close_dbs);
+       close_db_cnt++;
+       close_dbs = new;
+}
+
+int find_close(char *name)
+{
+       int i;
+       for (i=0 ; i < close_db_cnt ; i++)
+               if (strcmp(name, close_dbs[i])==0)
+                       return i;
+       return -1;
+}
+
+void del_close(int num)
+{
+       int i;
+       if (num < 0 || num >= close_db_cnt)
+               return;
+       free(close_dbs[num]);
+       for (i=num+1 ; i < close_db_cnt ; i++)
+               close_dbs[i-1] = close_dbs[i];
+       close_db_cnt--;
+}
+
+static int cmp_dbs(const void *av, const void *bv)
+{
+       const database_info *a = av;
+       const database_info *b = bv;
+       int ca, cb;
+       if (a->state != b->state)
+               return a->state - b->state;
+
+       ca = find_close(a->name);
+       cb = find_close(b->name);
+       if (ca != cb)
+               return cb - ca;
+       return strcmp(a->name, b->name);
+}
+
+    
+static void sort_dbs()
+{
+       qsort(databases.database_list_val,
+             databases.database_list_len,
+             sizeof(database_info),
+             cmp_dbs);
+}
+
+void set_database(char *name, database_state state)
+{
+       int i;
+       if (state == DB_UP && find_close(name)>= 0)
+               state = DB_CLOSE;
+       if (state == DB_CLOSE && find_close(name)<0)
+               add_close(name);
+    
+       for (i=0 ; i<databases.database_list_len ; i++)
+               if (strcmp(databases.database_list_val[i].name,
+                          name)==0)
+               {
+                       databases.database_list_val[i].state = state;
+                       break;
+               }
+
+       if (i == databases.database_list_len)
+       {
+               database_info *new = (database_info*)malloc(sizeof(database_info)*(i+1));
+
+               for (i=0 ; i < databases.database_list_len ; i++)
+                       new[i] = databases.database_list_val[i];
+               new[i].name = strdup(name);
+               new[i].state = state;
+               if (databases.database_list_val)
+                       free(databases.database_list_val);
+               databases.database_list_val = new;
+               databases.database_list_len ++;
+       }
+       sort_dbs();
+    
+}
+
+database_list *SVC(get_databases_2)(void *x, struct svc_req *rq)
+{
+       /* make sure the helper runs atleast every 10 minutes to make sure I am uptodate */
+       static time_t last=0;
+       if (last + 10*60 < time(0))
+       {
+               check_helper(0);
+               last = time(0);
+       }
+       return &databases;
+}
+
+void *SVC(set_database_2)(database_info *i, struct svc_req *rq)
+{
+       static int x;
+    
+       set_database(i->name, i->state);
+       write_databases();
+       return &x;
+}
+
+void init_databases(int argc, char *argv[])
+{
+       FILE *dbf;
+       int arg;
+       for (arg = 0 ; arg<argc ; arg++)
+               add_close(argv[arg]);
+    
+       databases.database_list_val = NULL;
+       databases.database_list_len = 0;
+
+       dbf = fopen(DB_LOC_FILE, "r");
+       if (dbf)
+       {
+               char buf[200];
+               while (fgets(buf, 200, dbf)!=  NULL)
+               {
+                       char *e = strchr(buf, '\n');
+                       if (e) *e = '\0';
+                       set_database(buf, DB_UNKNOWN);
+               }
+               fclose(dbf);
+       }
+}
+
+int write_databases(void)
+{
+       FILE *dbf;
+       int i;
+
+       dbf = fopen(DB_LOC_TMP, "w");
+    
+       for (i= 0 ; i < databases.database_list_len ; i++)
+               if (databases.database_list_val[i].state != DB_UNREGISTERED)
+                       fprintf(dbf, "%s\n", databases.database_list_val[i].name);
+       fflush(dbf);
+       if (ferror(dbf))
+       {
+               fclose(dbf);
+               unlink(DB_LOC_TMP);
+               return -1;
+       }
+       fclose(dbf);
+       rename(DB_LOC_TMP, DB_LOC_FILE);
+       return 0;
+}
+
diff --git a/manager/evictions.c b/manager/evictions.c
new file mode 100644 (file)
index 0000000..c058645
--- /dev/null
@@ -0,0 +1,250 @@
+
+#include       <stdio.h>
+#include       <stdlib.h>
+#include       <unistd.h>
+#include       <fcntl.h>
+#include       <time.h>
+#include       "helper.h"
+#include       <sys/stat.h>
+
+#include       "../lib/skip.h"
+/*
+ * current evictions are store in a skip_list
+ * index is workstation-id
+ * content is userid, wsname, time to leave, time of last warning
+ */
+
+char EvictLog[] = "/var/book/evict_log";
+void *evictlist = NULL;
+
+typedef struct evicts {
+    entityid   wsid;
+    bookuid_t  whom;
+    char       *wsname;
+    time_t     leavetime;
+    time_t     lastwarn;
+} *evicts;
+
+static int cmp_ev(evicts a, evicts b, entityid *c)
+{
+    entityid k;
+    if (b) k=b->wsid;
+    else k = *c;
+    return k - a->wsid;
+}
+static void free_ev(evicts a)
+{
+    free(a->wsname);
+    free(a);
+}
+
+static void etrace(char *msg, evicts ev)
+{
+    int fd;
+
+    fd = open(EvictLog, O_WRONLY|O_APPEND|O_CREAT, 0600);
+    if (fd >= 0)
+    {
+       char buf[1024];
+       struct tm *tm;
+       time_t now;
+
+       fchown(fd, 0, 0);
+       fchmod(fd, 0640);
+
+       time(&now);
+       tm = localtime(&now);
+       sprintf(buf, "%02d%02d%02d.%02d%02d%02d %s %s %s(%d) uid=%d leavein=%ds lastwarn=%ds\n",
+               tm->tm_year, tm->tm_mon+1, tm->tm_mday,
+               tm->tm_hour, tm->tm_min, tm->tm_sec,
+               my.hostname, msg, ev->wsname, ev->wsid,
+               (int)ev->whom, (int)(ev->leavetime-now), (int)(now-ev->lastwarn));
+
+       write(fd, buf, strlen(buf));
+       close(fd);
+    }
+}
+
+static int am_evicting(int wsid)
+{
+    return evictlist != NULL
+       && skip_search(evictlist, &wsid)!= NULL;
+}
+
+static void forget_evict(int wsid)
+{
+    if (evictlist)
+    {
+       evicts ev, *evp;
+       evp = skip_search(evictlist, &wsid);
+       if (evp)
+       {
+           ev = *evp;
+           etrace("forgeting", ev);
+           skip_delete(evictlist, &wsid);
+       }
+    }
+}
+
+/* this process ensures that the evict process is running */
+static void ensure_evict(int time_to_leave, int wsid, int who)
+{
+    time_t now;
+    int togo; /* how long until logoff */
+    int sincelast; /* how long since last message */
+    int dowarn = 0;
+    evicts ev, *evp;
+
+    if (evictlist==NULL)
+       evictlist = skip_new(cmp_ev, free_ev, NULL);
+    evp = skip_search(evictlist, &wsid);
+    if (evp) ev = *evp;
+    if (evp && ((*evp)->whom  != who))
+    {
+       forget_evict(wsid);
+       evp= NULL;
+    }
+    if (evp == NULL)
+    {
+       ev = (evicts)malloc(sizeof(struct evicts));
+       ev->wsid = wsid;
+       ev->whom = who;
+       ev->wsname = get_mappingchar(wsid, M_WORKSTATION);
+       if (ev->wsname == NULL)
+       {
+           free(ev);
+           return;
+       }
+       ev->lastwarn = 0;
+       time(&now);
+       if (now + 5*60 > time_to_leave)
+           ev->leavetime = now + 5*60;
+       else
+           ev->leavetime = time_to_leave;
+       skip_insert(evictlist, ev);
+    }
+    else
+       ev = *evp;
+    if (time_to_leave > ev->leavetime)
+       ev->leavetime = time_to_leave;
+
+    /* maybe show a message */
+    time(&now);
+    togo = ev->leavetime - now;
+    sincelast = now - ev->lastwarn;
+
+    if (togo < 2)
+    {
+       etrace("forced logoff", ev);
+       force_logoff(ev->wsname);
+    }
+    else if (togo < 90)
+    {
+       if (sincelast > 60)
+           dowarn=1;
+    }
+    else if (togo < 900)
+    {
+       if (sincelast > 180)
+           dowarn = 1;
+    }
+    else if (sincelast > 360)
+       dowarn = 1;
+
+    if (dowarn)
+    {
+       char buf[1024];
+       char tim[10];
+       if (togo > 120)
+           sprintf(tim, "%d minutes", togo/60);
+       else
+           sprintf(tim, "%d seconds", togo);
+       etrace(tim,ev);
+       sprintf(buf,
+"%s\n\n" /* X11 send_message relies on this being here */
+"This is a warning message from the Booking System\n\n"
+"This message was sent at %s\n"
+"This workstation is being reserved for another user\n"
+"Please log off within the next %s\n\n"
+"If you do not logoff within this time, you will be forced off\n",
+                   ev->wsname, ctime(&now), tim);
+       send_message(buf);
+       ev->lastwarn = now;
+    }
+}
+
+
+char *labof(int wsid)
+{
+    workstation_state *st;
+    int labid;
+    st= get_wsstate(wsid,0);
+    labid = desc_2_labind(&st->state);
+    return get_mappingchar(labid, M_ATTRIBUTE);
+}
+
+    
+/* PERFORM_EVICTIONS(): run an evict process for each workstation we */
+ /* control */
+void perform_evictions(table * tab)
+{
+    int i;
+    int book_lev = get_book_level();
+    int grace = get_configint("grace");
+
+    if (grace <=0) grace=7*60;
+
+    /* for each workstation, check eviction */
+    for (i=0 ; i < tab->table_len ; i++)
+       if (Cluster_type(tab,i) == WSSTATUSCLUSTER
+           && (am_evicting(Cluster_id(tab,i))
+               ||Ws_whereon(tab,i) == my.hostid))
+       {
+           /* some logged onto me, check for conflicting reservation */
+           
+           int wsid = Cluster_id(tab,i);
+           int resloc_in_table = lookup_table(tab, WSRESCLUSTER, wsid);
+           int evict=0;
+
+           sprintf(errbuf,"Check EVICT for ws %d\n",wsid);
+           Debug(errbuf,1);
+       
+           if (   Res_user(tab,resloc_in_table) != -1
+               && Ws_user(tab,i) != -1)
+           {
+               switch (book_lev)
+               {
+               case 0:
+                   break;
+               case 1:
+                   if (Cluster_modtime(tab, resloc_in_table) + grace < time(0))
+                       ;       /* do nothing, well into period */
+                   else if (Res_user(tab, resloc_in_table) == -1
+                            || Res_status(tab, resloc_in_table) == NODETENTATIVE)
+                       /* not reserved, do nothing */
+                       ;
+                   else if (Res_user(tab, resloc_in_table) == -2)
+                   {
+                       /* lab is closed */
+                       char *labname = labof(wsid);
+                       if (labname && in_neverclose(Ws_user(tab,i), labname) == 0)
+                           evict=1;
+                       if (labname) free(labname);
+                   }
+                   else if (in_class(Ws_user(tab, i), 
+                                     Res_user(tab, resloc_in_table))== 0)
+                       evict = 1;
+                   /* if in exempt group, never evict */
+                   if (in_exempt(Ws_user(tab,i))==1)
+                           evict=0;
+                   break;
+               }
+           
+           }
+           if (evict)
+               ensure_evict(Cluster_modtime(tab,resloc_in_table), wsid,
+                            Ws_user(tab,i));
+           else
+               forget_evict(wsid);
+       }
+}
diff --git a/manager/helper.c b/manager/helper.c
new file mode 100644 (file)
index 0000000..fdefe9a
--- /dev/null
@@ -0,0 +1,80 @@
+
+#include       <unistd.h>
+#include       "statush.h"
+#include       "../lib/misc.h"
+#include       <sys/wait.h>
+#include       <sys/signal.h>
+
+int helper_pid;
+
+static char *helper_prog;
+
+/* forks a manager process */
+void start_node_manager(char *program)
+{
+       int i;
+
+       static char *args[2];
+       if (program)
+               helper_prog = program;
+       if ((helper_pid = fork()) == 0) {
+               /* close all sockets opened by parent except standard ones */
+               for (i = 3 ; i <20;i ++)
+                       close(i);
+               /* have to do an exec.  can't just call the helper as another */
+               /* procedure as both the client and server stubs would have to */
+               /* be linked in.  both client and server stubs contain */
+               /* different definitions for remote procedures. */
+               args[0] = helper_prog;
+               args[1] = NULL;
+               i = execv(helper_prog,args);
+               if (i != 0) {
+                       printf("exec failed!, status %d\n",i);
+                       exit(4);
+               }
+       }
+}
+
+int * SVC(restart_helper_2)(int *dummy, struct svc_req *rq)
+{
+       char errbuff[200];
+       static int result;
+    
+       result = kill(helper_pid,SIGTERM );
+       if (wait(NULL) == -1)
+       {
+/*     printf("Child was dead already!!!!\n"); */
+       }
+    
+    
+       start_node_manager(NULL);
+       result = helper_pid;
+       sprintf(errbuff, "New Node Manager: pid = %d\n",helper_pid);
+       shout(errbuff);
+/*    printf("Restart %s", errbuff);*/
+    
+       return(&result);
+}
+
+void check_helper(int waken)
+{
+       /* restart helper if it is dead */
+       int pid;
+#ifdef ultrix
+       union wait status;
+       while ( (pid = wait3(&status, WNOHANG, (char*) NULL)) >  0)
+               ;
+#else  
+       int status;
+       while ( (pid = waitpid((pid_t) -1, &status, WNOHANG)) > 0)
+               ;
+#endif
+       if (kill(helper_pid, 0) < 0) {
+               /* it died */
+               start_node_manager(NULL);
+       }  else if (waken)  {
+               /* just make sure it is awake */ 
+               kill(helper_pid, SIGALRM);
+       }
+}
+               
diff --git a/manager/helper.h b/manager/helper.h
new file mode 100644 (file)
index 0000000..1a32cc6
--- /dev/null
@@ -0,0 +1,58 @@
+
+#include       "../db_client/db_client.h"
+
+
+extern struct mine {
+    char *hostname;
+    entityid hostid, hgid;
+    hostinfo hostdata;
+    hostgroup_info hgdata;
+    hostinfo *labdata;
+    int labcnt;
+    int wscnt;
+    void *wslist; /* skip list of workstations */
+} my;
+
+extern int am_master;
+
+extern CLIENT *status_client;
+
+extern char errbuf[200];
+
+/* from allocate2.c */
+int allocate(table *tab);
+
+/* from create_table.c */
+int check_whoison(int wsid, time_t *when);
+workstation_state *get_wsstate(entityid wsid, int no_cache);
+int  load_config(void);
+void new_table(table *newtab);
+
+/* from pass_table.c */
+bool_t isvalid_table(table * tab);
+int send_table(table * tab, CLIENT * cl);
+int next_host(table * tab, int index);
+bool_t give_table(table * t, int index);
+int propagate_table();
+void send_new_table(void);
+
+/* from evictions.c */
+char *labof(int wsid);
+void perform_evictions(table * tab);
+
+void force_logoff(char *ws);
+int send_message(char *mesg);
+
+/* from check_status.c */
+void check_status(table *t);
+
+/* from check_default.c */
+void check_default(table *t);
+
+/* from alloc.c */
+bool_t next_period_due(table * tab, int * doy, int *pod);
+int lab_status(int doy, int pod, int lab);
+
+
+/* from clnt_create.c */
+CLIENT * my_clntudp_create(char * hostname, int prog, int vers );
diff --git a/manager/lookup_table.c b/manager/lookup_table.c
new file mode 100644 (file)
index 0000000..025b383
--- /dev/null
@@ -0,0 +1,17 @@
+
+#include       <rpc/rpc.h>
+#include       "statush.h"
+
+int lookup_table(table * tbl, cluster_type cltype, int identifier)
+{
+    int i;
+
+    if (tbl == NULL)
+       return -1;
+    
+    for (i=0 ; i <tbl->table_len;i++)
+       if ((cltype == tbl->table_val[i].cltype) &&
+           (identifier == tbl->table_val[i].identifier))
+           return i;
+    return -1;
+}
diff --git a/manager/manager_access.c b/manager/manager_access.c
new file mode 100644 (file)
index 0000000..1f50eb0
--- /dev/null
@@ -0,0 +1,11 @@
+
+#include       <rpc/rpc.h>
+#include       "status.h"
+
+CLIENT *status_client;
+void open_status_client(char * host)
+{
+    if (status_client)
+       clnt_destroy(status_client);
+    status_client = clnt_create(host, BOOK_STATUS, BOOKVERS, "udp");
+}
diff --git a/manager/pass_table.c b/manager/pass_table.c
new file mode 100644 (file)
index 0000000..da538d9
--- /dev/null
@@ -0,0 +1,344 @@
+
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <time.h>
+#include       "helper.h"
+
+static int last_send;
+    
+#define WAITTIME 30 /* number of seconds to wait before sending */
+                   /* another table on.. applies to the master */
+#define OLDTIME 180  /* number of seconds before the table in the */
+                    /* cache becomes invalid, and a newone is created */
+
+
+/* before allocations are made, make sure that the table does not have */
+ /* any nodes in it that are suspicioius.  Sus nodes have not */
+ /* responded for > 5 mins even though they appear to be up...*/
+bool_t isvalid_table(table * tab)
+{
+    int i;
+    for (i=0;i < tab->table_len;i++)
+       if ((tab->table_val[i].cltype == HOSTCLUSTER &&
+            Host_status(tab,i) == NODEUP)
+           && (time(0) > tab->table_val[i].timestamp + 5 *60))
+       {
+           char buf[128];
+           sprintf(buf,"Table is invalid, node (index i %d) %d has bad time\n",
+                   i, tab->table_val[i].identifier); 
+           shout(buf);
+           return FALSE;
+       }
+    
+    return TRUE;
+    
+}
+
+
+/* put table into a buffer.  send chunks of table until NULL or CHUNK */
+ /* REFUSED is received or whole table send */
+/* returns 0 on failure, 1 on refused, 2 on success */
+int send_table(table * tab, CLIENT * cl)
+{
+    chnkxfer_status * result;
+    static table_chunk tabdata;
+    static time_t last_stamp = 0;
+    XDR xdrs;
+    int failcnt = 3;
+    static int  xdrbuf[8190/4]; /* this should be dynamic,
+                                  must be int for proper alignment */
+    
+    xdrmem_create(&xdrs, (char*)xdrbuf, sizeof(xdrbuf), XDR_ENCODE);
+    if (!xdr_table(&xdrs, tab))
+    {
+       shout("xdr_Table failed\n");
+       return 0;
+    }
+
+    
+    tabdata.chnk.max = XDR_GETPOS(&xdrs);
+    tabdata.chnk.index = 0;
+    tabdata.chnk.timestamp = time(0);
+    if (tabdata.chnk.timestamp <= last_stamp)
+       tabdata.chnk.timestamp = ++last_stamp;
+    last_stamp = tabdata.chnk.timestamp;
+    
+/*    printf("max %d, index %d\n",tabdata.chnk.max, */
+ /*    tabdata.chnk.index); */
+    
+    while (tabdata.chnk.index < tabdata.chnk.max) {
+       tabdata.data.data_val =  ((char*)xdrbuf) + tabdata.chnk.index ;
+       tabdata.data.data_len = tabdata.chnk.max - tabdata.chnk.index;
+       if (tabdata.data.data_len > XFERSIZE)
+           tabdata.data.data_len = XFERSIZE;
+
+       result = set_chunk_2(&tabdata, cl); 
+       if (result == NULL)
+       {
+           char buf[200];
+           sprintf(buf, "set_chunk_2: %d+%d of %d failed\n", tabdata.chnk.index,
+                   tabdata.data.data_len,  tabdata.chnk.max);
+           shout(buf);
+           if (failcnt-- > 0)
+               continue;
+           else
+               return 0;
+       }
+       if (* result == CHUNK_REFUSED )
+           return 1;
+       tabdata.chnk.index += tabdata.data.data_len;
+
+    }
+    return 2;
+}
+
+/* NEXT_HOST: given a table and an index, returns the index of the */
+ /* next host entry in the table that is up.  If passed index ==0, */
+ /* returns the master. */
+int next_host(table * tab, int index)
+{
+    int pos;
+
+    pos = index +1;
+    pos = pos % tab->table_len;
+    
+    while (pos != index) {
+       
+       /* if it is a host record and the host is up, this is good */
+       if ((Cluster_type(tab,pos) == HOSTCLUSTER)
+           && (Host_status(tab,pos) == NODEUP))
+           return pos;
+       pos++;
+       pos = pos % tab->table_len;
+    }
+    return pos;
+    
+}
+
+/* GIVE_TABLE: send table onto next node.  If index == -1, send to */
+ /* local bind */
+/* if can't send to node, mod table send to self */
+/* if can't send to self exit */
+/* return status.  TRUE sent to another node */
+/* FALSE.. sent to self */
+bool_t give_table(table * t, int index)
+{
+    char errbuf[200];
+    char * hostname;
+    CLIENT * cl;
+    bool_t result = TRUE ;
+    int send_result;
+    
+
+    /* create the RPC handle */
+    if (index == -1)
+    {
+       cl = status_client;
+       Debug("send to self\n", 1);
+    }
+    else
+    {
+       /* convert to a hostname */
+       hostname = get_mappingchar(Cluster_id(t,index), M_HOST);
+       
+       if (hostname == NULL) {
+           sprintf(errbuf,"Bad mapping for index %d,id =%d\n", index,
+                   Cluster_id(t,index)); 
+           shout(errbuf);
+           exit(5);
+       }
+       sprintf(errbuf,"send host hostname = %s\n",hostname);
+       Debug(errbuf,1);
+       /* create client */
+       cl = my_clntudp_create(hostname, BOOK_STATUS, BOOKVERS); 
+       if (cl == NULL) {
+/*         clnt_pcreateerror(hostname); */
+           free(hostname);
+           if (Cluster_id(t,index) != my.hostid)
+               Host_status(t,index) = NODEDOWN;
+           Cluster_modtime(t,index) = time(0);
+           give_table(t, -1);
+           return FALSE;
+       }
+       free(hostname);
+    }
+    /* now send table */
+    while ((send_result = send_table(t, cl))==1)
+    {
+       Debug("Send was not accepted, sleep 2...\n", 1);
+       sleep(2);
+    }
+    if (send_result == 0)
+    {
+       if (index == -1)
+       {
+           /* could not send to self */
+           shout("Could not contact our bind\n");
+           if (getppid()==1)
+               exit(6);
+           open_status_client("localhost");
+       }
+       else
+       {
+           /* mod table */
+           sprintf(errbuf,"send failed\n");
+           Debug(errbuf,1);
+
+           Host_status(t,index) = NODEDOWN;
+           Cluster_modtime(t, index) = time(0);
+
+           /* give back to self */
+           give_table(t, -1);
+       }
+       result = FALSE;
+    }
+    if (index != -1)
+    {
+       if (result != FALSE)
+           last_send = time(0);
+       clnt_destroy(cl);
+    }
+    return result;
+}
+
+
+/* if a host is marked down, we never try to talk to it, so it may
+ * come back up and not be noticed.
+ * This routine will mark one down-host as up so that a table gets sent
+ * to it. If it is still down, this will be noticed and the host will be marked
+ * down again.
+ * Only consider hosts which were last checked more than 5 minutes ago
+ */
+static void check_down(table *new)
+{
+    int i;
+    int oldest =  -1;
+
+    for (i=0;i< new->table_len;i++)
+    {
+       /* look for oldest host that is down */
+       if ((Cluster_type(new,i) == HOSTCLUSTER) &&
+           (Host_status(new,i) == NODEDOWN))
+       {
+           if (oldest < 0
+               || Cluster_modtime(new,i) <Cluster_modtime(new,oldest))
+               oldest =i;
+       }
+    }
+           
+    /* if found, and down for 5 mins , update it */
+    if ((oldest >= 0) &&(Cluster_modtime(new,oldest) +
+                               300 < time(0)) )
+    {
+       sprintf(errbuf, "MANAGER setting %d up again\n",
+               Cluster_id(new,oldest ));
+       Debug(errbuf,1);
+       Cluster_modtime(new,oldest) = time(0);
+       Host_status(new, oldest) = NODEUP;
+    }
+           
+}
+
+/* PROPAGATE_TABLE: called when a table has been received by the */
+ /* manager or WAITTIME seconds */ 
+ /* has elapsed.  It performs allocations if necessary, and evicts the */
+ /* current user if necessary.  It also tries to send the table on to */
+ /* the next node in the table */
+ /* return value is seconds to wait until time to try again */
+int propagate_table()
+{
+    char errbuf[200];
+    static table  new;
+    bool_t made_newtable = FALSE;
+    bool_t allocated = FALSE ;
+    int my_index;
+    int master_index;
+
+    int nexttime;
+    
+
+
+    /* if could not get table */
+    /* book_bind may have been restarted, and we were not killed */
+    if (! collect_tab(&new, status_client))
+    {
+       sprintf(errbuf,"Error getting table from host\n");
+       Debug(errbuf,1);
+       if (getppid() == 1)
+           exit(1);
+       else
+       {
+           /* try again soon */
+           return 10;
+       }
+    }
+    have_taken_2(NULL, status_client);
+
+    sprintf (errbuf,"propagate\n");
+    Debug(errbuf,1);
+
+    if (new.table_len == 0)
+    {
+       new_table(&new);
+       made_newtable = TRUE;
+    }
+    else
+    {
+       if (am_master)
+       {
+           time_t wait_time;
+           wait_time = last_send + WAITTIME - time(0);
+           if (wait_time > 2)
+           {
+               xdr_free((xdrproc_t)xdr_table, (char*) &new);
+               return wait_time;
+           }
+           check_down(&new);
+       }
+       /* check on the status of the workstations attached*/
+       check_status(&new);
+    }
+
+    my_index = lookup_table(&new, HOSTCLUSTER, my.hostid);
+    /* make sure I am marked as up, now! */
+    Host_status(&new,my_index) = NODEUP;
+    Cluster_modtime(&new,my_index) = time(0);
+
+    master_index = next_host(&new, 0);
+    am_master = (my_index == master_index);
+
+
+    if (am_master
+       && isvalid_table(&new)
+       && allocate(&new)>0)
+    {
+       allocated = TRUE;
+    }
+
+    if (allocated || made_newtable)
+    {
+       /* made significant change, make sure info safe with my book_status */
+       give_table(&new,-1);
+       nexttime = 2;
+    }
+    else
+    {
+       if (give_table(&new, next_host(&new, my_index)))
+           nexttime = 3 * WAITTIME;
+       else
+           nexttime = 2;
+    }
+    /* perform evictions and check for defaulters after table safely passed on */
+    check_default(&new);
+    perform_evictions(&new);
+
+    xdr_free((xdrproc_t)xdr_table, (char*) &new);
+    return nexttime;
+}
+
+void send_new_table(void)
+{
+    table new;
+    new_table(&new);
+    give_table(&new, -1);
+}
diff --git a/manager/print_tab.c b/manager/print_tab.c
new file mode 100644 (file)
index 0000000..fb259a4
--- /dev/null
@@ -0,0 +1,100 @@
+
+#include       "../db_client/db_client.h"
+
+static char *cltname[] = { NULL, "TABLE", NULL, "ALLOC", NULL, "HOST ", NULL, "STATE", NULL, "RESV " };
+static char *periods[] = { NULL, "FREE", "FULL", "CLOSED" };
+static char *bstatus[] = { "<null>", "OutOfService", "Broken", "NotBookable",
+               "UnAllocated", "Allocated", "Tentative" };
+
+void print_tab(table *tab, FILE *f)
+{
+    int r;
+    for (r=0; r<tab->table_len ; r++)
+    {
+       cluster *c = &tab->table_val[r];
+       int d;
+       char *nm = NULL;
+       nmapping_type mp = M_CLASS;
+       switch(c->cltype)
+       {
+       case TABLECLUSTER: mp = M_HOSTGROUP; break;
+       case HOSTCLUSTER: mp = M_HOST ; break;
+       case ALLOCCLUSTER: mp = M_ATTRIBUTE; break;
+       case WSRESCLUSTER:
+       case WSSTATUSCLUSTER: mp = M_WORKSTATION; break;
+       }
+       nm = get_mappingchar(c->identifier, mp);
+       if (nm)
+           fprintf(f, "%02d: %s %-12s %s (%d):", r, cltname[c->cltype], nm, myctime(c->timestamp), c->data.data_len);
+       else
+           fprintf(f, "%02d: %s %d %s %d:", r, cltname[c->cltype], c->identifier, myctime(c->timestamp), c->data.data_len);
+       if (nm) free(nm);
+       nm=NULL;
+       for (d=0 ; d<c->data.data_len ; d++)
+       {
+           int v= c->data.data_val[d];
+           switch(d*10+c->cltype)
+           {
+           default:
+               fprintf(f, " %d", v);
+               break;
+
+               
+           case ALLOCCLUSTER+0: /* alloc status */
+               fprintf(f, " %s", v>=PERIODFREE && v<= LABCLOSED? periods[v]:"???");
+               break;
+           case TABLECLUSTER+0: /* host who created */
+           case ALLOCCLUSTER+10: /* alloc host */
+               nm = get_mappingchar(v, M_HOST);
+               if (nm)
+               {
+                   fprintf(f, " on %s", nm);
+               }
+               else fprintf(f, " on host-%d", v);
+               break;
+           case ALLOCCLUSTER+20:
+           case WSSTATUSCLUSTER+20:
+           case WSSTATUSCLUSTER+40:
+               /* print a time..*/
+               fprintf(f, " %s", myctime((time_t)v));
+               break;
+           case ALLOCCLUSTER+30:
+               fprintf(f, " %d failed allocs: %d",
+                       c->data.data_len-3, v);
+               break;
+           case HOSTCLUSTER+0:
+           case WSSTATUSCLUSTER+10:
+               fprintf(f," %s", v==NODEUP?"UP":v==NODEDOWN?"DOWN":"unknown");
+               break;
+           case WSSTATUSCLUSTER+0:
+           case WSRESCLUSTER+0:
+               if (v==-2) nm="CLOSED";
+               else if (v==-1) nm="-";
+               else nm = find_username(v);
+               if (nm) fprintf(f, " %s", nm);
+               else fprintf(f, " user-%d", v);
+               if (v<0) nm=NULL; /* avoid free */
+               break;
+           case WSSTATUSCLUSTER+30: /* host logged on to */
+               nm = get_mappingchar(v, M_HOST);
+               if (nm)
+                   fprintf(f, " %s", nm);
+               else fprintf(f," host-%d", v);
+               break;
+           case WSRESCLUSTER+10: /* status */
+               if (v>=NODEOUTOFSERVICE && v<=NODETENTATIVE)
+                   fprintf(f, " %s", bstatus[v]);
+               else
+                   fprintf(f, " status-%d", v);
+               break;
+           case WSRESCLUSTER+20: /* firmness */
+               fprintf(f, " %s", v==0?"normal":v==1?"firm":"unknown-firmness");
+               break;
+           }
+           if (nm)
+               free(nm);
+           nm=NULL;
+       }
+       fprintf(f, "\n");
+    }
+}
diff --git a/manager/status.x b/manager/status.x
new file mode 100644 (file)
index 0000000..c8308c4
--- /dev/null
@@ -0,0 +1,135 @@
+
+
+const  XFERSIZE = 1000;
+
+
+enum database_state {
+    DB_CLOSE   = 0,    /* up, writable, and near by */
+    DB_UP      = 1,    /* up, writable */
+    DB_UNKNOWN = 2,    /* don't know the state of this DB */
+    DB_READONLY = 3,   /* not writable, but up */
+    DB_DOWN    = 4,    /* not contactable */
+    DB_UNREGISTERED= 5 /* not registered as a valid server */
+};
+
+struct database_info {
+       string          name<>;
+       database_state  state;
+    };
+
+typedef database_info database_list<>;
+
+/* this data structure  is used to implement the status of the lab. */
+/* This is dynamic information that concerns who is on a machine, who */
+/* has reserved a machine, etc.*/
+
+enum node_status
+{
+       NODEUP = 1,
+       NODEDOWN = 2
+};
+
+
+enum book_status
+{
+       NODEOUTOFSERVICE = 1,
+       NODEBROKEN = 2,
+       NODENOTBOOKABLE = 3,
+       NODENOTALLOC = 4,
+       NODEALLOCATED = 5,
+       NODETENTATIVE = 6
+};
+
+enum period_status
+{
+       PERIODFREE = 1,
+       PERIODFULL = 2,
+       LABCLOSED = 3
+};
+
+enum cluster_type
+{
+       TABLECLUSTER = 1,
+       ALLOCCLUSTER = 3,
+       HOSTCLUSTER = 5,
+       WSSTATUSCLUSTER =7,
+       WSRESCLUSTER = 9
+};
+struct cluster{
+       cluster_type cltype;
+       int identifier;
+       int timestamp;
+       int data<>;
+};
+
+struct table_key{
+       cluster_type cltype;
+       int identifier;
+};
+typedef  cluster table<>;
+
+struct node_pair {
+       book_status status;
+       int wsid;
+};
+
+struct ws_user_pair {
+       int wsid;
+       int userid;
+       int hostid;
+};
+
+struct resinfo {
+       int wsid;
+       int userid;
+       int when;
+};
+
+/* some stuff for xfer of tables */
+enum chnkxfer_status
+{
+       CHUNK_GOT = 1,
+       CHUNK_REFUSED = 2
+};
+typedef int chunk_index;
+struct chunk_info{
+       chunk_index index;
+       chunk_index max;
+       int timestamp;
+};
+struct table_chunk
+{
+       opaque data<>;
+       chunk_info chnk;
+};
+
+program BOOK_STATUS {
+       version BOOKVERS {
+
+               database_list
+                       GET_DATABASES(void) = 30;
+               void
+                       SET_DATABASE(database_info) = 32;
+
+               bool
+                       NEW_USER(ws_user_pair) = 42;
+               bool
+                       RES_USER(ws_user_pair) =  44;
+               bool
+                       RES_USER_TIME(resinfo) = 45;
+               bool
+                       SET_BOOKSTATUS(node_pair) = 46;
+
+               table_chunk
+                       GET_CHUNK(chunk_index) =53;
+               chnkxfer_status
+                       SET_CHUNK(table_chunk) = 55;
+               void
+                       HAVE_TAKEN(void)  = 56;
+                                               
+               int
+                       RESTART_HELPER(int) = 60;
+
+               void    STATUS_DIE(void) = 100;
+       } = 2;
+} = 0x20304051;
diff --git a/manager/status_client.h b/manager/status_client.h
new file mode 100644 (file)
index 0000000..e23c053
--- /dev/null
@@ -0,0 +1,35 @@
+
+#include       "statush.h"
+extern CLIENT *status_client;
+
+
+/* from choose_db.c */
+int refresh_db_client(void);
+
+/* from cache_names.c */
+void open_cachef(char *mode);
+void reset_cache();
+int get_mappingint_cache(char *name, int type);
+char *get_mappingchar_cache(int num, int type);
+
+/* from collect_table.c */
+bool_t collect_tab(table * newtab, CLIENT *cl);
+
+/* from manager_access */
+void open_status_client(char * host);
+
+/* from process_exclusions.c */
+description * process_exclusions(int pod, int doy, description * desc);
+
+/* from whoison.c */
+int get_whoison(char *wsname, time_t *when);
+void set_whoison(char *wsname, int who);
+
+/* from print_tab.c */
+void print_tab(table *tab, FILE *f);
+
+/* from up_host.c */
+CLIENT *up_host(hostgroup_info *hg, bool_t last);
+
+/* from check_servers.c */
+void check_servers(void);
diff --git a/manager/statush.h b/manager/statush.h
new file mode 100644 (file)
index 0000000..15dbfdd
--- /dev/null
@@ -0,0 +1,71 @@
+
+#include       <rpc/rpc.h>
+#include       "../manager/status.h"
+#include       <string.h>
+#include       <stdio.h>
+
+#ifdef linux
+#define SVC(x) x ## _svc
+#else
+#define SVC(x) x
+#endif
+#define DB_LOC_FILE "/var/book/database_loc"
+#define        DB_LOC_TMP  "/var/book/database_loc.new"
+
+
+#define cluster_type(c) (c)->cltype
+#define cluster_modtime(c) (c)->timestamp
+#define cluster_id(c) (c)->identifier
+
+#define alloc_status(c) (c)->data.data_val[0]
+#define alloc_host(c) (c)->data.data_val[1]
+#define        alloc_when(c) (c)->data.data_val[2]
+#define alloc_numfailed(c) ((c)->data.data_len>3 ? ((c)->data.data_len - 3):0)
+#define alloc_failid(c,n) ((c)->data.data_val[3+i])
+#define host_status(c) (c)->data.data_val[0]
+#define ws_user(c) (c)->data.data_val[0]
+#define ws_status(c) (c)->data.data_val[1]
+#define ws_whenon(c) (c)->data.data_val[2]
+#define ws_whereon(c) (c)->data.data_val[3]
+/* Whenalloc is updated at login and when reallocated to a machine */
+#define        ws_whenalloc(c) (c)->data.data_val[4]
+#define res_user(c) (c)->data.data_val[0]
+#define res_status(c) (c)->data.data_val[1]
+#define        res_status2(c) (c)->data.data_val[2]
+
+
+
+#define Cluster_type(t,i) (t)->table_val[i].cltype
+#define Cluster_modtime(t,i) (t)->table_val[i].timestamp
+#define Cluster_id(t,i) (t)->table_val[i].identifier
+
+#define Alloc_status(t,i) (t)->table_val[i].data.data_val[0]
+#define Alloc_host(t,i) (t)->table_val[i].data.data_val[1]
+#define        Alloc_when(t,i) (t)->table_val[i].data.data_val[2]
+#define Alloc_numfailed(t,i) alloc_numfailed((t)->table_val+i)
+#define Alloc_failid(t,i,n) ((t)->table_val[i].data.data_val[3+i])
+#define Host_status(t,i) (t)->table_val[i].data.data_val[0]
+#define Ws_user(t,i) (t)->table_val[i].data.data_val[0]
+#define Ws_status(t,i) (t)->table_val[i].data.data_val[1]
+#define Ws_whenon(t,i) (t)->table_val[i].data.data_val[2]
+#define Ws_whereon(t,i) (t)->table_val[i].data.data_val[3]
+#define        Ws_whenalloc(t,i) (t)->table_val[i].data.data_val[4]
+#define Res_user(t,i) (t)->table_val[i].data.data_val[0]
+#define Res_status(t,i) (t)->table_val[i].data.data_val[1]
+#define        Res_status2(t,i) (t)->table_val[i].data.data_val[2]
+
+
+/* from db_loc.c */
+void add_close(char *name);
+int find_close(char *name);
+void del_close(int num);
+void set_database(char *name, database_state state);
+void init_databases(int argc, char *argv[]);
+int write_databases(void);
+
+/* from helper.c */
+void start_node_manager(char *program);
+void check_helper(int waken);
+
+/* from lookup_table.c */
+int lookup_table(table * tbl, cluster_type cltype, int identifier);
diff --git a/manager/sysdep_windows.c b/manager/sysdep_windows.c
new file mode 100644 (file)
index 0000000..8a86862
--- /dev/null
@@ -0,0 +1,106 @@
+#include <windows.h>
+#include <wtsapi32.h>
+#include <time.h>
+#include "helper.h"
+
+static char current_username[32];
+static int current_uid;
+static time_t current_when;
+
+int get_whoison(char *wsname, time_t *when)
+{
+    BYTE sid_buffer[256];
+    DWORD sid_buffer_length = sizeof(sid_buffer);
+    TCHAR domain[256];
+    DWORD domain_length = sizeof(domain);
+    SID_NAME_USE sid_name_use;
+    PSID psid;
+    char *username;
+    DWORD length, rid;
+    int uid;
+
+    if (strcmp(my.hostname, wsname) != 0)
+       return -1;
+
+    if (!WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION,
+                               WTSUserName, &username, &length))
+    {
+       shout("WTSQuerySessionInformation failed\n");
+       return -1;
+    }
+
+    if (!username[0])
+       return -1;
+
+    if (strncmp(current_username, username, sizeof(current_username)-1) == 0)
+    {
+       WTSFreeMemory(username);
+       *when = current_when;
+       return current_uid;
+    }
+
+    strncpy(current_username, username, sizeof(current_username)-1);
+    current_username[sizeof(current_username)-1] = 0;
+    WTSFreeMemory(username);
+
+#if 0
+    /* map username to uid using YP */
+    p = ypmatch(username, "passwd.byname");
+    if (p == NULL)
+       return -1;
+
+    p = strchr(p, ':'); /* skip username */
+    if (p == NULL)
+       return -1;
+
+    p = strchr(p+1, ':'); /* skip password */
+    if (p == NULL)
+       return -1;
+
+    uid = strtol(p+1, &p, 10);
+    if (*p != ':')
+       return -1;
+#else
+    /* map username to uid using Samba */
+    if (!LookupAccountName(NULL, current_username, sid_buffer, &sid_buffer_length, domain, &domain_length, &sid_name_use))
+    {
+       shout("LookupAccountName failed\n");
+       current_username[0] = 0;
+       return -1;
+    }
+    psid = (PSID)sid_buffer;
+    if (!IsValidSid(psid))
+    {
+       shout("SID not valid\n");
+       current_username[0] = 0;
+       return -1;
+    }
+
+    if ((*GetSidSubAuthorityCount(psid)) == 0)
+    {
+       shout("No subauthorities\n");
+       current_username[0] = 0;
+       return -1;
+    }
+    rid = *GetSidSubAuthority(psid, (*GetSidSubAuthorityCount(psid))-1);
+    uid = (rid - 1000)/2; /* Samba algorithmic mapping */
+#endif
+
+    current_uid = uid;
+    current_when = time(when);
+    return uid;
+}
+
+int send_message(char *mesg)
+{
+    char *title = "Booking System Message";
+    DWORD response;
+    return WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION,
+                               title, strlen(title), mesg, strlen(mesg),
+                               MB_OK, 0, &response, FALSE) ? 0 : -1;
+}
+
+void force_logoff(char *wsname)
+{
+    WTSLogoffSession(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, FALSE);
+}
diff --git a/manager/sysdep_xdm.c b/manager/sysdep_xdm.c
new file mode 100644 (file)
index 0000000..a30259d
--- /dev/null
@@ -0,0 +1,90 @@
+#include       <stdio.h>
+#include        <stdlib.h>
+#include       <string.h>
+#include        <unistd.h>
+#include       <fcntl.h>
+#include        <signal.h>
+#include        <sys/socket.h>
+#include        <netinet/in.h>
+#include        <netdb.h>
+#include        "helper.h"
+
+char control_pid_dir[] = "/var/book/xdm-pid/"; /*  /var/X11/xdm/xdm-pids */
+
+int send_message(char *mesg)
+{
+    int sock;
+    int lport = IPPORT_RESERVED -1;
+    static int port = 0;
+    
+    struct sockaddr_in server;
+
+    if (port == 0)
+    {
+       struct servent * service;
+
+       if ((service = getservbyname("message", "tcp")) == NULL )
+       {
+           perror("getting service by name");
+           return(-1);
+       }
+       port = service->s_port;
+    }
+
+    sock = rresvport(&lport);
+    if (sock < 0) {
+/*         perror("rresvport: socket"); */
+           return(-1);
+    }
+
+    memset(&server, 0, sizeof(server));
+    server.sin_family = AF_INET;
+    server.sin_addr.s_addr = htonl(0x7f000001);
+    server.sin_port = port;
+
+    if (connect(sock, (struct sockaddr *) &server, sizeof(server)) != 0)
+    {
+       close(sock);
+/*     perror("Connect failed"); */
+       return(-1);
+    }
+
+    write(sock, mesg, strlen(mesg));
+    close(sock);
+    return 1;
+}
+
+static int find_xdmpid(char * wsname)
+{
+    char buf[128];
+    int fd;
+    int pid=0;
+    strcpy(buf, control_pid_dir);
+    strcat(buf, wsname);
+    fd = open(buf, 0); 
+
+
+    if (fd >= 0)
+    {
+       read(fd, buf, 20);
+       pid = atoi(buf);
+       close(fd);
+    }
+    return pid;
+}
+
+void force_logoff(char *ws)
+{
+    pid_t xdmpid;
+
+    xdmpid = find_xdmpid(ws);
+    /* changed to HUP - simonb 20sep2005. gdm doesn't deal with TERM quite as nicely
+     * and xdm seems to not mind.
+     * 25oct2005 simonb - change back to TERM after fixing gdm. xdm did handle
+     *                    it differently, and gdm mismanaged this situation with its slave IMO.
+     */
+    kill(xdmpid, SIGTERM); /* FIXME check something... */
+
+    //kill(xdmpid, SIGHUP); /* FIXME check something... */
+
+}
diff --git a/manager/table.c b/manager/table.c
new file mode 100644 (file)
index 0000000..595ece4
--- /dev/null
@@ -0,0 +1,412 @@
+
+#include       <time.h>
+#include       "statush.h"
+
+static table tab;
+static time_t last_change_time =0;
+
+bool_t * SVC(new_user_2)(ws_user_pair * data, struct svc_req *rqstp)
+{
+    static bool_t result = FALSE;
+    int labstart;
+    int wsres,userres;
+    int tableind;
+    if (ntohs(svc_getcaller(rqstp->rq_xprt)->sin_port) > 1024) {
+       return (&result);
+    }
+
+    /* THINKS:
+       when a user logs on, if they are currently allocated somewhere else in the lab,
+       we should re-allocate them.
+       If THIS workstation is unallocated, just swap status and set alloc id
+       If it is tentative  there could be a race with someone claiming.
+       So must get "claim" to check if anyone logged in, and possibly reallocate.
+     */
+    
+    tableind = lookup_table(&tab, WSSTATUSCLUSTER, data->wsid);
+    if (tableind == -1)
+       return (&result);
+
+    /* look back for start of lab */
+    for (labstart = tableind ; labstart>0 &&  Cluster_type(&tab,labstart)!=ALLOCCLUSTER ; labstart--)
+       ;
+    /* now look for WSRESCLUSTER for this ws and this user */
+    wsres=0; userres=0;
+    for (labstart++ ; labstart < tab.table_len && Cluster_type(&tab,labstart) != ALLOCCLUSTER ; labstart++)
+    {
+       if (Cluster_type(&tab,labstart)== WSRESCLUSTER
+           && Cluster_id(&tab,labstart)== data->wsid)
+           wsres = labstart;
+       if (Cluster_type(&tab,labstart) == WSRESCLUSTER
+           && (Res_status(&tab,labstart) == NODEALLOCATED || Res_status(&tab,labstart) == NODETENTATIVE)
+           && Res_user(&tab,labstart) == data->userid
+           )
+       {
+           userres = labstart;
+       }
+    }
+    if (userres>0 &&wsres > 0 && userres != wsres)
+    {
+       /* user is allocated somewhere else in the lab
+        */
+       cluster cl;
+       cl.data = tab.table_val[userres].data;
+       tab.table_val[userres].data = tab.table_val[wsres].data;
+       tab.table_val[wsres].data = cl.data;
+       tab.table_val[userres].timestamp++;
+       tab.table_val[wsres].timestamp++;
+    }
+          
+    Ws_user(&tab,tableind) = data->userid;
+    Ws_whenon(&tab,tableind) = time(0);
+    Ws_whereon(&tab,tableind) = data->hostid;
+    /* set whenalloc to 15 minutes ago. Should be 30, but I am
+     * being kind
+     */
+    Ws_whenalloc(&tab,tableind) = time(0) - (15*60);
+    Cluster_modtime(&tab,tableind) = time(0);
+    
+    last_change_time = time(0);
+
+    result = TRUE;
+    return(&result);
+}
+
+bool_t * SVC(res_user_2)(ws_user_pair * data, struct svc_req *rqstp)
+{
+    static bool_t result = FALSE;
+    int tableind;
+    time_t now;
+    
+    if (ntohs(svc_getcaller(rqstp->rq_xprt)->sin_port) > 1024) {
+       /* FIXME check host */
+       return (&result);
+    }
+    tableind = lookup_table(&tab, WSRESCLUSTER, data->wsid);
+    if (tableind == -1)
+       return (&result);
+    tab.table_val[tableind].data.data_val[0] = data->userid;
+    tab.table_val[tableind].data.data_val[1] = NODEALLOCATED;
+    now = time(0);
+    now += 5*60; /* always make reservations in the future */
+    if (tab.table_val[tableind].timestamp >= now)
+       tab.table_val[tableind].timestamp++;
+    else
+       tab.table_val[tableind].timestamp = now;
+    
+    last_change_time = time(0);
+
+
+/*     printf("Reserve for  user %d\n",*user); */
+    result = TRUE;
+    return(&result);
+}
+
+bool_t * SVC(res_user_time_2)(resinfo * data, struct svc_req *rqstp)
+{
+    static bool_t result = FALSE;
+    int tableind;
+    
+    if (ntohs(svc_getcaller(rqstp->rq_xprt)->sin_port) > 1024) {
+       /* FIXME check host */
+       return (&result);
+    }
+    tableind = lookup_table(&tab, WSRESCLUSTER, data->wsid);
+    if (tableind == -1)
+       return (&result);
+    tab.table_val[tableind].data.data_val[0] = data->userid;
+    tab.table_val[tableind].data.data_val[1] = NODEALLOCATED;
+
+    if (tab.table_val[tableind].timestamp >= data->when)
+       tab.table_val[tableind].timestamp++;
+    else
+       tab.table_val[tableind].timestamp = data->when;
+    
+    last_change_time = time(0);
+
+    result = TRUE;
+    return(&result);
+}
+
+
+bool_t * SVC(set_bookstatus_2)(node_pair * np, struct svc_req *rqstp)
+{
+    int posintab;
+    static bool_t result = FALSE;
+    
+    if (ntohs(svc_getcaller(rqstp->rq_xprt)->sin_port) > 1024) {
+       /* FIXME check host */
+       return (&result);
+    }
+    posintab = lookup_table(&tab, WSRESCLUSTER, np->wsid);
+    if (posintab == -1)
+       return (&result);
+    tab.table_val[posintab].data.data_val[1] = np->status;
+    tab.table_val[posintab].timestamp = time(0);
+    last_change_time = time(0);
+
+    result = TRUE;
+    return(&result);
+    
+}
+bool_t * SVC(set_wsstatus_2)(node_pair * np, struct svc_req *rqstp)
+{
+    int posintab;
+    static bool_t result = FALSE;
+    
+    if (ntohs(svc_getcaller(rqstp->rq_xprt)->sin_port) > 1024) {
+       /* FIXME check host */
+       return (&result);
+    }
+    posintab = lookup_table(&tab, WSSTATUSCLUSTER, np->wsid);
+    if (posintab == -1)
+       return (&result);
+    tab.table_val[posintab].data.data_val[1] = np->status;
+    tab.table_val[posintab].timestamp = time(0);
+    last_change_time = time(0);
+
+    result = TRUE;
+    return(&result);
+    
+}
+
+/* MERGE_TABLE: merges the old table with the new */
+/* careful about making sure data is COPIED, so that the one of the */
+ /* tables can be freed */
+/* Assume that the tables are sorted by clustertype and identifier */
+/* careful about missing entries */
+static void merge_table(table * newtab, table * oldtab)
+{
+    int i;
+    /* in newtab, copy new entries from oldtab */
+
+    for (i=0;i < newtab->table_len;i++)
+    {
+       int oldloc;
+       oldloc = lookup_table(oldtab,newtab->table_val[i].cltype,
+                             newtab->table_val[i].identifier);
+       if (oldloc == -1)
+           continue;
+       /* otherwise copy if timestamp in oldtab is newer */
+       if (newtab->table_val[i].timestamp <
+           oldtab->table_val[oldloc].timestamp )
+       {
+           int ind;
+
+           /* free the data in the new */
+           free(newtab->table_val[i].data.data_val);
+
+           /* copy the cluster accross */
+           bcopy(&oldtab->table_val[oldloc], &newtab->table_val[i],
+                 sizeof(cluster));
+
+           /* allocate some more space for the data */
+           newtab->table_val[i].data.data_val =
+               (int *)malloc(oldtab->table_val[oldloc].data.data_len * sizeof(int));
+           newtab->table_val[i].data.data_len =
+               oldtab->table_val[oldloc].data.data_len;
+
+           /* copy the data in the oldtab cluster */
+           for (ind = 0; ind < newtab->table_val[i].data.data_len;ind++)
+               newtab->table_val[i].data.data_val[ind] =
+                   oldtab->table_val[oldloc].data.data_val[ind];
+           
+       }
+    }
+}
+
+
+table_chunk * SVC(get_chunk_2)(chunk_index * ch_ind, struct svc_req *rq)
+{
+    static table_chunk result;
+    static char xdrbuf[8190];  /* this should be dynamic */
+    static XDR xdrs;
+
+/*     printf("tablen %d\n",tab.table_len); */
+    
+    if (tab.table_len == 0 || last_change_time > result.chnk.timestamp )
+    {
+/*     printf("Create copy (last_change %d, result.time %d)\n",
+              last_change_time, result.chnk.timestamp); */
+       xdrmem_create(&xdrs, xdrbuf, sizeof(xdrbuf), XDR_ENCODE);
+       if (!xdr_table(&xdrs, &tab)) {
+           /* this may not work */
+           result.data.data_val = NULL;
+           result.data.data_len =0;
+           return(&result);
+       }
+       result.chnk.max = XDR_GETPOS(&xdrs);
+       result.chnk.timestamp = last_change_time;
+    }
+    if (*ch_ind > result.chnk.max) {
+       result.data.data_val = NULL;
+       result.data.data_len =0;
+       return(&result);
+    }
+    result.chnk.index = *ch_ind;
+    result.data.data_val =  xdrbuf + result.chnk.index ;
+    result.data.data_len = result.chnk.max - result.chnk.index;
+    if (result.data.data_len > XFERSIZE)
+       result.data.data_len = XFERSIZE;
+
+/*    printf("returning %d\n",result.data.data_len); */
+    
+    return(&result);
+     
+}
+/* do a merge, free the old, copy in the update */
+/* update will be freed somewhere else */
+static void update_table(table * update)
+{
+    /* this is where there is no table */
+    if (tab.table_len == 0) {
+       table tmp;
+       tmp = tab;
+       tab = *update;
+       *update = tmp;
+       
+       last_change_time = time(0);
+       return;
+    }
+
+    if (update->table_len ==0 )
+       return;
+
+    /* the update is newer than this table... not a usual situation */
+    /* FIX THIS */
+    if (update->table_val[0].timestamp > tab.table_val[0].timestamp)
+    {
+       table tmp;
+       tmp = tab;
+       tab = *update;
+       *update = tmp;
+    }
+
+    merge_table(&tab, update); /* no need to free update as it */
+                                  /* will be freed somewhere else*/
+
+    last_change_time = time(0);
+}
+
+static int give_count = -2;
+static time_t take_time = 0;
+chnkxfer_status * SVC(set_chunk_2)(table_chunk * new, struct svc_req *rqstp)
+{
+    static char *xdrbuf;
+    XDR xdrs;
+    table newtab;
+    static chnkxfer_status result;
+    static int table_xfer_index = 0;
+    static time_t table_xfer_time;
+    static int table_xfer_from;
+    time_t now;
+    struct sockaddr_in * clxprt;
+
+    clxprt = svc_getcaller(rqstp->rq_xprt);
+    time(&now);
+    if (new->chnk.index == 0)
+    {
+       /* limit incoming tables to two consecutive gives between takes */
+       if (clxprt->sin_addr.s_addr == htonl(0x7f000001))
+       {
+           /* it is from localhost, accept it */
+           give_count = 0;
+       }
+       if (give_count >= 2 && take_time < now-2*60)
+       {
+           give_count++;
+           if (give_count > 20)
+           {
+               give_count = 2;
+               SVC(restart_helper_2)(NULL, NULL); /* the helper must be dead... */
+           }
+           else check_helper(1);
+           return NULL;
+       }
+    }
+    /* check that time stamp is really an appropriate TIME */
+    /* this makes sure the following check never blocks for ever */
+    if (new->chnk.timestamp < now-180 || new->chnk.timestamp > now+120)
+    {
+       return NULL;
+       /* either someones clock is wrong, or someone is doing the Wrong Thing (TM). In
+        * either case, pretend we never heard.
+        */
+    }
+    
+    /* check that an old transfer has not aborted, always accepted newest table
+     * this could cause to sender to continually override each other, but our
+     * sender (node_manager) is too polite for that
+     */
+    if ((table_xfer_time != 0)  && (table_xfer_time < new->chnk.timestamp)) {
+       free(xdrbuf);
+       table_xfer_time = 0;
+    }
+    /* if this is old, reject it */
+    if (table_xfer_time > new->chnk.timestamp)
+    {
+       result = CHUNK_REFUSED;
+       return &result;
+    }
+    /* if same time, but different source machine, reject */
+    if (table_xfer_time == new->chnk.timestamp
+       && table_xfer_from != clxprt->sin_addr.s_addr)
+    {
+       result = CHUNK_REFUSED;
+       return &result;
+    }
+    /* this is the first... set things up */
+    if (table_xfer_time == 0 && new->chnk.index == 0) {
+       table_xfer_time = new->chnk.timestamp;
+       table_xfer_index = 0;
+       table_xfer_from = clxprt->sin_addr.s_addr;
+       xdrbuf = (char *)malloc(new->chnk.max);
+    }
+
+    /* if same timestamp (and so same home) but lesser index, assume resend and accept */
+    if (table_xfer_time == new->chnk.timestamp &&
+       table_xfer_index > new->chnk.index)
+    {
+       result = CHUNK_GOT;
+       return &result;
+    }
+    
+    if (table_xfer_index != new->chnk.index)
+    {
+       result = CHUNK_REFUSED;
+       return &result;
+    }
+    /* copy the data */
+    bcopy(new->data.data_val, xdrbuf +new->chnk.index,
+         new->data.data_len);
+    table_xfer_index += new->data.data_len;
+
+    /* this is the end of the road */
+    if (new->chnk.max == table_xfer_index)
+    {
+
+       xdrmem_create(&xdrs, xdrbuf, new->chnk.max, XDR_DECODE);
+
+       /* what if this fails */
+       memset(&newtab, 0, sizeof(newtab));
+       xdr_table(&xdrs, &newtab);
+
+       update_table(&newtab);
+       give_count++;
+
+       /* now signal the node_manager process so it can send the table on */
+       check_helper(1);
+       xdr_free((xdrproc_t)xdr_table, (char*) &newtab);
+       free(xdrbuf);
+       table_xfer_time =0;
+    }
+    result = CHUNK_GOT;
+    return &result;
+       
+}
+void * SVC(have_taken_2)(void * dummy, struct svc_req *rq)
+{
+    give_count = 0;
+    time(&take_time);
+    return dummy;
+}
diff --git a/manager/up_host.c b/manager/up_host.c
new file mode 100644 (file)
index 0000000..bf91c07
--- /dev/null
@@ -0,0 +1,45 @@
+
+#include       "../db_client/db_client.h"
+
+/*
+ * return a client handle to a book_status on a host in the given
+ * hostgroup. If last, find last up host, otherwise find first.
+ */
+
+CLIENT *up_host(hostgroup_info *hg, bool_t last)
+{
+
+    int h;
+    CLIENT *rv = NULL;
+    static struct timeval tv = {3,0};
+    static struct timeval total = {13,0};
+
+    pmap_settimeouts(tv, total);
+    
+    for (h= 0; rv == NULL && h < hg->hosts.entitylist_len ; h++)
+    {
+       entityid hostid;
+       char *hname;
+       if (last)
+           hostid = hg->hosts.entitylist_val[hg->hosts.entitylist_len-1-h];
+       else
+           hostid = hg->hosts.entitylist_val[h];
+
+       hname = get_mappingchar(hostid, M_HOST);
+       if (hname)
+       {
+           static struct timeval t = { 10, 0};
+           rv = clnt_create(hname, BOOK_STATUS, BOOKVERS, "udp");
+           if (rv &&
+               clnt_call(rv, NULLPROC, (xdrproc_t)xdr_void, NULL, (xdrproc_t)xdr_void, NULL, t)!= 0)
+           {
+               clnt_destroy(rv);
+               rv = NULL;
+           }
+           free(hname);
+       }
+    }
+    return rv;
+}
+
+       
diff --git a/manager/whoison.c b/manager/whoison.c
new file mode 100644 (file)
index 0000000..24e5e70
--- /dev/null
@@ -0,0 +1,70 @@
+
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <time.h>
+#include       <sys/file.h>
+#ifdef sun
+#include       <sys/fcntl.h>
+#endif
+#include       <sys/stat.h>
+#include       <string.h>
+#include       <stdio.h>
+#define WhoIsOn  "/var/book/whoison"
+
+int get_whoison(char *wsname, time_t *when)
+{
+       char fname[1024];
+       char buf[20];
+       struct stat stb;
+       int fd, n;
+
+       strcpy(fname,WhoIsOn);
+       strcat(fname,"/");
+       strcat(fname,wsname);
+    
+       fd = open(fname, O_RDONLY);
+       if (fd == -1 )
+       {
+               if(when)
+                       time(when);
+               return -1;
+       }
+
+       n = read (fd, buf, 20);
+       fstat(fd, &stb);
+       close(fd);
+       if (when)
+               *when = stb.st_mtime;
+       if (n == 0)
+               return -1;
+
+       return(atoi(buf));
+}
+
+void set_whoison(char *wsname, int who)
+{
+       char fname[1024];
+
+       strcpy(fname,WhoIsOn);
+       strcat(fname,"/");
+       strcat(fname,wsname);
+
+       if (who < 0)
+               unlink(fname);
+       else
+       {
+               char buf[20];
+               int fd;
+               fd = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0600);
+               if (fd == -1 )
+                       return;
+
+               if (who>= 0)
+               {
+                       sprintf(buf, "%d\n", who);
+                       write(fd, buf, strlen(buf));
+               }
+               close(fd);
+       }
+}
diff --git a/messaged/Makefile b/messaged/Makefile
new file mode 100644 (file)
index 0000000..23e52c1
--- /dev/null
@@ -0,0 +1,17 @@
+
+target += sendmess
+obj-sendmess = sendmess.o
+
+target += saveauth
+obj-saveauth = saveauth.o normalise.o
+lib-saveauth = -L/usr/X11R6/lib -Wl,-rpath /usr/X11R6/lib -lXau -lX11
+
+
+target += multimessaged
+obj-multimessaged = multimessaged.o normalise.o
+lib-multimessaged = -L/usr/X11R6/lib -Wl,-rpath /usr/X11R6/lib -lXt -lXaw -lX11
+
+target-n += messaged
+obj-messaged = messaged.o normalise.o
+
+include $(S)$(D)../MakeRules
diff --git a/messaged/messaged.c b/messaged/messaged.c
new file mode 100644 (file)
index 0000000..5c2163c
--- /dev/null
@@ -0,0 +1,395 @@
+/* inetwall: place an xwindow with a message onto a workstation:
+   note that for apollos xmessage is execl however for everything else
+   the window is generated */
+
+
+#include       <sys/types.h>
+#include       <sys/stat.h>
+#include       <fcntl.h>
+#include       <stdio.h>
+#include       <sys/socket.h>
+#include       <netinet/in.h>
+#include       <netdb.h>
+#include       <syslog.h>
+
+#ifndef apollo
+#include       <X11/Intrinsic.h>
+#include       <X11/StringDefs.h>
+#include       <X11/Xaw/Form.h>
+#include       <X11/Xaw/AsciiText.h>
+#include       <X11/Xaw/Box.h>
+#include       <X11/Xaw/Command.h>
+#include       <X11/Xaw/Label.h>
+#include       <X11/Xatom.h>
+
+#include       "ok.xbm"
+#include       "ok1.xbm"
+# define rootwin(x)       RootWindow(XtDisplay(x), XtWindow(x))
+
+
+/* quit() causes the xmessage to disappear when */
+/* the OK button is pressed. */
+void quit(w,client_data,call_data)                 /* quit callback function */
+Widget w;
+XtPointer client_data,call_data;
+
+{
+  Arg wargs[1];
+  int n;
+  exit(0);
+}
+#endif
+
+#ifndef apollo
+#ifdef SYS5
+/* makenv makes a string that can be used to be put into 
+ * the environment.
+*/
+char * mkenv( char * a,char * b )
+{
+  char * c;
+  c = (char *) malloc (strlen(a)+1+strlen(b)+1);
+  sprintf( c,"%s=%s",a,b );
+  return c;
+}
+#endif
+
+char *normalise_display(char *disp)
+{
+    /* normalise display name.
+     * it should be host:num[.0]
+     * if host == unix or thing host, remove it
+     * remove any dot component of host
+     * if not .0, add .0
+     */
+    static char rv[200];
+    char hname[256];
+    char *p;
+
+    strcpy(rv, disp);
+    p = strchr(rv, ':');
+    if (p == NULL)
+       return rv;
+    *p = '\0';
+    p = strchr(rv, '.');
+    if (p) *p = '\0';
+    if (strcmp(rv, "unix") == 0)
+       rv[0] = '\0';
+    gethostname(hname, 256);
+    if (strcmp(rv, hname) == 0)
+       rv[0] = '\0';
+
+    p = strchr(disp, ':');
+    strcat(rv, p);
+    p = strchr(rv, '.');
+    if (p == NULL)
+       strcat(rv, ".0");
+    return rv;
+}
+
+       
+    
+/* create_xmessage() sends a xmessage to the screen. */
+create_xmessage(char * message, char *display)
+{
+  Widget toplevel;
+  char *temparg[20];
+  Widget    btn_widget,form_widget,text_widget;
+  Pixmap   ok_button_pixmap;
+  Arg      wargs[10];
+  char auth_file[200];
+  XtAppContext app_con;
+  int n = 3;
+
+  strcpy(auth_file, "/var/X11/xdm/save_auth/");
+  strcat(auth_file, normalise_display(display));
+
+#ifdef SYS5
+  putenv( mkenv( "XAUTHORITY", auth_file));
+#else
+  setenv( "XAUTHORITY", auth_file, 1);
+#endif
+  
+  temparg[0] = "messaged";
+  temparg[1] = "-display";
+  temparg[2] = display;
+  temparg[3] = "-geom";
+  temparg[4] = "+100+100";
+  temparg[5] = "-font";
+  temparg[6] = "-misc-fixed-*-*-normal-*-20-*-*-*-*-*-iso8859-*";
+  temparg[7] = NULL;
+
+  n=7;
+  toplevel = XtInitialize("Memo","Memo",NULL,0,&n,temparg);
+  
+  form_widget = XtCreateManagedWidget("buttonsForm",
+                                     formWidgetClass,
+                                     toplevel,
+                                     (ArgList) NULL, 0);
+  n=0;
+  XtSetArg (wargs[n], XtNlabel, message); n++;
+  text_widget = XtCreateManagedWidget ("message", labelWidgetClass,form_widget, wargs, n);
+  
+  n=0;
+  XtSetArg (wargs[n], XtNfromVert, text_widget); n++;
+  XtSetArg (wargs[n], XtNvertDistance, 5); n++;
+  XtSetArg (wargs[n],XtNfromHoriz,NULL); n++;
+  btn_widget = XtCreateManagedWidget("OK",commandWidgetClass,
+                                    form_widget,wargs,1);
+  ok_button_pixmap = XCreateBitmapFromData(XtDisplay(btn_widget),
+                                          rootwin(btn_widget),
+                                          ok_bits,
+                                          ok_width, ok_height);
+  
+  n=0;
+  XtSetArg(wargs[n], XtNbitmap, (XtArgVal) ok_button_pixmap);n++;
+  XtSetValues(btn_widget, wargs, 1);
+  XtAddCallback(btn_widget,XtNcallback, quit,0); 
+  XtRealizeWidget(toplevel);
+
+  XtMainLoop();
+}
+
+#endif
+
+/*****************************************************************************/
+
+#ifdef apollo                              /* APOLLO specific stuff */
+
+char X_SOCK[] = "/tmp/.X11-unix/X0";
+#include       <apollo/base.h>
+#include       <apollo/error.h>
+#include       <apollo/pad.h>
+#include       <apollo/gpr.h>
+
+
+dm_message(char * message, int lines, int columns)               /* write to the DM  */
+{
+    ios_$id_t wd;
+    pad_$window_desc_t pos;
+    short fid;
+    status_$t status;
+    int f2;
+    char c;
+    int i = 0;
+
+    pos.top = 30;
+    pos.left = 30;
+    pos.width = columns*11+13;
+    pos.height = lines*22+46;
+    if (pos.height > 770) pos.height = 770;
+    pad_$create_window("",0, pad_$transcript, 1, pos, &wd, &status);
+    
+    pad_$load_font(wd, "/sys/dm/fonts/f9x15", 19, &fid, &status);
+    pad_$use_font(wd, fid, &status);
+    while( (i += write(wd,&message[i],strlen(&message[i])+1)) < strlen(message) + 1 );
+    close(wd);
+}
+
+xmessage(char * message, int lines, int columns) /* executed on apollos: fires xmess */
+{
+    char geom[50];
+
+    sprintf(geom,"%dx%d+30+30",columns*10+16, lines*17+45);
+    execl("/usr/local/X11/bin/xmessage","xmessage",
+         "-font","-misc-fixed-*-*-normal-*-20-*-*-*-*-*-iso8859-*",
+         "-display","unix:0.0",
+         "-nsb",
+         "-b", "OK",
+         "-geometry",geom,
+         "-message",message,
+         0L);
+     exit(1);
+}
+
+#include       <sys/un.h>
+#include       <signal.h>
+#include       <setjmp.h>
+jmp_buf jb;
+
+catch()
+{
+    alarm(0);
+    longjmp(jb, 1);
+}
+check_X()                      /* check if the apollo machine is running X  */
+{
+    struct stat sb;
+    int sd;
+    struct sockaddr_un name;
+    FILE * f;
+
+    if (stat(X_SOCK, &sb)== -1
+       || ((sb.st_mode)&S_IFMT) != S_IFSOCK)
+       return 0;
+    signal(SIGALRM, (void *) catch);
+    if (setjmp(jb)) { close(sd); return 0;}
+    sd = socket(AF_UNIX, SOCK_STREAM, 0);
+    name.sun_family = AF_UNIX;
+    strcpy(name.sun_path, X_SOCK);
+    alarm(6);
+    if (connect(sd, (struct sockaddr *) &name, sizeof(name))== -1)
+    {
+       alarm(0);
+       close(sd);
+       return 0;
+       
+    }
+    alarm(0);
+    sleep(3);
+    close(sd);
+    return 1;
+}
+
+/* check_dm() checks if the apollo is running the dm. */
+check_dm()
+{
+    gpr_$offset_t size;
+    gpr_$bitmap_desc_t bd;
+    status_$t status;
+    int rv;
+
+    size.x_size = 20;
+    size.y_size = 20;
+    gpr_$init(gpr_$borrow_nc, 1, size, 0, &bd, &status);
+    if (status.all == 0)
+       rv = 1;
+    else
+       rv = 0;
+    gpr_$terminate(false, &status);
+    return rv;
+}
+#endif
+
+/*****************************************************************************/
+
+
+
+/* read_file() returns the contents of a file */
+char *read_file(int fd)
+{
+    int len = lseek(fd, 0L, 2);
+    char *rv = (char*)malloc(len+1);
+    lseek(fd, 0L, 0);
+    read(fd, rv, len);
+    rv[len] = 0;
+    return rv;
+}    
+
+main(argc,argv)
+char *argv[];
+{
+    FILE *f;
+    int cols=0;
+    int sock = 0;
+    int fd;
+    int ccnt=0;                        /* records the maximum width needed for a message in a box */
+    int lines=0;
+    char c;
+    char *message;
+    char mfile[40];
+    struct sockaddr_in name;
+    int namelen = sizeof(name);
+    char first_line[200];
+    int flp = 0;
+
+    strcpy(mfile,"/tmp/message.XXXXXX");
+
+    if (strcmp(argv[0],"messaged")==0)
+    {
+       if (getpeername(sock,(struct sockaddr *) &name,&namelen) == 0)
+       {
+           /* check to see if incoming message is coming from the localhost
+            * That is, the machine that messaged is on.
+            */
+           if (ntohs(name.sin_port) >= 1024) exit(1);
+           if (name.sin_addr.s_addr != htonl(0x7f000001))
+           {
+               struct hostent *he;
+               int a;
+               char host[1024];
+               he = gethostbyaddr((char*)&name.sin_addr, 4, AF_INET);
+               if (he == NULL)
+                   exit(1);
+               strcpy(host, he->h_name);
+               he = gethostbyname(host);
+               if (he == NULL)
+                   return exit(1);;
+               for (a=0; he->h_addr_list[a] ; a++)
+                   if (memcmp(&name.sin_addr, he->h_addr_list[a], 4)==0)
+                   {
+                       /* well, we have a believeable name */
+                       char *tail = ".cse.unsw.EDU.AU";
+                       int len;
+
+                       len = strlen(host);
+                       if (len > strlen(tail) && strcmp(tail, host+len - strlen(tail))== 0)
+                           break;
+                       exit(1);
+                   }
+               if (he->h_addr_list[a]==0)
+                   exit(1);
+           }
+       }
+       else
+           exit(1);
+    }
+    
+    fflush(stdout);
+
+    /* test to see if a temporary file can be created.
+     * If not, the program is exitted.
+     */
+    mktemp(mfile);
+    if ((fd = open(mfile,O_CREAT|O_RDWR,0600)) < 0) 
+       exit(1);
+    unlink(mfile);
+  
+    /* collect the message from the incoming socket. */
+    while(read(sock,&c,1) == 1)
+    {
+       if (lines != 0)
+           write(fd,&c,1);
+       else
+       {
+           if (flp < sizeof(first_line))
+               first_line[flp++] = c;
+       }
+       if ( c == '\n')
+       {
+           if (cols < ccnt)
+               cols = ccnt;
+           lines++;
+           ccnt=0;
+       }
+       ccnt++;
+    }
+    lines --;
+    message = read_file(fd);
+    close(fd);
+
+    if (first_line[flp-1] == '\n')
+       flp--;
+    first_line[flp] = 0;
+    
+#ifndef apollo
+
+    /****************************************************************************/
+    /* This is the X stuff that all the other machines use: should be in other  */
+    /* proc but I cant get it to work                                           */
+  
+    create_xmessage(message, first_line);
+    /****************************************************************************/
+  
+#else
+    /* APOLLO stuff */
+    if (!check_dm()) 
+       xmessage(message,lines,cols);
+    else
+       dm_message(message, lines, cols); 
+#endif
+}
+
+
+
diff --git a/messaged/multimessaged.c b/messaged/multimessaged.c
new file mode 100644 (file)
index 0000000..801bd0a
--- /dev/null
@@ -0,0 +1,424 @@
+
+/*
+ * messaged: send a message to a login session.
+ *   Currently only supports messages on X terminals
+ *
+ * Usage: messaged [-l] [-L port] [-t timeout] [-g geometry] [-f font] [-D auth-dir] [-d domain]
+ *
+ * -l:  messaged will listen on stdin to accept connections- each connection provides a message
+ * -L:  messaged will listen on the given tcp port and accept connections.
+ * with neither of these, messaged will read a single message from stdin and send it.
+ *
+ * -t:  When -l is used, messaged will exit after a period of inactiveity (no messages displayed).
+ *      This period is forever by default, but can be set to a number of minutes with -t
+ * -g:
+ * -f:  these specify the geometry (just position, not size) and font to use for the message
+ *      when displayed on an X terminal.
+ *      default -g +100+100
+ *     default -f -misc-fixed-*-*-normal-*-20-*-*-*-*-*-iso8859-*
+ * -D:  this specifies a directory in which to find Xauthority files.
+ *      default -D /var/X11/xdm/save_auth
+ * -d:  this specifies an internet domain to accept connections from.
+ *      all incoming connections must from a privileged port.
+ *      normally only connections from localhost are accepted.
+ *      If, however, a domain is specifed, connections from any host whose canonical
+ *     name ends with domain are accepted.
+ *
+ * message is one line describing target and a number of lines of message
+ * The total messages is limited to 2048 bytes
+ */
+
+#define        MaxMesgSize     2048
+char *font = "-misc-fixed-*-*-normal-*-20-*-*-*-*-*-iso8859-*";
+char *geometry = "+100+100";
+char *auth_dir = "/var/X11/xdm/save_auth";
+char *okdomain = (char*)0;
+int timeout = -1;
+
+#include       <sys/types.h>
+#include       <sys/stat.h>
+#include       <fcntl.h>
+#include       <stdio.h>
+#include       <sys/socket.h>
+#include       <netinet/in.h>
+#include       <netdb.h>
+#include       <syslog.h>
+#include       <string.h>
+#include       <setjmp.h>
+#include       <signal.h>
+
+#include       <X11/Intrinsic.h>
+#include       <X11/Shell.h>
+#include       <X11/StringDefs.h>
+#include       <X11/Xaw/Form.h>
+#include       <X11/Xaw/AsciiText.h>
+#include       <X11/Xaw/Box.h>
+#include       <X11/Xaw/Command.h>
+#include       <X11/Xaw/Label.h>
+#include       <X11/Xatom.h>
+#define _WCHAR_T
+#include       <unistd.h>
+#include       <stdlib.h>
+
+#include       "ok.xbm"
+
+extern char *normalise_display(char*);
+
+int listenon(char *portname)
+{
+       struct sockaddr_in sa;
+       int sock;
+       int i =1;
+       memset(&sa, 0, sizeof(sa));
+       if (portname[0]>='0' && portname[0]<='9')
+               sa.sin_port = htons(atoi(portname));
+       else
+       {
+               struct servent *sv;
+               sv = getservbyname(portname, "tcp");
+               if (sv == NULL)
+               {
+                       fprintf(stderr,"messaged: unknown port: %s\n", portname);
+                       return -1;
+               }
+               sa.sin_port = sv->s_port;
+       }
+       sa.sin_family = AF_INET;
+       sock = socket(AF_INET, SOCK_STREAM, 0);
+       setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&i, 4);
+       if (bind(sock, (struct sockaddr *)&sa, sizeof(sa))!= 0)
+       {
+               perror("messaged: bind");
+               exit(1);
+       }
+       listen(sock, 5);
+       return sock;
+}
+
+static int address_ok(struct sockaddr_in *sa, char *host)
+{
+       struct hostent *he;
+       int len;
+       int a;
+       static char name[256];
+       if (host == NULL) host = name;
+
+       if (ntohs(sa->sin_port) >= 1024 && geteuid() == 0)
+               return 0;
+       if (sa->sin_addr.s_addr == htonl(0x7f000001))
+       {
+               strcpy(host, "localhost");
+               return 1; /* localhost */
+       }
+       if (okdomain == NULL) return 0;
+       he = gethostbyaddr((char*)&sa->sin_addr, 4, AF_INET);
+       if (he == NULL)
+               return 0;
+       strcpy(host, he->h_name);
+       he = gethostbyname(host);
+       if (he == NULL)
+               return 0;
+       for (a=0; he->h_addr_list[a] ; a++)
+               if (memcmp(&sa->sin_addr, he->h_addr_list[a], 4)==0)
+               {
+                       /* well, we have a believable name */
+
+                       len = strlen(host);
+                       if (len > strlen(okdomain) && strcmp(okdomain, host+len - strlen(okdomain))== 0)
+                               return 1;
+                       return 0;
+               }
+       return 0;
+}
+
+void set_auth(char *file)
+{
+       static char ** newenv = NULL;
+       extern char **environ;
+       char *cp;
+       if (newenv)
+               free(newenv[0]);
+       else
+       {
+               int c;
+               for (c=0; environ[c] ; c++);
+               newenv = (char**)malloc((c+2) * sizeof(char*));
+               for (c=0; environ[c] ; c++)
+                       newenv[c+1] = environ[c];
+               newenv[c+1] = NULL;
+       }
+       cp = (char*)malloc(strlen("XAUTHORITY=") + strlen(file)+1);
+       strcat(strcpy(cp, "XAUTHORITY="), file);
+       newenv[0] = cp;
+       environ = newenv;
+}
+    
+XtAppContext app;
+jmp_buf keep_going;
+
+int active_cnt = 0;
+            
+
+struct windata {
+    Widget parent;
+    Pixmap ok;
+    Display *disp;
+};
+
+void deactive()
+{
+       active_cnt--;
+       if (active_cnt>0) return;
+       active_cnt=0;
+       if (timeout < 0) return;
+       if (timeout == 0) exit(0);
+       if (timeout > 0)
+               alarm(timeout*60);
+}
+
+void quit(Widget button, XtPointer data, XtPointer nothing)
+{
+       struct windata *wd = (struct windata *) data;
+       XtDestroyWidget(wd->parent);
+       XtCloseDisplay(wd->disp);
+       free(wd);
+       deactive();
+
+}
+
+int am_opening = 0;
+
+int io_error(Display *D)
+{
+       if (!am_opening)
+               XtCloseDisplay(D);
+       deactive();
+       am_opening = 0;
+       longjmp(keep_going, 1);
+}
+
+void xterror(char *msg)
+{
+       /* just keep going... */
+       return;
+}
+
+struct buf
+{
+       char buf[MaxMesgSize];
+       int len;
+};
+
+
+void do_read(XtPointer buf, int *sock, XtInputId *id)
+{
+       int nargs;
+       char *args[20];
+       Display *Disp;
+       char *display, *message;
+       Widget shell;
+       struct windata * context;
+
+       Widget    btn_widget,form_widget,text_widget;
+       Pixmap   ok_button_pixmap = 0;
+       Arg      wargs[10];
+       char auth_file[200];
+
+       int nsock = *sock;
+       struct buf *abuf = (struct buf*)buf;
+       int n = 0;
+       if (abuf->len < sizeof(abuf->buf)-2)
+               n=read(nsock, abuf->buf+abuf->len, sizeof(abuf->buf)-abuf->len-1);
+       if (n>0)
+       {
+               abuf->len += n;
+               return;
+       }
+       close(nsock);
+       XtRemoveInput(*id);
+
+       if (n < 0 || abuf->len > sizeof(abuf->buf)-2)
+       {
+               /* error or too much data */
+               free(abuf);
+               deactive();
+               return;
+       }
+       /* now find display name and display it ... FIXME */
+       display = abuf->buf;
+       abuf->buf[abuf->len] = 0;
+       message = strchr(abuf->buf, '\n');
+       if (!message) { free(abuf); return;}
+       *message++ = 0;
+        
+
+
+       strcpy(auth_file, auth_dir);
+       strcat(auth_file, "/");
+       strcat(auth_file, normalise_display(display));
+
+       set_auth(auth_file);
+    
+       nargs=0;
+       args[nargs++] = "Messaged";
+       args[nargs++] = "-geom";
+       args[nargs++] = geometry;
+       args[nargs++] = "-font";
+       args[nargs++] = font;
+
+       args[nargs] = NULL;
+       am_opening = 1;
+       Disp = XtOpenDisplay(app, display, "Memo", "Memo", NULL, 0, &nargs, args);
+       am_opening = 0;
+       if (Disp == NULL)
+       {
+               /* HACK try adding .x to host name */
+               char d2[100];
+               int l;
+               strcpy(d2, display);
+               l = strlen(d2);
+               if (strcmp(d2+l-2, ":0")==0)
+               {
+                       strcpy(d2+l-2, ".x:0");
+                       am_opening = 1;
+                       Disp = XtOpenDisplay(app, d2, "Memo", "Memo", NULL, 0, &nargs, args);
+                       am_opening = 0;
+               }
+       }
+       if (Disp == NULL)
+       {
+               free(abuf);
+               deactive();
+               return;
+       }
+       shell = XtAppCreateShell("Memo", "Memo", applicationShellWidgetClass, Disp, NULL, 0);
+       form_widget = XtCreateManagedWidget("buttonsForm", formWidgetClass, shell, (ArgList) NULL, 0);
+       n=0;
+       XtSetArg (wargs[n], XtNlabel, message); n++;
+       text_widget = XtCreateManagedWidget ("message", labelWidgetClass,form_widget, wargs, n);
+  
+       n=0;
+       XtSetArg (wargs[n], XtNfromVert, text_widget); n++;
+       XtSetArg (wargs[n], XtNvertDistance, 5); n++;
+       XtSetArg (wargs[n], XtNfromHoriz,NULL); n++;
+       btn_widget = XtCreateManagedWidget("OK",commandWidgetClass,
+                                          form_widget,wargs,1);
+       ok_button_pixmap = XCreateBitmapFromData(XtDisplay(btn_widget),
+                                                RootWindow(XtDisplay(btn_widget),
+                                                           XtWindow(btn_widget)),
+                                                ok_bits,
+                                                ok_width, ok_height);
+  
+       n=0;
+       XtSetArg(wargs[n], XtNbitmap, (XtArgVal) ok_button_pixmap);n++;
+       XtSetValues(btn_widget, wargs, 1);
+       context = (struct windata*)malloc(sizeof(struct windata));
+       context->parent = shell;
+       context->ok = ok_button_pixmap;
+       context->disp = Disp;
+       XtAddCallback(btn_widget,XtNcallback, quit,(XtPointer)context); 
+       XtRealizeWidget(shell);
+       free(abuf);
+       XFlush(Disp);
+}
+
+void do_accept(XtPointer closure, int *sock, XtInputId *id)
+{
+       struct sockaddr_in sa;
+       unsigned int salen = sizeof(sa);
+       int nsock = accept(*sock, (struct sockaddr *)&sa, &salen);
+       struct buf *abuf;
+
+
+       if (nsock <0) return;
+    
+
+       if (sa.sin_family != AF_INET)
+       {
+               close(nsock);
+               return;
+       }
+
+       if (!address_ok(&sa, NULL))
+       {
+               close(nsock);
+               return;
+       }
+       abuf = (struct buf*)malloc(sizeof(struct buf));
+       abuf->len = 0;
+       active_cnt++;
+       alarm(0);
+       XtAppAddInput(app, nsock, (XtPointer)XtInputReadMask, do_read, abuf);
+
+}
+
+void catchalrm(int sig)
+{
+       exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+       /* arg is port num */
+       int sock = -1;
+       int arg;
+       extern char *optarg;
+       extern int optind;
+
+
+       while ((arg=getopt(argc, argv, "lL:t:g:f:D:"))!= -1)
+               switch(arg)
+               {
+               case 'l':
+                       sock = 0; break;
+               case 'L':
+                       sock = listenon(optarg);
+                       if (sock < 0)
+                       {
+                               fprintf(stderr, "messaged: cannot bind to %s\n", optarg);
+                               exit(1);
+                       }
+                       break;
+               case 't':
+                       timeout = atoi(optarg);
+                       break;
+               case 'g':
+                       geometry = optarg;
+                       break;
+               case 'f':
+                       font = optarg;
+                       break;
+               case 'D':
+                       auth_dir = optarg;
+                       break;
+               case 'd':
+                       okdomain = optarg;
+                       break;
+               default:
+                       fprintf(stderr, "messaged: unrecognised option in %s.\n", argv[optind]);
+                       exit(1);
+               }
+    
+
+
+       XtToolkitInitialize();
+       app = XtCreateApplicationContext();
+
+       if (sock >= 0)
+               XtAppAddInput(app, sock, (XtPointer)XtInputReadMask, do_accept, &sock);
+       else
+       {
+               struct buf *abuf = (struct buf*)malloc(sizeof(struct buf));
+               XtAppAddInput(app, 0, (XtPointer)XtInputReadMask, do_read, abuf);
+               timeout = 0;
+               active_cnt = 1;
+       }
+
+       signal(SIGALRM, catchalrm);
+       setjmp(keep_going);
+       XSetIOErrorHandler(io_error);
+       XtSetErrorHandler(xterror);
+       active_cnt++; deactive();
+       XtAppMainLoop(app);
+       exit(1);
+}
diff --git a/messaged/normalise.c b/messaged/normalise.c
new file mode 100644 (file)
index 0000000..9134e8c
--- /dev/null
@@ -0,0 +1,47 @@
+
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <memory.h>
+#include       <sys/types.h>
+#include       <netdb.h>
+#include       <ctype.h>
+#include       <string.h>
+
+char *normalise_display(char *disp)
+{
+    /* normalise display name.
+     * normalised name should be FQDN:num.0
+     * original name may start unix: or just :
+     * in which case hostname is used.
+     */
+    static char rv[200];
+    char *colon;
+    char ad[40];
+    struct hostent *he;
+    int i;
+
+    colon = strchr(disp, ':');
+    if (colon == NULL) return NULL;
+    
+    strcpy(rv, disp);
+    rv[colon-disp]='\0';
+
+    if (strcmp(rv, "unix")==0 || rv[0] == '\0')
+       gethostname(rv, 200);
+    he = gethostbyname(rv);
+    if (he == NULL)
+       return NULL;
+    /* because of solaris, we must take an address and map it back */
+    memcpy(ad, he->h_addr_list[0], he->h_length);
+    he = gethostbyaddr(ad, he->h_length, he->h_addrtype);
+    if (he == NULL) return NULL;
+
+    strcpy(rv, he->h_name);
+    for (i=0; rv[i]; i++)
+       if (isupper(rv[i])) rv[i]=tolower(rv[i]);
+
+    strcat(rv, colon);
+    if (strchr(colon, '.')==0)
+       strcat(rv, ".0");
+    return rv;
+}
diff --git a/messaged/ok.xbm b/messaged/ok.xbm
new file mode 100644 (file)
index 0000000..422b9ea
--- /dev/null
@@ -0,0 +1,12 @@
+#define ok_width 25
+#define ok_height 25
+static char ok_bits[] = {
+   0xf0, 0xff, 0x1f, 0x00, 0x0c, 0x00, 0x60, 0x00, 0x02, 0x00, 0x80, 0x00,
+   0x02, 0x00, 0x80, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01,
+   0x01, 0x00, 0x00, 0x01, 0xe1, 0x30, 0x33, 0x01, 0xf1, 0x31, 0x33, 0x01,
+   0x19, 0xb3, 0x31, 0x01, 0x19, 0xf3, 0x30, 0x01, 0x19, 0x73, 0x30, 0x01,
+   0x19, 0x73, 0x30, 0x01, 0x19, 0xf3, 0x30, 0x01, 0x19, 0xb3, 0x01, 0x01,
+   0xf1, 0x31, 0x33, 0x01, 0xe1, 0x30, 0x33, 0x01, 0x01, 0x00, 0x00, 0x01,
+   0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01,
+   0x02, 0x00, 0x80, 0x00, 0x02, 0x00, 0x80, 0x00, 0x0c, 0x00, 0x60, 0x00,
+   0xf0, 0xff, 0x1f, 0x00};
diff --git a/messaged/ok1.xbm b/messaged/ok1.xbm
new file mode 100644 (file)
index 0000000..1801407
--- /dev/null
@@ -0,0 +1,12 @@
+#define ok1_width 25
+#define ok1_height 25
+static char ok1_bits[] = {
+   0xff, 0x01, 0xff, 0xff, 0x7f, 0xfe, 0xfc, 0xff, 0x9f, 0xff, 0xf3, 0xff,
+   0xef, 0xff, 0xef, 0xff, 0xf7, 0xff, 0xdf, 0xff, 0xfb, 0xff, 0xbf, 0xff,
+   0xfb, 0xff, 0xbf, 0xff, 0x1d, 0xef, 0x6d, 0xff, 0xed, 0xee, 0x6e, 0xff,
+   0xf6, 0x6d, 0xef, 0xfe, 0xf6, 0xad, 0xef, 0xfe, 0xf6, 0xcd, 0xef, 0xfe,
+   0xf6, 0xad, 0xef, 0xfe, 0xf6, 0x6d, 0xef, 0xfe, 0xf6, 0xed, 0xfe, 0xfe,
+   0xee, 0xee, 0xfd, 0xfe, 0x1d, 0xef, 0x6d, 0xff, 0xfd, 0xff, 0x7f, 0xff,
+   0xfb, 0xff, 0xbf, 0xff, 0xfb, 0xff, 0xbf, 0xff, 0xf7, 0xff, 0xdf, 0xff,
+   0xef, 0xff, 0xef, 0xff, 0x9f, 0xff, 0xf3, 0xff, 0x7f, 0xfe, 0xfc, 0xff,
+   0xff, 0x01, 0xff, 0xff};
diff --git a/messaged/saveauth.c b/messaged/saveauth.c
new file mode 100644 (file)
index 0000000..91e5a8a
--- /dev/null
@@ -0,0 +1,165 @@
+
+#include       <errno.h>
+#include       <sys/types.h>
+#include       <sys/stat.h>
+#include       <string.h>
+
+#include       <X11/Xlib.h>
+#include       <X11/Xauth.h>
+
+#define _WCHAR_T
+#include       <unistd.h>
+#include       <stdlib.h>
+
+#define FAILURE 0
+#define SUCCESS 1
+#ifndef NULL
+#define NULL '\0'
+#endif 
+#ifndef BOOLEAN
+#define BOOLEAN char
+#define FALSE 0
+#define TRUE 1
+#endif
+
+#define XauthFile "/var/X11/xdm/save_auth/"
+
+static int verbosity = 0;
+extern char *normalise_display(char*);
+
+
+char *old_normalise_display(char *disp)
+{
+       /* normalise display name.
+        * it should be host:num[.0]
+        * if host == unix or this host, remove it
+        * remove any dot component of host
+        * if not .0, add .0
+        */
+       static char rv[200];
+       char hname[256];
+       char *p;
+
+       strcpy(rv, disp);
+       p = strchr(rv, ':');
+       if (p == NULL)
+               return rv;
+       *p = '\0';
+       p = strchr(rv, '.');
+       if (p) *p = '\0';
+       if (strcmp(rv, "unix") == 0)
+               rv[0] = '\0';
+       gethostname(hname, 256);
+       if (strcmp(rv, hname) == 0)
+               rv[0] = '\0';
+
+       p = strchr(disp, ':');
+       strcat(rv, p);
+       p = strchr(rv, '.');
+       if (p == NULL)
+               strcat(rv, ".0");
+       return rv;
+}
+
+int copy_xauth(char *disp)
+{
+       FILE 
+               * xau_read,
+               * xau_write;
+       Xauth
+               * xauthrec = NULL;
+       int
+               success = SUCCESS;
+       struct stat
+               buff;
+
+       char authfile[200];
+
+       strcpy(authfile, XauthFile);
+       strcat(authfile, disp);
+       /* test for existance of file */
+       if ( stat(authfile,&buff) == 0 )
+       {
+               /* file exists. Let's remove it. */
+               if ( unlink(authfile) < 0 )
+               {
+                       perror( "unlink:" );
+                       return FAILURE;
+               }
+       }
+       else if ( errno != ENOENT )
+       {
+               perror( "stat:" );
+               return FAILURE;
+       }
+       if ( (xau_read = fopen( XauFileName(),"r" )) == NULL )
+       {
+               fprintf( stderr,"Unable to open %s for reading.\n",XauFileName() );
+               return FAILURE;
+       }
+       if ( verbosity )
+               printf( "Finished opening file to read.\n" );
+       umask(0177);
+       if ( (xau_write = fopen(authfile,"w" )) == NULL )
+       {
+               fprintf( stderr,"Unable to open %s for writing.\n",authfile );
+               return FAILURE;
+       }
+       if ( verbosity )
+               printf( "Finished opening file to write.\n" );
+       while( (xauthrec = XauReadAuth(xau_read)) != NULL && (success == SUCCESS) )
+               if ( XauWriteAuth(xau_write,xauthrec) == 0 )
+               {
+                       fprintf( stderr,"Unable to write data to file %s.\n",authfile );
+                       success = FAILURE;
+               }
+       if ( chmod(authfile,0600) < 0 )
+       {
+               perror( "chmod:" );
+               return FAILURE;
+       }
+       return success;
+}
+
+  
+  
+
+BOOLEAN can_connect_to_display()
+{
+       Display * display;
+
+       if ( (display = XOpenDisplay(NULL)) == NULL )
+               return FALSE;
+       XCloseDisplay( display );
+       return TRUE;
+}
+
+int main(int argc,char * argv[])
+{
+
+#if 0
+       while((c = getopt(argc,argv,"v")) != EOF)
+               switch(c)
+               {
+               case 'v':
+                       verbosity = 1;
+                       break;
+               }
+#endif
+       if ( !can_connect_to_display() )
+       {
+               fprintf( stderr,"Unable to connect to display.\n" );
+               exit(1);
+       }
+       if ( verbosity )
+               printf( "Connected to display.\n" );
+       if ( (copy_xauth(normalise_display(XDisplayName(NULL))) == FAILURE)
+            & (copy_xauth(old_normalise_display(XDisplayName(NULL))) == FAILURE))
+               exit(1);
+       if ( verbosity )
+               printf( "Able to copy xauthority.\n" );
+       exit(0);
+}
+
+      
+  
diff --git a/messaged/sendmess.c b/messaged/sendmess.c
new file mode 100644 (file)
index 0000000..76cb3d5
--- /dev/null
@@ -0,0 +1,89 @@
+/*****************************************************************************/
+/* program to send message to winmess...... my window message program        */
+
+#include       <sys/types.h>
+#include       <unistd.h>
+#include       <stdlib.h>
+#include       <sys/stat.h>
+#include       <fcntl.h>
+#include       <stdio.h>
+#include       <sys/socket.h>
+#include       <netinet/in.h>
+#include       <netdb.h>
+#include       <memory.h>
+
+#define PORT 607;              /* inetd port */
+
+void error(message)
+char *message;
+{
+       fprintf(stderr,"ERROR: %s\n",message);
+}
+
+FILE *connection_init(char *host)                      /* connect to local host socket */
+{
+       int sock;
+       struct sockaddr_in server;
+       struct hostent *hp, *gethostbyname();
+       struct servent *sev;
+       int lport = IPPORT_RESERVED -1;
+       FILE *write_file;
+       /* Create Socket */
+       if (geteuid() != 0)             /* if effective uid not privledged get out */
+               exit(1);
+       if ((sock= rresvport(&lport)) == -1 ) /* get priveldged socket */
+       {
+               perror("opening stream socket\n");
+               exit(1);
+       }
+       server.sin_family = AF_INET;
+       if ((hp = gethostbyname(host?host:"localhost")) == 0 )
+       {
+               fprintf(stderr,"Can't find %s??", host?host:"localhost");
+               exit(1);
+       }
+       memcpy(&server.sin_addr,hp->h_addr,hp->h_length );
+       if ( (sev = getservbyname("message","tcp")) == NULL )
+       {
+               fprintf( stderr,"Unable to get service %s\n","message" );
+               exit(1);
+       }
+       server.sin_port = sev->s_port;
+       if (connect(sock,(struct sockaddr *) &server,sizeof(server)) < 0)
+       {
+               perror("connection stream socket");
+               exit(1);
+       }
+       write_file = fdopen(sock,"w");
+       return(write_file);
+}
+
+
+
+int main(int argc, char *argv[])
+{
+       int c;
+       char *first_line=NULL;
+       FILE *write_file;
+       char *host = NULL;
+  
+       if (argc > 3)
+       {
+               error("Usage: sendmessage [display [host]]");
+               exit(1);
+       }
+       if (argc >= 2)
+               first_line = argv[1];
+       if (argc >=3)
+               host = argv[2];
+       write_file=connection_init(host);
+       if (first_line)
+       {
+               write(fileno(write_file),first_line,strlen(first_line));
+               write(fileno(write_file),"\n",1);
+       }
+       while ((c = getchar()) != EOF)
+               putc(c,write_file);
+       return 0;
+}
+
diff --git a/tools/Makefile b/tools/Makefile
new file mode 100644 (file)
index 0000000..0e5a221
--- /dev/null
@@ -0,0 +1,28 @@
+
+target += user
+obj-user =  user.o show_booking.o ../db_client/libdbclient.a  ../database/libdbclnt.a  ../manager/status_client.a ../lib/lib.a
+
+target += token
+obj-token = token.o ../db_client/libdbclient.a ../database/libdbclnt.a  ../manager/status_client.a ../lib/lib.a
+
+target += booking
+obj-booking =  booking.o show_booking.o ../lablist/lablist.o ../db_client/libdbclient.a ../database/libdbclnt.a  ../manager/status_client.a ../lib/lib.a
+
+target += dbadmin
+obj-dbadmin = dbadmin.o ../db_client/libdbclient.a ../database/libdbclnt.a  ../manager/status_client.a ../lib/lib.a
+
+target += bkhost
+obj-bkhost =  host.o  ../db_client/libdbclient.a ../database/libdbclnt.a  ../manager/status_client.a ../lib/lib.a
+
+#FIXME bkhg bkws bklb are links to bkhost
+
+target += attr
+obj-attr = attr.o  ../db_client/libdbclient.a ../database/libdbclnt.a  ../manager/status_client.a ../lib/lib.a
+
+target += conf
+obj-conf = conf.o ../db_client/libdbclient.a ../database/libdbclnt.a  ../manager/status_client.a ../lib/lib.a
+
+target += smadmin
+obj-smadmin = smadmin.o  ../manager/print_tab.o   ../manager/check_servers.o ../db_client/libdbclient.a ../database/libdbclnt.a ../manager/status_client.a  ../lib/lib.a
+
+include $(S)$(D)../MakeRules
diff --git a/tools/attr.c b/tools/attr.c
new file mode 100644 (file)
index 0000000..b4927f2
--- /dev/null
@@ -0,0 +1,693 @@
+#include       "../db_client/db_client.h"
+#include       "../lib/skip.h"
+#include       "../lib/dlink.h"
+
+char Digits[] = "0123456789";
+/*
+ * set up the attribute info
+ *
+ * we read a file listing attributes, implications
+ * and general config entries
+ * building a list
+ * this is compared with the database and changes made
+ * if necessary
+ *
+ * the file contains:
+ *
+ * attributes defines:   nnn:name type
+ * implications:    day-day time-time dow-dow attr !attr -> new
+ * config:          name=value
+ *
+ * config entries are checked and set whenever they are seen
+ * attributes and implications are stored in tables
+ * and then compared against the database
+ *
+ */
+
+typedef struct attr {
+    entityid num;
+    char *name;
+} *attr;
+
+static int attr_cmp_num(attr a, attr b, int *np)
+{
+       int n;
+       if (b != NULL)
+               n = b->num;
+       else n = *np;
+       return a->num - n;
+}
+
+static int attr_cmp_name(attr a, attr b, char *n)
+{
+       if (b!=NULL)
+               n = b->name;
+       return strcmp(a->name, n);
+}
+
+static void attr_free(attr a)
+{
+       free(a->name);
+       free(a);
+}
+
+void *nums, *names;
+void attr_store(int num, char *name)
+{
+       attr a = (attr)malloc(sizeof(struct attr));
+       a->num = num;
+       a->name = name;
+       skip_insert(nums, a);
+       skip_insert(names, a);
+}
+
+void attr_init()
+{
+       nums = skip_new(attr_cmp_num, attr_free, NULL);
+       names = skip_new(attr_cmp_name, NULL, NULL);
+}
+
+int attr_find(char *name)
+{
+       attr *ap = skip_search(names, name);
+       if (ap)
+               return (*ap)->num;
+       else return -1;
+}
+
+
+void attr_update(int check)
+{
+       /* first make sure each name we know about is set
+        * then check all other numbers an clear them
+        */
+       attr *ap;
+
+       for (ap = skip_first(nums); ap ; ap=skip_next(ap))
+       {
+               attr a = *ap;
+               char *nam;
+               nam = get_mappingchar(a->num, M_ATTRIBUTE);
+               if (nam == NULL || strcmp(nam, a->name)!= 0)
+               {
+                       /* need to change name
+                        * first make sure name not in use
+                        */
+                       entityid other;
+                       other = get_mappingint(a->name, M_ATTRIBUTE);
+                       if (other>=0)
+                       {
+                               if (other == a->num)
+                                       printf("WARNING: mapping confusion for %d %s\n",
+                                              a->num, a->name);
+                               else
+                               {
+                                       make_mapping(other, "", M_ATTRIBUTE);
+                               }
+                       }
+                       if (make_mapping(a->num, a->name, M_ATTRIBUTE) != 0)
+                               fprintf(stderr,"attr: failed to make mapping %d -> %s\n",
+                                       a->num, a->name);
+               }
+               if (nam) free(nam);
+       }
+       /* don't worry about others yet FIXME */
+}
+
+char *attr_add(char *line, attr_type_desc *at_type)
+{
+       /* it has been deduced that this line is an
+        * attribute line, probably because it has a colon.
+        * parse out the number, name and type(s) as
+        *  num:name type [type]
+        *
+        * if this does not conflict with list so far,
+        * store it and set appropriate bits in
+        * at_type
+        *
+        * types are: lab optional open closed available bookable reusable firmalloc
+        *  open et.al can only appear on one attribute.
+        */
+
+       int digits;
+       char *name;
+       int num;
+       char *cp;
+       attr *a;
+
+       digits = strspn(line, Digits);
+       if (digits<0)
+               return "no attribute number";
+       num = atoi(line);
+       if (num < 0 || num > BITINDMAX)
+               return "bad attribute number";
+       if (line[digits] != ':')
+               return "colon not found after number";
+
+       cp = line + digits+1;
+       while (*cp == ' ' || *cp == '\t' ) cp++;
+       if (!*cp)
+               return "attribute name missing";
+       name = cp;
+       while (*cp && *cp != ' ' && *cp != '\t')
+               cp++;
+       name = strndup(name, cp-name);
+       while (*cp == ' ' || *cp == '\t') cp++;
+
+       set_a_bit(&at_type->all, num);
+       set_a_bit(&at_type->mandatory, num);
+       while (*cp) /* looking at a type */
+       {
+               char *type = cp;
+               int *ip = NULL;
+               while (*cp && *cp != ' ' && *cp != '\t')
+                       cp++;
+               type = strndup(type, cp-type);
+               while (*cp == ' ' || *cp == '\t') cp++;
+
+               if (strcmp(type, "lab")==0)
+                       set_a_bit(&at_type->lab, num);
+               else if (strcmp(type, "optional")==0)
+                       del_a_bit(&at_type->mandatory, num);
+               else if (strcmp(type, "open")==0)
+                       ip = &at_type->open;
+               else if (strcmp(type, "closed")==0)
+                       ip = &at_type->closed;
+               else if (strcmp(type, "available")==0)
+                       ip = &at_type->available;
+               else if (strcmp(type, "bookable")==0)
+                       ip = &at_type->bookable;
+               else if (strcmp(type, "reusable")==0)
+                       ip = &at_type->reusable;
+               else if (strcmp(type, "firmalloc") == 0)
+                       ip = &at_type->firmalloc;
+               else
+                       return "unknown attribute type";
+
+               free(type);
+               if (ip)
+               {
+                       if (*ip >= 0)
+                               return "multiple defination of singular type";
+                       else
+                               *ip = num;
+               }
+       }
+
+       if ((a=skip_search(nums, &num)))
+               return "repeat definition of attribute number";
+       if ((a=skip_search(names, name)))
+               return "repeat definition of attribute name";
+    
+       attr_store(num, name);
+       return NULL;
+    
+}
+
+/*
+ * implicatations are stored in a dlist of implication
+ * structures
+ */
+
+void *implist;
+int impcnt = 0;
+
+char *msg(char *b, char *m, char *a1, char *a2)
+{
+       sprintf(b,m,a1,a2);
+       return b;
+}
+
+char *get_word(char **line)
+{
+       char *cp  = *line;
+       char *rv;
+       while (*cp == ' ' || *cp == '\t') cp++;
+       rv = cp;
+       while (*cp && *cp != ' ' && *cp != '\t')
+               cp++;
+       if (*cp) *cp++ = '\0';
+       if (!*rv) rv = NULL;
+       *line = cp;
+       return rv;
+}
+
+char *impl_add(char *line)
+{
+       /* this appears to be an implication,
+        * attempt to extract relevant fields
+        * and add implication to implist
+        *
+        * fields are:
+        *   01jan[-31dec]  startday  endday
+        *   su[-sa]    startdow enddow
+        *   0000-2330  starttime endtime
+        *   xxxxx              included
+        *      !xxxxx          excluded
+        *   -> yyyyy   attribute
+        */
+       static char buf[200];
+       char *w;
+       implication imp, *ip;
+       imp.startday = 0;
+       imp.endday = 366;
+       imp.startdow = 0;
+       imp.enddow = 6;
+       imp.starttime = 0;
+       imp.endtime = 24*60;
+       imp.included = new_desc();
+       imp.excluded = new_desc();
+       imp.attribute = 0;
+
+       while ((w=get_word(&line)) && strcmp(w, "->")!= 0)
+       {
+               int at;
+
+               if ((at=attr_find(w)) >= 0)
+                       set_a_bit(&imp.included, at);
+               else if (*w == '!' && (at=attr_find(w+1))>=0)
+                       set_a_bit(&imp.excluded, at);
+               else
+               {
+                       char *type;
+                       char *w2;
+                       int v1, v2;
+                       w2 = strchr(w, '-');
+                       if (w2) *w2++ = '\0';
+                       else w2 = w;
+
+                       if ((v1=dayofweek(w))>=0)
+                       {
+                               type="Day of Week";
+                               if ((v2=dayofweek(w2))>=0)
+                                       imp.startdow = v1,
+                                               imp.enddow = v2;
+                       }
+                       else if ((v1=dayofyear(w))>=0)
+                       {
+                               type = "Day of Year";
+                               if ((v2=dayofyear(w2))>= 0)
+                                       imp.startday = v1,
+                                               imp.endday = v2;
+                       }
+                       else if ((v1=podofday(w))>= 0)
+                       {
+                               type = "Time of day";
+                               if (v1 >= 48)
+                                       return msg(buf, "Invalid time: %s", w, NULL);
+                               if ((v2 = podofday(w2))>= 0)
+                               {
+                                       if (v2 >= 49)
+                                               return msg(buf,"Invalid time: %s", w, NULL);
+                                       imp.starttime = v1;
+                                       if (w2==w) imp.endtime = v1+1;
+                                       else imp.endtime = v2-1;
+                               }
+                       }
+                       else
+                               return msg(buf, "invalid implicand: %s", w, NULL);
+                       if (v2 < 0)
+                               return msg(buf, "%s is not a valid %s", w2, type);
+                       if (v2 < v1)
+                               return msg(buf, "%s preceeds %s", w2, w);
+               }
+       }
+       if (w == NULL)
+               return "missing ->";
+       w = get_word(&line);
+       if (w == NULL)
+               return "missing implicate";
+       imp.attribute = attr_find(w);
+       if (imp.attribute < 0)
+               return msg(buf, "unknown attribute: %s", w, NULL);
+
+       ip = dl_new(implication);
+       *ip = imp;
+       if (impcnt == 0)
+               implist = dl_head();
+       dl_add(implist, ip);
+       impcnt++;
+       return NULL;
+}
+
+int impl_store()
+{
+       /* store implications to the database
+        * about 100 bytes each, so send 8 at a time
+        */
+
+       int i;
+       implication *next;
+
+       if (implist)
+       {
+               next = dl_next(implist);
+
+               for (i=0 ; i<impcnt ; i+= 8)
+               {
+                       int i2;
+                       impls *ip;
+                       implication ilist[8];
+                       book_change ch;
+                       ch.chng = IMPLICATIONS;
+                       ip = &ch.book_change_u.implch;
+                       ip->first = i;
+                       ip->total = impcnt;
+                       ip->exlist.exlist_val = ilist;
+                       for (i2=0 ; i2<8 && i+i2 < impcnt ; i2++)
+                       {
+                               ilist[i2] = *next;
+                               next = dl_next(next);
+                       }
+                       ip->exlist.exlist_len = i2;
+                       if (send_change(&ch) != 0)
+                               return -1;
+               }
+       }
+       return 0;
+}
+
+int impl_cmp()
+{
+       /* complare implications in implist with
+        * those in impl_list
+        * return 1 if different
+        */
+       int i;
+       implication *next = dl_next(implist);
+
+       get_impl_list();
+    
+       if (impcnt != impl_list.total)
+               return 1;
+
+       for (i=0 ; i<impcnt ; i++, next = dl_next(next))
+       {
+               implication *ip = &impl_list.exlist.exlist_val[i];
+
+               if (ip->startday != next->startday
+                   || ip->endday != next->endday
+                   || ip->starttime != next->starttime
+                   || ip->endtime != next->endtime
+                   || ip->startdow != next->startdow
+                   || ip->enddow != next->enddow
+                   || ip->attribute != next->attribute
+                   || !desc_eql(ip->included, next->included)
+                   || !desc_eql(ip->excluded, next->excluded)
+                       )
+                       return 1;
+       }
+       return 0;
+}
+
+void impl_update()
+{
+       if (implist && impl_cmp())
+               impl_store();
+}
+
+attr_type_desc new_attrs;
+
+int attr_type_update()
+{
+       get_attr_types();
+
+       if (get_attr_types() == 0
+           || !desc_eql(new_attrs.mandatory, attr_types.mandatory)
+           || !desc_eql(new_attrs.lab, attr_types.lab)
+           || !desc_eql(new_attrs.all, attr_types.all)
+           || new_attrs.open != attr_types.open
+           || new_attrs.closed != attr_types.closed
+           || new_attrs.available != attr_types.available
+           || new_attrs.bookable != attr_types.bookable
+           || new_attrs.reusable != attr_types.reusable
+           || new_attrs.firmalloc != attr_types.firmalloc
+               )
+       {
+               /* there is a difference, send the change */
+               book_change ch;
+               ch.chng = ATTR_TYPE;
+               ch.book_change_u.attrs = new_attrs;
+               if (send_change(&ch) != 0)
+                       return -1;
+       }
+       return 0;
+}
+
+
+void attr_print()
+{
+       /* list attributes definitions just as they would be read in */
+       int i;
+       if (get_attr_types()==0)
+               printf("Cannot get attribute type information\n");
+       else
+               for (i=0 ; i <BITINDMAX ; i++)
+                       if (query_bit(&attr_types.all, i))
+                       {
+                               char *name = get_mappingchar(i, M_ATTRIBUTE);
+                               if (name)
+                               {
+                                       printf("%d:%s", i, name);
+                                       free(name);
+                               }
+                               else
+                                       printf("%d:#%d", i, i);
+                               if (query_bit(&attr_types.mandatory, i)==0)
+                                       printf(" optional");
+                               if (query_bit(&attr_types.lab, i)==1)
+                                       printf(" lab");
+                               if (i == attr_types.open) printf(" open");
+                               if (i == attr_types.closed) printf(" closed");
+                               if (i == attr_types.available) printf(" available");
+                               if (i == attr_types.bookable) printf(" bookable");
+                               if (i == attr_types.reusable) printf(" reusable");
+                               if (i == attr_types.firmalloc) printf(" firmalloc");
+                               printf("\n");
+                       }
+}
+           
+
+void impl_print()
+{
+       /* list the implications in form suitable for input
+        */
+       int i;
+       if (get_impl_list() == 0)
+               printf("Cannot get implication list\n");
+       else
+               for (i=0 ; i<impl_list.total ; i++)
+               {
+                       implication *ip = &impl_list.exlist.exlist_val[i];
+                       char str[30];
+                       char *name;
+                       int j;
+
+                       if (ip->startday > 0 || ip->endday < 366)
+                       {
+                               printf("%s", doy2str(str, ip->startday));
+                               if (ip->startday != ip->endday)
+                                       printf("-%s", doy2str(str, ip->endday));
+                               printf(" ");
+                       }
+                       if (ip->startdow > 0 || ip->enddow < 6)
+                       {
+                               extern char *days[];
+                               printf("%s", days[ip->startdow]);
+                               if (ip->startdow != ip->enddow)
+                                       printf("-%s", days[ip->enddow]);
+                               printf(" ");
+                       }
+                       if (ip->starttime >0 || ip->endtime < 24*60)
+                       {
+                               printf("%s", pod2str(str, ip->starttime));
+                               if (ip->starttime != ip->endtime)
+                                       printf("-%s", pod2str(str, ip->endtime+1));
+                               printf(" ");
+                       }
+                       for (j=0 ; j < BITINDMAX ; j++)
+                       {
+                               if (query_bit(&ip->included, j))
+                               {
+                                       char *name = get_mappingchar(j, M_ATTRIBUTE);
+                                       if (name)
+                                               printf("%s ", name);
+                                       else
+                                               printf("#%d ", j);
+                                       if (name)
+                                               free(name);
+                               }
+                               if (query_bit(&ip->excluded, j))
+                               {
+                                       char *name = get_mappingchar(j, M_ATTRIBUTE);
+                                       if (name)
+                                               printf("!%s ", name);
+                                       else
+                                               printf("!#%d ", j);
+                                       if (name)
+                                               free(name);
+                               }
+                       }
+                       name = get_mappingchar(ip->attribute, M_ATTRIBUTE);
+                       if (name)
+                               printf("-> %s\n", name);
+                       else
+                               printf("-> #%d\n", ip->attribute);
+                       if (name) free(name);
+               }
+}
+
+static void list_desc(char *prefix, description d)
+{
+       int first = 1;
+       int i;
+       for (i=0 ; i<d.item_len*8 ; i++)
+       {
+               if ((i%32)==0 && !first)
+               {
+                       printf("\n");
+                       first = 1;
+               }
+               if (query_bit(&d, i))
+               {
+                       char *n;
+                       if (first)
+                               printf("%s", prefix);
+                       first = 0;
+                       n = get_mappingchar(i, M_ATTRIBUTE);
+                       if (n== NULL) n = strdup("*unknown*attr*");
+                       printf("%s ", n);
+                       free(n);
+               }
+       }
+       if (!first) printf("\n");
+}
+
+int main(int argc, char *argv[])
+{
+       /* If no args, display attr file
+        * if -t then rest of args are time/date/attr - interpret
+        * else one arg- file to read and set
+        */
+
+       if (argc == 1) {
+               attr_print();
+               impl_print();
+       } else if (argc >= 2 && strcmp(argv[1], "-t")==0 ) {
+               int a;
+               int doy=-1, pod=-1;
+               description bits = new_desc();
+               for (a=2; a < argc ; a++) {
+                       int v;
+                       if ((v=get_mappingint(argv[a], M_ATTRIBUTE)) >= 0)
+                               set_a_bit(&bits, v);
+                       else if ((v=dayofyear(argv[a]))>=0)
+                               doy = v;
+                       else if ((v=podofday(argv[a]))>=0)
+                               pod = v;
+                       else if (strcmp(argv[a], "--")==0)
+                               break;
+                       else
+                               fprintf(stderr, "attr: cannot understand '%s' - ignoring\n", argv[a]);
+               }
+               if (doy == -1)
+                       fprintf(stderr, "attr: no day of year given\n");
+               else if (pod == -1 && a == argc)
+                       fprintf(stderr, "attr: no period of day given\n");
+               else {
+                       description *result;
+                       if (a == argc)
+                       {
+                               result = process_exclusions(pod, doy, &bits);
+                               list_desc("  ", *result);
+                       }
+                       else {
+                               int a1;
+                               int lo, hi;
+                               a++;
+
+                               if (pod==-1) lo=0, hi=47;
+                               else lo=hi=pod;
+                               for (; lo <= hi ; lo++) {
+                                       char *prefix="";
+                                       description b2;
+                                       desc_cpy(&b2, &bits);
+                                       result = process_exclusions(lo, doy, &b2);
+                                       if (pod == -1) printf("%02d:%02d: ", lo/2, (lo&1)*30);
+                                       for (a1=a ; a1 < argc ; a1++) {
+                                               int v;
+                                               v = get_mappingint(argv[a1], M_ATTRIBUTE);
+                                               if (v <0 )
+                                                       fprintf(stderr, "attr: unknown attribute: '%s'\n", argv[a1]);
+                                               else {
+                                                       if (query_bit(result, v)) {
+                                                               printf("%s%s", prefix, argv[a1]);
+                                                               prefix=", ";
+                                                       }
+                                               }
+                                       }
+                                       if (*prefix || pod==-1) printf("\n");
+                               }
+                       }
+               }
+       } else if (argc == 2) {
+               FILE *f;
+               int ln = 0;
+               int errs = 0;
+               char *err;
+               char line[1024];
+               if (strcmp(argv[1], "-")==0)
+                       f = stdin;
+               else
+                       f = fopen(argv[1], "r");
+               if (f == NULL)
+               {
+                       fprintf(stderr, "attr: cannot open ");
+                       perror(argv[1]);
+                       fprintf(stderr, "Usage: attr [file]\n");
+                       exit(1);
+               }
+               attr_init();
+               new_attrs.mandatory = new_desc();
+               new_attrs.lab = new_desc();
+               new_attrs.all = new_desc();
+               new_attrs.open = new_attrs.closed = new_attrs.available
+                       = new_attrs.bookable = new_attrs.reusable = new_attrs.firmalloc = -1;
+               while (fgets(line, sizeof(line), f))
+               {
+                       char *cp;
+                       ln++;
+                       cp = strchr(line, '\n');
+                       if (cp) *cp = '\0';
+                       cp = line+strlen(line);
+                       while (cp > line && (cp[-1]== ' ' || cp[-1] == '\t'))
+                               *--cp = '\0';
+                       cp = line;
+                       while (*cp==' ' || *cp == '\t')
+                               cp++;
+                       if (*cp == '#' || *cp == '\0')
+                               continue;
+                       if (strchr(cp, '>')==NULL)
+                               err = attr_add(cp, &new_attrs);
+                       else
+                               err = impl_add(cp);
+
+                       if (err)
+                       {
+                               fprintf(stderr,"attr: %s at line %d\n", err, ln);
+                               errs++;
+                       }
+               }
+               if (errs == 0)
+               {
+                       attr_update(1);
+                       attr_type_update();
+                       impl_update();
+               }
+       } else if (argc != 2) {
+               fprintf(stderr, "Usage: attr [file]\n");
+               fprintf(stderr, "       attr -t time date lab ( -- attributes to check)\n");
+               exit(2);
+       }
+       exit(0);
+}
diff --git a/tools/booking.c b/tools/booking.c
new file mode 100644 (file)
index 0000000..d134020
--- /dev/null
@@ -0,0 +1,302 @@
+
+/*
+ * extract and print booking information from the database
+ * also allow state changes of a list of bookings
+ * optionally remove the booking from the database
+ *
+ * given a set of days, periods, and lab, print out the
+ *  bookings showing
+ *    doy pod lab user count token time-made status (who_by)
+ *
+ * or... read in a list of bookings as
+ *   doy pod lab user time
+ *    and change the status of them all to something.
+ *
+ */
+
+#include       "../lablist/lablist.h"
+
+int podofday(char*);
+int dayofyear(char*);
+
+char *Usage[] = {
+    "Usage: booking [-R] dates periods labs",
+    "          dates == ddmmm[-ddmmm]  e.g. 29feb-05mar",
+    "          periods == hh:mm[-hh:mm] e.g. 9:00-12:30",
+    "          labs == labname or labgroup e.g. undercroft, ALL",
+    "        -R removes bookings (different from unbooking)",
+    "       booking -s new-status < booking-list",
+    "          booking-list == {day period lab user time}",
+    "       booking -t logintime < booking-list",
+    NULL };
+
+
+void show_bookings(int doy, int pod, int lab, int remove_bookings)
+{
+       book_key key = make_bookkey(doy, pod, lab);
+       char k[20];
+       bookhead bh;
+       booklist bl;
+       int pending = 0;
+
+       bh.data= NULL;
+       db_read(bookkey2key(k, key), &bh, (xdrproc_t)xdr_bookhead, BOOKINGS);
+
+       for (bl = bh.data ; bl ; bl=bl->next)
+       {
+               char *un = find_username(bl->who_for);
+               if (un)
+               {
+                       printf("%-12s", un);
+                       free(un);
+               }
+               else
+                       printf("uid=%-8d", (int)bl->who_for);
+               if (bl->status == B_PENDING)
+                       pending++;
+               show_booking(bl, key);
+       }
+       if (bh.data != NULL && remove_bookings && pending == 0)
+       {
+               if (do_removal(key)!= 0)
+               {
+                       fprintf(stderr, "failed to remove booking %d %d %d\n",
+                               doy, pod, lab);
+               }
+       }
+       xdr_free((xdrproc_t)xdr_bookhead, (char*) &bh);
+}
+
+int get_range(char *str, int *start, int *finish, int (*fn)(char*))
+{
+       /* if str is either acceptable by fn, or two substrings separated by '-'
+        * which are both acceptable by fn and return numbers in order,
+        * then succeed and set start and finish to the value(s) returned by fn
+        * else fail
+        */
+
+       int s,f;
+       char b[200];
+       char *dash;
+       if ((s=fn(str)) >= 0)
+       {
+               *start = *finish = s;
+               return 1;
+       }
+       strcpy(b, str);
+       dash = strchr(b, '-');
+       if (dash == NULL)
+               return 0;
+       *dash = '\0';
+       s = fn(b);
+       f = fn(dash+1);
+       if (s>=0 && f>=0 && s<=f)
+       {
+               *start = s;
+               *finish = f;
+               return 1;
+       }
+       return 0;
+}
+
+void do_changes(int is_status, char *newstatus)
+{
+       /* read changes from stdin and change their status to newstatus */
+       int status;
+       int ontime;
+       char buf[1024];
+
+       if (is_status)
+       {
+               status = char2bstatus(newstatus);
+               if (status<=0)
+               {
+                       fprintf(stderr,"booking: %s is not a valid status\n", newstatus);
+                       exit(2);
+               }
+       }
+       else
+       {
+               if (sscanf(newstatus, "%d", &ontime)!= 1)
+               {
+                       fprintf(stderr,"booking: %s in not a value number\n", newstatus);
+                       exit (2);
+               }
+               if (ontime < -10*60 || ontime > 20*60)
+               {
+                       fprintf(stderr, "booking: time %d out side range of -600 to 1200\n", ontime);
+                       exit(2);
+               }
+       }
+       while (fgets(buf, sizeof(buf), stdin)!= NULL)
+       {
+               /* want doy pod lab user myctime */
+               char doys[10], pods[10], labs[20], user[20], myct[30];
+               if (sscanf(buf, "%s %s %s %s %s", doys, pods, labs, user, myct)!= 5)
+               {
+                       fprintf(stderr, "booking: bad line %s", buf);
+               }
+               else
+               {
+                       int doy, pod, lab, uid;
+                       time_t when;
+                       if ((doy=dayofyear(doys))<0)
+                               fprintf(stderr, "booking: bad day of year: %s\n", doys);
+                       else if ((pod=podofday(pods))<0)
+                               fprintf(stderr, "booking: bad period of day: %s\n", pods);
+                       else if ((lab = get_mappingint(labs, M_ATTRIBUTE))<0)
+                               fprintf(stderr, "booking: bad lab name: %s\n", labs);
+                       else if ((uid= find_userid(user))<0)
+                               fprintf(stderr,"booking: bad user name: %s\n", user);
+                       else if ((when = unmyctime(myct)) <= 0)
+                               fprintf(stderr,"booking: bad time: %s\n", myct);
+                       else
+                       {
+                               if (is_status)
+                               {
+                                       book_change arg;
+                                       book_ch_ent buents[2];
+
+                                       arg.chng = CHANGE_BOOKING;
+                                       arg.book_change_u.changes.slot = make_bookkey(doy, pod, lab);
+                                       arg.book_change_u.changes.chlist.book_ch_list_len = 1;
+                                       arg.book_change_u.changes.chlist.book_ch_list_val = buents;
+                                       buents[0].user = uid;
+                                       buents[0].when = when;
+                                       buents[0].status = status;
+                                       buents[0].priv_refund = 0;
+                                       buents[0].tentative_alloc = 0;
+                                       buents[0].allocations.entitylist_len = 0;
+
+                                       if (send_change(&arg)==0)
+                                               /*return TRUE */ ; 
+                                       else
+                                       {
+                                               /* return FALSE;*/
+                                               fprintf(stderr,"booking: change failed\n");
+                                       }
+
+                               }
+                               else
+                               {
+                                       book_change arg;
+                                       book_set_claim *cp;
+                                       arg.chng = CLAIM_BOOKING;
+                                       cp = &arg.book_change_u.aclaim;
+                                       cp->slot = make_bookkey(doy, pod, lab);
+                                       cp->user = uid;
+                                       cp->when = when;
+                                       cp->where = -1;
+                                       cp->claimtime = DID_LOGIN | ((ontime+30*60)<<4);
+                                       if (send_change(&arg)==0)
+                                               /*return TRUE */ ; 
+                                       else
+                                       {
+                                               /* return FALSE;*/
+                                               fprintf(stderr,"booking: change failed\n");
+                                       }
+
+                               }
+                       }
+               }
+       }
+}
+
+int main(int argc, char *argv[])
+{
+       int podset=0, doyset=0;
+       int pods, pode, doys, doye;
+       int pod, doy;
+       void *lablist = lablist_make();
+       labinfo li;
+       int arg=1;
+       int doremove = 0;
+
+       if (argc >= 2 && strcmp(argv[arg], "-r")==0)
+       {
+               open_db_client(argv[arg+1], "tcp");
+               arg+= 2;
+       }
+       if (argc >= 2 && strcmp(argv[arg], "-R")==0)
+       {
+               doremove = 1;
+               arg++;
+       }
+       if (argc >= 2 && strcmp(argv[arg], "-s")==0)
+       {
+               if (argc != 3)
+               {
+                       fprintf(stderr,"booking: must give exactly one status with -s\n");
+                       exit(2);
+               }
+               do_changes(1, argv[2]);
+               exit(0);
+       }
+       if (argc >= 2 && strcmp(argv[1], "-t")==0)
+       {
+               if (argc != 3)
+               {
+                       fprintf(stderr,"booking: must give exactly one time with -t\n");
+                       exit(2);
+               }
+               do_changes(0, argv[2]);
+               exit(0);
+       }
+       for (; arg < argc ; arg++)
+       {
+               char *msg;
+               if (get_range(argv[arg], &pods, &pode, podofday))
+               {
+                       if (podset)
+                       {
+                               fprintf(stderr,"booking: only give one period-of-day string\n");
+                               usage();
+                               exit(2);
+                       }
+                       podset = 1;
+                       /* make 06:00-07:00 be just two periods, not three */
+                       if (pode > pods) pode--;
+
+               }
+               else if (get_range(argv[arg], &doys, &doye, dayofyear))
+               {
+                       if (doyset)
+                       {
+                               fprintf(stderr,"booking: only give one day-of-year string\n");
+                               usage();
+                               exit(2);
+                       }
+                       doyset = 1;
+               }
+               else if ((msg = add_lab(lablist, (strcmp(argv[arg],"ALL")==0)?NULL:argv[arg]))!=NULL)
+               {
+                       if (strncmp(msg, "unknown", 7) == 0)
+                               fprintf(stderr, "booking: cannot understand string: %s\n", argv[arg]);
+                       else
+                               fprintf(stderr, "booking: %s: %s\n", argv[arg], msg);
+                       exit(2);
+               }
+       }
+       if (lablist_next(lablist) == lablist)
+       {
+               fprintf(stderr, "booking: no labs specified\n");
+               usage();
+               exit(2);
+       }
+       if (doyset == 0)
+       {
+               fprintf(stderr, "booking: no day range specified\n");
+               usage();
+               exit(2);
+       }
+       if (podset == 0)
+       {
+               pods = 0;
+               pode = 47;
+       }
+       for (doy = doys ; doy <= doye ; doy++)
+               for (pod = pods ; pod <= pode ; pod++)
+                       for (li = lablist_next(lablist) ; li != lablist ; li = lablist_next(li))
+                               show_bookings(doy, pod, li->labid, doremove);
+       exit(0);
+}
diff --git a/tools/conf.c b/tools/conf.c
new file mode 100644 (file)
index 0000000..9394875
--- /dev/null
@@ -0,0 +1,76 @@
+
+/*
+ * quick and dirty config seter
+ * conf name    prints value
+ * conf name value sets value
+ *
+ */
+
+#include       "../db_client/db_client.h"
+
+int main(int argc, char *argv[])
+{
+
+       char *val;
+       switch(argc)
+       {
+       default:
+               fprintf(stderr, "Usage: conf name [value]\n");
+               exit(2);
+
+       case 1:
+               /* print all config entries */
+       {
+               query_req request;
+               char prefix[10];
+
+               sprintf(prefix, "%d_", C_GENERAL);
+               request.key.item_val=memdup(prefix,strlen(prefix));
+               request.key.item_len=strlen(prefix);
+               request.type=M_NEXT_PREFIX;
+               request.database = CONFIG;
+               refresh_db_client();
+               while(1)
+               {
+                       query_reply *result;
+                       result = query_db_2(&request, db_client);
+                       if (result == NULL)
+                               exit(1);
+
+                       free(request.key.item_val);
+                       if (result->error != R_RESOK ||
+                           result-> query_reply_u.data.key.item_len == 0 ||
+                           result->query_reply_u.data.key.item_val== NULL)
+                               exit (1);
+       
+                       request.key.item_len = result-> query_reply_u.data.key.item_len;
+                       request.key.item_val =
+                               memdup(result->query_reply_u.data.key.item_val,
+                                      request.key.item_len+1);
+                       request.key.item_val[request.key.item_len] = 0;
+                       xdr_free((xdrproc_t)xdr_query_reply, (char*) result);
+       
+                       if (strncmp(request.key.item_val, prefix, strlen(prefix))==0)
+                       {
+                               val = get_configchar(request.key.item_val+strlen(prefix));
+                               if (val)
+                               {
+                                       printf ("%s = %s\n", request.key.item_val+strlen(prefix), val);
+                                       free(val);
+                               }
+                       }
+               }
+       }
+       exit(0);
+       case 2: /* print value */
+               val = get_configchar(argv[1]);
+               if (val)
+                       printf("%s\n", val);
+               else
+                       printf("--Not-Defined--\n");
+               exit(0);
+       case 3: /* set value */
+               make_config(argv[1], argv[2]);
+               exit(0);
+       }
+}
diff --git a/tools/dbadmin.c b/tools/dbadmin.c
new file mode 100644 (file)
index 0000000..0c8ed84
--- /dev/null
@@ -0,0 +1,355 @@
+
+#include       "../db_client/db_client.h"
+#include       "../lib/dlink.h"
+#include       <time.h>
+#include       <getopt.h>
+/*
+ * db_admin : administer the database servers.
+ *
+ * db_admin -s -A | servers -- list replicas know by one or all servers
+ * db_admin -x servers      -- tell replica to exit
+ * db_admin -d replica     -- delete replica from list
+ * db_admin -a replica      -- add an active replica to replicas list
+ * db_admin -R replica             -- tell replica to reorganise itself
+ *
+ *  can take a -r replica argument to talk only to the named replica
+ *
+ */
+
+char *Usage[] = {
+    "Usage: db_admin [-r replica] -s [serverlist]      -- show replica status",
+    "       db_admin -x replica                        -- tell replica to exit",
+    "       db_admin [-r replica] -d replica           -- remove replica from list",
+    "       db_admin [-r replica] -a replica           -- add a replica to list",
+    "       db_admin -R replica                        -- tell replica to re-organise data files",
+    NULL };
+
+static void rep_status(char *replica);
+static char *get_replica_list();
+
+int main(int argc, char *argv[])
+{
+
+       book_change ch;
+       int num;
+       update_status *st;
+       int sts;
+    
+       char *firstchoice = NULL;
+       enum {Mstatus, Mexit, Mdelete, Madd, Mreorg, Mnone}  mode = Mnone;
+       int All = 0;
+
+       extern int optind;
+       extern char *optarg;
+       int opt;
+    
+       while ((opt = getopt(argc, argv, "r:sAxdaR"))!= EOF)
+               switch(opt)
+               {
+               case 'r':
+                       if (firstchoice)
+                       {
+                               fprintf(stderr,"dbadmin: give only one first choice replica (-r)\n");
+                               usage();
+                               exit(2);
+                       }
+                       firstchoice = optarg;
+                       break;
+               case 'R':
+               case 's':
+               case 'x':
+               case 'd':
+               case 'a':
+                       if (mode !=Mnone)
+                       {
+                               fprintf(stderr,"dbadmin: give only one mode (s,x,d,a)\n");
+                               usage();
+                               exit(2);
+                       }
+                       switch(opt) {
+                       case 'R': mode = Mreorg ; break;
+                       case 's': mode = Mstatus; break;
+                       case 'x': mode = Mexit; break;
+                       case 'd': mode = Mdelete; break;
+                       case 'a': mode = Madd; break;
+                       }
+                       break;
+               case 'A':
+                       All = 1;
+                       break;
+               default:
+                       usage();
+                       exit(2);
+               }
+       if (mode == Mnone)
+       {
+               fprintf(stderr, "dbadmin: must give one of s,x,d,a\n");
+               usage();
+               exit(2);
+       }
+       if (All && mode != Mstatus)
+       {
+               fprintf(stderr, "dbadmin: -A only allowed with -s\n");
+               usage();
+               exit(2);
+       }
+       if (firstchoice && mode == Mexit)
+       {
+               fprintf(stderr,"dbadmin: -r not meaningful with -x\n");
+               usage();
+               exit(2);
+       }
+
+       if (mode == Mstatus)
+       {
+               if (All && optind < argc)
+               {
+                       fprintf(stderr,"dbadmin: no serverlist can be given with -A\n");
+                       usage();
+                       exit(2);
+               }
+               if (!All && optind == argc)
+               {
+/*
+  fprintf(stderr,"dbadmin: -A or serverlist must be given with -s\n");
+  usage();
+  exit(2);
+*/
+                       All = 1;
+               }
+       }
+       else
+       {
+               if (optind == argc)
+               {
+                       fprintf(stderr, "dbadmin: must specfy a replica name for action\n");
+                       usage();
+                       exit(2);
+               }
+               if (optind+1 < argc)
+               {
+                       fprintf(stderr, "dbadmin: specify only one replica name for action\n");
+                       usage();
+                       exit(2);
+               }
+       }
+       switch(mode)
+       {
+       case Mnone: /* keep -Wall quite */
+               break;
+       case Mstatus:
+               if (All)
+               {
+                       char *replist, *rep;
+                       if (firstchoice)
+                               open_db_client(firstchoice, "tcp");
+                       replist = get_replica_list();
+                       if (replist)
+                               for(rep = dl_next(replist) ; rep != replist ; rep=dl_next(rep))
+                                       rep_status(rep);
+                       else
+                       {
+                               fprintf(stderr,"dbadmin: cannot get list of all replicas\n");
+                               exit(3);
+                       }
+               }
+               else
+                       for (  ; optind < argc ; optind++)
+                               rep_status(argv[optind]);
+       
+               break;
+       case Mexit:
+               open_db_client(argv[optind], "tcp");
+               if (db_client== NULL)
+               {
+                       fprintf(stderr,"dbadmin: cannot contact replica on %s\n", argv[optind]);
+                       exit(1);
+               }
+               else
+               {
+                       int d = 0;
+                       if (terminate_2(&d, db_client)== NULL)
+                       {
+                               fprintf(stderr,"dbadmin: problem sending exit request to replica %s\n", argv[optind]);
+                               exit(1);
+                       }
+               }
+               break;
+       case Mreorg:
+               open_db_client(argv[optind], "tcp");
+               if (db_client == NULL)
+               {
+                       fprintf(stderr,"dbadmin: cannot contact replica on %s\n", argv[optind]);
+                       exit(1);
+               }
+               else
+               {
+                       int d = 0;
+                       if (db_reorganize_2(&d, db_client)== NULL)
+                       {
+                               fprintf(stderr,"dbadmin: problem sending reoganise request to replica %s\n", argv[optind]);
+                               exit(1);
+                       }
+               }
+               break;
+       case Mdelete:
+               /* delete command must (appear to) come from the replica being
+                * deleted. If it is not contactable and firstchoice was given,
+                * then fake a change from the deleted replica
+                */
+               open_db_client(argv[optind], "tcp");
+               if (db_client == NULL
+                   || (st = get_updatest_2(NULL, db_client)) == NULL
+                       )
+               {
+                       /* have to fake it */
+                       book_changeent ce;
+                       book_changeres *result;
+                       if (firstchoice == NULL)
+                       {
+                               fprintf(stderr,"dbadmin: cannot contact %s, consider using -r\n", argv[optind]);
+                               exit(1);
+                       }
+                       open_db_client(firstchoice, "tcp");
+                       if (db_read(str_key(argv[optind]), &num, (xdrproc_t)xdr_replica_no, REPLICAS)
+                           != R_RESOK)
+                       {
+                               fprintf(stderr,"dbadmin: %s doesn't seem to know %s\n",
+                                       firstchoice, argv[optind]);
+                               exit(1);
+                       }
+                       ce.time = time(0);
+                       ce.machine = argv[optind];
+                       ce.changenum = num+1;
+                       ce.thechange.chng = DEL_SERVER;
+                       ce.thechange.book_change_u.delserver = argv[optind];
+                       result = change_db_2(&ce, db_client);
+                       if (result == NULL || *result != C_OK)
+                       {
+                               fprintf(stderr,"dbadmin: could not send delete request\n");
+                               exit(3);
+                       }
+               }
+               else
+               {
+                       ch.chng = DEL_SERVER;
+                       ch.book_change_u.delserver = argv[optind];
+                       if (send_change(&ch)!= 0)
+                       {
+                               fprintf(stderr,"dbadmin: delete request failed\n");
+                               exit(3);
+                       }
+               }
+               break;
+       case Madd:
+               /* server must be running */
+               open_db_client(argv[optind], "tcp");
+               if (db_client == NULL
+                   || (st = get_updatest_2(NULL, db_client)) == NULL
+                   || (*st) == NONE)
+               {
+                       fprintf(stderr,"dbadmin: replica at %s does not appear to be functional\n", argv[optind]);
+                       exit(1);
+               }
+           
+               open_db_client(firstchoice, NULL); /* null is ok here */
+               if (db_read(str_key(argv[optind]), &num, (xdrproc_t)xdr_replica_no, REPLICAS)
+                   != R_NOMATCH)
+               {
+                       fprintf(stderr, "dbadmin: replica %s already registered?\n", argv[optind]);
+                       exit(1);
+               }
+               ch.chng = ADD_SERVER;
+               ch.book_change_u.server = argv[optind];
+               if ((sts=send_change(&ch))!= 0)
+               {
+                       fprintf(stderr,"dbadmin: add server operation failed (%d)\n", sts);
+                       exit(3);
+               }
+               break;
+       }
+       exit(0);
+}
+
+static char *get_replica_list()
+{
+       /* get a list of replicas and return as a dlink list of char*s */
+       query_req arg;
+       query_reply *result;
+       char *rv;
+
+       arg.key.item_val = "dummy";
+       arg.key.item_len = 5;
+       arg.type = M_FIRST;
+       arg.database = REPLICAS;
+
+       if (db_client == NULL)
+               refresh_db_client();
+       if (db_client == NULL)
+               return NULL;
+    
+       result = query_db_2(&arg, db_client);
+       if (result== NULL)
+               return NULL;
+    
+       rv = dl_head();
+       while (result && result->error == R_RESOK)
+       {
+               char *rn;
+               rn = dl_strndup(result->query_reply_u.data.key.item_val,
+                               result->query_reply_u.data.key.item_len);
+               dl_add(rv,rn);
+               arg.key.item_val = rn;
+               arg.key.item_len = strlen(rn);
+               arg.type = M_NEXT;
+               xdr_free((xdrproc_t)xdr_query_reply, (char*) result);
+               result = query_db_2(&arg, db_client);
+       }
+       return rv;
+}
+
+
+static char* statuses[] = { NULL, "NO", "CP", "UP"};
+static void rep_status(char *replica)
+{
+       /* display status of replica */
+       query_req arg;
+       query_reply *result;
+       update_status *stat;
+       open_db_client(replica, "tcp");
+       if (db_client == NULL)
+       {
+               printf("%s: NO-COMMUNICATION\n", replica);
+               return;
+       }
+       stat = get_updatest_2(NULL, db_client);
+       if (stat == NULL)
+       {
+               printf("%s: NO-SERVER\n", replica);
+               return;
+       }
+       printf("%-10s %s: ",replica,statuses[*stat]);
+
+       arg.key.item_val = "dummy";
+       arg.key.item_len = 5;
+       arg.type = M_FIRST;
+       arg.database = REPLICAS;
+
+       result = query_db_2(&arg, db_client);
+       while (result && result->error == R_RESOK)
+       {
+               char *rn;
+               int chn;
+               rn = strndup(result->query_reply_u.data.key.item_val,
+                            result->query_reply_u.data.key.item_len);
+               chn = ntohl(*(int*)result->query_reply_u.data.value.item_val);
+               printf("%s %d ", rn, chn);
+       
+               arg.key.item_val = rn;
+               arg.key.item_len = strlen(rn);
+               arg.type = M_NEXT;
+               xdr_free((xdrproc_t)xdr_query_reply, (char*) result);
+               result = query_db_2(&arg, db_client);
+       }
+       printf("\n");
+}
diff --git a/tools/host.c b/tools/host.c
new file mode 100644 (file)
index 0000000..b4e810a
--- /dev/null
@@ -0,0 +1,812 @@
+
+#include       "../db_client/db_client.h"
+#include       <time.h>
+#include       <getopt.h>
+/*
+ * host/hg/lab/ws - manage the booking system hardware
+ *
+ * argv0 determines what sort of object is being managed
+ *
+ * -R old new          rename
+ * -D old              delete
+ * [-c] -g hostgroup -l lab -d [+-]desc -r reason  obj ...
+ *
+ */
+
+char *command;
+extern char *optarg;
+extern int optind;
+int    nargs=0, create=0, all=0, force=0;
+entityid objid;
+char *nm = NULL;
+
+void check_lab(item desc)
+{
+       /* check that no lab bits are set in desc */
+       if (get_attr_types()!= 1)
+       {
+               fprintf(stderr,"Cannot get attribute descriptor!!\n");
+               exit(1);
+       }
+       desc_sub(desc, attr_types.lab);
+}
+
+enum progtype { Phost, Phg, Plab, Pws, Pbad } ;
+
+enum progtype whichprog(char * arg0)
+{
+       int len;
+       len = strlen(arg0);
+       if (strcmp(arg0+len-4, "host")==0)
+               return Phost;
+       else if (strcmp(arg0+len-2, "hg")==0)
+               return Phg;
+       else if (strcmp(arg0+len-3, "lab")==0 || strcmp(arg0+len-2, "lb")==0)
+               return Plab;
+       else if (strcmp(arg0+len-2, "ws")==0)
+               return Pws;
+       else return Pbad;
+}
+
+char *Usage[] = {
+    "Usage:",
+    "  prog -R old new",
+    "  prog -D [-f] old",
+    "  prog [-acn] [-g hostgroup] [-l lab] [-h host]",
+    "       [-d [+-]desc] [-r reason] obj...",
+    "Function:",
+    "  Print, change or delete booking object(s).",
+    "  The type of object to be manipulated is determined by the program name:",
+    "  Name            Object Manipulated",
+    "  bkhg            hg (hostgroup)",
+    "  bklb            lb (lab)",
+    "  bkhost  host",
+    "  bkws            ws (workstation)",
+    "Option    Description                     Valid Object",
+    "  -n      print object's numerical ids    hg, lb, host, ws",
+    "  -c      create the object if necessary  hg, lb, host, ws",
+    "  -a      iterate over all objects        hg, lb, host, ws",
+    "  -R      rename object                   hg, lb, host, ws",
+    "  -D      delete object                   hg, lb, host, ws",
+    "  -f      force - delete subordinate      hg, lb, host, ws",
+    "                  objects if necessary",
+    "  -g      move object into hostgroup      lb, host",
+    "  -l      move ws into lab                ws",
+    "  -h      assign ws to host               ws",
+    "    (host may be \"none\")",
+    "  -d      assign, add or delete a         ws",
+    "    description to a ws",
+    "  -r      put ws in/out of service        ws",
+    "    empty reason: into service",
+    "    non-empty reason: out of service",
+    "",
+    "obj...    name(s) of the object(s) to print, change or delete.",
+    NULL };
+
+void die1(int ret, char *emsg1)
+{
+       fprintf(stderr, "%s: %s\n", command, emsg1);
+       usage();
+       exit(ret);
+}
+
+void die2(int ret, char *emsg1, char *emsg2)
+{
+       fprintf(stderr, "%s: %s: %s\n", command, emsg1, emsg2);
+       exit(ret);
+}
+
+void print_wsdata(workstation_state *ws, char *name)
+{
+       int first, i;
+       if (ws->host < 0)
+               printf("Workstation: %s\n", name);
+       else
+       {
+               char *hn = get_mappingchar(ws->host, M_HOST);
+               if (hn)
+                       printf("Workstation: %-15s On Host: %s\n", name, hn);
+               else
+                       printf("Workstation: %-15s On Host: #%d\n", name, ws->host);
+               if (hn) free(hn);
+       }
+       if (ws->why && ws->why[0]) {
+               time_t tim = ws->time;
+               printf("   %s since %s", ws->why, ctime(&tim));
+       }
+       first = 1;
+       printf("   ");
+       for (i=0; i<8*ws->state.item_len ; i++)
+               if (query_bit(&ws->state, i))
+               {
+                       char *nm = get_mappingchar(i, M_ATTRIBUTE);
+                       if (!first)
+                               printf(".");
+                       if (nm)
+                       {
+                               printf("%s", nm);
+                               free(nm);
+                       }
+                       else
+                               printf("#%d", i);
+                       first = 0;
+               }
+       printf("\n");
+}
+
+/* for use when stepping through the databases in getnextobj() */
+query_req request;
+char prefix[10];
+
+int getnextobj(nmapping_type map, char *argv[])
+{
+       /*
+        * get the next object of type map.
+        * either:
+        * get all such objects in the database, or
+        * get only those passed by argument to this program.
+        */
+       if (all)
+       {
+               query_reply *result;
+       
+               /* step through the database and return the next object
+                * of type map
+                * much of this code was adapted from conf.c
+                */
+               objid = -1;
+               if (all++ == 1)
+               {
+                       /* set up first request */
+                       request.key.item_val = memdup("dummy", 4);
+                       request.key.item_len = 4;
+                       request.type = M_FIRST;
+                       request.database = CONFIG;
+                       sprintf(prefix, "%d_", C_NUMBERS+map);
+               }
+               /* get the next record matching the type of object we want */
+               if (db_client == NULL && refresh_db_client() == 0)
+                       fputs("Cannot connect to db client\n", stderr);
+               else
+                       do
+                       {
+                               do
+                               {
+                                       result = query_db_2(&request, db_client);
+                                       if (result == NULL)
+                                               return 0;
+                                       request.type = M_NEXT;
+                                       free(request.key.item_val);
+                                       if (result->error != R_RESOK ||
+                                           result->query_reply_u.data.key.item_len == 0 ||
+                                           result->query_reply_u.data.key.item_val == NULL)
+                                               return 0;
+
+                                       /* copy the results of the reply into the (next) request */
+                                       request.key.item_len = result->query_reply_u.data.key.item_len;
+                                       request.key.item_val =
+                                               memdup(result->query_reply_u.data.key.item_val,
+                                                      request.key.item_len+1);
+                                       request.key.item_val[request.key.item_len] = 0;
+
+                                       /* free up the current reply */
+                                       xdr_free((xdrproc_t)xdr_query_reply, (char*) result);
+                               } while (strncmp(request.key.item_val, prefix, strlen(prefix)));
+
+                               objid = get_mappingint(request.key.item_val+strlen(prefix), map);
+                               /* lab objects are stored/indexed as general attributes.
+                                * don't return general attributes if we actually want labs
+                                */
+                       } while (map == M_ATTRIBUTE && ! query_bit(&attr_types.lab, objid));
+               if (objid >= 0)
+               {
+                       nm = get_mappingchar(objid, map);
+                       if (nm == NULL)
+                       {
+                               nm = strdup(request.key.item_val+strlen(prefix));
+                               fprintf(stderr, "%s: DB error - missing map from %d => %s\n", command, objid, nm);
+                       }
+               }
+       }
+       else
+       {
+               /* step through the command line arguments, and return the
+                * next object of type map corresponding to the arg.
+                * Create the object if required and allowed.
+                */
+               for (objid = -1; (objid < 0 && optind < nargs); optind++)
+               {
+                       objid = get_mappingint(argv[optind], map);
+                       if (objid < 0)
+                       {
+                               if (objid == -1 && create)
+                               {
+                                       /* create the object */
+                                       objid = choose_id(1, map);
+                                       if (objid < 0 || make_mapping(objid, argv[optind], map) != 0)
+                                       {
+                                               fprintf(stderr, "%s: DB error - cannot create mapping (%s, %d)\n", command, argv[optind], objid);
+                                               objid = -1;
+                                       }
+                               }
+                               else fprintf(stderr, "%s: '%s' does not exist\n", command, argv[optind]);
+                       }
+                       if (objid >= 0)
+                       {
+                               nm = get_mappingchar(objid, map);
+                               if (nm == NULL) nm = strdup(argv[optind]);
+                       }
+               }
+       }
+       return(objid >= 0);
+}
+
+int remove_namenum(nmapping_type map, char *objtype, entityid objid, char *nm)
+{
+       /* remove the name/num mappings (nm <=> objid)
+        * iff the associated data has been removed successfully
+        * and we are not removing labs (which are defined as attributes
+        * and which need to be removed from the attr file).
+        */
+       book_change ch;
+       int res;
+
+       if (map != M_ATTRIBUTE)
+       {
+               ch.chng = SET_NAMENUM;
+               ch.book_change_u.map.type = map;
+               ch.book_change_u.map.key = objid;
+               ch.book_change_u.map.value = "";        /* triggers removal */
+               res = send_change(&ch);
+       } else res = 0;
+       if (res == 0)
+       {
+               printf("Removed %s (%d,%s)\n", objtype, objid, nm);
+               /* successfull removal changes current database pointer.
+                * all => we are stepping through all NAMENUM objects,
+                * so allow getnextobj to get the first object again
+                */
+               if (map != M_ATTRIBUTE && all) all = 1;
+       }
+       else fprintf(stderr, "Failed to remove %s namenum mapping: (%d,%s) - send_change returns %d\n", objtype, objid, nm, res);
+       return res;
+}
+
+int remove_ws(nmapping_type map, entityid objid, char *nm)
+{
+       item key;
+       char k[20];
+       int res;
+       book_change ch;
+       workstation_state wsdata;
+
+       memset(&wsdata, 0, sizeof(wsdata));
+       key = key_int(k, C_WORKSTATIONS, objid);
+       switch (db_read(key, &wsdata, (xdrproc_t)xdr_workstation_state, CONFIG))
+       {
+       case R_RESOK:
+               /* remove workstation from associated lab
+                * trigger this by removing all labs from ws's state
+                */
+               desc_sub(wsdata.state, attr_types.lab);
+
+               /* remove workstation from host
+                * trigger this by setting host to -1
+                */
+               wsdata.host = -1;
+
+               /* create and submit the change */
+               ch.chng = CHANGE_WS;
+               ch.book_change_u.newws.state = wsdata;
+               ch.book_change_u.newws.ws = objid;
+               if ((res = send_change(&ch)) != 0)
+                       fprintf(stderr, "Failed to remove workstation '%s' from host and/or labs - send_change returns %d\n", nm, res);
+               break;
+
+       case R_NOMATCH:
+               fprintf(stderr, "Cannot find workstation object '%s' to delete\n", nm);
+               res = 0;
+               break;
+       default:
+               res = 1;
+               break;
+       }
+
+       if (res == 0) res = remove_namenum(map, "workstation", objid, nm);
+       return res;
+}
+
+int remove_labhost(nmapping_type map, entityid objid, char *nm)
+{
+       item key;
+       char k[20], *s;
+       hostinfo hdata;
+       int i, res;
+       book_change ch;
+
+       s = ((map == M_HOST) ? "host" : "lab");
+       memset(&hdata, 0, sizeof(hostinfo));
+       key = key_int(k, (map == M_HOST ? C_HOSTS : C_LABS), objid);
+       switch (db_read(key, &hdata, (xdrproc_t)xdr_hostinfo, CONFIG))
+       {
+       case R_RESOK:
+               /* make sure that there are no workstations in this host/lab */
+               res = 0;
+               if ((i = hdata.workstations.entitylist_len) > 0)
+               {
+                       if (force)
+                       {
+                               for (i=0; (i < hdata.workstations.entitylist_len && res == 0); i++)
+                               {
+                                       nmapping_type wmap = M_WORKSTATION;
+                                       entityid wsid = hdata.workstations.entitylist_val[i];
+                                       char *wnm = get_mappingchar(wsid, wmap);
+                                       res = remove_ws(wmap, wsid, wnm);
+                                       if (wnm) free(wnm);
+                               }
+                       }
+                       else
+                       {
+                               fprintf(stderr, "Cannot remove %s '%s' - still has %d associated workstations\n", s, nm, i);
+                               res = 1;
+                       }
+               }
+               if (res == 0)
+               {
+                       /* remove lab/host from hostgroup
+                        * by setting clump to -1
+                        */
+                       if (map == M_HOST)
+                       {
+                               ch.chng = CHANGE_HOST;
+                               ch.book_change_u.newhost.entity = objid;
+                               ch.book_change_u.newhost.clump = -1;
+                       }
+                       else
+                       {
+                               ch.chng = CHANGE_LAB;
+                               ch.book_change_u.newlab.entity = objid;
+                               ch.book_change_u.newlab.clump = -1;
+                       }
+                       if ((res = send_change(&ch)) != 0)
+                       {
+                               fprintf(stderr, "Failed to remove %s: '%s' from hostgroup - send_change returns %d\n", s, nm, res);
+                       }
+               }
+               break;
+       case R_NOMATCH:
+               fprintf(stderr, "Cannot find %s object '%s' to delete\n", s, nm);
+               res = 0;
+               break;
+       default:
+               res = 1;
+               break;
+       }
+
+       if (res == 0) res = remove_namenum(map, s, objid, nm);
+       return res;
+}
+
+int remove_hostgroup(nmapping_type map, entityid objid, char *nm)
+{
+       item key;
+       char k[20];
+       hostgroup_info hgdata;
+       int i, res;
+       nmapping_type map1;
+       entityid objid1;
+       char *nm1;
+
+       memset(&hgdata, 0, sizeof(hostinfo));
+       key = key_int(k, C_HOSTGROUPS, objid);
+       switch (db_read(key, &hgdata, (xdrproc_t)xdr_hostinfo, CONFIG))
+       {
+       case R_RESOK:
+               res = 0;
+               /* check for labs or hosts in this hostgroup */
+               if ((i = hgdata.hosts.entitylist_len) > 0) {
+                       if (force)
+                       {
+                               map1 = M_HOST;
+                               for (i=0; (i < hgdata.hosts.entitylist_len && res == 0); i++)
+                               {
+                                       objid1 = hgdata.hosts.entitylist_val[i];
+                                       nm1 = get_mappingchar(objid1, map1);
+                                       res = remove_labhost(map1, objid1, nm1);
+                                       if (nm1) free(nm1);
+                               }
+                       }
+                       else
+                       {
+                               fprintf(stderr, "Cannot remove hostgroup '%s' - still has %d associated hosts\n", nm, i);
+                               res = 1;
+                       }
+               }
+               if ((i = hgdata.labs.entitylist_len) > 0) {
+                       if (force)
+                       {
+                               map1 = M_ATTRIBUTE;
+                               for (i=0; (i < hgdata.labs.entitylist_len && res == 0); i++)
+                               {
+                                       objid1 = hgdata.labs.entitylist_val[i];
+                                       nm1 = get_mappingchar(objid1, map1);
+                                       res = remove_labhost(map1, objid1, nm1);
+                                       if (nm1) free(nm1);
+                               }
+                       }
+                       else
+                       {
+                               fprintf(stderr, "Cannot remove hostgroup '%s' - still has %d associated labs\n", nm, i);
+                               res = 1;
+                       }
+               }
+               /* We do not explicitly remove hostgroups.
+                * Instead, they are removed when all associated labs
+                * and hosts have been removed (case Plab | Phost below)
+                */
+               break;
+       case R_NOMATCH:
+               fprintf(stderr, "Cannot find hostgroup object '%s' to delete\n", nm);
+               res = 0;
+               break;
+       default:
+               res = 1;
+               break;
+       }
+
+       if (res == 0) res = remove_namenum(map, "hostgroup", objid, nm);
+       return res;
+}
+
+int main(int argc, char *argv[])
+{
+       enum progtype which;
+       int opt;
+       int rename=0, delete=0;
+       int numbers = 0;
+       char *hg=NULL, *lb=NULL, *reason=NULL, *host = NULL;
+       nmapping_type map;
+       item desc;
+       item add_desc, sub_desc, set_desc;
+       int have_add=0, have_sub=0, have_set=0;
+       int labid;
+       int hostid;
+
+       command = argv[0];
+       which = whichprog(command);
+       if (which == Pbad)
+               die1(2, "Unknown program name");
+       add_desc = new_desc();
+       sub_desc = new_desc();
+       set_desc = new_desc();
+       while ((opt=getopt(argc, argv, "RDfach:g:l:d:r:n"))!= EOF)
+               switch(opt)
+               {
+               case 'n': numbers = 1; break;
+               case 'R': rename = 1; break;
+               case 'D': delete = 1; break;
+               case 'f': force=1; break;
+               case 'a': all = 1; break;
+               case 'c': create = 1; break;
+               case 'g': hg = optarg; break;
+               case 'r': reason=optarg; break;
+               case 'l': lb = optarg; break;
+               case 'h': host = optarg; break;
+               case 'd':
+                       /* if +, add to add_desc,
+                          if -, add to sub_desc,
+                          else put in set_desc
+                       */
+                       if (optarg[0] == '+')
+                       {
+                               char *err;
+                               if ((err = parse_desc(optarg+1, &desc)))
+                                       die2(2, "Bad description", err);
+                               check_lab(desc);
+                               desc_add(add_desc, desc);
+                               desc_sub(sub_desc, desc);
+                               have_add = 1;
+                       }
+                       else if (optarg[0] == '-')
+                       {
+                               char *err;
+                               if ((err = parse_desc(optarg+1, &desc)))
+                                       die2(2, "Bad description", err);
+                               check_lab(desc);
+                               desc_add(sub_desc, desc);
+                               desc_sub(add_desc, desc);
+                               have_sub = 1;
+                       }
+                       else
+                       {
+                               char *err;
+                               if ((err = parse_desc(optarg, &desc)))
+                                       die2(2, "Bad description", err);
+                               check_lab(desc);
+                               if (have_set)
+                                       die2(2, "Can only set description once!", "");
+                               desc_add(set_desc, desc);
+                               have_set = 1;
+                       }
+                       free(desc.item_val);
+                       break;
+
+               default: usage(); exit(2);
+               }
+       nargs = argc;   /* transfer to a global var */
+
+       if (all && create)
+               die1(2, "-a (all) and -c (create) are mutually exclusive");
+       if ((rename && delete)
+           || ((rename||delete)&&(create || hg||lb||host||(have_set|have_add|have_sub)||reason))
+               )
+               die1(2, "Inconsistant flags");
+       if (have_set && (have_add||have_sub))
+               die1(2, "Cannot both set and modify description");
+       if (lb)
+       {
+               labid = get_mappingint(lb, M_ATTRIBUTE);
+               if (labid <0)
+                       die2(1, "Unknown lab", lb);
+       }
+       switch(which){
+       case Phost: map=M_HOST; break;
+       case Phg: map=M_HOSTGROUP; break;
+       case Pws: map=M_WORKSTATION; break;
+       case Plab: map=M_ATTRIBUTE; break;
+       case Pbad: break;
+       }
+       if (rename)
+       {
+               entityid uid;
+               /* there must be two argument, we rename first too second */
+               if (optind+2 != argc)
+                       die1(2, "There must be exactly two names given with -R");
+               /* first name must exist, second must not */
+               if (argv[optind][0] == '#')
+                       uid = atoi(argv[optind]+1);
+               else
+                       uid = get_mappingint(argv[optind], map);
+               if (uid < 0)
+                       die2(2, "Cannot find object", argv[optind]);
+               if (get_mappingint(argv[optind+1], map) != -1)
+                       die2(2, "New name already exists", argv[optind+1]);
+               if (make_mapping(uid, argv[optind+1], map)!= 0)
+                       die2(1, "Name change failed", argv[optind+1]);
+       }
+       else if (delete)
+       {
+               /* remove object from lab, host or hostgroup and remove mapping */
+               get_attr_types();
+               while (getnextobj(map, argv))
+               {
+                       switch (which)
+                       {
+                       case Phg:       remove_hostgroup(map, objid, nm); break;
+                       case Plab:      remove_labhost(map, objid, nm); break;
+                       case Phost:     remove_labhost(map, objid, nm); break;
+                       case Pws:       remove_ws(map, objid, nm);      break;
+                       default:        break;
+                       }
+               }       
+       }
+       else /* create or just print */
+       {
+               entityid hgid;
+               get_attr_types();
+               /* optionally create the object and set various features */
+               if (hg && (which == Phg || which == Pws))
+                       die1(2, "Cannot set hostgroup for hostgroup or workstation");
+               if (lb && (which != Pws))
+                       die1(2, "Can only set lab for a workstation");
+               if ((host||have_set||have_add||have_sub) && (which != Pws))
+                       die1(2, "Can only set description or host for a workstation");
+               if (reason && (which != Pws))
+                       die1(2, "Can only set reason for a workstation");
+               if (hg)
+               {
+                       /* try to get hostgroup id */
+                       hgid = get_mappingint(hg, M_HOSTGROUP);
+                       if (hgid < 0) die2(2, "Unknown hostgroup", hg);
+               }
+               if (host)
+               {
+                       if (host[0] == '\0' || strcmp(host, "none")==0)
+                               hostid = -1;
+                       else
+                       {
+                               hostid = get_mappingint(host, M_HOST);
+                               if (hostid < 0) die2(2, "Unknown host", host);
+                       }
+               }
+               while (getnextobj(map, argv))
+               {
+                       item key;
+                       char k[20];
+                       hostinfo hdata;
+                       hostgroup_info hgdata;
+                       workstation_state wsdata;
+                       int st;
+
+                       switch(which)
+                       {
+                       case Pbad: break;
+                       case Phg:               /* there are no direct operations on hostgroups, so just print it */
+                               memset(&hgdata, 0, sizeof(hgdata));
+                               key = key_int(k, C_HOSTGROUPS, objid);
+                               if ((st=db_read(key, &hgdata, (xdrproc_t)xdr_hostgroup_info, CONFIG)) != R_RESOK && st != R_NOMATCH)
+                               {
+                                       printf("%s: cannot get hostgroup info for %s\n", command, nm);
+                               }
+                               else
+                               {
+                                       char *sep = "";
+                                       int e;
+                                       printf("HostGroup: %s\n", nm);
+                                       printf("  hosts:\n    ");
+                                       for (e=0 ; e<hgdata.hosts.entitylist_len ; e++)
+                                       {
+                                               char *hnm = get_mappingchar(hgdata.hosts.entitylist_val[e], M_HOST);
+                                               if (hnm)
+                                               {
+                                                       printf("%s%s", sep, hnm);
+                                                       if (numbers) printf("(%d)", hgdata.hosts.entitylist_val[e]);
+                                               }
+                                               else
+                                                       printf("%s#%d", sep, hgdata.hosts.entitylist_val[e]);
+                                               if (hnm) free(hnm);
+                                               sep=", ";
+                                       }
+                                       printf("\n  labs:\n    ");
+                                       sep="";
+                                       for (e=0 ; e<hgdata.labs.entitylist_len ; e++)
+                                       {
+                                               char *hnm = get_mappingchar(hgdata.labs.entitylist_val[e], M_ATTRIBUTE);
+                                               if (hnm)
+                                                       printf("%s%s", sep, hnm);
+                                               else
+                                                       printf("%s #%d", sep, hgdata.labs.entitylist_val[e]);
+                                               sep=", ";
+                                       }
+                                       printf("\n");
+                               }
+                               xdr_free((xdrproc_t)xdr_hostinfo, (char*) &hgdata);
+                               break;
+
+                       case Plab:
+                       case Phost: /* set hostgroup then print */
+                               if (hg && hgid >= 0)
+                               {
+                                       book_change ch;
+                                       if (which == Plab)
+                                       {
+                                               ch.chng = CHANGE_LAB;
+                                               ch.book_change_u.newlab.entity = objid;
+                                               ch.book_change_u.newlab.clump = hgid;
+                                       }
+                                       else
+                                       {
+                                               ch.chng = CHANGE_HOST;
+                                               ch.book_change_u.newhost.entity = objid;
+                                               ch.book_change_u.newhost.clump = hgid;
+                                       }
+                                       if (send_change(&ch)!= 0)
+                                       {
+                                               fprintf(stderr,"%s: failed to set host group!!\n", command);
+                                       }
+                               }
+                               /* now print out the host information */
+                               memset(&hdata, 0, sizeof(hostinfo));
+                               key = key_int(k, which==Phost?C_HOSTS:C_LABS, objid);
+                               if (db_read(key, &hdata, (xdrproc_t)xdr_hostinfo, CONFIG)!= R_RESOK)
+                               {
+                                       printf("%s: cannot get info for %s\n", command, nm);
+                               }
+                               else
+                               {
+                                       char *sep = "";
+                                       int e;
+                                       char *nm2;
+                                       printf("%s: %-15s in HostGroup:", which==Phost?"Host":"Lab ", nm);
+                                       nm2 = get_mappingchar(hdata.clump, M_HOSTGROUP);
+                                       if (nm2)
+                                               printf(" %s\n", nm2);
+                                       else printf(" #%d\n", hdata.clump);
+                                       free(nm2);
+                                       printf("  workstations:\n    ");
+                                       for (e=0 ; e<hdata.workstations.entitylist_len ; e++)
+                                       {
+                                               entityid wsid = hdata.workstations.entitylist_val[e];
+                                               char *wnm = get_mappingchar(wsid, M_WORKSTATION);
+                                               if (wnm)
+                                               {
+                                                       printf("%s%s", sep, wnm), free(wnm);
+                                                       if (numbers) printf("(%d)", wsid);
+                                               }
+                                               else
+                                                       printf("%s#%d", sep , wsid);
+                                               sep = ", ";
+                                       }
+                                       printf("\n");
+                               }
+                               xdr_free((xdrproc_t)xdr_hostinfo, (char*) &hdata);
+                               break;
+                       case Pws:
+                               /* set lab, description, reason, and then print */
+                               /* if lab is not set, or have_add or have_sub, then
+                                * must be able to get current structure
+                                */
+                               memset(&wsdata, 0, sizeof(wsdata));
+                               key = key_int(k, C_WORKSTATIONS, objid);
+                               switch (db_read(key, &wsdata, (xdrproc_t)xdr_workstation_state, CONFIG))
+                               {
+                               case R_RESOK: /* fine */
+                                       break;
+                               case R_NOMATCH: /* doesn't exist */
+                                       if (lb == NULL)
+                                       {
+                                               fprintf(stderr, "ws: must set lab when creating workstation\n");
+                                               continue;
+                                       }
+#if 0
+                                       if (!have_set)
+                                       {
+                                               fprintf(stderr ,"ws: must set description when creating workstation\n");
+                                               continue;
+                                       }
+#endif
+                                       wsdata.state = new_desc();
+                                       wsdata.why = strdup("");
+                                       wsdata.time = 0;
+                                       wsdata.host = -1;
+                                       set_a_bit(&wsdata.state, attr_types.available);
+                                       break;
+                               default: /* error */
+                                       fprintf(stderr,"ws: cannot get info record for %s\n", nm);
+                                       continue;
+                               }
+                               if (lb)
+                               {
+                                       desc_sub(wsdata.state, attr_types.lab);
+                                       set_a_bit(&wsdata.state, labid);
+                               }
+                               if (have_set)
+                               {
+                                       item nlab = invert_desc(&attr_types.lab);
+                                       del_a_bit(&nlab, attr_types.available);
+                                       desc_sub(wsdata.state, nlab);
+                                       free(nlab.item_val);
+                                       desc_add(wsdata.state, set_desc);
+                               }
+                               if (have_add)
+                                       desc_add(wsdata.state, add_desc);
+                               if (have_sub)
+                                       desc_sub(wsdata.state, sub_desc);
+                               if (reason)
+                               {
+                                       free(wsdata.why);
+                                       wsdata.why = strdup(reason);
+                                       wsdata.time = time(0);
+                                       if (reason[0])
+                                               del_a_bit(&wsdata.state, attr_types.available);
+                                       else
+                                               set_a_bit(&wsdata.state, attr_types.available);
+                               }
+                               if (host)
+                                       wsdata.host = hostid;
+                               print_wsdata(&wsdata, nm);
+                               if (host || reason || have_set||have_add||have_sub||lb)
+                               {
+                                       book_change ch;
+                                       ch.chng = CHANGE_WS;
+                                       ch.book_change_u.newws.state = wsdata;
+                                       ch.book_change_u.newws.ws = objid;
+                                       if (send_change(&ch)!= 0)
+                                               fprintf(stderr,"ws: failed to set workstation info for %s\n", nm);
+                               }
+                       }
+                       free(nm);
+               }
+       }
+       exit(0);
+}
diff --git a/tools/labvis b/tools/labvis
new file mode 100755 (executable)
index 0000000..afbecb2
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/local/bin/ae
+alias bkattr /home/neilb/Common/obj.$ARCH/book/tools/attr
+# visualise opening times for lab
+# Usage:
+#     labvis 01mon 31mon lab
+#
+
+usage() {
+       echo >&2 'Usage: labvis start end [labs]'
+       exit 2
+}
+
+vartype -a, mon
+vartype -A',=' nom
+mon='jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec'
+vartype -a, mlen
+mlen='31,29,31,30,31,30,31,31,30,31,30,31'
+for i in $[1:12]
+do nom[${mon[$i]}]=$i
+done
+
+split() {
+ d= m= {
+       d=${1:h-3}
+       m=${1:t3}
+       mn=${nom[$m]}
+       eval $2=$mn
+       eval $3=$d
+       }
+}
+
+if [ $# -lt 2 ]
+then
+  usage
+fi
+
+split $1 sm sd
+split $2 em ed
+shift; shift
+
+echo $sm $sd $em $ed
+
+for m in $[sm:em]
+do for d in $[1:${mlen[$m]}]
+do
+  OK=""
+  if [ $sm -eq $em ]
+  then
+    [ $d -ge $sd -a $d -le $ed ] && OK=1
+  else
+    [ \( $m -eq $sm -a $d -ge $sd \) -o \( $m -eq $em -a $d -le $ed \) -o \( $m -gt $sm -a $m -lt $em \) ] && OK=1
+  fi
+  if [ -n "$OK" ]
+  then
+    doy=${d:R02}${mon[$m]}
+    p=0
+    echo -n "$doy  |"
+    bkattr -t $doy $* -- open closed | while read a b
+     do case $b in
+       open ) echo -n "*";;
+       closed ) echo -n "-" ;;
+       * ) echo -n " ";;
+       esac
+       p=$[p+1]
+       if [ $[p%6] -eq 0 ]
+       then echo -n '|'
+       fi
+    done
+    echo
+  fi
+done
+done
diff --git a/tools/res.c b/tools/res.c
new file mode 100644 (file)
index 0000000..8d0b968
--- /dev/null
@@ -0,0 +1,50 @@
+
+/*
+ * res host workstation user
+ *
+ * tell the host to reserve the workstation for the user
+ *
+ */
+#include       "../db_client/db_client.h"
+
+void main(int argc, char *argv[])
+{
+    int uid, wsid;
+    static ws_user_pair arg;
+    bool_t *result;
+    
+    if (argc != 4)
+    {
+       fprintf(stderr,"Usage: res host workstation user\n");
+       exit(2);
+    }
+
+    status_client = clnt_create(argv[1], BOOK_STATUS, BOOKVERS, "udp");
+    if (status_client == NULL)
+    {
+       fprintf(stderr,"res: Cannot contact %s\n", argv[1]);
+       exit(2);
+    }
+    wsid = get_mappingint(argv[2], M_WORKSTATION);
+    if (wsid < 0)
+    {
+       fprintf(stderr,"res: cannot find workstation %s\n", argv[2]);
+       exit(2);
+    }
+    uid = find_userid(argv[3]);
+    if (uid < 0)
+    {
+       fprintf(stderr, "res: cannot find user %s\n", argv[3]);
+       exit(2);
+    }
+    arg.userid = uid;
+    arg.hostid = 0;
+    arg.wsid = wsid;
+    result = res_user_2(&arg, status_client);
+    if (result == NULL)
+    {
+       fprintf(stderr,"res: failed, sorry\n");
+       exit(1);
+    }
+    exit(0);
+}
diff --git a/tools/show_booking.c b/tools/show_booking.c
new file mode 100644 (file)
index 0000000..394e1b2
--- /dev/null
@@ -0,0 +1,79 @@
+
+#include       "../db_client/db_client.h"
+
+static char *bstatus[] = { "", "PENDING",
+                             "TENTATIVE",
+                             "ALLOCATED",
+                             "RETURNED",
+                             "FREEBIE",
+                             "REFUNDED",
+                             "CANCELLED",
+                              NULL
+                     };
+int char2bstatus(char *s)
+{
+    int i;
+    for (i=0; bstatus[i] ; i++)
+       if (strccmp(s, bstatus[i])==0)
+           return i;
+    return -1;
+}
+
+void show_booking(booklist bl, book_key slot)
+{
+    char doys[20], pods[20];
+    int pod, doy, lab;
+    char *labname, *tokname;
+    book_key_bits(&slot, &doy, &pod, &lab);
+    doy2str(doys, doy);
+    pod2str(pods, pod);
+    labname = get_mappingchar(lab, M_ATTRIBUTE);
+    tokname = get_mappingchar(bl->tnum, M_TOKEN);
+
+    printf(" %s %s in %s %s x%d with %10s at %s ",
+          doys, pods, labname?labname:"-unknown-lab-", bstatus[bl->status], bl->number, tokname?tokname:"-unknown-token-", myctime(bl->time));
+    free(labname);
+    free(tokname);
+    if (bl->who_by != bl->who_for)
+    {
+       char *other;
+       other = find_username(bl->who_by);
+       printf(" (by %s)", other);
+       free(other);
+    }
+    if (bl->allocations.entitylist_len>0)
+    {
+       char sep = ' ';
+       int i;
+       printf(" alloc to");
+       for (i=0; i<bl->allocations.entitylist_len ; i++)
+       {
+           char *n = get_mappingchar(bl->allocations.entitylist_val[i], M_WORKSTATION);
+           if (n)
+               printf("%c%s", sep, n);
+           else
+               printf("%c%d", sep, bl->allocations.entitylist_val[i]);
+           sep = ',';
+           if (n) free(n);
+       }
+    }
+    if (bl->claimed != 0)
+    {
+       printf(" (");
+       if (bl->claimed & DID_LOGIN)
+       {
+           int sec = ((bl->claimed>>4)-30*60);
+           char * sgn = "";
+           if (sec<0) sgn="-", sec = -sec;
+           printf("claimed at %s%dm%02ds", sgn, sec/60, sec%60);
+       }
+       if (bl->claimed & DID_DEFAULT)
+           printf(" defaulted");
+       if (bl->claimed & WAS_TENTATIVE)
+           printf(" tentative");
+       if (bl->claimed & LOGIN_ON_ALLOC)
+           printf(" on allocated ws");
+       printf(")");
+    }
+    printf("\n");
+}
diff --git a/tools/smadmin.c b/tools/smadmin.c
new file mode 100644 (file)
index 0000000..63a30a1
--- /dev/null
@@ -0,0 +1,159 @@
+
+/*
+ * smadmin - status/manager admin
+ * Usage: smadmin -[tdDr] [-a dbserver] host
+ *   -t  get table
+ *   -d  get database list
+ *   -D  update database list
+ *   -r  restart manager
+ *   -x  tell manager to exit
+ *   -a server   add server to list of up databases
+ *
+ */
+
+
+#include       "../db_client/db_client.h"
+#include       <getopt.h>
+
+char *Usage[] = {
+    "Usage: smadmin -[tdDrx] [-a server] host",
+    "  -t  : get status table",
+    "  -d  : get database replica list",
+    "  -D  : update database replica list",
+    "  -r  : restart manager",
+    "  -x  : exit",
+    "  -a server : record server as a database server",
+    NULL
+};
+
+char *statuses[]=
+{ "Close", "Up", "Unknown", "ReadOnly", "Down", "Unregistered"};
+
+int main(int argc, char *argv[])
+{
+
+       extern CLIENT *status_client;
+
+       extern int optind;
+       extern char *optarg;
+    
+       char *host;
+       int get_tab=0;
+       int get_db=0;
+       int update_db=0;
+       int restart=0;
+       int doexit = 0;
+       char *server = NULL;
+
+       int arg;
+
+       while ((arg=getopt(argc, argv, "tdDrxa:"))!= EOF)
+               switch(arg)
+               {
+               case 't': get_tab=1; break;
+               case 'd': get_db=1;  break;
+               case 'D': update_db=1; break;
+               case 'r': restart=1; break;
+               case 'x': doexit = 1; break;
+               case 'a': server = optarg; break;
+               default:
+                       fprintf(stderr,"smadmin: Unknown option.");
+                       usage();exit(1);
+       
+               }
+
+       if (get_tab+get_db+update_db+restart+doexit+(server!=NULL) != 1)
+       {
+               fprintf(stderr, "smadmin: must give examply one flag.\n");
+               usage();
+               exit(1);
+       }
+       if (optind >= argc)
+       {
+               fprintf(stderr, "smadmin: not host name given\n");
+               usage();
+               exit(1);
+       }
+       if (optind +1 < argc)
+       {
+               fprintf(stderr, "smadmin: only one host name may be given\n");
+               usage();
+               exit(1);
+       }
+       host = argv[optind];
+       if (strcmp(host, ".")==0)
+               host = "localhost";
+
+       status_client = clnt_create(host, BOOK_STATUS, BOOKVERS, "udp");
+       if (status_client == NULL)
+       {
+               fprintf(stderr, "smadmin: cannot connect to %s\n", host);
+               exit(1);
+       }
+
+       if (get_tab)
+       {
+               table tab;
+               if (collect_tab(&tab, status_client)== -1)
+               {
+                       fprintf(stderr, "smadmin: cannot collect table\n");
+                       exit(1);
+               }
+               else
+                       print_tab(&tab, stdout);
+       }
+       if (get_db)
+       {
+               database_list *dblist;
+               int i;
+               dblist = get_databases_2(NULL, status_client);
+               if (dblist == NULL)
+               {
+                       fprintf(stderr, "smadmin: cannot get database list\n");
+                       exit(1);
+               }
+       
+               for (i=0 ; i<dblist->database_list_len ; i++)
+               {
+                       printf("%-20s %s\n", dblist->database_list_val[i].name,
+                              statuses[dblist->database_list_val[i].state]);
+               }
+       }
+       if (update_db)
+       {
+               check_servers();
+       }
+       if (restart)
+       {
+               int *pid;
+               int dummy=0;
+               pid = restart_helper_2(&dummy, status_client);
+               if (pid && *pid > 1)
+                       printf("manager restarted with pid %d\n", *pid);
+               else
+               {
+                       fprintf(stderr,"smadmin: manager restart failed\n");
+                       exit(1);
+               }
+       }
+       if (doexit)
+       {
+               if (status_die_2(NULL, status_client) == NULL)
+               {
+                       fprintf(stderr, "smadmin: could not send kill command to book_status on %s.\n", host );
+                       exit(1);
+               }
+       }
+       if (server)
+       {
+               database_info dbi;
+               dbi.name = server;
+               dbi.state = DB_UP;
+               if (set_database_2(&dbi, status_client) == NULL)
+               {
+                       fprintf(stderr, "smadmin: could not register %s as a server.\n", server);
+                       exit(1);
+               }
+       }
+       exit(0);
+}
diff --git a/tools/token.c b/tools/token.c
new file mode 100644 (file)
index 0000000..87f0592
--- /dev/null
@@ -0,0 +1,421 @@
+
+#include       "../db_client/db_client.h"
+#include       <getopt.h>
+
+char *Usage[] = {
+    "Usage: token [-v] [-l | tokens]   -- list (verbosely) some or all tokens",
+    "       token -R oldname newname   -- rename a token",
+    "       token [-f] -c tokename [-i idnum] attributes -- create a new token",
+    "       token -[ads] attributes token[/EL] - change attributes in a token",
+    "       token -D token             -- delete a token",
+    NULL };
+
+static int get_attrs(description d, char *names);
+static void list_token(entityid id, int verbose);    
+static int adj_token(char *tname, description *setp, description remove, description add);
+
+int main(int argc, char *argv[])
+{
+       int list=0, ren=0, make=0, change=0, delete = 0;
+
+       int verbose = 0;
+       int listall = 0;
+       int force = 0;
+       char *newtoken= NULL;
+       char *server = NULL;
+       int newid = -1;
+       description remove, add, set, *setp = NULL;
+
+       extern int optind;
+       extern char *optarg;
+       int opt;
+
+       remove = new_desc();
+       add = new_desc();
+       set = new_desc();
+    
+       while ((opt=getopt(argc, argv, "vlRDfc:i:a:d:s:r:"))!= EOF)
+               switch(opt)
+               {
+               case 'r': server = optarg; break;
+               case 'v': verbose = 1; break;
+               case 'l': listall = 1; list=1; break;
+               case 'R': ren=1 ; break;
+               case 'D': delete =1; break;
+               case 'f': force=1; break;
+               case 'c': newtoken = optarg; make=1 ; break;
+               case 'i': newid = atoi(optarg);
+                       if (newid <= 0)
+                       {
+                               fprintf(stderr, "token: id must be >= 1: %s\n", optarg);
+                               usage();
+                               exit(2);
+                       }
+                       break;
+               case 'a':
+                       if (!get_attrs(add, optarg)) exit(2);
+                       change = 1;
+                       break;
+               case 'd':
+                       if (!get_attrs(remove, optarg)) exit(2);
+                       change = 1;
+                       break;
+               case 's':
+                       if (!get_attrs(set, optarg)) exit(2);
+                       setp = &set;
+                       change = 1;
+                       break;
+               default:
+                       usage();
+                       exit(2);
+               }
+       if (ren+make+change+list+delete == 0)
+               list = 1;
+       if (ren+make+change+list+delete > 1)
+       {
+               fprintf(stderr, "token: multiple modes specified\n");
+               usage();
+               exit(2);
+       }
+
+       if (verbose && !list)
+       {
+               fprintf(stderr,"token: -v only allowed when listing\n");
+               usage();
+               exit(2);
+       }
+       if (force && !make)
+       {
+               fprintf(stderr,"token: -f only allowed with -c\n");
+               usage();
+               exit(2);
+       }
+       if (newid > 0 && !make)
+       {
+               fprintf(stderr,"token: -i only allowed with -c\n");
+               usage();
+               exit(2);
+       }
+       if (server)
+               open_db_client(server, "tcp");
+
+       if (delete)
+       {
+               int id;
+               book_change ch;
+               if (optind == argc)
+               {
+                       fprintf(stderr, "token: must give a token to delete\n");
+                       usage();
+                       exit(2);
+               }
+               if (optind < argc-1)
+               {
+                       fprintf(stderr, "token: can only delete one token at a time\n");
+                       usage();
+                       exit(2);
+               }
+               id = get_mappingint(argv[optind], M_TOKEN);
+               if (id < 0)
+               {
+                       fprintf(stderr, "token: unknown token: %s\n", argv[optind]);
+                       exit(2);
+               }
+               memset(&ch, 0, sizeof(ch)); /* this will null the token, hence delete */
+               ch.chng = ADD_TOKEN;
+               ch.book_change_u.addtok.tnum = id;
+               if (send_change(&ch)!= 0)
+               {
+                       fprintf(stderr,"token: delete token request failed\n");
+                       exit(3);
+               }
+               if (make_mapping(id, "", M_TOKEN)!= 0)
+               {
+                       fprintf(stderr,"token: failed to remove name mapping for token\n");
+                       exit(3);
+               }
+       }
+       if (list)
+       {
+               if (listall)
+               {
+                       intpairlist toks= NULL, t;
+                       char k[20];
+                       if (db_read(key_int(k, C_ALLOTMENTS, 0), &toks, (xdrproc_t)xdr_intpairlist, CONFIG)
+                           != R_RESOK)
+                       {
+                               fprintf(stderr,"token: cannot get list of tokens\n");
+                               exit(1);
+                       }
+                       for (t=toks ; t; t=t->next)
+                               list_token(t->data, verbose);
+                       xdr_free((xdrproc_t)xdr_intpairlist, (char*) &toks);
+               }
+               else if (optind == argc)
+               {
+                       fprintf(stderr,"token: no tokens given\n");
+                       usage();
+                       exit(2);
+               }
+               else while (optind < argc)
+               {
+                       int id = get_mappingint(argv[optind], M_TOKEN);
+                       if (id <0 )
+                               fprintf(stderr,"token: unknown token %s\n", argv[optind]);
+                       else
+                               list_token(id, 1/*verbose*/); /* when listing xplicit tokens, always be verbose */
+                       optind++;
+               }
+       }
+       if (ren)
+       {
+               int id;
+               if (argc - optind != 2)
+               {
+                       fprintf(stderr,"token: must give old and new token names for rename\n");
+                       usage();
+                       exit(2);
+               }
+               id = get_mappingint(argv[optind], M_TOKEN);
+               if (id <0)
+               {
+                       fprintf(stderr,"token: cannot find old token %s\n", argv[optind]);
+                       exit(2);
+               }
+               if (get_mappingint(argv[optind+1], M_TOKEN) != -1)
+               {
+                       fprintf(stderr,"token: new token name exists: %s\n", argv[optind+1]);
+                       exit(2);
+               }
+               if (make_mapping(id, argv[optind+1], M_TOKEN) != 0)
+               {
+                       fprintf(stderr, "token: changing name failed.\n");
+                       exit(1);
+               }
+       }
+       if (make)
+       {
+               /*
+                * create token called newtoken with id newid.
+                * if newid == -1, pick a free uid
+                * if force, then don't worry if it already exists
+                * if any args, set these attributes
+                */
+               int id;
+               token tok;
+               book_change ch;
+               id = get_mappingint(newtoken, M_TOKEN);
+               if (id != -1)
+               {
+                       /* already exists */
+                       if (id == -2)
+                       {
+                               fprintf(stderr, "token: problem communiating with database\n");
+                               exit(3);
+                       }
+                       if (!force || (newid >0 && newid != id))
+                       {
+                               fprintf(stderr,"token: token %s already exists with id %d\n",
+                                       newtoken, id);
+                       }
+                       newid = id;
+               }
+               if (newid == -1)
+                       newid = choose_id(1, M_TOKEN);
+               if (newid < 0)
+               {
+                       fprintf(stderr, "token: problem communicating with database\n");
+                       exit(3);
+               }
+               while (optind < argc)
+                       if (!get_attrs(set, argv[optind++]))
+                               exit(2);
+               if (make_mapping(newid, newtoken, M_TOKEN)!= 0)
+               {
+                       fprintf(stderr, "token: cannot record token name with database\n");
+                       exit(3);
+               }
+
+               tok.advance = set;
+               tok.late = set;
+               ch.chng = ADD_TOKEN;
+               ch.book_change_u.addtok.tnum = newid;
+               ch.book_change_u.addtok.tok = tok;
+               if (send_change(&ch)!= 0)
+               {
+                       fprintf(stderr, "token: cannot store token info in database\n");
+                       exit(3);
+               }
+       }
+       if (change)
+       {
+               if (optind == argc)
+               {
+                       fprintf(stderr, "token: no tokens given\n");
+                       usage();
+                       exit(2);
+               }
+               for ( ; optind < argc ; optind++)
+                       if (!adj_token(argv[optind], setp, remove, add)) exit(1);
+       }
+       exit(0);
+}
+
+static int adj_token(char *tname, description *setp, description remove, description add)
+{
+       /* token may end /A or /L for only changing advance or late */
+       int len = strlen(tname);
+       int late = 1, adv = 1;
+       int id;
+       token tok;
+       book_change ch;
+       char name[200];
+       char k[20];
+       strcpy(name, tname);
+       if (name[len-2] == '/')
+       {
+               if (name[len-1] == 'A')
+                       late = 0;
+               else if (name[len-1] == 'L')
+                       adv = 0;
+               else
+               {
+                       fprintf(stderr,"token: unknown switch: %s\n", tname);
+                       return 0;
+               }
+               name[len-2] = '\0';
+       }
+
+       id = get_mappingint(name, M_TOKEN);
+       if (id < 0)
+       {
+               fprintf(stderr, "token: unknown token %s\n", name);
+               return 0;
+       }
+
+       memset(&tok, 0, sizeof(tok));
+       if (db_read(key_int(k, C_TOKENS, id), &tok, (xdrproc_t)xdr_token, CONFIG)!= R_RESOK)
+       {
+               fprintf(stderr, "token: cannot find token %s, use -f -c\n", name);
+               return 0;
+       }
+       if (adv)
+       {
+               if (setp)
+               {
+                       zero_desc(&tok.advance);
+                       desc_add(tok.advance, *setp);
+               }
+               desc_sub(tok.advance, remove);
+               desc_add(tok.advance, add);
+       }
+       if (late)
+       {
+               if (setp)
+               {
+                       zero_desc(&tok.late);
+                       desc_add(tok.late, *setp);
+               }
+               desc_sub(tok.late, remove);
+               desc_add(tok.late, add);
+       }
+       ch.chng = ADD_TOKEN;
+       ch.book_change_u.addtok.tnum = id;
+       ch.book_change_u.addtok.tok = tok;
+       if (send_change(&ch)!= 0)
+       {
+               fprintf(stderr, "token: cannot store token info in database\n");
+               return 0;
+       }
+       return 1;
+}
+
+static int get_attr(description d, char *name)
+{
+       /* name either names an attribute or a config entry
+        * which lists some attributes
+        */
+       int a;
+       char * attrs;
+       a = get_mappingint(name, M_ATTRIBUTE);
+       if (a>=0)
+       {
+               set_a_bit(&d, a);
+               return 1;
+       }
+    
+       attrs = get_configchar(name);
+       if (attrs)
+               return get_attrs(d, attrs);
+
+       fprintf(stderr,"token: cannot find attribute %s\n", name);
+       return 0;
+}
+
+static int get_attrs(description d, char *names)
+{
+       char buf[200];
+       char *dot;
+
+       while ((dot = strchr(names, '.')))
+       {
+               strncpy(buf, names, dot-names);
+               buf[dot-names] = '\0';
+               if (! get_attr(d, buf))
+                       return 0;
+               names = dot+1;
+       }
+       return get_attr(d, names);
+}
+
+
+
+static void list_desc(char *prefix, description d)
+{
+       int first = 1;
+       int i;
+       for (i=0 ; i<d.item_len*8 ; i++)
+       {
+               if ((i%32)==0 && !first)
+               {
+                       printf("\n");
+                       first = 1;
+               }
+               if (query_bit(&d, i))
+               {
+                       char *n;
+                       if (first)
+                               printf("%s", prefix);
+                       first = 0;
+                       n = get_mappingchar(i, M_ATTRIBUTE);
+                       if (n== NULL) n = strdup("*unknown*attr*");
+                       printf("%s ", n);
+                       free(n);
+               }
+       }
+       if (!first) printf("\n");
+}
+
+static void list_token(entityid id, int verbose)
+{
+       char *name = get_mappingchar(id, M_TOKEN);
+       if (name == NULL)
+               name = strdup("*unknown*token*");
+
+       printf("%s\n", name);
+       if (verbose)
+       {
+               token t;
+               char k[20];
+               memset(&t, 0, sizeof(t));
+               if (db_read(key_int(k, C_TOKENS, id), &t, (xdrproc_t)xdr_token, CONFIG)== R_RESOK)
+               {
+                       printf("Advance:\n");
+                       list_desc("  ", t.advance);
+                       printf("Late:\n");
+                       list_desc("  ", t.late);
+                       xdr_free((xdrproc_t)xdr_token, (char*) &t);
+               }
+               printf("\n");
+       }
+}
diff --git a/tools/user.c b/tools/user.c
new file mode 100644 (file)
index 0000000..406bf9a
--- /dev/null
@@ -0,0 +1,490 @@
+
+#include       "../db_client/db_client.h"
+#include       <getopt.h>
+
+void show_tokens(char *user, intpairlist t, intpairlist p, int mach)
+{
+       /* Print info in t.
+        * for each, if matching element in p print t(p)
+        */
+       int col= 0;
+
+       for ( ; t ; t=t->next)
+       {
+               intpairlist match;
+               char *name = get_mappingchar(t->data, M_TOKEN);
+               if (name == NULL) name = strdup("<unknown>");
+               match = findintkey(p, t->data);
+               if (mach)
+               {
+                       if (p)
+                               printf("%-15s %-16s %4d %4d\n",  user, name, t->num,
+                                      match?match->num:0
+                                       );
+                       else
+                               printf("%-15s %-16s %4d\n", user, name, t->num);
+               }
+               else if (match)
+                       printf("%-16s%3d(%2d in use)  ", name, t->num, match->num);
+               else
+                       printf("%-19s%4d  ", name, t->num);
+       
+               if (!mach && ++col == 3)
+               {
+                       printf("\n");
+                       col = 0;
+               }
+               free(name);
+       }
+       if (col)
+               printf("\n");
+}
+
+
+int show_bookings(int uid, userbookinglist ubl, int mach)
+{
+       booklist bookings, b;
+
+
+       bookings = getfullbookings(ubl, uid);
+
+       if (bookings== NULL)
+       {
+               fprintf(stderr,"user: cannot show booking information\n");
+               return -1;
+       }
+
+       for (b = bookings ; b ; b=b->next, ubl = ubl->next)
+               show_booking(b, ubl->slot);
+       xdr_free((xdrproc_t)xdr_booklist, (char*)  &bookings);
+       return 1;
+}
+
+static query_req request;
+static char *prefix;
+
+void userlist_init(int allotments)
+{
+       if (db_client == NULL)
+               open_db_client(NULL, "tcp");
+       if (allotments)
+       {
+               request.key.item_val=memdup("9_", 2);
+               request.key.item_len=2;
+               request.type = M_NEXT_PREFIX;
+               request.database = CONFIG;
+               prefix = "9_";
+       }
+       else
+       {
+               request.key.item_val=memdup("dumy", 4);
+               request.key.item_len=4;
+               request.type = M_FIRST;
+               request.database = BOOKINGS;
+               prefix="";
+       }
+}
+
+char *userlist_next()
+{
+       while(1)
+       {
+               query_reply *result;
+               result = query_db_2(&request, db_client);
+               if (result == NULL) {
+                       return NULL;
+               }
+               if (request.type == M_FIRST)
+                       request.type = M_NEXT;
+               free(request.key.item_val);
+               if (result->error != R_RESOK ||
+                   result->query_reply_u.data.key.item_len == 0 ||
+                   result->query_reply_u.data.key.item_val== NULL)
+               {
+                       return NULL;
+               }
+       
+               request.key.item_len = result-> query_reply_u.data.key.item_len;
+               request.key.item_val =
+                       memdup(result->query_reply_u.data.key.item_val,
+                              request.key.item_len+1);
+               request.key.item_val[request.key.item_len] = 0;
+               xdr_free((xdrproc_t)xdr_query_reply, (char*) result);
+               if (strncmp(request.key.item_val, prefix, strlen(prefix))==0
+                   && strspn(request.key.item_val+strlen(prefix), "0123456789")
+                   == request.key.item_len-strlen(prefix))
+                       return request.key.item_val+strlen(prefix);
+       }
+}
+
+/*
+ * user can print out
+ *     total token allocation and usage        -t
+ *     personal token allocation               -a
+ *     pending bookings                        -b
+ *     past/cancelled bookings                 -p
+ *  these can be in a "machine readable" format -m
+ *
+ * user can also allocate tokens               -A
+ *     which requires a count                  -c
+ *
+ * user can also iterate though all users      ALL
+ *
+ * if no action flags are given, -tbp is assumed
+ *
+ */
+
+char *Usage[] = {
+    "Usage: user -[Dtabp] [-m] [user|ALL] ...",
+    "       user [-C [-i id]] -A token -c count user ...",
+    "       user -R oldname newname",
+    "  -D : delete user record if no bookings",
+    "  -t : total token allocation and usage",
+    "  -a : personal token allocation",
+    "  -b : pending bookings",
+    "  -p : past/cancelled bookings",
+    "  -m : machine readable, no headers",
+    "  -A : change personal allocation of token to count or by +-count",
+    "  -C : create class if it doesn't exist",
+    "  -i : provide id number to use for class",
+    "  -R : rename",
+    NULL };
+
+int main(int argc, char *argv[])
+{
+       extern int optind;
+       extern char *optarg;
+       int arg;
+       int errs = 0;
+       int all = 0;
+       char *user;
+
+       int tokens=0, alloc=0, bookings=0, past=0, mach=0;
+       char *token = NULL;
+       int tokid;
+       int count = -1, relcount = 0;
+       int clid = -1;
+       int create = 0;
+       int delete = 0;
+       int rename = 0;
+       char *server = NULL;
+
+       while((arg=getopt(argc,argv,"tabpmA:c:Ci:Rr:D"))!=EOF)
+               switch(arg)
+               {
+               case 'D': delete = 1; break;
+               case 'r': server = optarg; break;
+               case 'R': rename = 1; break;
+               case 'C': create = 1; break;
+               case 'i':
+                       if ((clid = atoi(optarg) < get_class_min()))
+                       {
+                               fprintf(stderr, "user: class id must be a least %d\n", get_class_min());
+                               usage();
+                               exit(2);
+                       }
+                       break;
+               case 't': tokens = 1; break;
+               case 'a': alloc = 1; break;
+               case 'b': bookings = 1; break;
+               case 'p': past = 1; break;
+               case 'm': mach = 1; break;
+               case 'A':
+                       token = optarg;
+                       break;
+               case 'c':
+                       if (optarg[0] == '-')
+                               relcount = -1;
+                       else if (optarg[0] == '+')
+                               relcount = 1;
+                       if (relcount) optarg++;
+                       if (optarg[0] < '0' || optarg[0] > '9')
+                       {
+                               fprintf(stderr,"user: count must be [+/-]num\n");
+                               usage();
+                               exit(2);
+                       }
+                       count = atoi(optarg);
+                       break;
+               default:
+                       usage();
+                       exit(2);
+               }
+       if (!(delete||create||tokens||alloc||bookings||past||token||rename))
+               tokens = bookings = past = 1;
+
+       if (rename && (delete||create||tokens||alloc||bookings||past||token))
+       {
+               fprintf(stderr,"user: no other flags allowed with rename (-R).\n");
+               usage();
+               exit(2);
+       }
+
+       if ((tokens|alloc|bookings|past|delete) && (token || count>=0 || create || clid>0))
+       {
+               fprintf(stderr,"user: cannot give token or count with a printing or deletion command.\n");
+               usage();
+               exit(2);
+       }
+       if (token && count<0)
+       {
+               fprintf(stderr, "user: must give a count for token allocation.\n");
+               usage();
+               exit(2);
+       }
+       if (token && mach)
+       {
+               fprintf(stderr, "user: -m flag not meaningful with -A\n");
+               usage();
+               exit(2);
+       }
+       if (server)
+               open_db_client(server, "tcp");
+       if (token)
+       {
+               tokid = get_mappingint(token, M_TOKEN);
+               if (tokid < 0)
+               {
+                       fprintf(stderr,"user: unknown token %s\n", token);
+                       exit(2);
+               }
+       }
+
+       if (rename)
+       {
+               char *newname, *oldname;
+               int id, otherid;
+               if (argc-optind != 2)
+               {
+                       fprintf(stderr,"user: two names required for rename\n");
+                       usage();
+                       exit(2);
+               }
+
+               oldname=argv[optind];
+               newname=argv[optind+1];
+               id = get_mappingint(oldname, M_CLASS);
+               if (id <0)
+               {
+                       fprintf(stderr,"user: old name '%s' not defined\n", oldname);
+                       exit(2);
+               }
+               otherid = get_mappingint(newname, M_CLASS);
+               if (otherid >=0)
+               {
+                       fprintf(stderr,"user: new name '%s' already in use, uid=%d!\n", newname, otherid);
+                       exit(2);
+               }
+               if (id < get_class_min())
+               {
+                       fprintf(stderr,"user: can only rename classes, with id >= %d\n", get_class_min());
+                       exit(2);
+               }
+
+
+               if (make_mapping(id, newname, M_CLASS) != 0)
+               {
+                       fprintf(stderr,"user: failed to make change in database\n");
+                       exit(3);
+               }
+               exit(0);
+       }
+    
+
+       if (optind == argc && ! mach)
+       {
+               fprintf(stderr, "user: no users given\n");
+               usage();
+               exit(2);
+       }
+
+       if (optind+1 == argc && strcmp(argv[optind],"ALL")==0)
+       {
+               /* need to find ALL users */
+               if (bookings || past || tokens)
+                       userlist_init(0);
+               else
+                       userlist_init(1);
+               all = 1;
+       }
+       for (user= all?userlist_next():argv[optind];
+            user != NULL ;
+            user = delete?user:all?userlist_next():argv[++optind])
+       {
+               users urec;
+               int uid;
+               char key[20];
+               char *uname;
+
+               if (user[0] >= '0' && user[0] <= '9') {
+                       uid = atoi(user);
+                       uname = find_username(uid);
+               } else {
+                       uid = find_userid(user);
+                       uname = user;
+               }
+               if (uid < 0 && create)
+               {
+                       uid = clid;
+                       if (uid == -1)
+                       {
+                               uid = choose_id(get_class_min(), M_CLASS);
+                               if (uid < 0)
+                               {
+                                       fprintf(stderr,"user: problem communicating with database\n");
+                                       exit(3);
+                               }
+                       }
+                       else
+                       {
+                               char *nm;
+                               if ((nm=get_mappingchar(uid, M_CLASS)) != NULL)
+                               {
+                                       fprintf(stderr,"user: id %d already used for %s\n", uid, nm);
+                                       free(nm);
+                                       exit(2);
+                               }
+                       }
+                       if (make_mapping(uid, uname, M_CLASS)!= 0)
+                       {
+                               fprintf(stderr,"user: cannot store new class name in database!\n");
+                               exit(3);
+                       }
+               }
+       
+               if (uid < 0)
+               {
+                       fprintf(stderr,"user: unknown user or class: %s\n", uname);
+                       errs++;
+                       continue;
+               }
+
+               memset(&urec, 0, sizeof(urec));
+               switch(db_read(user_key(key, uid), &urec, (xdrproc_t)xdr_users, BOOKINGS))
+               {
+               case R_RESOK:
+                       break;
+               case R_NOMATCH:
+                       urec.bookings = NULL;
+                       urec.pastbookings = NULL;
+                       urec.tokens = NULL;
+                       break;
+               default:
+                       fprintf(stderr,"user: cannot get information for %s\n", uname);
+                       continue;
+               }
+
+               if (tokens)
+               {
+                       intpairlist all_tokens;
+                       all_tokens = get_alloc_tokenlist(uid);
+                       if (!mach)  printf("Token allocation for %s\n", uname);
+                       show_tokens(uname, all_tokens, urec.tokens, mach);
+                       xdr_free((xdrproc_t)xdr_intpairlist, (char*) &all_tokens);
+               }
+       
+               if (alloc)
+               {
+                       intpairlist alot = NULL;
+                       char k[20];
+                       if (db_read(key_int(k, C_ALLOTMENTS,uid), &alot, (xdrproc_t)xdr_intpairlist, CONFIG)!= R_RESOK)
+                               (!mach) &&  printf("No personal allocation for %s\n", uname);
+                       else
+                       {
+                               (!mach) &&  printf("Personal allocation for %s\n",  uname);
+                               show_tokens(uname, alot, NULL, mach);
+                               xdr_free((xdrproc_t)xdr_intpairlist, (char*) &alot);
+                       }
+               }
+
+
+               if (bookings)
+               {
+                       if (urec.bookings == NULL)
+                       {
+                               if (!mach)  printf("No pending bookings\n");
+                       }
+                       else
+                       {
+                               if (!mach)  printf("Pending bookings for %s\n", uname);
+                               show_bookings(uid, urec.bookings, mach);
+                       }
+               }
+
+               if (past)
+               {
+                       if (is_defaulter(uid))
+                               printf("Is a defaulter\n");
+                       if (urec.pastbookings == NULL)
+                       {
+                               if (!mach)  printf("No past bookings\n");
+                       }
+                       else
+                       {
+                               if (!mach)  printf("Past bookings for %s\n", uname);
+                               show_bookings(uid, urec.pastbookings, mach);
+                       }
+               }
+
+               if (token)
+               {
+                       book_change ch;
+                       int change;
+                       if (relcount != 0)
+                               change = relcount * count;
+                       else
+                       {
+                               intpairlist alot = NULL;
+                               char k[20];
+                               change = count;
+                               if (db_read(key_int(k, C_ALLOTMENTS, uid), &alot, (xdrproc_t)xdr_intpairlist, CONFIG)== R_RESOK)
+                               {
+                                       intpairlist t;
+                                       t = findintkey(alot, tokid);
+                                       if (t) change = count - t->num;
+                               }
+                       }
+                       if (change)
+                       {
+                               ch.chng = ADD_CLASS;
+                               ch.book_change_u.addcl.tnum = tokid;
+                               ch.book_change_u.addcl.count = change;
+                               ch.book_change_u.addcl.user = uid;
+                               if (send_change(&ch) != 0)
+                               {
+                                       fprintf(stderr, "user: failed to send allocation change for %s\n",
+                                               uname);
+                                       errs++;
+                               }
+                               else
+                                       if (!mach)  printf("Changes token allocation for %s by %d\n",
+                                                          uname, change);
+                       }
+                       else
+                               if (!mach)  printf("No change required for %s\n", uname);
+               }
+
+               if (delete)
+               {
+                       user = all?userlist_next():argv[++optind];
+                       if (urec.bookings || urec.pastbookings)
+                       {
+                               fprintf(stderr, "user: Cannot delete user with bookings or pastbookings: %s\n", uname);
+                       }
+                       else
+                       {
+                               book_change ch;
+                               printf("deleting %s...\n", uname);
+                               ch.chng = REMOVE_USER;
+                               ch.book_change_u.user_togo = uid;
+                               if (send_change(&ch) != 0)
+                               {
+                                       fprintf(stderr, "user: failed to send delete request for %s\n",
+                                               uname);
+                                       errs++;
+                               }
+                       }
+               }
+       }
+       exit(errs?1:0);
+}