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