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