xref: /dragonfly/contrib/cvs-1.12/src/modules.c (revision 91dc43dd)
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     optind = 0;
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, RUN_NORMAL);
708 		free (expanded_path);
709 	    }
710 	    if (real_prog) free (real_prog);
711 	}
712     }
713 
714  do_module_return:
715     /* clean up */
716     if (xmodargv != NULL)
717 	free_names (&xmodargc, xmodargv);
718     if (mwhere)
719 	free (mwhere);
720     if (checkout_prog)
721 	free (checkout_prog);
722     if (export_prog)
723 	free (export_prog);
724     if (tag_prog)
725 	free (tag_prog);
726     if (cwd_saved)
727 	free_cwd (&cwd);
728     if (value != NULL)
729 	free (value);
730 
731     if (xvalue != NULL)
732 	free (xvalue);
733     return (err);
734 }
735 
736 
737 
738 /* External face of do_module so that we can have an internal version which
739  * accepts a stack argument to track alias recursion.
740  */
741 int
742 do_module (DBM *db, char *mname, enum mtype m_type, char *msg,
743            CALLBACKPROC callback_proc, char *where, int shorten,
744            int local_specified, int run_module_prog, int build_dirs,
745            char *extra_arg)
746 {
747     return my_module (db, mname, m_type, msg, callback_proc, where, shorten,
748                        local_specified, run_module_prog, build_dirs, extra_arg,
749                        NULL);
750 }
751 
752 
753 
754 /* - Read all the records from the modules database into an array.
755    - Sort the array depending on what format is desired.
756    - Print the array in the format desired.
757 
758    Currently, there are only two "desires":
759 
760    1. Sort by module name and format the whole entry including switches,
761       files and the comment field: (Including aliases)
762 
763       modulename	-s switches, one per line, even if
764 			it has many switches.
765 			Directories and files involved, formatted
766 			to cover multiple lines if necessary.
767 			# Comment, also formatted to cover multiple
768 			# lines if necessary.
769 
770    2. Sort by status field string and print:  (*not* including aliases)
771 
772       modulename    STATUS	Directories and files involved, formatted
773 				to cover multiple lines if necessary.
774 				# Comment, also formatted to cover multiple
775 				# lines if necessary.
776 */
777 
778 static struct sortrec *s_head;
779 
780 static int s_max = 0;			/* Number of elements allocated */
781 static int s_count = 0;			/* Number of elements used */
782 
783 static int Status;		        /* Nonzero if the user is
784 					   interested in status
785 					   information as well as
786 					   module name */
787 static char def_status[] = "NONE";
788 
789 /* Sort routine for qsort:
790    - If we want the "Status" field to be sorted, check it first.
791    - Then compare the "module name" fields.  Since they are unique, we don't
792      have to look further.
793 */
794 static int
795 sort_order (const void *l, const void *r)
796 {
797     int i;
798     const struct sortrec *left = (const struct sortrec *) l;
799     const struct sortrec *right = (const struct sortrec *) r;
800 
801     if (Status)
802     {
803 	/* If Sort by status field, compare them. */
804 	if ((i = strcmp (left->status, right->status)) != 0)
805 	    return (i);
806     }
807     return (strcmp (left->modname, right->modname));
808 }
809 
810 static void
811 save_d (char *k, int ks, char *d, int ds)
812 {
813     char *cp, *cp2;
814     struct sortrec *s_rec;
815 
816     if (Status && *d == '-' && *(d + 1) == 'a')
817 	return;				/* We want "cvs co -s" and it is an alias! */
818 
819     if (s_count == s_max)
820     {
821 	s_max += 64;
822 	s_head = xnrealloc (s_head, s_max, sizeof (*s_head));
823     }
824     s_rec = &s_head[s_count];
825     s_rec->modname = cp = xmalloc (ks + 1);
826     (void) strncpy (cp, k, ks);
827     *(cp + ks) = '\0';
828 
829     s_rec->rest = cp2 = xmalloc (ds + 1);
830     cp = d;
831     *(cp + ds) = '\0';	/* Assumes an extra byte at end of static dbm buffer */
832 
833     while (isspace ((unsigned char) *cp))
834 	cp++;
835     /* Turn <spaces> into one ' ' -- makes the rest of this routine simpler */
836     while (*cp)
837     {
838 	if (isspace ((unsigned char) *cp))
839 	{
840 	    *cp2++ = ' ';
841 	    while (isspace ((unsigned char) *cp))
842 		cp++;
843 	}
844 	else
845 	    *cp2++ = *cp++;
846     }
847     *cp2 = '\0';
848 
849     /* Look for the "-s statusvalue" text */
850     if (Status)
851     {
852 	s_rec->status = def_status;
853 
854 	for (cp = s_rec->rest; (cp2 = strchr (cp, '-')) != NULL; cp = ++cp2)
855 	{
856 	    if (*(cp2 + 1) == 's' && *(cp2 + 2) == ' ')
857 	    {
858 		char *status_start;
859 
860 		cp2 += 3;
861 		status_start = cp2;
862 		while (*cp2 != ' ' && *cp2 != '\0')
863 		    cp2++;
864 		s_rec->status = xmalloc (cp2 - status_start + 1);
865 		strncpy (s_rec->status, status_start, cp2 - status_start);
866 		s_rec->status[cp2 - status_start] = '\0';
867 		cp = cp2;
868 		break;
869 	    }
870 	}
871     }
872     else
873 	cp = s_rec->rest;
874 
875     /* Find comment field, clean up on all three sides & compress blanks */
876     if ((cp2 = cp = strchr (cp, '#')) != NULL)
877     {
878 	if (*--cp2 == ' ')
879 	    *cp2 = '\0';
880 	if (*++cp == ' ')
881 	    cp++;
882 	s_rec->comment = cp;
883     }
884     else
885 	s_rec->comment = "";
886 
887     s_count++;
888 }
889 
890 /* Print out the module database as we know it.  If STATUS is
891    non-zero, print out status information for each module. */
892 
893 void
894 cat_module (int status)
895 {
896     DBM *db;
897     datum key, val;
898     int i, c, wid, argc, cols = 80, indent, fill;
899     int moduleargc;
900     struct sortrec *s_h;
901     char *cp, *cp2, **argv;
902     char **moduleargv;
903 
904     Status = status;
905 
906     /* Read the whole modules file into allocated records */
907     if (!(db = open_module ()))
908 	error (1, 0, "failed to open the modules file");
909 
910     for (key = dbm_firstkey (db); key.dptr != NULL; key = dbm_nextkey (db))
911     {
912 	val = dbm_fetch (db, key);
913 	if (val.dptr != NULL)
914 	    save_d (key.dptr, key.dsize, val.dptr, val.dsize);
915     }
916 
917     close_module (db);
918 
919     /* Sort the list as requested */
920     qsort ((void *) s_head, s_count, sizeof (struct sortrec), sort_order);
921 
922     /*
923      * Run through the sorted array and format the entries
924      * indent = space for modulename + space for status field
925      */
926     indent = 12 + (status * 12);
927     fill = cols - (indent + 2);
928     for (s_h = s_head, i = 0; i < s_count; i++, s_h++)
929     {
930 	char *line;
931 
932 	/* Print module name (and status, if wanted) */
933 	line = Xasprintf ("%-12s", s_h->modname);
934 	cvs_output (line, 0);
935 	free (line);
936 	if (status)
937 	{
938 	    line = Xasprintf (" %-11s", s_h->status);
939 	    cvs_output (line, 0);
940 	    free (line);
941 	}
942 
943 	/* Parse module file entry as command line and print options */
944 	line = Xasprintf ("%s %s", s_h->modname, s_h->rest);
945 	line2argv (&moduleargc, &moduleargv, line, " \t");
946 	free (line);
947 	argc = moduleargc;
948 	argv = moduleargv;
949 
950 	optind = 0;
951 	wid = 0;
952 	while ((c = getopt (argc, argv, CVSMODULE_OPTS)) != -1)
953 	{
954 	    if (!status)
955 	    {
956 		if (c == 'a' || c == 'l')
957 		{
958 		    char buf[5];
959 
960 		    sprintf (buf, " -%c", c);
961 		    cvs_output (buf, 0);
962 		    wid += 3;		/* Could just set it to 3 */
963 		}
964 		else
965 		{
966 		    char buf[10];
967 
968 		    if (strlen (optarg) + 4 + wid > (unsigned) fill)
969 		    {
970 			int j;
971 
972 			cvs_output ("\n", 1);
973 			for (j = 0; j < indent; ++j)
974 			    cvs_output (" ", 1);
975 			wid = 0;
976 		    }
977 		    sprintf (buf, " -%c ", c);
978 		    cvs_output (buf, 0);
979 		    cvs_output (optarg, 0);
980 		    wid += strlen (optarg) + 4;
981 		}
982 	    }
983 	}
984 	argc -= optind;
985 	argv += optind;
986 
987 	/* Format and Print all the files and directories */
988 	for (; argc--; argv++)
989 	{
990 	    if (strlen (*argv) + wid > (unsigned) fill)
991 	    {
992 		int j;
993 
994 		cvs_output ("\n", 1);
995 		for (j = 0; j < indent; ++j)
996 		    cvs_output (" ", 1);
997 		wid = 0;
998 	    }
999 	    cvs_output (" ", 1);
1000 	    cvs_output (*argv, 0);
1001 	    wid += strlen (*argv) + 1;
1002 	}
1003 	cvs_output ("\n", 1);
1004 
1005 	/* Format the comment field -- save_d (), compressed spaces */
1006 	for (cp2 = cp = s_h->comment; *cp; cp2 = cp)
1007 	{
1008 	    int j;
1009 
1010 	    for (j = 0; j < indent; ++j)
1011 		cvs_output (" ", 1);
1012 	    cvs_output (" # ", 0);
1013 	    if (strlen (cp2) < (unsigned) (fill - 2))
1014 	    {
1015 		cvs_output (cp2, 0);
1016 		cvs_output ("\n", 1);
1017 		break;
1018 	    }
1019 	    cp += fill - 2;
1020 	    while (*cp != ' ' && cp > cp2)
1021 		cp--;
1022 	    if (cp == cp2)
1023 	    {
1024 		cvs_output (cp2, 0);
1025 		cvs_output ("\n", 1);
1026 		break;
1027 	    }
1028 
1029 	    *cp++ = '\0';
1030 	    cvs_output (cp2, 0);
1031 	    cvs_output ("\n", 1);
1032 	}
1033 
1034 	free_names(&moduleargc, moduleargv);
1035 	/* FIXME-leak: here is where we would free s_h->modname, s_h->rest,
1036 	   and if applicable, s_h->status.  Not exactly a memory leak,
1037 	   in the sense that we are about to exit(), but may be worth
1038 	   noting if we ever do a multithreaded server or something of
1039 	   the sort.  */
1040     }
1041     /* FIXME-leak: as above, here is where we would free s_head.  */
1042 }
1043