@Comment($HeadURL$ $Id$)
@Make(Manual)
@Device(PostScript)
@PageFooting(center "Draft of @value(date)")
@style(spacing = 1.5, indent 5, FontFamily = TimesRoman, size = 12)
@style(LeftMargin 1 inch, RightMargin 1 inch)

@Begin(TitlePage)
@Begin(TitleBox)
@b[The Athena Service Management System]
@blankspace ( 1 line )
@b[SMS Client Internals Document]
@blankspace ( 1 line )
@i[Chris D. Peterson]
@End(TitleBox)
@CopyrightNotice(Massachusetts Institute of Technology)
@End(TitlePage)

@Chapter(Introduction)

The @i(Sms Client) program was designed to allow ordinary users a
simple method of performing all database manipulations common here at
@i(Project Athena).  The user interacts with the program through a
terminal based menu.  This allows use of sms on a wide variety of
platforms.  Unlike previous packages where many different clients were
necessary to perform all these tasks, the @i(Sms Client) program
allows all operations to be performed from one application.  This
saves the user time and effort by having all common operations in one
convenient place.  I have spent a good deal of time cleaning up the
code to make it uniform throughout the entire client and am writing
this document to explain some of the decisions. My hope is that this
will allow you to add features with a minimum of amount effort, as
well as easing the transition between your code and mine.

@Chapter(Overview)

This section presents general concepts that hold throughout the code.
Some of the items may seem trivial, but together they keep the code
consistent and understandable.

@Section(File Organization)

Most files correspond to one type of item in the @i(SMS) database,
such as lists, machines, clusters, etc.  These files are self
contained so that you can pick and choose which of these you would
like to use in any new client you decide to build. There are a few
special files that do not follow this convention.
@begin(description)

@i[util.c]@\Utility functions used by many of the other files.

@i[menu.c]@\Menu definitions for the entire application.

@i[main.c]@\The main driver that handles application start up.

@i[delete.c]@\Functions that handle deletion of lists and users.

@i[globals.c]@\All global variable definitions.
@end(description)

@Section(Command Organization)

Each menu item has one and only one function associated with it,
although the same function may be used by two identical menu items.
The arguments passed to these functions from the menu package are an
array of character strings and the number of strings in the array.
The first argument in the array (argv[0]) is the name of the menu item
that has been selected.  Thus the actual user specified arguments are
passed starting at the second argument (argv[1]).

@Section(Naming and Comment Conventions)

@Index(Variables)
@Index(Functions)

I use all capital letters for constants, while variables are all small
letters, and functions are represented by mixing capital and small
letters, capitalizing each word in the function name.  I use long
function names as functions are usually not called often, and long
names are much more descriptive.  There are a few functions that do
not follow the mixed caps and small convention, these are generally
those I called out of @i(SMSLib), @i[CLib], or the menu package.

@Index(Comments)

Each file has a header containing the Revision Control System (RCS)
modification time and some history information, as well as some
information about the file.  The template for this is a file named
'header'.  Each function has a comment preceding it describing the
function, all variables used, and the value that this function
returns.  Notes of special concern for this function are also listed
here.  The function template is in a file called, surprisingly enough,
'function'.  Other comments are placed where appropriate.

@Section(Retrieving data from the server)
@Label(queue-def)

Since one of the limitations of @i[SMS] is that no query may be
executed recursively, all information returned by a query must be
received before another query can be executed.  To facilitate
programming simplicity I store the information returned from each
query into a queue as soon as it is received.  This allows a simple,
although rather memory intensive, method of getting around this
problem.  Another advantage of this method is that since no operations
are taking place on the data as it comes back from the server the time
that the server spends sending the data to the clients is kept to a
minimum, a necessity in large-scale environments.

@Chapter[Application Wide Definitions]

This section will cover all items that cross function boundaries,
including global variables, structure definitions, and constant
definitions.

@Section[Global Variables]

@Label(Globals)The @i[SMS Client] has a few global variables.  Since
you will run into them throughout the application they warrant mention
here.
@Index(program_name)
@Index(user)
@Index(verbose)
@Index(found_some)
@begin(description)
@i[program_name] @\This is the name of the program currently being
executed, and is retrieved from argv[0] in @i[main.c].

@i[user] @\This is the name of the user currently executing the program,
and is also set in @i[main.c].

@i[verbose] @\This is a boolean value that, when FALSE turns off the
delete confirmation.  For more information see section @ref(confirm).

