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