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