xref: /dragonfly/contrib/cvs-1.12/src/entries.c (revision abf903a5)
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 }
1134