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