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