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