Home | History | Annotate | Line # | Download | only in src
      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 *
     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
     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
     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
    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
    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
    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
    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 *
    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
    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 *
    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
    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
    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 *
    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
    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
    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
    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
    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 *
    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
    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
    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
    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 *
   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
   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
   1133 base_deregister (struct file_info *finfo)
   1134 {
   1135     base_walk (BASE_DEREGISTER, finfo, NULL);
   1136 }
   1137