source: trunk/athena/etc/track/stamp.c @ 1244

Revision 1244, 19.0 KB checked in by don, 36 years ago (diff)
support for stamp.c version 4.4 : this version's dec_entry has less static state, because it detects that the entry hasn't been processed before, in a better way.
Line 
1/*
2 *      $Source: /afs/dev.mit.edu/source/repository/athena/etc/track/stamp.c,v $
3 *      $Header: /afs/dev.mit.edu/source/repository/athena/etc/track/stamp.c,v 4.7 1988-06-20 18:57:38 don Exp $
4 *
5 *      $Log: not supported by cvs2svn $
6 * Revision 4.6  88/06/10  15:58:39  don
7 * changed DEV usage to RDEV.
8 *
9 * Revision 4.5  88/06/10  14:29:57  don
10 * better error-handling on encountering sockets.
11 *
12 * Revision 4.4  88/05/26  13:59:55  don
13 * cosmetics in sort_entries(): fixed indentation, and added comments.
14 *
15 * Revision 4.3  88/05/25  21:25:02  don
16 * fixed a bug in sort_entries: only one descendant was getting added
17 * to each entry's exception-list.
18 *
19 * Revision 4.2  88/05/24  17:41:08  don
20 * beefed up garbled-statfile messages, to aid in finding a bug.
21 * the bug is a garbled-statfile message at the end of an update.
22 * this is intermittent.
23 *
24 * Revision 4.1  88/05/04  18:14:26  shanzer
25 * fixed a bug in sort_entries(); its augmentation of a parent-entry's
26 * exception-list didn't always work.   -don
27 *
28 * Revision 4.0  88/04/14  16:43:00  don
29 * this version is not compatible with prior versions.
30 * it offers, chiefly, link-exporting, i.e., "->" systax in exception-lists.
31 * it also offers sped-up exception-checking, via hash-tables.
32 * a bug remains in -nopullflag support: if the entry's to-name top-level
33 * dir doesn't exist, update_file doesn't get over it.
34 * the fix should be put into the updated() routine, or possibly dec_entry().
35 *
36 * Revision 3.0  88/03/09  13:17:47  don
37 * this version is incompatible with prior versions. it offers:
38 * 1) checksum-handling for regular files, to detect filesystem corruption.
39 * 2) more concise & readable "updating" messages & error messages.
40 * 3) better update-simulation when nopullflag is set.
41 * 4) more support for non-default comparison-files.
42 * finally, the "currentness" data-structure has replaced the statbufs
43 * used before, so that the notion of currency is more readily extensible.
44 * note: the statfile format has been changed.
45 *
46 * Revision 2.5  88/02/23  19:21:36  don
47 * fixed pushpath() & poppath() so that pushing "" onto a path-stack
48 * doesn't push  a '/' as well. for example, pushpath( "/bin", "") should
49 * yield "/bin", instead of "/bin/". this was causing findparent() to fail
50 * when track needs to create bin under the subsciber's mount-point.
51 *
52 * Revision 2.4  88/01/29  18:24:02  don
53 * bug fixes. also, now track can update the root.
54 *
55 * Revision 2.3  87/12/03  19:50:02  don
56 * moved SIGN macro to track.h.
57 *
58 * Revision 2.2  87/12/03  17:31:15  don
59 * fixed rt-port bug in dec_statfile's use of sscanf():
60 * can't fill a short int directly, because sscanf will interpret it
61 * as a long-int, and on the rt, short* gets converted to int* via
62 * truncation!
63 *
64 * Revision 2.1  87/12/01  16:44:54  don
65 * fixed bugs in readstat's traversal of entries] and statfile:
66 * cur_ent is no longer global, but is now part of get_next_match's
67 * state. also, last_match() was causing entries[]'s last element to be
68 * skipped.
69 *
70 * Revision 2.0  87/11/30  15:19:43  don
71 * general rewrite; got rid of stamp data-type, with its attendant garbage,
72 * cleaned up pathname-handling. readstat & writestat now sort overything
73 * by pathname, which simplifies traversals/lookup. should be comprehensible
74 * now.
75 *
76 * Revision 1.1  87/02/12  21:15:36  rfrench
77 * Initial revision
78 *
79 */
80
81#ifndef lint
82static char *rcsid_header_h = "$Header: /afs/dev.mit.edu/source/repository/athena/etc/track/stamp.c,v 4.7 1988-06-20 18:57:38 don Exp $";
83#endif lint
84
85#include "mit-copyright.h"
86
87#include "track.h"
88
89/* XXX
90 * convert right-shifted st_mode type-bits to corresponding formats:
91 * S_IFCHR = 0020000 gets mapped to elt 1 of the array,
92 * S_IFDIR = 0040000 => elt 2 of the array,
93 * S_IFBLK = 0060000 => elt 3 of the array,
94 * S_IFREG = 0100000 => elt 4 of the array,
95 * S_IFLNK = 0120000 => elt 5 of the array,
96 * S_IFSOCK= 0140000 => elt 6 of the array, ( only for error messagess),
97 * S_IFMT  = 0170000 => elt 7 of the array, ( dropping 1 bit).
98 */
99static char *write_formats[] = {
100        "*ERROR (write_statline): %s's file type is 0.\n",
101        "c%s %c%d(%d.%d.%o)\n",         /* S_IFCHR */
102        "d%s %c%d(%d.%d.%o)\n",         /* S_IFDIR */
103        "b%s %c%d(%d.%d.%o)\n",         /* S_IFBLK */
104        "f%s %c%x(%d.%d.%o)%ld\n",      /* S_IFREG */
105        "l%s %c%s\n",                   /* S_IFLNK */
106        "*ERROR (write_statline): can't track socket %s.\n",
107        "*ERROR (write_statline): bad type S_IFMT %s.\n"
108};
109
110static char *read_formats[] = {
111        "",
112        "%d(%d.%d.%o)\n",       /* S_IFCHR */
113        "%d(%d.%d.%o)\n",       /* S_IFDIR */
114        "%d(%d.%d.%o)\n",       /* S_IFBLK */
115        "%x(%d.%d.%o)%ld\n",    /* S_IFREG */
116        "",                     /* S_IFLNK */
117        "",                     /* S_IFSOCK */
118        ""                      /* S_IFMT */
119};
120
121char type_char[] = " cdbfls*89ABCDEF";
122
123/*
124 * Place a time-stamp line in the format:
125 *       type file uid.gid.mode.time
126 * or a suitable derivate thereof
127 */
128
129write_statline( path, c)
130char **path; struct currentness *c;
131{
132        char  *format, *linebuf, *name;
133        int same_name;
134        unsigned int type;
135        struct stat fromstat, *s;
136        unsigned size, curr1 = 0, extra = 0;
137
138        if ( cur_line >= maxlines) {
139                maxlines += MAXLINES;
140                size = maxlines * sizeof statfilebufs[0];
141                statfilebufs =
142                        (Statline *) ( cur_line ?
143                                       realloc( (char *) statfilebufs, size)
144                                      : malloc( size));
145                if ( ! statfilebufs) {
146                        sprintf( errmsg, "alloc failed: %d statfile lines\n",
147                                 cur_line);
148                        do_panic();
149                }
150        }
151        /*
152         * set up type-dependent currency data:
153         */
154
155        s = &c->sbuf;
156
157        switch( type = TYPE( *s)) {
158        case S_IFREG:
159                curr1 = c->cksum;
160                extra = TIME( *s);
161                break;
162        case S_IFLNK:
163                curr1 = (unsigned int) c->link;
164                break;
165        case S_IFDIR:
166                curr1 = 0;
167                break;
168        case S_IFBLK:
169                curr1 = RDEV( *s);
170                break;
171        case S_IFCHR:
172                curr1 = RDEV( *s);
173                break;
174        case S_IFSOCK:
175                sprintf( errmsg, "can't track socket %s.\n", path[ ROOT]);
176                do_gripe();
177                return( type);
178        case S_IFMT:
179        default:
180                sprintf( errmsg,
181                "bad type for inode %d, pathname %s.\n\tapparent type = %c\n",
182                    c->sbuf.st_ino, path[ ROOT], type_char[ type >> 13]);
183                do_panic();
184        }
185        /* set up name & sortkey:
186         * root is a special case:
187         * its "relative path" is "", which dec_statfile() can't read.
188         */
189        name = path[ NAME];
190        if ( !*name) name = "/";
191
192        KEYCPY( statfilebufs[ cur_line].sortkey, name);
193
194        linebuf = statfilebufs[ cur_line].line;
195
196        /* if this entry's fromfile != cmpfile,
197         * the subscribing machine needs to know:
198         */
199        same_name = ! strcmp( path[ NAME], c->name);
200
201        /* to choose printing format, convert type-bits to array-index:
202         * the formats specify 3-7 arguments, according to type:
203         */
204        format = write_formats[ type >> 13];
205
206        sprintf( linebuf, format, name, same_name ? '=' : '~' , curr1,
207                 UID( *s), GID( *s), MODE( *s), extra);
208
209        cur_line++;
210
211        if ( verboseflag)
212                fputs( linebuf, stderr);
213
214        if ( same_name);
215        else if ( ! (*statf)( path[ ROOT], &fromstat))
216                type = TYPE( fromstat);
217        else {
218                sprintf( errmsg, "(write_statline) can't %s %s\n",
219                         statn, path[ ROOT]);
220                do_panic();
221        }
222        return( type);
223}
224
225fake_link( root, name, c) char *root, *name; struct currentness *c; {
226
227        /* it is difficult to fool write_statline(),
228         * update_file(), and curr_diff() all at once.
229         * write_statline() can't take a normal currency in this case,
230         * because it can't know the subscriber's fromroot.
231         * curr_diff() needs to see a normal link's currency,
232         * or else unneccessary link-updates will occur.
233         * update_file() can use a normal currency to make a link.
234         */
235        if ( name != c->name)           /* speed hack for dec_statfile() */
236                strcpy( c->name, name);
237
238        if ( *root)
239                sprintf( c->link, "%s/%s", root, name);
240        else    *c->link = '\0';        /* special case for write_statline() */
241
242        c->cksum = 0;
243        clear_stat( &c->sbuf);
244        c->sbuf.st_mode = S_IFLNK;
245}
246
247sort_stat() {
248        int i;
249
250        /* NOTE: this qsort call assumes that each statfilebufs[] element
251         * begins with a sortkey as its first field.
252         */
253        qsort( (char *) statfilebufs, cur_line,
254                sizeof( statfilebufs[ 0]), strcmp);
255
256        for ( i = 0; i < cur_line; i++) {
257                fputs( statfilebufs[ i].line, statfile);
258        }
259}
260
261sort_entries() {
262        char *tail;
263        Table list;
264        Entry *A, *C;
265        int i, j;
266
267        list.table = NULL;
268        list.shift = 0;
269
270        /* NOTE: we assume that each entry begins with a sortkey string.
271         * don't include entries[ 0] in the sort:
272         */
273        qsort( (char *)& entries[ 1], entrycnt - 1,
274               sizeof(   entries[ 1]), strcmp);
275
276        /* for each entry's fromfile (call it A),
277         * look for A's children amongst the subsequent entries,
278         * and add any that you find to A's exception-table.
279         * note that this may overfill the hash-table; in this event,
280         * we accept the performance-hit, and don't try to rehash.
281         */
282        for (     A = &entries[ i = 1  ]; i < entrycnt; A = &entries[ ++i]) {
283            for ( C = &entries[ j = i+1]; j < entrycnt; C = &entries[ ++j]) {
284                switch(  keyncmp( C->sortkey, i)) {
285                case 1:  break;                 /* get next A */
286                case 0:  tail = C->fromfile + A->keylen;
287                         while( '/' == *tail) tail++;
288                         if ( A->names.table)
289                             store( add_list_elt( tail, DONT_TRACK, NULL),
290                                    &A->names);
291                         else add_list_elt( tail, DONT_TRACK, LIST( list));
292                case -1: continue;      /* unlikely */
293                }
294                break; /* get next A */
295            }
296            /* if A doesn't already have an exception-list of names,
297             * then we've accumulated the list of descendant-entries
298             * in the list{} structure; convert it to a hash-table for A:
299             */
300            if ( ! list.table);
301            else if ( ! A->names.table) {
302                     A->names.table = list.table;
303                     A->names.shift = list.shift;
304                     list.table = NULL;
305                     list.shift = 0;
306                     list2hashtable( &A->names);
307            }
308            else {
309                     sprintf(errmsg, "sort_entries: internal error\n");
310                     do_panic();
311            }
312        }
313}
314
315/*
316 * Decode a statfile line into its individual fields.
317 * setup TYPE(), UID(), GID(), MODE(), TIME(), & RDEV() contents.
318 */
319
320char *
321dec_statfile( line, c)
322char *line; struct currentness *c;
323{
324        struct stat *s;
325        int *extra = 0;
326        char *end, *format, *name, same_name, type;
327        int dummy, *curr1 = &dummy, d = 0, u = 0, g = 0, m = 0;
328
329        /* these long-int temps are necessary for pc/rt compatibility:
330         * sscanf cannot scan into a short, though it may sometimes succeed
331         * in doing so. the difficulty is that it can't know about the
332         * target-integer's length, so it assumes that it's long.
333         * since the rt will truncate a short's addr, in order to treat
334         * it as a long, sscanf's data will often get lost.
335         * the solution is to give scanf longs, and then to convert these
336         * longs to shorts, explicitly.
337         */
338
339        /* for speed, we laboriously parse the type-independent part
340         * of the statline without calling sscanf().
341         * thus, we avoid copying the pathname strings around,
342         * since the caller can re-use the originals,
343         * once they're broken out of the line-format.
344         */
345        type = *line++;
346        name = line;
347        line = index( line, ' ');
348        if ( ! line) {
349                sprintf( errmsg, "garbled statfile: bad line =\n%s\n", line);
350                sprintf( errmsg, "line has only one field\n");
351                do_panic();
352        }
353        *line++ = '\0';
354
355        /* in the  statfile, which contains only relative pathnames,
356         * "/" is the only pathname which can begin with a slash.
357         * in entries[], the root appears as "", which is more natural,
358         * because "" is "/"'s pathname relative to the mount-point fromroot.
359         * and because pushpath() prepends slashes in the right places, anyway.
360         * "" is hard to read with sscanf(), so we handle "/" specially:
361         */
362        if ( '/' != *name);
363        else if ( ! name[ 1]) *name = '\0';
364        else {
365                sprintf(errmsg, "statfile passed an absolute pathname: %s\n",
366                        name);
367                do_gripe();
368        }
369        /* the subscriber needs to know whether the currency-data
370         * came from the remote fromfile or from the remote cmpfile.
371         */
372        switch( same_name = *line++) {
373        case '=': strcpy( c->name, name);
374                  break;
375        case '~': *c->name = '\0';
376                  break;
377        default:
378                sprintf( errmsg, "garbled statfile: bad line =\n%s\n", line);
379                sprintf( errmsg, "bad equality flag = %c\n", same_name);
380                do_panic();
381        }
382        *c->link = '\0';
383        c->cksum = 0;
384
385        /*
386         * set up scanf arg's for type-dependent currency-data:
387         */
388
389        s = &c->sbuf;
390        clear_stat( s);
391
392        switch( type) {
393        case 'f':
394                s->st_mode = S_IFREG;
395                curr1 = ( int*)&c->cksum;
396                extra = ( int*)&s->st_mtime;
397                break;
398        case 'l':       /* more common than dir's */
399                s->st_mode = S_IFLNK;
400                if ( end = index( line, '\n'))
401                        *end = '\0';
402                else {
403                        sprintf( errmsg, "garbled statfile: bad line =\n%s\n",
404                                line);
405                        sprintf( errmsg, "line doesn't end with a newline\n");
406                        do_panic();
407                }
408                if ( !*line) fake_link( fromroot, c->name, c);
409                else strcpy( c->link, line);
410                break;
411        case 'd':
412                s->st_mode = S_IFDIR;
413                curr1 = &dummy;
414                break;
415        case 'b':
416                s->st_mode = S_IFBLK;
417                curr1 = &d;
418                break;
419        case 'c':
420                s->st_mode = S_IFCHR;
421                curr1 = &d;
422                break;
423        default:
424                sprintf( errmsg, "garbled statfile: bad line =\n%s\n", line);
425                sprintf( errmsg, "first char isn't a file-type [fldbc]\n");
426                do_panic();
427        }
428        /* if we've already parsed the line,
429         * as in S_IFLNK case, skip the sscanf call:
430         */
431        if ( *(format = read_formats[ s->st_mode >> 13]))
432                sscanf( line, format, curr1, &u, &g, &m, extra);
433
434        s->st_uid   = (short) u;
435        s->st_gid   = (short) g;
436        s->st_mode |= (short) m & 07777;
437        s->st_rdev  = (short) d;
438        return( name);
439}
440
441/* the match abstractions implement a synchronized traversal
442 * of the entries[] array & the statfile. both sets of data
443 * are sorted by strcmp() on their sortkeys, which are made
444 * via the KEYCPY macro.
445 */
446
447int cur_ent = 0;        /* not global! index into entries[]. */
448
449init_next_match() {
450        cur_ent = 1;
451}
452
453int
454get_next_match( name)
455char *name;
456{
457        char key[ LINELEN];
458
459        KEYCPY( key, name);
460
461        for ( ; cur_ent < entrycnt; cur_ent++)
462                switch ( keyncmp( key, cur_ent)) {
463                case 0:  return( cur_ent);
464                case -1: return( 0);            /* advance name's    key */
465                case 1:  continue;              /* advance cur_ent's key */
466                }
467
468        return( -1); /* out of entries */
469}
470
471/* last_match:
472 * we know that path begins with entnum's fromfile,
473 * but path may also match other entries.
474 * for example, the path /a/b/c matches the entries /a & /a/b.
475 * we want the longest entry that matches.
476 * look-ahead to find last sub-list entry that matches
477 * the current pathname: this works because by keyncmp,
478 *          /   is greater than         /Z...
479 *         |    matches                 /a
480 *         |    is greater than         /a/a
481 *         \    matches                 /a/b
482 * /a/b/c  <    is greater than         /a/b/a
483 *         /    matches                 /a/b/c
484 *         |    is less than            /a/b/c/a
485 *         |    is less than            /a/b/d
486 *          \   is less than            /a/c...
487 * NOTE that the right-hand column is sorted by key,
488 * and that the last match is the longest.
489 */
490int
491last_match( path, entnum) char *path; int entnum; {
492        int i;
493        char key[ LINELEN];
494
495        KEYCPY( key, path);
496        for ( i = entnum + 1; i < entrycnt; i++) {
497                switch ( keyncmp( key, i)) {
498                case -1: break;         /* quit at   /a/b/d */
499                case 0:  entnum = i;    /* remember  /a or /a/b */
500                case 1:  continue;      /* skip over /a/a */
501                }
502                break; /* quit loop */
503        }
504        return( entnum);
505}
506
507/* compare the path r with i's sortkey in the following way:
508 * if the key is a subpath of r, return 0, as a match.
509 * if the key is < or > r, return 1 or -1  respectively.
510 */
511int
512keyncmp( r, i) char *r; int i; {
513        char *l;
514        int diff, n;
515
516        l = entries[ i].sortkey;
517        n = entries[ i].keylen;
518
519        diff = SIGN( strncmp( r, l, n));
520
521        /* if diff == 0 & n != 0 ( l isn't root) &
522         * r[n] == '\0' or '\001', we have a match.
523         * if n == 0, l is root, which matches everything.
524         */
525        return( diff? diff: n ? ( (unsigned) r[n] > '\001') : 0);   /* XXX */
526}
527
528struct currentness *
529dec_entry( entnum, fr, to, cmp, tail)
530int entnum; char *fr[], *to[], *cmp[], *tail; {
531        static struct currentness currency_buf, *entry_currency;
532        int i;
533        Entry *e;
534        static int xref_flag = 0;
535
536        /* this routine's main purpose is to transfer entnum's contents
537         * to the paths fr, to, & cmp.
538         * for efficiency and data-hiding reasons, we maintain various static
539         * data, including the currentness data for readstat's last update.
540         */
541
542        /* we avoid calling get_currentness(cmp) redundantly, but not just
543         * for efficiency reasons: this helps our update-simulation for
544         * nopullflag-support. see update_file().
545         * each cmpfile may get updated at most once;
546         * if it does, update_file() will mark its currency "out-of-date".
547         * xref_flag is set if any entry uses another entry's file
548         * as a cmpfile. in this case, we have to search entries[]
549         * to see if what we've modified is another entry's cmpfile.
550         * if so, propagate the "out-of-date" mark to that entry.
551         */
552        if ( xref_flag && updated( &currency_buf, NULL)) {
553                for ( e = &entries[ i = 1]; i <= entrycnt; e = &entries[ ++i]) {
554                        if ( updated( &e->currency, NULL) ||
555                             strcmp(   e->cmpfile,   currency_buf.name));
556                        else updated( &e->currency, &currency_buf);
557                }
558        }
559        currency_buf.sbuf.st_mode = S_IFMT;     /* kill short-term data. */
560
561        if ( ! tail || ! *tail) {
562
563                /* a subtler, longer search would set this flag less often.
564                 */
565                if ( ! writeflag)
566                        xref_flag = strncmp( entries[ entnum].cmpfile,
567                                             entries[ entnum].tofile,
568                                     strlen( entries[ entnum].tofile));
569
570                poppath( fr); pushpath( fr,  entries[ entnum].fromfile);
571                poppath( to); pushpath( to,  entries[ entnum].tofile);
572                poppath(cmp); pushpath( cmp, entries[ entnum].cmpfile);
573               
574                /* this function-var is global, and used generally.
575                 */
576                statf = entries[ entnum].followlink ?  stat  :  lstat;
577                statn = entries[ entnum].followlink ? "stat" : "lstat";
578
579                entry_currency = &entries[ entnum].currency;
580        }
581        if ( updated( entry_currency, NULL))
582                get_currentness( cmp, entry_currency);
583
584        if ( ! tail || ! *tail )
585                return( entry_currency);
586
587        if ( S_IFDIR == TYPE( entry_currency->sbuf)) { /* usual case */
588                pushpath( cmp, tail);
589                get_currentness( cmp, &currency_buf);
590                poppath( cmp);
591                return( &currency_buf);
592        }
593        /* rarely, a directory may have a non-dir as its cmpfile.
594         * if the entry's cmpfile isn't a dir, then cmp[ ROOT],
595         * is the comparison-file for each of the tofile's dependents.
596         */
597        return( entry_currency);
598}
599
600/* these routines handle a stack of pointers into a character-string,
601 * which typically is a UNIX pathname.
602 * the first element CNT of the stack points to a depth-counter.
603 * the second element ROOT points to the beginning of the pathname string.
604 * subsequent elements point to terminal substrings of the pathname.
605 * a slash precedes each element.
606 */
607#define COUNT(p) (*(int*)p[CNT])
608char **
609initpath( name) char *name; {
610        char **p;
611
612        /* for each stack-element, alloc a pointer
613         * and 15 chars for a filename:
614         */
615        p =      (char **) malloc( stackmax * sizeof NULL);
616        p[ CNT] = (char *) malloc( stackmax * 15 + sizeof ((int) 0));
617        COUNT( p) = 1;
618        p[ ROOT] = p[ CNT] + sizeof ((int) 1);
619        strcpy( p[ ROOT], name);
620        p[ NAME] = p[ ROOT] + strlen( name);
621        return( p);
622}
623int
624pushpath( p, name) char **p; char *name; {
625        char *top;
626
627        if ( ! p) return( -1);
628        if ( ++COUNT( p) >= stackmax) {
629                sprintf( errmsg, "%s\n%s\n%s %d.\n",
630                        "path stack overflow: directory too deep:", p[ ROOT],
631                        "use -S option, with value >", stackmax);
632                do_panic();
633        }
634        if ( *name) *p[ COUNT( p)]++ = '/';
635        top = p[ COUNT( p)];
636        strcpy( top, name);
637        p[ COUNT( p) + 1] = top + strlen( top);
638        return( COUNT( p));
639}
640poppath( p) char **p; {
641        if ( ! p) return;
642        else if ( 1 >= COUNT( p)) {
643                sprintf(errmsg,"can't pop root from path-stack");
644                do_panic();
645        }
646        else if ( *p[ COUNT( p)]) p[ COUNT( p)]--;
647                /* non-null last elt; remove its initial slash */
648        *p[  COUNT( p)--] = '\0';
649        return;
650}
Note: See TracBrowser for help on using the repository browser.