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