]> asedeno.scripts.mit.edu Git - 1ts-debian.git/blob - zephyr/server/main.c
76020c7fe24e1f61a39c26638f350131a757c920
[1ts-debian.git] / zephyr / server / main.c
1 /* This file is part of the Project Athena Zephyr Notification System.
2  * It contains the main loop of the Zephyr server
3  *
4  *      Created by:     John T. Kohl
5  *
6  *      $Source: /afs/dev.mit.edu/source/repository/athena/lib/zephyr/server/main.c,v $
7  *      $Author: zacheiss $
8  *
9  *      Copyright (c) 1987,1988,1991 by the Massachusetts Institute of Technology.
10  *      For copying and distribution information, see the file
11  *      "mit-copyright.h". 
12  */
13
14 #include <zephyr/mit-copyright.h>
15 #include "zserver.h"
16 #include <sys/socket.h>
17 #include <sys/resource.h>
18
19 #ifndef lint
20 #ifndef SABER
21 static const char rcsid_main_c[] =
22     "$Id: main.c,v 1.69 2001/02/27 04:50:08 zacheiss Exp $";
23 #endif
24 #endif
25
26 /*
27  * Server loop for Zephyr.
28  */
29
30 /*
31   The Zephyr server maintains several linked lists of information.
32
33   There is an array of servers (otherservers) initialized and maintained
34   by server_s.c.
35
36   Each server descriptor contains a pointer to a linked list of hosts
37   which are ``owned'' by that server.  The first server is the ``limbo''
38   server which owns any host which was formerly owned by a dead server.
39
40   Each of these host list entries has an IP address and a pointer to a
41   linked list of clients on that host.
42
43   Each client has a sockaddr_in, a list of subscriptions, and possibly
44   a session key.
45
46   In addition, the class manager has copies of the pointers to the
47   clients which are registered with a particular class, the
48   not-yet-acknowledged list has copies of pointers to some clients,
49   and the hostm manager may have copies of pointers to some clients
50   (if the client has not acknowledged a packet after a given timeout).
51 */
52
53 #define EVER            (;;)            /* don't stop looping */
54
55 static int do_net_setup __P((void));
56 static int initialize __P((void));
57 static void usage __P((void));
58 static void do_reset __P((void));
59 static RETSIGTYPE bye __P((int));
60 static RETSIGTYPE dbug_on __P((int));
61 static RETSIGTYPE dbug_off __P((int));
62 static RETSIGTYPE sig_dump_db __P((int));
63 static RETSIGTYPE sig_dump_strings __P((int));
64 static RETSIGTYPE reset __P((int));
65 static RETSIGTYPE reap __P((int));
66 static void read_from_dump __P((char *dumpfile));
67 static void dump_db __P((void));
68 static void dump_strings __P((void));
69
70 #ifndef DEBUG
71 static void detach __P((void));
72 #endif
73
74 static short doreset = 0;               /* if it becomes 1, perform
75                                            reset functions */
76
77 int nfds;                               /* max file descriptor for select() */
78 int srv_socket;                         /* dgram socket for clients
79                                            and other servers */
80 int bdump_socket = -1;                  /* brain dump socket fd
81                                            (closed most of the time) */
82 fd_set interesting;                     /* the file descrips we are listening
83                                            to right now */
84 struct sockaddr_in srv_addr;            /* address of the socket */
85
86 Unacked *nacklist = NULL;               /* list of packets waiting for ack's */
87
88 unsigned short hm_port;                 /* host manager receiver port */
89 unsigned short hm_srv_port;             /* host manager server sending port */
90
91 char *programname;                      /* set to the basename of argv[0] */
92 char myname[MAXHOSTNAMELEN];            /* my host name */
93
94 char list_file[128];
95 #ifdef HAVE_KRB5
96 char keytab_file[128];
97 static char tkt5_file[256];
98 #endif
99 #ifdef HAVE_KRB4
100 char srvtab_file[128];
101 char my_realm[REALM_SZ];
102 static char tkt_file[128];
103 #endif
104 char acl_dir[128];
105 char subs_file[128];
106
107 int zdebug;
108 #ifdef DEBUG_MALLOC
109 int dump_malloc_stats = 0;
110 unsigned long m_size;
111 #endif
112 #ifdef DEBUG
113 int zalone;
114 #endif
115
116 struct timeval t_local;                 /* store current time for other uses */
117
118 static int dump_db_flag = 0;
119 static int dump_strings_flag = 0;
120
121 u_long npackets;                        /* number of packets processed */
122 time_t uptime;                          /* when we started operations */
123 static int nofork;
124 struct in_addr my_addr;
125 char *bdump_version = "1.2";
126
127 #ifdef HAVE_KRB5
128 krb5_ccache Z_krb5_ccache;
129 #endif
130
131 #ifdef HAVE_KRB4
132 C_Block __Zephyr_session;
133 #endif
134
135 int
136 main(argc, argv)
137     int argc;
138     char **argv;
139 {
140     int nfound;                 /* #fildes ready on select */
141     fd_set readable;
142     struct timeval tv;
143     int init_from_dump = 0;
144     char *dumpfile;
145 #ifdef _POSIX_VERSION
146     struct sigaction action;
147 #endif
148     int optchar;                        /* option processing */
149     extern char *optarg;
150     extern int optind;
151
152     sprintf(list_file, "%s/zephyr/%s", SYSCONFDIR, SERVER_LIST_FILE);
153 #ifdef HAVE_KRB4
154     sprintf(srvtab_file, "%s/zephyr/%s", SYSCONFDIR, ZEPHYR_SRVTAB);
155     sprintf(tkt_file, "%s/zephyr/%s", SYSCONFDIR, ZEPHYR_TKFILE);
156 #endif
157 #ifdef HAVE_KRB5
158     sprintf(keytab_file, "%s/zephyr/%s", SYSCONFDIR, ZEPHYR_KEYTAB);
159     sprintf(tkt5_file, "FILE:%s/zephyr/%s", SYSCONFDIR, ZEPHYR_TK5FILE);
160 #endif
161     sprintf(acl_dir, "%s/zephyr/%s", SYSCONFDIR, ZEPHYR_ACL_DIR);
162     sprintf(subs_file, "%s/zephyr/%s", SYSCONFDIR, DEFAULT_SUBS_FILE);
163
164     /* set name */
165     programname = strrchr(argv[0],'/');
166     programname = (programname) ? programname + 1 : argv[0];
167
168     /* process arguments */
169     while ((optchar = getopt(argc, argv, "dsnv:f:k:")) != EOF) {
170         switch(optchar) {
171           case 'd':
172             zdebug = 1;
173             break;
174 #ifdef DEBUG
175           case 's':
176             zalone = 1;
177             break;
178 #endif
179           case 'n':
180             nofork = 1;
181             break;
182           case 'k':
183 #ifdef HAVE_KRB4
184             strncpy(my_realm, optarg, REALM_SZ);
185 #endif
186             break;
187           case 'v':
188             bdump_version = optarg;
189             break;
190           case 'f':
191             init_from_dump = 0;
192             dumpfile = optarg;
193             break;
194           case '?':
195           default:
196             usage();
197             /*NOTREACHED*/
198         }
199     }
200
201 #ifdef HAVE_KRB4
202     /* if there is no readable srvtab and we are not standalone, there
203        is no possible way we can succeed, so we exit */
204
205     if (access(srvtab_file, R_OK)
206 #ifdef DEBUG            
207         && !zalone
208 #endif /* DEBUG */
209         ) {
210         fprintf(stderr, "NO ZEPHYR SRVTAB (%s) available; exiting\n",
211                 srvtab_file);
212         exit(1);
213     }
214     /* Use local realm if not specified on command line. */
215     if (!*my_realm) {
216         if (krb_get_lrealm(my_realm, 1) != KSUCCESS) {
217             fputs("Couldn't get local Kerberos realm; exiting.\n", stderr);
218             exit(1);
219         }
220     }
221 #endif /* HAVE_KRB4 */
222
223 #ifndef DEBUG
224     if (!nofork)
225         detach();
226 #endif /* DEBUG */
227
228     /* open log */
229     OPENLOG(programname, LOG_PID, LOG_LOCAL6);
230
231 #if defined (DEBUG) && 0
232     if (zalone)
233         syslog(LOG_DEBUG, "standalone operation");
234 #endif
235 #if 0
236     if (zdebug)
237         syslog(LOG_DEBUG, "debugging on");
238 #endif
239
240     /* set up sockets & my_addr and myname, 
241        find other servers and set up server table, initialize queues
242        for retransmits, initialize error tables,
243        set up restricted classes */
244
245     /* Initialize t_local for other uses */
246     gettimeofday(&t_local, NULL);
247
248     if (initialize())
249         exit(1);
250
251     if (init_from_dump)
252         read_from_dump(dumpfile);
253
254     /* Seed random number set.  */
255     srandom(getpid() ^ time(0));
256
257     /* chdir to somewhere where a core dump will survive */
258     if (chdir(TEMP_DIRECTORY) != 0)
259         syslog(LOG_ERR, "chdir failed (%m) (execution continuing)");
260
261     FD_ZERO(&interesting);
262     FD_SET(srv_socket, &interesting);
263
264     nfds = srv_socket + 1;
265
266
267 #ifdef _POSIX_VERSION
268     action.sa_flags = 0;
269     sigemptyset(&action.sa_mask);
270
271     action.sa_handler = bye;
272     sigaction(SIGINT, &action, NULL);
273     sigaction(SIGTERM, &action, NULL);
274
275     action.sa_handler = dbug_on;
276     sigaction(SIGUSR1, &action, NULL);
277
278     action.sa_handler = dbug_off;
279     sigaction(SIGUSR2, &action, NULL);
280
281     action.sa_handler = reap;
282     sigaction(SIGCHLD, &action, NULL);
283
284     action.sa_handler = sig_dump_db;
285     sigaction(SIGFPE, &action, NULL);
286
287 #ifdef SIGEMT
288     action.sa_handler = sig_dump_strings;
289     sigaction(SIGEMT, &action, NULL);
290 #endif
291
292     action.sa_handler = reset;
293     sigaction(SIGHUP, &action, NULL);
294 #else /* !posix */
295     signal(SIGINT, bye);
296     signal(SIGTERM, bye);
297     signal(SIGUSR1, dbug_on);
298     signal(SIGUSR2, dbug_off);
299     signal(SIGCHLD, reap);
300     signal(SIGFPE, sig_dump_db);
301 #ifdef SIGEMT
302     signal(SIGEMT, sig_dump_strings);
303 #endif
304     signal(SIGHUP, reset);
305 #endif /* _POSIX_VERSION */
306
307     syslog(LOG_NOTICE, "Ready for action");
308
309     /* Reinitialize t_local now that initialization is done. */
310     gettimeofday(&t_local, NULL);
311     uptime = NOW;
312 #ifdef HAVE_KRB4
313     timer_set_rel(SWEEP_INTERVAL, sweep_ticket_hash_table, NULL);
314 #endif
315
316     realm_wakeup();
317 #ifdef DEBUG_MALLOC
318     malloc_inuse(&m_size);
319 #endif
320     for EVER {
321         if (doreset)
322             do_reset();
323
324         if (dump_db_flag)
325             dump_db();
326         if (dump_strings_flag)
327             dump_strings();
328
329         timer_process();
330
331         readable = interesting;
332         if (msgs_queued()) {
333             /* when there is input in the queue, we
334                artificially set up to pick up the input */
335             nfound = 1;
336             FD_ZERO(&readable);
337         } else  {
338             nfound = select(nfds, &readable, NULL, NULL, timer_timeout(&tv));
339         }
340
341         /* Initialize t_local for other uses */
342         gettimeofday(&t_local, (struct timezone *)0);
343                 
344         /* don't flame about EINTR, since a SIGUSR1 or SIGUSR2
345            can generate it by interrupting the select */
346         if (nfound < 0) {
347             if (errno != EINTR)
348                 syslog(LOG_WARNING, "select error: %m");
349 #ifdef DEBUG_MALLOC
350             if (dump_malloc_stats) {
351                 unsigned long foo,histid2;
352
353                 dump_malloc_stats = 0;
354                 foo = malloc_inuse(&histid2);
355                 printf("Total inuse: %d\n",foo);
356                 malloc_list(2,m_size,histid2);
357             }
358 #endif
359             continue;
360         }
361
362         if (nfound == 0) {
363             /* either we timed out or we were just
364                polling for input.  Either way we want to continue
365                the loop, and process the next timeout */
366             continue;
367         } else {
368             if (bdump_socket >= 0 && FD_ISSET(bdump_socket,&readable))
369                 bdump_send();
370             else if (msgs_queued() || FD_ISSET(srv_socket, &readable))
371                 handle_packet();
372             else
373                 syslog(LOG_ERR, "select weird?!?!");
374         }
375     }
376 }
377
378 /* Initialize net stuff.
379    Set up the server array.
380    Initialize the packet ack queues to be empty.
381    Initialize the error tables.
382    Restrict certain classes.
383    */
384
385 static int
386 initialize()
387 {
388     int zero = 0;
389     if (do_net_setup())
390         return(1);
391
392     server_init();
393
394 #ifdef HAVE_KRB4
395     krb_set_tkt_string(tkt_file);
396 #endif
397     realm_init();
398     
399     ZSetServerState(1);
400     ZInitialize();              /* set up the library */
401 #ifdef HAVE_KRB5
402     krb5_cc_resolve(Z_krb5_ctx, tkt5_file, &Z_krb5_ccache);
403 #ifdef HAVE_KRB5_CC_SET_DEFAULT_NAME
404     krb5_cc_set_default_name(Z_krb5_ctx, tkt5_file);
405 #else
406     {
407         /* Hack to make krb5_cc_default do something reasonable */
408         char *env=(char *)malloc(strlen(tkt5_file)+12);
409         if (!env) return(1);
410         sprintf(env, "KRB5CCNAME=%s", tkt5_file);
411         putenv(env);
412     }
413 #endif
414 #endif
415 #ifdef HAVE_KRB4
416     /* Override what Zinitialize set for ZGetRealm() */
417     if (*my_realm) 
418       strcpy(__Zephyr_realm, my_realm);
419 #endif
420     init_zsrv_err_tbl();        /* set up err table */
421
422     ZSetFD(srv_socket);         /* set up the socket as the input fildes */
423
424     /* set up default strings */
425
426     class_control = make_string(ZEPHYR_CTL_CLASS, 1);
427     class_admin = make_string(ZEPHYR_ADMIN_CLASS, 1);
428     class_hm = make_string(HM_CTL_CLASS, 1);
429     class_ulogin = make_string(LOGIN_CLASS, 1);
430     class_ulocate = make_string(LOCATE_CLASS, 1);
431     wildcard_instance = make_string(WILDCARD_INSTANCE, 1);
432     empty = make_string("", 0);
433
434     /* restrict certain classes */
435     access_init();
436     return 0;
437 }
438
439 /* 
440  * Set up the server and client sockets, and initialize my_addr and myname
441  */
442
443 static int
444 do_net_setup()
445 {
446     struct servent *sp;
447     struct hostent *hp;
448     char hostname[MAXHOSTNAMELEN+1];
449     int flags;
450
451     if (gethostname(hostname, MAXHOSTNAMELEN + 1)) {
452         syslog(LOG_ERR, "no hostname: %m");
453         return 1;
454     }
455     hp = gethostbyname(hostname);
456     if (!hp) {
457         syslog(LOG_ERR, "no gethostbyname repsonse");
458         strncpy(myname, hostname, MAXHOSTNAMELEN);
459         return 1;
460     }
461     strncpy(myname, hp->h_name, MAXHOSTNAMELEN);
462     memcpy(&my_addr, hp->h_addr, sizeof(hp->h_addr));
463
464     setservent(1);              /* keep file/connection open */
465
466     memset(&srv_addr, 0, sizeof(srv_addr));
467     srv_addr.sin_family = AF_INET;
468     sp = getservbyname(SERVER_SVCNAME, "udp");
469     srv_addr.sin_port = (sp) ? sp->s_port : SERVER_SVC_FALLBACK;
470
471     sp = getservbyname(HM_SVCNAME, "udp");
472     hm_port = (sp) ? sp->s_port : HM_SVC_FALLBACK;
473         
474     sp = getservbyname(HM_SRV_SVCNAME, "udp");
475     hm_srv_port = (sp) ? sp->s_port : HM_SRV_SVC_FALLBACK;
476         
477     srv_socket = socket(AF_INET, SOCK_DGRAM, 0);
478     if (srv_socket < 0) {
479         syslog(LOG_ERR, "client_sock failed: %m");
480         return 1;
481     } else {
482 #ifdef SO_BSDCOMPAT
483       int on = 1;
484
485       /* Prevent Linux from giving us socket errors we don't care about. */
486       setsockopt(srv_socket, SOL_SOCKET, SO_BSDCOMPAT, &on, sizeof(on));
487 #endif
488     }
489     if (bind(srv_socket, (struct sockaddr *) &srv_addr,
490              sizeof(srv_addr)) < 0) {
491         syslog(LOG_ERR, "client bind failed: %m");
492         return 1;
493     }
494
495     /* set not-blocking */
496 #ifdef _POSIX_VERSION
497     flags = fcntl(srv_socket, F_GETFL);
498     flags |= O_NONBLOCK;
499     fcntl(srv_socket, F_SETFL, flags);
500 #else
501     flags = 1;
502     ioctl(srv_socket, FIONBIO, &flags);
503 #endif
504
505     return 0;
506 }    
507
508
509 /*
510  * print out a usage message.
511  */
512
513 static void
514 usage()
515 {
516 #ifdef DEBUG
517         fprintf(stderr, "Usage: %s [-d] [-s] [-n] [-k realm] [-f dumpfile]\n",
518                 programname);
519 #else
520         fprintf(stderr, "Usage: %s [-d] [-n] [-k realm] [-f dumpfile]\n",
521                 programname);
522 #endif /* DEBUG */
523         exit(2);
524 }
525
526 int
527 packets_waiting()
528 {
529     fd_set readable, initial;
530     struct timeval tv;
531
532     if (msgs_queued())
533         return 1;
534     FD_ZERO(&initial);
535     FD_SET(srv_socket, &initial);
536     readable = initial;
537     tv.tv_sec = tv.tv_usec = 0;
538     return (select(srv_socket + 1, &readable, NULL, NULL, &tv) > 0);
539 }
540
541 static RETSIGTYPE
542 bye(sig)
543     int sig;
544 {
545     server_shutdown();          /* tell other servers */
546 #ifdef REALM_MGMT
547     realm_shutdown();           /* tell other realms */
548 #endif
549     hostm_shutdown();           /* tell our hosts */
550     kill_realm_pids();
551 #ifdef HAVE_KRB4
552     dest_tkt();
553 #endif
554     syslog(LOG_NOTICE, "goodbye (sig %d)", sig);
555     exit(0);
556 }
557
558 static RETSIGTYPE
559 dbug_on(sig)
560     int sig;
561 {
562     syslog(LOG_DEBUG, "debugging turned on");
563 #ifdef DEBUG_MALLOC
564     dump_malloc_stats = 1;
565 #endif
566     zdebug = 1;
567 }
568
569 static RETSIGTYPE
570 dbug_off(sig)
571     int sig;
572 {
573     syslog(LOG_DEBUG, "debugging turned off");
574 #ifdef DEBUG_MALLOC
575     malloc_inuse(&m_size);
576 #endif
577     zdebug = 0;
578 }
579
580 int fork_for_dump = 0;
581
582 static RETSIGTYPE
583 sig_dump_strings(sig)
584     int sig;
585 {
586     dump_strings_flag = 1;
587 }
588
589 static void dump_strings()
590 {
591     char filename[128];
592
593     FILE *fp;
594     int oerrno = errno;
595
596     sprintf(filename, "%szephyr.strings", TEMP_DIRECTORY);
597     fp = fopen (filename, "w");
598     if (!fp) {
599         syslog(LOG_ERR, "can't open strings dump file: %m");
600         errno = oerrno;
601         dump_strings_flag = 0;
602         return;
603     }
604     syslog(LOG_INFO, "dumping strings to disk");
605     print_string_table(fp);
606     if (fclose(fp) == EOF)
607         syslog(LOG_ERR, "error writing strings dump file");
608     else
609         syslog(LOG_INFO, "dump done");
610     oerrno = errno;
611     dump_strings_flag = 0;
612     return;
613 }
614
615 static RETSIGTYPE
616 sig_dump_db(sig)
617     int sig;
618 {
619     dump_db_flag = 1;
620 }
621
622 static void dump_db()
623 {
624     /* dump the in-core database to human-readable form on disk */
625     FILE *fp;
626     int oerrno = errno;
627     int pid;
628     char filename[128];
629
630     pid = (fork_for_dump) ? fork() : -1;
631     if (pid > 0) {
632         dump_db_flag = 0;
633         return;
634     }
635     sprintf(filename, "%szephyr.db", TEMP_DIRECTORY);
636     fp = fopen(filename, "w");
637     if (!fp) {
638         syslog(LOG_ERR, "can't open dump database");
639         errno = oerrno;
640         dump_db_flag = 0;
641         return;
642     }
643     syslog(LOG_INFO, "dumping to disk");
644     server_dump_servers(fp);
645     uloc_dump_locs(fp);
646     client_dump_clients(fp);
647     triplet_dump_subs(fp);
648     realm_dump_realms(fp);
649     syslog(LOG_INFO, "dump done");
650     if (fclose(fp) == EOF)
651         syslog(LOG_ERR, "can't close dump db");
652     if (pid == 0)
653         exit(0);
654     errno = oerrno;
655     dump_db_flag = 0;
656 }
657
658 static RETSIGTYPE
659 reset(sig)
660     int sig;
661 {
662 #if 1
663     zdbug((LOG_DEBUG,"reset()"));
664 #endif
665     doreset = 1;
666 }
667
668 static RETSIGTYPE
669 reap(sig)
670     int sig;
671 {
672     int pid, i = 0;
673     int oerrno = errno;
674     ZRealm *rlm;
675 #ifdef _POSIX_VERSION
676     int waitb;
677 #else
678     union wait waitb;
679 #endif
680 #if 1
681     zdbug((LOG_DEBUG,"reap()"));
682 #endif
683 #ifdef _POSIX_VERSION
684     while ((pid = waitpid(-1, &waitb, WNOHANG)) == 0) 
685       { i++; if (i > 10) break; }
686 #else
687     while ((pid = wait3 (&waitb, WNOHANG, (struct rusage*) 0)) == 0) 
688       { i++; if (i > 10) break; }
689 #endif
690
691     errno = oerrno;
692  
693     if (pid) {
694       if (WIFSIGNALED(waitb) == 0) {
695         if (WIFEXITED(waitb) != 0) {
696           rlm = realm_get_realm_by_pid(pid);
697           if (rlm) {
698             rlm->child_pid = 0;
699             rlm->have_tkt = 1;
700           }
701         }
702       } else {
703         rlm = realm_get_realm_by_pid(pid);
704         if (rlm) {
705           rlm->child_pid = 0;
706         }
707       }
708     }
709 }
710
711 static void
712 do_reset()
713 {
714     int oerrno = errno;
715 #ifdef _POSIX_VERSION
716     sigset_t mask, omask;
717 #else
718     int omask;
719 #endif
720 #if 0
721     zdbug((LOG_DEBUG,"do_reset()"));
722 #endif
723 #ifdef _POSIX_VERSION
724     sigemptyset(&mask);
725     sigaddset(&mask, SIGHUP);
726     sigprocmask(SIG_BLOCK, &mask, &omask);
727 #else
728     omask = sigblock(sigmask(SIGHUP));
729 #endif
730
731     /* reset various things in the server's state */
732     subscr_reset();
733     server_reset();
734     access_reinit();
735     syslog(LOG_INFO, "restart completed");
736     doreset = 0;
737     errno = oerrno;
738 #ifdef _POSIX_VERSION
739     sigprocmask(SIG_SETMASK, &omask, (sigset_t *)0);
740 #else
741     sigsetmask(omask);
742 #endif
743 }
744
745 #ifndef DEBUG
746 /*
747  * detach from the terminal
748  */
749
750 static void
751 detach()
752 {
753     /* detach from terminal and fork. */
754     int i;
755     long size;
756
757 #ifdef _POSIX_VERSION
758     size = sysconf(_SC_OPEN_MAX);
759 #else
760     size = getdtablesize();
761 #endif
762     /* profiling seems to get confused by fork() */
763     i = fork ();
764     if (i) {
765         if (i < 0)
766             perror("fork");
767         exit(0);
768     }
769
770     for (i = 0; i < size; i++)
771         close(i);
772
773     i = open("/dev/tty", O_RDWR, 666);
774 #ifdef TIOCNOTTY /* Only necessary on old systems. */
775     ioctl(i, TIOCNOTTY, NULL);
776 #endif
777     close(i);
778 #ifdef _POSIX_VERSION
779     setsid();
780 #endif
781 }
782 #endif /* not DEBUG */
783
784 static void
785 read_from_dump(dumpfile)
786     char *dumpfile;
787 {
788     /* Not yet implemented. */
789     return;
790 }
791