2 * Copyright (c) 1992, Brian Berliner and Jeff Polk
3 * Copyright (c) 1989-1992, Brian Berliner
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. */
13 #define DBLKSIZ 4096 /* since GNU ndbm doesn't define it */
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));
21 static void rename_dbmfile PROTO((char *temp));
22 static void write_dbmfile PROTO((char *temp));
25 /* Structure which describes an administrative file. */
27 /* Name of the file, within the CVSROOT directory. */
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). */
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.
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;
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",
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",
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",
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",
69 "# V = old version number (pre-checkin)\n",
70 "# v = new version number (post-checkin)\n",
73 "#DEFAULT (echo \"\"; id; echo %s; date; cat) >> $CVSROOT/CVSROOT/commitlog\n",
75 "#DEFAULT (echo \"\"; id; echo %{sVv}; date; cat) >> $CVSROOT/CVSROOT/commitlog\n",
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",
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",
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",
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",
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",
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",
110 "# If any of the above test failed, then the commit would be aborted.\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",
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",
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",
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",
135 "# If any of the above test failed, then the commit would be aborted.\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",
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",
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",
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",
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",
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",
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",
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",
174 "# A non-zero exit of the filter program will cause the tag to be aborted.\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",
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",
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",
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",
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",
200 "# [<whitespace>]<filename><whitespace><error message><end-of-line>\n",
202 "# comment lines begin with '#'\n",
206 static const char *const cvswrappers_contents[] = {
207 "# This file describes wrappers and other binary files to CVS.\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",
214 "# To solve the problem effectively, it was also necessary to be able to\n",
215 "# prevent rcsmerge from merging these files.\n",
217 "# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)\n",
219 "# wildcard [option value][option value]...\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",
226 "# and value is a single-quote delimited value.\n",
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",
241 "# \"ALL\" or \"DEFAULT\" can be used in place of the regular expression.\n",
244 "#ALL mail %s -s \"CVS notification\"\n",
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",
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",
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",
266 "# And \"directory\" is a path to a directory relative to $CVSROOT.\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",
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",
278 static const struct admin_file filelist[] = {
280 "no logging of 'cvs commit' messages is done without a %s file",
281 &loginfo_contents[0]},
283 "a %s file can be used to configure 'cvs commit' templates",
285 {CVSROOTADM_EDITINFO,
286 "a %s file can be used to validate log messages",
288 {CVSROOTADM_VERIFYMSG,
289 "a %s file can be used to validate log messages",
291 {CVSROOTADM_COMMITINFO,
292 "a %s file can be used to configure 'cvs commit' checking",
293 commitinfo_contents},
295 "a %s file can be used to configure 'cvs tag' checking",
298 "a %s file can be used to specify files to ignore",
300 {CVSROOTADM_CHECKOUTLIST,
301 "a %s file can specify extra CVSROOT files to auto-checkout",
302 checkoutlist_contents},
304 "a %s file can be used to specify files to treat as wrappers",
305 cvswrappers_contents},
307 "a %s file can be used to specify where notifications go",
310 /* modules is special-cased in mkmodules. */
314 "a %s file specifies read-only users",
317 "a %s file specifies read/write users",
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,
330 /* Rebuild the checked out administrative files in directory DIR. */
335 struct saved_cwd cwd;
337 char *cp, *last, *fname;
343 size_t line_allocated = 0;
344 const struct admin_file *fileptr;
349 if ( CVS_CHDIR (dir) < 0)
350 error (1, errno, "cannot chdir to %s", dir);
353 * First, do the work necessary to update the "modules" database.
355 temp = make_tempfile ();
356 switch (checkout_file (CVSROOTADM_MODULES, temp))
359 case 0: /* everything ok */
361 /* open it, to generate any duplicate errors */
362 if ((db = dbm_open (temp, O_RDONLY, 0666)) != NULL)
365 write_dbmfile (temp);
366 rename_dbmfile (temp);
368 rename_rcsfile (temp, CVSROOTADM_MODULES);
371 case -1: /* fork failed */
372 (void) unlink_file (temp);
373 error (1, errno, "cannot check out %s", CVSROOTADM_MODULES);
378 "'cvs checkout' is less functional without a %s file",
381 } /* switch on checkout_file() */
383 (void) unlink_file (temp);
386 /* Checkout the files that need it in CVSROOT dir */
387 for (fileptr = filelist; fileptr && fileptr->filename; fileptr++) {
388 if (fileptr->errormsg == NULL)
390 temp = make_tempfile ();
391 if (checkout_file (fileptr->filename, temp) == 0)
392 rename_rcsfile (temp, fileptr->filename);
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.
401 else if (fileptr->errormsg)
402 error (0, 0, fileptr->errormsg, fileptr->filename);
404 (void) unlink_file (temp);
408 fp = CVS_FOPEN (CVSROOTADM_CHECKOUTLIST, "r");
413 * [<whitespace>]<filename><whitespace><error message><end-of-line>
415 * comment lines begin with '#'
417 while (getline (&line, &line_allocated, fp) >= 0)
419 /* skip lines starting with # */
423 if ((last = strrchr (line, '\n')) != NULL)
424 *last = '\0'; /* strip the newline */
426 /* Skip leading white space. */
427 for (fname = line; *fname && isspace(*fname); fname++)
430 /* Find end of filename. */
431 for (cp = fname; *cp && !isspace(*cp); cp++)
435 temp = make_tempfile ();
436 if (checkout_file (fname, temp) == 0)
438 rename_rcsfile (temp, fname);
442 for (cp++; cp < last && *last && isspace(*last); cp++)
444 if (cp < last && *cp)
445 error (0, 0, cp, fname);
452 error (0, errno, "cannot read %s", CVSROOTADM_CHECKOUTLIST);
454 error (0, errno, "cannot close %s", CVSROOTADM_CHECKOUTLIST);
458 /* Error from CVS_FOPEN. */
459 if (!existence_error (errno))
460 error (0, errno, "cannot open %s", CVSROOTADM_CHECKOUTLIST);
463 if (restore_cwd (&cwd, NULL))
471 * Yeah, I know, there are NFS race conditions here.
482 temp = xmalloc (sizeof (BAKPREFIX) + 40);
485 (void) sprintf (temp, "%s%d", BAKPREFIX, seed++);
486 if ((fd = CVS_OPEN (temp, O_CREAT|O_EXCL|O_RDWR, 0666)) != -1)
489 error (1, errno, "cannot create temporary file %s", temp);
492 error(1, errno, "cannot close temporary file %s", temp);
497 checkout_file (file, temp)
508 rcs = xmalloc (strlen (file) + 5);
510 strcat (rcs, RCSEXT);
516 rcsnode = RCS_parsercsfile (rcs);
517 retcode = RCS_checkout (rcsnode, NULL, NULL, NULL, NULL, temp,
518 (RCSCHECKOUTPROC) NULL, (void *) NULL);
521 error (0, retcode == -1 ? errno : 0, "failed to check out %s file",
524 freercsnode (&rcsnode);
535 char line[DBLKSIZ], value[DBLKSIZ];
540 int len, cont, err = 0;
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;)
547 if ((cp = strrchr (line, '\n')) != NULL)
548 *cp = '\0'; /* strip the newline */
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.
557 vp += strlen (value);
560 * See if the line we read is a continuation line, and strip the
577 (void) strcpy (vp, line);
579 continue; /* comment line */
581 while (*vp && isspace (*vp))
584 continue; /* empty line */
587 * If this was not a continuation line, add the entry to the database
592 while (*vp && !isspace (*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 */
600 error (0, 0, "warning: NULL value for key `%s'", key.dptr);
604 val.dsize = strlen (vp);
605 if (dbm_store (db, key, val, DBM_INSERT) == 1)
607 error (0, 0, "duplicate key found for `%s'", key.dptr);
616 char dotdir[50], dotpag[50], dotdb[50];
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");
629 rename_dbmfile (temp)
632 char newdir[50], newpag[50], newdb[50];
633 char dotdir[50], dotpag[50], dotdb[50];
634 char bakdir[50], bakpag[50], bakdb[50];
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);
646 (void) chmod (newdir, 0666);
647 (void) chmod (newpag, 0666);
648 (void) chmod (newdb, 0666);
650 /* don't mess with me */
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 */
663 /* OK -- make my day */
667 #endif /* !MY_NDBM */
670 rename_rcsfile (temp, real)
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);
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 */
695 const char *const init_usage[] = {
705 /* Name of CVSROOT directory. */
707 /* Name of this administrative file. */
709 /* Name of ,v file for this administrative file. */
714 const struct admin_file *fileptr;
718 if (argc == -1 || argc > 1)
721 #ifdef CLIENT_SUPPORT
727 send_init_command ();
728 return get_responses_and_close ();
730 #endif /* CLIENT_SUPPORT */
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);
738 adm = xmalloc (strlen (CVSroot_directory) + sizeof (CVSROOTADM) + 10);
739 strcpy (adm, CVSroot_directory);
741 strcat (adm, CVSROOTADM);
742 mkdir_if_needed (adm);
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);
751 /* 80 is long enough for all the administrative file names, plus
753 info = xmalloc (strlen (adm) + 80);
754 info_v = xmalloc (strlen (adm) + 80);
755 for (fileptr = filelist; fileptr && fileptr->filename; ++fileptr)
757 if (fileptr->contents == NULL)
761 strcat (info, fileptr->filename);
762 strcpy (info_v, info);
763 strcat (info_v, RCSEXT);
765 /* We will check out this file in the mkmodules step.
766 Nothing else is required. */
775 const char * const *p;
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);
782 error (1, errno, "cannot close %s", info);
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,
791 /* add_rcs_file already printed an error message. */
796 /* Turn on history logging by default. The user can remove the file
800 strcat (info, CVSROOTADM_HISTORY);
805 fp = open_file (info, "w");
807 error (1, errno, "cannot close %s", info);