entries.c revision 81404
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		/* Fix non-standard format.  */
385		if (c[8] == '0') c[8] = ' ';
386
387		if (!strncmp (ts + 25, c, 24))
388		    ts = time_stamp (user);
389		else
390		{
391		    ts += 24;
392		    ts[0] = '*';
393		}
394	    }
395	}
396
397	ent = Entnode_Create (type, user, vn, ts, options, tag, date,
398			      ts_conflict);
399	break;
400    }
401
402    if (line_length < 0 && !feof (fpin))
403	error (0, errno, "cannot read entries file");
404
405    free (line);
406    return ent;
407}
408
409static int
410fputentent(fp, p)
411    FILE *fp;
412    Entnode *p;
413{
414    switch (p->type)
415    {
416    case ENT_FILE:
417        break;
418    case ENT_SUBDIR:
419        if (fprintf (fp, "D") < 0)
420	    return 1;
421	break;
422    }
423
424    if (fprintf (fp, "/%s/%s/%s", p->user, p->version, p->timestamp) < 0)
425	return 1;
426    if (p->conflict)
427    {
428	if (fprintf (fp, "+%s", p->conflict) < 0)
429	    return 1;
430    }
431    if (fprintf (fp, "/%s/", p->options) < 0)
432	return 1;
433
434    if (p->tag)
435    {
436	if (fprintf (fp, "T%s\n", p->tag) < 0)
437	    return 1;
438    }
439    else if (p->date)
440    {
441	if (fprintf (fp, "D%s\n", p->date) < 0)
442	    return 1;
443    }
444    else
445    {
446	if (fprintf (fp, "\n") < 0)
447	    return 1;
448    }
449
450    return 0;
451}
452
453
454/* Read the entries file into a list, hashing on the file name.
455
456   UPDATE_DIR is the name of the current directory, for use in error
457   messages, or NULL if not known (that is, noone has gotten around
458   to updating the caller to pass in the information).  */
459List *
460Entries_Open (aflag, update_dir)
461    int aflag;
462    char *update_dir;
463{
464    List *entries;
465    struct stickydirtag *sdtp = NULL;
466    Entnode *ent;
467    char *dirtag, *dirdate;
468    int dirnonbranch;
469    int do_rewrite = 0;
470    FILE *fpin;
471    int sawdir;
472
473    /* get a fresh list... */
474    entries = getlist ();
475
476    /*
477     * Parse the CVS/Tag file, to get any default tag/date settings. Use
478     * list-private storage to tuck them away for Version_TS().
479     */
480    ParseTag (&dirtag, &dirdate, &dirnonbranch);
481    if (aflag || dirtag || dirdate)
482    {
483	sdtp = (struct stickydirtag *) xmalloc (sizeof (*sdtp));
484	memset ((char *) sdtp, 0, sizeof (*sdtp));
485	sdtp->aflag = aflag;
486	sdtp->tag = xstrdup (dirtag);
487	sdtp->date = xstrdup (dirdate);
488	sdtp->nonbranch = dirnonbranch;
489
490	/* feed it into the list-private area */
491	entries->list->data = (char *) sdtp;
492	entries->list->delproc = freesdt;
493    }
494
495    sawdir = 0;
496
497    fpin = CVS_FOPEN (CVSADM_ENT, "r");
498    if (fpin == NULL)
499    {
500	if (update_dir != NULL)
501	    error (0, 0, "in directory %s:", update_dir);
502	error (0, errno, "cannot open %s for reading", CVSADM_ENT);
503    }
504    else
505    {
506	while ((ent = fgetentent (fpin, (char *) NULL, &sawdir)) != NULL)
507	{
508	    (void) AddEntryNode (entries, ent);
509	}
510
511	if (fclose (fpin) < 0)
512	    /* FIXME-update-dir: should include update_dir in message.  */
513	    error (0, errno, "cannot close %s", CVSADM_ENT);
514    }
515
516    fpin = CVS_FOPEN (CVSADM_ENTLOG, "r");
517    if (fpin != NULL)
518    {
519	char cmd;
520	Node *node;
521
522	while ((ent = fgetentent (fpin, &cmd, &sawdir)) != NULL)
523	{
524	    switch (cmd)
525	    {
526	    case 'A':
527		(void) AddEntryNode (entries, ent);
528		break;
529	    case 'R':
530		node = findnode_fn (entries, ent->user);
531		if (node != NULL)
532		    delnode (node);
533		Entnode_Destroy (ent);
534		break;
535	    default:
536		/* Ignore unrecognized commands.  */
537	        break;
538	    }
539	}
540	do_rewrite = 1;
541	if (fclose (fpin) < 0)
542	    /* FIXME-update-dir: should include update_dir in message.  */
543	    error (0, errno, "cannot close %s", CVSADM_ENTLOG);
544    }
545
546    /* Update the list private data to indicate whether subdirectory
547       information is known.  Nonexistent list private data is taken
548       to mean that it is known.  */
549    if (sdtp != NULL)
550	sdtp->subdirs = sawdir;
551    else if (! sawdir)
552    {
553	sdtp = (struct stickydirtag *) xmalloc (sizeof (*sdtp));
554	memset ((char *) sdtp, 0, sizeof (*sdtp));
555	sdtp->subdirs = 0;
556	entries->list->data = (char *) sdtp;
557	entries->list->delproc = freesdt;
558    }
559
560    if (do_rewrite && !noexec)
561	write_entries (entries);
562
563    /* clean up and return */
564    if (dirtag)
565	free (dirtag);
566    if (dirdate)
567	free (dirdate);
568    return (entries);
569}
570
571void
572Entries_Close(list)
573    List *list;
574{
575    if (list)
576    {
577	if (!noexec)
578        {
579            if (isfile (CVSADM_ENTLOG))
580		write_entries (list);
581	}
582	dellist(&list);
583    }
584}
585
586
587/*
588 * Free up the memory associated with the data section of an ENTRIES type
589 * node
590 */
591static void
592Entries_delproc (node)
593    Node *node;
594{
595    Entnode *p;
596
597    p = (Entnode *) node->data;
598    Entnode_Destroy(p);
599}
600
601/*
602 * Get an Entries file list node, initialize it, and add it to the specified
603 * list
604 */
605static Node *
606AddEntryNode (list, entdata)
607    List *list;
608    Entnode *entdata;
609{
610    Node *p;
611
612    /* was it already there? */
613    if ((p  = findnode_fn (list, entdata->user)) != NULL)
614    {
615	/* take it out */
616	delnode (p);
617    }
618
619    /* get a node and fill in the regular stuff */
620    p = getnode ();
621    p->type = ENTRIES;
622    p->delproc = Entries_delproc;
623
624    /* this one gets a key of the name for hashing */
625    /* FIXME This results in duplicated data --- the hash package shouldn't
626       assume that the key is dynamically allocated.  The user's free proc
627       should be responsible for freeing the key. */
628    p->key = xstrdup (entdata->user);
629    p->data = (char *) entdata;
630
631    /* put the node into the list */
632    addnode (list, p);
633    return (p);
634}
635
636/*
637 * Write out/Clear the CVS/Tag file.
638 */
639void
640WriteTag (dir, tag, date, nonbranch, update_dir, repository)
641    char *dir;
642    char *tag;
643    char *date;
644    int nonbranch;
645    char *update_dir;
646    char *repository;
647{
648    FILE *fout;
649    char *tmp;
650
651    if (noexec)
652	return;
653
654    tmp = xmalloc ((dir ? strlen (dir) : 0)
655		   + sizeof (CVSADM_TAG)
656		   + 10);
657    if (dir == NULL)
658	(void) strcpy (tmp, CVSADM_TAG);
659    else
660	(void) sprintf (tmp, "%s/%s", dir, CVSADM_TAG);
661
662    if (tag || date)
663    {
664	fout = open_file (tmp, "w+");
665	if (tag)
666	{
667	    if (nonbranch)
668	    {
669		if (fprintf (fout, "N%s\n", tag) < 0)
670		    error (1, errno, "write to %s failed", tmp);
671	    }
672	    else
673	    {
674		if (fprintf (fout, "T%s\n", tag) < 0)
675		    error (1, errno, "write to %s failed", tmp);
676	    }
677	}
678	else
679	{
680	    if (fprintf (fout, "D%s\n", date) < 0)
681		error (1, errno, "write to %s failed", tmp);
682	}
683	if (fclose (fout) == EOF)
684	    error (1, errno, "cannot close %s", tmp);
685    }
686    else
687	if (unlink_file (tmp) < 0 && ! existence_error (errno))
688	    error (1, errno, "cannot remove %s", tmp);
689    free (tmp);
690#ifdef SERVER_SUPPORT
691    if (server_active)
692	server_set_sticky (update_dir, repository, tag, date, nonbranch);
693#endif
694}
695
696/* Parse the CVS/Tag file for the current directory.
697
698   If it contains a date, sets *DATEP to the date in a newly malloc'd
699   string, *TAGP to NULL, and *NONBRANCHP to an unspecified value.
700
701   If it contains a branch tag, sets *TAGP to the tag in a newly
702   malloc'd string, *NONBRANCHP to 0, and *DATEP to NULL.
703
704   If it contains a nonbranch tag, sets *TAGP to the tag in a newly
705   malloc'd string, *NONBRANCHP to 1, and *DATEP to NULL.
706
707   If it does not exist, or contains something unrecognized by this
708   version of CVS, set *DATEP and *TAGP to NULL and *NONBRANCHP to
709   an unspecified value.
710
711   If there is an error, print an error message, set *DATEP and *TAGP
712   to NULL, and return.  */
713void
714ParseTag (tagp, datep, nonbranchp)
715    char **tagp;
716    char **datep;
717    int *nonbranchp;
718{
719    FILE *fp;
720
721    if (tagp)
722	*tagp = (char *) NULL;
723    if (datep)
724	*datep = (char *) NULL;
725    /* Always store a value here, even in the 'D' case where the value
726       is unspecified.  Shuts up tools which check for references to
727       uninitialized memory.  */
728    if (nonbranchp != NULL)
729	*nonbranchp = 0;
730    fp = CVS_FOPEN (CVSADM_TAG, "r");
731    if (fp)
732    {
733	char *line;
734	int line_length;
735	size_t line_chars_allocated;
736
737	line = NULL;
738	line_chars_allocated = 0;
739
740	if ((line_length = getline (&line, &line_chars_allocated, fp)) > 0)
741	{
742	    /* Remove any trailing newline.  */
743	    if (line[line_length - 1] == '\n')
744	        line[--line_length] = '\0';
745	    switch (*line)
746	    {
747		case 'T':
748		    if (tagp != NULL)
749			*tagp = xstrdup (line + 1);
750		    break;
751		case 'D':
752		    if (datep != NULL)
753			*datep = xstrdup (line + 1);
754		    break;
755		case 'N':
756		    if (tagp != NULL)
757			*tagp = xstrdup (line + 1);
758		    if (nonbranchp != NULL)
759			*nonbranchp = 1;
760		    break;
761		default:
762		    /* Silently ignore it; it may have been
763		       written by a future version of CVS which extends the
764		       syntax.  */
765		    break;
766	    }
767	}
768
769	if (line_length < 0)
770	{
771	    /* FIXME-update-dir: should include update_dir in messages.  */
772	    if (feof (fp))
773		error (0, 0, "cannot read %s: end of file", CVSADM_TAG);
774	    else
775		error (0, errno, "cannot read %s", CVSADM_TAG);
776	}
777
778	if (fclose (fp) < 0)
779	    /* FIXME-update-dir: should include update_dir in message.  */
780	    error (0, errno, "cannot close %s", CVSADM_TAG);
781
782	free (line);
783    }
784    else if (!existence_error (errno))
785	/* FIXME-update-dir: should include update_dir in message.  */
786	error (0, errno, "cannot open %s", CVSADM_TAG);
787}
788
789/*
790 * This is called if all subdirectory information is known, but there
791 * aren't any subdirectories.  It records that fact in the list
792 * private data.
793 */
794
795void
796Subdirs_Known (entries)
797     List *entries;
798{
799    struct stickydirtag *sdtp;
800
801    /* If there is no list private data, that means that the
802       subdirectory information is known.  */
803    sdtp = (struct stickydirtag *) entries->list->data;
804    if (sdtp != NULL && ! sdtp->subdirs)
805    {
806	FILE *fp;
807
808	sdtp->subdirs = 1;
809	if (!noexec)
810	{
811	    /* Create Entries.Log so that Entries_Close will do something.  */
812	    entfilename = CVSADM_ENTLOG;
813	    fp = CVS_FOPEN (entfilename, "a");
814	    if (fp == NULL)
815	    {
816		int save_errno = errno;
817
818		/* As in subdir_record, just silently skip the whole thing
819		   if there is no CVSADM directory.  */
820		if (! isdir (CVSADM))
821		    return;
822		error (1, save_errno, "cannot open %s", entfilename);
823	    }
824	    else
825	    {
826		if (fclose (fp) == EOF)
827		    error (1, errno, "cannot close %s", entfilename);
828	    }
829	}
830    }
831}
832
833/* Record subdirectory information.  */
834
835static Entnode *
836subdir_record (cmd, parent, dir)
837     int cmd;
838     const char *parent;
839     const char *dir;
840{
841    Entnode *entnode;
842
843    /* None of the information associated with a directory is
844       currently meaningful.  */
845    entnode = Entnode_Create (ENT_SUBDIR, dir, "", "", "",
846			      (char *) NULL, (char *) NULL,
847			      (char *) NULL);
848
849    if (!noexec)
850    {
851	if (parent == NULL)
852	    entfilename = CVSADM_ENTLOG;
853	else
854	{
855	    entfilename = xmalloc (strlen (parent)
856				   + sizeof CVSADM_ENTLOG
857				   + 10);
858	    sprintf (entfilename, "%s/%s", parent, CVSADM_ENTLOG);
859	}
860
861	entfile = CVS_FOPEN (entfilename, "a");
862	if (entfile == NULL)
863	{
864	    int save_errno = errno;
865
866	    /* It is not an error if there is no CVS administration
867               directory.  Permitting this case simplifies some
868               calling code.  */
869
870	    if (parent == NULL)
871	    {
872		if (! isdir (CVSADM))
873		    return entnode;
874	    }
875	    else
876	    {
877		sprintf (entfilename, "%s/%s", parent, CVSADM);
878		if (! isdir (entfilename))
879		{
880		    free (entfilename);
881		    entfilename = NULL;
882		    return entnode;
883		}
884	    }
885
886	    error (1, save_errno, "cannot open %s", entfilename);
887	}
888
889	if (fprintf (entfile, "%c ", cmd) < 0)
890	    error (1, errno, "cannot write %s", entfilename);
891
892	if (fputentent (entfile, entnode) != 0)
893	    error (1, errno, "cannot write %s", entfilename);
894
895	if (fclose (entfile) == EOF)
896	    error (1, errno, "error closing %s", entfilename);
897
898	if (parent != NULL)
899	{
900	    free (entfilename);
901	    entfilename = NULL;
902	}
903    }
904
905    return entnode;
906}
907
908/*
909 * Record the addition of a new subdirectory DIR in PARENT.  PARENT
910 * may be NULL, which means the current directory.  ENTRIES is the
911 * current entries list; it may be NULL, which means that it need not
912 * be updated.
913 */
914
915void
916Subdir_Register (entries, parent, dir)
917     List *entries;
918     const char *parent;
919     const char *dir;
920{
921    Entnode *entnode;
922
923    /* Ignore attempts to register ".".  These can happen in the
924       server code.  */
925    if (dir[0] == '.' && dir[1] == '\0')
926	return;
927
928    entnode = subdir_record ('A', parent, dir);
929
930    if (entries != NULL && (parent == NULL || strcmp (parent, ".") == 0))
931	(void) AddEntryNode (entries, entnode);
932    else
933	Entnode_Destroy (entnode);
934}
935
936/*
937 * Record the removal of a subdirectory.  The arguments are the same
938 * as for Subdir_Register.
939 */
940
941void
942Subdir_Deregister (entries, parent, dir)
943     List *entries;
944     const char *parent;
945     const char *dir;
946{
947    Entnode *entnode;
948
949    entnode = subdir_record ('R', parent, dir);
950    Entnode_Destroy (entnode);
951
952    if (entries != NULL && (parent == NULL || strcmp (parent, ".") == 0))
953    {
954	Node *p;
955
956	p = findnode_fn (entries, dir);
957	if (p != NULL)
958	    delnode (p);
959    }
960}
961
962
963
964/* OK, the following base_* code tracks the revisions of the files in
965   CVS/Base.  We do this in a file CVS/Baserev.  Separate from
966   CVS/Entries because it needs to go in separate data structures
967   anyway (the name in Entries must be unique), so this seemed
968   cleaner.  The business of rewriting the whole file in
969   base_deregister and base_register is the kind of thing we used to
970   do for Entries and which turned out to be slow, which is why there
971   is now the Entries.Log machinery.  So maybe from that point of
972   view it is a mistake to do this separately from Entries, I dunno.
973
974   We also need something analogous for:
975
976   1. CVS/Template (so we can update the Template file automagically
977   without the user needing to check out a new working directory).
978   Updating would probably print a message (that part might be
979   optional, although probably it should be visible because not all
980   cvs commands would make the update happen and so it is a
981   user-visible behavior).  Constructing version number for template
982   is a bit hairy (base it on the timestamp on the server?  Or see if
983   the template is in checkoutlist and if yes use its versioning and
984   if no don't version it?)....
985
986   2.  cvsignore (need to keep a copy in the working directory to do
987   "cvs release" on the client side; see comment at src/release.c
988   (release).  Would also allow us to stop needing Questionable.  */
989
990enum base_walk {
991    /* Set the revision for FILE to *REV.  */
992    BASE_REGISTER,
993    /* Get the revision for FILE and put it in a newly malloc'd string
994       in *REV, or put NULL if not mentioned.  */
995    BASE_GET,
996    /* Remove FILE.  */
997    BASE_DEREGISTER
998};
999
1000static void base_walk PROTO ((enum base_walk, struct file_info *, char **));
1001
1002/* Read through the lines in CVS/Baserev, taking the actions as documented
1003   for CODE.  */
1004
1005static void
1006base_walk (code, finfo, rev)
1007    enum base_walk code;
1008    struct file_info *finfo;
1009    char **rev;
1010{
1011    FILE *fp;
1012    char *line;
1013    size_t line_allocated;
1014    FILE *newf;
1015    char *baserev_fullname;
1016    char *baserevtmp_fullname;
1017
1018    line = NULL;
1019    line_allocated = 0;
1020    newf = NULL;
1021
1022    /* First compute the fullnames for the error messages.  This
1023       computation probably should be broken out into a separate function,
1024       as recurse.c does it too and places like Entries_Open should be
1025       doing it.  */
1026    baserev_fullname = xmalloc (sizeof (CVSADM_BASEREV)
1027				+ strlen (finfo->update_dir)
1028				+ 2);
1029    baserev_fullname[0] = '\0';
1030    baserevtmp_fullname = xmalloc (sizeof (CVSADM_BASEREVTMP)
1031				   + strlen (finfo->update_dir)
1032				   + 2);
1033    baserevtmp_fullname[0] = '\0';
1034    if (finfo->update_dir[0] != '\0')
1035    {
1036	strcat (baserev_fullname, finfo->update_dir);
1037	strcat (baserev_fullname, "/");
1038	strcat (baserevtmp_fullname, finfo->update_dir);
1039	strcat (baserevtmp_fullname, "/");
1040    }
1041    strcat (baserev_fullname, CVSADM_BASEREV);
1042    strcat (baserevtmp_fullname, CVSADM_BASEREVTMP);
1043
1044    fp = CVS_FOPEN (CVSADM_BASEREV, "r");
1045    if (fp == NULL)
1046    {
1047	if (!existence_error (errno))
1048	{
1049	    error (0, errno, "cannot open %s for reading", baserev_fullname);
1050	    goto out;
1051	}
1052    }
1053
1054    switch (code)
1055    {
1056	case BASE_REGISTER:
1057	case BASE_DEREGISTER:
1058	    newf = CVS_FOPEN (CVSADM_BASEREVTMP, "w");
1059	    if (newf == NULL)
1060	    {
1061		error (0, errno, "cannot open %s for writing",
1062		       baserevtmp_fullname);
1063		goto out;
1064	    }
1065	    break;
1066	case BASE_GET:
1067	    *rev = NULL;
1068	    break;
1069    }
1070
1071    if (fp != NULL)
1072    {
1073	while (getline (&line, &line_allocated, fp) >= 0)
1074	{
1075	    char *linefile;
1076	    char *p;
1077	    char *linerev;
1078
1079	    if (line[0] != 'B')
1080		/* Ignore, for future expansion.  */
1081		continue;
1082
1083	    linefile = line + 1;
1084	    p = strchr (linefile, '/');
1085	    if (p == NULL)
1086		/* Syntax error, ignore.  */
1087		continue;
1088	    linerev = p + 1;
1089	    p = strchr (linerev, '/');
1090	    if (p == NULL)
1091		continue;
1092
1093	    linerev[-1] = '\0';
1094	    if (fncmp (linefile, finfo->file) == 0)
1095	    {
1096		switch (code)
1097		{
1098		case BASE_REGISTER:
1099		case BASE_DEREGISTER:
1100		    /* Don't copy over the old entry, we don't want it.  */
1101		    break;
1102		case BASE_GET:
1103		    *p = '\0';
1104		    *rev = xstrdup (linerev);
1105		    *p = '/';
1106		    goto got_it;
1107		}
1108	    }
1109	    else
1110	    {
1111		linerev[-1] = '/';
1112		switch (code)
1113		{
1114		case BASE_REGISTER:
1115		case BASE_DEREGISTER:
1116		    if (fprintf (newf, "%s\n", line) < 0)
1117			error (0, errno, "error writing %s",
1118			       baserevtmp_fullname);
1119		    break;
1120		case BASE_GET:
1121		    break;
1122		}
1123	    }
1124	}
1125	if (ferror (fp))
1126	    error (0, errno, "cannot read %s", baserev_fullname);
1127    }
1128 got_it:
1129
1130    if (code == BASE_REGISTER)
1131    {
1132	if (fprintf (newf, "B%s/%s/\n", finfo->file, *rev) < 0)
1133	    error (0, errno, "error writing %s",
1134		   baserevtmp_fullname);
1135    }
1136
1137 out:
1138
1139    if (line != NULL)
1140	free (line);
1141
1142    if (fp != NULL)
1143    {
1144	if (fclose (fp) < 0)
1145	    error (0, errno, "cannot close %s", baserev_fullname);
1146    }
1147    if (newf != NULL)
1148    {
1149	if (fclose (newf) < 0)
1150	    error (0, errno, "cannot close %s", baserevtmp_fullname);
1151	rename_file (CVSADM_BASEREVTMP, CVSADM_BASEREV);
1152    }
1153
1154    free (baserev_fullname);
1155    free (baserevtmp_fullname);
1156}
1157
1158/* Return, in a newly malloc'd string, the revision for FILE in CVS/Baserev,
1159   or NULL if not listed.  */
1160
1161char *
1162base_get (finfo)
1163    struct file_info *finfo;
1164{
1165    char *rev;
1166    base_walk (BASE_GET, finfo, &rev);
1167    return rev;
1168}
1169
1170/* Set the revision for FILE to REV.  */
1171
1172void
1173base_register (finfo, rev)
1174    struct file_info *finfo;
1175    char *rev;
1176{
1177    base_walk (BASE_REGISTER, finfo, &rev);
1178}
1179
1180/* Remove FILE.  */
1181
1182void
1183base_deregister (finfo)
1184    struct file_info *finfo;
1185{
1186    base_walk (BASE_DEREGISTER, finfo, NULL);
1187}
1188