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
23static int rtag_proc (int argc, char **argv, char *xwhere,
24		      char *mwhere, char *mfile, int shorten,
25		      int local_specified, char *mname, char *msg);
26static int check_fileproc (void *callerdat, struct file_info *finfo);
27static int check_filesdoneproc (void *callerdat, int err,
28				const char *repos, const char *update_dir,
29				List *entries);
30static int pretag_proc (const char *_repository, const char *_filter,
31                        void *_closure);
32static void masterlist_delproc (Node *_p);
33static void tag_delproc (Node *_p);
34static int pretag_list_to_args_proc (Node *_p, void *_closure);
35
36static Dtype tag_dirproc (void *callerdat, const char *dir,
37                          const char *repos, const char *update_dir,
38                          List *entries);
39static int rtag_fileproc (void *callerdat, struct file_info *finfo);
40static int rtag_delete (RCSNode *rcsfile);
41static int tag_fileproc (void *callerdat, struct file_info *finfo);
42
43static char *numtag;			/* specific revision to tag */
44static bool numtag_validated = false;
45static char *date = NULL;
46static char *symtag;			/* tag to add or delete */
47static bool delete_flag;		/* adding a tag by default */
48static bool branch_mode;		/* make an automagic "branch" tag */
49static bool disturb_branch_tags = false;/* allow -F,-d to disturb branch tags */
50static bool force_tag_match = true;	/* force tag to match by default */
51static bool force_tag_move;		/* don't force tag to move by default */
52static bool check_uptodate;		/* no uptodate-check by default */
53static bool attic_too;			/* remove tag from Attic files */
54static bool is_rtag;
55
56struct tag_info
57{
58    Ctype status;
59    char *oldrev;
60    char *rev;
61    char *tag;
62    char *options;
63};
64
65struct master_lists
66{
67    List *tlist;
68};
69
70static List *mtlist;
71
72static const char rtag_opts[] = "+aBbdFflnQqRr:D:";
73static 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
91static const char tag_opts[] = "+BbcdFflQqRr:D:";
92static 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
111int
112cvstag (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    getoptreset ();
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
283struct 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 */
301static int
302posttag_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 */
363static int
364tag_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 */
399static int
400rtag_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 */
549static int
550check_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
679static int
680check_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 */
722static int
723pretag_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
814static void
815masterlist_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
826static void
827tag_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 */
854static int
855pretag_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 */
929static int
930rtag_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
1074free_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 */
1098static int
1099rtag_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 */
1150static int
1151tag_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 */
1361static Dtype
1362tag_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
1390struct val_args {
1391    const char *name;
1392    int found;
1393};
1394
1395static int
1396val_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 */
1455static 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 */
1524static void add_to_val_tags (const char *name)
1525{
1526    DBM *db;
1527    datum mytag;
1528    datum value;
1529
1530    if (noexec) 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
1554static Dtype
1555val_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 */
1598void
1599tag_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, "\
1630Numeric 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