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