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