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 *
open_module(void)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
close_module(DBM * db)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
my_module(DBM * db,char * mname,enum mtype m_type,char * msg,CALLBACKPROC callback_proc,char * where,int shorten,int local_specified,int run_module_prog,int build_dirs,char * extra_arg,List * stack)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
do_module(DBM * db,char * mname,enum mtype m_type,char * msg,CALLBACKPROC callback_proc,char * where,int shorten,int local_specified,int run_module_prog,int build_dirs,char * extra_arg)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
sort_order(const void * l,const void * r)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
save_d(char * k,int ks,char * d,int ds)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
cat_module(int status)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