mkmodules.c revision 17721
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
11#ifndef DBLKSIZ
12#define	DBLKSIZ	4096			/* since GNU ndbm doesn't define it */
13#endif
14
15static int checkout_file PROTO((char *file, char *temp));
16static void make_tempfile PROTO((char *temp));
17static void rename_rcsfile PROTO((char *temp, char *real));
18
19#ifndef MY_NDBM
20static void rename_dbmfile PROTO((char *temp));
21static void write_dbmfile PROTO((char *temp));
22#endif				/* !MY_NDBM */
23
24/* Structure which describes an administrative file.  */
25struct admin_file {
26   /* Name of the file, within the CVSROOT directory.  */
27   char *filename;
28
29   /* This is a one line description of what the file is for.  It is not
30      currently used, although one wonders whether it should be, somehow.
31      If NULL, then don't process this file in mkmodules (FIXME: a bit of
32      a kludge; probably should replace this with a flags field).  */
33   char *errormsg;
34
35   /* Contents which the file should have in a new repository.  To avoid
36      problems with brain-dead compilers which choke on long string constants,
37      this is a pointer to an array of char * terminated by NULL--each of
38      the strings is concatenated.  */
39   const char * const *contents;
40};
41
42static const char *const loginfo_contents[] = {
43    "# The \"loginfo\" file is used to control where \"cvs commit\" log information\n",
44    "# is sent.  The first entry on a line is a regular expression which is tested\n",
45    "# against the directory that the change is being made to, relative to the\n",
46    "# $CVSROOT.  For the first match that is found, then the remainder of the\n",
47    "# line is a filter program that should expect log information on its standard\n",
48    "# input.\n",
49    "#\n",
50    "# If the repository name does not match any of the regular expressions in the\n",
51    "# first field of this file, the \"DEFAULT\" line is used, if it is specified.\n",
52    "#\n",
53    "# If the name \"ALL\" appears as a regular expression it is always used\n",
54    "# in addition to the first matching regex or \"DEFAULT\".\n",
55    "#\n",
56    "# The filter program may use one and only one \"%s\" modifier (ala printf).  If\n",
57    "# such a \"%s\" is specified in the filter program, a brief title is included\n",
58    "# (as one argument, enclosed in single quotes) showing the relative directory\n",
59    "# name and listing the modified file names.\n",
60    "#\n",
61    "# For example:\n",
62    "#DEFAULT		(echo \"\"; who am i; date; cat) >> $CVSROOT/CVSROOT/commitlog\n",
63    NULL
64};
65
66static const char *const rcsinfo_contents[] = {
67    "# The \"rcsinfo\" file is used to control templates with which the editor\n",
68    "# is invoked on commit and import.\n",
69    "#\n",
70    "# The first entry on a line is a regular expression which is tested\n",
71    "# against the directory that the change is being made to, relative to the\n",
72    "# $CVSROOT.  For the first match that is found, then the remainder of the\n",
73    "# line is the name of the file that contains the template.\n",
74    "#\n",
75    "# If the repository name does not match any of the regular expressions in this\n",
76    "# file, the \"DEFAULT\" line is used, if it is specified.\n",
77    "#\n",
78    "# If the name \"ALL\" appears as a regular expression it is always used\n",
79    "# in addition to the first matching regex or \"DEFAULT\".\n",
80    NULL
81};
82
83static const char *const editinfo_contents[] = {
84    "# The \"editinfo\" file is used to allow verification of logging\n",
85    "# information.  It works best when a template (as specified in the\n",
86    "# rcsinfo file) is provided for the logging procedure.  Given a\n",
87    "# template with locations for, a bug-id number, a list of people who\n",
88    "# reviewed the code before it can be checked in, and an external\n",
89    "# process to catalog the differences that were code reviewed, the\n",
90    "# following test can be applied to the code:\n",
91    "#\n",
92    "#   Making sure that the entered bug-id number is correct.\n",
93    "#   Validating that the code that was reviewed is indeed the code being\n",
94    "#       checked in (using the bug-id number or a seperate review\n",
95    "#       number to identify this particular code set.).\n",
96    "#\n",
97    "# If any of the above test failed, then the commit would be aborted.\n",
98    "#\n",
99    "# Actions such as mailing a copy of the report to each reviewer are\n",
100    "# better handled by an entry in the loginfo file.\n",
101    "#\n",
102    "# One thing that should be noted  is the the ALL keyword is not\n",
103    "# supported. There can be only one entry that matches a given\n",
104    "# repository.\n",
105    NULL
106};
107
108static const char *const commitinfo_contents[] = {
109    "# The \"commitinfo\" file is used to control pre-commit checks.\n",
110    "# The filter on the right is invoked with the repository and a list \n",
111    "# of files to check.  A non-zero exit of the filter program will \n",
112    "# cause the commit to be aborted.\n",
113    "#\n",
114    "# The first entry on a line is a regular expression which is tested\n",
115    "# against the directory that the change is being committed to, relative\n",
116    "# to the $CVSROOT.  For the first match that is found, then the remainder\n",
117    "# of the line is the name of the filter to run.\n",
118    "#\n",
119    "# If the repository name does not match any of the regular expressions in this\n",
120    "# file, the \"DEFAULT\" line is used, if it is specified.\n",
121    "#\n",
122    "# If the name \"ALL\" appears as a regular expression it is always used\n",
123    "# in addition to the first matching regex or \"DEFAULT\".\n",
124    NULL
125};
126
127static const char *const taginfo_contents[] = {
128    "# The \"taginfo\" file is used to control pre-tag checks.\n",
129    "# The filter on the right is invoked with the following arguments:\n",
130    "#\n",
131    "# $1 -- tagname\n",
132    "# $2 -- operation \"add\" for tag, \"mov\" for tag -F, and \"del\" for tag -d\n",
133    "# $3 -- repository\n",
134    "# $4->  file revision [file revision ...]\n",
135    "#\n",
136    "# A non-zero exit of the filter program will cause the tag to be aborted.\n",
137    "#\n",
138    "# The first entry on a line is a regular expression which is tested\n",
139    "# against the directory that the change is being committed to, relative\n",
140    "# to the $CVSROOT.  For the first match that is found, then the remainder\n",
141    "# of the line is the name of the filter to run.\n",
142    "#\n",
143    "# If the repository name does not match any of the regular expressions in this\n",
144    "# file, the \"DEFAULT\" line is used, if it is specified.\n",
145    "#\n",
146    "# If the name \"ALL\" appears as a regular expression it is always used\n",
147    "# in addition to the first matching regex or \"DEFAULT\".\n",
148    NULL
149};
150
151static const char *const checkoutlist_contents[] = {
152    "# The \"checkoutlist\" file is used to support additional version controlled\n",
153    "# administrative files in $CVSROOT/CVSROOT, such as template files.\n",
154    "#\n",
155    "# The first entry on a line is a filename which will be checked out from\n",
156    "# the corresponding RCS file in the $CVSROOT/CVSROOT directory.\n",
157    "# The remainder of the line is an error message to use if the file cannot\n",
158    "# be checked out.\n",
159    "#\n",
160    "# File format:\n",
161    "#\n",
162    "#	[<whitespace>]<filename><whitespace><error message><end-of-line>\n",
163    "#\n",
164    "# comment lines begin with '#'\n",
165    NULL
166};
167
168static const char *const cvswrappers_contents[] = {
169    "# This file describes wrappers and other binary files to CVS.\n",
170    "#\n",
171    "# Wrappers are the concept where directories of files are to be\n",
172    "# treated as a single file.  The intended use is to wrap up a wrapper\n",
173    "# into a single tar such that the tar archive can be treated as a\n",
174    "# single binary file in CVS.\n",
175    "#\n",
176    "# To solve the problem effectively, it was also necessary to be able to\n",
177    "# prevent rcsmerge from merging these files.\n",
178    "#\n",
179    "# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)\n",
180    "#\n",
181    "#  wildcard	[option value][option value]...\n",
182    "#\n",
183    "#  where option is one of\n",
184    "#  -f		from cvs filter		value: path to filter\n",
185    "#  -t		to cvs filter		value: path to filter\n",
186    "#  -m		update methodology	value: MERGE or COPY\n",
187    "#\n",
188    "#  and value is a single-quote delimited value.\n",
189    "#\n",
190    "# For example:\n",
191    NULL
192};
193
194static const char *const notify_contents[] = {
195    "# The \"notify\" file controls where notifications from watches set by\n",
196    "# \"cvs watch add\" or \"cvs edit\" are sent.  The first entry on a line is\n",
197    "# a regular expression which is tested against the directory that the\n",
198    "# change is being made to, relative to the $CVSROOT.  If it matches,\n",
199    "# then the remainder of the line is a filter program that should contain\n",
200    "# one occurrence of %s for the user to notify, and information on its\n",
201    "# standard input.\n",
202    "#\n",
203    "# \"ALL\" or \"DEFAULT\" can be used in place of the regular expression.\n",
204    "#\n",
205    "# For example:\n",
206    "#ALL mail %s -s \"CVS notification\"\n",
207    NULL
208};
209
210static const char *const modules_contents[] = {
211    "# Three different line formats are valid:\n",
212    "#	key	-a    aliases...\n",
213    "#	key [options] directory\n",
214    "#	key [options] directory files...\n",
215    "#\n",
216    "# Where \"options\" are composed of:\n",
217    "#	-i prog		Run \"prog\" on \"cvs commit\" from top-level of module.\n",
218    "#	-o prog		Run \"prog\" on \"cvs checkout\" of module.\n",
219    "#	-e prog		Run \"prog\" on \"cvs export\" of module.\n",
220    "#	-t prog		Run \"prog\" on \"cvs rtag\" of module.\n",
221    "#	-u prog		Run \"prog\" on \"cvs update\" of module.\n",
222    "#	-d dir		Place module in directory \"dir\" instead of module name.\n",
223    "#	-l		Top-level directory only -- do not recurse.\n",
224    "#\n",
225    "# And \"directory\" is a path to a directory relative to $CVSROOT.\n",
226    "#\n",
227    "# The \"-a\" option specifies an alias.  An alias is interpreted as if\n",
228    "# everything on the right of the \"-a\" had been typed on the command line.\n",
229    "#\n",
230    "# You can encode a module within a module by using the special '&'\n",
231    "# character to interpose another module into the current module.  This\n",
232    "# can be useful for creating a module that consists of many directories\n",
233    "# spread out over the entire source repository.\n",
234    NULL
235};
236
237static const struct admin_file filelist[] = {
238    {CVSROOTADM_LOGINFO,
239	"no logging of 'cvs commit' messages is done without a %s file",
240	&loginfo_contents[0]},
241    {CVSROOTADM_RCSINFO,
242	"a %s file can be used to configure 'cvs commit' templates",
243	rcsinfo_contents},
244    {CVSROOTADM_EDITINFO,
245	"a %s file can be used to validate log messages",
246	editinfo_contents},
247    {CVSROOTADM_COMMITINFO,
248	"a %s file can be used to configure 'cvs commit' checking",
249	commitinfo_contents},
250    {CVSROOTADM_TAGINFO,
251	"a %s file can be used to configure 'cvs tag' checking",
252	taginfo_contents},
253    {CVSROOTADM_IGNORE,
254	"a %s file can be used to specify files to ignore",
255	NULL},
256    {CVSROOTADM_CHECKOUTLIST,
257	"a %s file can specify extra CVSROOT files to auto-checkout",
258	checkoutlist_contents},
259    {CVSROOTADM_WRAPPER,
260	"a %s file can be used to specify files to treat as wrappers",
261	cvswrappers_contents},
262    {CVSROOTADM_NOTIFY,
263	"a %s file can be used to specify where notifications go",
264	notify_contents},
265    {CVSROOTADM_MODULES,
266	/* modules is special-cased in mkmodules.  */
267	NULL,
268	modules_contents},
269    {NULL, NULL}
270};
271
272/* Rebuild the checked out administrative files in directory DIR.  */
273int
274mkmodules (dir)
275    char *dir;
276{
277    struct saved_cwd cwd;
278    /* FIXME: arbitrary limit */
279    char temp[PATH_MAX];
280    char *cp, *last, *fname;
281#ifdef MY_NDBM
282    DBM *db;
283#endif
284    FILE *fp;
285    /* FIXME: arbitrary limit */
286    char line[512];
287    const struct admin_file *fileptr;
288
289    if (save_cwd (&cwd))
290	exit (EXIT_FAILURE);
291
292    if (chdir (dir) < 0)
293	error (1, errno, "cannot chdir to %s", dir);
294
295    /*
296     * First, do the work necessary to update the "modules" database.
297     */
298    make_tempfile (temp);
299    switch (checkout_file (CVSROOTADM_MODULES, temp))
300    {
301
302	case 0:			/* everything ok */
303#ifdef MY_NDBM
304	    /* open it, to generate any duplicate errors */
305	    if ((db = dbm_open (temp, O_RDONLY, 0666)) != NULL)
306		dbm_close (db);
307#else
308	    write_dbmfile (temp);
309	    rename_dbmfile (temp);
310#endif
311	    rename_rcsfile (temp, CVSROOTADM_MODULES);
312	    break;
313
314	case -1:			/* fork failed */
315	    (void) unlink_file (temp);
316	    exit (EXIT_FAILURE);
317	    /* NOTREACHED */
318
319	default:
320	    error (0, 0,
321		"'cvs checkout' is less functional without a %s file",
322		CVSROOTADM_MODULES);
323	    break;
324    }					/* switch on checkout_file() */
325
326    (void) unlink_file (temp);
327
328    /* Checkout the files that need it in CVSROOT dir */
329    for (fileptr = filelist; fileptr && fileptr->filename; fileptr++) {
330	if (fileptr->errormsg == NULL)
331	    continue;
332	make_tempfile (temp);
333	if (checkout_file (fileptr->filename, temp) == 0)
334	    rename_rcsfile (temp, fileptr->filename);
335#if 0
336	/*
337	 * If there was some problem other than the file not existing,
338	 * checkout_file already printed a real error message.  If the
339	 * file does not exist, it is harmless--it probably just means
340	 * that the repository was created with an old version of CVS
341	 * which didn't have so many files in CVSROOT.
342	 */
343	else if (fileptr->errormsg)
344	    error (0, 0, fileptr->errormsg, fileptr->filename);
345#endif
346	(void) unlink_file (temp);
347    }
348
349    /* Use 'fopen' instead of 'open_file' because we want to ignore error */
350    fp = fopen (CVSROOTADM_CHECKOUTLIST, "r");
351    if (fp)
352    {
353	/*
354	 * File format:
355	 *  [<whitespace>]<filename><whitespace><error message><end-of-line>
356	 *
357	 * comment lines begin with '#'
358	 */
359	while (fgets (line, sizeof (line), fp) != NULL)
360	{
361	    /* skip lines starting with # */
362	    if (line[0] == '#')
363		continue;
364
365	    if ((last = strrchr (line, '\n')) != NULL)
366		*last = '\0';			/* strip the newline */
367
368	    /* Skip leading white space. */
369	    for (fname = line; *fname && isspace(*fname); fname++)
370		;
371
372	    /* Find end of filename. */
373	    for (cp = fname; *cp && !isspace(*cp); cp++)
374		;
375	    *cp = '\0';
376
377	    make_tempfile (temp);
378	    if (checkout_file (fname, temp) == 0)
379	    {
380		rename_rcsfile (temp, fname);
381	    }
382	    else
383	    {
384		for (cp++; cp < last && *last && isspace(*last); cp++)
385		    ;
386		if (cp < last && *cp)
387		    error (0, 0, cp, fname);
388	    }
389	}
390	(void) fclose (fp);
391    }
392
393    if (restore_cwd (&cwd, NULL))
394	exit (EXIT_FAILURE);
395    free_cwd (&cwd);
396
397    return (0);
398}
399
400/*
401 * Yeah, I know, there are NFS race conditions here.
402 */
403static void
404make_tempfile (temp)
405    char *temp;
406{
407    static int seed = 0;
408    int fd;
409
410    if (seed == 0)
411	seed = getpid ();
412    while (1)
413    {
414	(void) sprintf (temp, "%s%d", BAKPREFIX, seed++);
415	if ((fd = open (temp, O_CREAT|O_EXCL|O_RDWR, 0666)) != -1)
416	    break;
417	if (errno != EEXIST)
418	    error (1, errno, "cannot create temporary file %s", temp);
419    }
420    if (close(fd) < 0)
421	error(1, errno, "cannot close temporary file %s", temp);
422}
423
424static int
425checkout_file (file, temp)
426    char *file;
427    char *temp;
428{
429    char rcs[PATH_MAX];
430    int retcode = 0;
431
432    (void) sprintf (rcs, "%s%s", file, RCSEXT);
433    if (!isfile (rcs))
434	return (1);
435    run_setup ("%s%s -x,v/ -q -p", Rcsbin, RCS_CO);
436    run_arg (rcs);
437    if ((retcode = run_exec (RUN_TTY, temp, RUN_TTY, RUN_NORMAL)) != 0)
438    {
439	error (0, retcode == -1 ? errno : 0, "failed to check out %s file", file);
440    }
441    return (retcode);
442}
443
444#ifndef MY_NDBM
445
446static void
447write_dbmfile (temp)
448    char *temp;
449{
450    char line[DBLKSIZ], value[DBLKSIZ];
451    FILE *fp;
452    DBM *db;
453    char *cp, *vp;
454    datum key, val;
455    int len, cont, err = 0;
456
457    fp = open_file (temp, "r");
458    if ((db = dbm_open (temp, O_RDWR | O_CREAT | O_TRUNC, 0666)) == NULL)
459	error (1, errno, "cannot open dbm file %s for creation", temp);
460    for (cont = 0; fgets (line, sizeof (line), fp) != NULL;)
461    {
462	if ((cp = strrchr (line, '\n')) != NULL)
463	    *cp = '\0';			/* strip the newline */
464
465	/*
466	 * Add the line to the value, at the end if this is a continuation
467	 * line; otherwise at the beginning, but only after any trailing
468	 * backslash is removed.
469	 */
470	vp = value;
471	if (cont)
472	    vp += strlen (value);
473
474	/*
475	 * See if the line we read is a continuation line, and strip the
476	 * backslash if so.
477	 */
478	len = strlen (line);
479	if (len > 0)
480	    cp = &line[len - 1];
481	else
482	    cp = line;
483	if (*cp == '\\')
484	{
485	    cont = 1;
486	    *cp = '\0';
487	}
488	else
489	{
490	    cont = 0;
491	}
492	(void) strcpy (vp, line);
493	if (value[0] == '#')
494	    continue;			/* comment line */
495	vp = value;
496	while (*vp && isspace (*vp))
497	    vp++;
498	if (*vp == '\0')
499	    continue;			/* empty line */
500
501	/*
502	 * If this was not a continuation line, add the entry to the database
503	 */
504	if (!cont)
505	{
506	    key.dptr = vp;
507	    while (*vp && !isspace (*vp))
508		vp++;
509	    key.dsize = vp - key.dptr;
510	    *vp++ = '\0';		/* NULL terminate the key */
511	    while (*vp && isspace (*vp))
512		vp++;			/* skip whitespace to value */
513	    if (*vp == '\0')
514	    {
515		error (0, 0, "warning: NULL value for key `%s'", key.dptr);
516		continue;
517	    }
518	    val.dptr = vp;
519	    val.dsize = strlen (vp);
520	    if (dbm_store (db, key, val, DBM_INSERT) == 1)
521	    {
522		error (0, 0, "duplicate key found for `%s'", key.dptr);
523		err++;
524	    }
525	}
526    }
527    dbm_close (db);
528    (void) fclose (fp);
529    if (err)
530    {
531	char dotdir[50], dotpag[50], dotdb[50];
532
533	(void) sprintf (dotdir, "%s.dir", temp);
534	(void) sprintf (dotpag, "%s.pag", temp);
535	(void) sprintf (dotdb, "%s.db", temp);
536	(void) unlink_file (dotdir);
537	(void) unlink_file (dotpag);
538	(void) unlink_file (dotdb);
539	error (1, 0, "DBM creation failed; correct above errors");
540    }
541}
542
543static void
544rename_dbmfile (temp)
545    char *temp;
546{
547    char newdir[50], newpag[50], newdb[50];
548    char dotdir[50], dotpag[50], dotdb[50];
549    char bakdir[50], bakpag[50], bakdb[50];
550
551    (void) sprintf (dotdir, "%s.dir", CVSROOTADM_MODULES);
552    (void) sprintf (dotpag, "%s.pag", CVSROOTADM_MODULES);
553    (void) sprintf (dotdb, "%s.db", CVSROOTADM_MODULES);
554    (void) sprintf (bakdir, "%s%s.dir", BAKPREFIX, CVSROOTADM_MODULES);
555    (void) sprintf (bakpag, "%s%s.pag", BAKPREFIX, CVSROOTADM_MODULES);
556    (void) sprintf (bakdb, "%s%s.db", BAKPREFIX, CVSROOTADM_MODULES);
557    (void) sprintf (newdir, "%s.dir", temp);
558    (void) sprintf (newpag, "%s.pag", temp);
559    (void) sprintf (newdb, "%s.db", temp);
560
561    (void) chmod (newdir, 0666);
562    (void) chmod (newpag, 0666);
563    (void) chmod (newdb, 0666);
564
565    /* don't mess with me */
566    SIG_beginCrSect ();
567
568    (void) unlink_file (bakdir);	/* rm .#modules.dir .#modules.pag */
569    (void) unlink_file (bakpag);
570    (void) unlink_file (bakdb);
571    (void) rename (dotdir, bakdir);	/* mv modules.dir .#modules.dir */
572    (void) rename (dotpag, bakpag);	/* mv modules.pag .#modules.pag */
573    (void) rename (dotdb, bakdb);	/* mv modules.db .#modules.db */
574    (void) rename (newdir, dotdir);	/* mv "temp".dir modules.dir */
575    (void) rename (newpag, dotpag);	/* mv "temp".pag modules.pag */
576    (void) rename (newdb, dotdb);	/* mv "temp".db modules.db */
577
578    /* OK -- make my day */
579    SIG_endCrSect ();
580}
581
582#endif				/* !MY_NDBM */
583
584static void
585rename_rcsfile (temp, real)
586    char *temp;
587    char *real;
588{
589    char bak[50];
590    struct stat statbuf;
591    char rcs[PATH_MAX];
592
593    /* Set "x" bits if set in original. */
594    (void) sprintf (rcs, "%s%s", real, RCSEXT);
595    statbuf.st_mode = 0; /* in case rcs file doesn't exist, but it should... */
596    (void) stat (rcs, &statbuf);
597
598    if (chmod (temp, 0444 | (statbuf.st_mode & 0111)) < 0)
599	error (0, errno, "warning: cannot chmod %s", temp);
600    (void) sprintf (bak, "%s%s", BAKPREFIX, real);
601    (void) unlink_file (bak);		/* rm .#loginfo */
602    (void) rename (real, bak);		/* mv loginfo .#loginfo */
603    (void) rename (temp, real);		/* mv "temp" loginfo */
604}
605
606const char *const init_usage[] = {
607    "Usage: %s %s\n",
608    NULL
609};
610
611/* Create directory NAME if it does not already exist; fatal error for
612   other errors.  FIXME: This should be in filesubr.c or thereabouts,
613   probably.  Perhaps it should be further abstracted, though (for example
614   to handle CVSUMASK where appropriate?).  */
615static void
616mkdir_if_needed (name)
617    char *name;
618{
619    if (CVS_MKDIR (name, 0777) < 0)
620    {
621	if (errno != EEXIST
622#ifdef EACCESS
623	    /* OS/2; see longer comment in client.c.  */
624	    && errno != EACCESS
625#endif
626	    )
627	    error (1, errno, "cannot mkdir %s", name);
628    }
629}
630
631int
632init (argc, argv)
633    int argc;
634    char **argv;
635{
636    /* Name of CVSROOT directory.  */
637    char *adm;
638    /* Name of this administrative file.  */
639    char *info;
640    /* Name of ,v file for this administrative file.  */
641    char *info_v;
642
643    const struct admin_file *fileptr;
644
645    umask (cvsumask);
646
647    if (argc > 1)
648	usage (init_usage);
649
650    if (client_active)
651    {
652	start_server ();
653
654	ign_setup ();
655	send_init_command ();
656	return get_responses_and_close ();
657    }
658
659    /* Note: we do *not* create parent directories as needed like the
660       old cvsinit.sh script did.  Few utilities do that, and a
661       non-existent parent directory is as likely to be a typo as something
662       which needs to be created.  */
663    mkdir_if_needed (CVSroot);
664
665    adm = xmalloc (strlen (CVSroot) + sizeof (CVSROOTADM) + 10);
666    strcpy (adm, CVSroot);
667    strcat (adm, "/");
668    strcat (adm, CVSROOTADM);
669    mkdir_if_needed (adm);
670
671    /* This is needed by the call to "ci" below.  */
672    if (chdir (adm) < 0)
673	error (1, errno, "cannot change to directory %s", adm);
674
675    /* 80 is long enough for all the administrative file names, plus
676       "/" and so on.  */
677    info = xmalloc (strlen (adm) + 80);
678    info_v = xmalloc (strlen (adm) + 80);
679    for (fileptr = filelist; fileptr && fileptr->filename; ++fileptr)
680    {
681	if (fileptr->contents == NULL)
682	    continue;
683	strcpy (info, adm);
684	strcat (info, "/");
685	strcat (info, fileptr->filename);
686	strcpy (info_v, info);
687	strcat (info_v, RCSEXT);
688	if (isfile (info_v))
689	    /* We will check out this file in the mkmodules step.
690	       Nothing else is required.  */
691	    ;
692	else
693	{
694	    int retcode;
695
696	    if (!isfile (info))
697	    {
698		FILE *fp;
699		const char * const *p;
700
701		fp = open_file (info, "w");
702		for (p = fileptr->contents; *p != NULL; ++p)
703		    if (fputs (*p, fp) < 0)
704			error (1, errno, "cannot write %s", info);
705		if (fclose (fp) < 0)
706		    error (1, errno, "cannot close %s", info);
707	    }
708	    /* Now check the file in.  FIXME: we could be using
709	       add_rcs_file from import.c which is faster (if it were
710	       tweaked slightly).  */
711	    run_setup ("%s%s -x,v/ -q -u -t-", Rcsbin, RCS_CI);
712	    run_args ("-minitial checkin of %s", fileptr->filename);
713	    run_arg (fileptr->filename);
714	    retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
715	    if (retcode != 0)
716		error (1, retcode == -1 ? errno : 0,
717		       "failed to check in %s", info);
718	}
719    }
720
721    /* Turn on history logging by default.  The user can remove the file
722       to disable it.  */
723    strcpy (info, adm);
724    strcat (info, "/");
725    strcat (info, CVSROOTADM_HISTORY);
726    if (!isfile (info))
727    {
728	FILE *fp;
729
730	fp = open_file (info, "w");
731	if (fclose (fp) < 0)
732	    error (1, errno, "cannot close %s", info);
733    }
734
735    free (info);
736    free (info_v);
737
738    mkmodules (adm);
739
740    free (adm);
741    return 0;
742}
743