]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/cvs/src/mkmodules.c
merge fix for boot-time hang on centos' xen
[FreeBSD/FreeBSD.git] / contrib / cvs / src / mkmodules.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 as
6  * specified in the README file that comes with the CVS kit.
7  *
8  * $FreeBSD$
9  */
10
11 #include "cvs.h"
12 #include "getline.h"
13 #include "history.h"
14 #include "savecwd.h"
15
16 #ifndef DBLKSIZ
17 #define DBLKSIZ 4096                    /* since GNU ndbm doesn't define it */
18 #endif
19
20 static int checkout_file PROTO((char *file, char *temp));
21 static char *make_tempfile PROTO((void));
22 static void rename_rcsfile PROTO((char *temp, char *real));
23
24 #ifndef MY_NDBM
25 static void rename_dbmfile PROTO((char *temp));
26 static void write_dbmfile PROTO((char *temp));
27 #endif                          /* !MY_NDBM */
28
29 /* Structure which describes an administrative file.  */
30 struct admin_file {
31    /* Name of the file, within the CVSROOT directory.  */
32    char *filename;
33
34    /* This is a one line description of what the file is for.  It is not
35       currently used, although one wonders whether it should be, somehow.
36       If NULL, then don't process this file in mkmodules (FIXME?: a bit of
37       a kludge; probably should replace this with a flags field).  */
38    char *errormsg;
39
40    /* Contents which the file should have in a new repository.  To avoid
41       problems with brain-dead compilers which choke on long string constants,
42       this is a pointer to an array of char * terminated by NULL--each of
43       the strings is concatenated.
44
45       If this field is NULL, the file is not created in a new
46       repository, but it can be added with "cvs add" (just as if one
47       had created the repository with a version of CVS which didn't
48       know about the file) and the checked-out copy will be updated
49       without having to add it to checkoutlist.  */
50    const char * const *contents;
51 };
52
53 static const char *const loginfo_contents[] = {
54     "# The \"loginfo\" file controls where \"cvs commit\" log information\n",
55     "# is sent.  The first entry on a line is a regular expression which must match\n",
56     "# the directory that the change is being made to, relative to the\n",
57     "# $CVSROOT.  If a match is found, then the remainder of the line is a filter\n",
58     "# program that should expect log information on its standard input.\n",
59     "#\n",
60     "# If the repository name does not match any of the regular expressions in this\n",
61     "# file, the \"DEFAULT\" line is used, if it is specified.\n",
62     "#\n",
63     "# If the name ALL appears as a regular expression it is always used\n",
64     "# in addition to the first matching regex or DEFAULT.\n",
65     "#\n",
66     "# You may specify a format string as part of the\n",
67     "# filter.  The string is composed of a `%' followed\n",
68     "# by a single format character, or followed by a set of format\n",
69     "# characters surrounded by `{' and `}' as separators.  The format\n",
70     "# characters are:\n",
71     "#\n",
72     "#   s = file name\n",
73     "#   V = old version number (pre-checkin)\n",
74     "#   v = new version number (post-checkin)\n",
75     "#\n",
76     "# For example:\n",
77     "#DEFAULT (echo \"\"; id; echo %s; date; cat) >> $CVSROOT/CVSROOT/commitlog\n",
78     "# or\n",
79     "#DEFAULT (echo \"\"; id; echo %{sVv}; date; cat) >> $CVSROOT/CVSROOT/commitlog\n",
80     NULL
81 };
82
83 static const char *const rcsinfo_contents[] = {
84     "# The \"rcsinfo\" file is used to control templates with which the editor\n",
85     "# is invoked on commit and import.\n",
86     "#\n",
87     "# The first entry on a line is a regular expression which is tested\n",
88     "# against the directory that the change is being made to, relative to the\n",
89     "# $CVSROOT.  For the first match that is found, then the remainder of the\n",
90     "# line is the name of the file that contains the template.\n",
91     "#\n",
92     "# If the repository name does not match any of the regular expressions in this\n",
93     "# file, the \"DEFAULT\" line is used, if it is specified.\n",
94     "#\n",
95     "# If the name \"ALL\" appears as a regular expression it is always used\n",
96     "# in addition to the first matching regex or \"DEFAULT\".\n",
97     NULL
98 };
99
100 static const char *const editinfo_contents[] = {
101     "# The \"editinfo\" file is used to allow verification of logging\n",
102     "# information.  It works best when a template (as specified in the\n",
103     "# rcsinfo file) is provided for the logging procedure.  Given a\n",
104     "# template with locations for, a bug-id number, a list of people who\n",
105     "# reviewed the code before it can be checked in, and an external\n",
106     "# process to catalog the differences that were code reviewed, the\n",
107     "# following test can be applied to the code:\n",
108     "#\n",
109     "#   Making sure that the entered bug-id number is correct.\n",
110     "#   Validating that the code that was reviewed is indeed the code being\n",
111     "#       checked in (using the bug-id number or a seperate review\n",
112     "#       number to identify this particular code set.).\n",
113     "#\n",
114     "# If any of the above test failed, then the commit would be aborted.\n",
115     "#\n",
116     "# Actions such as mailing a copy of the report to each reviewer are\n",
117     "# better handled by an entry in the loginfo file.\n",
118     "#\n",
119     "# One thing that should be noted is the the ALL keyword is not\n",
120     "# supported.  There can be only one entry that matches a given\n",
121     "# repository.\n",
122     NULL
123 };
124
125 static const char *const verifymsg_contents[] = {
126     "# The \"verifymsg\" file is used to allow verification of logging\n",
127     "# information.  It works best when a template (as specified in the\n",
128     "# rcsinfo file) is provided for the logging procedure.  Given a\n",
129     "# template with locations for, a bug-id number, a list of people who\n",
130     "# reviewed the code before it can be checked in, and an external\n",
131     "# process to catalog the differences that were code reviewed, the\n",
132     "# following test can be applied to the code:\n",
133     "#\n",
134     "#   Making sure that the entered bug-id number is correct.\n",
135     "#   Validating that the code that was reviewed is indeed the code being\n",
136     "#       checked in (using the bug-id number or a seperate review\n",
137     "#       number to identify this particular code set.).\n",
138     "#\n",
139     "# If any of the above test failed, then the commit would be aborted.\n",
140     "#\n",
141     "# Actions such as mailing a copy of the report to each reviewer are\n",
142     "# better handled by an entry in the loginfo file.\n",
143     "#\n",
144     "# One thing that should be noted is the the ALL keyword is not\n",
145     "# supported.  There can be only one entry that matches a given\n",
146     "# repository.\n",
147     NULL
148 };
149
150 static const char *const commitinfo_contents[] = {
151     "# The \"commitinfo\" file is used to control pre-commit checks.\n",
152     "# The filter on the right is invoked with the repository and a list \n",
153     "# of files to check.  A non-zero exit of the filter program will \n",
154     "# cause the commit to be aborted.\n",
155     "#\n",
156     "# The first entry on a line is a regular expression which is tested\n",
157     "# against the directory that the change is being committed to, relative\n",
158     "# to the $CVSROOT.  For the first match that is found, then the remainder\n",
159     "# of the line is the name of the filter to run.\n",
160     "#\n",
161     "# If the repository name does not match any of the regular expressions in this\n",
162     "# file, the \"DEFAULT\" line is used, if it is specified.\n",
163     "#\n",
164     "# If the name \"ALL\" appears as a regular expression it is always used\n",
165     "# in addition to the first matching regex or \"DEFAULT\".\n",
166     NULL
167 };
168
169 static const char *const taginfo_contents[] = {
170     "# The \"taginfo\" file is used to control pre-tag checks.\n",
171     "# The filter on the right is invoked with the following arguments:\n",
172     "#\n",
173     "# $1 -- tagname\n",
174     "# $2 -- operation \"add\" for tag, \"mov\" for tag -F, and \"del\" for tag -d\n",
175     "# $3 -- repository\n",
176     "# $4->  file revision [file revision ...]\n",
177     "#\n",
178     "# A non-zero exit of the filter program will cause the tag to be aborted.\n",
179     "#\n",
180     "# The first entry on a line is a regular expression which is tested\n",
181     "# against the directory that the change is being committed to, relative\n",
182     "# to the $CVSROOT.  For the first match that is found, then the remainder\n",
183     "# of the line is the name of the filter to run.\n",
184     "#\n",
185     "# If the repository name does not match any of the regular expressions in this\n",
186     "# file, the \"DEFAULT\" line is used, if it is specified.\n",
187     "#\n",
188     "# If the name \"ALL\" appears as a regular expression it is always used\n",
189     "# in addition to the first matching regex or \"DEFAULT\".\n",
190     NULL
191 };
192
193 static const char *const checkoutlist_contents[] = {
194     "# The \"checkoutlist\" file is used to support additional version controlled\n",
195     "# administrative files in $CVSROOT/CVSROOT, such as template files.\n",
196     "#\n",
197     "# The first entry on a line is a filename which will be checked out from\n",
198     "# the corresponding RCS file in the $CVSROOT/CVSROOT directory.\n",
199     "# The remainder of the line is an error message to use if the file cannot\n",
200     "# be checked out.\n",
201     "#\n",
202     "# File format:\n",
203     "#\n",
204     "#  [<whitespace>]<filename>[<whitespace><error message>]<end-of-line>\n",
205     "#\n",
206     "# comment lines begin with '#'\n",
207     NULL
208 };
209
210 static const char *const cvswrappers_contents[] = {
211     "# This file affects handling of files based on their names.\n",
212     "#\n",
213 #if 0    /* see comments in wrap_add in wrapper.c */
214     "# The -t/-f options allow one to treat directories of files\n",
215     "# as a single file, or to transform a file in other ways on\n",
216     "# its way in and out of CVS.\n",
217     "#\n",
218 #endif
219     "# The -m option specifies whether CVS attempts to merge files.\n",
220     "#\n",
221     "# The -k option specifies keyword expansion (e.g. -kb for binary).\n",
222     "#\n",
223     "# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)\n",
224     "#\n",
225     "#  wildcard        [option value][option value]...\n",
226     "#\n",
227     "#  where option is one of\n",
228     "#  -f              from cvs filter         value: path to filter\n",
229     "#  -t              to cvs filter           value: path to filter\n",
230     "#  -m              update methodology      value: MERGE or COPY\n",
231     "#  -k              expansion mode          value: b, o, kkv, &c\n",
232     "#\n",
233     "#  and value is a single-quote delimited value.\n",
234     "# For example:\n",
235     "#*.gif -k 'b'\n",
236     NULL
237 };
238
239 static const char *const notify_contents[] = {
240     "# The \"notify\" file controls where notifications from watches set by\n",
241     "# \"cvs watch add\" or \"cvs edit\" are sent.  The first entry on a line is\n",
242     "# a regular expression which is tested against the directory that the\n",
243     "# change is being made to, relative to the $CVSROOT.  If it matches,\n",
244     "# then the remainder of the line is a filter program that should contain\n",
245     "# one occurrence of %s for the user to notify, and information on its\n",
246     "# standard input.\n",
247     "#\n",
248     "# \"ALL\" or \"DEFAULT\" can be used in place of the regular expression.\n",
249     "#\n",
250     "# For example:\n",
251     "#ALL mail -s \"CVS notification\" %s\n",
252     NULL
253 };
254
255 static const char *const modules_contents[] = {
256     "# Three different line formats are valid:\n",
257     "#  key     -a    aliases...\n",
258     "#  key [options] directory\n",
259     "#  key [options] directory files...\n",
260     "#\n",
261     "# Where \"options\" are composed of:\n",
262     "#  -i prog         Run \"prog\" on \"cvs commit\" from top-level of module.\n",
263     "#  -o prog         Run \"prog\" on \"cvs checkout\" of module.\n",
264     "#  -e prog         Run \"prog\" on \"cvs export\" of module.\n",
265     "#  -t prog         Run \"prog\" on \"cvs rtag\" of module.\n",
266     "#  -u prog         Run \"prog\" on \"cvs update\" of module.\n",
267     "#  -d dir          Place module in directory \"dir\" instead of module name.\n",
268     "#  -l              Top-level directory only -- do not recurse.\n",
269     "#\n",
270     "# NOTE:  If you change any of the \"Run\" options above, you'll have to\n",
271     "# release and re-checkout any working directories of these modules.\n",
272     "#\n",
273     "# And \"directory\" is a path to a directory relative to $CVSROOT.\n",
274     "#\n",
275     "# The \"-a\" option specifies an alias.  An alias is interpreted as if\n",
276     "# everything on the right of the \"-a\" had been typed on the command line.\n",
277     "#\n",
278     "# You can encode a module within a module by using the special '&'\n",
279     "# character to interpose another module into the current module.  This\n",
280     "# can be useful for creating a module that consists of many directories\n",
281     "# spread out over the entire source repository.\n",
282     NULL
283 };
284
285 static const char *const config_contents[] = {
286     "# Set this to \"no\" if pserver shouldn't check system users/passwords\n",
287     "#SystemAuth=no\n",
288     "\n",
289     "# Put CVS lock files in this directory rather than directly in the repository.\n",
290     "#LockDir=/var/lock/cvs\n",
291     "\n",
292 #ifdef PRESERVE_PERMISSIONS_SUPPORT
293     "# Set `PreservePermissions' to `yes' to save file status information\n",
294     "# in the repository.\n",
295     "#PreservePermissions=no\n",
296     "\n",
297 #endif
298     "# Set `TopLevelAdmin' to `yes' to create a CVS directory at the top\n",
299     "# level of the new working directory when using the `cvs checkout'\n",
300     "# command.\n",
301     "#TopLevelAdmin=no\n",
302     "\n",
303     "# Set `LogHistory' to `all' or `" ALL_HISTORY_REC_TYPES "' to log all transactions to the\n",
304     "# history file, or a subset as needed (ie `TMAR' logs all write operations)\n",
305     "#LogHistory=" ALL_HISTORY_REC_TYPES "\n",
306     "\n",
307     "# Set `RereadLogAfterVerify' to `always' (the default) to allow the verifymsg\n",
308     "# script to change the log message.  Set it to `stat' to force CVS to verify",
309     "# that the file has changed before reading it (this can take up to an extra\n",
310     "# second per directory being committed, so it is not recommended for large\n",
311     "# repositories.  Set it to `never' (the previous CVS behavior) to prevent\n",
312     "# verifymsg scripts from changing the log message.\n",
313     "#RereadLogAfterVerify=always\n",
314     NULL
315 };
316
317 static const struct admin_file filelist[] = {
318     {CVSROOTADM_LOGINFO, 
319         "no logging of 'cvs commit' messages is done without a %s file",
320         &loginfo_contents[0]},
321     {CVSROOTADM_RCSINFO,
322         "a %s file can be used to configure 'cvs commit' templates",
323         rcsinfo_contents},
324     {CVSROOTADM_EDITINFO,
325         "a %s file can be used to validate log messages",
326         editinfo_contents},
327     {CVSROOTADM_VERIFYMSG,
328         "a %s file can be used to validate log messages",
329         verifymsg_contents},
330     {CVSROOTADM_COMMITINFO,
331         "a %s file can be used to configure 'cvs commit' checking",
332         commitinfo_contents},
333     {CVSROOTADM_TAGINFO,
334         "a %s file can be used to configure 'cvs tag' checking",
335         taginfo_contents},
336     {CVSROOTADM_IGNORE,
337         "a %s file can be used to specify files to ignore",
338         NULL},
339     {CVSROOTADM_CHECKOUTLIST,
340         "a %s file can specify extra CVSROOT files to auto-checkout",
341         checkoutlist_contents},
342     {CVSROOTADM_WRAPPER,
343         "a %s file can be used to specify files to treat as wrappers",
344         cvswrappers_contents},
345     {CVSROOTADM_NOTIFY,
346         "a %s file can be used to specify where notifications go",
347         notify_contents},
348     {CVSROOTADM_MODULES,
349         /* modules is special-cased in mkmodules.  */
350         NULL,
351         modules_contents},
352     {CVSROOTADM_READERS,
353         "a %s file specifies read-only users",
354         NULL},
355     {CVSROOTADM_WRITERS,
356         "a %s file specifies read/write users",
357         NULL},
358
359     /* Some have suggested listing CVSROOTADM_PASSWD here too.  This
360        would mean that CVS commands which operate on the
361        CVSROOTADM_PASSWD file would transmit hashed passwords over the
362        net.  This might seem to be no big deal, as pserver normally
363        transmits cleartext passwords, but the difference is that
364        CVSROOTADM_PASSWD contains *all* passwords, not just the ones
365        currently being used.  For example, it could be too easy to
366        accidentally give someone readonly access to CVSROOTADM_PASSWD
367        (e.g. via anonymous CVS or cvsweb), and then if there are any
368        guessable passwords for read/write access (usually there will be)
369        they get read/write access.
370
371        Another worry is the implications of storing old passwords--if
372        someone used a password in the past they might be using it
373        elsewhere, using a similar password, etc, and so saving old
374        passwords, even hashed, is probably not a good idea.  */
375
376     {CVSROOTADM_CONFIG,
377          "a %s file configures various behaviors",
378          config_contents},
379     {NULL, NULL, NULL}
380 };
381
382 /* Rebuild the checked out administrative files in directory DIR.  */
383 int
384 mkmodules (dir)
385     char *dir;
386 {
387     struct saved_cwd cwd;
388     char *temp;
389     char *cp, *last, *fname;
390 #ifdef MY_NDBM
391     DBM *db;
392 #endif
393     FILE *fp;
394     char *line = NULL;
395     size_t line_allocated = 0;
396     const struct admin_file *fileptr;
397
398     if (noexec)
399         return 0;
400
401     if (save_cwd (&cwd))
402         error_exit ();
403
404     if ( CVS_CHDIR (dir) < 0)
405         error (1, errno, "cannot chdir to %s", dir);
406
407     /*
408      * First, do the work necessary to update the "modules" database.
409      */
410     temp = make_tempfile ();
411     switch (checkout_file (CVSROOTADM_MODULES, temp))
412     {
413
414         case 0:                 /* everything ok */
415 #ifdef MY_NDBM
416             /* open it, to generate any duplicate errors */
417             if ((db = dbm_open (temp, O_RDONLY, 0666)) != NULL)
418                 dbm_close (db);
419 #else
420             write_dbmfile (temp);
421             rename_dbmfile (temp);
422 #endif
423             rename_rcsfile (temp, CVSROOTADM_MODULES);
424             break;
425
426         default:
427             error (0, 0,
428                 "'cvs checkout' is less functional without a %s file",
429                 CVSROOTADM_MODULES);
430             break;
431     }                                   /* switch on checkout_file() */
432
433     if (unlink_file (temp) < 0
434         && !existence_error (errno))
435         error (0, errno, "cannot remove %s", temp);
436     free (temp);
437
438     /* Checkout the files that need it in CVSROOT dir */
439     for (fileptr = filelist; fileptr && fileptr->filename; fileptr++) {
440         if (fileptr->errormsg == NULL)
441             continue;
442         temp = make_tempfile ();
443         if (checkout_file (fileptr->filename, temp) == 0)
444             rename_rcsfile (temp, fileptr->filename);
445 #if 0
446         /*
447          * If there was some problem other than the file not existing,
448          * checkout_file already printed a real error message.  If the
449          * file does not exist, it is harmless--it probably just means
450          * that the repository was created with an old version of CVS
451          * which didn't have so many files in CVSROOT.
452          */
453         else if (fileptr->errormsg)
454             error (0, 0, fileptr->errormsg, fileptr->filename);
455 #endif
456         if (unlink_file (temp) < 0
457             && !existence_error (errno))
458             error (0, errno, "cannot remove %s", temp);
459         free (temp);
460     }
461
462     fp = CVS_FOPEN (CVSROOTADM_CHECKOUTLIST, "r");
463     if (fp)
464     {
465         /*
466          * File format:
467          *  [<whitespace>]<filename>[<whitespace><error message>]<end-of-line>
468          *
469          * comment lines begin with '#'
470          */
471         while (getline (&line, &line_allocated, fp) >= 0)
472         {
473             /* skip lines starting with # */
474             if (line[0] == '#')
475                 continue;
476
477             if ((last = strrchr (line, '\n')) != NULL)
478                 *last = '\0';                   /* strip the newline */
479
480             /* Skip leading white space. */
481             for (fname = line;
482                  *fname && isspace ((unsigned char) *fname);
483                  fname++)
484                 ;
485
486             /* Find end of filename. */
487             for (cp = fname; *cp && !isspace ((unsigned char) *cp); cp++)
488                 ;
489             *cp = '\0';
490
491             temp = make_tempfile ();
492             if (checkout_file (fname, temp) == 0)
493             {
494                 rename_rcsfile (temp, fname);
495             }
496             else
497             {
498                 /* Skip leading white space before the error message.  */
499                 for (cp++;
500                      cp < last && *cp && isspace ((unsigned char) *cp);
501                      cp++)
502                     ;
503                 if (cp < last && *cp)
504                     error (0, 0, "%s", cp);
505             }
506             if (unlink_file (temp) < 0
507                 && !existence_error (errno))
508                 error (0, errno, "cannot remove %s", temp);
509             free (temp);
510         }
511         if (line)
512             free (line);
513         if (ferror (fp))
514             error (0, errno, "cannot read %s", CVSROOTADM_CHECKOUTLIST);
515         if (fclose (fp) < 0)
516             error (0, errno, "cannot close %s", CVSROOTADM_CHECKOUTLIST);
517     }
518     else
519     {
520         /* Error from CVS_FOPEN.  */
521         if (!existence_error (errno))
522             error (0, errno, "cannot open %s", CVSROOTADM_CHECKOUTLIST);
523     }
524
525     if (restore_cwd (&cwd, NULL))
526         error_exit ();
527     free_cwd (&cwd);
528
529     return (0);
530 }
531
532 /*
533  * Yeah, I know, there are NFS race conditions here.
534  */
535 static char *
536 make_tempfile ()
537 {
538     static int seed = 0;
539     int fd;
540     char *temp;
541
542     if (seed == 0)
543         seed = getpid ();
544     temp = xmalloc (sizeof (BAKPREFIX) + 40);
545     while (1)
546     {
547         (void) sprintf (temp, "%s%d", BAKPREFIX, seed++);
548         if ((fd = CVS_OPEN (temp, O_CREAT|O_EXCL|O_RDWR, 0666)) != -1)
549             break;
550         if (errno != EEXIST)
551             error (1, errno, "cannot create temporary file %s", temp);
552     }
553     if (close(fd) < 0)
554         error(1, errno, "cannot close temporary file %s", temp);
555     return temp;
556 }
557
558 /* Get a file.  If the file does not exist, return 1 silently.  If
559    there is an error, print a message and return 1 (FIXME: probably
560    not a very clean convention).  On success, return 0.  */
561
562 static int
563 checkout_file (file, temp)
564     char *file;
565     char *temp;
566 {
567     char *rcs;
568     RCSNode *rcsnode;
569     int retcode = 0;
570
571     if (noexec)
572         return 0;
573
574     rcs = xmalloc (strlen (file) + 5);
575     strcpy (rcs, file);
576     strcat (rcs, RCSEXT);
577     if (!isfile (rcs))
578     {
579         free (rcs);
580         return (1);
581     }
582     rcsnode = RCS_parsercsfile (rcs);
583     retcode = RCS_checkout (rcsnode, NULL, NULL, NULL, NULL, temp,
584                             (RCSCHECKOUTPROC) NULL, (void *) NULL);
585     if (retcode != 0)
586     {
587         /* Probably not necessary (?); RCS_checkout already printed a
588            message.  */
589         error (0, 0, "failed to check out %s file",
590                file);
591     }
592     freercsnode (&rcsnode);
593     free (rcs);
594     return (retcode);
595 }
596
597 #ifndef MY_NDBM
598
599 static void
600 write_dbmfile (temp)
601     char *temp;
602 {
603     char line[DBLKSIZ], value[DBLKSIZ];
604     FILE *fp;
605     DBM *db;
606     char *cp, *vp;
607     datum key, val;
608     int len, cont, err = 0;
609
610     fp = open_file (temp, "r");
611     if ((db = dbm_open (temp, O_RDWR | O_CREAT | O_TRUNC, 0666)) == NULL)
612         error (1, errno, "cannot open dbm file %s for creation", temp);
613     for (cont = 0; fgets (line, sizeof (line), fp) != NULL;)
614     {
615         if ((cp = strrchr (line, '\n')) != NULL)
616             *cp = '\0';                 /* strip the newline */
617
618         /*
619          * Add the line to the value, at the end if this is a continuation
620          * line; otherwise at the beginning, but only after any trailing
621          * backslash is removed.
622          */
623         vp = value;
624         if (cont)
625             vp += strlen (value);
626
627         /*
628          * See if the line we read is a continuation line, and strip the
629          * backslash if so.
630          */
631         len = strlen (line);
632         if (len > 0)
633             cp = &line[len - 1];
634         else
635             cp = line;
636         if (*cp == '\\')
637         {
638             cont = 1;
639             *cp = '\0';
640         }
641         else
642         {
643             cont = 0;
644         }
645         (void) strcpy (vp, line);
646         if (value[0] == '#')
647             continue;                   /* comment line */
648         vp = value;
649         while (*vp && isspace ((unsigned char) *vp))
650             vp++;
651         if (*vp == '\0')
652             continue;                   /* empty line */
653
654         /*
655          * If this was not a continuation line, add the entry to the database
656          */
657         if (!cont)
658         {
659             key.dptr = vp;
660             while (*vp && !isspace ((unsigned char) *vp))
661                 vp++;
662             key.dsize = vp - key.dptr;
663             *vp++ = '\0';               /* NULL terminate the key */
664             while (*vp && isspace ((unsigned char) *vp))
665                 vp++;                   /* skip whitespace to value */
666             if (*vp == '\0')
667             {
668                 error (0, 0, "warning: NULL value for key `%s'", key.dptr);
669                 continue;
670             }
671             val.dptr = vp;
672             val.dsize = strlen (vp);
673             if (dbm_store (db, key, val, DBM_INSERT) == 1)
674             {
675                 error (0, 0, "duplicate key found for `%s'", key.dptr);
676                 err++;
677             }
678         }
679     }
680     dbm_close (db);
681     if (fclose (fp) < 0)
682         error (0, errno, "cannot close %s", temp);
683     if (err)
684     {
685         /* I think that the size of the buffer needed here is
686            just determined by sizeof (CVSROOTADM_MODULES), the
687            filenames created by make_tempfile, and other things that won't
688            overflow.  */
689         char dotdir[50], dotpag[50], dotdb[50];
690
691         (void) sprintf (dotdir, "%s.dir", temp);
692         (void) sprintf (dotpag, "%s.pag", temp);
693         (void) sprintf (dotdb, "%s.db", temp);
694         if (unlink_file (dotdir) < 0
695             && !existence_error (errno))
696             error (0, errno, "cannot remove %s", dotdir);
697         if (unlink_file (dotpag) < 0
698             && !existence_error (errno))
699             error (0, errno, "cannot remove %s", dotpag);
700         if (unlink_file (dotdb) < 0
701             && !existence_error (errno))
702             error (0, errno, "cannot remove %s", dotdb);
703         error (1, 0, "DBM creation failed; correct above errors");
704     }
705 }
706
707 static void
708 rename_dbmfile (temp)
709     char *temp;
710 {
711     /* I think that the size of the buffer needed here is
712        just determined by sizeof (CVSROOTADM_MODULES), the
713        filenames created by make_tempfile, and other things that won't
714        overflow.  */
715     char newdir[50], newpag[50], newdb[50];
716     char dotdir[50], dotpag[50], dotdb[50];
717     char bakdir[50], bakpag[50], bakdb[50];
718
719     int dir1_errno = 0, pag1_errno = 0, db1_errno = 0;
720     int dir2_errno = 0, pag2_errno = 0, db2_errno = 0;
721     int dir3_errno = 0, pag3_errno = 0, db3_errno = 0;
722
723     (void) sprintf (dotdir, "%s.dir", CVSROOTADM_MODULES);
724     (void) sprintf (dotpag, "%s.pag", CVSROOTADM_MODULES);
725     (void) sprintf (dotdb, "%s.db", CVSROOTADM_MODULES);
726     (void) sprintf (bakdir, "%s%s.dir", BAKPREFIX, CVSROOTADM_MODULES);
727     (void) sprintf (bakpag, "%s%s.pag", BAKPREFIX, CVSROOTADM_MODULES);
728     (void) sprintf (bakdb, "%s%s.db", BAKPREFIX, CVSROOTADM_MODULES);
729     (void) sprintf (newdir, "%s.dir", temp);
730     (void) sprintf (newpag, "%s.pag", temp);
731     (void) sprintf (newdb, "%s.db", temp);
732
733     (void) chmod (newdir, 0666);
734     (void) chmod (newpag, 0666);
735     (void) chmod (newdb, 0666);
736
737     /* don't mess with me */
738     SIG_beginCrSect ();
739
740     /* rm .#modules.dir .#modules.pag */
741     if (unlink_file (bakdir) < 0)
742         dir1_errno = errno;
743     if (unlink_file (bakpag) < 0)
744         pag1_errno = errno;
745     if (unlink_file (bakdb) < 0)
746         db1_errno = errno;
747
748     /* mv modules.dir .#modules.dir */
749     if (CVS_RENAME (dotdir, bakdir) < 0)
750         dir2_errno = errno;
751     /* mv modules.pag .#modules.pag */
752     if (CVS_RENAME (dotpag, bakpag) < 0)
753         pag2_errno = errno;
754     /* mv modules.db .#modules.db */
755     if (CVS_RENAME (dotdb, bakdb) < 0)
756         db2_errno = errno;
757
758     /* mv "temp".dir modules.dir */
759     if (CVS_RENAME (newdir, dotdir) < 0)
760         dir3_errno = errno;
761     /* mv "temp".pag modules.pag */
762     if (CVS_RENAME (newpag, dotpag) < 0)
763         pag3_errno = errno;
764     /* mv "temp".db modules.db */
765     if (CVS_RENAME (newdb, dotdb) < 0)
766         db3_errno = errno;
767
768     /* OK -- make my day */
769     SIG_endCrSect ();
770
771     /* I didn't want to call error() when we had signals blocked
772        (unnecessary?), but do it now.  */
773     if (dir1_errno && !existence_error (dir1_errno))
774         error (0, dir1_errno, "cannot remove %s", bakdir);
775     if (pag1_errno && !existence_error (pag1_errno))
776         error (0, pag1_errno, "cannot remove %s", bakpag);
777     if (db1_errno && !existence_error (db1_errno))
778         error (0, db1_errno, "cannot remove %s", bakdb);
779
780     if (dir2_errno && !existence_error (dir2_errno))
781         error (0, dir2_errno, "cannot remove %s", bakdir);
782     if (pag2_errno && !existence_error (pag2_errno))
783         error (0, pag2_errno, "cannot remove %s", bakpag);
784     if (db2_errno && !existence_error (db2_errno))
785         error (0, db2_errno, "cannot remove %s", bakdb);
786
787     if (dir3_errno && !existence_error (dir3_errno))
788         error (0, dir3_errno, "cannot remove %s", bakdir);
789     if (pag3_errno && !existence_error (pag3_errno))
790         error (0, pag3_errno, "cannot remove %s", bakpag);
791     if (db3_errno && !existence_error (db3_errno))
792         error (0, db3_errno, "cannot remove %s", bakdb);
793 }
794
795 #endif                          /* !MY_NDBM */
796
797 static void
798 rename_rcsfile (temp, real)
799     char *temp;
800     char *real;
801 {
802     char *bak;
803     struct stat statbuf;
804     char *rcs;
805
806     /* Set "x" bits if set in original. */
807     rcs = xmalloc (strlen (real) + sizeof (RCSEXT) + 10);
808     (void) sprintf (rcs, "%s%s", real, RCSEXT);
809     statbuf.st_mode = 0; /* in case rcs file doesn't exist, but it should... */
810     if (CVS_STAT (rcs, &statbuf) < 0
811         && !existence_error (errno))
812         error (0, errno, "cannot stat %s", rcs);
813     free (rcs);
814
815     if (chmod (temp, 0444 | (statbuf.st_mode & 0111)) < 0)
816         error (0, errno, "warning: cannot chmod %s", temp);
817     bak = xmalloc (strlen (real) + sizeof (BAKPREFIX) + 10);
818     (void) sprintf (bak, "%s%s", BAKPREFIX, real);
819
820     /* rm .#loginfo */
821     if (unlink_file (bak) < 0
822         && !existence_error (errno))
823         error (0, errno, "cannot remove %s", bak);
824
825     /* mv loginfo .#loginfo */
826     if (CVS_RENAME (real, bak) < 0
827         && !existence_error (errno))
828         error (0, errno, "cannot rename %s to %s", real, bak);
829
830     /* mv "temp" loginfo */
831     if (CVS_RENAME (temp, real) < 0
832         && !existence_error (errno))
833         error (0, errno, "cannot rename %s to %s", temp, real);
834
835     free (bak);
836 }
837 \f
838 const char *const init_usage[] = {
839     "Usage: %s %s\n",
840     "(Specify the --help global option for a list of other help options)\n",
841     NULL
842 };
843
844 int
845 init (argc, argv)
846     int argc;
847     char **argv;
848 {
849     /* Name of CVSROOT directory.  */
850     char *adm;
851     /* Name of this administrative file.  */
852     char *info;
853     /* Name of ,v file for this administrative file.  */
854     char *info_v;
855     /* Exit status.  */
856     int err = 0;
857
858     const struct admin_file *fileptr;
859
860     umask (cvsumask);
861
862     if (argc == -1 || argc > 1)
863         usage (init_usage);
864
865 #ifdef CLIENT_SUPPORT
866     if (current_parsed_root->isremote)
867     {
868         start_server ();
869
870         ign_setup ();
871         send_init_command ();
872         return get_responses_and_close ();
873     }
874 #endif /* CLIENT_SUPPORT */
875
876     /* Note: we do *not* create parent directories as needed like the
877        old cvsinit.sh script did.  Few utilities do that, and a
878        non-existent parent directory is as likely to be a typo as something
879        which needs to be created.  */
880     mkdir_if_needed (current_parsed_root->directory);
881
882     adm = xmalloc (strlen (current_parsed_root->directory) + sizeof (CVSROOTADM) + 2);
883     sprintf (adm, "%s/%s", current_parsed_root->directory, CVSROOTADM);
884     mkdir_if_needed (adm);
885
886     /* This is needed because we pass "fileptr->filename" not "info"
887        to add_rcs_file below.  I think this would be easy to change,
888        thus nuking the need for CVS_CHDIR here, but I haven't looked
889        closely (e.g. see wrappers calls within add_rcs_file).  */
890     if ( CVS_CHDIR (adm) < 0)
891         error (1, errno, "cannot change to directory %s", adm);
892
893     /* Make Emptydir so it's there if we need it */
894     mkdir_if_needed (CVSNULLREPOS);
895
896     /* 80 is long enough for all the administrative file names, plus
897        "/" and so on.  */
898     info = xmalloc (strlen (adm) + 80);
899     info_v = xmalloc (strlen (adm) + 80);
900     for (fileptr = filelist; fileptr && fileptr->filename; ++fileptr)
901     {
902         if (fileptr->contents == NULL)
903             continue;
904         strcpy (info, adm);
905         strcat (info, "/");
906         strcat (info, fileptr->filename);
907         strcpy (info_v, info);
908         strcat (info_v, RCSEXT);
909         if (isfile (info_v))
910             /* We will check out this file in the mkmodules step.
911                Nothing else is required.  */
912             ;
913         else
914         {
915             int retcode;
916
917             if (!isfile (info))
918             {
919                 FILE *fp;
920                 const char * const *p;
921
922                 fp = open_file (info, "w");
923                 for (p = fileptr->contents; *p != NULL; ++p)
924                     if (fputs (*p, fp) < 0)
925                         error (1, errno, "cannot write %s", info);
926                 if (fclose (fp) < 0)
927                     error (1, errno, "cannot close %s", info);
928             }
929             /* The message used to say " of " and fileptr->filename after
930                "initial checkin" but I fail to see the point as we know what
931                file it is from the name.  */
932             retcode = add_rcs_file ("initial checkin", info_v,
933                                     fileptr->filename, "1.1", NULL,
934
935                                     /* No vendor branch.  */
936                                     NULL, NULL, 0, NULL,
937
938                                     NULL, 0, NULL);
939             if (retcode != 0)
940                 /* add_rcs_file already printed an error message.  */
941                 err = 1;
942         }
943     }
944
945     /* Turn on history logging by default.  The user can remove the file
946        to disable it.  */
947     strcpy (info, adm);
948     strcat (info, "/");
949     strcat (info, CVSROOTADM_HISTORY);
950     if (!isfile (info))
951     {
952         FILE *fp;
953
954         fp = open_file (info, "w");
955         if (fclose (fp) < 0)
956             error (1, errno, "cannot close %s", info);
957  
958         /* Make the new history file world-writeable, since every CVS
959            user will need to be able to write to it.  We use chmod()
960            because xchmod() is too shy. */
961         chmod (info, 0666);
962     }
963
964     /* Make an empty val-tags file to prevent problems creating it later.  */
965     strcpy (info, adm);
966     strcat (info, "/");
967     strcat (info, CVSROOTADM_VALTAGS);
968     if (!isfile (info))
969     {
970         FILE *fp;
971
972         fp = open_file (info, "w");
973         if (fclose (fp) < 0)
974             error (1, errno, "cannot close %s", info);
975  
976         /* Make the new val-tags file world-writeable, since every CVS
977            user will need to be able to write to it.  We use chmod()
978            because xchmod() is too shy. */
979         chmod (info, 0666);
980     }
981
982     free (info);
983     free (info_v);
984
985     mkmodules (adm);
986
987     free (adm);
988     return err;
989 }