]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/cvs/src/mkmodules.c
Import cvs-1.9.10
[FreeBSD/FreeBSD.git] / contrib / cvs / src / mkmodules.c
1 /*
2  * Copyright (c) 1992, Brian Berliner and Jeff Polk
3  * Copyright (c) 1989-1992, Brian Berliner
4  * 
5  * You may distribute under the terms of the GNU General Public License as
6  * specified in the README file that comes with the CVS kit.  */
7
8 #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 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
232 static 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
248 static 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
278 static 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.  */
331 int
332 mkmodules (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  */
473 static char *
474 make_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
496 static int
497 checkout_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
531 static void
532 write_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
628 static void
629 rename_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
669 static void
670 rename_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 \f
695 const char *const init_usage[] = {
696     "Usage: %s %s\n",
697     NULL
698 };
699
700 int
701 init (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     /* Exit status.  */
712     int err;
713
714     const struct admin_file *fileptr;
715
716     umask (cvsumask);
717
718     if (argc == -1 || argc > 1)
719         usage (init_usage);
720
721 #ifdef CLIENT_SUPPORT
722     if (client_active)
723     {
724         start_server ();
725
726         ign_setup ();
727         send_init_command ();
728         return get_responses_and_close ();
729     }
730 #endif /* CLIENT_SUPPORT */
731
732     /* Note: we do *not* create parent directories as needed like the
733        old cvsinit.sh script did.  Few utilities do that, and a
734        non-existent parent directory is as likely to be a typo as something
735        which needs to be created.  */
736     mkdir_if_needed (CVSroot_directory);
737
738     adm = xmalloc (strlen (CVSroot_directory) + sizeof (CVSROOTADM) + 10);
739     strcpy (adm, CVSroot_directory);
740     strcat (adm, "/");
741     strcat (adm, CVSROOTADM);
742     mkdir_if_needed (adm);
743
744     /* This is needed because we pass "fileptr->filename" not "info"
745        to add_rcs_file below.  I think this would be easy to change,
746        thus nuking the need for CVS_CHDIR here, but I haven't looked
747        closely (e.g. see wrappers calls within add_rcs_file).  */
748     if ( CVS_CHDIR (adm) < 0)
749         error (1, errno, "cannot change to directory %s", adm);
750
751     /* 80 is long enough for all the administrative file names, plus
752        "/" and so on.  */
753     info = xmalloc (strlen (adm) + 80);
754     info_v = xmalloc (strlen (adm) + 80);
755     for (fileptr = filelist; fileptr && fileptr->filename; ++fileptr)
756     {
757         if (fileptr->contents == NULL)
758             continue;
759         strcpy (info, adm);
760         strcat (info, "/");
761         strcat (info, fileptr->filename);
762         strcpy (info_v, info);
763         strcat (info_v, RCSEXT);
764         if (isfile (info_v))
765             /* We will check out this file in the mkmodules step.
766                Nothing else is required.  */
767             ;
768         else
769         {
770             int retcode;
771
772             if (!isfile (info))
773             {
774                 FILE *fp;
775                 const char * const *p;
776
777                 fp = open_file (info, "w");
778                 for (p = fileptr->contents; *p != NULL; ++p)
779                     if (fputs (*p, fp) < 0)
780                         error (1, errno, "cannot write %s", info);
781                 if (fclose (fp) < 0)
782                     error (1, errno, "cannot close %s", info);
783             }
784             /* The message used to say " of " and fileptr->filename after
785                "initial checkin" but I fail to see the point as we know what
786                file it is from the name.  */
787             retcode = add_rcs_file ("initial checkin", info_v,
788                                     fileptr->filename, "1.1", NULL, NULL,
789                                     0, NULL, NULL);
790             if (retcode != 0)
791                 /* add_rcs_file already printed an error message.  */
792                 err = 1;
793         }
794     }
795
796     /* Turn on history logging by default.  The user can remove the file
797        to disable it.  */
798     strcpy (info, adm);
799     strcat (info, "/");
800     strcat (info, CVSROOTADM_HISTORY);
801     if (!isfile (info))
802     {
803         FILE *fp;
804
805         fp = open_file (info, "w");
806         if (fclose (fp) < 0)
807             error (1, errno, "cannot close %s", info);
808     }
809
810     free (info);
811     free (info_v);
812
813     mkmodules (adm);
814
815     free (adm);
816     return 0;
817 }