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