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

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