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