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