]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - 6/contrib/cvs/src/entries.c
merge fix for boot-time hang on centos' xen
[FreeBSD/FreeBSD.git] / 6 / contrib / cvs / src / entries.c
1 /*
2  * Copyright (c) 1992, Brian Berliner and Jeff Polk
3  * Copyright (c) 1989-1992, Brian Berliner
4  * 
5  * You may distribute under the terms of the GNU General Public License as
6  * specified in the README file that comes with the CVS source distribution.
7  * 
8  * Entries file to Files file
9  * 
10  * Creates the file Files containing the names that comprise the project, from
11  * the Entries file.
12  */
13
14 /*
15  * $FreeBSD$
16  */
17 #include "cvs.h"
18 #include "getline.h"
19
20 static Node *AddEntryNode PROTO((List * list, Entnode *entnode));
21
22 static Entnode *fgetentent PROTO((FILE *, char *, int *));
23 static int   fputentent PROTO((FILE *, Entnode *));
24
25 static Entnode *subdir_record PROTO((int, const char *, const char *));
26
27 static FILE *entfile;
28 static char *entfilename;               /* for error messages */
29
30
31
32 /*
33  * Construct an Entnode
34  */
35 static Entnode *Entnode_Create PROTO ((enum ent_type, const char *,
36                                        const char *, const char *,
37                                        const char *, const char *,
38                                        const char *, const char *));
39
40 static Entnode *
41 Entnode_Create(type, user, vn, ts, options, tag, date, ts_conflict)
42     enum ent_type type;
43     const char *user;
44     const char *vn;
45     const char *ts;
46     const char *options;
47     const char *tag;
48     const char *date;
49     const char *ts_conflict;
50 {
51     Entnode *ent;
52     
53     /* Note that timestamp and options must be non-NULL */
54     ent = (Entnode *) xmalloc (sizeof (Entnode));
55     ent->type      = type;
56     ent->user      = xstrdup (user);
57     ent->version   = xstrdup (vn);
58     ent->timestamp = xstrdup (ts ? ts : "");
59     ent->options   = xstrdup (options ? options : "");
60     ent->tag       = xstrdup (tag);
61     ent->date      = xstrdup (date);
62     ent->conflict  = xstrdup (ts_conflict);
63
64     return ent;
65 }
66
67 /*
68  * Destruct an Entnode
69  */
70 static void Entnode_Destroy PROTO ((Entnode *));
71
72 static void
73 Entnode_Destroy (ent)
74     Entnode *ent;
75 {
76     free (ent->user);
77     free (ent->version);
78     free (ent->timestamp);
79     free (ent->options);
80     if (ent->tag)
81         free (ent->tag);
82     if (ent->date)
83         free (ent->date);
84     if (ent->conflict)
85         free (ent->conflict);
86     free (ent);
87 }
88
89 /*
90  * Write out the line associated with a node of an entries file
91  */
92 static int write_ent_proc PROTO ((Node *, void *));
93 static int
94 write_ent_proc (node, closure)
95      Node *node;
96      void *closure;
97 {
98     Entnode *entnode = node->data;
99
100     if (closure != NULL && entnode->type != ENT_FILE)
101         *(int *) closure = 1;
102
103     if (fputentent(entfile, entnode))
104         error (1, errno, "cannot write %s", entfilename);
105
106     return (0);
107 }
108
109 /*
110  * write out the current entries file given a list,  making a backup copy
111  * first of course
112  */
113 static void
114 write_entries (list)
115     List *list;
116 {
117     int sawdir;
118
119     sawdir = 0;
120
121     /* open the new one and walk the list writing entries */
122     entfilename = CVSADM_ENTBAK;
123     entfile = CVS_FOPEN (entfilename, "w+");
124     if (entfile == NULL)
125     {
126         /* Make this a warning, not an error.  For example, one user might
127            have checked out a working directory which, for whatever reason,
128            contains an Entries.Log file.  A second user, without write access
129            to that working directory, might want to do a "cvs log".  The
130            problem rewriting Entries shouldn't affect the ability of "cvs log"
131            to work, although the warning is probably a good idea so that
132            whether Entries gets rewritten is not an inexplicable process.  */
133         /* FIXME: should be including update_dir in message.  */
134         error (0, errno, "cannot rewrite %s", entfilename);
135
136         /* Now just return.  We leave the Entries.Log file around.  As far
137            as I know, there is never any data lying around in 'list' that
138            is not in Entries.Log at this time (if there is an error writing
139            Entries.Log that is a separate problem).  */
140         return;
141     }
142
143     (void) walklist (list, write_ent_proc, (void *) &sawdir);
144     if (! sawdir)
145     {
146         struct stickydirtag *sdtp;
147
148         /* We didn't write out any directories.  Check the list
149            private data to see whether subdirectory information is
150            known.  If it is, we need to write out an empty D line.  */
151         sdtp = list->list->data;
152         if (sdtp == NULL || sdtp->subdirs)
153             if (fprintf (entfile, "D\n") < 0)
154                 error (1, errno, "cannot write %s", entfilename);
155     }
156     if (fclose (entfile) == EOF)
157         error (1, errno, "error closing %s", entfilename);
158
159     /* now, atomically (on systems that support it) rename it */
160     rename_file (entfilename, CVSADM_ENT);
161
162     /* now, remove the log file */
163     if (unlink_file (CVSADM_ENTLOG) < 0
164         && !existence_error (errno))
165         error (0, errno, "cannot remove %s", CVSADM_ENTLOG);
166 }
167
168
169
170 /*
171  * Removes the argument file from the Entries file if necessary.
172  */
173 void
174 Scratch_Entry (list, fname)
175     List *list;
176     const char *fname;
177 {
178     Node *node;
179
180     if (trace)
181         (void) fprintf (stderr, "%s-> Scratch_Entry(%s)\n",
182                         CLIENT_SERVER_STR, fname);
183
184     /* hashlookup to see if it is there */
185     if ((node = findnode_fn (list, fname)) != NULL)
186     {
187         if (!noexec)
188         {
189             entfilename = CVSADM_ENTLOG;
190             entfile = open_file (entfilename, "a");
191
192             if (fprintf (entfile, "R ") < 0)
193                 error (1, errno, "cannot write %s", entfilename);
194
195             write_ent_proc (node, NULL);
196
197             if (fclose (entfile) == EOF)
198                 error (1, errno, "error closing %s", entfilename);
199         }
200
201         delnode (node);                 /* delete the node */
202
203 #ifdef SERVER_SUPPORT
204         if (server_active)
205             server_scratch (fname);
206 #endif
207     }
208 }
209
210
211
212 /*
213  * Enters the given file name/version/time-stamp into the Entries file,
214  * removing the old entry first, if necessary.
215  */
216 void
217 Register (list, fname, vn, ts, options, tag, date, ts_conflict)
218     List *list;
219     const char *fname;
220     const char *vn;
221     const char *ts;
222     const char *options;
223     const char *tag;
224     const char *date;
225     const char *ts_conflict;
226 {
227     Entnode *entnode;
228     Node *node;
229
230 #ifdef SERVER_SUPPORT
231     if (server_active)
232     {
233         server_register (fname, vn, ts, options, tag, date, ts_conflict);
234     }
235 #endif
236
237     if (trace)
238     {
239         (void) fprintf (stderr, "%s-> Register(%s, %s, %s%s%s, %s, %s %s)\n",
240                         CLIENT_SERVER_STR,
241                         fname, vn, ts ? ts : "",
242                         ts_conflict ? "+" : "", ts_conflict ? ts_conflict : "",
243                         options, tag ? tag : "", date ? date : "");
244     }
245
246     entnode = Entnode_Create (ENT_FILE, fname, vn, ts, options, tag, date,
247                               ts_conflict);
248     node = AddEntryNode (list, entnode);
249
250     if (!noexec)
251     {
252         entfilename = CVSADM_ENTLOG;
253         entfile = CVS_FOPEN (entfilename, "a");
254
255         if (entfile == NULL)
256         {
257             /* Warning, not error, as in write_entries.  */
258             /* FIXME-update-dir: should be including update_dir in message.  */
259             error (0, errno, "cannot open %s", entfilename);
260             return;
261         }
262
263         if (fprintf (entfile, "A ") < 0)
264             error (1, errno, "cannot write %s", entfilename);
265
266         write_ent_proc (node, NULL);
267
268         if (fclose (entfile) == EOF)
269             error (1, errno, "error closing %s", entfilename);
270     }
271 }
272
273 /*
274  * Node delete procedure for list-private sticky dir tag/date info
275  */
276 static void
277 freesdt (p)
278     Node *p;
279 {
280     struct stickydirtag *sdtp = p->data;
281
282     if (sdtp->tag)
283         free (sdtp->tag);
284     if (sdtp->date)
285         free (sdtp->date);
286     free ((char *) sdtp);
287 }
288
289 /* Return the next real Entries line.  On end of file, returns NULL.
290    On error, prints an error message and returns NULL.  */
291
292 static Entnode *
293 fgetentent(fpin, cmd, sawdir)
294     FILE *fpin;
295     char *cmd;
296     int *sawdir;
297 {
298     Entnode *ent;
299     char *line;
300     size_t line_chars_allocated;
301     register char *cp;
302     enum ent_type type;
303     char *l, *user, *vn, *ts, *options;
304     char *tag_or_date, *tag, *date, *ts_conflict;
305     int line_length;
306
307     line = NULL;
308     line_chars_allocated = 0;
309
310     ent = NULL;
311     while ((line_length = getline (&line, &line_chars_allocated, fpin)) > 0)
312     {
313         l = line;
314
315         /* If CMD is not NULL, we are reading an Entries.Log file.
316            Each line in the Entries.Log file starts with a single
317            character command followed by a space.  For backward
318            compatibility, the absence of a space indicates an add
319            command.  */
320         if (cmd != NULL)
321         {
322             if (l[1] != ' ')
323                 *cmd = 'A';
324             else
325             {
326                 *cmd = l[0];
327                 l += 2;
328             }
329         }
330
331         type = ENT_FILE;
332
333         if (l[0] == 'D')
334         {
335             type = ENT_SUBDIR;
336             *sawdir = 1;
337             ++l;
338             /* An empty D line is permitted; it is a signal that this
339                Entries file lists all known subdirectories.  */
340         }
341
342         if (l[0] != '/')
343             continue;
344
345         user = l + 1;
346         if ((cp = strchr (user, '/')) == NULL)
347             continue;
348         *cp++ = '\0';
349         vn = cp;
350         if ((cp = strchr (vn, '/')) == NULL)
351             continue;
352         *cp++ = '\0';
353         ts = cp;
354         if ((cp = strchr (ts, '/')) == NULL)
355             continue;
356         *cp++ = '\0';
357         options = cp;
358         if ((cp = strchr (options, '/')) == NULL)
359             continue;
360         *cp++ = '\0';
361         tag_or_date = cp;
362         if ((cp = strchr (tag_or_date, '\n')) == NULL)
363             continue;
364         *cp = '\0';
365         tag = (char *) NULL;
366         date = (char *) NULL;
367         if (*tag_or_date == 'T')
368             tag = tag_or_date + 1;
369         else if (*tag_or_date == 'D')
370             date = tag_or_date + 1;
371
372         if ((ts_conflict = strchr (ts, '+')))
373             *ts_conflict++ = '\0';
374             
375         /*
376          * XXX - Convert timestamp from old format to new format.
377          *
378          * If the timestamp doesn't match the file's current
379          * mtime, we'd have to generate a string that doesn't
380          * match anyways, so cheat and base it on the existing
381          * string; it doesn't have to match the same mod time.
382          *
383          * For an unmodified file, write the correct timestamp.
384          */
385         {
386             struct stat sb;
387             if (strlen (ts) > 30 && CVS_STAT (user, &sb) == 0)
388             {
389                 char *c = ctime (&sb.st_mtime);
390                 /* Fix non-standard format.  */
391                 if (c[8] == '0') c[8] = ' ';
392
393                 if (!strncmp (ts + 25, c, 24))
394                     ts = time_stamp (user);
395                 else
396                 {
397                     ts += 24;
398                     ts[0] = '*';
399                 }
400             }
401         }
402
403         ent = Entnode_Create (type, user, vn, ts, options, tag, date,
404                               ts_conflict);
405         break;
406     }
407
408     if (line_length < 0 && !feof (fpin))
409         error (0, errno, "cannot read entries file");
410
411     free (line);
412     return ent;
413 }
414
415 static int
416 fputentent(fp, p)
417     FILE *fp;
418     Entnode *p;
419 {
420     switch (p->type)
421     {
422     case ENT_FILE:
423         break;
424     case ENT_SUBDIR:
425         if (fprintf (fp, "D") < 0)
426             return 1;
427         break;
428     }
429
430     if (fprintf (fp, "/%s/%s/%s", p->user, p->version, p->timestamp) < 0)
431         return 1;
432     if (p->conflict)
433     {
434         if (fprintf (fp, "+%s", p->conflict) < 0)
435             return 1;
436     }
437     if (fprintf (fp, "/%s/", p->options) < 0)
438         return 1;
439
440     if (p->tag)
441     {
442         if (fprintf (fp, "T%s\n", p->tag) < 0)
443             return 1;
444     }
445     else if (p->date)
446     {
447         if (fprintf (fp, "D%s\n", p->date) < 0)
448             return 1;
449     }
450     else 
451     {
452         if (fprintf (fp, "\n") < 0)
453             return 1;
454     }
455
456     return 0;
457 }
458
459
460 /* Read the entries file into a list, hashing on the file name.
461
462    UPDATE_DIR is the name of the current directory, for use in error
463    messages, or NULL if not known (that is, noone has gotten around
464    to updating the caller to pass in the information).  */
465 List *
466 Entries_Open (aflag, update_dir)
467     int aflag;
468     char *update_dir;
469 {
470     List *entries;
471     struct stickydirtag *sdtp = NULL;
472     Entnode *ent;
473     char *dirtag, *dirdate;
474     int dirnonbranch;
475     int do_rewrite = 0;
476     FILE *fpin;
477     int sawdir;
478
479     /* get a fresh list... */
480     entries = getlist ();
481
482     /*
483      * Parse the CVS/Tag file, to get any default tag/date settings. Use
484      * list-private storage to tuck them away for Version_TS().
485      */
486     ParseTag (&dirtag, &dirdate, &dirnonbranch);
487     if (aflag || dirtag || dirdate)
488     {
489         sdtp = (struct stickydirtag *) xmalloc (sizeof (*sdtp));
490         memset ((char *) sdtp, 0, sizeof (*sdtp));
491         sdtp->aflag = aflag;
492         sdtp->tag = xstrdup (dirtag);
493         sdtp->date = xstrdup (dirdate);
494         sdtp->nonbranch = dirnonbranch;
495
496         /* feed it into the list-private area */
497         entries->list->data = sdtp;
498         entries->list->delproc = freesdt;
499     }
500
501     sawdir = 0;
502
503     fpin = CVS_FOPEN (CVSADM_ENT, "r");
504     if (fpin == NULL)
505     {
506         if (update_dir != NULL)
507             error (0, 0, "in directory %s:", update_dir);
508         error (0, errno, "cannot open %s for reading", CVSADM_ENT);
509     }
510     else
511     {
512         while ((ent = fgetentent (fpin, (char *) NULL, &sawdir)) != NULL) 
513         {
514             (void) AddEntryNode (entries, ent);
515         }
516
517         if (fclose (fpin) < 0)
518             /* FIXME-update-dir: should include update_dir in message.  */
519             error (0, errno, "cannot close %s", CVSADM_ENT);
520     }
521
522     fpin = CVS_FOPEN (CVSADM_ENTLOG, "r");
523     if (fpin != NULL) 
524     {
525         char cmd;
526         Node *node;
527
528         while ((ent = fgetentent (fpin, &cmd, &sawdir)) != NULL)
529         {
530             switch (cmd)
531             {
532             case 'A':
533                 (void) AddEntryNode (entries, ent);
534                 break;
535             case 'R':
536                 node = findnode_fn (entries, ent->user);
537                 if (node != NULL)
538                     delnode (node);
539                 Entnode_Destroy (ent);
540                 break;
541             default:
542                 /* Ignore unrecognized commands.  */
543                 break;
544             }
545         }
546         do_rewrite = 1;
547         if (fclose (fpin) < 0)
548             /* FIXME-update-dir: should include update_dir in message.  */
549             error (0, errno, "cannot close %s", CVSADM_ENTLOG);
550     }
551
552     /* Update the list private data to indicate whether subdirectory
553        information is known.  Nonexistent list private data is taken
554        to mean that it is known.  */
555     if (sdtp != NULL)
556         sdtp->subdirs = sawdir;
557     else if (! sawdir)
558     {
559         sdtp = (struct stickydirtag *) xmalloc (sizeof (*sdtp));
560         memset ((char *) sdtp, 0, sizeof (*sdtp));
561         sdtp->subdirs = 0;
562         entries->list->data = sdtp;
563         entries->list->delproc = freesdt;
564     }
565
566     if (do_rewrite && !noexec)
567         write_entries (entries);
568
569     /* clean up and return */
570     if (dirtag)
571         free (dirtag);
572     if (dirdate)
573         free (dirdate);
574     return (entries);
575 }
576
577 void
578 Entries_Close(list)
579     List *list;
580 {
581     if (list)
582     {
583         if (!noexec) 
584         {
585             if (isfile (CVSADM_ENTLOG))
586                 write_entries (list);
587         }
588         dellist(&list);
589     }
590 }
591
592
593 /*
594  * Free up the memory associated with the data section of an ENTRIES type
595  * node
596  */
597 static void
598 Entries_delproc (node)
599     Node *node;
600 {
601     Entnode *p = node->data;
602
603     Entnode_Destroy(p);
604 }
605
606 /*
607  * Get an Entries file list node, initialize it, and add it to the specified
608  * list
609  */
610 static Node *
611 AddEntryNode (list, entdata)
612     List *list;
613     Entnode *entdata;
614 {
615     Node *p;
616
617     /* was it already there? */
618     if ((p  = findnode_fn (list, entdata->user)) != NULL)
619     {
620         /* take it out */
621         delnode (p);
622     }
623
624     /* get a node and fill in the regular stuff */
625     p = getnode ();
626     p->type = ENTRIES;
627     p->delproc = Entries_delproc;
628
629     /* this one gets a key of the name for hashing */
630     /* FIXME This results in duplicated data --- the hash package shouldn't
631        assume that the key is dynamically allocated.  The user's free proc
632        should be responsible for freeing the key. */
633     p->key = xstrdup (entdata->user);
634     p->data = entdata;
635
636     /* put the node into the list */
637     addnode (list, p);
638     return (p);
639 }
640
641 static char *root_template;
642
643 static int
644 get_root_template(const char *repository, const char *path)
645 {
646     if (root_template) {
647         if (strcmp(path, root_template) == 0)
648             return(0);
649         free(root_template);
650     }
651     if ((root_template = strdup(path)) == NULL)
652         return(-1);
653     return(0);
654 }
655
656 /*
657  * Write out/Clear the CVS/Template file.
658  */
659 void
660 WriteTemplate (dir, update_dir)
661     const char *dir;
662     const char *update_dir;
663 {
664     char *tmp = NULL;
665     struct stat st1;
666     struct stat st2;
667
668     if (Parse_Info(CVSROOTADM_RCSINFO, "cvs", get_root_template, 1) < 0)
669         return;
670
671     if (asprintf(&tmp, "%s/%s", dir, CVSADM_TEMPLATE) < 0)
672         error (1, errno, "out of memory");
673
674     if (stat(root_template, &st1) == 0) {
675         if (stat(tmp, &st2) < 0 || st1.st_mtime != st2.st_mtime) {
676             FILE *fi;
677             FILE *fo;
678
679             if ((fi = open_file(root_template, "r")) != NULL) {
680                 if ((fo = open_file(tmp, "w")) != NULL) {
681                     int n;
682                     char buf[256];
683
684                     while ((n = fread(buf, 1, sizeof(buf), fi)) > 0)
685                         fwrite(buf, 1, n, fo);
686                     fflush(fo);
687                     if (ferror(fi) || ferror(fo)) {
688                         fclose(fo);
689                         remove(tmp);
690                         error (1, errno, "error copying Template");
691                     } else {
692                         struct timeval times[2];
693                         fclose(fo);
694                         times[0].tv_sec = st1.st_mtime;
695                         times[0].tv_usec = 0;
696                         times[1] = times[0];
697                         utimes(tmp, times);
698                     }
699                 } 
700                 fclose(fi);
701             }
702         }
703     }
704     free(tmp);
705 }
706
707 /*
708  * Write out/Clear the CVS/Tag file.
709  */
710 void
711 WriteTag (dir, tag, date, nonbranch, update_dir, repository)
712     const char *dir;
713     const char *tag;
714     const char *date;
715     int nonbranch;
716     const char *update_dir;
717     const char *repository;
718 {
719     FILE *fout;
720     char *tmp;
721
722     if (noexec)
723         return;
724
725     tmp = xmalloc ((dir ? strlen (dir) : 0)
726                    + sizeof (CVSADM_TAG)
727                    + 10);
728     if (dir == NULL)
729         (void) strcpy (tmp, CVSADM_TAG);
730     else
731         (void) sprintf (tmp, "%s/%s", dir, CVSADM_TAG);
732
733     if (tag || date)
734     {
735         fout = open_file (tmp, "w+");
736         if (tag)
737         {
738             if (nonbranch)
739             {
740                 if (fprintf (fout, "N%s\n", tag) < 0)
741                     error (1, errno, "write to %s failed", tmp);
742             }
743             else
744             {
745                 if (fprintf (fout, "T%s\n", tag) < 0)
746                     error (1, errno, "write to %s failed", tmp);
747             }
748         }
749         else
750         {
751             if (fprintf (fout, "D%s\n", date) < 0)
752                 error (1, errno, "write to %s failed", tmp);
753         }
754         if (fclose (fout) == EOF)
755             error (1, errno, "cannot close %s", tmp);
756     }
757     else
758         if (unlink_file (tmp) < 0 && ! existence_error (errno))
759             error (1, errno, "cannot remove %s", tmp);
760     free (tmp);
761 #ifdef SERVER_SUPPORT
762     if (server_active)
763         server_set_sticky (update_dir, repository, tag, date, nonbranch);
764 #endif
765 }
766
767 /* Parse the CVS/Tag file for the current directory.
768
769    If it contains a date, sets *DATEP to the date in a newly malloc'd
770    string, *TAGP to NULL, and *NONBRANCHP to an unspecified value.
771
772    If it contains a branch tag, sets *TAGP to the tag in a newly
773    malloc'd string, *NONBRANCHP to 0, and *DATEP to NULL.
774
775    If it contains a nonbranch tag, sets *TAGP to the tag in a newly
776    malloc'd string, *NONBRANCHP to 1, and *DATEP to NULL.
777
778    If it does not exist, or contains something unrecognized by this
779    version of CVS, set *DATEP and *TAGP to NULL and *NONBRANCHP to
780    an unspecified value.
781
782    If there is an error, print an error message, set *DATEP and *TAGP
783    to NULL, and return.  */
784 void
785 ParseTag (tagp, datep, nonbranchp)
786     char **tagp;
787     char **datep;
788     int *nonbranchp;
789 {
790     FILE *fp;
791
792     if (tagp)
793         *tagp = (char *) NULL;
794     if (datep)
795         *datep = (char *) NULL;
796     /* Always store a value here, even in the 'D' case where the value
797        is unspecified.  Shuts up tools which check for references to
798        uninitialized memory.  */
799     if (nonbranchp != NULL)
800         *nonbranchp = 0;
801     fp = CVS_FOPEN (CVSADM_TAG, "r");
802     if (fp)
803     {
804         char *line;
805         int line_length;
806         size_t line_chars_allocated;
807
808         line = NULL;
809         line_chars_allocated = 0;
810
811         if ((line_length = getline (&line, &line_chars_allocated, fp)) > 0)
812         {
813             /* Remove any trailing newline.  */
814             if (line[line_length - 1] == '\n')
815                 line[--line_length] = '\0';
816             switch (*line)
817             {
818                 case 'T':
819                     if (tagp != NULL)
820                         *tagp = xstrdup (line + 1);
821                     break;
822                 case 'D':
823                     if (datep != NULL)
824                         *datep = xstrdup (line + 1);
825                     break;
826                 case 'N':
827                     if (tagp != NULL)
828                         *tagp = xstrdup (line + 1);
829                     if (nonbranchp != NULL)
830                         *nonbranchp = 1;
831                     break;
832                 default:
833                     /* Silently ignore it; it may have been
834                        written by a future version of CVS which extends the
835                        syntax.  */
836                     break;
837             }
838         }
839
840         if (line_length < 0)
841         {
842             /* FIXME-update-dir: should include update_dir in messages.  */
843             if (feof (fp))
844                 error (0, 0, "cannot read %s: end of file", CVSADM_TAG);
845             else
846                 error (0, errno, "cannot read %s", CVSADM_TAG);
847         }
848
849         if (fclose (fp) < 0)
850             /* FIXME-update-dir: should include update_dir in message.  */
851             error (0, errno, "cannot close %s", CVSADM_TAG);
852
853         free (line);
854     }
855     else if (!existence_error (errno))
856         /* FIXME-update-dir: should include update_dir in message.  */
857         error (0, errno, "cannot open %s", CVSADM_TAG);
858 }
859
860 /*
861  * This is called if all subdirectory information is known, but there
862  * aren't any subdirectories.  It records that fact in the list
863  * private data.
864  */
865
866 void
867 Subdirs_Known (entries)
868      List *entries;
869 {
870     struct stickydirtag *sdtp = entries->list->data;
871
872     /* If there is no list private data, that means that the
873        subdirectory information is known.  */
874     if (sdtp != NULL && ! sdtp->subdirs)
875     {
876         FILE *fp;
877
878         sdtp->subdirs = 1;
879         if (!noexec)
880         {
881             /* Create Entries.Log so that Entries_Close will do something.  */
882             entfilename = CVSADM_ENTLOG;
883             fp = CVS_FOPEN (entfilename, "a");
884             if (fp == NULL)
885             {
886                 int save_errno = errno;
887
888                 /* As in subdir_record, just silently skip the whole thing
889                    if there is no CVSADM directory.  */
890                 if (! isdir (CVSADM))
891                     return;
892                 error (1, save_errno, "cannot open %s", entfilename);
893             }
894             else
895             {
896                 if (fclose (fp) == EOF)
897                     error (1, errno, "cannot close %s", entfilename);
898             }
899         }
900     }
901 }
902
903 /* Record subdirectory information.  */
904
905 static Entnode *
906 subdir_record (cmd, parent, dir)
907      int cmd;
908      const char *parent;
909      const char *dir;
910 {
911     Entnode *entnode;
912
913     /* None of the information associated with a directory is
914        currently meaningful.  */
915     entnode = Entnode_Create (ENT_SUBDIR, dir, "", "", "",
916                               (char *) NULL, (char *) NULL,
917                               (char *) NULL);
918
919     if (!noexec)
920     {
921         if (parent == NULL)
922             entfilename = CVSADM_ENTLOG;
923         else
924         {
925             entfilename = xmalloc (strlen (parent)
926                                    + sizeof CVSADM_ENTLOG
927                                    + 10);
928             sprintf (entfilename, "%s/%s", parent, CVSADM_ENTLOG);
929         }
930
931         entfile = CVS_FOPEN (entfilename, "a");
932         if (entfile == NULL)
933         {
934             int save_errno = errno;
935
936             /* It is not an error if there is no CVS administration
937                directory.  Permitting this case simplifies some
938                calling code.  */
939
940             if (parent == NULL)
941             {
942                 if (! isdir (CVSADM))
943                     return entnode;
944             }
945             else
946             {
947                 sprintf (entfilename, "%s/%s", parent, CVSADM);
948                 if (! isdir (entfilename))
949                 {
950                     free (entfilename);
951                     entfilename = NULL;
952                     return entnode;
953                 }
954             }
955
956             error (1, save_errno, "cannot open %s", entfilename);
957         }
958
959         if (fprintf (entfile, "%c ", cmd) < 0)
960             error (1, errno, "cannot write %s", entfilename);
961
962         if (fputentent (entfile, entnode) != 0)
963             error (1, errno, "cannot write %s", entfilename);
964
965         if (fclose (entfile) == EOF)
966             error (1, errno, "error closing %s", entfilename);
967
968         if (parent != NULL)
969         {
970             free (entfilename);
971             entfilename = NULL;
972         }
973     }
974
975     return entnode;
976 }
977
978 /*
979  * Record the addition of a new subdirectory DIR in PARENT.  PARENT
980  * may be NULL, which means the current directory.  ENTRIES is the
981  * current entries list; it may be NULL, which means that it need not
982  * be updated.
983  */
984
985 void
986 Subdir_Register (entries, parent, dir)
987      List *entries;
988      const char *parent;
989      const char *dir;
990 {
991     Entnode *entnode;
992
993     /* Ignore attempts to register ".".  These can happen in the
994        server code.  */
995     if (dir[0] == '.' && dir[1] == '\0')
996         return;
997
998     entnode = subdir_record ('A', parent, dir);
999
1000     if (entries != NULL && (parent == NULL || strcmp (parent, ".") == 0))
1001         (void) AddEntryNode (entries, entnode);
1002     else
1003         Entnode_Destroy (entnode);
1004 }
1005
1006 /*
1007  * Record the removal of a subdirectory.  The arguments are the same
1008  * as for Subdir_Register.
1009  */
1010
1011 void
1012 Subdir_Deregister (entries, parent, dir)
1013      List *entries;
1014      const char *parent;
1015      const char *dir;
1016 {
1017     Entnode *entnode;
1018
1019     entnode = subdir_record ('R', parent, dir);
1020     Entnode_Destroy (entnode);
1021
1022     if (entries != NULL && (parent == NULL || strcmp (parent, ".") == 0))
1023     {
1024         Node *p;
1025
1026         p = findnode_fn (entries, dir);
1027         if (p != NULL)
1028             delnode (p);
1029     }
1030 }
1031
1032
1033
1034 /* OK, the following base_* code tracks the revisions of the files in
1035    CVS/Base.  We do this in a file CVS/Baserev.  Separate from
1036    CVS/Entries because it needs to go in separate data structures
1037    anyway (the name in Entries must be unique), so this seemed
1038    cleaner.  The business of rewriting the whole file in
1039    base_deregister and base_register is the kind of thing we used to
1040    do for Entries and which turned out to be slow, which is why there
1041    is now the Entries.Log machinery.  So maybe from that point of
1042    view it is a mistake to do this separately from Entries, I dunno.
1043
1044    We also need something analogous for:
1045
1046    1. CVS/Template (so we can update the Template file automagically
1047    without the user needing to check out a new working directory).
1048    Updating would probably print a message (that part might be
1049    optional, although probably it should be visible because not all
1050    cvs commands would make the update happen and so it is a
1051    user-visible behavior).  Constructing version number for template
1052    is a bit hairy (base it on the timestamp on the server?  Or see if
1053    the template is in checkoutlist and if yes use its versioning and
1054    if no don't version it?)....
1055
1056    2.  cvsignore (need to keep a copy in the working directory to do
1057    "cvs release" on the client side; see comment at src/release.c
1058    (release).  Would also allow us to stop needing Questionable.  */
1059
1060 enum base_walk {
1061     /* Set the revision for FILE to *REV.  */
1062     BASE_REGISTER,
1063     /* Get the revision for FILE and put it in a newly malloc'd string
1064        in *REV, or put NULL if not mentioned.  */
1065     BASE_GET,
1066     /* Remove FILE.  */
1067     BASE_DEREGISTER
1068 };
1069
1070 static void base_walk PROTO ((enum base_walk, struct file_info *, char **));
1071
1072 /* Read through the lines in CVS/Baserev, taking the actions as documented
1073    for CODE.  */
1074
1075 static void
1076 base_walk (code, finfo, rev)
1077     enum base_walk code;
1078     struct file_info *finfo;
1079     char **rev;
1080 {
1081     FILE *fp;
1082     char *line;
1083     size_t line_allocated;
1084     FILE *newf;
1085     char *baserev_fullname;
1086     char *baserevtmp_fullname;
1087
1088     line = NULL;
1089     line_allocated = 0;
1090     newf = NULL;
1091
1092     /* First compute the fullnames for the error messages.  This
1093        computation probably should be broken out into a separate function,
1094        as recurse.c does it too and places like Entries_Open should be
1095        doing it.  */
1096     baserev_fullname = xmalloc (sizeof (CVSADM_BASEREV)
1097                                 + strlen (finfo->update_dir)
1098                                 + 2);
1099     baserev_fullname[0] = '\0';
1100     baserevtmp_fullname = xmalloc (sizeof (CVSADM_BASEREVTMP)
1101                                    + strlen (finfo->update_dir)
1102                                    + 2);
1103     baserevtmp_fullname[0] = '\0';
1104     if (finfo->update_dir[0] != '\0')
1105     {
1106         strcat (baserev_fullname, finfo->update_dir);
1107         strcat (baserev_fullname, "/");
1108         strcat (baserevtmp_fullname, finfo->update_dir);
1109         strcat (baserevtmp_fullname, "/");
1110     }
1111     strcat (baserev_fullname, CVSADM_BASEREV);
1112     strcat (baserevtmp_fullname, CVSADM_BASEREVTMP);
1113
1114     fp = CVS_FOPEN (CVSADM_BASEREV, "r");
1115     if (fp == NULL)
1116     {
1117         if (!existence_error (errno))
1118         {
1119             error (0, errno, "cannot open %s for reading", baserev_fullname);
1120             goto out;
1121         }
1122     }
1123
1124     switch (code)
1125     {
1126         case BASE_REGISTER:
1127         case BASE_DEREGISTER:
1128             newf = CVS_FOPEN (CVSADM_BASEREVTMP, "w");
1129             if (newf == NULL)
1130             {
1131                 error (0, errno, "cannot open %s for writing",
1132                        baserevtmp_fullname);
1133                 goto out;
1134             }
1135             break;
1136         case BASE_GET:
1137             *rev = NULL;
1138             break;
1139     }
1140
1141     if (fp != NULL)
1142     {
1143         while (getline (&line, &line_allocated, fp) >= 0)
1144         {
1145             char *linefile;
1146             char *p;
1147             char *linerev;
1148
1149             if (line[0] != 'B')
1150                 /* Ignore, for future expansion.  */
1151                 continue;
1152
1153             linefile = line + 1;
1154             p = strchr (linefile, '/');
1155             if (p == NULL)
1156                 /* Syntax error, ignore.  */
1157                 continue;
1158             linerev = p + 1;
1159             p = strchr (linerev, '/');
1160             if (p == NULL)
1161                 continue;
1162
1163             linerev[-1] = '\0';
1164             if (fncmp (linefile, finfo->file) == 0)
1165             {
1166                 switch (code)
1167                 {
1168                 case BASE_REGISTER:
1169                 case BASE_DEREGISTER:
1170                     /* Don't copy over the old entry, we don't want it.  */
1171                     break;
1172                 case BASE_GET:
1173                     *p = '\0';
1174                     *rev = xstrdup (linerev);
1175                     *p = '/';
1176                     goto got_it;
1177                 }
1178             }
1179             else
1180             {
1181                 linerev[-1] = '/';
1182                 switch (code)
1183                 {
1184                 case BASE_REGISTER:
1185                 case BASE_DEREGISTER:
1186                     if (fprintf (newf, "%s\n", line) < 0)
1187                         error (0, errno, "error writing %s",
1188                                baserevtmp_fullname);
1189                     break;
1190                 case BASE_GET:
1191                     break;
1192                 }
1193             }
1194         }
1195         if (ferror (fp))
1196             error (0, errno, "cannot read %s", baserev_fullname);
1197     }
1198  got_it:
1199
1200     if (code == BASE_REGISTER)
1201     {
1202         if (fprintf (newf, "B%s/%s/\n", finfo->file, *rev) < 0)
1203             error (0, errno, "error writing %s",
1204                    baserevtmp_fullname);
1205     }
1206
1207  out:
1208
1209     if (line != NULL)
1210         free (line);
1211
1212     if (fp != NULL)
1213     {
1214         if (fclose (fp) < 0)
1215             error (0, errno, "cannot close %s", baserev_fullname);
1216     }
1217     if (newf != NULL)
1218     {
1219         if (fclose (newf) < 0)
1220             error (0, errno, "cannot close %s", baserevtmp_fullname);
1221         rename_file (CVSADM_BASEREVTMP, CVSADM_BASEREV);
1222     }
1223
1224     free (baserev_fullname);
1225     free (baserevtmp_fullname);
1226 }
1227
1228 /* Return, in a newly malloc'd string, the revision for FILE in CVS/Baserev,
1229    or NULL if not listed.  */
1230
1231 char *
1232 base_get (finfo)
1233     struct file_info *finfo;
1234 {
1235     char *rev;
1236     base_walk (BASE_GET, finfo, &rev);
1237     return rev;
1238 }
1239
1240 /* Set the revision for FILE to REV.  */
1241
1242 void
1243 base_register (finfo, rev)
1244     struct file_info *finfo;
1245     char *rev;
1246 {
1247     base_walk (BASE_REGISTER, finfo, &rev);
1248 }
1249
1250 /* Remove FILE.  */
1251
1252 void
1253 base_deregister (finfo)
1254     struct file_info *finfo;
1255 {
1256     base_walk (BASE_DEREGISTER, finfo, NULL);
1257 }