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