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