@i[found_some] @\This is a boolean that allows the @b[PrintByType()]
(sect. @ref(pbytype) function to be able to communicate with the
function calling the query that invokes it, as well as among calls to
itself.
@end(description)

@Section[Structure Definitions]

@Index(qelem)

The only global structure definition is the one for the queue element.
What I will refer to as the queue is actually a doubly linked list
that all my queue manipulation functions (sect. @ref(queuecount))
treat as a true queue.

@begin(Format)
struct qelem {
  struct    qelem *q_forw;
  struct    qelem *q_back;
  char * q_data;
};
@end(Format)

The queue data need not be a string; @i[char *] is used as a generic
pointer here.

@Section[Definitions]

@Index(FVoid)
@Index(FInt)
@Index(FCharStar)

For readability there are a few important global definitions.
Function pointers in @i[C] tend to be tough to read so I have typedef'ed the
function pointers; FVoid, FInt, and FCharStar.  These refer to functions
that return nothing, integers, and strings, respectively.

@Index(MOD_FORMAT)

Another global definition, MOD_FORMAT, is used to format the
modification information of an item for printing.  It is important that
all modification information is presented to the user with this
format, to keep the @i[SMS Client] consistent.

@Index(Infodefs)
@Label(infodefs)

Information about an item in the database is returned by sms_query()
in an array of strings.  Remembering what @i[info][7] is can be
difficult and it is possible that in future updates of the @i[SMS
Server] @i[info][7] may contain completely different information.  To
solve both of these problems I have created a set of definitions for
each element of the argument list of each data type that the @i[SMS
Client] uses.  A list of these can be found in appendix
@ref(a_infodefs), as well as the file @i[infodefs.h].

@Chapter(Utility Functions)

I created several utility functions that handle common operations used
by many of the functions in the @i[SMS Client].  These functions
perform things like the storing of information in a queue as it comes
in from the @i(SMS Server), queue manipulation, querying the user, 
and printing information.

@Section(Storing and Freeing the Queue) 

As noted in section @ref(queue-def), all incoming information from the
@i(SMS Server) is stored in a queue as soon as it is received.  The
function that does this, @b[StoreInfo()], as well the one that frees the
queue when we are finished with it, @b[FreeQueue()], are described here.

@Index(StoreInfo) 

Use the function @b[StoreInfo()] to store all information received
from the @i[SMS Server] into a queue.  Space for this queue is
dynamically allocated using malloc().  The actual information is
stored in a NULL terminated array of strings.  The number of strings
in this array can be easily found with the function @b[CountArgs()]
(sect. @ref(countargs)).
@begin(format)

int
StoreInfo(@i[argc, argv, data])
int @i[argc];
char ** @i[argv];
char * @i[data];
@end(format)
@begin(Description)

@i[argc] @\The number of strings passed in argv.

@i[argv] @\An array of strings to be stored in the queue.

@i[data] @\A pointer to the previous element in this queue, or NULL if@\it
should create a new queue.  This is updated with each call to @b[StoreInfo()]
and the value associated with this pointer is the last element in the
queue.

@end(Description)
@blankspace(1 line)
Here is an example of an @i[SMS] query that would use this function to store
its arguments.

@Begin[Format]

struct qelem * elem = NULL;
status = sms_query("query handle", num_args, args, StoreInfo, &elem);

@End[Format]

The variable elem must initially be set to NULL, or @b[StoreInfo()]
will try to add the first element of this queue to a currently
existing queue that is really just the garbage left on the stack.  The
value of elem when this query returns is the last element in the
queue, and the function @b[QueueTop()] (section @ref(queuetop)) should
be used to get the top element of the queue, which is expected by most
other queue operations.

@Center(________)

@Index(FreeQueue)

To free the queue and any data associated with it, the function
@b[FreeQueue()] is used.  This function expects a queue similar to the
one created by the @b[StoreInfo()] function.  @b[FreeQueue()] frees
all memory associated with this particular queue, and will do bad
things if the information stored in the queue is not a NULL
terminated, allocated array of allocated strings.  All allocations
are assumed to have been done with the @i[C] malloc() function.

@begin(Format)

void
FreeQueue(@i[elem])
struct qelem * @i[elem];
@end(Format)
@begin(Description)

@i[elem] @\Any element in the queue.  The entire queue will be freed
regardless of the element passed.
@end(Description)

@Section[Queue Manipulation]

The following group of functions perform common tasks that involve
retrieving information from or manipulating a queue.  All these functions
will handle NULL passed to them in an appropriate manner, either returning
zero, or NULL.

@Label(queuetop)
@Index(QueueTop)

To find the top element of a queue use the function @b[QueueTop()].  

@begin(Format)

struct qelem * 
QueueTop(@i[elem])
struct qelem * @i[elem];
@end(Format)
@begin(Description)
@i[elem] @\Any element in the queue.
@end(Description)

@blankspace(1 line)

@b[QueueTop()] returns the top element of the queue defined by its
argument.

@Center(________)


@label(queuecount)
@Index(QueueCount)

The function @b[QueueCount()] counts the number of elements in a queue.

@begin(Format)
int
QueueCount(@i[elem])
struct qelem * @i[elem];
@end(Format)
@begin(Description)
@i[elem] @\Any element in the queue.
@end(Description)

@blankspace(1 line)

@b[QueueCount()] returns the number of elements in the queue defined
by it argument, it need not be the top element, any
element will do.

@Section[Array Manipulation]

The arrays of strings have some manipulation functions of their own.
All arrays used in the @i[SMS Client] are assumed to be NULL terminated. 
In general this is no problem as @b[StoreInfo()] creates NULL terminated
arrays.

@Label(countargs)
@Index(CountArgs)

@b[CountArgs()] counts the number of elements in a NULL
terminated array of strings.

@begin(Format)
int
CountArgs(@i[info])
char ** @i[info];
@end(Format)
@begin(description)
info @\A NULL terminated array of character strings.
@end(description)

@blankspace(1 line)

@b[CountArgs()] returns the number of strings in the NULL terminated array
of strings passed to it.  A common use of @b[CountArgs()] is to tell
sms_query() how many items are in the argument list passed to the
query, as shown below.
@begin(format)

status = sms_query("query", CountArgs(args), args, StoreInfo, &elem);
@end(format)

@Center(________)
@Index(FreeInfo)

To free all elements in a NULL terminated array of strings use the 
@b[FreeInfo()] function.

@begin(Format)
void
FreeInfo(@i[info])
char ** @i[info];
@end(Format)

@begin(Description) 

@i[info] @\A NULL terminated array of strings allocated with malloc(),
to be freed.
@end(Description)

@Center(________)


@Label(sinn)
@Index(SlipInNewName)

Most update queries use the same argument list that is returned by the
information query for the database item is question.  For instance,
update_list uses almost the same arguments that get_list_info returns.
The difference in these arguments lists is that update queries expect
the new name to be the second element in the list.  The
@b[SlipInNewName()] function adds the new name as the second argument
in the list passed to it, bumping all the other arguments down one
slot.

@begin(Format)
void
SlipInNewName(@i[info, name])
char ** @i[info];
char * @i[name];
@end(Format)

@begin(Description)
@i[info] @\A NULL terminated array of strings.

@i[name] @\The new name to be slipped into @i[info] at position @i[info][1].
@end(Description)

@Blankspace(1 line)

Once @b[SlipInNewName()] has been performed on a argument list do not
try to use the definitions for that type of element,  as they will be
wrong.  Don't use @i[info][L_ACE] to try to retrieve the ace of the
list, as this function has shifted all the items except the old name
down one element.

@Section[Special Queue Functions]

The following two functions @b[Loop()] and @b[QueryLoop()] are used
when an operation needs to be performed on every element of the queue.
The difference between these two functions is that @b[QueryLoop()]
presents a method of asking the user if the operation should be
performed, while @b[Loop()] just performs it on each element.

@Index(Loop)
@Index(LoopFunc)

To perform an operation on every element in the queue use the
@b[Loop()] function.

@begin(Format)
void
Loop(@i[top, LoopFunc])
struct qelem * @i[top];
FVoid @i[LoopFunc];
@end(Format)
@begin(Description)
@i[top] @\The top element of the queue.

@i[LoopFunc] @\The function to perform on each element of the queue.
@end(Description)

@blankspace(1 line)

The @i[LoopFunc] has the following format and is performed with the
data of each element in the queue.

@begin(Format)
void
LoopFunc(@i[info])
char ** @i[info];
@end(Format)
@begin(Description)
@i[info] @\The array of strings that is stored as the @i[q_data] of each element
of the queue.
@end(Description)

@blankspace(1 line)

No provision is made for the @i[LoopFunc] function to return a value.

@Center(________)


@Label(qloop)
@Index(QueryLoop)
@Index(PrintFunc)
@Index(QueryLoopFunc)

If you want to query the user before your function is called on each
element of the queue, use the @b[QueryLoop()] routine.

@Begin[Format]
void
QueryLoop(@i[top, PrintFunc, QueryLoopFunc, query_string])
struct qelem * @i[top];
FCharStar @i[PrintFunc];
FVoid @i[QueryLoopFunc];
char * @i[query_string];
@End[Format]
@begin(Description)
@i[top] @\The top element of the queue.

@i[PrintFunc] @\The function that will print information to the user
about each specific element of the queue, its return value is appended
to the query string, and asked as a @b[YesNoQuitQuestion()] (sect.
@ref[ynqquestion]) of the user.

@i[QueryLoopFunc] @\This function is called  if the user answers yes to the
question asked of him, or if there is only one element in the queue.

query_string @\Basic prompt for the @b[YesNoQuitQuestion()] asked of
the user, the string returned by the @i[PrintFunc] is appended to this
string to form the full prompt.
@end(Description)

@blankspace(1 line)

@Index(PrintFunc)

@i[PrintFunc] is called using the data stored in each element of the
queue, unless there is only one element in the queue, in which case
@i[QueryLoopFunc] is called without querying the user. @i[PrintFunc]
has the following format.

@begin(Format)
char *
PrintFunc(@i[info])
char ** @i[info];
@end(Format)
@begin(Description)

@i[info] @\The array of strings that is stored as the @i[q_data] of
each element in the queue.
@end(Description)

@blankspace(1 line)

The @i[PrintFunc] returns a string that is to be appended to the
query_string() of the @b[QueryLoop()] function.  This string is then
used to ask the user if the @i[QueryLoopFunc] should be called.  The
return string from the @i[PrintFunc] is usually just the name of the
item contained in the data field of each queue element.

@Index(QueryLoopFunc)

@i[QueryLoopFunc] is called each time the user answers 'yes' to the
question asked of him, with the current element's information.  The
only exception to this is if the queue has only one element, in which
case @i[QueryLoopFunc] is called immediately with @i[one_item] set to TRUE.
@i[QueryLoopFunc] has this format.

@begin(Format)
void
QueryLoopFunc(@i[info, one_item])
char ** @i[info];
Bool @i[one_item]
@end(Format)
@begin(Description) 

@i[info] @\The array of strings that is stored as the @i[q_data] of
each element in the queue.

@i[one_item] @\This is TRUE if this function is being called without asking
the user, because there is only one item in the queue.
@end(Description)

@blankspace(1 line)

No provision is made for the @b[QueryLoop()] function to return a value.

@b[QueryLoop()] automatically performs the @i[QueryLoopFunc] without
calling the @i[PrintFunc] if there is only one item in the queue, but
the @i[QueryLoopFunc] is alerted to this fact by the @i[one_item]
variable.  If there is more than one item on the queue the the
@i[PrintFunc] is executed, and the user is prompted, as in the
following example.

@begin(format)
@tabset(10)

@b[Source Code:]

QueryLoop(top_of_queue, PrintListInfo, UpdateList, "Would you like to update list");

@tabclear()
@tabset(50, 60)
@b[User Sees:]

List: foo  @\Members: @\35
This list is a group@\GID: @\9803

Would you like to update list foo (y/n/q) [n]: 
@end(format)

This question is asked of the user for every item in the queue, and if
the answer is yes then the @i[QueryLoopFunc] is called.  If the answer
is no then it continues onto the next element, quit drops out of the
loop entirely.

@Index(NullPrint)

The return value of the @i[PrintFunc] is the name of the list in the
above example (i.e. foo).  A simple print function, @b[Nullprint()],
is detailed in section @ref(nullprint). If @b[Nullprint()] were used
in the above example following would be the result:

@blankspace(1 line)

@begin(Format)
Would you like to update list foo (y/n/q) [n]: 
@end(Format)

@Section[Generic @i{SMS} query routines]

These two very common routines are used as sms_query() callbacks.
Using these common routines helps other programmers immediately
identify what type of tuples your query is expected to return.

@Index(Scream)

Use the @b[Scream()] function when you do not expect any tuples to be
returned by the @i[SMS Server], as in an update, add, or delete query.

@begin(Format)
int
Scream()
@end(Format)

A Sample use of this function.

@begin(Format)
status = sms_query("update_list", CountArgs(args), args, Scream, NULL);
@end(Format)

@Center(________)


@Index(NullFunc)

@b[NullFunc()] does nothing at all with the tuples, it just ignores them.

@begin(Format)
int
NullFunc()
@end(Format)

@b[NullFunc()] is useful when you are expecting tuples to be returned,
but are not interested in their contents, I used it when I was testing
for existence of a list by performing a count_members_of_list query
and then testing the return value, but was not interested in the
tuples. A sample use of this @b[NullFunc()].

@begin(Format)
status = sms_query("get_list_info", CountArgs(args), args, NullFunc, NULL);
@end(Format)

@Section[Retrieving User Input]

To keep a consistent user interface across the entire application, the
@i[SMS Client] uses the following functions to handle most user input.
The only other accepted method of retrieving user input is by making
a call to @b[Prompt_input()], which is in the menu library, and the
basis for all these functions.

@Index(PromptWithDefault)

Use @b[PromptWithDefault()] when you need to prompt the user for a value
and you want to provide him with a default value.

@begin(Format)
int
PromptWithDefault(@i[prompt, buf, buflen, def])
char * @i[prompt], * @i[buf];
int @i[buflen];
char * @i[def];
@end(Format)

@begin(Description)
prompt @\The string to prompt the user with.

buf @\The buffer to return the answer in.

buflen @\The length of the return buffer.

def @\The default return value (if NULL then "" is the default).

@End[Description]

@Blankspace( 1 line)

A typical call to this function is as follows:
@begin(format)

@b[Source Code:]

PromptWithDefault("Input a value for the string", buf, BUFSIZ, "foo");

@b[User Sees:]

Input a value for the string [foo]: 

@end(format)

@b[PromptWithDefault()] will return (-1) if the user breaks out of this
prompt with control-C or some other break character.

@Center(________)


@Label(ynquestion)
@Index(YesNoQuestion)

@b[YesNoQuestion()] is used to ask the user a question that is best
answered with a response of either yes or no.  It also gives the user
a default value in the same form as @b[PromptWithDefault()]. 

@begin(Format)
Bool
YesNoQuestion(@i[prompt, bool_def])
char @i[*prompt];
Bool @i[bool_def];
@end(Format)

@begin(Description)
@i[prompt] @\The string to prompt the user with.

@i[bool_def] @\The default value, either TRUE or FALSE.
@end(Description)

@blankspace(1 line)

@b[YesNoQuestion()] returns either TRUE or FALSE, or possibly (-1) on
a break by the user.  The only accepted values from the user are 'y',
'Y', 'n', and 'N'.  All other values prompt the user with "Please
answer 'y' or 'n'." and ask the question again.

@Center(________)


@Label(ynqquestion)
@Index(YesNoQuitQuestion)

Use @b[YesNoQuitQuestion()] when you want the user to have the option to
quit as well as answer yes or no.

@begin(Format)
Bool
YesNoQuitQuestion(@i[prompt, bool_def])
char @i[*prompt];
Bool @i[bool_def];
@end(Format)

@begin(Description)

@i[prompt] @\The string to prompt the user with.

@i[bool_def] @\The default value, either TRUE or FALSE.
@end(Description)

@blankspace(1 line)

@b[YesNoQuitQuestion()] is very similar to @b[YesNoQuestion()], in
fact the only difference is that this function interprets the keys 'q'
and 'Q' the same as hitting a break, causing
@b[YesNoQuitQuestion()] to return (-1).

@Center(________)


@Label(confirm)
@Index(Confirm)

When you want to ask the user for delete confirmation use the @b[Confirm()]
function.

@begin(Format)
Bool
Confirm(@i[prompt])
char * @i[prompt];
@end(Format)
@begin(Description)
@i[prompt] @\The string to prompt the user with.
@end(Description)

@blankspace(1 line)

@b[Confirm()] is a specialized user input function.  This function is
meant to be used only when making delete confirmation.  It will
immediately return TRUE if the verbose global variable (see section
@ref(globals)) is FALSE, implying that no confirmation is wanted.
Otherwise it asks the user a @b[YesNoQuestion()] using its argument as
the prompt, with a default value of FALSE.  If the user answers yes
then the function will return TRUE, otherwise FALSE is returned.

@Center(________)


@Label(gvfu)
@Index(GetValueFromUser)

The @b[GetValueFromUser()] routine is used to update a string that was
previously allocated with malloc().

@begin(format)
int
GetValueFromUser(@i[prompt, pointer])
char * @i[prompt], ** pointer;
@end(format)
@begin(Description)
@i[prompt] @\The string to prompt the user with.

@i[pointer] @\A pointer to the malloced item that is to be updated, or a
pointer to NULL.
@end(Description)

@Blankspace( 1 line )

@b[GetValueFromUser()] returns SUB_ERROR if the user broke out of the
prompt with control-C, else SUB_NORMAL.  The old value is freed,
unless it is NULL, and the new value is allocated and pointed to by
@i[pointer].

@Center(________)


@Index(GetYesNoValueFromUser)

If the value to be updated is an @i[SMS] style boolean ("0" or "1")
then use the the @b[GetYesNoValueFromUser()] function, which asks the
user a @b[YesNoQuestion()].

@begin(Format)
int
GetYesNoValueFromUser(@i[prompt, pointer])
char * @i[prompt], ** @i[pointer];
@end(Format)

@begin(Description)
@i[prompt] @\The string to prompt the user with.

@i[pointer] @\A pointer to the malloced item that is to be updated, or a
pointer to NULL.
@end(Description)

@Blankspace( 1 line)

@b[GetYesNoValueFromUser()] also returns SUB_ERROR if the user broke
out of the prompt with control-C, and SUB_NORMAL otherwise.
@i[pointer] is freed - Unless it is NULL - and reset to point to a
properly allocated value of "0" or "1" depending upon the user's answer
to the question.

@Center(________)


@Index(GetFSTypes)

The @i[SMS Server] expects file types to be passed as an integer bit
mask.  Use the @b[GetFSTypes()] function to query the user about the
new value of this field.

@begin(Format)
int
GetFSTypes(@i[pointer])
char **  @i[pointer];
@end(Format)

@begin(Description) 
@i[pointer] @\A pointer to the current allocated filesystem type that
is to be updated, or a pointer to NULL.
@end(Description)

@Blankspace( 1 line)

@b[GetFSTypes()] returns SUB_ERROR if the user broke out of the prompt
with control-C, and SUB_NORMAL otherwise.  It also constructs the
correct bit field by asking the user about each type, providing a
default value, of course.  The new filesystem type is passed back as
the new value pointed to by @i[pointer].  If non-NULL the old pointer
is freed.

@Section[Printing Routines]

Here are a few general printing routines.  Most printing is done with
routines that are specific to the type of database item we are dealing
with, but these few printing functions are general enough to describe
here.

@Label(print)
@Index(Print)

Use the @b[Print()] function to print out all elements in each tuple 
returned by a call to sms_query().

@begin(Format)
int
Print(@i[argc, argv, callback]);
int @i[argc];
char ** @i[argv], * @i[callback];
@end(Format)

@begin(Description)
@i[argc] @\The number if strings passed in argv.

@i[argv] @\An array of strings that is the data to be stored in the queue.

@i[callback] @\Not Used.
@end(Description)

@blankspace(1 line)

@b[Print()] returns SMS_CONT.  Every time this function is called it
sets the global variable @i[found_some] to TRUE.

@Center(________)

@Label(pbytype)
@Index(PrintByType)

If you want something a bit more specialized then try
@b[PrintByType()].  If a string compare of @i[argv][0] and
@i[callback] shows them to be equal then the tuple is printed.  If
@i[callback] is NULL then it acts exactly like @b[Print()].

@begin(Format)
int
PrintByType(@i[argc, argv, callback]);
int @i[argc];
char ** @i[argv], * @i[callback];
@end(Format)

@begin(Description)
@i[argc] @\The number if strings passed in argv.

@i[argv] @\An array of strings that is the data to be stored in the queue.

@i[callback] @\@i[Argv][0] is compared to this with strcmp() and if
they are equal then the tuple printed.  In the special case where
callback is NULL all tuples are printed.
@end(Description)

@blankspace(1 line)

@b[PrintByType()] returns SMS_CONT.  Every time this function is
called it sets the global variable found_some to TRUE.

@Center(________)


@Index(PrintHelp)

If the help information is stored in a NULL terminated array of strings
then use the @b[PrintHelp()] function to print the information for the user.

@begin(Format)
int
PrintHelp(@i[message])
char ** @i[message];
@end(Format)

@begin(Description)
@i[message] @\The help message stored in a NULL terminated array of strings.
@end(Description)

@blankspace(1 line)

@b[PrintHelp()] returns DM_NORMAL to allow help routines to remain as
simple as possible.

@Center(________)


@Index(NullPrint)
@Label(nullprint)

When using the @b[QueryLoop()] function (sect. @ref(qloop)) there are
times when all the @i[PrintFunc] needs to return is the name of the item, if
the item's name is the first element of the tuple then use the @b[NullPrint()]
function to retrieve this name.

@begin(Format)
char *
NullPrint(@i[info])
char ** @i[info];
@end(Format)
@begin(Description)
info@\ An array of strings.
@end(Description)

@blankspace(1 line)

@b[NullPrint()] returns the first element in the array passed to it,
@i[info][0].

@Section[Miscellaneous Utility Functions]

This section contains the rest of the utility functions, these don't
really fit into a category.

@Index(FreeAndClear)

Use @b[FreeAndClear()] to free a pointer and set it to NULL.

@Begin[Format]
void
FreeAndClear(@i[pointer, free_it])
char ** @i[pointer];
Bool @i[free_it];
@End[Format]

@begin(Description) 

@i[pointer] @\A pointer to the item to free or clear, if this item is
NULL then the function does not try to free it.

@i[free_it] @\This function always sets the item to NULL, but only if
this is TRUE is it freed.

@end(Description)

@Blankspace(1 line)
@Center(________)


@Index(ValidName)

@b[ValidName()] check to see if string is a valid name for a new
item in the database.

@begin(Format)
Bool
ValidName(@i[s])
char * @i[s];
@end(Format)

@begin(Description)
@i[s] @\The string to check.
@end(Description)

@Blankspace( 1 line )

If the name is not valid, @b[ValidName()] alerts the user to the
problems encountered and returns FALSE.  Checks are made to see if
this name contains a wildcard, contains spaces, or has no characters
at all.  This is by no means an exhaustive search, but does handle some
common things that confuse the database.

@Center(________)


@Index(ToggelVerboseMode)

The @b[ToggleVerboseMode()] function is associated with the menu items
that allows the user to toggle the verbose global variable (see sect.
@ref(globals)).

@Center(________)


@Index(Strsave)

Use the @b[Strsave()] function to allocate space for a new string.

@begin(Format)
char *
Strsave(@i[str])
char * @i[str];
@end(Format)

@begin(Description)
@i[str] @\The string that needs to have space allocated for it.
@end(Description)

@Blankspace( 1 line )

@b[Strsave()] returns an allocated copy of the string passed to it.

@Center(________)


@Index(EncryptMITID)
@Index(RemoveHyphens)

To save an MIT ID number two functions are needed, @b[EncryptMITID()] and
@b[RemoveHyphens()].

@begin(Format)
void
EncryptMITID(@i[sbuf], @i[idnumber], @i[first], @i[last])
char * @i[sbuf], * @i[idnumber], * @i[first], * @i[last];
@end(Format)

@begin(Description)
@i[spbuf] @\The buffer used to return the encrypted id number.

@i[idnumber] @\The unencrypted id number.

@i[first, last] @\The name of the user whose id number we are encrypting.

@end(Description)

@Blankspace(1 line)
@Center(________)


@begin(Format)
void
RemoveHyphens(@i[str)]
char * @i[str];
@end(Format)

@begin(Description) 

@i[str] @\This is the string with hyphens to remove, when the function
returns it will no longer have hyphens.
@end(Description)

@Chapter[Function Organization within Files]

The functions within each of the files tend to be quite similar.  I
have organized these so that at the top of the file are all the
general routines for the type of element currently being worked upon.
In @i[attach.c], for example, there is a @b[PrintFSInfo()] function, an
@b[AskFSInfo()] function, and a @b[GetFSInfo()] function.  Each of the
item types will have a file that contains very similar functions, and
all will be grouped near the top of the file.  As an example consider the
general functions in @i[attach.c].

@b[PrintFSInfo()] is used to print out all the information about a filesystem.

@begin(Format)
static char *
PrintFSInfo(@i[info])
char ** @i[info];
@end(Format)

@begin(Description)
@i[info] @\A NULL terminated array of strings that contains all information 
about a filesystem.
@end(Description)

@blankspace(1 line)

@b[PrintFSInfo()] has the same format as @i[PrintFunc] (sect.
@ref(qloop)) and can be used as the @i[PrintFunc] for @b[QueryLoop()]
calls in the functions that update and delete filesystems.

@Center(________)


@b[AskFSInfo()] is used to ask the user to update the current filesystem
information.

@begin(Format)
static char **
AskFSInfo(@i[info, name])
char ** @i[info];
Bool @i[name];
@end(Format)

@begin(Description) 

@i[info] @\A NULL terminated array of malloced strings that contains
all information about a filesystem.  When the function is called this
array contains the default values to present to the user, and when the
function is returned this array contains the argument list for the
update or add query.

@i[name] @\A boolean value that when TRUE tells this function to ask
the user for a new value for the name of this filesystem, and then
slip it into the @i[info] list, using @b[SlipInNewName()] (sect.
@ref(sinn))
@end(Description)

@Blankspace( 1 line )

The return value of @b[AskFSInfo()] is just the @i[info] array after
it has been updated with the new values.  This function is used to
perform most of the information gathering in the update and add
filesystem queries.  For an update query the information returned from
a @i[get_filesys_by_label] can be used for the @i[info] array, but for
an add filesystem, the program must come up with a reasonable set
default values for the @i[info] array.

If the @i[name] flag is set then the @b[SlipInNewName()] function is
called to add a new name to the @i[info] array, this causes the array
indices for most values to be changed and thus the predefined values
used to access the information in this array can no longer be used
(see sections @ref(infodefs) and @Ref(sinn)).

@Center(________)


The @b[GetFSInfo()] function is used to to perform common information
gathering @i[SMS] queries for a filesystem.

@begin(Format)
static struct qelem *
GetFSInfo(@i[type, name])
int @i[type];
char * @i[name];
@end(Format)

@begin(Description)
@i[type] @\The type of filesystem query to make, can be one of;
LABEL, MACHINE, GROUP or ALIAS.  The valid names for @i[type] are 
typedef'ed at the beginning of each file.

@i[name] @\The name to pass as the argument to the query.
@end(Description)

@Blankspace(1 line)

The @b[GetFSInfo()] function is a simple method of making the most
common information gathering @i[SMS] queries, it handles all return
values from the call to sms_query() and returns the top element of a
queue containing all information returned by the server.  If an error
is detected then NULL is returned and an error message is printed to
the user.

After this group of general functions come those that actually
correspond to items in the menu.  These commonly perform get, add,
update, and delete operations.  For more information on the functions
in each of the files see the appendix @ref(fqlist).

@Appendix(The @i[SMS] Query String Array Definitions)

@Label(a_infodefs)

@Begin[Format]

/*
 *     This file contains all definitions that allow easy access to 
 * elements returned by most of the @i[SMS] queries.
 *
 *     If the order of the arguments in the @i[SMS] queries change (again)
 *  then all that needs to be changed are the values of these items 
 * and all should be well, (hopefully :-).
 *
 *                                 Chris D. Peterson - kit@@athena
 *                                   6/8/88
 */

@Begin[Verbatim]

#ifndef _infodefs_
#define _infodefs_

#include "mit-copyright.h"

#define NAME 0

/* get_ace_use */

#define ACE_TYPE 0
#define ACE_NAME 1
#define ACE_END  2

/* alias queries. */

#define ALIAS_NAME  0
#define ALIAS_TYPE  1
#define ALIAS_TRANS 2
#define ALIAS_END   3

/* Cluster information queries */

#define C_NAME       0
#define C_DESCRIPT   1
#define C_LOCATION   2
#define C_MODTIME    3
#define C_MODBY      4
#define C_MODWITH    5
#define C_END        6

/* Cluster Data information queries */

#define CD_NAME      0
#define CD_LABEL     1
#define CD_DATA      2
#define CD_END       3

/* Delete Member from list queries. */

#define DM_LIST      0
#define DM_TYPE      1
#define DM_MEMBER    2
#define DM_END       3

/* Filesys queries */

#define FS_NAME         0
#define FS_TYPE         1
#define FS_MACHINE      2
#define FS_PACK         3
#define FS_M_POINT      4
#define FS_ACCESS       5
#define FS_COMMENTS     6
#define FS_OWNER        7
#define FS_OWNERS       8
#define FS_CREATE       9
#define FS_L_TYPE       10
#define FS_MODTIME      11
#define FS_MODBY        12
#define FS_MODWITH      13
#define FS_END          14

/* Get List Of Member queries. */

#define GLOM_NAME     0
#define GLOM_ACTIVE   1
#define GLOM_PUBLIC   2
#define GLOM_HIDDEN   3
#define GLOM_MAILLIST 4
#define GLOM_GROUP    5
#define GLOM_END      6

/* General List information queries. */

#define L_NAME     0
#define L_ACTIVE   1
#define L_PUBLIC   2
#define L_HIDDEN   3
#define L_MAILLIST 4
#define L_GROUP    5
#define L_GID      6
#define L_ACE_TYPE 7
#define L_ACE_NAME 8
#define L_DESC     9
#define L_MODTIME  10
#define L_MODBY    11
#define L_MODWITH  12
#define L_END      13

/* List Member information queries. */

#define LM_LIST   0
#define LM_TYPE   1
#define LM_MEMBER 2
#define LM_END    3

/* Machine information queries */

#define M_NAME       0
#define M_TYPE       1
#define M_MODTIME    2
#define M_MODBY      3
#define M_MODWITH    4
#define M_END        5

/*  Machine to cluster Mapping */

#define MAP_MACHINE  0
#define MAP_CLUSTER  1
#define MAP_END      2

/*  NFS phys. queries. */

#define NFS_NAME    0
#define NFS_DIR     1
#define NFS_DEVICE  2
#define NFS_STATUS  3
#define NFS_ALLOC   4
#define NFS_SIZE    5
#define NFS_MODTIME 6
#define NFS_MODBY   7
#define NFS_MODWITH 8
#define NFS_END     9

/* PO box information queries */

#define PO_NAME    0
#define PO_TYPE    1
#define PO_BOX     2
#define PO_END     3

/* Quota queries */

#define Q_FILESYS   0
#define Q_LOGIN     1
#define Q_QUOTA     2
#define Q_DIRECTORY 3  
#define Q_MACHINE   4
#define Q_MODTIME   5
#define Q_MODBY     6
#define Q_MODWITH   7
#define Q_END       8

/* User information queries */

#define U_NAME    0
#define U_UID     1
#define U_SHELL   2
#define U_LAST    3
#define U_FIRST   4
#define U_MIDDLE  5
#define U_STATE   6  
#define U_MITID   7
#define U_CLASS   8
#define U_MODTIME 9
#define U_MODBY   10
#define U_MODWITH 11
#define U_END     12

#endif _infodefs_      /* Do not add anything after this line. */

@End[Verbatim]

@End[Format]

@Appendix(List of Menus, Functions, and Query Names)

@Label(fqlist)

@blankspace(2 lines)

The menu tree for the @i[SMS Client] has three levels, the root, tier
1 and tier 2.  A diagram of the menu tree is show in appendix @ref(tree).
The root of the tree is composed of calls to other menus.

@blankspace(2 lines)

@begin(Group)
@Center(Sms Database Manipulation)
@begin(Verbatim)

 1. (cluster)      Cluster Menu.
 2. (filesys)      Filesystem Menu.
 3. (list)         Lists and Group Menu.
 4. (machine)      Machine Menu.
 5. (nfs)          Physical NFS Menu.
 6. (user)         User Menu. 
 t. (toggle)       Toggle logging on and off.
 q. (quit)         Quit.

@end(Verbatim)
@Hinge()
@Section[First Tier Menus]

@Center{ @b[Cluster Menu] }

@begin(Verbatim)

 1. (show)         Get cluster information.
 2. (add)          Add a new cluster.
 3. (update)       Update cluster information.
 4. (delete)       Delete this cluster.
 5. (mappings)     Machine To Cluster Mappings Menu.
 6. (c_data)       Cluster Data Menu.
 7. (verbose)      Toggle Verbosity of Delete.
 r. (return)       Return to previous menu.
 t. (toggle)       Toggle logging on and off.
 q. (quit)         Quit.

@end(Verbatim)

@Begin(Format)
@tabset(20,40,60)
show@\get_cluster
add@\get_cluster@\add_cluster
update@\get_cluster@\update_cluster
delete@\get_cluster@\delete_cluster

@End[Format]
@Hinge()
@Center{ @b[Filesystem Menu] }

@begin(Verbatim)

 1. (get)          Get Filesystem Name Information.
 2. (add)          Add New Filesystem to Database.
 3. (change)       Update Filesystem Information.
 4. (delete)       Delete Filesystem.
 5. (check)        Check An Association.
 6. (alias)        Associate with a Filesystem.
 7. (unalias)      Dissociate from a Filesystem.
 8. (quotas)       Quota Menu.
 9. (verbose)      Toggle Verbosity of Delete.
10. (help)         Help ...
 r. (return)       Return to previous menu.
 t. (toggle)       Toggle logging on and off.
 q. (quit)         Quit.

@end(Verbatim)

@begin(Format)
@tabset(20,40,60)
get@\get_filesys_by_label
add@\get_filesys_by_label@\add_filesys
change@\get_filesys_by_label@\update_filesys
delete@\get_filesys_by_label@\delete_filesys
check@\get_alias
alias@\get_alias@\add_alias
unalias@\get_alias@\delete_alias

@end(Format)
@Hinge()
@Center{ @b[List Menu] }

@begin(Verbatim)

 1. (show)         Display information about a list.
 2. (add)          Create new List.
 3. (update)       Update characteristics of a list.
 4. (delete)       Delete a List.
 5. (query_remove) Interactively remove an item from all lists.
 6. (members)      Member Menu - Change/Show Members of a List.
 7. (list_info)    List Info Menu.
 8. (quotas)       Quota Menu.
 9. (verbose)      Toggle Verbosity of Delete.
10. (help)         Print Help.
 r. (return)       Return to previous menu.
 t. (toggle)       Toggle logging on and off.
 q. (quit)         Quit.

@end(Verbatim)

@begin(Format)
@tabset(20,40,60)
show@\get_list_info
add@\get_list_info@\add_list
update@\get_list_info@\update_list
delete@\get_list_info@\get_ace_use
@\count_members_of_list@\get_lists_of_member
@\get_members_of_list@\delete_list
@\delete_member_from_list
query_remove@\get_lists_of_member@\delete_member_from_list

@end(Format)
@Hinge()
@Center{ @b[Machine Menu] }

@begin(Verbatim)

 1. (show)         Get machine information.
 2. (add)          Add a new machine.
 3. (update)       Update machine information.
 4. (delete)       Delete this machine.
 5. (mappings)     Machine To Cluster Mappings Menu.
 6. (verbose)      Toggle Verbosity of Delete.
 r. (return)       Return to previous menu.
 t. (toggle)       Toggle logging on and off.
 q. (quit)         Quit.

@end(Verbatim)

@begin(Format)
@tabset(20,40, 60)
show@\get_machine
add@\get_machine@\add_machine
update@\get_machine@\update_machine
delete@\get_machine@\delete_machine

@end(Format)

Delete machine needs to make some other checks. For lack of time these
have not been implemented.
@begin(Itemize)
@tabset(45)
Is the machine a server@\get_server_host_info

Is the machine an NFS server@\get_nfsphys

Are there any ACE's that this machine references@\get_server_host_access
@end(Itemize)

@Hinge()

@Center{ @b[NFS Physical Menu] }

@begin(Verbatim)

 1. (show)         Show an NFS server.
 2. (add)          Add NFS server.
 3. (update)       Update NFS server.
 4. (delete)       Delete NFS server.
 5. (quotas)       Quota Menu.
 6. (verbose)      Toggle Verbosity of Delete.
 r. (return)       Return to previous menu.
 t. (toggle)       Toggle logging on and off.
 q. (quit)         Quit.

@end(Verbatim)

@begin(Format)
@tabset(20,40, 60)
show@\get_nfsphys
add@\get_nfsphys@\add_nfsphys
update@\get_nfsphys@\update_nfsphys
delete@\get_nfsphys@\get_filesys_by_nfsphys
@\delete_nfsphys

@end(Format)
@Hinge()
@Center{ @b[User Menu] }

@begin(Verbatim)

 1. (login)        Show user information by login name.
 2. (name)         Show user information by name.
 3. (class)        Show names of users in a given class.
 4. (modify)       Change all user fields.
 5. (adduser)      Add a new user to the database.
 6. (register)     Register a user.
 7. (delete)       Delete user.
 8. (udelete)      Delete user by uid.
 9. (pobox)        Post Office Box Menu.
10. (quota)        Quota Menu.
11. (verbose)      Toggle Verbosity of Delete.
 r. (return)       Return to previous menu.
 t. (toggle)       Toggle logging on and off.
 q. (quit)         Quit.

@end(Verbatim)

@begin(Format)
@tabset(20,40, 60)
login@\get_user_by_login
name@\get_user_by_name
class@\get_user_by_class
modify@\get_user_by_login@\update_user
adduser@\add_user
register@\get_user_by_name@\register_user
@\Kerberos server is queried also.
delete@\delete_user@\get_filesys_by_label
@\delete_filesys@\get_members_of_list
@\delete_list@\count_members_of_list
@\get_lists_of_member@\delete_member_from_list
@\get_list_info@\get_ace_use
udelete@\get_user_by_uid@\(plus all of the above).

@end(Format)
@Hinge()
@Section[Second Tier Menus]

@Center{ @b[Cluster Data Menu] }

@begin(Verbatim)

 1. (show)         Show Data on a given Cluster.
 2. (add)          Add Data to a given Cluster.
 3. (delete)       Remove Data to a given Cluster.
 4. (verbose)      Toggle Verbosity of Delete.
 r. (return)       Return to previous menu.
 t. (toggle)       Toggle logging on and off.
 q. (quit)         Quit.

@end(Verbatim)

@begin(Format)
@tabset(20,40, 60)
show@\get_cluster_data
add@\add_cluster_data
delete@\get_cluster_data@\delete_cluster_data

@end(Format)
@Hinge()
@Center{ @b[List Information Menu] }

@begin(Verbatim)

 1. (member)       Show all lists to which a given member belongs.
 2. (admin)        Show all items which a given member can administer.
 3. (groups)       Show all lists which are groups.
 4. (public)       Show all public mailing lists.
 5. (maillists)    Show all mailing lists.
 r. (return)       Return to previous menu.
 t. (toggle)       Toggle logging on and off.
 q. (quit)         Quit.

@end(Verbatim)

@begin(Format)
@tabset(20,40, 60)
member@\get_lists_of_member
admin@\get_ace_use
groups@\qualified_get_lists
public@\qualified_get_lists
maillists@\qualified_get_lists

@end(Format)
@Hinge()
@Center{ @b[Mappings Menu] }

@begin(Verbatim)

 1. (map)          Show Machine to cluster mapping.
 2. (addcluster)   Add machines to a clusters.
 3. (remcluster)   Remove machines from clusters.
 4. (verbose)      Toggle Verbosity of Delete.
 r. (return)       Return to previous menu.
 t. (toggle)       Toggle logging on and off.
 q. (quit)         Quit.

@end(Verbatim)

@begin(Format)
@tabset(20,40, 60)
map@\get_machine_to_cluster_map
addcluster@\get_machine@\get_cluster
@\add_machine_to_cluster
remcluster@\get_machine_to_cluster_map
@\delete_machine_from_cluster

@end(Format)
@Hinge()
@Center{ @b[Membership Menu] }

@begin(Verbatim)

 1. (add)          Add a member to this list.
 2. (remove)       Remove a member from this list.
 3. (all)          Show the members of this list.
 4. (user)         Show the members of type USER.
 5. (list)         Show the members of type LIST.
 6. (string)       Show the members of type STRING.
 7. (verbose)      Toggle Verbosity of Delete.
 r. (return)       Return to previous menu.
 t. (toggle)       Toggle logging on and off.
 q. (quit)         Quit.

@end(Verbatim)

@begin(Format)
@tabset(20,40, 60)
add@\add_member_to_list
remove@\delete_member_from_list
all@\get_members_of_list
user@\get_members_of_list
list@\get_members_of_list
string@\get_members_of_list

@end(Format)
@Hinge()
@Center{ @b[Post Office Box Menu] }

@begin(Verbatim)

 1. (show)         Show a user's post office box.
 2. (set)          Set (Add or Change) a user's post office box.
 3. (remove)       Remove a user's post office box.
 4. (verbose)      Toggle Verbosity of Delete.
 r. (return)       Return to previous menu.
 t. (toggle)       Toggle logging on and off.
 q. (quit)         Quit.

@end(Verbatim)

@begin(Format)
@tabset(20,40, 60)
show@\get_pobox
set@\get_pobox@\get_server_locations
@\set_pobox_pop@\set_pobox
remove@\delete_pobox

@end(Format)
@Hinge()
@Center{ @b[Quota Menu] }

@begin(Verbatim)

 1. (shdef)        Show default user quota (in KB).
 2. (chdef)        Change default user quota.
 3. (shquota)      Show a user's disk quota on a filesystem.
 4. (addquota)     Add a new disk quota for user on a filesystem.
 5. (chquota)      Change a user's disk quota on a filesystem.
 6. (rmquota)      Remove a user's disk quota on a filesystem.
 7. (verbose)      Toggle Verbosity of Delete.
 r. (return)       Return to previous menu.
 t. (toggle)       Toggle logging on and off.
 q. (quit)         Quit.

@end(Verbatim)

@begin(Format)
@tabset(20,40, 60)
shdef@\get_value
chdef@\update_value@\get_value
shquota@\get_nfs_quota
addquota@\add_nfs_quota
chquota@\get_nfs_quota@\update_nfs_quota
rmquota@\get_nfs_quota@\delete_nfs_quota
@end(Format)
@end(Group)

@Appendix(Menu Tree Diagram)

@Label(tree)
