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