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