mkmodules.c revision 25843
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
16static int checkout_file PROTO((char *file, char *temp));
17static char *make_tempfile PROTO((void));
18static void rename_rcsfile PROTO((char *temp, char *real));
19
20#ifndef MY_NDBM
21static void rename_dbmfile PROTO((char *temp));
22static void write_dbmfile PROTO((char *temp));
23#endif				/* !MY_NDBM */
24
25/* Structure which describes an administrative file.  */
26struct 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
49static 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
79static 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
96static 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
121static 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
146static 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
165static 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
189static 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
206static const char *const cvswrappers_contents[] = {
207    "# This file describes wrappers and other binary files to CVS.\n",
208    "#\n",
209    "# Wrappers are the concept where directories of files are to be\n",
210    "# treated as a single file.  The intended use is to wrap up a wrapper\n",
211    "# into a single tar such that the tar archive can be treated as a\n",
212    "# single binary file in CVS.\n",
213    "#\n",
214    "# To solve the problem effectively, it was also necessary to be able to\n",
215    "# prevent rcsmerge from merging these files.\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    "#\n",
226    "#  and value is a single-quote delimited value.\n",
227    "#\n",
228    "# For example:\n",
229    NULL
230};
231
232static const char *const notify_contents[] = {
233    "# The \"notify\" file controls where notifications from watches set by\n",
234    "# \"cvs watch add\" or \"cvs edit\" are sent.  The first entry on a line is\n",
235    "# a regular expression which is tested against the directory that the\n",
236    "# change is being made to, relative to the $CVSROOT.  If it matches,\n",
237    "# then the remainder of the line is a filter program that should contain\n",
238    "# one occurrence of %s for the user to notify, and information on its\n",
239    "# standard input.\n",
240    "#\n",
241    "# \"ALL\" or \"DEFAULT\" can be used in place of the regular expression.\n",
242    "#\n",
243    "# For example:\n",
244    "#ALL mail %s -s \"CVS notification\"\n",
245    NULL
246};
247
248static const char *const modules_contents[] = {
249    "# Three different line formats are valid:\n",
250    "#	key	-a    aliases...\n",
251    "#	key [options] directory\n",
252    "#	key [options] directory files...\n",
253    "#\n",
254    "# Where \"options\" are composed of:\n",
255    "#	-i prog		Run \"prog\" on \"cvs commit\" from top-level of module.\n",
256    "#	-o prog		Run \"prog\" on \"cvs checkout\" of module.\n",
257    "#	-e prog		Run \"prog\" on \"cvs export\" of module.\n",
258    "#	-t prog		Run \"prog\" on \"cvs rtag\" of module.\n",
259    "#	-u prog		Run \"prog\" on \"cvs update\" of module.\n",
260    "#	-d dir		Place module in directory \"dir\" instead of module name.\n",
261    "#	-l		Top-level directory only -- do not recurse.\n",
262    "#\n",
263    "# NOTE:  If you change any of the \"Run\" options above, you'll have to\n",
264    "# release and re-checkout any working directories of these modules.\n",
265    "#\n",
266    "# And \"directory\" is a path to a directory relative to $CVSROOT.\n",
267    "#\n",
268    "# The \"-a\" option specifies an alias.  An alias is interpreted as if\n",
269    "# everything on the right of the \"-a\" had been typed on the command line.\n",
270    "#\n",
271    "# You can encode a module within a module by using the special '&'\n",
272    "# character to interpose another module into the current module.  This\n",
273    "# can be useful for creating a module that consists of many directories\n",
274    "# spread out over the entire source repository.\n",
275    NULL
276};
277
278static const struct admin_file filelist[] = {
279    {CVSROOTADM_LOGINFO,
280	"no logging of 'cvs commit' messages is done without a %s file",
281	&loginfo_contents[0]},
282    {CVSROOTADM_RCSINFO,
283	"a %s file can be used to configure 'cvs commit' templates",
284	rcsinfo_contents},
285    {CVSROOTADM_EDITINFO,
286	"a %s file can be used to validate log messages",
287	editinfo_contents},
288    {CVSROOTADM_VERIFYMSG,
289	"a %s file can be used to validate log messages",
290	verifymsg_contents},
291    {CVSROOTADM_COMMITINFO,
292	"a %s file can be used to configure 'cvs commit' checking",
293	commitinfo_contents},
294    {CVSROOTADM_TAGINFO,
295	"a %s file can be used to configure 'cvs tag' checking",
296	taginfo_contents},
297    {CVSROOTADM_IGNORE,
298	"a %s file can be used to specify files to ignore",
299	NULL},
300    {CVSROOTADM_CHECKOUTLIST,
301	"a %s file can specify extra CVSROOT files to auto-checkout",
302	checkoutlist_contents},
303    {CVSROOTADM_WRAPPER,
304	"a %s file can be used to specify files to treat as wrappers",
305	cvswrappers_contents},
306    {CVSROOTADM_NOTIFY,
307	"a %s file can be used to specify where notifications go",
308	notify_contents},
309    {CVSROOTADM_MODULES,
310	/* modules is special-cased in mkmodules.  */
311	NULL,
312	modules_contents},
313    {CVSROOTADM_READERS,
314	"a %s file specifies read-only users",
315	NULL},
316    {CVSROOTADM_WRITERS,
317	"a %s file specifies read/write users",
318	NULL},
319    /* Some have suggested listing CVSROOTADM_PASSWD here too.  The
320       security implications of transmitting hashed passwords over the
321       net are no worse than transmitting cleartext passwords which pserver
322       does, so this isn't a problem.  But I'm worried about the implications
323       of storing old passwords--if someone used a password in the past
324       they might be using it elsewhere, using a similar password, etc,
325       and so it doesn't seem to me like we should be saving old passwords,
326       even hashed.  */
327    {NULL, NULL}
328};
329
330/* Rebuild the checked out administrative files in directory DIR.  */
331int
332mkmodules (dir)
333    char *dir;
334{
335    struct saved_cwd cwd;
336    char *temp;
337    char *cp, *last, *fname;
338#ifdef MY_NDBM
339    DBM *db;
340#endif
341    FILE *fp;
342    char *line = NULL;
343    size_t line_allocated = 0;
344    const struct admin_file *fileptr;
345
346    if (save_cwd (&cwd))
347	error_exit ();
348
349    if ( CVS_CHDIR (dir) < 0)
350	error (1, errno, "cannot chdir to %s", dir);
351
352    /*
353     * First, do the work necessary to update the "modules" database.
354     */
355    temp = make_tempfile ();
356    switch (checkout_file (CVSROOTADM_MODULES, temp))
357    {
358
359	case 0:			/* everything ok */
360#ifdef MY_NDBM
361	    /* open it, to generate any duplicate errors */
362	    if ((db = dbm_open (temp, O_RDONLY, 0666)) != NULL)
363		dbm_close (db);
364#else
365	    write_dbmfile (temp);
366	    rename_dbmfile (temp);
367#endif
368	    rename_rcsfile (temp, CVSROOTADM_MODULES);
369	    break;
370
371	case -1:			/* fork failed */
372	    (void) unlink_file (temp);
373	    error (1, errno, "cannot check out %s", CVSROOTADM_MODULES);
374	    /* NOTREACHED */
375
376	default:
377	    error (0, 0,
378		"'cvs checkout' is less functional without a %s file",
379		CVSROOTADM_MODULES);
380	    break;
381    }					/* switch on checkout_file() */
382
383    (void) unlink_file (temp);
384    free (temp);
385
386    /* Checkout the files that need it in CVSROOT dir */
387    for (fileptr = filelist; fileptr && fileptr->filename; fileptr++) {
388	if (fileptr->errormsg == NULL)
389	    continue;
390	temp = make_tempfile ();
391	if (checkout_file (fileptr->filename, temp) == 0)
392	    rename_rcsfile (temp, fileptr->filename);
393#if 0
394	/*
395	 * If there was some problem other than the file not existing,
396	 * checkout_file already printed a real error message.  If the
397	 * file does not exist, it is harmless--it probably just means
398	 * that the repository was created with an old version of CVS
399	 * which didn't have so many files in CVSROOT.
400	 */
401	else if (fileptr->errormsg)
402	    error (0, 0, fileptr->errormsg, fileptr->filename);
403#endif
404	(void) unlink_file (temp);
405	free (temp);
406    }
407
408    fp = CVS_FOPEN (CVSROOTADM_CHECKOUTLIST, "r");
409    if (fp)
410    {
411	/*
412	 * File format:
413	 *  [<whitespace>]<filename><whitespace><error message><end-of-line>
414	 *
415	 * comment lines begin with '#'
416	 */
417	while (getline (&line, &line_allocated, fp) >= 0)
418	{
419	    /* skip lines starting with # */
420	    if (line[0] == '#')
421		continue;
422
423	    if ((last = strrchr (line, '\n')) != NULL)
424		*last = '\0';			/* strip the newline */
425
426	    /* Skip leading white space. */
427	    for (fname = line; *fname && isspace(*fname); fname++)
428		;
429
430	    /* Find end of filename. */
431	    for (cp = fname; *cp && !isspace(*cp); cp++)
432		;
433	    *cp = '\0';
434
435	    temp = make_tempfile ();
436	    if (checkout_file (fname, temp) == 0)
437	    {
438		rename_rcsfile (temp, fname);
439	    }
440	    else
441	    {
442		for (cp++; cp < last && *last && isspace(*last); cp++)
443		    ;
444		if (cp < last && *cp)
445		    error (0, 0, cp, fname);
446	    }
447	    free (temp);
448	}
449	if (line)
450	    free (line);
451	if (ferror (fp))
452	    error (0, errno, "cannot read %s", CVSROOTADM_CHECKOUTLIST);
453	if (fclose (fp) < 0)
454	    error (0, errno, "cannot close %s", CVSROOTADM_CHECKOUTLIST);
455    }
456    else
457    {
458	/* Error from CVS_FOPEN.  */
459	if (!existence_error (errno))
460	    error (0, errno, "cannot open %s", CVSROOTADM_CHECKOUTLIST);
461    }
462
463    if (restore_cwd (&cwd, NULL))
464	error_exit ();
465    free_cwd (&cwd);
466
467    return (0);
468}
469
470/*
471 * Yeah, I know, there are NFS race conditions here.
472 */
473static char *
474make_tempfile ()
475{
476    static int seed = 0;
477    int fd;
478    char *temp;
479
480    if (seed == 0)
481	seed = getpid ();
482    temp = xmalloc (sizeof (BAKPREFIX) + 40);
483    while (1)
484    {
485	(void) sprintf (temp, "%s%d", BAKPREFIX, seed++);
486	if ((fd = CVS_OPEN (temp, O_CREAT|O_EXCL|O_RDWR, 0666)) != -1)
487	    break;
488	if (errno != EEXIST)
489	    error (1, errno, "cannot create temporary file %s", temp);
490    }
491    if (close(fd) < 0)
492	error(1, errno, "cannot close temporary file %s", temp);
493    return temp;
494}
495
496static int
497checkout_file (file, temp)
498    char *file;
499    char *temp;
500{
501    char *rcs;
502    RCSNode *rcsnode;
503    int retcode = 0;
504
505    if (noexec)
506	return 0;
507
508    rcs = xmalloc (strlen (file) + 5);
509    strcpy (rcs, file);
510    strcat (rcs, RCSEXT);
511    if (!isfile (rcs))
512    {
513	free (rcs);
514	return (1);
515    }
516    rcsnode = RCS_parsercsfile (rcs);
517    retcode = RCS_checkout (rcsnode, NULL, NULL, NULL, NULL, temp,
518			    (RCSCHECKOUTPROC) NULL, (void *) NULL);
519    if (retcode != 0)
520    {
521	error (0, retcode == -1 ? errno : 0, "failed to check out %s file",
522	       file);
523    }
524    freercsnode (&rcsnode);
525    free (rcs);
526    return (retcode);
527}
528
529#ifndef MY_NDBM
530
531static void
532write_dbmfile (temp)
533    char *temp;
534{
535    char line[DBLKSIZ], value[DBLKSIZ];
536    FILE *fp;
537    DBM *db;
538    char *cp, *vp;
539    datum key, val;
540    int len, cont, err = 0;
541
542    fp = open_file (temp, "r");
543    if ((db = dbm_open (temp, O_RDWR | O_CREAT | O_TRUNC, 0666)) == NULL)
544	error (1, errno, "cannot open dbm file %s for creation", temp);
545    for (cont = 0; fgets (line, sizeof (line), fp) != NULL;)
546    {
547	if ((cp = strrchr (line, '\n')) != NULL)
548	    *cp = '\0';			/* strip the newline */
549
550	/*
551	 * Add the line to the value, at the end if this is a continuation
552	 * line; otherwise at the beginning, but only after any trailing
553	 * backslash is removed.
554	 */
555	vp = value;
556	if (cont)
557	    vp += strlen (value);
558
559	/*
560	 * See if the line we read is a continuation line, and strip the
561	 * backslash if so.
562	 */
563	len = strlen (line);
564	if (len > 0)
565	    cp = &line[len - 1];
566	else
567	    cp = line;
568	if (*cp == '\\')
569	{
570	    cont = 1;
571	    *cp = '\0';
572	}
573	else
574	{
575	    cont = 0;
576	}
577	(void) strcpy (vp, line);
578	if (value[0] == '#')
579	    continue;			/* comment line */
580	vp = value;
581	while (*vp && isspace (*vp))
582	    vp++;
583	if (*vp == '\0')
584	    continue;			/* empty line */
585
586	/*
587	 * If this was not a continuation line, add the entry to the database
588	 */
589	if (!cont)
590	{
591	    key.dptr = vp;
592	    while (*vp && !isspace (*vp))
593		vp++;
594	    key.dsize = vp - key.dptr;
595	    *vp++ = '\0';		/* NULL terminate the key */
596	    while (*vp && isspace (*vp))
597		vp++;			/* skip whitespace to value */
598	    if (*vp == '\0')
599	    {
600		error (0, 0, "warning: NULL value for key `%s'", key.dptr);
601		continue;
602	    }
603	    val.dptr = vp;
604	    val.dsize = strlen (vp);
605	    if (dbm_store (db, key, val, DBM_INSERT) == 1)
606	    {
607		error (0, 0, "duplicate key found for `%s'", key.dptr);
608		err++;
609	    }
610	}
611    }
612    dbm_close (db);
613    (void) fclose (fp);
614    if (err)
615    {
616	char dotdir[50], dotpag[50], dotdb[50];
617
618	(void) sprintf (dotdir, "%s.dir", temp);
619	(void) sprintf (dotpag, "%s.pag", temp);
620	(void) sprintf (dotdb, "%s.db", temp);
621	(void) unlink_file (dotdir);
622	(void) unlink_file (dotpag);
623	(void) unlink_file (dotdb);
624	error (1, 0, "DBM creation failed; correct above errors");
625    }
626}
627
628static void
629rename_dbmfile (temp)
630    char *temp;
631{
632    char newdir[50], newpag[50], newdb[50];
633    char dotdir[50], dotpag[50], dotdb[50];
634    char bakdir[50], bakpag[50], bakdb[50];
635
636    (void) sprintf (dotdir, "%s.dir", CVSROOTADM_MODULES);
637    (void) sprintf (dotpag, "%s.pag", CVSROOTADM_MODULES);
638    (void) sprintf (dotdb, "%s.db", CVSROOTADM_MODULES);
639    (void) sprintf (bakdir, "%s%s.dir", BAKPREFIX, CVSROOTADM_MODULES);
640    (void) sprintf (bakpag, "%s%s.pag", BAKPREFIX, CVSROOTADM_MODULES);
641    (void) sprintf (bakdb, "%s%s.db", BAKPREFIX, CVSROOTADM_MODULES);
642    (void) sprintf (newdir, "%s.dir", temp);
643    (void) sprintf (newpag, "%s.pag", temp);
644    (void) sprintf (newdb, "%s.db", temp);
645
646    (void) chmod (newdir, 0666);
647    (void) chmod (newpag, 0666);
648    (void) chmod (newdb, 0666);
649
650    /* don't mess with me */
651    SIG_beginCrSect ();
652
653    (void) unlink_file (bakdir);	/* rm .#modules.dir .#modules.pag */
654    (void) unlink_file (bakpag);
655    (void) unlink_file (bakdb);
656    (void) CVS_RENAME (dotdir, bakdir);	/* mv modules.dir .#modules.dir */
657    (void) CVS_RENAME (dotpag, bakpag);	/* mv modules.pag .#modules.pag */
658    (void) CVS_RENAME (dotdb, bakdb);	/* mv modules.db .#modules.db */
659    (void) CVS_RENAME (newdir, dotdir);	/* mv "temp".dir modules.dir */
660    (void) CVS_RENAME (newpag, dotpag);	/* mv "temp".pag modules.pag */
661    (void) CVS_RENAME (newdb, dotdb);	/* mv "temp".db modules.db */
662
663    /* OK -- make my day */
664    SIG_endCrSect ();
665}
666
667#endif				/* !MY_NDBM */
668
669static void
670rename_rcsfile (temp, real)
671    char *temp;
672    char *real;
673{
674    char *bak;
675    struct stat statbuf;
676    char *rcs;
677
678    /* Set "x" bits if set in original. */
679    rcs = xmalloc (strlen (real) + sizeof (RCSEXT) + 10);
680    (void) sprintf (rcs, "%s%s", real, RCSEXT);
681    statbuf.st_mode = 0; /* in case rcs file doesn't exist, but it should... */
682    (void) CVS_STAT (rcs, &statbuf);
683    free (rcs);
684
685    if (chmod (temp, 0444 | (statbuf.st_mode & 0111)) < 0)
686	error (0, errno, "warning: cannot chmod %s", temp);
687    bak = xmalloc (strlen (real) + sizeof (BAKPREFIX) + 10);
688    (void) sprintf (bak, "%s%s", BAKPREFIX, real);
689    (void) unlink_file (bak);		/* rm .#loginfo */
690    (void) CVS_RENAME (real, bak);		/* mv loginfo .#loginfo */
691    (void) CVS_RENAME (temp, real);		/* mv "temp" loginfo */
692    free (bak);
693}
694
695const char *const init_usage[] = {
696    "Usage: %s %s\n",
697    NULL
698};
699
700int
701init (argc, argv)
702    int argc;
703    char **argv;
704{
705    /* Name of CVSROOT directory.  */
706    char *adm;
707    /* Name of this administrative file.  */
708    char *info;
709    /* Name of ,v file for this administrative file.  */
710    char *info_v;
711
712    const struct admin_file *fileptr;
713
714    umask (cvsumask);
715
716    if (argc == -1 || argc > 1)
717	usage (init_usage);
718
719#ifdef CLIENT_SUPPORT
720    if (client_active)
721    {
722	start_server ();
723
724	ign_setup ();
725	send_init_command ();
726	return get_responses_and_close ();
727    }
728#endif /* CLIENT_SUPPORT */
729
730    /* Note: we do *not* create parent directories as needed like the
731       old cvsinit.sh script did.  Few utilities do that, and a
732       non-existent parent directory is as likely to be a typo as something
733       which needs to be created.  */
734    mkdir_if_needed (CVSroot_directory);
735
736    adm = xmalloc (strlen (CVSroot_directory) + sizeof (CVSROOTADM) + 10);
737    strcpy (adm, CVSroot_directory);
738    strcat (adm, "/");
739    strcat (adm, CVSROOTADM);
740    mkdir_if_needed (adm);
741
742    /* This is needed by the call to "ci" below.  */
743    if ( CVS_CHDIR (adm) < 0)
744	error (1, errno, "cannot change to directory %s", adm);
745
746    /* 80 is long enough for all the administrative file names, plus
747       "/" and so on.  */
748    info = xmalloc (strlen (adm) + 80);
749    info_v = xmalloc (strlen (adm) + 80);
750    for (fileptr = filelist; fileptr && fileptr->filename; ++fileptr)
751    {
752	if (fileptr->contents == NULL)
753	    continue;
754	strcpy (info, adm);
755	strcat (info, "/");
756	strcat (info, fileptr->filename);
757	strcpy (info_v, info);
758	strcat (info_v, RCSEXT);
759	if (isfile (info_v))
760	    /* We will check out this file in the mkmodules step.
761	       Nothing else is required.  */
762	    ;
763	else
764	{
765	    int retcode;
766
767	    if (!isfile (info))
768	    {
769		FILE *fp;
770		const char * const *p;
771
772		fp = open_file (info, "w");
773		for (p = fileptr->contents; *p != NULL; ++p)
774		    if (fputs (*p, fp) < 0)
775			error (1, errno, "cannot write %s", info);
776		if (fclose (fp) < 0)
777		    error (1, errno, "cannot close %s", info);
778	    }
779	    /* Now check the file in.  FIXME: we could be using
780	       add_rcs_file from import.c which is faster (if it were
781	       tweaked slightly).  */
782	    run_setup ("%s%s -x,v/ -q -u -t-", Rcsbin, RCS_CI);
783	    run_args ("-minitial checkin of %s", fileptr->filename);
784	    run_arg (fileptr->filename);
785	    retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
786	    if (retcode != 0)
787		error (1, retcode == -1 ? errno : 0,
788		       "failed to check in %s", info);
789	}
790    }
791
792    /* Turn on history logging by default.  The user can remove the file
793       to disable it.  */
794    strcpy (info, adm);
795    strcat (info, "/");
796    strcat (info, CVSROOTADM_HISTORY);
797    if (!isfile (info))
798    {
799	FILE *fp;
800
801	fp = open_file (info, "w");
802	if (fclose (fp) < 0)
803	    error (1, errno, "cannot close %s", info);
804    }
805
806    free (info);
807    free (info_v);
808
809    mkmodules (adm);
810
811    free (adm);
812    return 0;
813}
814