xref: /openbsd-src/gnu/usr.bin/cvs/src/modules.c (revision a4afd6dad3fba28f80e70208181c06c482259988)
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 1.4 kit.
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 "cvs.h"
24 #include "savecwd.h"
25 
26 
27 /* Defines related to the syntax of the modules file.  */
28 
29 /* Options in modules file.  Note that it is OK to use GNU getopt features;
30    we already are arranging to make sure we are using the getopt distributed
31    with CVS.  */
32 #define	CVSMODULE_OPTS	"+ad:i:lo:e:s:t:u:"
33 
34 /* Special delimiter.  */
35 #define CVSMODULE_SPEC	'&'
36 
37 struct sortrec
38 {
39     char *modname;
40     char *status;
41     char *rest;
42     char *comment;
43 };
44 
45 static int sort_order PROTO((const PTR l, const PTR r));
46 static void save_d PROTO((char *k, int ks, char *d, int ds));
47 
48 
49 /*
50  * Open the modules file, and die if the CVSROOT environment variable
51  * was not set.  If the modules file does not exist, that's fine, and
52  * a warning message is displayed and a NULL is returned.
53  */
54 DBM *
55 open_module ()
56 {
57     char mfile[PATH_MAX];
58 
59     if (CVSroot_original == NULL)
60     {
61 	(void) fprintf (stderr,
62 			"%s: must set the CVSROOT environment variable\n",
63 			program_name);
64 	error (1, 0, "or specify the '-d' option to %s", program_name);
65     }
66     (void) sprintf (mfile, "%s/%s/%s", CVSroot_directory,
67 		    CVSROOTADM, CVSROOTADM_MODULES);
68     return (dbm_open (mfile, O_RDONLY, 0666));
69 }
70 
71 /*
72  * Close the modules file, if the open succeeded, that is
73  */
74 void
75 close_module (db)
76     DBM *db;
77 {
78     if (db != NULL)
79 	dbm_close (db);
80 }
81 
82 /*
83  * This is the recursive function that processes a module name.
84  * It calls back the passed routine for each directory of a module
85  * It runs the post checkout or post tag proc from the modules file
86  */
87 int
88 do_module (db, mname, m_type, msg, callback_proc, where,
89 	   shorten, local_specified, run_module_prog, extra_arg)
90     DBM *db;
91     char *mname;
92     enum mtype m_type;
93     char *msg;
94     CALLBACKPROC callback_proc;
95     char *where;
96     int shorten;
97     int local_specified;
98     int run_module_prog;
99     char *extra_arg;
100 {
101     char *checkin_prog = NULL;
102     char *checkout_prog = NULL;
103     char *export_prog = NULL;
104     char *tag_prog = NULL;
105     char *update_prog = NULL;
106     struct saved_cwd cwd;
107     char *line;
108     int modargc;
109     int xmodargc;
110     char **modargv;
111     char *xmodargv[MAXFILEPERDIR];
112     char *value;
113     char *zvalue;
114     char *mwhere = NULL;
115     char *mfile = NULL;
116     char *spec_opt = NULL;
117     char xvalue[PATH_MAX];
118     int alias = 0;
119     datum key, val;
120     char *cp;
121     int c, err = 0;
122 
123 #ifdef SERVER_SUPPORT
124     if (trace)
125     {
126 	char *buf;
127 
128 	/* We use cvs_outerr, rather than fprintf to stderr, because
129 	   this may be called by server code with error_use_protocol
130 	   set.  */
131 	buf = xmalloc (100
132 		       + strlen (mname)
133 		       + strlen (msg)
134 		       + (where ? strlen (where) : 0)
135 		       + (extra_arg ? strlen (extra_arg) : 0));
136 	sprintf (buf, "%c-> do_module (%s, %s, %s, %s)\n",
137 		 (server_active) ? 'S' : ' ',
138 		 mname, msg, where ? where : "",
139 		 extra_arg ? extra_arg : "");
140 	cvs_outerr (buf, 0);
141 	free (buf);
142     }
143 #endif
144 
145     /* if this is a directory to ignore, add it to that list */
146     if (mname[0] == '!' && mname[1] != '\0')
147     {
148 	ign_dir_add (mname+1);
149 	return(err);
150     }
151 
152     /* strip extra stuff from the module name */
153     strip_path (mname);
154 
155     /*
156      * Look up the module using the following scheme:
157      *	1) look for mname as a module name
158      *	2) look for mname as a directory
159      *	3) look for mname as a file
160      *  4) take mname up to the first slash and look it up as a module name
161      *	   (this is for checking out only part of a module)
162      */
163 
164     /* look it up as a module name */
165     key.dptr = mname;
166     key.dsize = strlen (key.dptr);
167     if (db != NULL)
168 	val = dbm_fetch (db, key);
169     else
170 	val.dptr = NULL;
171     if (val.dptr != NULL)
172     {
173 	/* null terminate the value  XXX - is this space ours? */
174 	val.dptr[val.dsize] = '\0';
175 
176 	/* If the line ends in a comment, strip it off */
177 	if ((cp = strchr (val.dptr, '#')) != NULL)
178 	{
179 	    do
180 		*cp-- = '\0';
181 	    while (isspace (*cp));
182 	}
183 	else
184 	{
185 	    /* Always strip trailing spaces */
186 	    cp = strchr (val.dptr, '\0');
187 	    while (cp > val.dptr && isspace(*--cp))
188 		*cp = '\0';
189 	}
190 
191 	value = val.dptr;
192 	mwhere = xstrdup (mname);
193 	goto found;
194     }
195     else
196     {
197 	char file[PATH_MAX];
198 	char attic_file[PATH_MAX];
199 	char *acp;
200 
201 	/* check to see if mname is a directory or file */
202 
203 	(void) sprintf (file, "%s/%s", CVSroot_directory, mname);
204 	if ((acp = strrchr (mname, '/')) != NULL)
205 	{
206 	    *acp = '\0';
207 	    (void) sprintf (attic_file, "%s/%s/%s/%s%s", CVSroot_directory,
208 			    mname, CVSATTIC, acp + 1, RCSEXT);
209 	    *acp = '/';
210 	}
211 	else
212 	    (void) sprintf (attic_file, "%s/%s/%s%s", CVSroot_directory,
213 			    CVSATTIC, mname, RCSEXT);
214 
215 	if (isdir (file))
216 	{
217 	    value = mname;
218 	    goto found;
219 	}
220 	else
221 	{
222 	    (void) strcat (file, RCSEXT);
223 	    if (isfile (file) || isfile (attic_file))
224 	    {
225 		/* if mname was a file, we have to split it into "dir file" */
226 		if ((cp = strrchr (mname, '/')) != NULL && cp != mname)
227 		{
228 		    char *slashp;
229 
230 		    /* put the ' ' in a copy so we don't mess up the original */
231 		    value = strcpy (xvalue, mname);
232 		    slashp = strrchr (value, '/');
233 		    *slashp = ' ';
234 		}
235 		else
236 		{
237 		    /*
238 		     * the only '/' at the beginning or no '/' at all
239 		     * means the file we are interested in is in CVSROOT
240 		     * itself so the directory should be '.'
241 		     */
242 		    if (cp == mname)
243 		    {
244 			/* drop the leading / if specified */
245 			value = strcpy (xvalue, ". ");
246 			(void) strcat (xvalue, mname + 1);
247 		    }
248 		    else
249 		    {
250 			/* otherwise just copy it */
251 			value = strcpy (xvalue, ". ");
252 			(void) strcat (xvalue, mname);
253 		    }
254 		}
255 		goto found;
256 	    }
257 	}
258     }
259 
260     /* look up everything to the first / as a module */
261     if (mname[0] != '/' && (cp = strchr (mname, '/')) != NULL)
262     {
263 	/* Make the slash the new end of the string temporarily */
264 	*cp = '\0';
265 	key.dptr = mname;
266 	key.dsize = strlen (key.dptr);
267 
268 	/* do the lookup */
269 	if (db != NULL)
270 	    val = dbm_fetch (db, key);
271 	else
272 	    val.dptr = NULL;
273 
274 	/* if we found it, clean up the value and life is good */
275 	if (val.dptr != NULL)
276 	{
277 	    char *cp2;
278 
279 	    /* null terminate the value XXX - is this space ours? */
280 	    val.dptr[val.dsize] = '\0';
281 
282 	    /* If the line ends in a comment, strip it off */
283 	    if ((cp2 = strchr (val.dptr, '#')) != NULL)
284 	    {
285 		do
286 		    *cp2-- = '\0';
287 		while (isspace (*cp2));
288 	    }
289 	    value = val.dptr;
290 
291 	    /* mwhere gets just the module name */
292 	    mwhere = xstrdup (mname);
293 	    mfile = cp + 1;
294 
295 	    /* put the / back in mname */
296 	    *cp = '/';
297 
298 	    goto found;
299 	}
300 
301 	/* put the / back in mname */
302 	*cp = '/';
303     }
304 
305     /* if we got here, we couldn't find it using our search, so give up */
306     error (0, 0, "cannot find module `%s' - ignored", mname);
307     err++;
308     if (mwhere)
309 	free (mwhere);
310     return (err);
311 
312 
313     /*
314      * At this point, we found what we were looking for in one
315      * of the many different forms.
316      */
317   found:
318 
319     /* remember where we start */
320     if (save_cwd (&cwd))
321 	exit (EXIT_FAILURE);
322 
323     /* copy value to our own string since if we go recursive we'll be
324        really screwed if we do another dbm lookup */
325     zvalue = xstrdup (value);
326     value = zvalue;
327 
328     /* search the value for the special delimiter and save for later */
329     if ((cp = strchr (value, CVSMODULE_SPEC)) != NULL)
330     {
331 	*cp = '\0';			/* null out the special char */
332 	spec_opt = cp + 1;		/* save the options for later */
333 
334 	if (cp != value)		/* strip whitespace if necessary */
335 	    while (isspace (*--cp))
336 		*cp = '\0';
337 
338 	if (cp == value)
339 	{
340 	    /*
341 	     * we had nothing but special options, so skip arg
342 	     * parsing and regular stuff entirely
343 	     *
344 	     * If there were only special ones though, we must
345 	     * make the appropriate directory and cd to it
346 	     */
347 	    char *dir;
348 
349 	    /* XXX - XXX - MAJOR HACK - DO NOT SHIP - this needs to
350 	       be !pipeout, but we don't know that here yet */
351 	    if (!run_module_prog)
352 		goto out;
353 
354 	    dir = where ? where : mname;
355 	    /* XXX - think about making null repositories at each dir here
356 		     instead of just at the bottom */
357 	    make_directories (dir);
358 	    if ( CVS_CHDIR (dir) < 0)
359 	    {
360 		error (0, errno, "cannot chdir to %s", dir);
361 		spec_opt = NULL;
362 		err++;
363 		goto out;
364 	    }
365 	    if (!isfile (CVSADM))
366 	    {
367 		char nullrepos[PATH_MAX];
368 
369 		(void) sprintf (nullrepos, "%s/%s/%s", CVSroot_directory,
370 				CVSROOTADM, CVSNULLREPOS);
371 		if (!isfile (nullrepos))
372 		{
373 		    mode_t omask;
374 		    omask = umask (cvsumask);
375 		    (void) CVS_MKDIR (nullrepos, 0777);
376 		    (void) umask (omask);
377 		}
378 		if (!isdir (nullrepos))
379 		    error (1, 0, "there is no repository %s", nullrepos);
380 
381 		Create_Admin (".", dir,
382 			      nullrepos, (char *) NULL, (char *) NULL);
383 		if (!noexec)
384 		{
385 		    FILE *fp;
386 
387 		    fp = open_file (CVSADM_ENTSTAT, "w+");
388 		    if (fclose (fp) == EOF)
389 			error (1, errno, "cannot close %s", CVSADM_ENTSTAT);
390 #ifdef SERVER_SUPPORT
391 		    if (server_active)
392 			server_set_entstat (dir, nullrepos);
393 #endif
394 		}
395 	    }
396 	  out:
397 	    goto do_special;
398 	}
399     }
400 
401     /* don't do special options only part of a module was specified */
402     if (mfile != NULL)
403 	spec_opt = NULL;
404 
405     /*
406      * value now contains one of the following:
407      *    1) dir
408      *	  2) dir file
409      *    3) the value from modules without any special args
410      *		    [ args ] dir [file] [file] ...
411      *	     or     -a module [ module ] ...
412      */
413 
414     /* Put the value on a line with XXX prepended for getopt to eat */
415     line = xmalloc (strlen (value) + 10);
416     (void) sprintf (line, "%s %s", "XXX", value);
417 
418     /* turn the line into an argv[] array */
419     line2argv (&xmodargc, xmodargv, line);
420     free (line);
421     modargc = xmodargc;
422     modargv = xmodargv;
423 
424     /* parse the args */
425     optind = 1;
426     while ((c = getopt (modargc, modargv, CVSMODULE_OPTS)) != -1)
427     {
428 	switch (c)
429 	{
430 	    case 'a':
431 		alias = 1;
432 		break;
433 	    case 'd':
434 		if (mwhere)
435 		    free (mwhere);
436 		mwhere = xstrdup (optarg);
437 		break;
438 	    case 'i':
439 		checkin_prog = optarg;
440 		break;
441 	    case 'l':
442 		local_specified = 1;
443 		break;
444 	    case 'o':
445 		checkout_prog = optarg;
446 		break;
447 	    case 'e':
448 		export_prog = optarg;
449 		break;
450 	    case 't':
451 		tag_prog = optarg;
452 		break;
453 	    case 'u':
454 		update_prog = optarg;
455 		break;
456 	    case '?':
457 		error (0, 0,
458 		       "modules file has invalid option for key %s value %s",
459 		       key.dptr, val.dptr);
460 		err++;
461 		if (mwhere)
462 		    free (mwhere);
463 		free (zvalue);
464 		free_cwd (&cwd);
465 		return (err);
466 	}
467     }
468     modargc -= optind;
469     modargv += optind;
470     if (modargc == 0)
471     {
472 	error (0, 0, "modules file missing directory for module %s", mname);
473 	if (mwhere)
474 	    free (mwhere);
475 	free (zvalue);
476 	free_cwd (&cwd);
477 	return (++err);
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 	    if (strcmp (mname, modargv[i]) == 0)
488 		error (0, 0,
489 		       "module `%s' in modules file contains infinite loop",
490 		       mname);
491 	    else
492 		err += do_module (db, modargv[i], m_type, msg, callback_proc,
493 				  where, shorten, local_specified,
494 				  run_module_prog, extra_arg);
495 	}
496 	if (mwhere)
497 	    free (mwhere);
498 	free (zvalue);
499 	free_cwd (&cwd);
500 	return (err);
501     }
502 
503     /* otherwise, process this module */
504     err += callback_proc (&modargc, modargv, where, mwhere, mfile, shorten,
505 			  local_specified, mname, msg);
506 
507 #if 0
508     /* FIXME: I've fixed this so that the correct arguments are called,
509        but now this fails because there is code below this point that
510        uses optarg values extracted from the arg vector. */
511     free_names (&xmodargc, xmodargv);
512 #endif
513 
514     /* if there were special include args, process them now */
515 
516   do_special:
517 
518     /* blow off special options if -l was specified */
519     if (local_specified)
520 	spec_opt = NULL;
521 
522     while (spec_opt != NULL)
523     {
524 	char *next_opt;
525 
526 	cp = strchr (spec_opt, CVSMODULE_SPEC);
527 	if (cp != NULL)
528 	{
529 	    /* save the beginning of the next arg */
530 	    next_opt = cp + 1;
531 
532 	    /* strip whitespace off the end */
533 	    do
534 		*cp = '\0';
535 	    while (isspace (*--cp));
536 	}
537 	else
538 	    next_opt = NULL;
539 
540 	/* strip whitespace from front */
541 	while (isspace (*spec_opt))
542 	    spec_opt++;
543 
544 	if (*spec_opt == '\0')
545 	    error (0, 0, "Mal-formed %c option for module %s - ignored",
546 		   CVSMODULE_SPEC, mname);
547 	else
548 	    err += do_module (db, spec_opt, m_type, msg, callback_proc,
549 			      (char *) NULL, 0, local_specified,
550 			      run_module_prog, extra_arg);
551 	spec_opt = next_opt;
552     }
553 
554     /* write out the checkin/update prog files if necessary */
555 #ifdef SERVER_SUPPORT
556     if (err == 0 && !noexec && m_type == CHECKOUT && server_expanding)
557     {
558 	if (checkin_prog != NULL)
559 	    server_prog (where ? where : mname, checkin_prog, PROG_CHECKIN);
560 	if (update_prog != NULL)
561 	    server_prog (where ? where : mname, update_prog, PROG_UPDATE);
562     }
563     else
564 #endif
565     if (err == 0 && !noexec && m_type == CHECKOUT && run_module_prog)
566     {
567 	FILE *fp;
568 
569 	if (checkin_prog != NULL)
570 	{
571 	    fp = open_file (CVSADM_CIPROG, "w+");
572 	    (void) fprintf (fp, "%s\n", checkin_prog);
573 	    if (fclose (fp) == EOF)
574 		error (1, errno, "cannot close %s", CVSADM_CIPROG);
575 	}
576 	if (update_prog != NULL)
577 	{
578 	    fp = open_file (CVSADM_UPROG, "w+");
579 	    (void) fprintf (fp, "%s\n", update_prog);
580 	    if (fclose (fp) == EOF)
581 		error (1, errno, "cannot close %s", CVSADM_UPROG);
582 	}
583     }
584 
585     /* cd back to where we started */
586     if (restore_cwd (&cwd, NULL))
587 	exit (EXIT_FAILURE);
588     free_cwd (&cwd);
589 
590     /* run checkout or tag prog if appropriate */
591     if (err == 0 && run_module_prog)
592     {
593 	if ((m_type == TAG && tag_prog != NULL) ||
594 	    (m_type == CHECKOUT && checkout_prog != NULL) ||
595 	    (m_type == EXPORT && export_prog != NULL))
596 	{
597 	    /*
598 	     * If a relative pathname is specified as the checkout, tag
599 	     * or export proc, try to tack on the current "where" value.
600 	     * if we can't find a matching program, just punt and use
601 	     * whatever is specified in the modules file.
602 	     */
603 	    char real_prog[PATH_MAX];
604 	    char *prog = (m_type == TAG ? tag_prog :
605 			  (m_type == CHECKOUT ? checkout_prog : export_prog));
606 	    char *real_where = (where != NULL ? where : mwhere);
607 	    char *expanded_path;
608 
609 	    if ((*prog != '/') && (*prog != '.'))
610 	    {
611 		(void) sprintf (real_prog, "%s/%s", real_where, prog);
612 		if (isfile (real_prog))
613 		    prog = real_prog;
614 	    }
615 
616 	    /* XXX can we determine the line number for this entry??? */
617 	    expanded_path = expand_path (prog, "modules", 0);
618 	    if (expanded_path != NULL)
619 	    {
620 		run_setup ("%s %s", expanded_path, real_where);
621 
622 		if (extra_arg)
623 		    run_arg (extra_arg);
624 
625 		if (!quiet)
626 		{
627 		    (void) printf ("%s %s: Executing '", program_name,
628 				   command_name);
629 		    run_print (stdout);
630 		    (void) printf ("'\n");
631 		}
632 		err += run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
633 		free (expanded_path);
634 	    }
635 	}
636     }
637 
638     /* clean up */
639     if (mwhere)
640 	free (mwhere);
641     free (zvalue);
642 
643     return (err);
644 }
645 
646 /* - Read all the records from the modules database into an array.
647    - Sort the array depending on what format is desired.
648    - Print the array in the format desired.
649 
650    Currently, there are only two "desires":
651 
652    1. Sort by module name and format the whole entry including switches,
653       files and the comment field: (Including aliases)
654 
655       modulename	-s switches, one per line, even if
656 			-i it has many switches.
657 			Directories and files involved, formatted
658 			to cover multiple lines if necessary.
659 			# Comment, also formatted to cover multiple
660 			# lines if necessary.
661 
662    2. Sort by status field string and print:  (*not* including aliases)
663 
664       modulename    STATUS	Directories and files involved, formatted
665 				to cover multiple lines if necessary.
666 				# Comment, also formatted to cover multiple
667 				# lines if necessary.
668 */
669 
670 static struct sortrec *s_head;
671 
672 static int s_max = 0;			/* Number of elements allocated */
673 static int s_count = 0;			/* Number of elements used */
674 
675 static int Status;		        /* Nonzero if the user is
676 					   interested in status
677 					   information as well as
678 					   module name */
679 static char def_status[] = "NONE";
680 
681 /* Sort routine for qsort:
682    - If we want the "Status" field to be sorted, check it first.
683    - Then compare the "module name" fields.  Since they are unique, we don't
684      have to look further.
685 */
686 static int
687 sort_order (l, r)
688     const PTR l;
689     const PTR r;
690 {
691     int i;
692     const struct sortrec *left = (const struct sortrec *) l;
693     const struct sortrec *right = (const struct sortrec *) r;
694 
695     if (Status)
696     {
697 	/* If Sort by status field, compare them. */
698 	if ((i = strcmp (left->status, right->status)) != 0)
699 	    return (i);
700     }
701     return (strcmp (left->modname, right->modname));
702 }
703 
704 static void
705 save_d (k, ks, d, ds)
706     char *k;
707     int ks;
708     char *d;
709     int ds;
710 {
711     char *cp, *cp2;
712     struct sortrec *s_rec;
713 
714     if (Status && *d == '-' && *(d + 1) == 'a')
715 	return;				/* We want "cvs co -s" and it is an alias! */
716 
717     if (s_count == s_max)
718     {
719 	s_max += 64;
720 	s_head = (struct sortrec *) xrealloc ((char *) s_head, s_max * sizeof (*s_head));
721     }
722     s_rec = &s_head[s_count];
723     s_rec->modname = cp = xmalloc (ks + 1);
724     (void) strncpy (cp, k, ks);
725     *(cp + ks) = '\0';
726 
727     s_rec->rest = cp2 = xmalloc (ds + 1);
728     cp = d;
729     *(cp + ds) = '\0';	/* Assumes an extra byte at end of static dbm buffer */
730 
731     while (isspace (*cp))
732 	cp++;
733     /* Turn <spaces> into one ' ' -- makes the rest of this routine simpler */
734     while (*cp)
735     {
736 	if (isspace (*cp))
737 	{
738 	    *cp2++ = ' ';
739 	    while (isspace (*cp))
740 		cp++;
741 	}
742 	else
743 	    *cp2++ = *cp++;
744     }
745     *cp2 = '\0';
746 
747     /* Look for the "-s statusvalue" text */
748     if (Status)
749     {
750 	s_rec->status = def_status;
751 
752 	/* Minor kluge, but general enough to maintain */
753 	for (cp = s_rec->rest; (cp2 = strchr (cp, '-')) != NULL; cp = ++cp2)
754 	{
755 	    if (*(cp2 + 1) == 's' && *(cp2 + 2) == ' ')
756 	    {
757 		s_rec->status = (cp2 += 3);
758 		while (*cp2 != ' ')
759 		    cp2++;
760 		*cp2++ = '\0';
761 		cp = cp2;
762 		break;
763 	    }
764 	}
765     }
766     else
767 	cp = s_rec->rest;
768 
769     /* Find comment field, clean up on all three sides & compress blanks */
770     if ((cp2 = cp = strchr (cp, '#')) != NULL)
771     {
772 	if (*--cp2 == ' ')
773 	    *cp2 = '\0';
774 	if (*++cp == ' ')
775 	    cp++;
776 	s_rec->comment = cp;
777     }
778     else
779 	s_rec->comment = "";
780 
781     s_count++;
782 }
783 
784 /* Print out the module database as we know it.  If STATUS is
785    non-zero, print out status information for each module. */
786 
787 void
788 cat_module (status)
789     int status;
790 {
791     DBM *db;
792     datum key, val;
793     int i, c, wid, argc, cols = 80, indent, fill;
794     int moduleargc;
795     struct sortrec *s_h;
796     char *cp, *cp2, **argv;
797     char *line;
798     char *moduleargv[MAXFILEPERDIR];
799 
800     Status = status;
801 
802     /* Read the whole modules file into allocated records */
803     if (!(db = open_module ()))
804 	error (1, 0, "failed to open the modules file");
805 
806     for (key = dbm_firstkey (db); key.dptr != NULL; key = dbm_nextkey (db))
807     {
808 	val = dbm_fetch (db, key);
809 	if (val.dptr != NULL)
810 	    save_d (key.dptr, key.dsize, val.dptr, val.dsize);
811     }
812 
813     /* Sort the list as requested */
814     qsort ((PTR) s_head, s_count, sizeof (struct sortrec), sort_order);
815 
816     /*
817      * Run through the sorted array and format the entries
818      * indent = space for modulename + space for status field
819      */
820     indent = 12 + (status * 12);
821     fill = cols - (indent + 2);
822     for (s_h = s_head, i = 0; i < s_count; i++, s_h++)
823     {
824 	/* Print module name (and status, if wanted) */
825 	(void) printf ("%-12s", s_h->modname);
826 	if (status)
827 	{
828 	    (void) printf (" %-11s", s_h->status);
829 	    if (s_h->status != def_status)
830 		*(s_h->status + strlen (s_h->status)) = ' ';
831 	}
832 
833 	/* Parse module file entry as command line and print options */
834 	line = xmalloc (strlen (s_h->modname) + strlen (s_h->rest) + 10);
835 	(void) sprintf (line, "%s %s", s_h->modname, s_h->rest);
836 	line2argv (&moduleargc, moduleargv, line);
837 	free (line);
838 	argc = moduleargc;
839 	argv = moduleargv;
840 
841 	optind = 0;
842 	wid = 0;
843 	while ((c = getopt (argc, argv, CVSMODULE_OPTS)) != -1)
844 	{
845 	    if (!status)
846 	    {
847 		if (c == 'a' || c == 'l')
848 		{
849 		    (void) printf (" -%c", c);
850 		    wid += 3;		/* Could just set it to 3 */
851 		}
852 		else
853 		{
854 		    if (strlen (optarg) + 4 + wid > (unsigned) fill)
855 		    {
856 			(void) printf ("\n%*s", indent, "");
857 			wid = 0;
858 		    }
859 		    (void) printf (" -%c %s", c, optarg);
860 		    wid += strlen (optarg) + 4;
861 		}
862 	    }
863 	}
864 	argc -= optind;
865 	argv += optind;
866 
867 	/* Format and Print all the files and directories */
868 	for (; argc--; argv++)
869 	{
870 	    if (strlen (*argv) + wid > (unsigned) fill)
871 	    {
872 		(void) printf ("\n%*s", indent, "");
873 		wid = 0;
874 	    }
875 	    (void) printf (" %s", *argv);
876 	    wid += strlen (*argv) + 1;
877 	}
878 	(void) printf ("\n");
879 
880 	/* Format the comment field -- save_d (), compressed spaces */
881 	for (cp2 = cp = s_h->comment; *cp; cp2 = cp)
882 	{
883 	    (void) printf ("%*s # ", indent, "");
884 	    if (strlen (cp2) < (unsigned) (fill - 2))
885 	    {
886 		(void) printf ("%s\n", cp2);
887 		break;
888 	    }
889 	    cp += fill - 2;
890 	    while (*cp != ' ' && cp > cp2)
891 		cp--;
892 	    if (cp == cp2)
893 	    {
894 		(void) printf ("%s\n", cp2);
895 		break;
896 	    }
897 
898 	    *cp++ = '\0';
899 	    (void) printf ("%s\n", cp2);
900 	}
901 
902 	free_names(&moduleargc, moduleargv);
903     }
904 }
905