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