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