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