modules.c revision 32785
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
37struct 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
52static int sort_order PROTO((const PTR l, const PTR r));
53static 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 */
61DBM *
62open_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 */
84void
85close_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 */
97int
98do_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, "%c-> do_module (%s, %s, %s, %s)\n",
151		 (server_active) ? 'S' : ' ',
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 (*cp));
196	}
197	else
198	{
199	    /* Always strip trailing spaces */
200	    cp = strchr (val.dptr, '\0');
201	    while (cp > val.dptr && isspace(*--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 (*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 (*--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, "\
545module `%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 (*--cp));
613	}
614	else
615	    next_opt = NULL;
616
617	/* strip whitespace from front */
618	while (isspace (*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
778static struct sortrec *s_head;
779
780static int s_max = 0;			/* Number of elements allocated */
781static int s_count = 0;			/* Number of elements used */
782
783static int Status;		        /* Nonzero if the user is
784					   interested in status
785					   information as well as
786					   module name */
787static 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*/
794static int
795sort_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
812static void
813save_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 (*cp))
840	cp++;
841    /* Turn <spaces> into one ' ' -- makes the rest of this routine simpler */
842    while (*cp)
843    {
844	if (isspace (*cp))
845	{
846	    *cp2++ = ' ';
847	    while (isspace (*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
899void
900cat_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