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