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