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