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  * Tag and Rtag
     14  *
     15  * Add or delete a symbolic name to an RCS file, or a collection of RCS files.
     16  * Tag uses the checked out revision in the current directory, rtag uses
     17  * the modules database, if necessary.
     18  */
     19 #include <sys/cdefs.h>
     20 __RCSID("$NetBSD: tag.c,v 1.5 2019/01/05 00:27:58 christos Exp $");
     21 
     22 #include "cvs.h"
     23 #include <grp.h>
     24 #include "save-cwd.h"
     25 
     26 static int rtag_proc (int argc, char **argv, char *xwhere,
     27 		      char *mwhere, char *mfile, int shorten,
     28 		      int local_specified, char *mname, char *msg);
     29 static int check_fileproc (void *callerdat, struct file_info *finfo);
     30 static int check_filesdoneproc (void *callerdat, int err,
     31 				const char *repos, const char *update_dir,
     32 				List *entries);
     33 static int pretag_proc (const char *_repository, const char *_filter,
     34                         void *_closure);
     35 static void masterlist_delproc (Node *_p);
     36 static void tag_delproc (Node *_p);
     37 static int pretag_list_to_args_proc (Node *_p, void *_closure);
     38 
     39 static Dtype tag_dirproc (void *callerdat, const char *dir,
     40                           const char *repos, const char *update_dir,
     41                           List *entries);
     42 static int rtag_fileproc (void *callerdat, struct file_info *finfo);
     43 static int rtag_delete (RCSNode *rcsfile);
     44 static int tag_fileproc (void *callerdat, struct file_info *finfo);
     45 
     46 static char *numtag;			/* specific revision to tag */
     47 static bool numtag_validated = false;
     48 static char *date = NULL;
     49 static char *symtag;			/* tag to add or delete */
     50 static bool delete_flag;		/* adding a tag by default */
     51 static bool branch_mode;		/* make an automagic "branch" tag */
     52 static bool disturb_branch_tags = false;/* allow -F,-d to disturb branch tags */
     53 static bool force_tag_match = true;	/* force tag to match by default */
     54 static bool force_tag_move;		/* don't force tag to move by default */
     55 static bool check_uptodate;		/* no uptodate-check by default */
     56 static bool attic_too;			/* remove tag from Attic files */
     57 static bool is_rtag;
     58 
     59 struct tag_info
     60 {
     61     Ctype status;
     62     char *oldrev;
     63     char *rev;
     64     char *tag;
     65     char *options;
     66 };
     67 
     68 struct master_lists
     69 {
     70     List *tlist;
     71 };
     72 
     73 static List *mtlist;
     74 
     75 static const char rtag_opts[] = "+aBbdFflnQqRr:D:";
     76 static const char *const rtag_usage[] =
     77 {
     78     "Usage: %s %s [-abdFflnR] [-r rev|-D date] tag modules...\n",
     79     "\t-a\tClear tag from removed files that would not otherwise be tagged.\n",
     80     "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
     81     "\t-B\tAllows -F and -d to disturb branch tags.  Use with extreme care.\n",
     82     "\t-d\tDelete the given tag.\n",
     83     "\t-F\tMove tag if it already exists.\n",
     84     "\t-f\tForce a head revision match if tag/date not found.\n",
     85     "\t-l\tLocal directory only, not recursive.\n",
     86     "\t-n\tNo execution of 'tag program'.\n",
     87     "\t-R\tProcess directories recursively.\n",
     88     "\t-r rev\tExisting revision/tag.\n",
     89     "\t-D\tExisting date.\n",
     90     "(Specify the --help global option for a list of other help options)\n",
     91     NULL
     92 };
     93 
     94 static const char tag_opts[] = "+BbcdFflQqRr:D:";
     95 static const char *const tag_usage[] =
     96 {
     97     "Usage: %s %s [-bcdFflR] [-r rev|-D date] tag [files...]\n",
     98     "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
     99     "\t-B\tAllows -F and -d to disturb branch tags.  Use with extreme care.\n",
    100     "\t-c\tCheck that working files are unmodified.\n",
    101     "\t-d\tDelete the given tag.\n",
    102     "\t-F\tMove tag if it already exists.\n",
    103     "\t-f\tForce a head revision match if tag/date not found.\n",
    104     "\t-l\tLocal directory only, not recursive.\n",
    105     "\t-R\tProcess directories recursively.\n",
    106     "\t-r rev\tExisting revision/tag.\n",
    107     "\t-D\tExisting date.\n",
    108     "(Specify the --help global option for a list of other help options)\n",
    109     NULL
    110 };
    111 
    112 char *UserTagOptions = "bcflRrD";
    113 
    114 int
    115 cvstag (int argc, char **argv)
    116 {
    117     struct group *grp;
    118     bool local = false;			/* recursive by default */
    119     int c;
    120     int err = 0;
    121     bool run_module_prog = true;
    122     int only_allowed_options;
    123 
    124     is_rtag = (strcmp (cvs_cmd_name, "rtag") == 0);
    125 
    126     if (argc == -1)
    127 	usage (is_rtag ? rtag_usage : tag_usage);
    128 
    129     getoptreset ();
    130     only_allowed_options = 1;
    131     while ((c = getopt (argc, argv, is_rtag ? rtag_opts : tag_opts)) != -1)
    132     {
    133 	if (!strchr(UserTagOptions, c))
    134 	    only_allowed_options = 0;
    135 	switch (c)
    136 	{
    137 	    case 'a':
    138 		attic_too = true;
    139 		break;
    140 	    case 'b':
    141 		branch_mode = true;
    142 		break;
    143 	    case 'B':
    144 		disturb_branch_tags = true;
    145 		break;
    146 	    case 'c':
    147 		check_uptodate = true;
    148 		break;
    149 	    case 'd':
    150 		delete_flag = true;
    151 		break;
    152             case 'F':
    153 		force_tag_move = true;
    154 		break;
    155 	    case 'f':
    156 		force_tag_match = false;
    157 		break;
    158 	    case 'l':
    159 		local = true;
    160 		break;
    161 	    case 'n':
    162 		run_module_prog = false;
    163 		break;
    164 	    case 'Q':
    165 	    case 'q':
    166 		/* The CVS 1.5 client sends these options (in addition to
    167 		   Global_option requests), so we must ignore them.  */
    168 		if (!server_active)
    169 		    error (1, 0,
    170 			   "-q or -Q must be specified before \"%s\"",
    171 			   cvs_cmd_name);
    172 		break;
    173 	    case 'R':
    174 		local = false;
    175 		break;
    176             case 'r':
    177 		parse_tagdate (&numtag, &date, optarg);
    178                 break;
    179             case 'D':
    180                 if (date) free (date);
    181                 date = Make_Date (optarg);
    182                 break;
    183 	    case '?':
    184 	    default:
    185 		usage (is_rtag ? rtag_usage : tag_usage);
    186 		break;
    187 	}
    188     }
    189     argc -= optind;
    190     argv += optind;
    191 
    192     if (argc < (is_rtag ? 2 : 1))
    193 	usage (is_rtag ? rtag_usage : tag_usage);
    194     symtag = argv[0];
    195     argc--;
    196     argv++;
    197 
    198     if (date && delete_flag)
    199 	error (1, 0, "-d makes no sense with a date specification.");
    200     if (delete_flag && branch_mode)
    201 	error (0, 0, "warning: -b ignored with -d options");
    202     RCS_check_tag (symtag);
    203 
    204 #ifdef CVS_ADMIN_GROUP
    205     if (!only_allowed_options &&
    206 	(grp = getgrnam(CVS_ADMIN_GROUP)) != NULL)
    207     {
    208 #ifdef HAVE_GETGROUPS
    209 	gid_t *grps;
    210 	int i, n;
    211 
    212 	/* get number of auxiliary groups */
    213 	n = getgroups (0, NULL);
    214 	if (n < 0)
    215 	    error (1, errno, "unable to get number of auxiliary groups");
    216 	grps = (gid_t *) xmalloc((n + 1) * sizeof *grps);
    217 	n = getgroups (n, grps);
    218 	if (n < 0)
    219 	    error (1, errno, "unable to get list of auxiliary groups");
    220 	grps[n] = getgid();
    221 	for (i = 0; i <= n; i++)
    222 	    if (grps[i] == grp->gr_gid) break;
    223 	free (grps);
    224 	if (i > n)
    225 	    error (1, 0, "usage is restricted to members of the group %s",
    226 		   CVS_ADMIN_GROUP);
    227 #else
    228 	char *me = getcaller();
    229 	char **grnam;
    230 
    231 	for (grnam = grp->gr_mem; *grnam; grnam++)
    232 	    if (strcmp (*grnam, me) == 0) break;
    233 	if (!*grnam && getgid() != grp->gr_gid)
    234 	    error (1, 0, "usage is restricted to members of the group %s",
    235 		   CVS_ADMIN_GROUP);
    236 #endif
    237     }
    238 #endif /* defined CVS_ADMIN_GROUP */
    239 
    240 #ifdef CLIENT_SUPPORT
    241     if (current_parsed_root->isremote)
    242     {
    243 	/* We're the client side.  Fire up the remote server.  */
    244 	start_server ();
    245 
    246 	ign_setup ();
    247 
    248 	if (attic_too)
    249 	    send_arg ("-a");
    250 	if (branch_mode)
    251 	    send_arg ("-b");
    252 	if (disturb_branch_tags)
    253 	    send_arg ("-B");
    254 	if (check_uptodate)
    255 	    send_arg ("-c");
    256 	if (delete_flag)
    257 	    send_arg ("-d");
    258 	if (force_tag_move)
    259 	    send_arg ("-F");
    260 	if (!force_tag_match)
    261 	    send_arg ("-f");
    262 	if (local)
    263 	    send_arg ("-l");
    264 	if (!run_module_prog)
    265 	    send_arg ("-n");
    266 
    267 	if (numtag)
    268 	    option_with_arg ("-r", numtag);
    269 	if (date)
    270 	    client_senddate (date);
    271 
    272 	send_arg ("--");
    273 
    274 	send_arg (symtag);
    275 
    276 	if (is_rtag)
    277 	{
    278 	    int i;
    279 	    for (i = 0; i < argc; ++i)
    280 		send_arg (argv[i]);
    281 	    send_to_server ("rtag\012", 0);
    282 	}
    283 	else
    284 	{
    285 	    send_files (argc, argv, local, 0,
    286 
    287 		    /* I think the -c case is like "cvs status", in
    288 		       which we really better be correct rather than
    289 		       being fast; it is just too confusing otherwise.  */
    290 			check_uptodate ? 0 : SEND_NO_CONTENTS);
    291 	    send_file_names (argc, argv, SEND_EXPAND_WILD);
    292 	    send_to_server ("tag\012", 0);
    293 	}
    294 
    295         return get_responses_and_close ();
    296     }
    297 #endif
    298 
    299     if (is_rtag)
    300     {
    301 	DBM *db;
    302 	int i;
    303 	db = open_module ();
    304 	for (i = 0; i < argc; i++)
    305 	{
    306 	    /* XXX last arg should be repository, but doesn't make sense here */
    307 	    history_write ('T', (delete_flag ? "D" : (numtag ? numtag :
    308 			   (date ? date : "A"))), symtag, argv[i], "");
    309 	    err += do_module (db, argv[i], TAG,
    310 			      delete_flag ? "Untagging" : "Tagging",
    311 			      rtag_proc, NULL, 0, local, run_module_prog,
    312 			      0, symtag);
    313 	}
    314 	close_module (db);
    315     }
    316     else
    317     {
    318 	int i;
    319 	for (i = 0; i < argc; i++)
    320 	{
    321 	    /* XXX last arg should be repository, but doesn't make sense here */
    322 	    history_write ('T', (delete_flag ? "D" : (numtag ? numtag :
    323 			   (date ? date : "A"))), symtag, argv[i], "");
    324 	}
    325 	err = rtag_proc (argc + 1, argv - 1, NULL, NULL, NULL, 0, local, NULL,
    326 			 NULL);
    327     }
    328 
    329     return err;
    330 }
    331 
    332 
    333 
    334 struct pretag_proc_data {
    335      List *tlist;
    336      bool delete_flag;
    337      bool force_tag_move;
    338      char *symtag;
    339 };
    340 
    341 /*
    342  * called from Parse_Info, this routine processes a line that came out
    343  * of the posttag file and turns it into a command and executes it.
    344  *
    345  * RETURNS
    346  *    the absolute value of the return value of run_exec, which may or
    347  *    may not be the return value of the child process.  this is
    348  *    contrained to return positive values because Parse_Info is summing
    349  *    return values and testing for non-zeroness to signify one or more
    350  *    of its callbacks having returned an error.
    351  */
    352 static int
    353 posttag_proc (const char *repository, const char *filter, void *closure)
    354 {
    355     char *cmdline;
    356     const char *srepos = Short_Repository (repository);
    357     struct pretag_proc_data *ppd = closure;
    358 
    359     /* %t = tag being added/moved/removed
    360      * %o = operation = "add" | "mov" | "del"
    361      * %b = branch mode = "?" (delete ops - unknown) | "T" (branch)
    362      *                    | "N" (not branch)
    363      * %c = cvs_cmd_name
    364      * %p = path from $CVSROOT
    365      * %r = path from root
    366      * %{sVv} = attribute list = file name, old version tag will be deleted
    367      *                           from, new version tag will be added to (or
    368      *                           deleted from until
    369      *                           SUPPORT_OLD_INFO_FMT_STRINGS is undefined).
    370      */
    371     /*
    372      * Cast any NULL arguments as appropriate pointers as this is an
    373      * stdarg function and we need to be certain the caller gets what
    374      * is expected.
    375      */
    376     cmdline = format_cmdline (
    377 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
    378 			      false, srepos,
    379 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
    380 			      filter,
    381 			      "t", "s", ppd->symtag,
    382 			      "o", "s", ppd->delete_flag
    383 			      ? "del" : ppd->force_tag_move ? "mov" : "add",
    384 			      "b", "c", delete_flag
    385 			      ? '?' : branch_mode ? 'T' : 'N',
    386 			      "c", "s", cvs_cmd_name,
    387 #ifdef SERVER_SUPPORT
    388 			      "R", "s", referrer ? referrer->original : "NONE",
    389 #endif /* SERVER_SUPPORT */
    390 			      "p", "s", srepos,
    391 			      "r", "s", current_parsed_root->directory,
    392 			      "sVv", ",", ppd->tlist,
    393 			      pretag_list_to_args_proc, (void *) NULL,
    394 			      (char *) NULL);
    395 
    396     if (!cmdline || !strlen (cmdline))
    397     {
    398 	if (cmdline) free (cmdline);
    399 	error (0, 0, "pretag proc resolved to the empty string!");
    400 	return 1;
    401     }
    402 
    403     run_setup (cmdline);
    404 
    405     free (cmdline);
    406     return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL));
    407 }
    408 
    409 
    410 
    411 /*
    412  * Call any postadmin procs.
    413  */
    414 static int
    415 tag_filesdoneproc (void *callerdat, int err, const char *repository,
    416                    const char *update_dir, List *entries)
    417 {
    418     Node *p;
    419     List *mtlist, *tlist;
    420     struct pretag_proc_data ppd;
    421 
    422     TRACE (TRACE_FUNCTION, "tag_filesdoneproc (%d, %s, %s)", err, repository,
    423            update_dir);
    424 
    425     mtlist = callerdat;
    426     p = findnode (mtlist, update_dir);
    427     if (p != NULL)
    428         tlist = ((struct master_lists *) p->data)->tlist;
    429     else
    430         tlist = NULL;
    431     if (tlist == NULL || tlist->list->next == tlist->list)
    432         return err;
    433 
    434     ppd.tlist = tlist;
    435     ppd.delete_flag = delete_flag;
    436     ppd.force_tag_move = force_tag_move;
    437     ppd.symtag = symtag;
    438     Parse_Info (CVSROOTADM_POSTTAG, repository, posttag_proc,
    439                 PIOPT_ALL, &ppd);
    440 
    441     return err;
    442 }
    443 
    444 
    445 
    446 /*
    447  * callback proc for doing the real work of tagging
    448  */
    449 /* ARGSUSED */
    450 static int
    451 rtag_proc (int argc, char **argv, char *xwhere, char *mwhere, char *mfile,
    452            int shorten, int local_specified, char *mname, char *msg)
    453 {
    454     /* Begin section which is identical to patch_proc--should this
    455        be abstracted out somehow?  */
    456     char *myargv[2];
    457     int err = 0;
    458     int which;
    459     char *repository;
    460     char *where;
    461 
    462 #ifdef HAVE_PRINTF_PTR
    463     TRACE (TRACE_FUNCTION,
    464 	   "rtag_proc (argc=%d, argv=%p, xwhere=%s,\n"
    465       "                mwhere=%s, mfile=%s, shorten=%d,\n"
    466       "                local_specified=%d, mname=%s, msg=%s)",
    467 	    argc, (void *)argv, xwhere ? xwhere : "(null)",
    468 	    mwhere ? mwhere : "(null)", mfile ? mfile : "(null)",
    469 	    shorten, local_specified,
    470 	    mname ? mname : "(null)", msg ? msg : "(null)" );
    471 #else
    472     TRACE (TRACE_FUNCTION,
    473 	   "rtag_proc (argc=%d, argv=%lx, xwhere=%s,\n"
    474       "                mwhere=%s, mfile=%s, shorten=%d,\n"
    475       "                local_specified=%d, mname=%s, msg=%s )",
    476 	    argc, (unsigned long)argv, xwhere ? xwhere : "(null)",
    477 	    mwhere ? mwhere : "(null)", mfile ? mfile : "(null)",
    478 	    shorten, local_specified,
    479 	    mname ? mname : "(null)", msg ? msg : "(null)" );
    480 #endif
    481 
    482     if (is_rtag)
    483     {
    484 	repository = xmalloc (strlen (current_parsed_root->directory)
    485                               + strlen (argv[0])
    486 			      + (mfile == NULL ? 0 : strlen (mfile) + 1)
    487                               + 2);
    488 	(void) sprintf (repository, "%s/%s", current_parsed_root->directory,
    489                         argv[0]);
    490 	where = xmalloc (strlen (argv[0])
    491                          + (mfile == NULL ? 0 : strlen (mfile) + 1)
    492 			 + 1);
    493 	(void) strcpy (where, argv[0]);
    494 
    495 	/* If MFILE isn't null, we need to set up to do only part of the
    496          * module.
    497          */
    498 	if (mfile != NULL)
    499 	{
    500 	    char *cp;
    501 	    char *path;
    502 
    503 	    /* If the portion of the module is a path, put the dir part on
    504              * REPOS.
    505              */
    506 	    if ((cp = strrchr (mfile, '/')) != NULL)
    507 	    {
    508 		*cp = '\0';
    509 		(void) strcat (repository, "/");
    510 		(void) strcat (repository, mfile);
    511 		(void) strcat (where, "/");
    512 		(void) strcat (where, mfile);
    513 		mfile = cp + 1;
    514 	    }
    515 
    516 	    /* take care of the rest */
    517 	    path = xmalloc (strlen (repository) + strlen (mfile) + 5);
    518 	    (void) sprintf (path, "%s/%s", repository, mfile);
    519 	    if (isdir (path))
    520 	    {
    521 		/* directory means repository gets the dir tacked on */
    522 		(void) strcpy (repository, path);
    523 		(void) strcat (where, "/");
    524 		(void) strcat (where, mfile);
    525 	    }
    526 	    else
    527 	    {
    528 		myargv[0] = argv[0];
    529 		myargv[1] = mfile;
    530 		argc = 2;
    531 		argv = myargv;
    532 	    }
    533 	    free (path);
    534 	}
    535 
    536 	/* cd to the starting repository */
    537 	if (CVS_CHDIR (repository) < 0)
    538 	{
    539 	    error (0, errno, "cannot chdir to %s", repository);
    540 	    free (repository);
    541 	    free (where);
    542 	    return 1;
    543 	}
    544 	/* End section which is identical to patch_proc.  */
    545 
    546 	if (delete_flag || attic_too || (force_tag_match && numtag))
    547 	    which = W_REPOS | W_ATTIC;
    548 	else
    549 	    which = W_REPOS;
    550     }
    551     else
    552     {
    553         where = NULL;
    554         which = W_LOCAL;
    555         repository = "";
    556     }
    557 
    558     if (numtag != NULL && !numtag_validated)
    559     {
    560 	tag_check_valid (numtag, argc - 1, argv + 1, local_specified, 0,
    561 			 repository, false);
    562 	numtag_validated = true;
    563     }
    564 
    565     /* check to make sure they are authorized to tag all the
    566        specified files in the repository */
    567 
    568     mtlist = getlist ();
    569     err = start_recursion (check_fileproc, check_filesdoneproc,
    570                            NULL, NULL, NULL,
    571 			   argc - 1, argv + 1, local_specified, which, 0,
    572 			   CVS_LOCK_READ, where, 1, repository);
    573 
    574     if (err)
    575     {
    576        error (1, 0, "correct the above errors first!");
    577     }
    578 
    579     /* It would be nice to provide consistency with respect to
    580        commits; however CVS lacks the infrastructure to do that (see
    581        Concurrency in cvs.texinfo and comment in do_recursion).  */
    582 
    583     /* start the recursion processor */
    584     err = start_recursion
    585 	(is_rtag ? rtag_fileproc : tag_fileproc,
    586 	 tag_filesdoneproc, tag_dirproc, NULL, mtlist, argc - 1, argv + 1,
    587 	 local_specified, which, 0, CVS_LOCK_WRITE, where, 1,
    588 	 repository);
    589     dellist (&mtlist);
    590     if (which & W_REPOS) free (repository);
    591     if (where != NULL)
    592 	free (where);
    593     return err;
    594 }
    595 
    596 
    597 
    598 /* check file that is to be tagged */
    599 /* All we do here is add it to our list */
    600 static int
    601 check_fileproc (void *callerdat, struct file_info *finfo)
    602 {
    603     const char *xdir;
    604     Node *p;
    605     Vers_TS *vers;
    606     List *tlist;
    607     struct tag_info *ti;
    608     int addit = 1;
    609 
    610     TRACE (TRACE_FUNCTION, "check_fileproc (%s, %s, %s)",
    611 	   finfo->repository ? finfo->repository : "(null)",
    612 	   finfo->fullname ? finfo->fullname : "(null)",
    613 	   finfo->rcs ? (finfo->rcs->path ? finfo->rcs->path : "(null)")
    614 	   : "NULL");
    615 
    616     if (check_uptodate)
    617     {
    618 	switch (Classify_File (finfo, NULL, NULL, NULL, 1, 0, &vers, 0))
    619 	{
    620 	case T_UPTODATE:
    621 	case T_CHECKOUT:
    622 	case T_PATCH:
    623 	case T_REMOVE_ENTRY:
    624 	    break;
    625 	case T_UNKNOWN:
    626 	case T_CONFLICT:
    627 	case T_NEEDS_MERGE:
    628 	case T_MODIFIED:
    629 	case T_ADDED:
    630 	case T_REMOVED:
    631 	default:
    632 	    error (0, 0, "%s is locally modified", finfo->fullname);
    633 	    freevers_ts (&vers);
    634 	    return 1;
    635 	}
    636     }
    637     else
    638 	vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
    639 
    640     if (finfo->update_dir[0] == '\0')
    641 	xdir = ".";
    642     else
    643 	xdir = finfo->update_dir;
    644     if ((p = findnode (mtlist, xdir)) != NULL)
    645     {
    646 	tlist = ((struct master_lists *) p->data)->tlist;
    647     }
    648     else
    649     {
    650 	struct master_lists *ml;
    651 
    652 	tlist = getlist ();
    653 	p = getnode ();
    654 	p->key = xstrdup (xdir);
    655 	p->type = UPDATE;
    656 	ml = xmalloc (sizeof (struct master_lists));
    657 	ml->tlist = tlist;
    658 	p->data = ml;
    659 	p->delproc = masterlist_delproc;
    660 	(void) addnode (mtlist, p);
    661     }
    662     /* do tlist */
    663     p = getnode ();
    664     p->key = xstrdup (finfo->file);
    665     p->type = UPDATE;
    666     p->delproc = tag_delproc;
    667     if (vers->srcfile == NULL)
    668     {
    669         if (!really_quiet)
    670 	    error (0, 0, "nothing known about %s", finfo->file);
    671 	freevers_ts (&vers);
    672 	freenode (p);
    673 	return 1;
    674     }
    675 
    676     /* Here we duplicate the calculation in tag_fileproc about which
    677        version we are going to tag.  There probably are some subtle races
    678        (e.g. numtag is "foo" which gets moved between here and
    679        tag_fileproc).  */
    680     p->data = ti = xmalloc (sizeof (struct tag_info));
    681     ti->tag = xstrdup (numtag ? numtag : vers->tag);
    682     if (!is_rtag && numtag == NULL && date == NULL)
    683 	ti->rev = xstrdup (vers->vn_user);
    684     else
    685 	ti->rev = RCS_getversion (vers->srcfile, numtag, date,
    686 				  force_tag_match, NULL);
    687 
    688     if (ti->rev != NULL)
    689     {
    690         ti->oldrev = RCS_getversion (vers->srcfile, symtag, NULL, 1, NULL);
    691 
    692 	if (ti->oldrev == NULL)
    693         {
    694             if (delete_flag)
    695             {
    696 		/* Deleting a tag which did not exist is a noop and
    697 		   should not be logged.  */
    698                 addit = 0;
    699             }
    700         }
    701 	else if (delete_flag)
    702 	{
    703 	    free (ti->rev);
    704 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
    705 	    /* a hack since %v used to mean old or new rev */
    706 	    ti->rev = xstrdup (ti->oldrev);
    707 #else /* SUPPORT_OLD_INFO_FMT_STRINGS */
    708 	    ti->rev = NULL;
    709 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
    710 	}
    711         else if (strcmp(ti->oldrev, p->data) == 0)
    712             addit = 0;
    713         else if (!force_tag_move)
    714             addit = 0;
    715     }
    716     else
    717 	addit = 0;
    718     if (!addit)
    719     {
    720 	free(p->data);
    721 	p->data = NULL;
    722     }
    723     freevers_ts (&vers);
    724     (void)addnode (tlist, p);
    725     return 0;
    726 }
    727 
    728 
    729 
    730 static int
    731 check_filesdoneproc (void *callerdat, int err, const char *repos,
    732                      const char *update_dir, List *entries)
    733 {
    734     int n;
    735     Node *p;
    736     List *tlist;
    737     struct pretag_proc_data ppd;
    738 
    739     p = findnode (mtlist, update_dir);
    740     if (p != NULL)
    741         tlist = ((struct master_lists *) p->data)->tlist;
    742     else
    743         tlist = NULL;
    744     if (tlist == NULL || tlist->list->next == tlist->list)
    745         return err;
    746 
    747     ppd.tlist = tlist;
    748     ppd.delete_flag = delete_flag;
    749     ppd.force_tag_move = force_tag_move;
    750     ppd.symtag = symtag;
    751     if ((n = Parse_Info (CVSROOTADM_TAGINFO, repos, pretag_proc, PIOPT_ALL,
    752 			 &ppd)) > 0)
    753     {
    754         error (0, 0, "Pre-tag check failed");
    755         err += n;
    756     }
    757     return err;
    758 }
    759 
    760 
    761 
    762 /*
    763  * called from Parse_Info, this routine processes a line that came out
    764  * of a taginfo file and turns it into a command and executes it.
    765  *
    766  * RETURNS
    767  *    the absolute value of the return value of run_exec, which may or
    768  *    may not be the return value of the child process.  this is
    769  *    contrained to return positive values because Parse_Info is adding up
    770  *    return values and testing for non-zeroness to signify one or more
    771  *    of its callbacks having returned an error.
    772  */
    773 static int
    774 pretag_proc (const char *repository, const char *filter, void *closure)
    775 {
    776     char *newfilter = NULL;
    777     char *cmdline;
    778     const char *srepos = Short_Repository (repository);
    779     struct pretag_proc_data *ppd = closure;
    780 
    781 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
    782     if (!strchr (filter, '%'))
    783     {
    784 	error (0,0,
    785                "warning: taginfo line contains no format strings:\n"
    786                "    \"%s\"\n"
    787                "Filling in old defaults ('%%t %%o %%p %%{sv}'), but please be aware that this\n"
    788                "usage is deprecated.", filter);
    789 	newfilter = xmalloc (strlen (filter) + 16);
    790 	strcpy (newfilter, filter);
    791 	strcat (newfilter, " %t %o %p %{sv}");
    792 	filter = newfilter;
    793     }
    794 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
    795 
    796     /* %t = tag being added/moved/removed
    797      * %o = operation = "add" | "mov" | "del"
    798      * %b = branch mode = "?" (delete ops - unknown) | "T" (branch)
    799      *                    | "N" (not branch)
    800      * %c = cvs_cmd_name
    801      * %p = path from $CVSROOT
    802      * %r = path from root
    803      * %{sVv} = attribute list = file name, old version tag will be deleted
    804      *                           from, new version tag will be added to (or
    805      *                           deleted from until
    806      *                           SUPPORT_OLD_INFO_FMT_STRINGS is undefined)
    807      */
    808     /*
    809      * Cast any NULL arguments as appropriate pointers as this is an
    810      * stdarg function and we need to be certain the caller gets what
    811      * is expected.
    812      */
    813     cmdline = format_cmdline (
    814 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
    815 			      false, srepos,
    816 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
    817 			      filter,
    818 			      "t", "s", ppd->symtag,
    819 			      "o", "s", ppd->delete_flag ? "del" :
    820 			      ppd->force_tag_move ? "mov" : "add",
    821 			      "b", "c", delete_flag
    822 			      ? '?' : branch_mode ? 'T' : 'N',
    823 			      "c", "s", cvs_cmd_name,
    824 #ifdef SERVER_SUPPORT
    825 			      "R", "s", referrer ? referrer->original : "NONE",
    826 #endif /* SERVER_SUPPORT */
    827 			      "p", "s", srepos,
    828 			      "r", "s", current_parsed_root->directory,
    829 			      "sVv", ",", ppd->tlist,
    830 			      pretag_list_to_args_proc, (void *) NULL,
    831 			      (char *) NULL);
    832 
    833     if (newfilter) free (newfilter);
    834 
    835     if (!cmdline || !strlen (cmdline))
    836     {
    837 	if (cmdline) free (cmdline);
    838 	error (0, 0, "pretag proc resolved to the empty string!");
    839 	return 1;
    840     }
    841 
    842     run_setup (cmdline);
    843 
    844     /* FIXME - the old code used to run the following here:
    845      *
    846      * if (!isfile(s))
    847      * {
    848      *     error (0, errno, "cannot find pre-tag filter '%s'", s);
    849      *     free(s);
    850      *     return (1);
    851      * }
    852      *
    853      * not sure this is really necessary.  it might give a little finer grained
    854      * error than letting the execution attempt fail but i'm not sure.  in any
    855      * case it should be easy enough to add a function in run.c to test its
    856      * first arg for fileness & executability.
    857      */
    858 
    859     free (cmdline);
    860     return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL));
    861 }
    862 
    863 
    864 
    865 static void
    866 masterlist_delproc (Node *p)
    867 {
    868     struct master_lists *ml = p->data;
    869 
    870     dellist (&ml->tlist);
    871     free (ml);
    872     return;
    873 }
    874 
    875 
    876 
    877 static void
    878 tag_delproc (Node *p)
    879 {
    880     struct tag_info *ti;
    881     if (p->data)
    882     {
    883 	ti = (struct tag_info *) p->data;
    884 	if (ti->oldrev) free (ti->oldrev);
    885 	if (ti->rev) free (ti->rev);
    886 	free (ti->tag);
    887         free (p->data);
    888         p->data = NULL;
    889     }
    890     return;
    891 }
    892 
    893 
    894 
    895 /* to be passed into walklist with a list of tags
    896  * p->key = tagname
    897  * p->data = struct tag_info *
    898  * p->data->oldrev = rev tag will be deleted from
    899  * p->data->rev = rev tag will be added to
    900  * p->data->tag = tag oldrev is attached to, if any
    901  *
    902  * closure will be a struct format_cmdline_walklist_closure
    903  * where closure is undefined
    904  */
    905 static int
    906 pretag_list_to_args_proc (Node *p, void *closure)
    907 {
    908     struct tag_info *taginfo = (struct tag_info *)p->data;
    909     struct format_cmdline_walklist_closure *c =
    910             (struct format_cmdline_walklist_closure *)closure;
    911     char *arg = NULL;
    912     const char *f;
    913     char *d;
    914     size_t doff;
    915 
    916     if (!p->data) return 1;
    917 
    918     f = c->format;
    919     d = *c->d;
    920     /* foreach requested attribute */
    921     while (*f)
    922     {
    923    	switch (*f++)
    924 	{
    925 	    case 's':
    926 		arg = p->key;
    927 		break;
    928 	    case 'T':
    929 		arg = taginfo->tag ? taginfo->tag : "";
    930 		break;
    931 	    case 'v':
    932 		arg = taginfo->rev ? taginfo->rev : "NONE";
    933 		break;
    934 	    case 'V':
    935 		arg = taginfo->oldrev ? taginfo->oldrev : "NONE";
    936 		break;
    937 	    default:
    938 		error(1,0,
    939                       "Unknown format character or not a list attribute: %c",
    940 		      f[-1]);
    941 		break;
    942 	}
    943 	/* copy the attribute into an argument */
    944 	if (c->quotes)
    945 	{
    946 	    arg = cmdlineescape (c->quotes, arg);
    947 	}
    948 	else
    949 	{
    950 	    arg = cmdlinequote ('"', arg);
    951 	}
    952 
    953 	doff = d - *c->buf;
    954 	expand_string (c->buf, c->length, doff + strlen (arg));
    955 	d = *c->buf + doff;
    956 	strncpy (d, arg, strlen (arg));
    957 	d += strlen (arg);
    958 
    959 	free (arg);
    960 
    961 	/* and always put the extra space on.  we'll have to back up a char when we're
    962 	 * done, but that seems most efficient
    963 	 */
    964 	doff = d - *c->buf;
    965 	expand_string (c->buf, c->length, doff + 1);
    966 	d = *c->buf + doff;
    967 	*d++ = ' ';
    968     }
    969     /* correct our original pointer into the buff */
    970     *c->d = d;
    971     return 0;
    972 }
    973 
    974 
    975 /*
    976  * Called to rtag a particular file, as appropriate with the options that were
    977  * set above.
    978  */
    979 /* ARGSUSED */
    980 static int
    981 rtag_fileproc (void *callerdat, struct file_info *finfo)
    982 {
    983     RCSNode *rcsfile;
    984     char *version = NULL, *rev = NULL;
    985     int retcode = 0;
    986     int retval = 0;
    987     static bool valtagged = false;
    988 
    989     /* find the parsed RCS data */
    990     if ((rcsfile = finfo->rcs) == NULL)
    991     {
    992 	retval = 1;
    993 	goto free_vars_and_return;
    994     }
    995 
    996     /*
    997      * For tagging an RCS file which is a symbolic link, you'd best be
    998      * running with RCS 5.6, since it knows how to handle symbolic links
    999      * correctly without breaking your link!
   1000      */
   1001 
   1002 /* cvsacl patch */
   1003 #ifdef SERVER_SUPPORT
   1004     if (use_cvs_acl /* && server_active */)
   1005     {
   1006 	if (!access_allowed (finfo->file, finfo->repository, numtag, 4,
   1007 	    NULL, NULL, 1))
   1008 	{
   1009 	    if (stop_at_first_permission_denied)
   1010 		error (1, 0, "permission denied for %s",
   1011 		       Short_Repository (finfo->repository));
   1012 	    else
   1013 		error (0, 0, "permission denied for %s/%s",
   1014 		       Short_Repository (finfo->repository), finfo->file);
   1015 
   1016 	    return (0);
   1017 	}
   1018     }
   1019 #endif
   1020 
   1021     if (delete_flag)
   1022     {
   1023 	retval = rtag_delete (rcsfile);
   1024 	goto free_vars_and_return;
   1025     }
   1026 
   1027     /*
   1028      * If we get here, we are adding a tag.  But, if -a was specified, we
   1029      * need to check to see if a -r or -D option was specified.  If neither
   1030      * was specified and the file is in the Attic, remove the tag.
   1031      */
   1032     if (attic_too && (!numtag && !date))
   1033     {
   1034 	if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
   1035 	{
   1036 	    retval = rtag_delete (rcsfile);
   1037 	    goto free_vars_and_return;
   1038 	}
   1039     }
   1040 
   1041     version = RCS_getversion (rcsfile, numtag, date, force_tag_match, NULL);
   1042     if (version == NULL)
   1043     {
   1044 	/* If -a specified, clean up any old tags */
   1045 	if (attic_too)
   1046 	    (void)rtag_delete (rcsfile);
   1047 
   1048 	if (!quiet && !force_tag_match)
   1049 	{
   1050 	    error (0, 0, "cannot find tag `%s' in `%s'",
   1051 		   numtag ? numtag : "head", rcsfile->path);
   1052 	    retval = 1;
   1053 	}
   1054 	goto free_vars_and_return;
   1055     }
   1056     if (numtag
   1057 	&& isdigit ((unsigned char)*numtag)
   1058 	&& strcmp (numtag, version) != 0)
   1059     {
   1060 
   1061 	/*
   1062 	 * We didn't find a match for the numeric tag that was specified, but
   1063 	 * that's OK.  just pass the numeric tag on to rcs, to be tagged as
   1064 	 * specified.  Could get here if one tried to tag "1.1.1" and there
   1065 	 * was a 1.1.1 branch with some head revision.  In this case, we want
   1066 	 * the tag to reference "1.1.1" and not the revision at the head of
   1067 	 * the branch.  Use a symbolic tag for that.
   1068 	 */
   1069 	rev = branch_mode ? RCS_magicrev (rcsfile, version) : numtag;
   1070 	retcode = RCS_settag(rcsfile, symtag, numtag);
   1071 	if (retcode == 0)
   1072 	    RCS_rewrite (rcsfile, NULL, NULL);
   1073     }
   1074     else
   1075     {
   1076 	char *oversion;
   1077 
   1078 	/*
   1079 	 * As an enhancement for the case where a tag is being re-applied to
   1080 	 * a large body of a module, make one extra call to RCS_getversion to
   1081 	 * see if the tag is already set in the RCS file.  If so, check to
   1082 	 * see if it needs to be moved.  If not, do nothing.  This will
   1083 	 * likely save a lot of time when simply moving the tag to the
   1084 	 * "current" head revisions of a module -- which I have found to be a
   1085 	 * typical tagging operation.
   1086 	 */
   1087 	rev = branch_mode ? RCS_magicrev (rcsfile, version) : version;
   1088 	oversion = RCS_getversion (rcsfile, symtag, NULL, 1, NULL);
   1089 	if (oversion != NULL)
   1090 	{
   1091 	    int isbranch = RCS_nodeisbranch (finfo->rcs, symtag);
   1092 
   1093 	    /*
   1094 	     * if versions the same and neither old or new are branches don't
   1095 	     * have to do anything
   1096 	     */
   1097 	    if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch)
   1098 	    {
   1099 		free (oversion);
   1100 		goto free_vars_and_return;
   1101 	    }
   1102 
   1103 	    if (!force_tag_move)
   1104 	    {
   1105 		/* we're NOT going to move the tag */
   1106 		(void)printf ("W %s", finfo->fullname);
   1107 
   1108 		(void)printf (" : %s already exists on %s %s",
   1109 			      symtag, isbranch ? "branch" : "version",
   1110 			      oversion);
   1111 		(void)printf (" : NOT MOVING tag to %s %s\n",
   1112 			      branch_mode ? "branch" : "version", rev);
   1113 		free (oversion);
   1114 		goto free_vars_and_return;
   1115 	    }
   1116 	    else /* force_tag_move is set and... */
   1117 		if ((isbranch && !disturb_branch_tags) ||
   1118 		    (!isbranch && disturb_branch_tags))
   1119 	    {
   1120 	        error(0,0, "%s: Not moving %s tag `%s' from %s to %s%s.",
   1121 			finfo->fullname,
   1122 			isbranch ? "branch" : "non-branch",
   1123 			symtag, oversion, rev,
   1124 			isbranch ? "" : " due to `-B' option");
   1125 		free (oversion);
   1126 		goto free_vars_and_return;
   1127 	    }
   1128 	    free (oversion);
   1129 	}
   1130 	retcode = RCS_settag (rcsfile, symtag, rev);
   1131 	if (retcode == 0)
   1132 	    RCS_rewrite (rcsfile, NULL, NULL);
   1133     }
   1134 
   1135     if (retcode != 0)
   1136     {
   1137 	error (1, retcode == -1 ? errno : 0,
   1138 	       "failed to set tag `%s' to revision `%s' in `%s'",
   1139 	       symtag, rev, rcsfile->path);
   1140         retval = 1;
   1141 	goto free_vars_and_return;
   1142     }
   1143 
   1144 free_vars_and_return:
   1145     if (branch_mode && rev) free (rev);
   1146     if (version) free (version);
   1147     if (!delete_flag && !retval && !valtagged)
   1148     {
   1149 	tag_check_valid (symtag, 0, NULL, 0, 0, NULL, true);
   1150 	valtagged = true;
   1151     }
   1152     return retval;
   1153 }
   1154 
   1155 
   1156 
   1157 /*
   1158  * If -d is specified, "force_tag_match" is set, so that this call to
   1159  * RCS_getversion() will return a NULL version string if the symbolic
   1160  * tag does not exist in the RCS file.
   1161  *
   1162  * If the -r flag was used, numtag is set, and we only delete the
   1163  * symtag from files that have numtag.
   1164  *
   1165  * This is done here because it's MUCH faster than just blindly calling
   1166  * "rcs" to remove the tag... trust me.
   1167  */
   1168 static int
   1169 rtag_delete (RCSNode *rcsfile)
   1170 {
   1171     char *version;
   1172     int retcode, isbranch;
   1173 
   1174     if (numtag)
   1175     {
   1176 	version = RCS_getversion (rcsfile, numtag, NULL, 1, NULL);
   1177 	if (version == NULL)
   1178 	    return (0);
   1179 	free (version);
   1180     }
   1181 
   1182     version = RCS_getversion (rcsfile, symtag, NULL, 1, NULL);
   1183     if (version == NULL)
   1184 	return 0;
   1185     free (version);
   1186 
   1187 
   1188     isbranch = RCS_nodeisbranch (rcsfile, symtag);
   1189     if ((isbranch && !disturb_branch_tags) ||
   1190 	(!isbranch && disturb_branch_tags))
   1191     {
   1192 	if (!really_quiet)
   1193 	    error (0, 0,
   1194                    "Not removing %s tag `%s' from `%s'%s.",
   1195                    isbranch ? "branch" : "non-branch",
   1196                    symtag, rcsfile->path,
   1197                    isbranch ? "" : " due to `-B' option");
   1198 	return 1;
   1199     }
   1200 
   1201     if ((retcode = RCS_deltag(rcsfile, symtag)) != 0)
   1202     {
   1203 	if (!really_quiet)
   1204 	    error (0, retcode == -1 ? errno : 0,
   1205 		   "failed to remove tag `%s' from `%s'", symtag,
   1206 		   rcsfile->path);
   1207 	return 1;
   1208     }
   1209     RCS_rewrite (rcsfile, NULL, NULL);
   1210     return 0;
   1211 }
   1212 
   1213 
   1214 
   1215 /*
   1216  * Called to tag a particular file (the currently checked out version is
   1217  * tagged with the specified tag - or the specified tag is deleted).
   1218  */
   1219 /* ARGSUSED */
   1220 static int
   1221 tag_fileproc (void *callerdat, struct file_info *finfo)
   1222 {
   1223     char *version, *oversion;
   1224     char *nversion = NULL;
   1225     char *rev;
   1226     Vers_TS *vers;
   1227     int retcode = 0;
   1228     int retval = 0;
   1229     static bool valtagged = false;
   1230 
   1231     vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
   1232 
   1233     if (numtag || date)
   1234     {
   1235         nversion = RCS_getversion (vers->srcfile, numtag, date,
   1236                                    force_tag_match, NULL);
   1237         if (!nversion)
   1238 	    goto free_vars_and_return;
   1239     }
   1240 
   1241 /* cvsacl patch */
   1242 #ifdef SERVER_SUPPORT
   1243 	if (use_cvs_acl /* && server_active */)
   1244 	{
   1245 	if (!access_allowed (finfo->file, finfo->repository, vers->tag, 4,
   1246 			     NULL, NULL, 1))
   1247 	{
   1248 	    error (0, 0, "permission denied for %s/%s",
   1249 		   Short_Repository (finfo->repository), finfo->file);
   1250 	    return (0);
   1251 	}
   1252     }
   1253 #endif
   1254 
   1255     if (delete_flag)
   1256     {
   1257 
   1258 	int isbranch;
   1259 	/*
   1260 	 * If -d is specified, "force_tag_match" is set, so that this call to
   1261 	 * RCS_getversion() will return a NULL version string if the symbolic
   1262 	 * tag does not exist in the RCS file.
   1263 	 *
   1264 	 * This is done here because it's MUCH faster than just blindly calling
   1265 	 * "rcs" to remove the tag... trust me.
   1266 	 */
   1267 
   1268 	version = RCS_getversion (vers->srcfile, symtag, NULL, 1, NULL);
   1269 	if (version == NULL || vers->srcfile == NULL)
   1270 	    goto free_vars_and_return;
   1271 
   1272 	free (version);
   1273 
   1274 	isbranch = RCS_nodeisbranch (finfo->rcs, symtag);
   1275 	if ((isbranch && !disturb_branch_tags) ||
   1276 	    (!isbranch && disturb_branch_tags))
   1277 	{
   1278 	    if (!really_quiet)
   1279 		error(0, 0,
   1280 		       "Not removing %s tag `%s' from `%s'%s.",
   1281 			isbranch ? "branch" : "non-branch",
   1282 			symtag, vers->srcfile->path,
   1283 			isbranch ? "" : " due to `-B' option");
   1284 	    retval = 1;
   1285 	    goto free_vars_and_return;
   1286 	}
   1287 
   1288 	if ((retcode = RCS_deltag (vers->srcfile, symtag)) != 0)
   1289 	{
   1290 	    if (!really_quiet)
   1291 		error (0, retcode == -1 ? errno : 0,
   1292 		       "failed to remove tag %s from %s", symtag,
   1293 		       vers->srcfile->path);
   1294 	    retval = 1;
   1295 	    goto free_vars_and_return;
   1296 	}
   1297 	RCS_rewrite (vers->srcfile, NULL, NULL);
   1298 
   1299 	/* warm fuzzies */
   1300 	if (!really_quiet)
   1301 	{
   1302 	    cvs_output ("D ", 2);
   1303 	    cvs_output (finfo->fullname, 0);
   1304 	    cvs_output ("\n", 1);
   1305 	}
   1306 
   1307 	goto free_vars_and_return;
   1308     }
   1309 
   1310     /*
   1311      * If we are adding a tag, we need to know which version we have checked
   1312      * out and we'll tag that version.
   1313      */
   1314     if (!nversion)
   1315         version = vers->vn_user;
   1316     else
   1317         version = nversion;
   1318     if (!version)
   1319 	goto free_vars_and_return;
   1320     else if (strcmp (version, "0") == 0)
   1321     {
   1322 	if (!quiet)
   1323 	    error (0, 0, "couldn't tag added but un-commited file `%s'",
   1324 	           finfo->file);
   1325 	goto free_vars_and_return;
   1326     }
   1327     else if (version[0] == '-')
   1328     {
   1329 	if (!quiet)
   1330 	    error (0, 0, "skipping removed but un-commited file `%s'",
   1331 		   finfo->file);
   1332 	goto free_vars_and_return;
   1333     }
   1334     else if (vers->srcfile == NULL)
   1335     {
   1336 	if (!quiet)
   1337 	    error (0, 0, "cannot find revision control file for `%s'",
   1338 		   finfo->file);
   1339 	goto free_vars_and_return;
   1340     }
   1341 
   1342     /*
   1343      * As an enhancement for the case where a tag is being re-applied to a
   1344      * large number of files, make one extra call to RCS_getversion to see
   1345      * if the tag is already set in the RCS file.  If so, check to see if it
   1346      * needs to be moved.  If not, do nothing.  This will likely save a lot of
   1347      * time when simply moving the tag to the "current" head revisions of a
   1348      * module -- which I have found to be a typical tagging operation.
   1349      */
   1350     rev = branch_mode ? RCS_magicrev (vers->srcfile, version) : version;
   1351     oversion = RCS_getversion (vers->srcfile, symtag, NULL, 1, NULL);
   1352     if (oversion != NULL)
   1353     {
   1354 	int isbranch = RCS_nodeisbranch (finfo->rcs, symtag);
   1355 
   1356 	/*
   1357 	 * if versions the same and neither old or new are branches don't have
   1358 	 * to do anything
   1359 	 */
   1360 	if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch)
   1361 	{
   1362 	    free (oversion);
   1363 	    if (branch_mode)
   1364 		free (rev);
   1365 	    goto free_vars_and_return;
   1366 	}
   1367 
   1368 	if (!force_tag_move)
   1369 	{
   1370 	    /* we're NOT going to move the tag */
   1371 	    cvs_output ("W ", 2);
   1372 	    cvs_output (finfo->fullname, 0);
   1373 	    cvs_output (" : ", 0);
   1374 	    cvs_output (symtag, 0);
   1375 	    cvs_output (" already exists on ", 0);
   1376 	    cvs_output (isbranch ? "branch" : "version", 0);
   1377 	    cvs_output (" ", 0);
   1378 	    cvs_output (oversion, 0);
   1379 	    cvs_output (" : NOT MOVING tag to ", 0);
   1380 	    cvs_output (branch_mode ? "branch" : "version", 0);
   1381 	    cvs_output (" ", 0);
   1382 	    cvs_output (rev, 0);
   1383 	    cvs_output ("\n", 1);
   1384 	    free (oversion);
   1385 	    if (branch_mode)
   1386 		free (rev);
   1387 	    goto free_vars_and_return;
   1388 	}
   1389 	else 	/* force_tag_move == 1 and... */
   1390 		if ((isbranch && !disturb_branch_tags) ||
   1391 		    (!isbranch && disturb_branch_tags))
   1392 	{
   1393 	    error (0,0, "%s: Not moving %s tag `%s' from %s to %s%s.",
   1394 		   finfo->fullname,
   1395 		   isbranch ? "branch" : "non-branch",
   1396 		   symtag, oversion, rev,
   1397 		   isbranch ? "" : " due to `-B' option");
   1398 	    free (oversion);
   1399 	    if (branch_mode)
   1400 		free (rev);
   1401 	    goto free_vars_and_return;
   1402 	}
   1403 	free (oversion);
   1404     }
   1405 
   1406     if ((retcode = RCS_settag(vers->srcfile, symtag, rev)) != 0)
   1407     {
   1408 	error (1, retcode == -1 ? errno : 0,
   1409 	       "failed to set tag %s to revision %s in %s",
   1410 	       symtag, rev, vers->srcfile->path);
   1411 	if (branch_mode)
   1412 	    free (rev);
   1413 	retval = 1;
   1414 	goto free_vars_and_return;
   1415     }
   1416     if (branch_mode)
   1417 	free (rev);
   1418     RCS_rewrite (vers->srcfile, NULL, NULL);
   1419 
   1420     /* more warm fuzzies */
   1421     if (!really_quiet)
   1422     {
   1423 	cvs_output ("T ", 2);
   1424 	cvs_output (finfo->fullname, 0);
   1425 	cvs_output ("\n", 1);
   1426     }
   1427 
   1428  free_vars_and_return:
   1429     if (nversion != NULL)
   1430         free (nversion);
   1431     freevers_ts (&vers);
   1432     if (!delete_flag && !retval && !valtagged)
   1433     {
   1434 	tag_check_valid (symtag, 0, NULL, 0, 0, NULL, true);
   1435 	valtagged = true;
   1436     }
   1437     return retval;
   1438 }
   1439 
   1440 
   1441 
   1442 /*
   1443  * Print a warm fuzzy message
   1444  */
   1445 /* ARGSUSED */
   1446 static Dtype
   1447 tag_dirproc (void *callerdat, const char *dir, const char *repos,
   1448              const char *update_dir, List *entries)
   1449 {
   1450 
   1451     if (ignore_directory (update_dir))
   1452     {
   1453 	/* print the warm fuzzy message */
   1454 	if (!quiet)
   1455 	  error (0, 0, "Ignoring %s", update_dir);
   1456         return R_SKIP_ALL;
   1457     }
   1458 
   1459     if (!quiet)
   1460 	error (0, 0, "%s %s", delete_flag ? "Untagging" : "Tagging",
   1461                update_dir);
   1462     return R_PROCESS;
   1463 }
   1464 
   1465 
   1466 
   1467 /* Code relating to the val-tags file.  Note that this file has no way
   1468    of knowing when a tag has been deleted.  The problem is that there
   1469    is no way of knowing whether a tag still exists somewhere, when we
   1470    delete it some places.  Using per-directory val-tags files (in
   1471    CVSREP) might be better, but that might slow down the process of
   1472    verifying that a tag is correct (maybe not, for the likely cases,
   1473    if carefully done), and/or be harder to implement correctly.  */
   1474 
   1475 struct val_args {
   1476     const char *name;
   1477     int found;
   1478 };
   1479 
   1480 static int
   1481 val_fileproc (void *callerdat, struct file_info *finfo)
   1482 {
   1483     RCSNode *rcsdata;
   1484     struct val_args *args = callerdat;
   1485     char *tag;
   1486 
   1487     if ((rcsdata = finfo->rcs) == NULL)
   1488 	/* Not sure this can happen, after all we passed only
   1489 	   W_REPOS | W_ATTIC.  */
   1490 	return 0;
   1491 
   1492     tag = RCS_gettag (rcsdata, args->name, 1, NULL);
   1493     if (tag != NULL)
   1494     {
   1495 	/* FIXME: should find out a way to stop the search at this point.  */
   1496 	args->found = 1;
   1497 	free (tag);
   1498     }
   1499     return 0;
   1500 }
   1501 
   1502 
   1503 
   1504 /* This routine determines whether a tag appears in CVSROOT/val-tags.
   1505  *
   1506  * The val-tags file will be open read-only when IDB is NULL.  Since writes to
   1507  * val-tags always append to it, the lack of locking is okay.  The worst case
   1508  * race condition might misinterpret a partially written "foobar" matched, for
   1509  * instance,  a request for "f", "foo", of "foob".  Such a mismatch would be
   1510  * caught harmlessly later.
   1511  *
   1512  * Before CVS adds a tag to val-tags, it will lock val-tags for write and
   1513  * verify that the tag is still not present to avoid adding it twice.
   1514  *
   1515  * NOTES
   1516  *   This function expects its parent to handle any necessary locking of the
   1517  *   val-tags file.
   1518  *
   1519  * INPUTS
   1520  *   idb	When this value is NULL, the val-tags file is opened in
   1521  *   		in read-only mode.  When present, the val-tags file is opened
   1522  *   		in read-write mode and the DBM handle is stored in *IDB.
   1523  *   name	The tag to search for.
   1524  *
   1525  * OUTPUTS
   1526  *   *idb	The val-tags file opened for read/write, or NULL if it couldn't
   1527  *   		be opened.
   1528  *
   1529  * ERRORS
   1530  *   Exits with an error message if the val-tags file cannot be opened for
   1531  *   read (failure to open val-tags read/write is harmless - see below).
   1532  *
   1533  * RETURNS
   1534  *   true	1. If NAME exists in val-tags.
   1535  *   		2. If IDB is non-NULL and val-tags cannot be opened for write.
   1536  *   		   This allows callers to ignore the harmless inability to
   1537  *   		   update the val-tags cache.
   1538  *   false	If the file could be opened and the tag is not present.
   1539  */
   1540 static int is_in_val_tags (DBM **idb, const char *name)
   1541 {
   1542     DBM *db = NULL;
   1543     char *valtags_filename;
   1544     datum mytag;
   1545     int status;
   1546 
   1547     /* Casting out const should be safe here - input datums are not
   1548      * written to by the myndbm functions.
   1549      */
   1550     mytag.dptr = (char *)name;
   1551     mytag.dsize = strlen (name);
   1552 
   1553     valtags_filename = Xasprintf ("%s/%s/%s", current_parsed_root->directory,
   1554 				  CVSROOTADM, CVSROOTADM_VALTAGS);
   1555 
   1556     if (idb)
   1557     {
   1558 	mode_t omask;
   1559 
   1560 	omask = umask (cvsumask);
   1561 	db = dbm_open (valtags_filename, O_RDWR | O_CREAT, 0666);
   1562 	umask (omask);
   1563 
   1564 	if (!db)
   1565 	{
   1566 
   1567 	    error (0, errno, "warning: cannot open `%s' read/write",
   1568 		   valtags_filename);
   1569 	    *idb = NULL;
   1570 	    return 1;
   1571 	}
   1572 
   1573 	*idb = db;
   1574     }
   1575     else
   1576     {
   1577 	db = dbm_open (valtags_filename, O_RDONLY, 0444);
   1578 	if (!db && !existence_error (errno))
   1579 	    error (1, errno, "cannot read %s", valtags_filename);
   1580     }
   1581 
   1582     /* If the file merely fails to exist, we just keep going and create
   1583        it later if need be.  */
   1584 
   1585     status = 0;
   1586     if (db)
   1587     {
   1588 	datum val;
   1589 
   1590 	val = dbm_fetch (db, mytag);
   1591 	if (val.dptr != NULL)
   1592 	    /* Found.  The tag is valid.  */
   1593 	    status = 1;
   1594 
   1595 	/* FIXME: should check errors somehow (add dbm_error to myndbm.c?).  */
   1596 
   1597 	if (!idb) dbm_close (db);
   1598     }
   1599 
   1600     free (valtags_filename);
   1601     return status;
   1602 }
   1603 
   1604 
   1605 
   1606 /* Add a tag to the CVSROOT/val-tags cache.  Establishes a write lock and
   1607  * reverifies that the tag does not exist before adding it.
   1608  */
   1609 static void add_to_val_tags (const char *name)
   1610 {
   1611     DBM *db;
   1612     datum mytag;
   1613     datum value;
   1614 
   1615     if (noexec) return;
   1616 
   1617     val_tags_lock (current_parsed_root->directory);
   1618 
   1619     /* Check for presence again since we have a lock now.  */
   1620     if (is_in_val_tags (&db, name)) return;
   1621 
   1622     /* Casting out const should be safe here - input datums are not
   1623      * written to by the myndbm functions.
   1624      */
   1625     mytag.dptr = (char *)name;
   1626     mytag.dsize = strlen (name);
   1627     value.dptr = "y";
   1628     value.dsize = 1;
   1629 
   1630     if (dbm_store (db, mytag, value, DBM_REPLACE) < 0)
   1631 	error (0, errno, "failed to store %s into val-tags", name);
   1632     dbm_close (db);
   1633 
   1634     clear_val_tags_lock ();
   1635 }
   1636 
   1637 
   1638 
   1639 static Dtype
   1640 val_direntproc (void *callerdat, const char *dir, const char *repository,
   1641                 const char *update_dir, List *entries)
   1642 {
   1643     /* This is not quite right--it doesn't get right the case of "cvs
   1644        update -d -r foobar" where foobar is a tag which exists only in
   1645        files in a directory which does not exist yet, but which is
   1646        about to be created.  */
   1647     if (isdir (dir))
   1648 	return R_PROCESS;
   1649     return R_SKIP_ALL;
   1650 }
   1651 
   1652 
   1653 
   1654 /* With VALID set, insert NAME into val-tags if it is not already present
   1655  * there.
   1656  *
   1657  * Without VALID set, check to see whether NAME is a valid tag.  If so, return.
   1658  * If not print an error message and exit.
   1659  *
   1660  * INPUTS
   1661  *
   1662  *   ARGC, ARGV, LOCAL, and AFLAG specify which files we will be operating on.
   1663  *
   1664  *   REPOSITORY is the repository if we need to cd into it, or NULL if
   1665  *     we are already there, or "" if we should do a W_LOCAL recursion.
   1666  *     Sorry for three cases, but the "" case is needed in case the
   1667  *     working directories come from diverse parts of the repository, the
   1668  *     NULL case avoids an unneccesary chdir, and the non-NULL, non-""
   1669  *     case is needed for checkout, where we don't want to chdir if the
   1670  *     tag is found in CVSROOTADM_VALTAGS, but there is not (yet) any
   1671  *     local directory.
   1672  *
   1673  * ERRORS
   1674  *   Errors may be encountered opening and accessing the DBM file.  Write
   1675  *   errors generate warnings and read errors are fatal.  When !VALID and NAME
   1676  *   is not in val-tags, errors may also be generated as per start_recursion.
   1677  *   When !VALID, non-existance of tags both in val-tags and in the archive
   1678  *   files also causes a fatal error.
   1679  *
   1680  * RETURNS
   1681  *   Nothing.
   1682  */
   1683 void
   1684 tag_check_valid (const char *name, int argc, char **argv, int local, int aflag,
   1685                  char *repository, bool valid)
   1686 {
   1687     struct val_args the_val_args;
   1688     struct saved_cwd cwd;
   1689     int which;
   1690 
   1691 #ifdef HAVE_PRINTF_PTR
   1692     TRACE (TRACE_FUNCTION,
   1693 	   "tag_check_valid (name=%s, argc=%d, argv=%p, local=%d,\n"
   1694       "                      aflag=%d, repository=%s, valid=%s)",
   1695 	   name ? name : "(name)", argc, (void *)argv, local, aflag,
   1696 	   repository ? repository : "(null)",
   1697 	   valid ? "true" : "false");
   1698 #else
   1699     TRACE (TRACE_FUNCTION,
   1700 	   "tag_check_valid (name=%s, argc=%d, argv=%lx, local=%d,\n"
   1701       "                      aflag=%d, repository=%s, valid=%s)",
   1702 	   name ? name : "(name)", argc, (unsigned long)argv, local, aflag,
   1703 	   repository ? repository : "(null)",
   1704 	   valid ? "true" : "false");
   1705 #endif
   1706 
   1707     /* Numeric tags require only a syntactic check.  */
   1708     if (isdigit ((unsigned char) name[0]))
   1709     {
   1710 	/* insert is not possible for numeric revisions */
   1711 	assert (!valid);
   1712 	if (RCS_valid_rev (name)) return;
   1713 	else
   1714 	    error (1, 0, "\
   1715 Numeric tag %s invalid.  Numeric tags should be of the form X[.X]...", name);
   1716     }
   1717 
   1718     /* Special tags are always valid.  */
   1719     if (strcmp (name, TAG_BASE) == 0
   1720 	|| strcmp (name, TAG_HEAD) == 0)
   1721     {
   1722 	/* insert is not possible for numeric revisions */
   1723 	assert (!valid);
   1724 	return;
   1725     }
   1726 
   1727     /* Verify that the tag is valid syntactically.  Some later code once made
   1728      * assumptions about this.
   1729      */
   1730     RCS_check_tag (name);
   1731 
   1732     if (is_in_val_tags (NULL, name)) return;
   1733 
   1734     if (!valid)
   1735     {
   1736 	/* We didn't find the tag in val-tags, so look through all the RCS files
   1737 	 * to see whether it exists there.  Yes, this is expensive, but there
   1738 	 * is no other way to cope with a tag which might have been created
   1739 	 * by an old version of CVS, from before val-tags was invented
   1740 	 */
   1741 
   1742 	the_val_args.name = name;
   1743 	the_val_args.found = 0;
   1744 	which = W_REPOS | W_ATTIC;
   1745 
   1746 	if (repository == NULL || repository[0] == '\0')
   1747 	    which |= W_LOCAL;
   1748 	else
   1749 	{
   1750 	    if (save_cwd (&cwd))
   1751 		error (1, errno, "Failed to save current directory.");
   1752 	    if (CVS_CHDIR (repository) < 0)
   1753 		error (1, errno, "cannot change to %s directory", repository);
   1754 	}
   1755 
   1756 	start_recursion
   1757 	    (val_fileproc, NULL, val_direntproc, NULL,
   1758 	     &the_val_args, argc, argv, local, which, aflag,
   1759 	     CVS_LOCK_READ, NULL, 1, repository);
   1760 	if (repository != NULL && repository[0] != '\0')
   1761 	{
   1762 	    if (restore_cwd (&cwd))
   1763 		error (1, errno, "Failed to restore current directory, `%s'.",
   1764 		       cwd.name);
   1765 	    free_cwd (&cwd);
   1766 	}
   1767 
   1768 	if (!the_val_args.found)
   1769 	    error (1, 0, "no such tag `%s'", name);
   1770     }
   1771 
   1772     /* The tags is valid but not mentioned in val-tags.  Add it.  */
   1773     add_to_val_tags (name);
   1774 }
   1775