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