File: | src/gnu/usr.bin/cvs/src/admin.c |
Warning: | line 208, column 33 Null pointer passed as 1st argument to string length function |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* | |||
2 | * Copyright (c) 1992, Brian Berliner and Jeff Polk | |||
3 | * Copyright (c) 1989-1992, Brian Berliner | |||
4 | * | |||
5 | * You may distribute under the terms of the GNU General Public License as | |||
6 | * specified in the README file that comes with the CVS source distribution. | |||
7 | * | |||
8 | * Administration ("cvs admin") | |||
9 | * | |||
10 | */ | |||
11 | ||||
12 | #include "cvs.h" | |||
13 | #ifdef CVS_ADMIN_GROUP"cvsadmin" | |||
14 | #include <grp.h> | |||
15 | #endif | |||
16 | #include <assert.h> | |||
17 | ||||
18 | static Dtype admin_dirproc PROTO ((void *callerdat, char *dir,(void *callerdat, char *dir, char *repos, char *update_dir, List *entries) | |||
19 | char *repos, char *update_dir,(void *callerdat, char *dir, char *repos, char *update_dir, List *entries) | |||
20 | List *entries))(void *callerdat, char *dir, char *repos, char *update_dir, List *entries); | |||
21 | static int admin_fileproc PROTO ((void *callerdat, struct file_info *finfo))(void *callerdat, struct file_info *finfo); | |||
22 | ||||
23 | static const char *const admin_usage[] = | |||
24 | { | |||
25 | "Usage: %s %s [options] files...\n", | |||
26 | "\t-a users Append (comma-separated) user names to access list.\n", | |||
27 | "\t-A file Append another file's access list.\n", | |||
28 | "\t-b[rev] Set default branch (highest branch on trunk if omitted).\n", | |||
29 | "\t-c string Set comment leader.\n", | |||
30 | "\t-C rev:id Set revision's commit id.\n", | |||
31 | "\t-e[users] Remove (comma-separated) user names from access list\n", | |||
32 | "\t (all names if omitted).\n", | |||
33 | "\t-I Run interactively.\n", | |||
34 | "\t-k subst Set keyword substitution mode:\n", | |||
35 | "\t kv (Default) Substitue keyword and value.\n", | |||
36 | "\t kvl Substitue keyword, value, and locker (if any).\n", | |||
37 | "\t k Substitue keyword only.\n", | |||
38 | "\t o Preserve original string.\n", | |||
39 | "\t b Like o, but mark file as binary.\n", | |||
40 | "\t v Substitue value only.\n", | |||
41 | "\t-l[rev] Lock revision (latest revision on branch,\n", | |||
42 | "\t latest revision on trunk if omitted).\n", | |||
43 | "\t-L Set strict locking.\n", | |||
44 | "\t-m rev:msg Replace revision's log message.\n", | |||
45 | "\t-n tag[:[rev]] Tag branch or revision. If :rev is omitted,\n", | |||
46 | "\t delete the tag; if rev is omitted, tag the latest\n", | |||
47 | "\t revision on the default branch.\n", | |||
48 | "\t-N tag[:[rev]] Same as -n except override existing tag.\n", | |||
49 | "\t-o range Delete (outdate) specified range of revisions:\n", | |||
50 | "\t rev1:rev2 Between rev1 and rev2, including rev1 and rev2.\n", | |||
51 | "\t rev1::rev2 Between rev1 and rev2, excluding rev1 and rev2.\n", | |||
52 | "\t rev: rev and following revisions on the same branch.\n", | |||
53 | "\t rev:: After rev on the same branch.\n", | |||
54 | "\t :rev rev and previous revisions on the same branch.\n", | |||
55 | "\t ::rev Before rev on the same branch.\n", | |||
56 | "\t rev Just rev.\n", | |||
57 | "\t-q Run quietly.\n", | |||
58 | "\t-s state[:rev] Set revision state (latest revision on branch,\n", | |||
59 | "\t latest revision on trunk if omitted).\n", | |||
60 | "\t-t[file] Get descriptive text from file (stdin if omitted).\n", | |||
61 | "\t-t-string Set descriptive text.\n", | |||
62 | "\t-u[rev] Unlock the revision (latest revision on branch,\n", | |||
63 | "\t latest revision on trunk if omitted).\n", | |||
64 | "\t-U Unset strict locking.\n", | |||
65 | "(Specify the --help global option for a list of other help options)\n", | |||
66 | NULL((void*)0) | |||
67 | }; | |||
68 | ||||
69 | /* This structure is used to pass information through start_recursion. */ | |||
70 | struct admin_data | |||
71 | { | |||
72 | /* Set default branch (-b). It is "-b" followed by the value | |||
73 | given, or NULL if not specified, or merely "-b" if -b is | |||
74 | specified without a value. */ | |||
75 | char *branch; | |||
76 | ||||
77 | /* Set comment leader (-c). It is "-c" followed by the value | |||
78 | given, or NULL if not specified. The comment leader is | |||
79 | relevant only for old versions of RCS, but we let people set it | |||
80 | anyway. */ | |||
81 | char *comment; | |||
82 | ||||
83 | /* Set strict locking (-L). */ | |||
84 | int set_strict; | |||
85 | ||||
86 | /* Set nonstrict locking (-U). */ | |||
87 | int set_nonstrict; | |||
88 | ||||
89 | /* Delete revisions (-o). It is "-o" followed by the value specified. */ | |||
90 | char *delete_revs; | |||
91 | ||||
92 | /* Keyword substitution mode (-k), e.g. "-kb". */ | |||
93 | char *kflag; | |||
94 | ||||
95 | /* Description (-t). */ | |||
96 | char *desc; | |||
97 | ||||
98 | /* Interactive (-I). Problematic with client/server. */ | |||
99 | int interactive; | |||
100 | ||||
101 | /* This is the cheesy part. It is a vector with the options which | |||
102 | we don't deal with above (e.g. "-afoo" "-abar,baz"). In the future | |||
103 | this presumably will be replaced by other variables which break | |||
104 | out the data in a more convenient fashion. AV as well as each of | |||
105 | the strings it points to is malloc'd. */ | |||
106 | int ac; | |||
107 | char **av; | |||
108 | int av_alloc; | |||
109 | }; | |||
110 | ||||
111 | /* Add an argument. OPT is the option letter, e.g. 'a'. ARG is the | |||
112 | argument to that option, or NULL if omitted (whether NULL can actually | |||
113 | happen depends on whether the option was specified as optional to | |||
114 | getopt). */ | |||
115 | static void | |||
116 | arg_add (dat, opt, arg) | |||
117 | struct admin_data *dat; | |||
118 | int opt; | |||
119 | char *arg; | |||
120 | { | |||
121 | char *newelt = xmalloc ((arg == NULL((void*)0) ? 0 : strlen (arg)) + 3); | |||
122 | strcpy (newelt, "-"); | |||
123 | newelt[1] = opt; | |||
124 | if (arg == NULL((void*)0)) | |||
125 | newelt[2] = '\0'; | |||
126 | else | |||
127 | strcpy (newelt + 2, arg); | |||
128 | ||||
129 | if (dat->av_alloc == 0) | |||
130 | { | |||
131 | dat->av_alloc = 1; | |||
132 | dat->av = (char **) xmalloc (dat->av_alloc * sizeof (*dat->av)); | |||
133 | } | |||
134 | else if (dat->ac >= dat->av_alloc) | |||
135 | { | |||
136 | dat->av_alloc *= 2; | |||
137 | dat->av = (char **) xrealloc (dat->av, | |||
138 | dat->av_alloc * sizeof (*dat->av)); | |||
139 | } | |||
140 | dat->av[dat->ac++] = newelt; | |||
141 | } | |||
142 | ||||
143 | int | |||
144 | admin (argc, argv) | |||
145 | int argc; | |||
146 | char **argv; | |||
147 | { | |||
148 | int err; | |||
149 | #ifdef CVS_ADMIN_GROUP"cvsadmin" | |||
150 | struct group *grp; | |||
151 | struct group *getgrnam(); | |||
152 | #endif | |||
153 | struct admin_data admin_data; | |||
154 | int c; | |||
155 | int i; | |||
156 | int only_k_option; | |||
157 | ||||
158 | if (argc <= 1) | |||
| ||||
159 | usage (admin_usage); | |||
160 | ||||
161 | wrap_setup (); | |||
162 | ||||
163 | memset (&admin_data, 0, sizeof admin_data); | |||
164 | ||||
165 | /* TODO: get rid of `-' switch notation in admin_data. For | |||
166 | example, admin_data->branch should be not `-bfoo' but simply `foo'. */ | |||
167 | ||||
168 | optind = 0; | |||
169 | only_k_option = 1; | |||
170 | while ((c = getopt (argc, argv, | |||
171 | "+ib::c:C:a:A:e::l::u::LUn:N:m:o:s:t::IqxV:k:")) != -1) | |||
172 | { | |||
173 | if (c != 'k') | |||
174 | only_k_option = 0; | |||
175 | ||||
176 | switch (c) | |||
177 | { | |||
178 | case 'i': | |||
179 | /* This has always been documented as useless in cvs.texinfo | |||
180 | and it really is--admin_fileproc silently does nothing | |||
181 | if vers->vn_user is NULL. */ | |||
182 | error (0, 0, "the -i option to admin is not supported"); | |||
183 | error (0, 0, "run add or import to create an RCS file"); | |||
184 | goto usage_error; | |||
185 | ||||
186 | case 'b': | |||
187 | if (admin_data.branch
| |||
188 | { | |||
189 | error (0, 0, "duplicate 'b' option"); | |||
190 | goto usage_error; | |||
191 | } | |||
192 | if (optarg == NULL((void*)0)) | |||
193 | admin_data.branch = xstrdup ("-b"); | |||
194 | else | |||
195 | { | |||
196 | admin_data.branch = xmalloc (strlen (optarg) + 5); | |||
197 | strcpy (admin_data.branch, "-b"); | |||
198 | strcat (admin_data.branch, optarg); | |||
199 | } | |||
200 | break; | |||
201 | ||||
202 | case 'c': | |||
203 | if (admin_data.comment
| |||
204 | { | |||
205 | error (0, 0, "duplicate 'c' option"); | |||
206 | goto usage_error; | |||
207 | } | |||
208 | admin_data.comment = xmalloc (strlen (optarg) + 5); | |||
| ||||
209 | strcpy (admin_data.comment, "-c"); | |||
210 | strcat (admin_data.comment, optarg); | |||
211 | break; | |||
212 | ||||
213 | case 'C': | |||
214 | /* Add commitid. */ | |||
215 | arg_add (&admin_data, 'C', optarg); | |||
216 | break; | |||
217 | ||||
218 | case 'a': | |||
219 | arg_add (&admin_data, 'a', optarg); | |||
220 | break; | |||
221 | ||||
222 | case 'A': | |||
223 | /* In the client/server case, this is cheesy because | |||
224 | we just pass along the name of the RCS file, which | |||
225 | then will want to exist on the server. This is | |||
226 | accidental; having the client specify a pathname on | |||
227 | the server is not a design feature of the protocol. */ | |||
228 | arg_add (&admin_data, 'A', optarg); | |||
229 | break; | |||
230 | ||||
231 | case 'e': | |||
232 | arg_add (&admin_data, 'e', optarg); | |||
233 | break; | |||
234 | ||||
235 | case 'l': | |||
236 | /* Note that multiple -l options are legal. */ | |||
237 | arg_add (&admin_data, 'l', optarg); | |||
238 | break; | |||
239 | ||||
240 | case 'u': | |||
241 | /* Note that multiple -u options are legal. */ | |||
242 | arg_add (&admin_data, 'u', optarg); | |||
243 | break; | |||
244 | ||||
245 | case 'L': | |||
246 | /* Probably could also complain if -L is specified multiple | |||
247 | times, although RCS doesn't and I suppose it is reasonable | |||
248 | just to have it mean the same as a single -L. */ | |||
249 | if (admin_data.set_nonstrict) | |||
250 | { | |||
251 | error (0, 0, "-U and -L are incompatible"); | |||
252 | goto usage_error; | |||
253 | } | |||
254 | admin_data.set_strict = 1; | |||
255 | break; | |||
256 | ||||
257 | case 'U': | |||
258 | /* Probably could also complain if -U is specified multiple | |||
259 | times, although RCS doesn't and I suppose it is reasonable | |||
260 | just to have it mean the same as a single -U. */ | |||
261 | if (admin_data.set_strict) | |||
262 | { | |||
263 | error (0, 0, "-U and -L are incompatible"); | |||
264 | goto usage_error; | |||
265 | } | |||
266 | admin_data.set_nonstrict = 1; | |||
267 | break; | |||
268 | ||||
269 | case 'n': | |||
270 | /* Mostly similar to cvs tag. Could also be parsing | |||
271 | the syntax of optarg, although for now we just pass | |||
272 | it to rcs as-is. Note that multiple -n options are | |||
273 | legal. */ | |||
274 | arg_add (&admin_data, 'n', optarg); | |||
275 | break; | |||
276 | ||||
277 | case 'N': | |||
278 | /* Mostly similar to cvs tag. Could also be parsing | |||
279 | the syntax of optarg, although for now we just pass | |||
280 | it to rcs as-is. Note that multiple -N options are | |||
281 | legal. */ | |||
282 | arg_add (&admin_data, 'N', optarg); | |||
283 | break; | |||
284 | ||||
285 | case 'm': | |||
286 | /* Change log message. Could also be parsing the syntax | |||
287 | of optarg, although for now we just pass it to rcs | |||
288 | as-is. Note that multiple -m options are legal. */ | |||
289 | arg_add (&admin_data, 'm', optarg); | |||
290 | break; | |||
291 | ||||
292 | case 'o': | |||
293 | /* Delete revisions. Probably should also be parsing the | |||
294 | syntax of optarg, so that the client can give errors | |||
295 | rather than making the server take care of that. | |||
296 | Other than that I'm not sure whether it matters much | |||
297 | whether we parse it here or in admin_fileproc. | |||
298 | ||||
299 | Note that multiple -o options are illegal, in RCS | |||
300 | as well as here. */ | |||
301 | ||||
302 | if (admin_data.delete_revs != NULL((void*)0)) | |||
303 | { | |||
304 | error (0, 0, "duplicate '-o' option"); | |||
305 | goto usage_error; | |||
306 | } | |||
307 | admin_data.delete_revs = xmalloc (strlen (optarg) + 5); | |||
308 | strcpy (admin_data.delete_revs, "-o"); | |||
309 | strcat (admin_data.delete_revs, optarg); | |||
310 | break; | |||
311 | ||||
312 | case 's': | |||
313 | /* Note that multiple -s options are legal. */ | |||
314 | arg_add (&admin_data, 's', optarg); | |||
315 | break; | |||
316 | ||||
317 | case 't': | |||
318 | if (admin_data.desc != NULL((void*)0)) | |||
319 | { | |||
320 | error (0, 0, "duplicate 't' option"); | |||
321 | goto usage_error; | |||
322 | } | |||
323 | if (optarg != NULL((void*)0) && optarg[0] == '-') | |||
324 | admin_data.desc = xstrdup (optarg + 1); | |||
325 | else | |||
326 | { | |||
327 | size_t bufsize = 0; | |||
328 | size_t len; | |||
329 | ||||
330 | get_file (optarg, optarg, "r", &admin_data.desc, | |||
331 | &bufsize, &len); | |||
332 | } | |||
333 | break; | |||
334 | ||||
335 | case 'I': | |||
336 | /* At least in RCS this can be specified several times, | |||
337 | with the same meaning as being specified once. */ | |||
338 | admin_data.interactive = 1; | |||
339 | break; | |||
340 | ||||
341 | case 'q': | |||
342 | /* Silently set the global really_quiet flag. This keeps admin in | |||
343 | * sync with the RCS man page and allows us to silently support | |||
344 | * older servers when necessary. | |||
345 | * | |||
346 | * Some logic says we might want to output a deprecation warning | |||
347 | * here, but I'm opting not to in order to stay quietly in sync | |||
348 | * with the RCS man page. | |||
349 | */ | |||
350 | really_quiet = 1; | |||
351 | break; | |||
352 | ||||
353 | case 'x': | |||
354 | error (0, 0, "the -x option has never done anything useful"); | |||
355 | error (0, 0, "RCS files in CVS always end in ,v"); | |||
356 | goto usage_error; | |||
357 | ||||
358 | case 'V': | |||
359 | /* No longer supported. */ | |||
360 | error (0, 0, "the `-V' option is obsolete"); | |||
361 | break; | |||
362 | ||||
363 | case 'k': | |||
364 | if (admin_data.kflag != NULL((void*)0)) | |||
365 | { | |||
366 | error (0, 0, "duplicate '-k' option"); | |||
367 | goto usage_error; | |||
368 | } | |||
369 | admin_data.kflag = RCS_check_kflag (optarg); | |||
370 | break; | |||
371 | default: | |||
372 | case '?': | |||
373 | /* getopt will have printed an error message. */ | |||
374 | ||||
375 | usage_error: | |||
376 | /* Don't use command_name; it might be "server". */ | |||
377 | error (1, 0, "specify %s -H admin for usage information", | |||
378 | program_name); | |||
379 | } | |||
380 | } | |||
381 | argc -= optind; | |||
382 | argv += optind; | |||
383 | ||||
384 | #ifdef CVS_ADMIN_GROUP"cvsadmin" | |||
385 | /* The use of `cvs admin -k' is unrestricted. However, any other | |||
386 | option is restricted if the group CVS_ADMIN_GROUP exists. */ | |||
387 | if (!only_k_option && | |||
388 | (grp = getgrnam(CVS_ADMIN_GROUP"cvsadmin")) != NULL((void*)0)) | |||
389 | { | |||
390 | #ifdef HAVE_GETGROUPS1 | |||
391 | gid_t *grps; | |||
392 | int n; | |||
393 | ||||
394 | /* get number of auxiliary groups */ | |||
395 | n = getgroups (0, NULL((void*)0)); | |||
396 | if (n < 0) | |||
397 | error (1, errno(*__errno()), "unable to get number of auxiliary groups"); | |||
398 | grps = (gid_t *) xmalloc((n + 1) * sizeof *grps); | |||
399 | n = getgroups (n, grps); | |||
400 | if (n < 0) | |||
401 | error (1, errno(*__errno()), "unable to get list of auxiliary groups"); | |||
402 | grps[n] = getgid(); | |||
403 | for (i = 0; i <= n; i++) | |||
404 | if (grps[i] == grp->gr_gid) break; | |||
405 | free (grps); | |||
406 | if (i > n) | |||
407 | error (1, 0, "usage is restricted to members of the group %s", | |||
408 | CVS_ADMIN_GROUP"cvsadmin"); | |||
409 | #else | |||
410 | char *me = getcaller(); | |||
411 | char **grnam; | |||
412 | ||||
413 | for (grnam = grp->gr_mem; *grnam; grnam++) | |||
414 | if (strcmp (*grnam, me) == 0) break; | |||
415 | if (!*grnam && getgid() != grp->gr_gid) | |||
416 | error (1, 0, "usage is restricted to members of the group %s", | |||
417 | CVS_ADMIN_GROUP"cvsadmin"); | |||
418 | #endif | |||
419 | } | |||
420 | #endif | |||
421 | ||||
422 | for (i = 0; i < admin_data.ac; ++i) | |||
423 | { | |||
424 | assert (admin_data.av[i][0] == '-')((admin_data.av[i][0] == '-') ? (void)0 : __assert2("/usr/src/gnu/usr.bin/cvs/src/admin.c" , 424, __func__, "admin_data.av[i][0] == '-'")); | |||
425 | switch (admin_data.av[i][1]) | |||
426 | { | |||
427 | case 'm': | |||
428 | case 'l': | |||
429 | case 'u': | |||
430 | check_numeric (&admin_data.av[i][2], argc, argv); | |||
431 | break; | |||
432 | default: | |||
433 | break; | |||
434 | } | |||
435 | } | |||
436 | if (admin_data.branch != NULL((void*)0)) | |||
437 | check_numeric (admin_data.branch + 2, argc, argv); | |||
438 | if (admin_data.delete_revs != NULL((void*)0)) | |||
439 | { | |||
440 | char *p; | |||
441 | ||||
442 | check_numeric (admin_data.delete_revs + 2, argc, argv); | |||
443 | p = strchr (admin_data.delete_revs + 2, ':'); | |||
444 | if (p != NULL((void*)0) && isdigit ((unsigned char) p[1])) | |||
445 | check_numeric (p + 1, argc, argv); | |||
446 | else if (p != NULL((void*)0) && p[1] == ':' && isdigit ((unsigned char) p[2])) | |||
447 | check_numeric (p + 2, argc, argv); | |||
448 | } | |||
449 | ||||
450 | #ifdef CLIENT_SUPPORT1 | |||
451 | if (current_parsed_root->isremote) | |||
452 | { | |||
453 | /* We're the client side. Fire up the remote server. */ | |||
454 | start_server (); | |||
455 | ||||
456 | ign_setup (); | |||
457 | ||||
458 | /* Note that option_with_arg does not work for us, because some | |||
459 | of the options must be sent without a space between the option | |||
460 | and its argument. */ | |||
461 | if (admin_data.interactive) | |||
462 | error (1, 0, "-I option not useful with client/server"); | |||
463 | if (admin_data.branch != NULL((void*)0)) | |||
464 | send_arg (admin_data.branch); | |||
465 | if (admin_data.comment != NULL((void*)0)) | |||
466 | send_arg (admin_data.comment); | |||
467 | if (admin_data.set_strict) | |||
468 | send_arg ("-L"); | |||
469 | if (admin_data.set_nonstrict) | |||
470 | send_arg ("-U"); | |||
471 | if (admin_data.delete_revs != NULL((void*)0)) | |||
472 | send_arg (admin_data.delete_revs); | |||
473 | if (admin_data.desc != NULL((void*)0)) | |||
474 | { | |||
475 | char *p = admin_data.desc; | |||
476 | send_to_server ("Argument -t-", 0); | |||
477 | while (*p) | |||
478 | { | |||
479 | if (*p == '\n') | |||
480 | { | |||
481 | send_to_server ("\012Argumentx ", 0); | |||
482 | ++p; | |||
483 | } | |||
484 | else | |||
485 | { | |||
486 | char *q = strchr (p, '\n'); | |||
487 | if (q == NULL((void*)0)) q = p + strlen (p); | |||
488 | send_to_server (p, q - p); | |||
489 | p = q; | |||
490 | } | |||
491 | } | |||
492 | send_to_server ("\012", 1); | |||
493 | } | |||
494 | /* Send this for all really_quiets since we know that it will be silently | |||
495 | * ignored when unneeded. This supports old servers. | |||
496 | */ | |||
497 | if (really_quiet) | |||
498 | send_arg ("-q"); | |||
499 | if (admin_data.kflag != NULL((void*)0)) | |||
500 | send_arg (admin_data.kflag); | |||
501 | ||||
502 | for (i = 0; i < admin_data.ac; ++i) | |||
503 | send_arg (admin_data.av[i]); | |||
504 | ||||
505 | send_files (argc, argv, 0, 0, SEND_NO_CONTENTS4); | |||
506 | send_file_names (argc, argv, SEND_EXPAND_WILD1); | |||
507 | send_to_server ("admin\012", 0); | |||
508 | err = get_responses_and_close (); | |||
509 | goto return_it; | |||
510 | } | |||
511 | #endif /* CLIENT_SUPPORT */ | |||
512 | ||||
513 | lock_tree_for_write (argc, argv, 0, W_LOCAL0x01, 0); | |||
514 | ||||
515 | err = start_recursion (admin_fileproc, (FILESDONEPROC) NULL((void*)0), admin_dirproc, | |||
516 | (DIRLEAVEPROC) NULL((void*)0), (void *)&admin_data, | |||
517 | argc, argv, 0, | |||
518 | W_LOCAL0x01, 0, 0, (char *) NULL((void*)0), 1); | |||
519 | Lock_Cleanup (); | |||
520 | ||||
521 | return_it: | |||
522 | if (admin_data.branch != NULL((void*)0)) | |||
523 | free (admin_data.branch); | |||
524 | if (admin_data.comment != NULL((void*)0)) | |||
525 | free (admin_data.comment); | |||
526 | if (admin_data.delete_revs != NULL((void*)0)) | |||
527 | free (admin_data.delete_revs); | |||
528 | if (admin_data.kflag != NULL((void*)0)) | |||
529 | free (admin_data.kflag); | |||
530 | if (admin_data.desc != NULL((void*)0)) | |||
531 | free (admin_data.desc); | |||
532 | for (i = 0; i < admin_data.ac; ++i) | |||
533 | free (admin_data.av[i]); | |||
534 | if (admin_data.av != NULL((void*)0)) | |||
535 | free (admin_data.av); | |||
536 | ||||
537 | return (err); | |||
538 | } | |||
539 | ||||
540 | /* | |||
541 | * Called to run "rcs" on a particular file. | |||
542 | */ | |||
543 | /* ARGSUSED */ | |||
544 | static int | |||
545 | admin_fileproc (callerdat, finfo) | |||
546 | void *callerdat; | |||
547 | struct file_info *finfo; | |||
548 | { | |||
549 | struct admin_data *admin_data = (struct admin_data *) callerdat; | |||
550 | Vers_TS *vers; | |||
551 | char *version; | |||
552 | int i; | |||
553 | int status = 0; | |||
554 | RCSNode *rcs, *rcs2; | |||
555 | ||||
556 | vers = Version_TS (finfo, NULL((void*)0), NULL((void*)0), NULL((void*)0), 0, 0); | |||
557 | ||||
558 | version = vers->vn_user; | |||
559 | if (version == NULL((void*)0)) | |||
560 | goto exitfunc; | |||
561 | else if (strcmp (version, "0") == 0) | |||
562 | { | |||
563 | error (0, 0, "cannot admin newly added file `%s'", finfo->file); | |||
564 | goto exitfunc; | |||
565 | } | |||
566 | ||||
567 | rcs = vers->srcfile; | |||
568 | if (rcs == NULL((void*)0)) | |||
569 | { | |||
570 | error (0, 0, "lost revision control file for `%s'", finfo->file); | |||
571 | goto exitfunc; | |||
572 | } | |||
573 | if (rcs->flags & PARTIAL0x4) | |||
574 | RCS_reparsercsfile (rcs, (FILE **) NULL((void*)0), (struct rcsbuffer *) NULL((void*)0)); | |||
575 | ||||
576 | status = 0; | |||
577 | ||||
578 | if (!really_quiet) | |||
579 | { | |||
580 | cvs_output ("RCS file: ", 0); | |||
581 | cvs_output (rcs->path, 0); | |||
582 | cvs_output ("\n", 1); | |||
583 | } | |||
584 | ||||
585 | if (admin_data->branch != NULL((void*)0)) | |||
586 | { | |||
587 | char *branch = &admin_data->branch[2]; | |||
588 | if (*branch != '\0' && ! isdigit ((unsigned char) *branch)) | |||
589 | { | |||
590 | branch = RCS_whatbranch (rcs, admin_data->branch + 2); | |||
591 | if (branch == NULL((void*)0)) | |||
592 | { | |||
593 | error (0, 0, "%s: Symbolic name %s is undefined.", | |||
594 | rcs->path, admin_data->branch + 2); | |||
595 | status = 1; | |||
596 | } | |||
597 | } | |||
598 | if (status == 0) | |||
599 | RCS_setbranch (rcs, branch); | |||
600 | if (branch != NULL((void*)0) && branch != &admin_data->branch[2]) | |||
601 | free (branch); | |||
602 | } | |||
603 | if (admin_data->comment != NULL((void*)0)) | |||
604 | { | |||
605 | if (rcs->comment != NULL((void*)0)) | |||
606 | free (rcs->comment); | |||
607 | rcs->comment = xstrdup (admin_data->comment + 2); | |||
608 | } | |||
609 | if (admin_data->set_strict) | |||
610 | rcs->strict_locks = 1; | |||
611 | if (admin_data->set_nonstrict) | |||
612 | rcs->strict_locks = 0; | |||
613 | if (admin_data->delete_revs != NULL((void*)0)) | |||
614 | { | |||
615 | char *s, *t, *rev1, *rev2; | |||
616 | /* Set for :, clear for ::. */ | |||
617 | int inclusive; | |||
618 | char *t2; | |||
619 | ||||
620 | s = admin_data->delete_revs + 2; | |||
621 | inclusive = 1; | |||
622 | t = strchr (s, ':'); | |||
623 | if (t != NULL((void*)0)) | |||
624 | { | |||
625 | if (t[1] == ':') | |||
626 | { | |||
627 | inclusive = 0; | |||
628 | t2 = t + 2; | |||
629 | } | |||
630 | else | |||
631 | t2 = t + 1; | |||
632 | } | |||
633 | ||||
634 | /* Note that we don't support '-' for ranges. RCS considers it | |||
635 | obsolete and it is problematic with tags containing '-'. "cvs log" | |||
636 | has made the same decision. */ | |||
637 | ||||
638 | if (t == NULL((void*)0)) | |||
639 | { | |||
640 | /* -orev */ | |||
641 | rev1 = xstrdup (s); | |||
642 | rev2 = xstrdup (s); | |||
643 | } | |||
644 | else if (t == s) | |||
645 | { | |||
646 | /* -o:rev2 */ | |||
647 | rev1 = NULL((void*)0); | |||
648 | rev2 = xstrdup (t2); | |||
649 | } | |||
650 | else | |||
651 | { | |||
652 | *t = '\0'; | |||
653 | rev1 = xstrdup (s); | |||
654 | *t = ':'; /* probably unnecessary */ | |||
655 | if (*t2 == '\0') | |||
656 | /* -orev1: */ | |||
657 | rev2 = NULL((void*)0); | |||
658 | else | |||
659 | /* -orev1:rev2 */ | |||
660 | rev2 = xstrdup (t2); | |||
661 | } | |||
662 | ||||
663 | if (rev1 == NULL((void*)0) && rev2 == NULL((void*)0)) | |||
664 | { | |||
665 | /* RCS segfaults if `-o:' is given */ | |||
666 | error (0, 0, "no valid revisions specified in `%s' option", | |||
667 | admin_data->delete_revs); | |||
668 | status = 1; | |||
669 | } | |||
670 | else | |||
671 | { | |||
672 | status |= RCS_delete_revs (rcs, rev1, rev2, inclusive); | |||
673 | if (rev1) | |||
674 | free (rev1); | |||
675 | if (rev2) | |||
676 | free (rev2); | |||
677 | } | |||
678 | } | |||
679 | if (admin_data->desc != NULL((void*)0)) | |||
680 | { | |||
681 | free (rcs->desc); | |||
682 | rcs->desc = xstrdup (admin_data->desc); | |||
683 | } | |||
684 | if (admin_data->kflag != NULL((void*)0)) | |||
685 | { | |||
686 | char *kflag = admin_data->kflag + 2; | |||
687 | char *oldexpand = RCS_getexpand (rcs); | |||
688 | if (oldexpand == NULL((void*)0) || strcmp (oldexpand, kflag) != 0) | |||
689 | RCS_setexpand (rcs, kflag); | |||
690 | } | |||
691 | ||||
692 | /* Handle miscellaneous options. TODO: decide whether any or all | |||
693 | of these should have their own fields in the admin_data | |||
694 | structure. */ | |||
695 | for (i = 0; i < admin_data->ac; ++i) | |||
696 | { | |||
697 | char *arg; | |||
698 | char *p, *rev, *revnum, *tag, *msg, *commitid; | |||
699 | char **users; | |||
700 | int argc, u; | |||
701 | Node *n; | |||
702 | RCSVers *delta; | |||
703 | ||||
704 | arg = admin_data->av[i]; | |||
705 | switch (arg[1]) | |||
706 | { | |||
707 | case 'a': /* fall through */ | |||
708 | case 'e': | |||
709 | line2argv (&argc, &users, arg + 2, " ,\t\n"); | |||
710 | if (arg[1] == 'a') | |||
711 | for (u = 0; u < argc; ++u) | |||
712 | RCS_addaccess (rcs, users[u]); | |||
713 | else if (argc == 0) | |||
714 | RCS_delaccess (rcs, NULL((void*)0)); | |||
715 | else | |||
716 | for (u = 0; u < argc; ++u) | |||
717 | RCS_delaccess (rcs, users[u]); | |||
718 | free_names (&argc, users); | |||
719 | break; | |||
720 | case 'A': | |||
721 | ||||
722 | /* See admin-19a-admin and friends in sanity.sh for | |||
723 | relative pathnames. It makes sense to think in | |||
724 | terms of a syntax which give pathnames relative to | |||
725 | the repository or repository corresponding to the | |||
726 | current directory or some such (and perhaps don't | |||
727 | include ,v), but trying to worry about such things | |||
728 | is a little pointless unless you first worry about | |||
729 | whether "cvs admin -A" as a whole makes any sense | |||
730 | (currently probably not, as access lists don't | |||
731 | affect the behavior of CVS). */ | |||
732 | ||||
733 | rcs2 = RCS_parsercsfile (arg + 2); | |||
734 | if (rcs2 == NULL((void*)0)) | |||
735 | error (1, 0, "cannot continue"); | |||
736 | ||||
737 | p = xstrdup (RCS_getaccess (rcs2)); | |||
738 | line2argv (&argc, &users, p, " \t\n"); | |||
739 | free (p); | |||
740 | freercsnode (&rcs2); | |||
741 | ||||
742 | for (u = 0; u < argc; ++u) | |||
743 | RCS_addaccess (rcs, users[u]); | |||
744 | free_names (&argc, users); | |||
745 | break; | |||
746 | case 'C': | |||
747 | p = strchr (arg, ':'); | |||
748 | if (p == NULL((void*)0)) | |||
749 | { | |||
750 | error (0, 0, "%s: -C option lacks commitid", rcs->path); | |||
751 | status = 1; | |||
752 | continue; | |||
753 | } | |||
754 | *p = '\0'; | |||
755 | rev = RCS_gettag (rcs, arg + 2, 1, NULL((void*)0)); | |||
756 | if (rev == NULL((void*)0)) | |||
757 | { | |||
758 | error (0, 0, "%s: no such revision %s", rcs->path, arg + 2); | |||
759 | status = 1; | |||
760 | continue; | |||
761 | } | |||
762 | *p++ = ':'; | |||
763 | commitid = p; | |||
764 | ||||
765 | if (*commitid == '\0') | |||
766 | { | |||
767 | error (0, 0, "%s: -C option lacks commitid", rcs->path); | |||
768 | free (rev); | |||
769 | status = 1; | |||
770 | continue; | |||
771 | } | |||
772 | ||||
773 | n = findnode (rcs->versions, rev); | |||
774 | delta = (RCSVers *) n->data; | |||
775 | ||||
776 | if (delta->other_delta == NULL((void*)0)) | |||
777 | delta->other_delta = getlist(); | |||
778 | ||||
779 | if ((n = findnode (delta->other_delta, "commitid"))) | |||
780 | { | |||
781 | error (0, 0, "%s: revision %s already has commitid %s", | |||
782 | rcs->path, rev, n->data); | |||
783 | free (rev); | |||
784 | status = 1; | |||
785 | continue; | |||
786 | } | |||
787 | ||||
788 | n = getnode(); | |||
789 | n->type = RCSFIELD; | |||
790 | n->key = xstrdup ("commitid"); | |||
791 | n->data = xstrdup(commitid); | |||
792 | addnode (delta->other_delta, n); | |||
793 | ||||
794 | free (rev); | |||
795 | ||||
796 | break; | |||
797 | case 'n': /* fall through */ | |||
798 | case 'N': | |||
799 | if (arg[2] == '\0') | |||
800 | { | |||
801 | cvs_outerr ("missing symbolic name after ", 0); | |||
802 | cvs_outerr (arg, 0); | |||
803 | cvs_outerr ("\n", 1); | |||
804 | break; | |||
805 | } | |||
806 | p = strchr (arg, ':'); | |||
807 | if (p == NULL((void*)0)) | |||
808 | { | |||
809 | if (RCS_deltag (rcs, arg + 2) != 0) | |||
810 | { | |||
811 | error (0, 0, "%s: Symbolic name %s is undefined.", | |||
812 | rcs->path, | |||
813 | arg + 2); | |||
814 | status = 1; | |||
815 | continue; | |||
816 | } | |||
817 | break; | |||
818 | } | |||
819 | *p = '\0'; | |||
820 | tag = xstrdup (arg + 2); | |||
821 | *p++ = ':'; | |||
822 | ||||
823 | /* Option `n' signals an error if this tag is already bound. */ | |||
824 | if (arg[1] == 'n') | |||
825 | { | |||
826 | n = findnode (RCS_symbols (rcs), tag); | |||
827 | if (n != NULL((void*)0)) | |||
828 | { | |||
829 | error (0, 0, | |||
830 | "%s: symbolic name %s already bound to %s", | |||
831 | rcs->path, | |||
832 | tag, n->data); | |||
833 | status = 1; | |||
834 | free (tag); | |||
835 | continue; | |||
836 | } | |||
837 | } | |||
838 | ||||
839 | /* Attempt to perform the requested tagging. */ | |||
840 | ||||
841 | if ((*p == 0 && (rev = RCS_head (rcs))) | |||
842 | || (rev = RCS_tag2rev (rcs, p))) /* tag2rev may exit */ | |||
843 | { | |||
844 | RCS_check_tag (tag); /* exit if not a valid tag */ | |||
845 | RCS_settag (rcs, tag, rev); | |||
846 | free (rev); | |||
847 | } | |||
848 | else | |||
849 | { | |||
850 | if (!really_quiet) | |||
851 | error (0, 0, | |||
852 | "%s: Symbolic name or revision %s is undefined.", | |||
853 | rcs->path, p); | |||
854 | status = 1; | |||
855 | } | |||
856 | free (tag); | |||
857 | break; | |||
858 | case 's': | |||
859 | p = strchr (arg, ':'); | |||
860 | if (p == NULL((void*)0)) | |||
861 | { | |||
862 | tag = xstrdup (arg + 2); | |||
863 | rev = RCS_head (rcs); | |||
864 | } | |||
865 | else | |||
866 | { | |||
867 | *p = '\0'; | |||
868 | tag = xstrdup (arg + 2); | |||
869 | *p++ = ':'; | |||
870 | rev = xstrdup (p); | |||
871 | } | |||
872 | revnum = RCS_gettag (rcs, rev, 0, NULL((void*)0)); | |||
873 | if (revnum != NULL((void*)0)) | |||
874 | { | |||
875 | n = findnode (rcs->versions, revnum); | |||
876 | free (revnum); | |||
877 | } | |||
878 | else | |||
879 | n = NULL((void*)0); | |||
880 | if (n == NULL((void*)0)) | |||
881 | { | |||
882 | error (0, 0, | |||
883 | "%s: can't set state of nonexisting revision %s", | |||
884 | rcs->path, | |||
885 | rev); | |||
886 | free (rev); | |||
887 | status = 1; | |||
888 | continue; | |||
889 | } | |||
890 | free (rev); | |||
891 | delta = (RCSVers *) n->data; | |||
892 | free (delta->state); | |||
893 | delta->state = tag; | |||
894 | break; | |||
895 | ||||
896 | case 'm': | |||
897 | p = strchr (arg, ':'); | |||
898 | if (p == NULL((void*)0)) | |||
899 | { | |||
900 | error (0, 0, "%s: -m option lacks revision number", | |||
901 | rcs->path); | |||
902 | status = 1; | |||
903 | continue; | |||
904 | } | |||
905 | *p = '\0'; | |||
906 | rev = RCS_gettag (rcs, arg + 2, 0, NULL((void*)0)); | |||
907 | if (rev == NULL((void*)0)) | |||
908 | { | |||
909 | error (0, 0, "%s: no such revision %s", rcs->path, rev); | |||
910 | status = 1; | |||
911 | continue; | |||
912 | } | |||
913 | *p++ = ':'; | |||
914 | msg = p; | |||
915 | ||||
916 | n = findnode (rcs->versions, rev); | |||
917 | free (rev); | |||
918 | delta = (RCSVers *) n->data; | |||
919 | if (delta->text == NULL((void*)0)) | |||
920 | { | |||
921 | delta->text = (Deltatext *) xmalloc (sizeof (Deltatext)); | |||
922 | memset ((void *) delta->text, 0, sizeof (Deltatext)); | |||
923 | } | |||
924 | delta->text->version = xstrdup (delta->version); | |||
925 | delta->text->log = make_message_rcslegal (msg); | |||
926 | break; | |||
927 | ||||
928 | case 'l': | |||
929 | status |= RCS_lock (rcs, arg[2] ? arg + 2 : NULL((void*)0), 0); | |||
930 | break; | |||
931 | case 'u': | |||
932 | status |= RCS_unlock (rcs, arg[2] ? arg + 2 : NULL((void*)0), 0); | |||
933 | break; | |||
934 | default: assert(0)((0) ? (void)0 : __assert2("/usr/src/gnu/usr.bin/cvs/src/admin.c" , 934, __func__, "0")); /* can't happen */ | |||
935 | } | |||
936 | } | |||
937 | ||||
938 | if (status == 0) | |||
939 | { | |||
940 | RCS_rewrite (rcs, NULL((void*)0), NULL((void*)0)); | |||
941 | if (!really_quiet) | |||
942 | cvs_output ("done\n", 5); | |||
943 | } | |||
944 | else | |||
945 | { | |||
946 | /* Note that this message should only occur after another | |||
947 | message has given a more specific error. The point of this | |||
948 | additional message is to make it clear that the previous problems | |||
949 | caused CVS to forget about the idea of modifying the RCS file. */ | |||
950 | if (!really_quiet) | |||
951 | error (0, 0, "RCS file for `%s' not modified.", finfo->file); | |||
952 | RCS_abandon (rcs); | |||
953 | } | |||
954 | ||||
955 | exitfunc: | |||
956 | freevers_ts (&vers); | |||
957 | return status; | |||
958 | } | |||
959 | ||||
960 | /* | |||
961 | * Print a warm fuzzy message | |||
962 | */ | |||
963 | /* ARGSUSED */ | |||
964 | static Dtype | |||
965 | admin_dirproc (callerdat, dir, repos, update_dir, entries) | |||
966 | void *callerdat; | |||
967 | char *dir; | |||
968 | char *repos; | |||
969 | char *update_dir; | |||
970 | List *entries; | |||
971 | { | |||
972 | if (!quiet) | |||
973 | error (0, 0, "Administrating %s", update_dir); | |||
974 | return (R_PROCESS); | |||
975 | } |