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