@Comment{$Header: techapp.mss,v 1.2 88/08/09 11:30:48 jtkohl Exp $} @Comment{$Source: /mit/zephyr/doc/techplan/RCS/techapp.mss,v $} @Comment{$Author: jtkohl $} @Comment{$Revision: 1.2 $} @Comment{Copyright 1988 Massachusetts Institute of Technology} @Part[techapp, Root "zephyr.mss"] @Begin(Comment) Random Editorial Comments --------------------------------------------------- ----------------------------------------------------------------------------- @End(Comment) @Process(PageHeadings) @Append{Appendix@YImplementation Specifications} @Label(implspecs) This section discusses implementation details of the current implementation in use at Project Athena. @Section(Server implementation) @cbon() WARNING: This section on Server implementation is out of date with respect to Zephyr Servers version 3.0 and higher. The Zephyr Server is comprised of several subsystems, described below. @define(progexample, Use ProgramExample, above 2, below 0) @tabclear() @tabset(1 inch, 4 inches) @define(arg, Facecode F, BeforeEntry "``", AfterExit "''") @define(func, Facecode F, BeforeEntry "The ", AfterExit " routine") @SubSection(Server Subsystem) The ZEPHYR_ADMIN class messages and server-to-server maintenance (such as updates of subscriptions, acknowledgement, HELLO's and FSM state transitions) are handled by the Server subsystem. External routines are: @begin(progexample) void server_init() @end(progexample) @func[server_init] initializes the table of known servers, marking each one of them DEAD, and setting an immediate timeout to cause transmission of a HELLO packet to each of those servers. The table is initialized by either querying @i(Hesiod) for @t[zephyr.sloc] entries (which are the hostnames of hosts running @value(name) servers) or by consulting a file containing a list of hostnames, one per line. The choice of which way to retrieve the server names is decided by a compile-time option. @begin(progexample) void server_shutdown() @end(progexample) @func[server_shutdown] informs all the other known servers that this server is going down. @begin(progexample) void server_timo(which) @\ZServerDesc_t *which; @end(progexample) @func[server_timo] handles an expired timer for the server described by @arg[which], changing the state as appropriate (see Figure @ref(fsm)) and sending a HELLO if appropriate. @begin(progexample) void server_dispatch(notice, auth, who) @\ZNotice_t *notice; @\int auth; @\struct sockaddr_in *who; @end(progexample) @func[server_dispatch] is called to process @arg[notice] which was received from another server. @arg[auth] is set if the received packet was authenticated. @arg[who] is the sender of this notice. @begin(progexample) void server_recover(client) @\ZClient_t *client; @end(progexample) @func[server_recover] initiates the HostManager contact protocol (described above) for @arg[client]. It is used when @arg[client] is deemed not responding. @begin(progexample) void server_adispatch(notice, auth, who, server) @\ZNotice_t *notice; @\int auth; @\struct sockaddr_in *who; @\ZServerDesc_t *server; @end(progexample) @func[server_adispatch] dispatches a ZEPHYR_ADMIN notice that was not transmitted by another server. @begin(progexample) void server_forward(notice, auth, who) @\ZNotice_t *notice; @\int auth; @\struct sockaddr_in *who; @end(progexample) @func[server_forward] forwards @arg[notice] to the other servers. @begin(progexample) ZServerDesc_t *server_which_server(who) @\struct sockaddr_in *who; @end(progexample) @func[server_which_server] returns the server descriptor of the server at address @arg[who], or NULLZSDT if there is no server at that address. @begin(progexample) void server_kill_clt(client) ZClient_t *client; @end(progexample) @func[server_kill_clt] informs the other servers that the client has not acknowledged notices and is considered dead. @begin(progexample) void server_dump_servers(fp) @\FILE *fp; @end(progexample) @func[server_dump_servers] prints a representation of the known server table to the I/O stream @arg[fp]. @SubSection(Client Subsystem) The Client Subsystem registers and de-registers clients as directed by notices. External routines are: @begin(progexample) Code_t client_register(notice, who, client, server) @\ZNotice_t *notice; @\struct sockaddr_in *who; @\ZClient_t **client;@\/* RETURN */ @\ZServerDesc_t *server; @end(progexample) @func[client_register] registers the client which originated @arg[notice]. The authoritative server for the client is set to @arg[server]. It returns a pointer to the client description in @arg[client]. It returns ENOMEM in case of malloc() failure, EINVAL if the server host list is corrupted, ZSRV_HNOTFOUND if the HostManager for the client's host cannot be located, and ZERR_NONE otherwise. @begin(progexample) void client_deregister(client,host) @\ZClient_t *client; @\ZHostList_t *host; @end(progexample) @func[client_deregister] deregisters the client @arg[client] at host @arg[host]. This action includes canceling all of the clients' subscriptions and removing any packets unacknowledged by the client from the retransmit queue (so as not to waste server resources on dead clients). @begin(progexample) ZClient_t *client_which_client(who, notice) @\struct sockaddr_in *who; @\ZNotice_t *notice; @end(progexample) @func[client_which_client] returns the client originating the notice @arg[notice] or NULLZCNT if there is no such client. @begin(progexample) void client_dump_clients(fp, clist) @\FILE *fp; @\ZClientList_t *clist; @end(progexample) @func[client_dump_clients] prints a representation of each of the clients in the linked list @arg[clist] to the I/O stream @arg[fp]. @SubSection(Subscription subsystem) The subscription subsystem maintains subscription lists. It adds, deletes, and matches subscriptions. It handles ZEPHYR_CTL class messages with opcodes SUBSCRIBE, CANCEL, and CLEARSUB. The subscription subsystem uses the @f[server_forward] routine to exchange subscription information with other servers as appropriate. External routines are: @begin(progexample) Code_t subscr_subscribe(sin,notice) @\struct sockaddr_in *sin; @\ZNotice_t *notice; @end(progexample) @func[subscr_subscribe] enters the subscription described by @arg[notice] for the client at address @arg[sin] into the internal storage. It returns ENOMEM in case of malloc() failure, ZSRV_NOCLT if the originating client is not registered, and ZERR_NONE otherwise. @begin(progexample) Code_t subscr_cancel(sin, notice) @\ZNotice_t *notice; @end(progexample) @func[subscr_cancel] deletes the subscription for the client at address @arg[sin] described by @arg[notice]. If the client is not registered, it returns ZSRV_NOCLT. If there is no such subscription, it returns ZSRV_NOSUB. Otherwise it returns ZERR_NONE. @begin(progexample) void subscr_cancel_client(client) @\ZClient_t *client; @end(progexample) @func[subscr_cancel_client] cancels all the subscriptions associated with @arg[client]. @begin(progexample) Code_t subscr_cancel_host(addr) @\struct in_addr *addr; @end(progexample) @func[subscr_cancel_host] cancels all the subscriptions associated with clients on the host with IP address @arg[addr]. If the host is unknown, it returns ZSRV_HNOTFOUND. Otherwise it returns ZERR_NONE. @begin(progexample) ZClientList_t *subscr_match_list(notice) ZNotice_t *notice; @end(progexample) @func[subscr_match_list] returns a linked list@foot{Routines that return linked lists in the style of @b[insque](3) return a pointer to the header of the list which contains no data. This special header makes pointer bookkeeping easier.} of clients subscribing to the notice @arg[notice], or NULLZCLT if no clients are subscribed to this notice. @begin(progexample) void subscr_free_list(list) ZClientList_t *list; @end(progexample) @func[subscr_free_list] frees the resources associated with the list @arg[list] which was returned by a call to @f[subscr_match_list()]. @begin(progexample) void subscr_sendlist(notice, auth, who) @\ZNotice_t *notice; @\struct sockaddr_in *who; @end(progexample) @func[subscr_sendlist] sends a list of the current subscriptions for the client associated with @arg[notice] as the message portion of the acknowledgement of @arg[notice] to the client @arg[who]. If there are no subscriptions, a null list is sent. If there are subscriptions, @arg[auth] must be set or the request is refused. @begin(progexample) void subscr_send_subs(client) @\ZClient_t *client; @end(progexample) @func[subscr_send_subs] sends notices containing all of @f[client]'s subscriptions to the brain-dump peer. @begin(progexample) void subscr_dump_subs(fp, subs) @\FILE *fp; @\ZSubscr_t *subs; @end(progexample) @func[subscr_dump_subs] prints a representation of the subscriptions in the structure @arg[subs] to the I/O stream @arg[fp]. @SubSection(Host Subsystem) @tag(hmsec) The Host subsystem deals with the HostManager clients. External routines are: @begin(progexample) void hostm_dispatch(notice, auth, who, server) @\ZNotice_t *notice; @\int auth; @\struct sockaddr_in *who; @end(progexample) @func[hostm_dispatch] is called to process @arg[notice] which was received from a HostManger client and indicates a HostManager function. @begin(progexample) void hostm_flush(host, server) @\ZHostList_t *host; @\ZServerDesc_t *server; @end(progexample) @func[hostm_flush] flushes all information stored about @arg[host] (whose authoritative server is @arg[server]) and any clients on @arg[host]. @begin(progexample) void hostm_transfer(host, server) @\ZHostList_t *host; @\ZServerDesc_t *server; @end(progexample) @func[hostm_flush] transfers ownership of the host @arg[host] to @arg[server]. @begin(progexample) ZHostList_t *hostm_find_host(addr) @\struct in_addr *addr; @end(progexample) @func[hostm_find_host] returns a pointer to the host structure describing the host @arg[addr], or NULLZHLT if there is no such structure. @begin(progexample) ZServerDesc_t *hostm_find_server(addr) @\struct in_addr *addr; @end(progexample) @func[hostm_find_server] returns the server descriptor for the server which ``owns'' the client at @arg[addr], or NULLZSDT if no such server is known. @begin(progexample) void hostm_shutdown() @end(progexample) @func[hm_shutdown] notifies all ``owned'' clients that this server is shutting down. @begin(progexample) void hostm_losing(client, host) @\ZClient_t *client; @\ZHostList_t *host; @end(progexample) @func[hostm_losing] pings the hostmanager described by @arg[host] and sets a timeout after which the host will be considered dead. This is called when a client does not acknowledge notices for an extended period of time. @begin(progexample) void hostm_deathgram(sin, server) @\struct sockaddr_in *sin; @\ZServerDesc_t *server; @end(progexample) @func[hostm_deathgram] tells the hostmanager to seek another server. If @arg[server] is non-null, the hostmanager is hinted to try using the server described by @arg[server] as its new server. Another server will only be suggested if it is the UP state of the FSM. The hints are randomly chosen from the others servers in the UP state, so that the loading of the servers will be roughly equal. @begin(progexample) void hostm_dump_hosts(fp) @\FILE *fp; @end(progexample) @func[hostm_dump_hosts] prints a representation of all the hosts ``owned'' by this server to the I/O stream @arg[fp]. @SubSection(Class Subsystem) The Class subsystem maintains classes and associates clients with the classes to which they subscribe. The classes are stored in a hash table, with the contents of each hash bucket linked together in the style of @b[insque](). It is used primarily by the subscription subsystem. Routines provided for external use are: @begin(progexample) Code_t class_register(client, subs) @\ZClientDesc_t *client; @\ZSubscr_t *subs; @end(progexample) @func[class_register] associates the client described by @arg[client] with the class of @arg[subs]. If that class has not yet been seen by the server, it is added to the hash table of known classes. It returns ENOMEM on malloc() failure, and ZERR_NONE otherwise. @begin(progexample) Code_t class_deregister(client, subs) @\ZClientDesc_t *client; @\ZSubscr_t *subs; @end(progexample) @func[class_deregister] disassociates the client @arg[client] from the class of @arg[subs]. If this was the last client associated with that class, the entry in the hash table is removed. If the class is not registered or @arg[client] is not associated with it, @f[class_deregister] returns ZSRV_BADASSOC. @begin(progexample) ZClientList_t *class_lookup(subs) @\ZSubscr_t *subs; @end(progexample) @func[class_lookup] returns a linked list of the clients associated with the class of @arg[subs], or NULLZCLT if that class is unknown. @begin(progexample) void *class_free(lyst) @\ZClientList_t *lyst; @end(progexample) @func[class_free] frees the storage used by @arg[lyst]. @arg[lyst] should be the return value of a previous call to @f[class_lookup](). @begin(progexample) Code_t class_setup_restricted(class, acl) @\char *class; @\ZAcl_t *acl; @end(progexample) @func[class_setup_restricted] registers @arg[class] and associates the access control list @arg[acl] with it. It returns ZSRV_CLASSXISTS if @arg[class] is already in use, ENOMEM in case of malloc() failure, and ZERR_NONE otherwise. It can be used to restrict a class without associating any client with it, such as during initialization. @begin(progexample) int class_is_admin(notice) int class_is_control(notice) int class_is_hm(notice) int class_is_ulogin(notice) int class_is_ulocate(notice) @\ZNotice_t *notice @end(progexample) These functions return 1 if the class of @arg[notice] is the administrative class, the control class, the HostManager class, the Login class, or the User Locator class, respectively, and 0 otherwise. @begin(progexample) ZAcl_t *class_get_acl(class) @\char *class; @end(progexample) @func[class_get_acl] returns the ACL associated with the class @arg[class], or NULLZACLT if there is no such ACL. @SubSection(Access Control Subsystem) The Access Control subsystem reads access control lists for registered classes, and grants access to these classes. The access control lists are provided and maintained externally (The Service Management System could be used to maintain these lists.). External routines are: @begin(progexample) int access_check(notice, acl, accesstype) @\ZNotice_t *notice; @\ZAcl_t *acl; @\ZAccess_t accesstype; @end(progexample) @func[access_check] returns 1 if the client sending @arg[notice] is granted @arg[accesstype] access by @arg[acl], and 0 if the client is not granted the access. @arg[accesstype] is SUBSCRIBE, TRANSMIT, INSTWILD, or INSTUID. @begin(progexample) int access_init() @end(progexample) @func[access_init] initializes the registered classes from the class registry. @SubSection(Brain-dump subsystem) The Brain-dump subsystem controls and generates ``brain-dumps'' for initializing peer servers when connection is established. External routines are: @begin(progexample) void bdump_offer(who) @\strut sockaddr_in *who; @end(progexample) @func[bdump_offer] creates a TCP socket, listens for a connection on it, and then informs the server at @arg[who] that a dump is available from the bound port. It sets a timer to close the socket if the brain-dump remains unused. @begin(progexample) void bdump_send() @end(progexample) @func[bdump_send] accepts a pending connection on the brain-dump TCP socket, and starts sending state to the peer server. @begin(progexample) void bdump_get(notice, auth, who, server) @\ZNotice_t *notice; @\int auth; @\struct sockaddr_in *who; @\ZServerDesc_t *server; @end(progexample) @func[bdump_get] processes @arg[notice] which indicates a brain-dump available from @arg[server], connects to the brain-dump TCP socket and initiates the transfer. @begin(progexample, TabExport False) @tabclear() @tabset(1 inch) Code_t bdump_send_list_tcp(@^kind, port, class, inst, opcode, @\@\sender, recip, lyst, num) @\ZNotice_Kind_t kind; @\u_short port; @\char *class, *inst, *opcode, *sender, *recip; @\char *lyst[]; @\int num; @tabclear() @tabset(1 inch, 4 inches) @end(progexample) @func[bdump_send_list_tcp] sends a notice over the TCP brain-dump socket with message body composed of @arg[num] strings found in @arg[lyst], and header fields as specified by the other arguments. @SubSection(User Locator subsystem) The User Locator subsystem maintains an internal table of user locations, directed by notices received from clients. Each location has an IP address and port associated with it. This address/port pair is used for flushing invalid locations. External routines are: @begin(progexample) void ulocate_dispatch(notice, auth, who, server) @\ZNotice_t *notice; @\int auth; @\struct sockaddr_in *who; @\ZServerDesc_t *server; @end(progexample) @func[ulocate_dispatch] processes a user locator notice @arg[notice], returning the requested location (if the opcode is LOCATE). @begin(progexample) void ulogin_dispatch(notice, auth, who, server) @\ZNotice_t *notice; @\int auth; @\nstruct sockaddr_in *who; @\ZServerDesc_t *server; @end(progexample) @func[ulogin_dispatch] processes a user login or logout notice. If the opcode is USER_FLUSH, all locations of the user are flushed from the table. If the opcode is OPSTAFF, REALM-VISIBLE, REALM-ANNOUNCED, NET-VISIBLE, or NET-ANNOUNCED, the user is added to the table with the exposure level indicated by the opcode. If appropriate, login notices are transmitted to subscribing clients. If the opcode is NONE, the user's location is removed from the table. @begin(progexample) void uloc_hflush(addr) @\struct in_addr *addr; @end(progexample) @func[uloc_hflush] flushes all locations at the IP address @arg[addr]. It is used only when a hostmanager indicates that it has restarted (via a FLUSH or BOOT message as described above in Section @ref(hmsec)). @begin(progexample) void uloc_send_locations(host, vers) @\ZHostList_t *host; @\char *vers; @end(progexample) @func[uloc_send_locations] sends notices to register all users on @arg[host] to the brain-dump peer. @arg[vers] is the @value(name) protocol version number in use by the brain-dump peer. If necessary, compatibility code will send different format data to the peer based on the version numbers. @begin(progexample) void uloc_flush_client(sin) @\struct sockaddr_in *sin; @end(progexample) @func[uloc_flush_client] flushes all locations with address/port pairs matching that specified by @arg[*sin]. @begin(progexample) void uloc_dump_locs(fp) @\FILE *fp; @end(progexample) @func[uloc_dump_locs] prints a representation of the location table to the I/O stream @arg[fp]. @cboff() @Section(WindowGram Implementation specifications) @cbon() WARNING: This section on the WindowGram subsystems is horribly outdated. @section{WindowGram Display Subsystem} @subsection{Textual Interface} It is necessary to provide a fallback system to provide reasonable action for users without X running. This is provided through a simple textual interface that duplicates most of the capabilities of the X interface. The text is printed, with optional beep, on the controlling terminal. A seperate utility can be used to store and acknowledge messages, since it would be difficult to grab control of the terminal when a message arrives. Text browser interfaces (such as @i(discuss), @i(notes), and @i(rn)) are difficult to get right, although there are many examples. This is a low priority task. @subsection{Graphic Notice Delivery} With X running, the notices are delivered onto the screen so as to grab the users attention within user-defined limits. Note that all graphic featues of the interface are controlled through the standard X Version 11 resource specifications and WindowGram Description files. @b(Quiet Mode) In Quiet Mode the only manifestation of the WindowGram Client that the user sees is a small icon somewhere. When a notice arrives, the icon attracts the user's attention by some combination of the following user configurable methods: @begin(itemize) Flashing (reversing colors) Beeping Raising itself to the top of the window stack Incrementing a counter (displayed as part of the icon) of pending notices @end(itemize) This allows the user to avoid the distraction of otherwise important notices. Any mouse click in the icon window will bring up the browser and any pending notices. @b(Normal Mode) Normally the message appears in the WindowGram area. All buttons appear, allowing the user to respond to them. In addition to the programmed buttons for the specific message type, every window has the default buttons @i(Save) (or @i(Keep)) and @i(Delete) (or @i(Destroy), @i(Remove)). Each window also has standard mouse cut-to-x-buffer enabled. @subsection{Multiple Notice Arrival}@comment{This should not be above Text...} As messages arrive, they are queued. As queued messages are dealt with by the user, they disappear or are saved. Dealing with a current message causes another queued message to become current. Queued messages are stacked, like a deck of cards, with the current one on "top" and the others stacked beneath. Although the direction of stacking is arbitrary, enough of each hidden window should be visible to display a summary line describing the notice. @comment{ Tony noted here: What happens when a machine crash occurs?} @comment{My immediate answer: the windowgram will have crashed anyhow,} @comment{by virtue of X crashing, right? So we can't help. Maybe I} @comment{don't quite get the question...-mwe} As the queue gets large (3-5 messages or more) the latest messages, rather than taking up more screen space, display a summary line saying something like @b(--- 3 more messages ---) on the rearmost pane. As messages are removed from the front of the queue, this number is decremented, and when only one message is there the message itself is displayed. @subsection{Notice Storage and Retrieval} So far it has been assumed that as messages appear, the user deals with them and they go away. If the user is away from the terminal, or the messages arrive too fast for the user to deal with, they should be handled in a reasonable way. There will be three types of messages, @i(queued), @i(current), and @i(saved). @i(Saved) messages are those that have arrived, and the user has looked at and pushed the @i(save) button in order to deal with it later. @i(queued) messages are those that have arrived recently and have not been seen by the user. Queued messages do not have active buttons, since they are not completely visible, and we assume the user hasn't actually seen them yet. The @i(current) message is the one that is visible at the moment, and has all of its buttons active for the user to deal with it. If the user saves a notice, it goes to the save queue. For simplicity, the save queue can simply be displayed at the bottom of the input queue. The fundamental differences are that there is an index page of saved messages (since there are more likely to be a large number of saved messages than queued messages.) The index will include a button box of message types (and ALL for any type) and a window of summary lines. Either a scroll bar or up/down buttons will move through this if more than a given number of entries appear. If the user calls up a notice from the save queue, it appears in front of the save index with the @i(save) button changed to @i(return) or something similar. That button will return the message to the index, and @i(delete) will delete it from the index. @subsection{Interaction between Browser and Input Queue} Arriving notices always assume priority over the browser, so that if the browser is in the same stack as the input queue, notices will appear on top of it to catch the user's attention. The notices will be placed so as not to obscure the active area (buttons of the browser) completely to avoid an unexpected disposition of an incoming notice. @Section(WindowGram Description File) The WindowGram client description file is used to tell the WindowGram client how to act when various classes of notices are received. The descriptions are written in the WindowGram Command Language. The language allows different actions to be taken depending upon the class of the notice, and allows the user to take advantage of many of the features of The X Window System. A complete grammar for the WindowGram Command Language will be specified at a later date. A sample description file might look like: @Begin(ProgramExample, size -2) # Sample description file does $auth match Yes set aval Authentic endmatch match No set aval UNAUTHENTIC endmatch match Forged set aval FORGED endmatch enddoes does $class match MESSAGE # Incoming zwrite messages # Ignore zwrite pings does $opcode match PING exit endmatch enddoes # Handle normal types of messages set type Instance $instance does $instance match PERSONAL set type Personal endmatch match URGENT set type Urgent endmatch enddoes show @@center(@@bold($aval) $type message at $time on $date:) From: @@bold($sender) $message endshow endmatch # Default message does match show (Authentication: @@bold($aval)) $default endshow endmatch enddoes @End(ProgramExample) @subsection{Partial Language Specification} The main purpose of the WindowGram Language is to provide a method of converting the raw Notice to a form the user can quickly deal with. This can be as simple as highlighting an important field, or as complicated as running a program in a new window. The language should have a very free grammar. This will make it easy for the user to change the standard configuration to something they find more pleasant. The language should be more like BASIC or FORTRAN than PASCAL. Normal users should not be deterred by the complexities of computer science. There should be some way of mapping parts of the notice into keywords so they can be referenced. The fixed header fields can be assigned standard names. The data itself should be assigned names after the message type has been identified. Dividing the body into lines should be sufficient, since anything more complicated would be likewise more complicated for the user to specify. For simplicity, the display specification should be seperate from the flow specification. It would be difficult to construct a language that would be good for both flow and formatting. In fact, a simple display "sub-language" would be a set of Scribe-like commands, like @i(@@center), @i(@@bold), and so on. Anything not recognized as a formatting command or a field name would be displayed as text. This sub-language would include a few primitives for popping up buttons to ask questions of the user, especially "yes/no" questions. The description will also include a summary line, which will not always be visible, and which is describe using commands from the display language. This line will be associated with the actual displayed notice, although it will not necessarily be on-screen at the same time. There will be very little flow structure in this language. Notices are meant to be @i(simple) in and of themselves. There will be a shell escape, possibly opening a window, to perform more complex responses. Loops should not be necessary. The primary construct should be a switch statement, which takes a field and matches it against several possible values. If the field contains the value, the window description following it will be executed. The first match is used, the remainder ignored. Since notices can arrive randomly, the descriptions must run asynchronously. The individual displays can be moved around, saved, or destroyed arbitrarily. This is simple to achieve using the message passing techniques available in the X Toolkit. @Section(The WindowGram Client Control Subsystems) The WindowGram client also includes several programs that allow the user to easily perform tasks other than recieve notices. @subsection{Subscription Manager} A program that allows the user to change and view the classes of notices that he is currently subscribing to. This talks to the server directly, and requests subscriptions on behalf of the WindowGram Client. This program also allows the user to toggle their 'hidden' status in the database. @subsection{WindowGram Description Tester} A program to test WindowGram Language Description files by performing actions based on the descriptions and some sample messages. This could parse the script in an attempt to find common errors, as well as identifying which messages actually have special actions which need to be tested. @subsection{WindowGram Description Installation Tool} A program to install a new WindowGram Description file. This should be robust, so that if the new file fails, the currently active one remains active. @subsection{Command Oriented Interface} A program to provide an input interface to the Ascii Terminal version of the WindowGram client. This should include most of the functions of the X Window System interface, such as Delete, Save, Browse, and Copy to File. @cboff() @Section(Modifications To Existing @c(Unix) Systems) @Label{unix} @Subsection{Syslog} @i(/etc/syslogd) was modified to send syslog messages to users, under control of the standard configuration file. Usernames as targets of syslog messages cause transmission of a Zephyr notice. A list of names may be preceded by `!' to indicate that the syslog notice should be displayed on the users' terminals directly (this action used to occur on any list of names in the configuration file). All syslog Zephyr messages are sent as class ``SYSLOG,'' with instance set to the hostname, and recipient of either a ZID (if a username is specified in the configuration file) or a wildcard (if ``*'' is specified in the configuration file). See the Zephyr version of the syslogd(8) manual page for more details. @SubSection{Login} In the Athena environment, when users log into a workstation, a WindowGram client is started automatically by their initialization files (usually @i(.login), which is executed by @i(/bin/csh)). When they log out, a SIGHUP is sent to the process group of the login shell, causing the WindowGram client to deregister the user's location, cancel any subscriptions, and exit.