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