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