117721Speter/*
2175261Sobrien * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
317721Speter *
4175261Sobrien * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5175261Sobrien *                                  and others.
6175261Sobrien *
7175261Sobrien * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8175261Sobrien * Portions Copyright (C) 1989-1992, Brian Berliner
9175261Sobrien *
1017721Speter *    You may distribute under the terms of the GNU General Public License
11128266Speter *    as specified in the README file that comes with the CVS source
12128266Speter *    distribution.
1317721Speter *
1417721Speter * Modules
1517721Speter *
1617721Speter *	Functions for accessing the modules file.
1717721Speter *
1817721Speter *	The modules file supports basically three formats of lines:
1917721Speter *		key [options] directory files... [ -x directory [files] ] ...
2017721Speter *		key [options] directory [ -x directory [files] ] ...
2117721Speter *		key -a aliases...
2217721Speter *
2317721Speter *	The -a option allows an aliasing step in the parsing of the modules
2417721Speter *	file.  The "aliases" listed on a line following the -a are
2517721Speter *	processed one-by-one, as if they were specified as arguments on the
2617721Speter *	command line.
2717721Speter */
2817721Speter
2954427Speter#include <assert.h>
3017721Speter#include "cvs.h"
3117721Speter#include "savecwd.h"
3217721Speter
3317721Speter
3417721Speter/* Defines related to the syntax of the modules file.  */
3517721Speter
3617721Speter/* Options in modules file.  Note that it is OK to use GNU getopt features;
3717721Speter   we already are arranging to make sure we are using the getopt distributed
3817721Speter   with CVS.  */
39128266Speter#define	CVSMODULE_OPTS	"+ad:lo:e:s:t:"
4017721Speter
4117721Speter/* Special delimiter.  */
4217721Speter#define CVSMODULE_SPEC	'&'
4317721Speter
4417721Speterstruct sortrec
4517721Speter{
4632785Speter    /* Name of the module, malloc'd.  */
4717721Speter    char *modname;
4832785Speter    /* If Status variable is set, this is either def_status or the malloc'd
4932785Speter       name of the status.  If Status is not set, the field is left
5032785Speter       uninitialized.  */
5117721Speter    char *status;
5232785Speter    /* Pointer to a malloc'd array which contains (1) the raw contents
5332785Speter       of the options and arguments, excluding comments, (2) a '\0',
5432785Speter       and (3) the storage for the "comment" field.  */
5517721Speter    char *rest;
5617721Speter    char *comment;
5717721Speter};
5817721Speter
5917721Speterstatic int sort_order PROTO((const PTR l, const PTR r));
6017721Speterstatic void save_d PROTO((char *k, int ks, char *d, int ds));
6117721Speter
6217721Speter
6317721Speter/*
6417721Speter * Open the modules file, and die if the CVSROOT environment variable
6517721Speter * was not set.  If the modules file does not exist, that's fine, and
6617721Speter * a warning message is displayed and a NULL is returned.
6717721Speter */
6817721SpeterDBM *
6917721Speteropen_module ()
7017721Speter{
7125839Speter    char *mfile;
7225839Speter    DBM *retval;
7317721Speter
7481404Speter    if (current_parsed_root == NULL)
7517721Speter    {
7625839Speter	error (0, 0, "must set the CVSROOT environment variable");
7725839Speter	error (1, 0, "or specify the '-d' global option");
7817721Speter    }
7981404Speter    mfile = xmalloc (strlen (current_parsed_root->directory)
8081404Speter		     + sizeof (CVSROOTADM)
8181404Speter		     + sizeof (CVSROOTADM_MODULES) + 3);
8281404Speter    (void) sprintf (mfile, "%s/%s/%s", current_parsed_root->directory,
8325839Speter		    CVSROOTADM, CVSROOTADM_MODULES);
8425839Speter    retval = dbm_open (mfile, O_RDONLY, 0666);
8525839Speter    free (mfile);
8625839Speter    return retval;
8717721Speter}
8817721Speter
8917721Speter/*
9017721Speter * Close the modules file, if the open succeeded, that is
9117721Speter */
9217721Spetervoid
9317721Speterclose_module (db)
9417721Speter    DBM *db;
9517721Speter{
9617721Speter    if (db != NULL)
9717721Speter	dbm_close (db);
9817721Speter}
9917721Speter
100128266Speter
101128266Speter
10217721Speter/*
10317721Speter * This is the recursive function that processes a module name.
10417721Speter * It calls back the passed routine for each directory of a module
10517721Speter * It runs the post checkout or post tag proc from the modules file
10617721Speter */
107128266Speterstatic int
108128266Spetermy_module (db, mname, m_type, msg, callback_proc, where, shorten,
109128266Speter	   local_specified, run_module_prog, build_dirs, extra_arg,
110128266Speter	   stack)
11117721Speter    DBM *db;
11217721Speter    char *mname;
11317721Speter    enum mtype m_type;
11417721Speter    char *msg;
11517721Speter    CALLBACKPROC callback_proc;
11617721Speter    char *where;
11717721Speter    int shorten;
11817721Speter    int local_specified;
11917721Speter    int run_module_prog;
12081404Speter    int build_dirs;
12117721Speter    char *extra_arg;
122128266Speter    List *stack;
12317721Speter{
12417721Speter    char *checkout_prog = NULL;
12517721Speter    char *export_prog = NULL;
12617721Speter    char *tag_prog = NULL;
12717721Speter    struct saved_cwd cwd;
12825839Speter    int cwd_saved = 0;
12917721Speter    char *line;
13017721Speter    int modargc;
13117721Speter    int xmodargc;
132177391Sobrien    char **modargv = NULL;
13366525Speter    char **xmodargv = NULL;
13454427Speter    /* Found entry from modules file, including options and such.  */
13554427Speter    char *value = NULL;
13617721Speter    char *mwhere = NULL;
13717721Speter    char *mfile = NULL;
13817721Speter    char *spec_opt = NULL;
13917721Speter    int alias = 0;
14017721Speter    datum key, val;
14117721Speter    char *cp;
14217721Speter    int c, err = 0;
14325839Speter    int nonalias_opt = 0;
14417721Speter
14517721Speter#ifdef SERVER_SUPPORT
14625839Speter    int restore_server_dir = 0;
14732785Speter    char *server_dir_to_restore = NULL;
14817721Speter    if (trace)
14925839Speter    {
15025839Speter	char *buf;
15125839Speter
15225839Speter	/* We use cvs_outerr, rather than fprintf to stderr, because
15325839Speter	   this may be called by server code with error_use_protocol
15425839Speter	   set.  */
15525839Speter	buf = xmalloc (100
15625839Speter		       + strlen (mname)
15725839Speter		       + strlen (msg)
15825839Speter		       + (where ? strlen (where) : 0)
15925839Speter		       + (extra_arg ? strlen (extra_arg) : 0));
160128266Speter	sprintf (buf, "%s-> my_module (%s, %s, %s, %s)\n",
16154427Speter		 CLIENT_SERVER_STR,
16217721Speter		 mname, msg, where ? where : "",
16317721Speter		 extra_arg ? extra_arg : "");
16425839Speter	cvs_outerr (buf, 0);
16525839Speter	free (buf);
16625839Speter    }
16717721Speter#endif
16817721Speter
169124793Snectar    /* Don't process absolute directories.  Anything else could be a security
170124793Snectar     * problem.  Before this check was put in place:
171124793Snectar     *
172124793Snectar     *   $ cvs -d:fork:/cvsroot co /foo
173124793Snectar     *   cvs server: warning: cannot make directory CVS in /: Permission denied
174124793Snectar     *   cvs [server aborted]: cannot make directory /foo: Permission denied
175124793Snectar     *   $
176124793Snectar     */
177124793Snectar    if (isabsolute (mname))
178124793Snectar	error (1, 0, "Absolute module reference invalid: `%s'", mname);
179124793Snectar
180128266Speter    /* Similarly for directories that attempt to step above the root of the
181128266Speter     * repository.
182128266Speter     */
183128266Speter    if (pathname_levels (mname) > 0)
184128266Speter	error (1, 0, "up-level in module reference (`..') invalid: `%s'.",
185128266Speter               mname);
186128266Speter
18717721Speter    /* if this is a directory to ignore, add it to that list */
18817721Speter    if (mname[0] == '!' && mname[1] != '\0')
18917721Speter    {
19017721Speter	ign_dir_add (mname+1);
19125839Speter	goto do_module_return;
19217721Speter    }
19317721Speter
19417721Speter    /* strip extra stuff from the module name */
19525839Speter    strip_trailing_slashes (mname);
19617721Speter
19717721Speter    /*
19817721Speter     * Look up the module using the following scheme:
19917721Speter     *	1) look for mname as a module name
20017721Speter     *	2) look for mname as a directory
20117721Speter     *	3) look for mname as a file
20217721Speter     *  4) take mname up to the first slash and look it up as a module name
20317721Speter     *	   (this is for checking out only part of a module)
20417721Speter     */
20517721Speter
20617721Speter    /* look it up as a module name */
20717721Speter    key.dptr = mname;
20817721Speter    key.dsize = strlen (key.dptr);
20917721Speter    if (db != NULL)
21017721Speter	val = dbm_fetch (db, key);
21117721Speter    else
21217721Speter	val.dptr = NULL;
21317721Speter    if (val.dptr != NULL)
21417721Speter    {
21566525Speter	/* copy and null terminate the value */
21666525Speter	value = xmalloc (val.dsize + 1);
21766525Speter	memcpy (value, val.dptr, val.dsize);
21866525Speter	value[val.dsize] = '\0';
21917721Speter
22017721Speter	/* If the line ends in a comment, strip it off */
22166525Speter	if ((cp = strchr (value, '#')) != NULL)
22266525Speter	    *cp = '\0';
22317721Speter	else
22466525Speter	    cp = value + val.dsize;
22517721Speter
22666525Speter	/* Always strip trailing spaces */
22766525Speter	while (cp > value && isspace ((unsigned char) *--cp))
22866525Speter	    *cp = '\0';
22966525Speter
23017721Speter	mwhere = xstrdup (mname);
23117721Speter	goto found;
23217721Speter    }
23317721Speter    else
23417721Speter    {
23525839Speter	char *file;
23625839Speter	char *attic_file;
23717721Speter	char *acp;
23825839Speter	int is_found = 0;
23917721Speter
24017721Speter	/* check to see if mname is a directory or file */
24181404Speter	file = xmalloc (strlen (current_parsed_root->directory)
24281404Speter			+ strlen (mname) + sizeof(RCSEXT) + 2);
24381404Speter	(void) sprintf (file, "%s/%s", current_parsed_root->directory, mname);
24481404Speter	attic_file = xmalloc (strlen (current_parsed_root->directory)
24581404Speter			      + strlen (mname)
24681404Speter			      + sizeof (CVSATTIC) + sizeof (RCSEXT) + 3);
24717721Speter	if ((acp = strrchr (mname, '/')) != NULL)
24817721Speter	{
24917721Speter	    *acp = '\0';
25081404Speter	    (void) sprintf (attic_file, "%s/%s/%s/%s%s", current_parsed_root->directory,
25125839Speter			    mname, CVSATTIC, acp + 1, RCSEXT);
25217721Speter	    *acp = '/';
25317721Speter	}
25417721Speter	else
255128266Speter	    (void) sprintf (attic_file, "%s/%s/%s%s",
256128266Speter	                    current_parsed_root->directory,
25725839Speter			    CVSATTIC, mname, RCSEXT);
25817721Speter
25917721Speter	if (isdir (file))
26017721Speter	{
26154427Speter	    modargv = xmalloc (sizeof (*modargv));
26254427Speter	    modargv[0] = xstrdup (mname);
26354427Speter	    modargc = 1;
26425839Speter	    is_found = 1;
26517721Speter	}
26617721Speter	else
26717721Speter	{
26817721Speter	    (void) strcat (file, RCSEXT);
26917721Speter	    if (isfile (file) || isfile (attic_file))
27017721Speter	    {
27117721Speter		/* if mname was a file, we have to split it into "dir file" */
27217721Speter		if ((cp = strrchr (mname, '/')) != NULL && cp != mname)
27317721Speter		{
27454427Speter		    modargv = xmalloc (2 * sizeof (*modargv));
27554427Speter		    modargv[0] = xmalloc (strlen (mname) + 2);
27654427Speter		    strncpy (modargv[0], mname, cp - mname);
27754427Speter		    modargv[0][cp - mname] = '\0';
27854427Speter		    modargv[1] = xstrdup (cp + 1);
27954427Speter		    modargc = 2;
28017721Speter		}
28117721Speter		else
28217721Speter		{
28317721Speter		    /*
28417721Speter		     * the only '/' at the beginning or no '/' at all
28517721Speter		     * means the file we are interested in is in CVSROOT
28617721Speter		     * itself so the directory should be '.'
28717721Speter		     */
28817721Speter		    if (cp == mname)
28917721Speter		    {
29017721Speter			/* drop the leading / if specified */
29154427Speter			modargv = xmalloc (2 * sizeof (*modargv));
29254427Speter			modargv[0] = xstrdup (".");
29354427Speter			modargv[1] = xstrdup (mname + 1);
29454427Speter			modargc = 2;
29517721Speter		    }
29617721Speter		    else
29717721Speter		    {
29817721Speter			/* otherwise just copy it */
29954427Speter			modargv = xmalloc (2 * sizeof (*modargv));
30054427Speter			modargv[0] = xstrdup (".");
30154427Speter			modargv[1] = xstrdup (mname);
30254427Speter			modargc = 2;
30317721Speter		    }
30417721Speter		}
30525839Speter		is_found = 1;
30617721Speter	    }
30717721Speter	}
30825839Speter	free (attic_file);
30925839Speter	free (file);
31025839Speter
31125839Speter	if (is_found)
31254427Speter	{
31354427Speter	    assert (value == NULL);
31454427Speter
31554427Speter	    /* OK, we have now set up modargv with the actual
31654427Speter	       file/directory we want to work on.  We duplicate a
31754427Speter	       small amount of code here because the vast majority of
31854427Speter	       the code after the "found" label does not pertain to
31954427Speter	       the case where we found a file/directory rather than
32054427Speter	       finding an entry in the modules file.  */
32154427Speter	    if (save_cwd (&cwd))
32254427Speter		error_exit ();
32354427Speter	    cwd_saved = 1;
32454427Speter
32566525Speter	    err += callback_proc (modargc, modargv, where, mwhere, mfile,
32654427Speter				  shorten,
32754427Speter				  local_specified, mname, msg);
32854427Speter
32954427Speter	    free_names (&modargc, modargv);
33054427Speter
33154427Speter	    /* cd back to where we started.  */
33254427Speter	    if (restore_cwd (&cwd, NULL))
33354427Speter		error_exit ();
33454427Speter	    free_cwd (&cwd);
33554427Speter	    cwd_saved = 0;
33654427Speter
33754427Speter	    goto do_module_return;
33854427Speter	}
33917721Speter    }
34017721Speter
34117721Speter    /* look up everything to the first / as a module */
34217721Speter    if (mname[0] != '/' && (cp = strchr (mname, '/')) != NULL)
34317721Speter    {
34417721Speter	/* Make the slash the new end of the string temporarily */
34517721Speter	*cp = '\0';
34617721Speter	key.dptr = mname;
34717721Speter	key.dsize = strlen (key.dptr);
34817721Speter
34917721Speter	/* do the lookup */
35017721Speter	if (db != NULL)
35117721Speter	    val = dbm_fetch (db, key);
35217721Speter	else
35317721Speter	    val.dptr = NULL;
35417721Speter
35517721Speter	/* if we found it, clean up the value and life is good */
35617721Speter	if (val.dptr != NULL)
35717721Speter	{
35817721Speter	    char *cp2;
35917721Speter
36066525Speter	    /* copy and null terminate the value */
36166525Speter	    value = xmalloc (val.dsize + 1);
36266525Speter	    memcpy (value, val.dptr, val.dsize);
36366525Speter	    value[val.dsize] = '\0';
36417721Speter
36517721Speter	    /* If the line ends in a comment, strip it off */
36666525Speter	    if ((cp2 = strchr (value, '#')) != NULL)
36766525Speter		*cp2 = '\0';
36866525Speter	    else
36966525Speter		cp2 = value + val.dsize;
37017721Speter
37166525Speter	    /* Always strip trailing spaces */
37266525Speter	    while (cp2 > value  &&  isspace ((unsigned char) *--cp2))
37366525Speter		*cp2 = '\0';
37466525Speter
37517721Speter	    /* mwhere gets just the module name */
37617721Speter	    mwhere = xstrdup (mname);
37717721Speter	    mfile = cp + 1;
378175261Sobrien	    assert (strlen (mfile));
37917721Speter
38017721Speter	    /* put the / back in mname */
38117721Speter	    *cp = '/';
38217721Speter
38317721Speter	    goto found;
38417721Speter	}
38517721Speter
38617721Speter	/* put the / back in mname */
38717721Speter	*cp = '/';
38817721Speter    }
38917721Speter
39017721Speter    /* if we got here, we couldn't find it using our search, so give up */
39117721Speter    error (0, 0, "cannot find module `%s' - ignored", mname);
39217721Speter    err++;
39325839Speter    goto do_module_return;
39417721Speter
39517721Speter
39617721Speter    /*
39717721Speter     * At this point, we found what we were looking for in one
39817721Speter     * of the many different forms.
39917721Speter     */
40017721Speter  found:
40117721Speter
40217721Speter    /* remember where we start */
40317721Speter    if (save_cwd (&cwd))
40425839Speter	error_exit ();
40525839Speter    cwd_saved = 1;
40617721Speter
40754427Speter    assert (value != NULL);
40817721Speter
40917721Speter    /* search the value for the special delimiter and save for later */
41017721Speter    if ((cp = strchr (value, CVSMODULE_SPEC)) != NULL)
41117721Speter    {
41217721Speter	*cp = '\0';			/* null out the special char */
41317721Speter	spec_opt = cp + 1;		/* save the options for later */
41417721Speter
41566525Speter	/* strip whitespace if necessary */
41666525Speter	while (cp > value  &&  isspace ((unsigned char) *--cp))
41766525Speter	    *cp = '\0';
41817721Speter    }
41917721Speter
42017721Speter    /* don't do special options only part of a module was specified */
42117721Speter    if (mfile != NULL)
42217721Speter	spec_opt = NULL;
42317721Speter
42417721Speter    /*
42517721Speter     * value now contains one of the following:
42617721Speter     *    1) dir
42717721Speter     *	  2) dir file
42817721Speter     *    3) the value from modules without any special args
42917721Speter     *		    [ args ] dir [file] [file] ...
43017721Speter     *	     or     -a module [ module ] ...
43117721Speter     */
43217721Speter
43317721Speter    /* Put the value on a line with XXX prepended for getopt to eat */
43481404Speter    line = xmalloc (strlen (value) + 5);
43566525Speter    strcpy(line, "XXX ");
43666525Speter    strcpy(line + 4, value);
43717721Speter
43817721Speter    /* turn the line into an argv[] array */
43932785Speter    line2argv (&xmodargc, &xmodargv, line, " \t");
44017721Speter    free (line);
44117721Speter    modargc = xmodargc;
44217721Speter    modargv = xmodargv;
44317721Speter
44417721Speter    /* parse the args */
44526065Speter    optind = 0;
44617721Speter    while ((c = getopt (modargc, modargv, CVSMODULE_OPTS)) != -1)
44717721Speter    {
44817721Speter	switch (c)
44917721Speter	{
45017721Speter	    case 'a':
45117721Speter		alias = 1;
45217721Speter		break;
45317721Speter	    case 'd':
45417721Speter		if (mwhere)
45517721Speter		    free (mwhere);
45617721Speter		mwhere = xstrdup (optarg);
45766525Speter		nonalias_opt = 1;
45817721Speter		break;
45917721Speter	    case 'l':
46066525Speter		local_specified = 1;
46125839Speter		nonalias_opt = 1;
46217721Speter		break;
46317721Speter	    case 'o':
46426065Speter		if (checkout_prog)
46526065Speter		    free (checkout_prog);
46626065Speter		checkout_prog = xstrdup (optarg);
46766525Speter		nonalias_opt = 1;
46817721Speter		break;
46917721Speter	    case 'e':
47026065Speter		if (export_prog)
47126065Speter		    free (export_prog);
47226065Speter		export_prog = xstrdup (optarg);
47366525Speter		nonalias_opt = 1;
47417721Speter		break;
47517721Speter	    case 't':
47626065Speter		if (tag_prog)
47726065Speter		    free (tag_prog);
47826065Speter		tag_prog = xstrdup (optarg);
47966525Speter		nonalias_opt = 1;
48017721Speter		break;
48117721Speter	    case '?':
48217721Speter		error (0, 0,
48317721Speter		       "modules file has invalid option for key %s value %s",
48466525Speter		       key.dptr, value);
48517721Speter		err++;
48625839Speter		goto do_module_return;
48717721Speter	}
48817721Speter    }
48917721Speter    modargc -= optind;
49017721Speter    modargv += optind;
49166525Speter    if (modargc == 0  &&  spec_opt == NULL)
49217721Speter    {
49317721Speter	error (0, 0, "modules file missing directory for module %s", mname);
49425839Speter	++err;
49525839Speter	goto do_module_return;
49617721Speter    }
49717721Speter
49825839Speter    if (alias && nonalias_opt)
49925839Speter    {
50025839Speter	/* The documentation has never said it is legal to specify
50125839Speter	   -a along with another option.  And I believe that in the past
50225839Speter	   CVS has ignored the options other than -a, more or less, in this
50325839Speter	   situation.  */
50425839Speter	error (0, 0, "\
50525839Speter-a cannot be specified in the modules file along with other options");
50625839Speter	++err;
50725839Speter	goto do_module_return;
50825839Speter    }
50925839Speter
51017721Speter    /* if this was an alias, call ourselves recursively for each module */
51117721Speter    if (alias)
51217721Speter    {
51317721Speter	int i;
51417721Speter
51517721Speter	for (i = 0; i < modargc; i++)
51617721Speter	{
517128266Speter	    /*
518128266Speter	     * Recursion check: if an alias module calls itself or a module
519128266Speter	     * which causes the first to be called again, print an error
520128266Speter	     * message and stop recursing.
521128266Speter	     *
522128266Speter	     * Algorithm:
523128266Speter	     *
524128266Speter	     *   1. Check that MNAME isn't in the stack.
525128266Speter	     *   2. Push MNAME onto the stack.
526128266Speter	     *   3. Call do_module().
527128266Speter	     *   4. Pop MNAME from the stack.
528128266Speter	     */
529128266Speter	    if (stack && findnode (stack, mname))
53017721Speter		error (0, 0,
53117721Speter		       "module `%s' in modules file contains infinite loop",
53217721Speter		       mname);
53317721Speter	    else
534128266Speter	    {
535128266Speter		if (!stack) stack = getlist();
536128266Speter		push_string (stack, mname);
537128266Speter		err += my_module (db, modargv[i], m_type, msg, callback_proc,
538128266Speter                                   where, shorten, local_specified,
539128266Speter                                   run_module_prog, build_dirs, extra_arg,
540128266Speter                                   stack);
541128266Speter		pop_string (stack);
542128266Speter		if (isempty (stack)) dellist (&stack);
543128266Speter	    }
54417721Speter	}
54525839Speter	goto do_module_return;
54617721Speter    }
54717721Speter
54832785Speter    if (mfile != NULL && modargc > 1)
54932785Speter    {
55032785Speter	error (0, 0, "\
55132785Spetermodule `%s' is a request for a file in a module which is not a directory",
55232785Speter	       mname);
55332785Speter	++err;
55432785Speter	goto do_module_return;
55532785Speter    }
55632785Speter
55717721Speter    /* otherwise, process this module */
55866525Speter    if (modargc > 0)
55966525Speter    {
56066525Speter	err += callback_proc (modargc, modargv, where, mwhere, mfile, shorten,
56166525Speter			      local_specified, mname, msg);
56266525Speter    }
56366525Speter    else
56466525Speter    {
56566525Speter	/*
56666525Speter	 * we had nothing but special options, so we must
56766525Speter	 * make the appropriate directory and cd to it
56866525Speter	 */
56966525Speter	char *dir;
57017721Speter
57181404Speter	if (!build_dirs)
57266525Speter	    goto do_special;
57317721Speter
57466525Speter	dir = where ? where : (mwhere ? mwhere : mname);
57566525Speter	/* XXX - think about making null repositories at each dir here
57666525Speter		 instead of just at the bottom */
57766525Speter	make_directories (dir);
578128266Speter	if (CVS_CHDIR (dir) < 0)
57966525Speter	{
58066525Speter	    error (0, errno, "cannot chdir to %s", dir);
58166525Speter	    spec_opt = NULL;
58266525Speter	    err++;
58366525Speter	    goto do_special;
58466525Speter	}
58566525Speter	if (!isfile (CVSADM))
58666525Speter	{
58766525Speter	    char *nullrepos;
58866525Speter
58966525Speter	    nullrepos = emptydir_name ();
59066525Speter
59166525Speter	    Create_Admin (".", dir,
59266525Speter			  nullrepos, (char *) NULL, (char *) NULL, 0, 0, 1);
59366525Speter	    if (!noexec)
59466525Speter	    {
59566525Speter		FILE *fp;
59666525Speter
59766525Speter		fp = open_file (CVSADM_ENTSTAT, "w+");
59866525Speter		if (fclose (fp) == EOF)
59966525Speter		    error (1, errno, "cannot close %s", CVSADM_ENTSTAT);
60066525Speter#ifdef SERVER_SUPPORT
60166525Speter		if (server_active)
60266525Speter		    server_set_entstat (dir, nullrepos);
60366525Speter#endif
60466525Speter	    }
60566525Speter	    free (nullrepos);
60666525Speter	}
60766525Speter    }
60866525Speter
60917721Speter    /* if there were special include args, process them now */
61017721Speter
61117721Speter  do_special:
61217721Speter
61366525Speter    free_names (&xmodargc, xmodargv);
61466525Speter    xmodargv = NULL;
61566525Speter
61617721Speter    /* blow off special options if -l was specified */
61717721Speter    if (local_specified)
61817721Speter	spec_opt = NULL;
61917721Speter
62025839Speter#ifdef SERVER_SUPPORT
62125839Speter    /* We want to check out into the directory named by the module.
62225839Speter       So we set a global variable which tells the server to glom that
62325839Speter       directory name onto the front.  A cleaner approach would be some
62425839Speter       way of passing it down to the recursive call, through the
62525839Speter       callback_proc, to start_recursion, and then into the update_dir in
62625839Speter       the struct file_info.  That way the "Updating foo" message could
62725839Speter       print the actual directory we are checking out into.
62825839Speter
62925839Speter       For local CVS, this is handled by the chdir call above
63025839Speter       (directly or via the callback_proc).  */
63125839Speter    if (server_active && spec_opt != NULL)
63225839Speter    {
63325839Speter	char *change_to;
63425839Speter
63525839Speter	change_to = where ? where : (mwhere ? mwhere : mname);
63625839Speter	server_dir_to_restore = server_dir;
63725839Speter	restore_server_dir = 1;
63825839Speter	server_dir =
63925839Speter	    xmalloc ((server_dir_to_restore != NULL
64025839Speter		      ? strlen (server_dir_to_restore)
64125839Speter		      : 0)
64225839Speter		     + strlen (change_to)
64325839Speter		     + 5);
64425839Speter	server_dir[0] = '\0';
64525839Speter	if (server_dir_to_restore != NULL)
64625839Speter	{
64725839Speter	    strcat (server_dir, server_dir_to_restore);
64825839Speter	    strcat (server_dir, "/");
64925839Speter	}
65025839Speter	strcat (server_dir, change_to);
65125839Speter    }
65225839Speter#endif
65325839Speter
65417721Speter    while (spec_opt != NULL)
65517721Speter    {
65617721Speter	char *next_opt;
65717721Speter
65817721Speter	cp = strchr (spec_opt, CVSMODULE_SPEC);
65917721Speter	if (cp != NULL)
66017721Speter	{
66117721Speter	    /* save the beginning of the next arg */
66217721Speter	    next_opt = cp + 1;
66317721Speter
66417721Speter	    /* strip whitespace off the end */
66517721Speter	    do
66617721Speter		*cp = '\0';
66766525Speter	    while (cp > spec_opt  &&  isspace ((unsigned char) *--cp));
66817721Speter	}
66917721Speter	else
67017721Speter	    next_opt = NULL;
67117721Speter
67217721Speter	/* strip whitespace from front */
67354427Speter	while (isspace ((unsigned char) *spec_opt))
67417721Speter	    spec_opt++;
67517721Speter
67617721Speter	if (*spec_opt == '\0')
67717721Speter	    error (0, 0, "Mal-formed %c option for module %s - ignored",
67817721Speter		   CVSMODULE_SPEC, mname);
67917721Speter	else
680128266Speter	    err += my_module (db, spec_opt, m_type, msg, callback_proc,
681128266Speter                               (char *) NULL, 0, local_specified,
682128266Speter                               run_module_prog, build_dirs, extra_arg,
683128266Speter	                       stack);
68417721Speter	spec_opt = next_opt;
68517721Speter    }
68617721Speter
68725839Speter#ifdef SERVER_SUPPORT
68825839Speter    if (server_active && restore_server_dir)
68925839Speter    {
69025839Speter	free (server_dir);
69125839Speter	server_dir = server_dir_to_restore;
69225839Speter    }
69325839Speter#endif
69425839Speter
69517721Speter    /* cd back to where we started */
69617721Speter    if (restore_cwd (&cwd, NULL))
69725839Speter	error_exit ();
69817721Speter    free_cwd (&cwd);
69925839Speter    cwd_saved = 0;
70017721Speter
70117721Speter    /* run checkout or tag prog if appropriate */
70217721Speter    if (err == 0 && run_module_prog)
70317721Speter    {
70417721Speter	if ((m_type == TAG && tag_prog != NULL) ||
70517721Speter	    (m_type == CHECKOUT && checkout_prog != NULL) ||
70617721Speter	    (m_type == EXPORT && export_prog != NULL))
70717721Speter	{
70817721Speter	    /*
70917721Speter	     * If a relative pathname is specified as the checkout, tag
71017721Speter	     * or export proc, try to tack on the current "where" value.
71117721Speter	     * if we can't find a matching program, just punt and use
71217721Speter	     * whatever is specified in the modules file.
71317721Speter	     */
71425839Speter	    char *real_prog = NULL;
71517721Speter	    char *prog = (m_type == TAG ? tag_prog :
71617721Speter			  (m_type == CHECKOUT ? checkout_prog : export_prog));
71717721Speter	    char *real_where = (where != NULL ? where : mwhere);
71825839Speter	    char *expanded_path;
71917721Speter
72017721Speter	    if ((*prog != '/') && (*prog != '.'))
72117721Speter	    {
72225839Speter		real_prog = xmalloc (strlen (real_where) + strlen (prog)
72325839Speter				     + 10);
72417721Speter		(void) sprintf (real_prog, "%s/%s", real_where, prog);
72517721Speter		if (isfile (real_prog))
72617721Speter		    prog = real_prog;
72717721Speter	    }
72817721Speter
72925839Speter	    /* XXX can we determine the line number for this entry??? */
73025839Speter	    expanded_path = expand_path (prog, "modules", 0);
73125839Speter	    if (expanded_path != NULL)
73225839Speter	    {
73332785Speter		run_setup (expanded_path);
73432785Speter		run_arg (real_where);
73517721Speter
73625839Speter		if (extra_arg)
73725839Speter		    run_arg (extra_arg);
73825839Speter
73925839Speter		if (!quiet)
74025839Speter		{
74125839Speter		    cvs_output (program_name, 0);
74225839Speter		    cvs_output (" ", 1);
743128266Speter		    cvs_output (cvs_cmd_name, 0);
74425839Speter		    cvs_output (": Executing '", 0);
74525839Speter		    run_print (stdout);
74625839Speter		    cvs_output ("'\n", 0);
74766525Speter		    cvs_flushout ();
74825839Speter		}
74925839Speter		err += run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
75025839Speter		free (expanded_path);
75117721Speter	    }
752175261Sobrien	    if (real_prog) free (real_prog);
75317721Speter	}
75417721Speter    }
75517721Speter
75625839Speter do_module_return:
75717721Speter    /* clean up */
75866525Speter    if (xmodargv != NULL)
75966525Speter	free_names (&xmodargc, xmodargv);
76017721Speter    if (mwhere)
76117721Speter	free (mwhere);
76226065Speter    if (checkout_prog)
76326065Speter	free (checkout_prog);
76426065Speter    if (export_prog)
76526065Speter	free (export_prog);
76626065Speter    if (tag_prog)
76726065Speter	free (tag_prog);
76825839Speter    if (cwd_saved)
76925839Speter	free_cwd (&cwd);
77066525Speter    if (value != NULL)
77166525Speter	free (value);
77217721Speter
77317721Speter    return (err);
77417721Speter}
77517721Speter
776128266Speter
777128266Speter
778128266Speter/* External face of do_module so that we can have an internal version which
779128266Speter * accepts a stack argument to track alias recursion.
780128266Speter */
781128266Speterint
782128266Speterdo_module (db, mname, m_type, msg, callback_proc, where, shorten,
783128266Speter	   local_specified, run_module_prog, build_dirs, extra_arg)
784128266Speter    DBM *db;
785128266Speter    char *mname;
786128266Speter    enum mtype m_type;
787128266Speter    char *msg;
788128266Speter    CALLBACKPROC callback_proc;
789128266Speter    char *where;
790128266Speter    int shorten;
791128266Speter    int local_specified;
792128266Speter    int run_module_prog;
793128266Speter    int build_dirs;
794128266Speter    char *extra_arg;
795128266Speter{
796128266Speter    return my_module (db, mname, m_type, msg, callback_proc, where, shorten,
797128266Speter                       local_specified, run_module_prog, build_dirs, extra_arg,
798128266Speter                       NULL);
799128266Speter}
800128266Speter
801128266Speter
802128266Speter
80317721Speter/* - Read all the records from the modules database into an array.
80417721Speter   - Sort the array depending on what format is desired.
80517721Speter   - Print the array in the format desired.
80617721Speter
80717721Speter   Currently, there are only two "desires":
80817721Speter
80917721Speter   1. Sort by module name and format the whole entry including switches,
81017721Speter      files and the comment field: (Including aliases)
81117721Speter
81217721Speter      modulename	-s switches, one per line, even if
813128266Speter			it has many switches.
81417721Speter			Directories and files involved, formatted
81517721Speter			to cover multiple lines if necessary.
81617721Speter			# Comment, also formatted to cover multiple
81717721Speter			# lines if necessary.
81817721Speter
81917721Speter   2. Sort by status field string and print:  (*not* including aliases)
82017721Speter
82117721Speter      modulename    STATUS	Directories and files involved, formatted
82217721Speter				to cover multiple lines if necessary.
82317721Speter				# Comment, also formatted to cover multiple
82417721Speter				# lines if necessary.
82517721Speter*/
82617721Speter
82717721Speterstatic struct sortrec *s_head;
82817721Speter
82917721Speterstatic int s_max = 0;			/* Number of elements allocated */
83017721Speterstatic int s_count = 0;			/* Number of elements used */
83117721Speter
83217721Speterstatic int Status;		        /* Nonzero if the user is
83317721Speter					   interested in status
83417721Speter					   information as well as
83517721Speter					   module name */
83617721Speterstatic char def_status[] = "NONE";
83717721Speter
83817721Speter/* Sort routine for qsort:
83917721Speter   - If we want the "Status" field to be sorted, check it first.
84017721Speter   - Then compare the "module name" fields.  Since they are unique, we don't
84117721Speter     have to look further.
84217721Speter*/
84317721Speterstatic int
84417721Spetersort_order (l, r)
84517721Speter    const PTR l;
84617721Speter    const PTR r;
84717721Speter{
84817721Speter    int i;
84917721Speter    const struct sortrec *left = (const struct sortrec *) l;
85017721Speter    const struct sortrec *right = (const struct sortrec *) r;
85117721Speter
85217721Speter    if (Status)
85317721Speter    {
85417721Speter	/* If Sort by status field, compare them. */
85517721Speter	if ((i = strcmp (left->status, right->status)) != 0)
85617721Speter	    return (i);
85717721Speter    }
85817721Speter    return (strcmp (left->modname, right->modname));
85917721Speter}
86017721Speter
86117721Speterstatic void
86217721Spetersave_d (k, ks, d, ds)
86317721Speter    char *k;
86417721Speter    int ks;
86517721Speter    char *d;
86617721Speter    int ds;
86717721Speter{
86817721Speter    char *cp, *cp2;
86917721Speter    struct sortrec *s_rec;
87017721Speter
87117721Speter    if (Status && *d == '-' && *(d + 1) == 'a')
87217721Speter	return;				/* We want "cvs co -s" and it is an alias! */
87317721Speter
87417721Speter    if (s_count == s_max)
87517721Speter    {
87617721Speter	s_max += 64;
87717721Speter	s_head = (struct sortrec *) xrealloc ((char *) s_head, s_max * sizeof (*s_head));
87817721Speter    }
87917721Speter    s_rec = &s_head[s_count];
88017721Speter    s_rec->modname = cp = xmalloc (ks + 1);
88117721Speter    (void) strncpy (cp, k, ks);
88217721Speter    *(cp + ks) = '\0';
88317721Speter
88417721Speter    s_rec->rest = cp2 = xmalloc (ds + 1);
88517721Speter    cp = d;
88617721Speter    *(cp + ds) = '\0';	/* Assumes an extra byte at end of static dbm buffer */
88717721Speter
88854427Speter    while (isspace ((unsigned char) *cp))
88917721Speter	cp++;
89017721Speter    /* Turn <spaces> into one ' ' -- makes the rest of this routine simpler */
89117721Speter    while (*cp)
89217721Speter    {
89354427Speter	if (isspace ((unsigned char) *cp))
89417721Speter	{
89517721Speter	    *cp2++ = ' ';
89654427Speter	    while (isspace ((unsigned char) *cp))
89717721Speter		cp++;
89817721Speter	}
89917721Speter	else
90017721Speter	    *cp2++ = *cp++;
90117721Speter    }
90217721Speter    *cp2 = '\0';
90317721Speter
90417721Speter    /* Look for the "-s statusvalue" text */
90517721Speter    if (Status)
90617721Speter    {
90717721Speter	s_rec->status = def_status;
90817721Speter
90917721Speter	for (cp = s_rec->rest; (cp2 = strchr (cp, '-')) != NULL; cp = ++cp2)
91017721Speter	{
91117721Speter	    if (*(cp2 + 1) == 's' && *(cp2 + 2) == ' ')
91217721Speter	    {
91332785Speter		char *status_start;
91432785Speter
91532785Speter		cp2 += 3;
91632785Speter		status_start = cp2;
91732785Speter		while (*cp2 != ' ' && *cp2 != '\0')
91817721Speter		    cp2++;
91932785Speter		s_rec->status = xmalloc (cp2 - status_start + 1);
92032785Speter		strncpy (s_rec->status, status_start, cp2 - status_start);
92132785Speter		s_rec->status[cp2 - status_start] = '\0';
92217721Speter		cp = cp2;
92317721Speter		break;
92417721Speter	    }
92517721Speter	}
92617721Speter    }
92717721Speter    else
92817721Speter	cp = s_rec->rest;
92917721Speter
93017721Speter    /* Find comment field, clean up on all three sides & compress blanks */
93117721Speter    if ((cp2 = cp = strchr (cp, '#')) != NULL)
93217721Speter    {
93317721Speter	if (*--cp2 == ' ')
93417721Speter	    *cp2 = '\0';
93517721Speter	if (*++cp == ' ')
93617721Speter	    cp++;
93717721Speter	s_rec->comment = cp;
93817721Speter    }
93917721Speter    else
94017721Speter	s_rec->comment = "";
94117721Speter
94217721Speter    s_count++;
94317721Speter}
94417721Speter
94517721Speter/* Print out the module database as we know it.  If STATUS is
94617721Speter   non-zero, print out status information for each module. */
94717721Speter
94817721Spetervoid
94917721Spetercat_module (status)
95017721Speter    int status;
95117721Speter{
95217721Speter    DBM *db;
95317721Speter    datum key, val;
95417721Speter    int i, c, wid, argc, cols = 80, indent, fill;
95517721Speter    int moduleargc;
95617721Speter    struct sortrec *s_h;
95717721Speter    char *cp, *cp2, **argv;
95825839Speter    char **moduleargv;
95917721Speter
96017721Speter    Status = status;
96117721Speter
96217721Speter    /* Read the whole modules file into allocated records */
96317721Speter    if (!(db = open_module ()))
96417721Speter	error (1, 0, "failed to open the modules file");
96517721Speter
96617721Speter    for (key = dbm_firstkey (db); key.dptr != NULL; key = dbm_nextkey (db))
96717721Speter    {
96817721Speter	val = dbm_fetch (db, key);
96917721Speter	if (val.dptr != NULL)
97017721Speter	    save_d (key.dptr, key.dsize, val.dptr, val.dsize);
97117721Speter    }
97217721Speter
97325839Speter    close_module (db);
97425839Speter
97517721Speter    /* Sort the list as requested */
97617721Speter    qsort ((PTR) s_head, s_count, sizeof (struct sortrec), sort_order);
97717721Speter
97817721Speter    /*
97917721Speter     * Run through the sorted array and format the entries
98017721Speter     * indent = space for modulename + space for status field
98117721Speter     */
98217721Speter    indent = 12 + (status * 12);
98317721Speter    fill = cols - (indent + 2);
98417721Speter    for (s_h = s_head, i = 0; i < s_count; i++, s_h++)
98517721Speter    {
98625839Speter	char *line;
98725839Speter
98817721Speter	/* Print module name (and status, if wanted) */
98925839Speter	line = xmalloc (strlen (s_h->modname) + 15);
99025839Speter	sprintf (line, "%-12s", s_h->modname);
99125839Speter	cvs_output (line, 0);
99225839Speter	free (line);
99317721Speter	if (status)
99417721Speter	{
99525839Speter	    line = xmalloc (strlen (s_h->status) + 15);
99625839Speter	    sprintf (line, " %-11s", s_h->status);
99725839Speter	    cvs_output (line, 0);
99825839Speter	    free (line);
99917721Speter	}
100017721Speter
100125839Speter	line = xmalloc (strlen (s_h->modname) + strlen (s_h->rest) + 15);
100217721Speter	/* Parse module file entry as command line and print options */
100317721Speter	(void) sprintf (line, "%s %s", s_h->modname, s_h->rest);
100432785Speter	line2argv (&moduleargc, &moduleargv, line, " \t");
100517721Speter	free (line);
100617721Speter	argc = moduleargc;
100717721Speter	argv = moduleargv;
100817721Speter
100917721Speter	optind = 0;
101017721Speter	wid = 0;
101117721Speter	while ((c = getopt (argc, argv, CVSMODULE_OPTS)) != -1)
101217721Speter	{
101317721Speter	    if (!status)
101417721Speter	    {
101517721Speter		if (c == 'a' || c == 'l')
101617721Speter		{
101725839Speter		    char buf[5];
101825839Speter
101925839Speter		    sprintf (buf, " -%c", c);
102025839Speter		    cvs_output (buf, 0);
102117721Speter		    wid += 3;		/* Could just set it to 3 */
102217721Speter		}
102317721Speter		else
102417721Speter		{
102525839Speter		    char buf[10];
102625839Speter
102717721Speter		    if (strlen (optarg) + 4 + wid > (unsigned) fill)
102817721Speter		    {
102925839Speter			int j;
103025839Speter
103125839Speter			cvs_output ("\n", 1);
103225839Speter			for (j = 0; j < indent; ++j)
103325839Speter			    cvs_output (" ", 1);
103417721Speter			wid = 0;
103517721Speter		    }
103625839Speter		    sprintf (buf, " -%c ", c);
103725839Speter		    cvs_output (buf, 0);
103825839Speter		    cvs_output (optarg, 0);
103917721Speter		    wid += strlen (optarg) + 4;
104017721Speter		}
104117721Speter	    }
104217721Speter	}
104317721Speter	argc -= optind;
104417721Speter	argv += optind;
104517721Speter
104617721Speter	/* Format and Print all the files and directories */
104717721Speter	for (; argc--; argv++)
104817721Speter	{
104917721Speter	    if (strlen (*argv) + wid > (unsigned) fill)
105017721Speter	    {
105125839Speter		int j;
105225839Speter
105325839Speter		cvs_output ("\n", 1);
105425839Speter		for (j = 0; j < indent; ++j)
105525839Speter		    cvs_output (" ", 1);
105617721Speter		wid = 0;
105717721Speter	    }
105825839Speter	    cvs_output (" ", 1);
105925839Speter	    cvs_output (*argv, 0);
106017721Speter	    wid += strlen (*argv) + 1;
106117721Speter	}
106225839Speter	cvs_output ("\n", 1);
106317721Speter
106417721Speter	/* Format the comment field -- save_d (), compressed spaces */
106517721Speter	for (cp2 = cp = s_h->comment; *cp; cp2 = cp)
106617721Speter	{
106725839Speter	    int j;
106825839Speter
106925839Speter	    for (j = 0; j < indent; ++j)
107025839Speter		cvs_output (" ", 1);
107125839Speter	    cvs_output (" # ", 0);
107217721Speter	    if (strlen (cp2) < (unsigned) (fill - 2))
107317721Speter	    {
107425839Speter		cvs_output (cp2, 0);
107525839Speter		cvs_output ("\n", 1);
107617721Speter		break;
107717721Speter	    }
107817721Speter	    cp += fill - 2;
107917721Speter	    while (*cp != ' ' && cp > cp2)
108017721Speter		cp--;
108117721Speter	    if (cp == cp2)
108217721Speter	    {
108325839Speter		cvs_output (cp2, 0);
108425839Speter		cvs_output ("\n", 1);
108517721Speter		break;
108617721Speter	    }
108717721Speter
108817721Speter	    *cp++ = '\0';
108925839Speter	    cvs_output (cp2, 0);
109025839Speter	    cvs_output ("\n", 1);
109117721Speter	}
109217721Speter
109317721Speter	free_names(&moduleargc, moduleargv);
109432785Speter	/* FIXME-leak: here is where we would free s_h->modname, s_h->rest,
109532785Speter	   and if applicable, s_h->status.  Not exactly a memory leak,
109632785Speter	   in the sense that we are about to exit(), but may be worth
109732785Speter	   noting if we ever do a multithreaded server or something of
109832785Speter	   the sort.  */
109917721Speter    }
110032785Speter    /* FIXME-leak: as above, here is where we would free s_head.  */
110117721Speter}
1102