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