xref: /netbsd-src/external/gpl2/xcvs/dist/src/tag.c (revision f7a36a949c0f55cabe53e46b41c29ccafe28356a)
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
cvstag(int argc,char ** argv)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
posttag_proc(const char * repository,const char * filter,void * closure)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
tag_filesdoneproc(void * callerdat,int err,const char * repository,const char * update_dir,List * entries)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
rtag_proc(int argc,char ** argv,char * xwhere,char * mwhere,char * mfile,int shorten,int local_specified,char * mname,char * msg)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
check_fileproc(void * callerdat,struct file_info * finfo)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
check_filesdoneproc(void * callerdat,int err,const char * repos,const char * update_dir,List * entries)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
pretag_proc(const char * repository,const char * filter,void * closure)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
masterlist_delproc(Node * p)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
tag_delproc(Node * p)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
pretag_list_to_args_proc(Node * p,void * closure)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
rtag_fileproc(void * callerdat,struct file_info * finfo)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
rtag_delete(RCSNode * rcsfile)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
tag_fileproc(void * callerdat,struct file_info * finfo)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
tag_dirproc(void * callerdat,const char * dir,const char * repos,const char * update_dir,List * entries)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
val_fileproc(void * callerdat,struct file_info * finfo)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  */
is_in_val_tags(DBM ** idb,const char * name)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  */
add_to_val_tags(const char * name)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
val_direntproc(void * callerdat,const char * dir,const char * repository,const char * update_dir,List * entries)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
tag_check_valid(const char * name,int argc,char ** argv,int local,int aflag,char * repository,bool valid)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