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