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