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