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