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