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