source: trunk/athena/etc/track/track.c @ 14093

Revision 14093, 18.9 KB checked in by danw, 25 years ago (diff)
autoconfiscate
Line 
1/*
2 *      $Id: track.c,v 4.25 1999-12-16 01:58:11 danw Exp $
3 */
4
5#ifndef lint
6static char *rcsid_header_h = "$Id: track.c,v 4.25 1999-12-16 01:58:11 danw Exp $";
7#endif
8
9#include "bellcore-copyright.h"
10#include "mit-copyright.h"
11
12#include "track.h"
13
14char admin[WORDLEN] = DEF_ADM;          /* track administrator */
15char workdir[LINELEN];                  /* working directory under src/dest
16                                         * root where slists, statfiles, etc.
17                                         * can be found */
18char binarydir[LINELEN] = DEF_BINDIR;   /* directory in working dir to
19                                         * find executables */
20char fromroot[LINELEN] = DEF_FROMROOT;  /* Root directory for source */
21char toroot[LINELEN] = DEF_TOROOT;      /* Root directory for destination */
22
23char lockpath[LINELEN];                 /* starting lock filename */
24char logfilepath[LINELEN] = DEF_LOG;    /* default log file */
25FILE *logfile = NULL;                   /* the logfile */
26char subfilename[LINELEN] = DEF_SUB;    /* default subscription file */
27char subfilepath[LINELEN] = "";         /* alternate subscription file */
28char statfilepath[LINELEN];             /* pathname to statfile */
29FILE *statfile;                         /* the statfile! */
30char **statfilebufs;                    /* array of lines for sort() */
31int cur_line;                           /* index into statfilebufs      */
32unsigned maxlines = 0;                  /* max # lines in statfile buf array.
33                                         * write_statline maintains maxlines */
34unsigned stackmax = STACKMAX;           /* max depth of pathname-stack vars */
35
36char prgname[LINELEN];
37
38extern int errno;               /* global error number location */
39char errmsg[LINELEN];
40
41int writeflag = 0;      /* if set, translate subscription list -> statfile,
42                         * rather than pulling files */
43int parseflag = 0;      /* if set, just parse the subscription list */
44int forceflag = 0;      /* if set, will over-ride lock files */
45int verboseflag = 0;    /* if set, files listed on stdout as they're updated */
46int cksumflag = 0;      /* if set, compare file checksums when updating */
47int nopullflag = 0;     /* if set, find out the differences,
48                         *         but don't pull anything */
49int quietflag = 0;      /* if set, don't print non-fatal error messages */
50int interactive = 1;    /* if set, don't send errors via mail, print them */
51int uflag = NO_CLOBBER; /* if set, copy a older file on top of a newer one */
52int debug = 0;          /* if set, print debugging information */
53int ignore_prots = 0;   /* if set, don't use or set uid/gid/mode-bits */
54int incl_devs = 0;      /* if set, include devices in update */
55
56/* initialize the global entry-counters with bad values,
57 * so that printmsg() can detect them.
58 */
59Entry entries[ ENTRYMAX];       /* Subscription list entries */
60int entrycnt = -1;              /* Number of entries */
61int entnum = -1;                /* Current entry number */
62
63void cleanup(), readstat(), clearlocks(), writestat(), walk_trees();
64void get_arg(), justshow(), setuperr(), build_path(), openstat();
65void closestat();
66
67main(argc,argv)
68int argc;
69char **argv;
70{
71        char    scratch[LINELEN];
72        int     i;
73        struct sigaction sa;
74
75        strcpy(prgname,argv[0]);
76        strcpy(errmsg,"");
77
78        umask(022);     /* set default umask for daemons */
79
80        sa.sa_flags = 0;
81        sigemptyset(&sa.sa_mask);
82        sa.sa_handler = cleanup;
83
84        sigaction( SIGINT, &sa, NULL);
85        sigaction( SIGHUP, &sa, NULL);
86        sigaction( SIGPIPE, &sa, NULL);
87
88        for(i=1;i<argc;i++) {
89                if (argv[i][0] != '-') {
90                        strcpy( subfilepath, argv[i]);
91                        continue;
92                }
93                switch (argv[i][1]) {
94                /* -F dirname
95                 *    Specify source "root" directory.
96                 */
97                case 'F':
98                        get_arg(scratch,argv,&i);
99                        if (*scratch != '/') {
100                                getcwd( fromroot, sizeof(fromroot));
101                                strcat( strcat( fromroot, "/"), scratch);
102                        }
103                        else if (! scratch[1]) *fromroot = '\0';
104                        else strcpy( fromroot, scratch);
105                        break;
106
107                /* -I
108                 *    Ignore protections (uid,gid,mode) when tracking,
109                 *    except when creating a file: then, use remote prots.
110                 */
111                case 'I':
112                        ignore_prots = 1;
113                        break;
114
115                /* -S stackmax
116                 *    Specify deeper path stacks
117                 */
118                case 'S':
119                        get_arg(scratch,argv,&i);
120                        sscanf( scratch, "%d", &stackmax);
121                        break;
122
123                /* -T dirname
124                 *    Specify destination "root" directory.
125                 */
126                case 'T':
127                        get_arg(scratch,argv,&i);
128                        if (*scratch != '/') {
129                                getcwd( toroot, sizeof(toroot));
130                                strcat( strcat( toroot, "/"), scratch);
131                        }
132                        else if (! scratch[1]) *toroot = '\0';
133                        else strcpy( toroot, scratch);
134                        break;
135
136                /* -W dirname
137                 *    Specify the working directory for
138                 *    accessing the subscription-list and statfile.
139                 */
140                case 'W':
141                        get_arg(workdir,argv,&i);
142                        break;
143                /* -c
144                 *    compare checksums of regular files, when updating.
145                 *    the checksums are used to detect file-system
146                 *    corruption.
147                 */
148                case 'c':
149                        cksumflag = 1;
150                        break;
151                /* -d
152                 *    Include devices in an update.
153                 */
154                case 'd':
155                        incl_devs = 1;
156                        break;
157                /* -f
158                 *    Force updating regardless of locks.
159                 */
160                case 'f':
161                        forceflag = 1;
162                        break;
163
164                /* -m {user}
165                 *    Send mail to root/user instead of
166                 * displaying messages on the terminal.
167                 */
168                case 'm':
169                        interactive = 0;
170                        get_arg(admin,argv,&i);
171                        break;
172                /* -n
173                 *    Produce a list of files that need updating,
174                 * but don't actually do anything about them.
175                 */
176                case 'n':
177                        nopullflag = 1;
178                        verboseflag = 1;
179                        fprintf( stderr, "-n: what we WOULD do:\n");
180                        break;
181                /* -p
182                 *    Parse only.  Display a detailed list of the fields in the
183                 * subscription file.
184                 */
185                case 'p':
186                        parseflag = 1;
187                        break;
188                /* -q
189                 *    Be quiet about warning messages.
190                 */
191                case 'q':
192                        quietflag = 1;
193                        break;
194                /* -s {pathname}
195                 *   use specified file as statfile,
196                 * or use stdio if pathname is "-".
197                 */
198                case 's':
199                        get_arg( statfilepath, argv, &i);
200                        break;
201                /* -u
202                 *    Copy over files regardless of which is newer.
203                 */
204                case 'u':
205                        uflag = DO_CLOBBER;
206                        break;
207                /* -v
208                 *    Explain what is going on verbosely.
209                 */
210                case 'v':
211                        verboseflag = 1;
212                        break;
213                /* -w
214                 *    Create a statfile.
215                 */
216                case 'w':
217                        writeflag = 1;
218                        break;
219                /* -x
220                 *    Display debugging information.
221                 */
222                case 'x':
223                        debug = 1;
224                        break;
225                /*
226                 * Something isn't right if we got this far...
227                 */
228                default:
229                        fprintf(stderr,"track error: bad option %s\n",argv[i]);
230                        break;
231                }
232        }
233
234        /*
235         * Set up nullmail interface if not an interactive session.
236         */
237        if (!interactive)
238                setuperr();
239
240        /* check for existence of root directories:
241         * we shouldn't create them, as they are likely to be remote,
242         * so that the user may have forgotten to attach them.
243         */
244        if ( *fromroot && access( fromroot, 0)) {
245                sprintf(errmsg,"can't access source root-directory %s\n",
246                        fromroot);
247                do_panic();
248        }
249        if ( !writeflag && *toroot && access( toroot, 0)) {
250                sprintf(errmsg,"can't access target root-directory %s\n",
251                        toroot);
252                do_panic();
253        }
254        build_path( fromroot, workdir, DEF_SLISTDIR, subfilepath);
255        build_path( fromroot, workdir, DEF_STATDIR, statfilepath);
256
257        fprintf( stderr, "using %s as subscription-list\n", subfilepath);
258        fprintf( stderr, "using %s as statfile\n",         statfilepath);
259
260        /*
261        **      redirect yacc/lex i/o
262        */
263        parseinit( opensubfile( subfilepath));
264        if (yyparse()) {
265                strcpy(errmsg,"parse aborted.\n");
266                do_panic();
267        }
268        if (debug)
269                printf("parse worked\n");
270
271        sort_entries();
272
273        if (parseflag) {  /* -p: Just show the fields */
274                justshow();
275                cleanup();
276        }
277
278        setlock();
279
280        openstat( statfilepath, writeflag);
281
282        if ( writeflag)         /* -w: Write the exporting statfile */
283                writestat();
284        else {
285                /* update in two passes: links & their parent-dirs first,
286                 * which frees up file-system space when links replace files,
287                 * then everything else. re-update dirs in second pass,
288                 * to facilitate statfile-traversal.
289                 */
290                readstat( "ld");
291                rewind( statfile);
292                readstat( "fdbc");
293        }
294        closestat();
295
296        clearlocks();
297        exit( 0);
298}                       /* end of main() */
299
300#define pathtail( p) p[1+*(int*)p[CNT]]
301
302void
303readstat( types) char *types; {
304        struct currentness rem_currency, *cmp_currency;
305        char statline[ LINELEN], *remname;
306        char **from, **to, **cmp;
307        char *tail = NULL;
308
309        from = initpath( fromroot);
310        to   = initpath(   toroot);
311        cmp  = initpath(   toroot);
312
313        /* prime the path-stacks for dec_entry() to
314         * pop the "old" entry-names off.
315         */
316        pushpath( from, ""); pushpath( to, ""); pushpath( cmp, "");
317
318        init_next_match();
319
320        while ( NULL != fgets( statline, sizeof statline, statfile)) {
321
322                /* XXX : needs data-hiding work, but will do:
323                 *       only update what main tells us to in this pass.
324                 */
325                if ( ! strchr( types, *statline)) continue;
326
327                /* extract the currency data from the statline:
328                 */
329                remname = dec_statfile( statline, &rem_currency);
330
331                /* find the subscription entry corresponding to the
332                 * current pathname.
333                 * if we reach an entry which is lexicographically greater than
334                 * the current pathname, read statfile for the next pathname.
335                 * both entries[] & statfile must be sorted by sortkey!
336                 */
337                if ( 0 >= ( entnum = get_next_match( remname))) continue;
338
339                /* do a breadth-first search of the tree of entries,
340                 * to find the entry corresponding to remname:
341                 * for example, if /usr & /usr/bin are both entries,
342                 * they appear in that order in the entries[] array.
343                 * if remname is /usr/bin/foo, we want gettail() to
344                 * use /usr/bin's exception-list, not /usr's exception-list.
345                 * thus, /usr/bin is the "last match" for /usr/bin/foo.
346                 */
347                entnum = last_match( remname, entnum);
348
349                tail = remname;
350                switch ( gettail( &tail, TYPE( rem_currency.sbuf), entnum)) {
351                case NORMALCASE: break;
352                case DONT_TRACK: continue;
353                case FORCE_LINK: fake_link( fromroot, remname, &rem_currency);
354                                 break;
355                default:         sprintf(errmsg,"bad value from gettail\n");
356                                 do_panic();
357                }
358
359                /* loosely, tail == remname - fromfile, as
360                 * long as tail isn't in the exception-list.
361                 * the string remname begins with the string from[ PATH]:
362                 * for example: remname =            /usr/bin/foo.
363                 *          from[ PATH] =            /usr/bin.
364                 *          from[ ROOT] = /mountpoint/usr/bin.
365                 * in this example, we get tail == "foo".
366                 */
367
368                cmp_currency = dec_entry( entnum, from, to, cmp, tail);
369
370                pushpath( to,  tail);
371                pushpath( from, tail);
372
373                if ( ! update_file( cmp_currency, to,
374                                   &rem_currency, from))
375                        /* REWRITE:
376                        do_cmds( entries[entnum].cmdbuf, to[ ROOT])
377                         */
378                        ;
379                /* remove tail from each path:
380                 */
381                poppath( to);
382                poppath( from);
383        }
384        /* make sure that the file-systems' superblocks are up-to-date.
385         */
386        sync();
387        sleep(2);
388}
389
390/*
391 * Set lock for subscriptionlist file.
392 */
393
394setlock()
395{
396        sprintf( lockpath,"%s/%s.started", DEF_LOCKDIR, subfilename);
397
398        if ( access( lockpath, 0));
399        else if ( too_old( lockpath, LOCK_TIME) || forceflag) clearlocks();
400        else {
401                sprintf( errmsg, "lock set on %s--quitting\n", lockpath);
402                do_gripe();
403                exit(0);
404        }
405        if ( close( creat( lockpath,220))) {
406                sprintf( errmsg, "can't create lockfile %s\n", lockpath);
407                do_panic();
408        }
409        return(0);
410}
411
412/*
413 * Erase those locks...
414 */
415
416void
417clearlocks()
418{
419        if ( !*lockpath) return;
420        if ( unlink( lockpath)) {
421                fprintf( stderr, "can't remove lockfile %s", lockpath);
422                perror( "system error is: ");
423        }
424        else if ( verboseflag)
425                fprintf( stderr, "cleared lock %s\n",lockpath);
426}
427
428/* the array from[] is a set of pointers into a single pathname.
429 * it allows us to pass a parsed pathname along in walk_trees()'
430 * recursive descent of a directory.
431 * the ROOT component is the entire absolute pathname,
432 * including the mount-point, for use in file-system calls.
433 * the NAME component is the portable pathname, without the mount-point,
434 * as the file is described in the statfile.
435 * the TAIL component is everything that's added during the recursive descent,
436 * for comparison with the exception-list. TAIL lacks both mount-point &
437 * the fromfile name.
438 */
439
440/*
441 * Act like a librarian and write out the statfile.
442 */
443
444void
445writestat()
446{
447        char **from, **cmp, **dummy = (char **) NULL;
448        struct currentness *entry_currency;
449
450        from = initpath( fromroot);
451        cmp  = initpath( fromroot);
452
453        /* prime the path-stacks for dec_entry() to
454         * pop the "old" entry-names off.
455         */
456        pushpath( from, ""); pushpath( cmp, "");
457
458        for( entnum = 1; entnum < entrycnt; entnum++) {
459
460                /* dec_entry pushes pathname-qualification
461                 * onto the paths 'from' & 'cmp',
462                 * and pops when appropriate.
463                 */
464                entry_currency = dec_entry( entnum, from, dummy, cmp, NULL);
465
466                if (entries[entnum].islink) {
467                        fake_link( "", from[ NAME], entry_currency);
468                        write_statline( from, entry_currency);
469                        continue;
470                }
471
472                /* write_statline returns fromfile's true type,
473                 * regardless of cmpfile's type:
474                 */
475                if      ( S_IFDIR != write_statline( from, entry_currency));
476                else if ( S_IFDIR != TYPE( entry_currency->sbuf))
477
478                        walk_trees( from, dummy, entry_currency);
479                else    walk_trees( from, cmp,   entry_currency);
480
481                /* WARNING: walk_trees alters ALL of its arguments */
482                /* sort the statfile, and write it out
483                 * to the correct directory:
484                 */
485        }
486        sort_stat();
487}
488
489/* if the current entry's fromfile is a directory, but its cmpfile isn't,
490 * put the same cmpstat in all of fromfile's contents' statlines.
491 * if cmpfile is a directory too, its subtree must parallel fromfile's subtree,
492 * and each statline reflects the one-to-one (not onto) mapping from
493 * fromfile's subtree to cmpfile's subtree:
494 * we take fromfile's subnode's pathname ( not including the prefix fromroot),
495 * and we take the stat from cmpfile's corresponding subnode.
496 */
497void
498walk_trees( f, c, currency)
499char *f[], *c[];
500struct currentness *currency;
501{
502        DIR *dirp;
503        struct dirent *dp;
504        char *tail;
505
506        dirp = opendir( f[ ROOT]);
507        if (!dirp) {
508                sprintf(errmsg,"can't open directory %s\n", f[ ROOT]);
509                do_gripe();
510                return;
511        }
512
513        while( dp = readdir( dirp)) {
514                if (strcmp(dp->d_name, ".") == 0)
515                  continue;
516                if (strcmp(dp->d_name, "..") == 0)
517                  continue;
518
519                if (! dp->d_ino) continue;    /* empty dir-block */
520
521                pushpath( f, dp->d_name);
522                pushpath( c, dp->d_name);
523
524                tail = f[ NAME];
525                switch ( gettail( &tail, 0, entnum)) {
526                case NORMALCASE: break;
527                case FORCE_LINK: fake_link( "", f[ NAME], currency);
528                                 write_statline( f, currency);
529                                 /* fall through to poppath() calls */
530                case DONT_TRACK: poppath( f);
531                                 poppath( c);
532                                 continue;
533                default:         sprintf(errmsg,"bad value from gettail\n");
534                                 do_panic();
535                }
536                /* normal case: tail isn't an exception or a forced link.
537                 */
538                if ( c && get_currentness( c, currency)) {
539                        sprintf(errmsg,"can't lstat comparison-file %s.\n",
540                                c[ ROOT]);
541                        do_panic();
542                }
543                /* write_statline returns fromfile's type:
544                 */
545                else if ( S_IFDIR == write_statline( f, currency))
546                        walk_trees( f, c, currency);
547
548                poppath( f);
549                poppath( c);
550        }
551        closedir(dirp);
552}
553
554/*
555 * Get a command line argument
556 */
557
558void
559get_arg(to,list,ptr)
560char *to,**list;
561int *ptr;
562{
563        int offset = 2;
564
565        if (strlen(list[*ptr]) == 2) {
566                (*ptr)++;
567                offset = 0;
568        }
569        strcpy(to,list[*ptr]+offset);
570}
571
572/*
573 * Log a message to the logfile.
574   UNUSED
575
576log(ptr)
577char *ptr;
578{
579        extern long time();
580        extern char *ctime();
581        static FILE *logfile = NULL;
582        char namebuf[LINELEN];
583        char timestring[LINELEN];
584        long timebuf;
585
586        if ( NULL == logfile) {
587                sprintf( namebuf,"%s/%s",workdir,DEF_LOG);
588                if( access( namebuf, 0))
589                        return;
590                if (NULL == (logfile = fopen( namebuf,"a"))) {
591                        sprintf(errmsg,"can't open log file %s",namebuf);
592                        do_panic();
593                }
594        }
595        timebuf = time(0);
596        strcpy(timestring,ctime(&timebuf));
597        timestring[ strlen(timestring)-1] = '\0';
598        fprintf(logfile,"%s %s %s\n",timestring,subfilename,ptr);
599}
600 */
601
602#undef ROOT
603
604/*
605 * Execute shell commands
606 */
607
608void
609do_cmds(cmds,local)
610char *cmds,*local;
611{
612        char *ptr,*nptr;
613        FILE *shell;
614
615        if ( ! *cmds) return;
616        ptr = cmds;
617
618        shell = popen(DEF_SHELL,"w");
619        if (!shell) {
620                sprintf(errmsg,"can't open shell %s\n",DEF_SHELL);
621                do_gripe();
622                return;
623        }
624
625        fprintf(shell,"chdir %s\n",toroot);
626        fprintf(shell,"%sFILE=%s\n",DEF_SETCMD,local);
627        fprintf(shell,"%sROOT=%s\n",DEF_SETCMD,toroot);
628
629        for (;;) {
630                nptr = strchr(ptr,'\n');
631                if (nptr)
632                        *nptr = '\0';
633                fprintf(shell,"%s\n",ptr);
634                if (!nptr) {
635                        pclose(shell);
636                        return;
637                }
638                ptr = nptr+1;
639        }
640}
641/*
642 * Show parsing.
643 */
644
645void
646justshow()
647{
648        int i,j, size;
649        Entry *e;
650        List_element *p;
651
652        fprintf( stderr, "subscription-list as parsed:\n\n");
653
654        for (i = 0; i <= entrycnt; i++) {
655                e = &entries[ i];
656                if ( ! e->fromfile) break;
657                fprintf(stderr,
658                        "entry %d:\n\tislink-- %d\tfromfile-- %s\n",
659                        i,
660                        e->islink,
661                        e->fromfile);
662                fprintf(stderr,
663                        "\tcmpfile-- %s\n\ttofile-- %s\n\tpatterns--\n",
664                        e->cmpfile,
665                        e->tofile);
666                for( p = e->patterns; p ; p = NEXT( p))
667                    fprintf(stderr,"\t\t%s%s\n",
668                            FLAG( p) == FORCE_LINK ? "-> " : "",
669                            TEXT( p));
670                fprintf( stderr, "\texceptions--\n");
671                switch( SIGN( e->names.shift)) {
672                case -1:
673                    for( p = ( List_element *) e->names.table; p ; p = NEXT( p))
674                        fprintf(stderr,"\t\t%s%s\n",
675                                FLAG( p) == FORCE_LINK ? "-> " : "",
676                                TEXT( p));
677                    fprintf(stderr,
678                        "track didn't fully parse the exception-list.\n");
679                    fprintf(stderr,
680                        "the most-recently parsed exception was:\n%s%s\n",
681                        FLAG( e->names.table) == FORCE_LINK ? "-> " : "",
682                        TEXT( e->names.table));
683                    continue;
684                case 0: break;
685                case 1:
686                    size = (unsigned) 0x80000000 >> e->names.shift - 1;
687                    for( j = 0; j < size; j++)
688                    {
689                        if ( ! e->names.table[j]) continue;
690                        fprintf( stderr,"\t\t");
691                        for ( p = e->names.table[j]; p; p = NEXT( p))
692                            fprintf(stderr,"%s%s, ",
693                                    FLAG( p) == FORCE_LINK ? "-> " : "",
694                                    TEXT( p));
695                        fprintf( stderr,"\n");
696                    }
697                    break;
698                }
699                fprintf(stderr,"\tcommand-- %s\n",e->cmdbuf);
700        }
701}
702
703/*
704 *      redirect standard error output to mail to the administrator
705 *      use nullmail so that null messages won't get sent.
706 */
707
708/* REWORK */
709
710void
711setuperr()
712{
713        char msg[LINELEN];
714        FILE *tmp;
715
716        sprintf(msg,"%s/nullmail %s",binarydir,admin);
717        /*
718        **      start a process that will send mail to the adminstrator
719        */
720        if ((tmp = popen(msg,"w")) == NULL) {
721/*              sprintf( msg, "echo HELP track --%s %s", gargv[0],
722                        "can't execute nullmail cmd  > /dev/console");
723                system(msg); */
724                exit(1);
725        }
726        /*
727        **      now connect stderr to the pipe
728        */
729        if (dup2(fileno(tmp),2) ==  -1) {
730/*              sprintf( msg, "echo HELP track --%s %s", gargv[0],
731                        "can't dup stderr  > /dev/console");
732                system(msg); */
733                exit(1);
734        }
735}
736
737void
738build_path( f, w, d, p) char *f, *w, *d, *p; {
739        static char buf[ LINELEN];
740
741        if ( ! strcmp( p, "-")) return;
742        /*
743         * Get the proper working directory,
744         * where the subscription-list & statfile are.
745         */
746        if ( *w) f = "";  /* don't add root-qualification to user's workdir */
747        else if ( *p) f = "."; /* don't use default workdir with user's filen */
748        else w = DEF_WORKDIR; /* default workdir, default filename */
749
750        if ( *p) d = "";
751        else strcpy( p, subfilename);
752
753        sprintf( buf, "%s%s%s%s/%s", f, w, *d ? "/" : "", d, p);
754        strcpy( p, buf);
755}
756
757FILE *
758opensubfile( path) char *path; {
759        FILE *subfile;
760        if ( ! ( subfile = fopen( path, "r"))) {
761                sprintf( errmsg, "Can't open subscriptionlist %s\n", path);
762                do_panic();
763        }
764        return( subfile);
765}
766
767void
768openstat( path, write) char *path; int write;
769{
770        char *mode = write? "w"   : "r";
771        FILE *std =  write? stdout : stdin;
772
773        if ( ! strcmp( path, "-"))
774                statfile = std;
775        else if ( ! ( statfile = fopen( path, mode))) {
776                sprintf( errmsg, "can't open statfile %s\n", path);
777                do_panic();
778        }
779}
780
781void
782closestat() {
783        if ( EOF == fclose( statfile)) {
784                sprintf( errmsg, "can't close %s\n", statfilepath);
785                do_panic();
786        }
787}
788
789void
790cleanup()
791{
792        clearlocks();
793        exit(0);
794}
Note: See TracBrowser for help on using the repository browser.