1 /* 2 * Copyright (C) 1986-2005 The Free Software Foundation, Inc. 3 * 4 * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>, 5 * and others. 6 * 7 * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk 8 * Portions Copyright (C) 1989-1992, Brian Berliner 9 * 10 * You may distribute under the terms of the GNU General Public License 11 * as specified in the README file that comes with the CVS source 12 * distribution. 13 * 14 * Modules 15 * 16 * Functions for accessing the modules file. 17 * 18 * The modules file supports basically three formats of lines: 19 * key [options] directory files... [ -x directory [files] ] ... 20 * key [options] directory [ -x directory [files] ] ... 21 * key -a aliases... 22 * 23 * The -a option allows an aliasing step in the parsing of the modules 24 * file. The "aliases" listed on a line following the -a are 25 * processed one-by-one, as if they were specified as arguments on the 26 * command line. 27 */ 28 29 #include "cvs.h" 30 #include "save-cwd.h" 31 32 33 /* Defines related to the syntax of the modules file. */ 34 35 /* Options in modules file. Note that it is OK to use GNU getopt features; 36 we already are arranging to make sure we are using the getopt distributed 37 with CVS. */ 38 #define CVSMODULE_OPTS "+ad:lo:e:s:t:" 39 40 /* Special delimiter. */ 41 #define CVSMODULE_SPEC '&' 42 43 struct sortrec 44 { 45 /* Name of the module, malloc'd. */ 46 char *modname; 47 /* If Status variable is set, this is either def_status or the malloc'd 48 name of the status. If Status is not set, the field is left 49 uninitialized. */ 50 char *status; 51 /* Pointer to a malloc'd array which contains (1) the raw contents 52 of the options and arguments, excluding comments, (2) a '\0', 53 and (3) the storage for the "comment" field. */ 54 char *rest; 55 char *comment; 56 }; 57 58 static int sort_order (const void *l, const void *r); 59 static void save_d (char *k, int ks, char *d, int ds); 60 61 62 /* 63 * Open the modules file, and die if the CVSROOT environment variable 64 * was not set. If the modules file does not exist, that's fine, and 65 * a warning message is displayed and a NULL is returned. 66 */ 67 DBM * 68 open_module (void) 69 { 70 char *mfile; 71 DBM *retval; 72 73 if (current_parsed_root == NULL) 74 { 75 error (0, 0, "must set the CVSROOT environment variable"); 76 error (1, 0, "or specify the '-d' global option"); 77 } 78 mfile = Xasprintf ("%s/%s/%s", current_parsed_root->directory, 79 CVSROOTADM, CVSROOTADM_MODULES); 80 retval = dbm_open (mfile, O_RDONLY, 0666); 81 free (mfile); 82 return retval; 83 } 84 85 /* 86 * Close the modules file, if the open succeeded, that is 87 */ 88 void 89 close_module (DBM *db) 90 { 91 if (db != NULL) 92 dbm_close (db); 93 } 94 95 96 97 /* 98 * This is the recursive function that processes a module name. 99 * It calls back the passed routine for each directory of a module 100 * It runs the post checkout or post tag proc from the modules file 101 */ 102 int 103 my_module (DBM *db, char *mname, enum mtype m_type, char *msg, 104 CALLBACKPROC callback_proc, char *where, int shorten, 105 int local_specified, int run_module_prog, int build_dirs, 106 char *extra_arg, List *stack) 107 { 108 char *checkout_prog = NULL; 109 char *export_prog = NULL; 110 char *tag_prog = NULL; 111 struct saved_cwd cwd; 112 int cwd_saved = 0; 113 char *line; 114 int modargc; 115 int xmodargc; 116 char **modargv = NULL; 117 char **xmodargv = NULL; 118 /* Found entry from modules file, including options and such. */ 119 char *value = NULL; 120 char *mwhere = NULL; 121 char *mfile = NULL; 122 char *spec_opt = NULL; 123 char *xvalue = NULL; 124 int alias = 0; 125 datum key, val; 126 char *cp; 127 int c, err = 0; 128 int nonalias_opt = 0; 129 130 #ifdef SERVER_SUPPORT 131 int restore_server_dir = 0; 132 char *server_dir_to_restore = NULL; 133 #endif 134 135 TRACE (TRACE_FUNCTION, "my_module (%s, %s, %s, %s)", 136 mname ? mname : "(null)", msg ? msg : "(null)", 137 where ? where : "NULL", extra_arg ? extra_arg : "NULL"); 138 139 /* Don't process absolute directories. Anything else could be a security 140 * problem. Before this check was put in place: 141 * 142 * $ cvs -d:fork:/cvsroot co /foo 143 * cvs server: warning: cannot make directory CVS in /: Permission denied 144 * cvs [server aborted]: cannot make directory /foo: Permission denied 145 * $ 146 */ 147 if (ISABSOLUTE (mname)) 148 error (1, 0, "Absolute module reference invalid: `%s'", mname); 149 150 /* Similarly for directories that attempt to step above the root of the 151 * repository. 152 */ 153 if (pathname_levels (mname) > 0) 154 error (1, 0, "up-level in module reference (`..') invalid: `%s'.", 155 mname); 156 157 /* if this is a directory to ignore, add it to that list */ 158 if (mname[0] == '!' && mname[1] != '\0') 159 { 160 ign_dir_add (mname+1); 161 goto do_module_return; 162 } 163 164 /* strip extra stuff from the module name */ 165 strip_trailing_slashes (mname); 166 167 /* 168 * Look up the module using the following scheme: 169 * 1) look for mname as a module name 170 * 2) look for mname as a directory 171 * 3) look for mname as a file 172 * 4) take mname up to the first slash and look it up as a module name 173 * (this is for checking out only part of a module) 174 */ 175 176 /* look it up as a module name */ 177 key.dptr = mname; 178 key.dsize = strlen (key.dptr); 179 if (db != NULL) 180 val = dbm_fetch (db, key); 181 else 182 val.dptr = NULL; 183 if (val.dptr != NULL) 184 { 185 /* copy and null terminate the value */ 186 value = xmalloc (val.dsize + 1); 187 memcpy (value, val.dptr, val.dsize); 188 value[val.dsize] = '\0'; 189 190 /* If the line ends in a comment, strip it off */ 191 if ((cp = strchr (value, '#')) != NULL) 192 *cp = '\0'; 193 else 194 cp = value + val.dsize; 195 196 /* Always strip trailing spaces */ 197 while (cp > value && isspace ((unsigned char) *--cp)) 198 *cp = '\0'; 199 200 mwhere = xstrdup (mname); 201 goto found; 202 } 203 else 204 { 205 char *file; 206 char *attic_file; 207 char *acp; 208 int is_found = 0; 209 210 /* check to see if mname is a directory or file */ 211 file = xmalloc (strlen (current_parsed_root->directory) 212 + strlen (mname) + sizeof(RCSEXT) + 2); 213 (void) sprintf (file, "%s/%s", current_parsed_root->directory, mname); 214 attic_file = xmalloc (strlen (current_parsed_root->directory) 215 + strlen (mname) 216 + sizeof (CVSATTIC) + sizeof (RCSEXT) + 3); 217 if ((acp = strrchr (mname, '/')) != NULL) 218 { 219 *acp = '\0'; 220 (void) sprintf (attic_file, "%s/%s/%s/%s%s", current_parsed_root->directory, 221 mname, CVSATTIC, acp + 1, RCSEXT); 222 *acp = '/'; 223 } 224 else 225 (void) sprintf (attic_file, "%s/%s/%s%s", 226 current_parsed_root->directory, 227 CVSATTIC, mname, RCSEXT); 228 229 if (isdir (file)) 230 { 231 modargv = xmalloc (sizeof (*modargv)); 232 modargv[0] = xstrdup (mname); 233 modargc = 1; 234 is_found = 1; 235 } 236 else 237 { 238 (void) strcat (file, RCSEXT); 239 if (isfile (file) || isfile (attic_file)) 240 { 241 /* if mname was a file, we have to split it into "dir file" */ 242 if ((cp = strrchr (mname, '/')) != NULL && cp != mname) 243 { 244 modargv = xnmalloc (2, sizeof (*modargv)); 245 modargv[0] = xmalloc (strlen (mname) + 2); 246 strncpy (modargv[0], mname, cp - mname); 247 modargv[0][cp - mname] = '\0'; 248 modargv[1] = xstrdup (cp + 1); 249 modargc = 2; 250 } 251 else 252 { 253 /* 254 * the only '/' at the beginning or no '/' at all 255 * means the file we are interested in is in CVSROOT 256 * itself so the directory should be '.' 257 */ 258 if (cp == mname) 259 { 260 /* drop the leading / if specified */ 261 modargv = xnmalloc (2, sizeof (*modargv)); 262 modargv[0] = xstrdup ("."); 263 modargv[1] = xstrdup (mname + 1); 264 modargc = 2; 265 } 266 else 267 { 268 /* otherwise just copy it */ 269 modargv = xnmalloc (2, sizeof (*modargv)); 270 modargv[0] = xstrdup ("."); 271 modargv[1] = xstrdup (mname); 272 modargc = 2; 273 } 274 } 275 is_found = 1; 276 } 277 } 278 free (attic_file); 279 free (file); 280 281 if (is_found) 282 { 283 assert (value == NULL); 284 285 /* OK, we have now set up modargv with the actual 286 file/directory we want to work on. We duplicate a 287 small amount of code here because the vast majority of 288 the code after the "found" label does not pertain to 289 the case where we found a file/directory rather than 290 finding an entry in the modules file. */ 291 if (save_cwd (&cwd)) 292 error (1, errno, "Failed to save current directory."); 293 cwd_saved = 1; 294 295 err += callback_proc (modargc, modargv, where, mwhere, mfile, 296 shorten, 297 local_specified, mname, msg); 298 299 free_names (&modargc, modargv); 300 301 /* cd back to where we started. */ 302 if (restore_cwd (&cwd)) 303 error (1, errno, "Failed to restore current directory, `%s'.", 304 cwd.name); 305 free_cwd (&cwd); 306 cwd_saved = 0; 307 308 goto do_module_return; 309 } 310 } 311 312 /* look up everything to the first / as a module */ 313 if (mname[0] != '/' && (cp = strchr (mname, '/')) != NULL) 314 { 315 /* Make the slash the new end of the string temporarily */ 316 *cp = '\0'; 317 key.dptr = mname; 318 key.dsize = strlen (key.dptr); 319 320 /* do the lookup */ 321 if (db != NULL) 322 val = dbm_fetch (db, key); 323 else 324 val.dptr = NULL; 325 326 /* if we found it, clean up the value and life is good */ 327 if (val.dptr != NULL) 328 { 329 char *cp2; 330 331 /* copy and null terminate the value */ 332 value = xmalloc (val.dsize + 1); 333 memcpy (value, val.dptr, val.dsize); 334 value[val.dsize] = '\0'; 335 336 /* If the line ends in a comment, strip it off */ 337 if ((cp2 = strchr (value, '#')) != NULL) 338 *cp2 = '\0'; 339 else 340 cp2 = value + val.dsize; 341 342 /* Always strip trailing spaces */ 343 while (cp2 > value && isspace ((unsigned char) *--cp2)) 344 *cp2 = '\0'; 345 346 /* mwhere gets just the module name */ 347 mwhere = xstrdup (mname); 348 mfile = cp + 1; 349 assert (strlen (mfile)); 350 351 /* put the / back in mname */ 352 *cp = '/'; 353 354 goto found; 355 } 356 357 /* put the / back in mname */ 358 *cp = '/'; 359 } 360 361 /* if we got here, we couldn't find it using our search, so give up */ 362 error (0, 0, "cannot find module `%s' - ignored", mname); 363 err++; 364 goto do_module_return; 365 366 367 /* 368 * At this point, we found what we were looking for in one 369 * of the many different forms. 370 */ 371 found: 372 373 /* remember where we start */ 374 if (save_cwd (&cwd)) 375 error (1, errno, "Failed to save current directory."); 376 cwd_saved = 1; 377 378 assert (value != NULL); 379 380 /* search the value for the special delimiter and save for later */ 381 if ((cp = strchr (value, CVSMODULE_SPEC)) != NULL) 382 { 383 *cp = '\0'; /* null out the special char */ 384 spec_opt = cp + 1; /* save the options for later */ 385 386 /* strip whitespace if necessary */ 387 while (cp > value && isspace ((unsigned char) *--cp)) 388 *cp = '\0'; 389 } 390 391 /* don't do special options only part of a module was specified */ 392 if (mfile != NULL) 393 spec_opt = NULL; 394 395 /* 396 * value now contains one of the following: 397 * 1) dir 398 * 2) dir file 399 * 3) the value from modules without any special args 400 * [ args ] dir [file] [file] ... 401 * or -a module [ module ] ... 402 */ 403 404 /* Put the value on a line with XXX prepended for getopt to eat */ 405 line = Xasprintf ("XXX %s", value); 406 407 /* turn the line into an argv[] array */ 408 line2argv (&xmodargc, &xmodargv, line, " \t"); 409 free (line); 410 modargc = xmodargc; 411 modargv = xmodargv; 412 413 /* parse the args */ 414 getoptreset (); 415 while ((c = getopt (modargc, modargv, CVSMODULE_OPTS)) != -1) 416 { 417 switch (c) 418 { 419 case 'a': 420 alias = 1; 421 break; 422 case 'd': 423 if (mwhere) 424 free (mwhere); 425 mwhere = xstrdup (optarg); 426 nonalias_opt = 1; 427 break; 428 case 'l': 429 local_specified = 1; 430 nonalias_opt = 1; 431 break; 432 case 'o': 433 if (checkout_prog) 434 free (checkout_prog); 435 checkout_prog = xstrdup (optarg); 436 nonalias_opt = 1; 437 break; 438 case 'e': 439 if (export_prog) 440 free (export_prog); 441 export_prog = xstrdup (optarg); 442 nonalias_opt = 1; 443 break; 444 case 't': 445 if (tag_prog) 446 free (tag_prog); 447 tag_prog = xstrdup (optarg); 448 nonalias_opt = 1; 449 break; 450 case '?': 451 error (0, 0, 452 "modules file has invalid option for key %s value %s", 453 key.dptr, value); 454 err++; 455 goto do_module_return; 456 } 457 } 458 modargc -= optind; 459 modargv += optind; 460 if (modargc == 0 && spec_opt == NULL) 461 { 462 error (0, 0, "modules file missing directory for module %s", mname); 463 ++err; 464 goto do_module_return; 465 } 466 467 if (alias && nonalias_opt) 468 { 469 /* The documentation has never said it is valid to specify 470 -a along with another option. And I believe that in the past 471 CVS has ignored the options other than -a, more or less, in this 472 situation. */ 473 error (0, 0, "\ 474 -a cannot be specified in the modules file along with other options"); 475 ++err; 476 goto do_module_return; 477 } 478 479 /* if this was an alias, call ourselves recursively for each module */ 480 if (alias) 481 { 482 int i; 483 484 for (i = 0; i < modargc; i++) 485 { 486 /* 487 * Recursion check: if an alias module calls itself or a module 488 * which causes the first to be called again, print an error 489 * message and stop recursing. 490 * 491 * Algorithm: 492 * 493 * 1. Check that MNAME isn't in the stack. 494 * 2. Push MNAME onto the stack. 495 * 3. Call do_module(). 496 * 4. Pop MNAME from the stack. 497 */ 498 if (stack && findnode (stack, mname)) 499 error (0, 0, 500 "module `%s' in modules file contains infinite loop", 501 mname); 502 else 503 { 504 if (!stack) stack = getlist(); 505 push_string (stack, mname); 506 err += my_module (db, modargv[i], m_type, msg, callback_proc, 507 where, shorten, local_specified, 508 run_module_prog, build_dirs, extra_arg, 509 stack); 510 pop_string (stack); 511 if (isempty (stack)) dellist (&stack); 512 } 513 } 514 goto do_module_return; 515 } 516 517 if (mfile != NULL && modargc > 1) 518 { 519 error (0, 0, "\ 520 module `%s' is a request for a file in a module which is not a directory", 521 mname); 522 ++err; 523 goto do_module_return; 524 } 525 526 /* otherwise, process this module */ 527 if (modargc > 0) 528 { 529 err += callback_proc (modargc, modargv, where, mwhere, mfile, shorten, 530 local_specified, mname, msg); 531 } 532 else 533 { 534 /* 535 * we had nothing but special options, so we must 536 * make the appropriate directory and cd to it 537 */ 538 char *dir; 539 540 if (!build_dirs) 541 goto do_special; 542 543 dir = where ? where : (mwhere ? mwhere : mname); 544 /* XXX - think about making null repositories at each dir here 545 instead of just at the bottom */ 546 make_directories (dir); 547 if (CVS_CHDIR (dir) < 0) 548 { 549 error (0, errno, "cannot chdir to %s", dir); 550 spec_opt = NULL; 551 err++; 552 goto do_special; 553 } 554 if (!isfile (CVSADM)) 555 { 556 char *nullrepos; 557 558 nullrepos = emptydir_name (); 559 560 Create_Admin (".", dir, nullrepos, NULL, NULL, 0, 0, 1); 561 if (!noexec) 562 { 563 FILE *fp; 564 565 fp = xfopen (CVSADM_ENTSTAT, "w+"); 566 if (fclose (fp) == EOF) 567 error (1, errno, "cannot close %s", CVSADM_ENTSTAT); 568 #ifdef SERVER_SUPPORT 569 if (server_active) 570 server_set_entstat (dir, nullrepos); 571 #endif 572 } 573 free (nullrepos); 574 } 575 } 576 577 /* if there were special include args, process them now */ 578 579 do_special: 580 581 free_names (&xmodargc, xmodargv); 582 xmodargv = NULL; 583 584 /* blow off special options if -l was specified */ 585 if (local_specified) 586 spec_opt = NULL; 587 588 #ifdef SERVER_SUPPORT 589 /* We want to check out into the directory named by the module. 590 So we set a global variable which tells the server to glom that 591 directory name onto the front. A cleaner approach would be some 592 way of passing it down to the recursive call, through the 593 callback_proc, to start_recursion, and then into the update_dir in 594 the struct file_info. That way the "Updating foo" message could 595 print the actual directory we are checking out into. 596 597 For local CVS, this is handled by the chdir call above 598 (directly or via the callback_proc). */ 599 if (server_active && spec_opt != NULL) 600 { 601 char *change_to; 602 603 change_to = where ? where : (mwhere ? mwhere : mname); 604 server_dir_to_restore = server_dir; 605 restore_server_dir = 1; 606 if (server_dir_to_restore != NULL) 607 server_dir = Xasprintf ("%s/%s", server_dir_to_restore, change_to); 608 else 609 server_dir = xstrdup (change_to); 610 } 611 #endif 612 613 while (spec_opt != NULL) 614 { 615 char *next_opt; 616 617 cp = strchr (spec_opt, CVSMODULE_SPEC); 618 if (cp != NULL) 619 { 620 /* save the beginning of the next arg */ 621 next_opt = cp + 1; 622 623 /* strip whitespace off the end */ 624 do 625 *cp = '\0'; 626 while (cp > spec_opt && isspace ((unsigned char) *--cp)); 627 } 628 else 629 next_opt = NULL; 630 631 /* strip whitespace from front */ 632 while (isspace ((unsigned char) *spec_opt)) 633 spec_opt++; 634 635 if (*spec_opt == '\0') 636 error (0, 0, "Mal-formed %c option for module %s - ignored", 637 CVSMODULE_SPEC, mname); 638 else 639 err += my_module (db, spec_opt, m_type, msg, callback_proc, 640 NULL, 0, local_specified, run_module_prog, 641 build_dirs, extra_arg, stack); 642 spec_opt = next_opt; 643 } 644 645 #ifdef SERVER_SUPPORT 646 if (server_active && restore_server_dir) 647 { 648 free (server_dir); 649 server_dir = server_dir_to_restore; 650 } 651 #endif 652 653 /* cd back to where we started */ 654 if (restore_cwd (&cwd)) 655 error (1, errno, "Failed to restore current directory, `%s'.", 656 cwd.name); 657 free_cwd (&cwd); 658 cwd_saved = 0; 659 660 /* run checkout or tag prog if appropriate */ 661 if (err == 0 && run_module_prog) 662 { 663 if ((m_type == TAG && tag_prog != NULL) || 664 (m_type == CHECKOUT && checkout_prog != NULL) || 665 (m_type == EXPORT && export_prog != NULL)) 666 { 667 /* 668 * If a relative pathname is specified as the checkout, tag 669 * or export proc, try to tack on the current "where" value. 670 * if we can't find a matching program, just punt and use 671 * whatever is specified in the modules file. 672 */ 673 char *real_prog = NULL; 674 char *prog = (m_type == TAG ? tag_prog : 675 (m_type == CHECKOUT ? checkout_prog : export_prog)); 676 char *real_where = (where != NULL ? where : mwhere); 677 char *expanded_path; 678 679 if ((*prog != '/') && (*prog != '.')) 680 { 681 real_prog = Xasprintf ("%s/%s", real_where, prog); 682 if (isfile (real_prog)) 683 prog = real_prog; 684 } 685 686 /* XXX can we determine the line number for this entry??? */ 687 expanded_path = expand_path (prog, current_parsed_root->directory, 688 false, "modules", 0); 689 if (expanded_path != NULL) 690 { 691 run_setup (expanded_path); 692 run_add_arg (real_where); 693 694 if (extra_arg) 695 run_add_arg (extra_arg); 696 697 if (!quiet) 698 { 699 cvs_output (program_name, 0); 700 cvs_output (" ", 1); 701 cvs_output (cvs_cmd_name, 0); 702 cvs_output (": Executing '", 0); 703 run_print (stdout); 704 cvs_output ("'\n", 0); 705 cvs_flushout (); 706 } 707 err += run_exec (RUN_TTY, RUN_TTY, RUN_TTY, 708 RUN_NORMAL | RUN_UNSETXID); 709 free (expanded_path); 710 } 711 if (real_prog) free (real_prog); 712 } 713 } 714 715 do_module_return: 716 /* clean up */ 717 if (xmodargv != NULL) 718 free_names (&xmodargc, xmodargv); 719 if (mwhere) 720 free (mwhere); 721 if (checkout_prog) 722 free (checkout_prog); 723 if (export_prog) 724 free (export_prog); 725 if (tag_prog) 726 free (tag_prog); 727 if (cwd_saved) 728 free_cwd (&cwd); 729 if (value != NULL) 730 free (value); 731 732 if (xvalue != NULL) 733 free (xvalue); 734 return (err); 735 } 736 737 738 739 /* External face of do_module so that we can have an internal version which 740 * accepts a stack argument to track alias recursion. 741 */ 742 int 743 do_module (DBM *db, char *mname, enum mtype m_type, char *msg, 744 CALLBACKPROC callback_proc, char *where, int shorten, 745 int local_specified, int run_module_prog, int build_dirs, 746 char *extra_arg) 747 { 748 return my_module (db, mname, m_type, msg, callback_proc, where, shorten, 749 local_specified, run_module_prog, build_dirs, extra_arg, 750 NULL); 751 } 752 753 754 755 /* - Read all the records from the modules database into an array. 756 - Sort the array depending on what format is desired. 757 - Print the array in the format desired. 758 759 Currently, there are only two "desires": 760 761 1. Sort by module name and format the whole entry including switches, 762 files and the comment field: (Including aliases) 763 764 modulename -s switches, one per line, even if 765 it has many switches. 766 Directories and files involved, formatted 767 to cover multiple lines if necessary. 768 # Comment, also formatted to cover multiple 769 # lines if necessary. 770 771 2. Sort by status field string and print: (*not* including aliases) 772 773 modulename STATUS Directories and files involved, formatted 774 to cover multiple lines if necessary. 775 # Comment, also formatted to cover multiple 776 # lines if necessary. 777 */ 778 779 static struct sortrec *s_head; 780 781 static int s_max = 0; /* Number of elements allocated */ 782 static int s_count = 0; /* Number of elements used */ 783 784 static int Status; /* Nonzero if the user is 785 interested in status 786 information as well as 787 module name */ 788 static char def_status[] = "NONE"; 789 790 /* Sort routine for qsort: 791 - If we want the "Status" field to be sorted, check it first. 792 - Then compare the "module name" fields. Since they are unique, we don't 793 have to look further. 794 */ 795 static int 796 sort_order (const void *l, const void *r) 797 { 798 int i; 799 const struct sortrec *left = (const struct sortrec *) l; 800 const struct sortrec *right = (const struct sortrec *) r; 801 802 if (Status) 803 { 804 /* If Sort by status field, compare them. */ 805 if ((i = strcmp (left->status, right->status)) != 0) 806 return (i); 807 } 808 return (strcmp (left->modname, right->modname)); 809 } 810 811 static void 812 save_d (char *k, int ks, char *d, int ds) 813 { 814 char *cp, *cp2; 815 struct sortrec *s_rec; 816 817 if (Status && *d == '-' && *(d + 1) == 'a') 818 return; /* We want "cvs co -s" and it is an alias! */ 819 820 if (s_count == s_max) 821 { 822 s_max += 64; 823 s_head = xnrealloc (s_head, s_max, sizeof (*s_head)); 824 } 825 s_rec = &s_head[s_count]; 826 s_rec->modname = cp = xmalloc (ks + 1); 827 (void) strncpy (cp, k, ks); 828 *(cp + ks) = '\0'; 829 830 s_rec->rest = cp2 = xmalloc (ds + 1); 831 cp = d; 832 *(cp + ds) = '\0'; /* Assumes an extra byte at end of static dbm buffer */ 833 834 while (isspace ((unsigned char) *cp)) 835 cp++; 836 /* Turn <spaces> into one ' ' -- makes the rest of this routine simpler */ 837 while (*cp) 838 { 839 if (isspace ((unsigned char) *cp)) 840 { 841 *cp2++ = ' '; 842 while (isspace ((unsigned char) *cp)) 843 cp++; 844 } 845 else 846 *cp2++ = *cp++; 847 } 848 *cp2 = '\0'; 849 850 /* Look for the "-s statusvalue" text */ 851 if (Status) 852 { 853 s_rec->status = def_status; 854 855 for (cp = s_rec->rest; (cp2 = strchr (cp, '-')) != NULL; cp = ++cp2) 856 { 857 if (*(cp2 + 1) == 's' && *(cp2 + 2) == ' ') 858 { 859 char *status_start; 860 861 cp2 += 3; 862 status_start = cp2; 863 while (*cp2 != ' ' && *cp2 != '\0') 864 cp2++; 865 s_rec->status = xmalloc (cp2 - status_start + 1); 866 strncpy (s_rec->status, status_start, cp2 - status_start); 867 s_rec->status[cp2 - status_start] = '\0'; 868 cp = cp2; 869 break; 870 } 871 } 872 } 873 else 874 cp = s_rec->rest; 875 876 /* Find comment field, clean up on all three sides & compress blanks */ 877 if ((cp2 = cp = strchr (cp, '#')) != NULL) 878 { 879 if (*--cp2 == ' ') 880 *cp2 = '\0'; 881 if (*++cp == ' ') 882 cp++; 883 s_rec->comment = cp; 884 } 885 else 886 s_rec->comment = ""; 887 888 s_count++; 889 } 890 891 /* Print out the module database as we know it. If STATUS is 892 non-zero, print out status information for each module. */ 893 894 void 895 cat_module (int status) 896 { 897 DBM *db; 898 datum key, val; 899 int i, c, wid, argc, cols = 80, indent, fill; 900 int moduleargc; 901 struct sortrec *s_h; 902 char *cp, *cp2, **argv; 903 char **moduleargv; 904 905 Status = status; 906 907 /* Read the whole modules file into allocated records */ 908 if (!(db = open_module ())) 909 error (1, 0, "failed to open the modules file"); 910 911 for (key = dbm_firstkey (db); key.dptr != NULL; key = dbm_nextkey (db)) 912 { 913 val = dbm_fetch (db, key); 914 if (val.dptr != NULL) 915 save_d (key.dptr, key.dsize, val.dptr, val.dsize); 916 } 917 918 close_module (db); 919 920 /* Sort the list as requested */ 921 qsort ((void *) s_head, s_count, sizeof (struct sortrec), sort_order); 922 923 /* 924 * Run through the sorted array and format the entries 925 * indent = space for modulename + space for status field 926 */ 927 indent = 12 + (status * 12); 928 fill = cols - (indent + 2); 929 for (s_h = s_head, i = 0; i < s_count; i++, s_h++) 930 { 931 char *line; 932 933 /* Print module name (and status, if wanted) */ 934 line = Xasprintf ("%-12s", s_h->modname); 935 cvs_output (line, 0); 936 free (line); 937 if (status) 938 { 939 line = Xasprintf (" %-11s", s_h->status); 940 cvs_output (line, 0); 941 free (line); 942 } 943 944 /* Parse module file entry as command line and print options */ 945 line = Xasprintf ("%s %s", s_h->modname, s_h->rest); 946 line2argv (&moduleargc, &moduleargv, line, " \t"); 947 free (line); 948 argc = moduleargc; 949 argv = moduleargv; 950 951 getoptreset (); 952 wid = 0; 953 while ((c = getopt (argc, argv, CVSMODULE_OPTS)) != -1) 954 { 955 if (!status) 956 { 957 if (c == 'a' || c == 'l') 958 { 959 char buf[5]; 960 961 sprintf (buf, " -%c", c); 962 cvs_output (buf, 0); 963 wid += 3; /* Could just set it to 3 */ 964 } 965 else 966 { 967 char buf[10]; 968 969 if (strlen (optarg) + 4 + wid > (unsigned) fill) 970 { 971 int j; 972 973 cvs_output ("\n", 1); 974 for (j = 0; j < indent; ++j) 975 cvs_output (" ", 1); 976 wid = 0; 977 } 978 sprintf (buf, " -%c ", c); 979 cvs_output (buf, 0); 980 cvs_output (optarg, 0); 981 wid += strlen (optarg) + 4; 982 } 983 } 984 } 985 argc -= optind; 986 argv += optind; 987 988 /* Format and Print all the files and directories */ 989 for (; argc--; argv++) 990 { 991 if (strlen (*argv) + wid > (unsigned) fill) 992 { 993 int j; 994 995 cvs_output ("\n", 1); 996 for (j = 0; j < indent; ++j) 997 cvs_output (" ", 1); 998 wid = 0; 999 } 1000 cvs_output (" ", 1); 1001 cvs_output (*argv, 0); 1002 wid += strlen (*argv) + 1; 1003 } 1004 cvs_output ("\n", 1); 1005 1006 /* Format the comment field -- save_d (), compressed spaces */ 1007 for (cp2 = cp = s_h->comment; *cp; cp2 = cp) 1008 { 1009 int j; 1010 1011 for (j = 0; j < indent; ++j) 1012 cvs_output (" ", 1); 1013 cvs_output (" # ", 0); 1014 if (strlen (cp2) < (unsigned) (fill - 2)) 1015 { 1016 cvs_output (cp2, 0); 1017 cvs_output ("\n", 1); 1018 break; 1019 } 1020 cp += fill - 2; 1021 while (*cp != ' ' && cp > cp2) 1022 cp--; 1023 if (cp == cp2) 1024 { 1025 cvs_output (cp2, 0); 1026 cvs_output ("\n", 1); 1027 break; 1028 } 1029 1030 *cp++ = '\0'; 1031 cvs_output (cp2, 0); 1032 cvs_output ("\n", 1); 1033 } 1034 1035 free_names(&moduleargc, moduleargv); 1036 /* FIXME-leak: here is where we would free s_h->modname, s_h->rest, 1037 and if applicable, s_h->status. Not exactly a memory leak, 1038 in the sense that we are about to exit(), but may be worth 1039 noting if we ever do a multithreaded server or something of 1040 the sort. */ 1041 } 1042 /* FIXME-leak: as above, here is where we would free s_head. */ 1043 } 1044