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