| File: | src/gnu/usr.bin/cvs/src/admin.c |
| Warning: | line 307, column 37 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 != NULL((void*)0)) | |||
| 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
| |||
| 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 | } |