xref: /openbsd/gnu/usr.bin/cvs/src/entries.c (revision f9bbbf45)
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 
17 static Node *AddEntryNode PROTO((List * list, Entnode *entnode));
18 
19 static Entnode *fgetentent PROTO((FILE *, char *, int *));
20 static int   fputentent PROTO((FILE *, Entnode *));
21 
22 static Entnode *subdir_record PROTO((int, const char *, const char *));
23 
24 static FILE *entfile;
25 static char *entfilename;		/* for error messages */
26 
27 /*
28  * Construct an Entnode
29  */
30 static 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 
35 static Entnode *
Entnode_Create(type,user,vn,ts,options,tag,date,ts_conflict)36 Entnode_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  */
65 static void Entnode_Destroy PROTO ((Entnode *));
66 
67 static void
Entnode_Destroy(ent)68 Entnode_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  */
87 static int write_ent_proc PROTO ((Node *, void *));
88 static int
write_ent_proc(node,closure)89 write_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  */
110 static void
write_entries(list)111 write_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  */
168 void
Scratch_Entry(list,fname)169 Scratch_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  */
209 void
Register(list,fname,vn,ts,options,tag,date,ts_conflict)210 Register (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  */
269 static void
freesdt(p)270 freesdt (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 
286 static Entnode *
fgetentent(fpin,cmd,sawdir)287 fgetentent(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 = get_line (&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 
409 static int
fputentent(fp,p)410 fputentent(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).  */
459 List *
Entries_Open(aflag,update_dir)460 Entries_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 
571 void
Entries_Close(list)572 Entries_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  */
591 static void
Entries_delproc(node)592 Entries_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  */
605 static Node *
AddEntryNode(list,entdata)606 AddEntryNode (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  */
639 void
WriteTag(dir,tag,date,nonbranch,update_dir,repository)640 WriteTag (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.  */
713 void
ParseTag(tagp,datep,nonbranchp)714 ParseTag (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 = get_line (&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 
795 void
Subdirs_Known(entries)796 Subdirs_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 
835 static Entnode *
subdir_record(cmd,parent,dir)836 subdir_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 
915 void
Subdir_Register(entries,parent,dir)916 Subdir_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 
941 void
Subdir_Deregister(entries,parent,dir)942 Subdir_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 
990 enum 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 
1000 static 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 
1005 static void
base_walk(code,finfo,rev)1006 base_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 (get_line (&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 
1161 char *
base_get(finfo)1162 base_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 
1172 void
base_register(finfo,rev)1173 base_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 
1182 void
base_deregister(finfo)1183 base_deregister (finfo)
1184     struct file_info *finfo;
1185 {
1186     base_walk (BASE_DEREGISTER, finfo, NULL);
1187 }
1188