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