PageRenderTime 70ms CodeModel.GetById 14ms app.highlight 47ms RepoModel.GetById 1ms app.codeStats 1ms

/contrib/cvs/src/entries.c

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