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