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