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