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