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