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