xref: /netbsd-src/external/gpl2/xcvs/dist/src/mkmodules.c (revision 6a493d6bc668897c91594964a732d38505b70cbb)
1 /*
2  * Copyright (C) 1986-2005 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 "cvs.h"
14 #include "getline.h"
15 #include "history.h"
16 #include "save-cwd.h"
17 
18 #ifndef DBLKSIZ
19 #define	DBLKSIZ	4096			/* since GNU ndbm doesn't define it */
20 #endif
21 
22 static int checkout_file (char *file, char *temp);
23 static char *make_tempfile (void);
24 static void rename_rcsfile (char *temp, char *real);
25 
26 #ifndef MY_NDBM
27 static void rename_dbmfile (char *temp);
28 static void write_dbmfile (char *temp);
29 #endif				/* !MY_NDBM */
30 
31 /* cvsacl patch */
32 static const char *const aclconfig_contents[] = {
33 	"# Set `UseCVSACL' to yes to use CVSACL feature.\n",
34 	"UseCVSACL=yes\n",
35 	"\n",
36 	"# Default CVSACL Permission to use.\n",
37 	"#CVSACLDefaultPermissions=p\n",
38 	"\n",
39 	"# Default file location for CVS ACL file (access) is CVSROOT/access.\n",
40 	"# If you want to use a different location, define it below.\n",
41 	"#CVSACLFileLocation=/path/to/cvs/access\n",
42 	"\n",
43 	"# Set `UseSystemGroups' to yes to use system group definitions (/etc/group).\n",
44 	"UseSystemGroups=yes\n",
45 	"\n",
46 	"# Set `UseCVSGroups' to yes to use another group file.\n",
47 	"#UseCVSGroups=yes\n",
48 	"\n",
49 	"# Default file location for CVS groups file is CVSROOT/group.\n",
50 	"# If you want to use a different location, define it below.\n",
51 	"#CVSGroupsFileLocation=/path/to/cvs/group\n",
52 	"\n",
53 	"# Set UseSeperateACLFileForEachDir to yes in order to use a\n",
54 	"# seperate 'access' file for each directory.\n",
55 	"# This increased the performance if you have really big repository.\n",
56 	"#UseSeperateACLFileForEachDir=no\n",
57 	"\n",
58 	"# If StopAtFirstPermissionDenied is set to yes\n",
59 	"# operation will stop at first permission denied message.\n",
60 	"# Default is no.\n",
61 	"#StopAtFirstPermissionDenied=no\n",
62 	"\n",
63 	"# Set CVSServerRunAsUser to a system user, in order CVS server\n",
64 	"# to run as.\n",
65 	"#CVSServerRunAsUser=runascvsuser",
66 	NULL
67 };
68 
69 static const char *const access_contents[] = {
70 	"# CVS ACL definitions file. DO NOT EDIT MANUALLY\n",
71 	"#\n",
72 	"# BNF:\n",
73 	"#     <entry>:           "
74 	    "<filetype>:<path>:<tag-branch>:<permission-list>:\n",
75 	"#     <filetype>:        d|f\n",
76 	"#     <path>:            ALL | unix-file-path\n",
77 	"#     <tag-branch>:      ALL | HEAD | name\n",
78 	"#     <permission-list>: <permission>\n",
79 	"#                        | <permission-list> , <permission>\n",
80 	"#     <permission>:      <actor>!<privilege>\n",
81 	"#     <actor>:           ALL | user | group\n",
82 	"#     <privilege>        n | a | w | t | r | c | d | p\n",
83 	"#\n",
84 	"# Valid privileges are:\n",
85 	"# * - no permission      (n) (1)\n",
86 	"# * - all permissions    (a) (2)\n",
87 	"# * - write permission   (w) (3)\n",
88 	"# * - tag permission     (t) (4)\n",
89 	"# * - read permission    (r) (5)\n",
90 	"# * - add permission     (c) (6)\n",
91 	"# * - remove permission  (d) (7)\n",
92 	"# * - permission change  (p) (8)\n",
93 	"#\n",
94 	"# Valid filetypes are:\n",
95 	"# * - plain file (f)\n",
96 	"# * - directory  (d)\n",
97 	"#\n",
98 	NULL
99 };
100 
101 static const char *const group_contents[] = {
102 	NULL
103 };
104 
105 /* Structure which describes an administrative file.  */
106 struct admin_file {
107    /* Name of the file, within the CVSROOT directory.  */
108    char *filename;
109 
110    /* This is a one line description of what the file is for.  It is not
111       currently used, although one wonders whether it should be, somehow.
112       If NULL, then don't process this file in mkmodules (FIXME?: a bit of
113       a kludge; probably should replace this with a flags field).  */
114    char *errormsg;
115 
116    /* Contents which the file should have in a new repository.  To avoid
117       problems with brain-dead compilers which choke on long string constants,
118       this is a pointer to an array of char * terminated by NULL--each of
119       the strings is concatenated.
120 
121       If this field is NULL, the file is not created in a new
122       repository, but it can be added with "cvs add" (just as if one
123       had created the repository with a version of CVS which didn't
124       know about the file) and the checked-out copy will be updated
125       without having to add it to checkoutlist.  */
126    const char * const *contents;
127 };
128 
129 static const char *const loginfo_contents[] = {
130     "# The \"loginfo\" file controls where \"cvs commit\" log information is\n",
131     "# sent. The first entry on a line is a regular expression which must\n",
132     "# match the directory that the change is being made to, relative to the\n",
133     "# $CVSROOT.  If a match is found, then the remainder of the line is a\n",
134     "# filter program that should expect log information on its standard input.\n",
135     "#\n",
136     "# If the repository name does not match any of the regular expressions in this\n",
137     "# file, the \"DEFAULT\" line is used, if it is specified.\n",
138     "#\n",
139     "# If the name ALL appears as a regular expression it is always used\n",
140     "# in addition to the first matching regex or DEFAULT.\n",
141     "#\n",
142     "# If any format strings are present in the filter, they will be replaced\n",
143     "# as follows:\n",
144     "#    %c = canonical name of the command being executed\n",
145 #ifdef PROXY_SUPPORT
146     "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
147 #endif
148     "#    %p = path relative to repository\n",
149     "#    %r = repository (path portion of $CVSROOT)\n",
150     "#    %t = tagname\n",
151     "#    %{sVv} = attribute list = file name, old version number (pre-checkin),\n",
152     "#           new version number (post-checkin).  When either old or new revision\n",
153     "#           is unknown, doesn't exist, or isn't applicable, the string \"NONE\"\n",
154     "#           will be placed on the command line instead.\n",
155     "#\n",
156     "# Note that %{sVv} is a list operator and not all elements are necessary.\n",
157     "# Thus %{sv} is a legal format string, but will only be replaced with\n",
158     "# file name and new revision.\n",
159     "# It also generates multiple arguments for each file being operated upon.\n",
160     "# That is, if two files, file1 & file2, are being commited from 1.1 to\n",
161     "# version 1.1.2.1 and from 1.1.2.2 to 1.1.2.3, respectively, %{sVv} will\n",
162     "# generate the following six arguments in this order:\n",
163     "# file1, 1.1, 1.1.2.1, file2, 1.1.2.2, 1.1.2.3.\n",
164     "#\n",
165     "# For example:\n",
166     "#DEFAULT (echo \"\"; id; echo %s; date; cat) >> $CVSROOT/CVSROOT/commitlog\n",
167     "# or\n",
168     "#DEFAULT (echo \"\"; id; echo %{sVv}; date; cat) >> $CVSROOT/CVSROOT/commitlog\n",
169     NULL
170 };
171 
172 static const char *const rcsinfo_contents[] = {
173     "# The \"rcsinfo\" file is used to control templates with which the editor\n",
174     "# is invoked on commit and import.\n",
175     "#\n",
176     "# The first entry on a line is a regular expression which is tested\n",
177     "# against the directory that the change is being made to, relative to the\n",
178     "# $CVSROOT.  For the first match that is found, then the remainder of the\n",
179     "# line is the name of the file that contains the template.\n",
180     "#\n",
181     "# If the repository name does not match any of the regular expressions in this\n",
182     "# file, the \"DEFAULT\" line is used, if it is specified.\n",
183     "#\n",
184     "# If the name \"ALL\" appears as a regular expression it is always used\n",
185     "# in addition to the first matching regex or \"DEFAULT\".\n",
186     NULL
187 };
188 
189 
190 
191 static const char *const verifymsg_contents[] = {
192     "# The \"verifymsg\" file is used to allow verification of logging\n",
193     "# information.  It works best when a template (as specified in the\n",
194     "# rcsinfo file) is provided for the logging procedure.  Given a\n",
195     "# template with locations for, a bug-id number, a list of people who\n",
196     "# reviewed the code before it can be checked in, and an external\n",
197     "# process to catalog the differences that were code reviewed, the\n",
198     "# following test can be applied to the code:\n",
199     "#\n",
200     "#   Making sure that the entered bug-id number is correct.\n",
201     "#   Validating that the code that was reviewed is indeed the code being\n",
202     "#       checked in (using the bug-id number or a seperate review\n",
203     "#       number to identify this particular code set.).\n",
204     "#\n",
205     "# If any of the above test failed, then the commit would be aborted.\n",
206     "#\n",
207     "# Format strings present in the filter will be replaced as follows:\n",
208     "#    %c = canonical name of the command being executed\n",
209 #ifdef PROXY_SUPPORT
210     "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
211 #endif
212     "#    %p = path relative to repository\n",
213     "#    %r = repository (path portion of $CVSROOT)\n",
214     "#    %l = name of log file to be verified.\n",
215     "#\n",
216     "# If no format strings are present in the filter, a default \" %l\" will\n",
217     "# be appended to the filter, but this usage is deprecated.\n",
218     "#\n",
219     "# Actions such as mailing a copy of the report to each reviewer are\n",
220     "# better handled by an entry in the loginfo file.\n",
221     "#\n",
222     "# One thing that should be noted is the the ALL keyword is not\n",
223     "# supported.  There can be only one entry that matches a given\n",
224     "# repository.\n",
225     NULL
226 };
227 
228 static const char *const commitinfo_contents[] = {
229     "# The \"commitinfo\" file is used to control pre-commit checks.\n",
230     "# The filter on the right is invoked with the repository and a list \n",
231     "# of files to check.  A non-zero exit of the filter program will \n",
232     "# cause the commit to be aborted.\n",
233     "#\n",
234     "# The first entry on a line is a regular expression which is tested\n",
235     "# against the directory that the change is being committed to, relative\n",
236     "# to the $CVSROOT.  For the first match that is found, then the remainder\n",
237     "# of the line is the name of the filter to run.\n",
238     "#\n",
239     "# Format strings present in the filter will be replaced as follows:\n",
240     "#    %c = canonical name of the command being executed\n",
241 #ifdef PROXY_SUPPORT
242     "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
243 #endif
244     "#    %p = path relative to repository\n",
245     "#    %r = repository (path portion of $CVSROOT)\n",
246     "#    %t = tagname\n",
247     "#    %{s} = file name, file name, ...\n",
248     "#\n",
249     "# If no format strings are present in the filter string, a default of\n",
250     "# \" %r %s\" will be appended to the filter string, but this usage is\n",
251     "# deprecated.\n",
252     "#\n",
253     "# If the repository name does not match any of the regular expressions in this\n",
254     "# file, the \"DEFAULT\" line is used, if it is specified.\n",
255     "#\n",
256     "# If the name \"ALL\" appears as a regular expression it is always used\n",
257     "# in addition to the first matching regex or \"DEFAULT\".\n",
258     NULL
259 };
260 
261 static const char *const taginfo_contents[] = {
262     "# The \"taginfo\" file is used to control pre-tag checks.\n",
263     "# The filter on the right is invoked with the following arguments\n",
264     "# if no format strings are present:\n",
265     "#\n",
266     "# $1 -- tagname\n",
267     "# $2 -- operation \"add\" for tag, \"mov\" for tag -F, and \"del\" for tag -d\n",
268     "# $3 -- tagtype \"?\" on delete, \"T\" for branch, \"N\" for static\n",
269     "# $4 -- repository\n",
270     "# $5->  file revision [file revision ...]\n",
271     "#\n",
272     "# If any format strings are present in the filter, they will be replaced\n",
273     "# as follows:\n",
274     "#    %b = branch mode = \"?\" (delete ops - unknown) | \"T\" (branch)\n",
275     "#                     | \"N\" (not branch)\n",
276     "#    %o = operation = \"add\" | \"mov\" | \"del\"\n",
277     "#    %c = canonical name of the command being executed\n",
278 #ifdef PROXY_SUPPORT
279     "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
280 #endif
281     "#    %p = path relative to repository\n",
282     "#    %r = repository (path portion of $CVSROOT)\n",
283     "#    %t = tagname\n",
284     "#    %{sVv} = attribute list = file name, old version tag will be deleted\n",
285     "#             from, new version tag will be added to (or deleted from, but\n",
286     "#             this feature is deprecated.  When either old or new revision is\n",
287     "#             unknown, doesn't exist, or isn't applicable, the string \"NONE\"\n",
288     "#             will be placed on the command line.\n",
289     "#\n",
290     "# Note that %{sVv} is a list operator and not all elements are necessary.\n",
291     "# Thus %{sV} is a legal format string, but will only be replaced with file\n",
292     "# name and old revision. it also generates multiple arguments for each file\n",
293     "# being operated upon.  i.e. if two files, file1 & file2, are having a tag\n",
294     "# moved from version 1.1 to version 1.1.2.9, %{sVv} will generate the\n",
295     "# following six arguments in this order:\n",
296     "# file1, 1.1, 1.1.2.9, file2, 1.1, 1.1.2.9.\n",
297     "#\n",
298     "# A non-zero exit of the filter program will cause the tag to be aborted.\n",
299     "#\n",
300     "# The first entry on a line is a regular expression which is tested\n",
301     "# against the directory that the change is being committed to, relative\n",
302     "# to the $CVSROOT.  For the first match that is found, then the remainder\n",
303     "# of the line is the name of the filter to run.\n",
304     "#\n",
305     "# If the repository name does not match any of the regular expressions in this\n",
306     "# file, the \"DEFAULT\" line is used, if it is specified.\n",
307     "#\n",
308     "# If the name \"ALL\" appears as a regular expression it is always used\n",
309     "# in addition to the first matching regex or \"DEFAULT\".\n",
310     NULL
311 };
312 
313 static const char *const preproxy_contents[] = {
314     "# The \"preproxy\" file is called form the secondary server as soon as\n",
315     "# the secondary server determines that it will be proxying a write\n",
316     "# command to a primary server and immediately before it opens a\n",
317     "# connection to the primary server.  This script might, for example, be\n",
318     "# used to launch a dial up or VPN connection to the primary server's\n",
319     "# network.\n",
320     "#\n",
321     "# If any format strings are present in the filter, they will be replaced\n",
322     "# as follows:\n",
323     "#    %c = canonical name of the command being executed\n",
324 #ifdef PROXY_SUPPORT
325     "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
326 #endif
327     "#    %p = path relative to repository (currently always \".\")\n",
328     "#    %r = repository (path portion of $CVSROOT)\n",
329     "#\n",
330     "# The first entry on a line is a regular expression which is tested\n",
331     "# against the directory that the change is being committed to, relative\n",
332     "# to the $CVSROOT.  For the first match that is found, then the remainder\n",
333     "# of the line is the name of the filter to run.\n",
334     "#\n",
335     "# If the repository name does not match any of the regular expressions in this\n",
336     "# file, the \"DEFAULT\" line is used, if it is specified.\n",
337     "#\n",
338     "# If the name \"ALL\" appears as a regular expression it is always used\n",
339     "# in addition to the first matching regex or \"DEFAULT\".\n",
340     NULL
341 };
342 
343 static const char *const postadmin_contents[] = {
344     "# The \"postadmin\" file is called after the \"admin\" command finishes\n",
345     "# processing a directory.\n",
346     "#\n",
347     "# If any format strings are present in the filter, they will be replaced\n",
348     "# as follows:\n",
349     "#    %c = canonical name of the command being executed\n",
350 #ifdef PROXY_SUPPORT
351     "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
352 #endif
353     "#    %p = path relative to repository\n",
354     "#    %r = repository (path portion of $CVSROOT)\n",
355     "#\n",
356     "# The first entry on a line is a regular expression which is tested\n",
357     "# against the directory that the change is being committed to, relative\n",
358     "# to the $CVSROOT.  For the first match that is found, then the remainder\n",
359     "# of the line is the name of the filter to run.\n",
360     "#\n",
361     "# If the repository name does not match any of the regular expressions in this\n",
362     "# file, the \"DEFAULT\" line is used, if it is specified.\n",
363     "#\n",
364     "# If the name \"ALL\" appears as a regular expression it is always used\n",
365     "# in addition to the first matching regex or \"DEFAULT\".\n",
366     NULL
367 };
368 
369 static const char *const postproxy_contents[] = {
370     "# The \"postproxy\" file is called from a secondary server as soon as\n",
371     "# the secondary server closes its connection to the primary server.\n",
372     "# This script might, for example, be used to shut down a dial up\n",
373     "# or VPN connection to the primary server's network.\n",
374     "#\n",
375     "# If any format strings are present in the filter, they will be replaced\n",
376     "# as follows:\n",
377     "#    %c = canonical name of the command being executed\n",
378 #ifdef PROXY_SUPPORT
379     "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
380 #endif
381     "#    %p = path relative to repository (currently always \".\")\n",
382     "#    %r = repository (path portion of $CVSROOT)\n",
383     "#\n",
384     "# The first entry on a line is a regular expression which is tested\n",
385     "# against the directory that the change is being committed to, relative\n",
386     "# to the $CVSROOT.  For the first match that is found, then the remainder\n",
387     "# of the line is the name of the filter to run.\n",
388     "#\n",
389     "# If the repository name does not match any of the regular expressions in this\n",
390     "# file, the \"DEFAULT\" line is used, if it is specified.\n",
391     "#\n",
392     "# If the name \"ALL\" appears as a regular expression it is always used\n",
393     "# in addition to the first matching regex or \"DEFAULT\".\n",
394     NULL
395 };
396 
397 static const char *const posttag_contents[] = {
398     "# The \"posttag\" file is called after the \"tag\" command finishes\n",
399     "# processing a directory.\n",
400     "#\n",
401     "# If any format strings are present in the filter, they will be replaced\n",
402     "# as follows:\n",
403     "#    %b = branch mode = \"?\" (delete ops - unknown) | \"T\" (branch)\n",
404     "#                     | \"N\" (not branch)\n",
405     "#    %o = operation = \"add\" | \"mov\" | \"del\"\n",
406     "#    %c = canonical name of the command being executed\n",
407 #ifdef PROXY_SUPPORT
408     "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
409 #endif
410     "#    %p = path relative to repository\n",
411     "#    %r = repository (path portion of $CVSROOT)\n",
412     "#    %t = tagname\n",
413     "#    %{sVv} = attribute list = file name, old version tag will be deleted\n",
414     "#             from, new version tag will be added to (or deleted from, but\n",
415     "#             this feature is deprecated.  When either old or new revision is\n",
416     "#             unknown, doesn't exist, or isn't applicable, the string \"NONE\"\n",
417     "#             will be placed on the command line.\n",
418     "#\n",
419     "# Note that %{sVv} is a list operator and not all elements are necessary.\n",
420     "# Thus %{sV} is a legal format string, but will only be replaced with file\n",
421     "# name and old revision. it also generates multiple arguments for each file\n",
422     "# being operated upon.  i.e. if two files, file1 & file2, are having a tag\n",
423     "# moved from version 1.1 to version 1.1.2.9, %{sVv} will generate the\n",
424     "# following six arguments in this order:\n",
425     "# file1, 1.1, 1.1.2.9, file2, 1.1, 1.1.2.9.\n",
426     "#\n",
427     "# The first entry on a line is a regular expression which is tested\n",
428     "# against the directory that the change is being committed to, relative\n",
429     "# to the $CVSROOT.  For the first match that is found, then the remainder\n",
430     "# of the line is the name of the filter to run.\n",
431     "#\n",
432     "# If the repository name does not match any of the regular expressions in this\n",
433     "# file, the \"DEFAULT\" line is used, if it is specified.\n",
434     "#\n",
435     "# If the name \"ALL\" appears as a regular expression it is always used\n",
436     "# in addition to the first matching regex or \"DEFAULT\".\n",
437     NULL
438 };
439 
440 static const char *const postwatch_contents[] = {
441     "# The \"postwatch\" file is called after any command finishes writing new\n",
442     "# file attibute (watch/edit) information in a directory.\n",
443     "#\n",
444     "# If any format strings are present in the filter, they will be replaced\n",
445     "# as follows:\n",
446     "#    %c = canonical name of the command being executed\n",
447 #ifdef PROXY_SUPPORT
448     "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
449 #endif
450     "#    %p = path relative to repository\n",
451     "#    %r = repository (path portion of $CVSROOT)\n",
452     "#\n",
453     "# The first entry on a line is a regular expression which is tested\n",
454     "# against the directory that the change is being committed to, relative\n",
455     "# to the $CVSROOT.  For the first match that is found, then the remainder\n",
456     "# of the line is the name of the filter to run.\n",
457     "#\n",
458     "# If the repository name does not match any of the regular expressions in this\n",
459     "# file, the \"DEFAULT\" line is used, if it is specified.\n",
460     "#\n",
461     "# If the name \"ALL\" appears as a regular expression it is always used\n",
462     "# in addition to the first matching regex or \"DEFAULT\".\n",
463     NULL
464 };
465 
466 static const char *const checkoutlist_contents[] = {
467     "# The \"checkoutlist\" file is used to support additional version controlled\n",
468     "# administrative files in $CVSROOT/CVSROOT, such as template files.\n",
469     "#\n",
470     "# The first entry on a line is a filename which will be checked out from\n",
471     "# the corresponding RCS file in the $CVSROOT/CVSROOT directory.\n",
472     "# The remainder of the line is an error message to use if the file cannot\n",
473     "# be checked out.\n",
474     "#\n",
475     "# File format:\n",
476     "#\n",
477     "#	[<whitespace>]<filename>[<whitespace><error message>]<end-of-line>\n",
478     "#\n",
479     "# comment lines begin with '#'\n",
480     NULL
481 };
482 
483 static const char *const cvswrappers_contents[] = {
484     "# This file affects handling of files based on their names.\n",
485     "#\n",
486 #if 0    /* see comments in wrap_add in wrapper.c */
487     "# The -t/-f options allow one to treat directories of files\n",
488     "# as a single file, or to transform a file in other ways on\n",
489     "# its way in and out of CVS.\n",
490     "#\n",
491 #endif
492     "# The -m option specifies whether CVS attempts to merge files.\n",
493     "#\n",
494     "# The -k option specifies keyword expansion (e.g. -kb for binary).\n",
495     "#\n",
496     "# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)\n",
497     "#\n",
498     "#  wildcard	[option value][option value]...\n",
499     "#\n",
500     "#  where option is one of\n",
501     "#  -f		from cvs filter		value: path to filter\n",
502     "#  -t		to cvs filter		value: path to filter\n",
503     "#  -m		update methodology	value: MERGE or COPY\n",
504     "#  -k		expansion mode		value: b, o, kkv, &c\n",
505     "#\n",
506     "#  and value is a single-quote delimited value.\n",
507     "# For example:\n",
508     "#*.gif -k 'b'\n",
509     NULL
510 };
511 
512 static const char *const notify_contents[] = {
513     "# The \"notify\" file controls where notifications from watches set by\n",
514     "# \"cvs watch add\" or \"cvs edit\" are sent.  The first entry on a line is\n",
515     "# a regular expression which is tested against the directory that the\n",
516     "# change is being made to, relative to the $CVSROOT.  If it matches,\n",
517     "# then the remainder of the line is a filter program that should contain\n",
518     "# one occurrence of %s for the user to notify, and information on its\n",
519     "# standard input.\n",
520     "#\n",
521     "# \"ALL\" or \"DEFAULT\" can be used in place of the regular expression.\n",
522     "#\n",
523     "# format strings are replaceed as follows:\n",
524     "#    %c = canonical name of the command being executed\n",
525 #ifdef PROXY_SUPPORT
526     "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
527 #endif
528     "#    %p = path relative to repository\n",
529     "#    %r = repository (path portion of $CVSROOT)\n",
530     "#    %s = user to notify\n",
531     "#\n",
532     "# For example:\n",
533     "#ALL (echo Committed to %r/%p; cat) |mail %s -s \"CVS notification\"\n",
534     NULL
535 };
536 
537 static const char *const modules_contents[] = {
538     "# Three different line formats are valid:\n",
539     "#	key	-a    aliases...\n",
540     "#	key [options] directory\n",
541     "#	key [options] directory files...\n",
542     "#\n",
543     "# Where \"options\" are composed of:\n",
544     "#	-i prog		Run \"prog\" on \"cvs commit\" from top-level of module.\n",
545     "#	-o prog		Run \"prog\" on \"cvs checkout\" of module.\n",
546     "#	-e prog		Run \"prog\" on \"cvs export\" of module.\n",
547     "#	-t prog		Run \"prog\" on \"cvs rtag\" of module.\n",
548     "#	-u prog		Run \"prog\" on \"cvs update\" of module.\n",
549     "#	-d dir		Place module in directory \"dir\" instead of module name.\n",
550     "#	-l		Top-level directory only -- do not recurse.\n",
551     "#\n",
552     "# NOTE:  If you change any of the \"Run\" options above, you'll have to\n",
553     "# release and re-checkout any working directories of these modules.\n",
554     "#\n",
555     "# And \"directory\" is a path to a directory relative to $CVSROOT.\n",
556     "#\n",
557     "# The \"-a\" option specifies an alias.  An alias is interpreted as if\n",
558     "# everything on the right of the \"-a\" had been typed on the command line.\n",
559     "#\n",
560     "# You can encode a module within a module by using the special '&'\n",
561     "# character to interpose another module into the current module.  This\n",
562     "# can be useful for creating a module that consists of many directories\n",
563     "# spread out over the entire source repository.\n",
564     NULL
565 };
566 
567 static const char *const config_contents[] = {
568     "# Set `SystemAuth' to `no' if pserver shouldn't check system users/passwords.\n",
569     "#SystemAuth=no\n",
570     "\n",
571     "# Set `LocalKeyword' to specify a local alias for a standard keyword.\n",
572     "#LocalKeyword=MYCVS=CVSHeader\n",
573     "\n",
574     "# Set `KeywordExpand' to `i' followed by a list of keywords to expand or\n",
575     "# `e' followed by a list of keywords to not expand.\n"
576     "#KeywordExpand=iMYCVS,Name,Date\n",
577     "#KeywordExpand=eCVSHeader\n",
578     "\n",
579 #ifdef PRESERVE_PERMISSIONS_SUPPORT
580     "# Set `PreservePermissions' to `yes' to save file status information\n",
581     "# in the repository.\n",
582     "#PreservePermissions=no\n",
583     "\n",
584 #endif
585     "# Set `TopLevelAdmin' to `yes' to create a CVS directory at the top\n",
586     "# level of the new working directory when using the `cvs checkout'\n",
587     "# command.\n",
588     "#TopLevelAdmin=no\n",
589     "\n",
590     "# Put CVS lock files in this directory rather than directly in the repository.\n",
591     "#LockDir=/var/lock/cvs\n",
592     "\n",
593     "# Set `LogHistory' to `all' or `" ALL_HISTORY_REC_TYPES "' to log all transactions to the\n",
594     "# history file, or a subset as needed (ie `TMAR' logs all write operations)\n",
595     "#LogHistory=" ALL_HISTORY_REC_TYPES "\n",
596     "\n",
597     "# Set `RereadLogAfterVerify' to `always' (the default) to allow the verifymsg\n",
598     "# script to change the log message.  Set it to `stat' to force CVS to verify\n",
599     "# that the file has changed before reading it (this can take up to an extra\n",
600     "# second per directory being committed, so it is not recommended for large\n",
601     "# repositories.  Set it to `never' (the previous CVS behavior) to prevent\n",
602     "# verifymsg scripts from changing the log message.\n",
603     "#RereadLogAfterVerify=always\n",
604     "\n",
605     "# Set `UserAdminOptions' to the list of `cvs admin' commands (options)\n",
606     "# that users not in the `cvsadmin' group are allowed to run.  This\n",
607     "# defaults to `k', or only allowing the changing of the default\n",
608     "# keyword expansion mode for files for users not in the `cvsadmin' group.\n",
609     "# This value is ignored if the `cvsadmin' group does not exist.\n",
610     "#\n",
611     "# The following string would enable all `cvs admin' commands for all\n",
612     "# users:\n",
613     "#AdminGroup=wheel\n",
614     "#AdminOptions=aAbceIklLmnNostuU\n",
615 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
616     "\n",
617     "# Set `UseNewInfoFmtStrings' to `no' if you must support a legacy system by\n",
618     "# enabling the deprecated old style info file command line format strings.\n",
619     "# Be warned that these strings could be disabled in any new version of CVS.\n",
620     "UseNewInfoFmtStrings=yes\n",
621 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
622     "\n",
623     "# Set `ImportNewFilesToVendorBranchOnly' to `yes' if you wish to force\n",
624     "# every `cvs import' command to behave as if the `-X' flag was\n",
625     "# specified.\n",
626     "#ImportNewFilesToVendorBranchOnly=no\n",
627 #ifdef PROXY_SUPPORT
628     "\n",
629     "# Set `PrimaryServer' to the CVSROOT to the primary, or write, server when\n",
630     "# establishing one or more read-only mirrors which serve as proxies for\n",
631     "# the write server in write mode or redirect the client to the primary for\n",
632     "# write requests.\n",
633     "#\n",
634     "# For example:\n",
635     "#\n",
636     "#   PrimaryServer=:fork:localhost/cvsroot\n",
637     "\n",
638     "# Set `MaxProxyBufferSize' to the the maximum allowable secondary\n",
639     "# buffer memory cache size before the buffer begins being stored to disk, in\n",
640     "# bytes.  Must be a positive integer but may end in `k', `M', `G', or `T' (for\n",
641     "# kiilo, mega, giga, & tera, respectively).  If an otherwise valid number you\n",
642     "# specify is greater than the SIZE_MAX defined by your system's C compiler,\n",
643     "# then it will be resolved to SIZE_MAX without a warning.  Defaults to 8M (8\n",
644     "# megabytes).\n",
645     "#\n",
646     "# High values for MaxProxyBufferSize may speed up a secondary server\n",
647     "# with old hardware and a lot of available memory but can actually slow a\n",
648     "# modern system down slightly.\n",
649     "#\n",
650     "# For example:\n",
651     "#\n",
652     "#   MaxProxyBufferSize=1G\n",
653 #endif /* PROXY_SUPPORT */
654     "\n",
655     "# Set `MaxCommentLeaderLength' to the maximum length permitted for the\n",
656     "# automagically determined comment leader used when expanding the Log\n",
657     "# keyword, in bytes.  CVS's behavior when the automagically determined\n",
658     "# comment leader exceeds this length is dependant on the value of\n",
659     "# `UseArchiveCommentLeader' set in this file.  `unlimited' is a valid\n",
660     "# setting for this value.  Defaults to 20 bytes.\n",
661     "#\n",
662     "# For example:\n",
663     "#\n",
664     "#   MaxCommentLeaderLength=20\n",
665     "\n",
666     "# Set `UseArchiveCommentLeader' to `yes' to cause CVS to fall back on\n",
667     "# the comment leader set in the RCS archive file, if any, when the\n",
668     "# automagically determined comment leader exceeds `MaxCommentLeaderLength'\n",
669     "# bytes.  If `UseArchiveCommentLeader' is not set and a comment leader\n",
670     "# greater than `MaxCommentLeaderLength' is calculated, the Log keyword\n",
671     "# being examined will not be expanded.  Defaults to `no'.\n",
672     "#\n",
673     "# For example:\n",
674     "#\n",
675     "#   UseArchiveCommentLeader=no\n",
676     "#\n",
677     "# Set this to the name of a local tag to use in addition to Id\n",
678     "#tag=OurTag\n",
679     NULL
680 };
681 
682 static const struct admin_file filelist[] = {
683     {CVSROOTADM_CHECKOUTLIST,
684 	"a %s file can specify extra CVSROOT files to auto-checkout",
685 	checkoutlist_contents},
686     {CVSROOTADM_COMMITINFO,
687 	"a %s file can be used to configure 'cvs commit' checking",
688 	commitinfo_contents},
689     {CVSROOTADM_IGNORE,
690 	"a %s file can be used to specify files to ignore",
691 	NULL},
692     {CVSROOTADM_LOGINFO,
693 	"no logging of 'cvs commit' messages is done without a %s file",
694 	&loginfo_contents[0]},
695     {CVSROOTADM_MODULES,
696 	/* modules is special-cased in mkmodules.  */
697 	NULL,
698 	modules_contents},
699     {CVSROOTADM_NOTIFY,
700 	"a %s file can be used to specify where notifications go",
701 	notify_contents},
702     {CVSROOTADM_POSTADMIN,
703 	"a %s file can be used to configure 'cvs admin' logging",
704 	postadmin_contents},
705     {CVSROOTADM_POSTPROXY,
706 	"a %s file can be used to close or log connections to a primary server",
707 	postproxy_contents},
708     {CVSROOTADM_POSTTAG,
709 	"a %s file can be used to configure 'cvs tag' logging",
710 	posttag_contents},
711     {CVSROOTADM_POSTWATCH,
712 	"a %s file can be used to configure 'cvs watch' logging",
713 	postwatch_contents},
714     {CVSROOTADM_PREPROXY,
715 	"a %s file can be used to open or log connections to a primary server",
716 	preproxy_contents},
717     {CVSROOTADM_RCSINFO,
718 	"a %s file can be used to configure 'cvs commit' templates",
719 	rcsinfo_contents},
720     {CVSROOTADM_READERS,
721 	"a %s file specifies read-only users",
722 	NULL},
723     {CVSROOTADM_TAGINFO,
724 	"a %s file can be used to configure 'cvs tag' checking",
725 	taginfo_contents},
726     {CVSROOTADM_VERIFYMSG,
727 	"a %s file can be used to validate log messages",
728 	verifymsg_contents},
729     {CVSROOTADM_WRAPPER,
730 	"a %s file can be used to specify files to treat as wrappers",
731 	cvswrappers_contents},
732     {CVSROOTADM_WRITERS,
733 	"a %s file specifies read/write users",
734 	NULL},
735 
736     /* Some have suggested listing CVSROOTADM_PASSWD here too.  This
737        would mean that CVS commands which operate on the
738        CVSROOTADM_PASSWD file would transmit hashed passwords over the
739        net.  This might seem to be no big deal, as pserver normally
740        transmits cleartext passwords, but the difference is that
741        CVSROOTADM_PASSWD contains *all* passwords, not just the ones
742        currently being used.  For example, it could be too easy to
743        accidentally give someone readonly access to CVSROOTADM_PASSWD
744        (e.g. via anonymous CVS or cvsweb), and then if there are any
745        guessable passwords for read/write access (usually there will be)
746        they get read/write access.
747 
748        Another worry is the implications of storing old passwords--if
749        someone used a password in the past they might be using it
750        elsewhere, using a similar password, etc, and so saving old
751        passwords, even hashed, is probably not a good idea.  */
752 
753     {CVSROOTADM_CONFIG,
754 	 "a %s file configures various behaviors",
755 	 config_contents},
756 
757      /* cvsacl patch */
758     {CVSROOTADM_ACLCONFIG,
759 	 "a %s file configures Access Control List behaviors",
760 	 aclconfig_contents},
761 
762     {CVSROOTADM_ACCESS,
763 	 "a %s file configures Access Control Lists",
764 	 access_contents},
765 
766     {CVSROOTADM_GROUP,
767 	 "a %s file configures Access Control List group definitions",
768 	 group_contents},
769 
770     {NULL, NULL, NULL}
771 };
772 
773 /* Rebuild the checked out administrative files in directory DIR.  */
774 int
775 mkmodules (char *dir)
776 {
777     struct saved_cwd cwd;
778     char *temp;
779     char *cp, *last, *fname;
780 #ifdef MY_NDBM
781     DBM *db;
782 #endif
783     FILE *fp;
784     char *line = NULL;
785     size_t line_allocated = 0;
786     const struct admin_file *fileptr;
787 
788     if (noexec)
789 	return 0;
790 
791     if (save_cwd (&cwd))
792 	error (1, errno, "Failed to save current directory.");
793 
794     if (CVS_CHDIR (dir) < 0)
795 	error (1, errno, "cannot chdir to %s", dir);
796 
797     /*
798      * First, do the work necessary to update the "modules" database.
799      */
800     temp = make_tempfile ();
801     switch (checkout_file (CVSROOTADM_MODULES, temp))
802     {
803 
804 	case 0:			/* everything ok */
805 #ifdef MY_NDBM
806 	    /* open it, to generate any duplicate errors */
807 	    if ((db = dbm_open (temp, O_RDONLY, 0666)) != NULL)
808 		dbm_close (db);
809 #else
810 	    write_dbmfile (temp);
811 	    rename_dbmfile (temp);
812 #endif
813 	    rename_rcsfile (temp, CVSROOTADM_MODULES);
814 	    break;
815 
816 	default:
817 	    error (0, 0,
818 		"'cvs checkout' is less functional without a %s file",
819 		CVSROOTADM_MODULES);
820 	    break;
821     }					/* switch on checkout_file() */
822 
823     if (unlink_file (temp) < 0
824 	&& !existence_error (errno))
825 	error (0, errno, "cannot remove %s", temp);
826     free (temp);
827 
828     /* Checkout the files that need it in CVSROOT dir */
829     for (fileptr = filelist; fileptr && fileptr->filename; fileptr++) {
830 	if (fileptr->errormsg == NULL)
831 	    continue;
832 	temp = make_tempfile ();
833 	if (checkout_file (fileptr->filename, temp) == 0)
834 	    rename_rcsfile (temp, fileptr->filename);
835 	/* else
836 	 *   If there was some problem other than the file not existing,
837 	 *   checkout_file already printed a real error message.  If the
838 	 *   file does not exist, it is harmless--it probably just means
839 	 *   that the repository was created with an old version of CVS
840 	 *   which didn't have so many files in CVSROOT.
841 	 */
842 
843 	if (unlink_file (temp) < 0
844 	    && !existence_error (errno))
845 	    error (0, errno, "cannot remove %s", temp);
846 	free (temp);
847     }
848 
849     fp = CVS_FOPEN (CVSROOTADM_CHECKOUTLIST, "r");
850     if (fp)
851     {
852 	/*
853 	 * File format:
854 	 *  [<whitespace>]<filename>[<whitespace><error message>]<end-of-line>
855 	 *
856 	 * comment lines begin with '#'
857 	 */
858 	while (getline (&line, &line_allocated, fp) >= 0)
859 	{
860 	    /* skip lines starting with # */
861 	    if (line[0] == '#')
862 		continue;
863 
864 	    if ((last = strrchr (line, '\n')) != NULL)
865 		*last = '\0';			/* strip the newline */
866 
867 	    /* Skip leading white space. */
868 	    for (fname = line;
869 		 *fname && isspace ((unsigned char) *fname);
870 		 fname++)
871 		;
872 
873 	    /* Find end of filename. */
874 	    for (cp = fname; *cp && !isspace ((unsigned char) *cp); cp++)
875 		;
876 	    *cp = '\0';
877 
878 	    temp = make_tempfile ();
879 	    if (checkout_file (fname, temp) == 0)
880 	    {
881 		rename_rcsfile (temp, fname);
882 	    }
883 	    else
884 	    {
885 		/* Skip leading white space before the error message.  */
886 		for (cp++;
887 		     cp < last && *cp && isspace ((unsigned char) *cp);
888 		     cp++)
889 		    ;
890 		if (cp < last && *cp)
891 		    error (0, 0, "%s", cp);
892 	    }
893 	    if (unlink_file (temp) < 0
894 		&& !existence_error (errno))
895 		error (0, errno, "cannot remove %s", temp);
896 	    free (temp);
897 	}
898 	if (line)
899 	    free (line);
900 	if (ferror (fp))
901 	    error (0, errno, "cannot read %s", CVSROOTADM_CHECKOUTLIST);
902 	if (fclose (fp) < 0)
903 	    error (0, errno, "cannot close %s", CVSROOTADM_CHECKOUTLIST);
904     }
905     else
906     {
907 	/* Error from CVS_FOPEN.  */
908 	if (!existence_error (errno))
909 	    error (0, errno, "cannot open %s", CVSROOTADM_CHECKOUTLIST);
910     }
911 
912     if (restore_cwd (&cwd))
913 	error (1, errno, "Failed to restore current directory, `%s'.",
914 	       cwd.name);
915     free_cwd (&cwd);
916 
917     return 0;
918 }
919 
920 
921 
922 /*
923  * Yeah, I know, there are NFS race conditions here.
924  */
925 static char *
926 make_tempfile (void)
927 {
928     static int seed = 0;
929     int fd;
930     char *temp;
931 
932     if (seed == 0)
933 	seed = getpid ();
934     temp = xmalloc (sizeof (BAKPREFIX) + 40);
935     while (1)
936     {
937 	(void) sprintf (temp, "%s%d", BAKPREFIX, seed++);
938 	if ((fd = CVS_OPEN (temp, O_CREAT|O_EXCL|O_RDWR, 0666)) != -1)
939 	    break;
940 	if (errno != EEXIST)
941 	    error (1, errno, "cannot create temporary file %s", temp);
942     }
943     if (close(fd) < 0)
944 	error(1, errno, "cannot close temporary file %s", temp);
945     return temp;
946 }
947 
948 
949 
950 /* Get a file.  If the file does not exist, return 1 silently.  If
951    there is an error, print a message and return 1 (FIXME: probably
952    not a very clean convention).  On success, return 0.  */
953 static int
954 checkout_file (char *file, char *temp)
955 {
956     char *rcs;
957     RCSNode *rcsnode;
958     int retcode = 0;
959 
960     if (noexec)
961 	return 0;
962 
963     rcs = Xasprintf ("%s%s", file, RCSEXT);
964     if (!isfile (rcs))
965     {
966 	free (rcs);
967 	return 1;
968     }
969 
970     rcsnode = RCS_parsercsfile (rcs);
971     if (!rcsnode)
972     {
973 	/* Probably not necessary (?); RCS_parsercsfile already printed a
974 	   message.  */
975 	error (0, 0, "Failed to parse `%s'.", rcs);
976 	free (rcs);
977 	return 1;
978     }
979 
980     retcode = RCS_checkout (rcsnode, NULL, NULL, NULL, NULL, temp, NULL, NULL);
981     if (retcode != 0)
982     {
983 	/* Probably not necessary (?); RCS_checkout already printed a
984 	   message.  */
985 	error (0, 0, "failed to check out %s file",
986 	       file);
987     }
988     freercsnode (&rcsnode);
989     free (rcs);
990     return retcode;
991 }
992 
993 
994 
995 #ifndef MY_NDBM
996 
997 static void
998 write_dbmfile( char *temp )
999 {
1000     char line[DBLKSIZ], value[DBLKSIZ];
1001     FILE *fp;
1002     DBM *db;
1003     char *cp, *vp;
1004     datum key, val;
1005     int len, cont, err = 0;
1006 
1007     fp = xfopen (temp, "r");
1008     if ((db = dbm_open (temp, O_RDWR | O_CREAT | O_TRUNC, 0666)) == NULL)
1009 	error (1, errno, "cannot open dbm file %s for creation", temp);
1010     for (cont = 0; fgets (line, sizeof (line), fp) != NULL;)
1011     {
1012 	if ((cp = strrchr (line, '\n')) != NULL)
1013 	    *cp = '\0';			/* strip the newline */
1014 
1015 	/*
1016 	 * Add the line to the value, at the end if this is a continuation
1017 	 * line; otherwise at the beginning, but only after any trailing
1018 	 * backslash is removed.
1019 	 */
1020 	vp = value;
1021 	if (cont)
1022 	    vp += strlen (value);
1023 
1024 	/*
1025 	 * See if the line we read is a continuation line, and strip the
1026 	 * backslash if so.
1027 	 */
1028 	len = strlen (line);
1029 	if (len > 0)
1030 	    cp = &line[len - 1];
1031 	else
1032 	    cp = line;
1033 	if (*cp == '\\')
1034 	{
1035 	    cont = 1;
1036 	    *cp = '\0';
1037 	}
1038 	else
1039 	{
1040 	    cont = 0;
1041 	}
1042 	(void) strcpy (vp, line);
1043 	if (value[0] == '#')
1044 	    continue;			/* comment line */
1045 	vp = value;
1046 	while (*vp && isspace ((unsigned char) *vp))
1047 	    vp++;
1048 	if (*vp == '\0')
1049 	    continue;			/* empty line */
1050 
1051 	/*
1052 	 * If this was not a continuation line, add the entry to the database
1053 	 */
1054 	if (!cont)
1055 	{
1056 	    key.dptr = vp;
1057 	    while (*vp && !isspace ((unsigned char) *vp))
1058 		vp++;
1059 	    key.dsize = vp - key.dptr;
1060 	    *vp++ = '\0';		/* NULL terminate the key */
1061 	    while (*vp && isspace ((unsigned char) *vp))
1062 		vp++;			/* skip whitespace to value */
1063 	    if (*vp == '\0')
1064 	    {
1065 		error (0, 0, "warning: NULL value for key `%s'", key.dptr);
1066 		continue;
1067 	    }
1068 	    val.dptr = vp;
1069 	    val.dsize = strlen (vp);
1070 	    if (dbm_store (db, key, val, DBM_INSERT) == 1)
1071 	    {
1072 		error (0, 0, "duplicate key found for `%s'", key.dptr);
1073 		err++;
1074 	    }
1075 	}
1076     }
1077     dbm_close (db);
1078     if (fclose (fp) < 0)
1079 	error (0, errno, "cannot close %s", temp);
1080     if (err)
1081     {
1082 	/* I think that the size of the buffer needed here is
1083 	   just determined by sizeof (CVSROOTADM_MODULES), the
1084 	   filenames created by make_tempfile, and other things that won't
1085 	   overflow.  */
1086 	char dotdir[50], dotpag[50], dotdb[50];
1087 
1088 	(void) sprintf (dotdir, "%s.dir", temp);
1089 	(void) sprintf (dotpag, "%s.pag", temp);
1090 	(void) sprintf (dotdb, "%s.db", temp);
1091 	if (unlink_file (dotdir) < 0
1092 	    && !existence_error (errno))
1093 	    error (0, errno, "cannot remove %s", dotdir);
1094 	if (unlink_file (dotpag) < 0
1095 	    && !existence_error (errno))
1096 	    error (0, errno, "cannot remove %s", dotpag);
1097 	if (unlink_file (dotdb) < 0
1098 	    && !existence_error (errno))
1099 	    error (0, errno, "cannot remove %s", dotdb);
1100 	error (1, 0, "DBM creation failed; correct above errors");
1101     }
1102 }
1103 
1104 static void
1105 rename_dbmfile( char *temp )
1106 {
1107     /* I think that the size of the buffer needed here is
1108        just determined by sizeof (CVSROOTADM_MODULES), the
1109        filenames created by make_tempfile, and other things that won't
1110        overflow.  */
1111     char newdir[50], newpag[50], newdb[50];
1112     char dotdir[50], dotpag[50], dotdb[50];
1113     char bakdir[50], bakpag[50], bakdb[50];
1114 
1115     int dir1_errno = 0, pag1_errno = 0, db1_errno = 0;
1116     int dir2_errno = 0, pag2_errno = 0, db2_errno = 0;
1117     int dir3_errno = 0, pag3_errno = 0, db3_errno = 0;
1118 
1119     (void) sprintf (dotdir, "%s.dir", CVSROOTADM_MODULES);
1120     (void) sprintf (dotpag, "%s.pag", CVSROOTADM_MODULES);
1121     (void) sprintf (dotdb, "%s.db", CVSROOTADM_MODULES);
1122     (void) sprintf (bakdir, "%s%s.dir", BAKPREFIX, CVSROOTADM_MODULES);
1123     (void) sprintf (bakpag, "%s%s.pag", BAKPREFIX, CVSROOTADM_MODULES);
1124     (void) sprintf (bakdb, "%s%s.db", BAKPREFIX, CVSROOTADM_MODULES);
1125     (void) sprintf (newdir, "%s.dir", temp);
1126     (void) sprintf (newpag, "%s.pag", temp);
1127     (void) sprintf (newdb, "%s.db", temp);
1128 
1129     (void) chmod (newdir, 0666);
1130     (void) chmod (newpag, 0666);
1131     (void) chmod (newdb, 0666);
1132 
1133     /* don't mess with me */
1134     SIG_beginCrSect ();
1135 
1136     /* rm .#modules.dir .#modules.pag */
1137     if (unlink_file (bakdir) < 0)
1138 	dir1_errno = errno;
1139     if (unlink_file (bakpag) < 0)
1140 	pag1_errno = errno;
1141     if (unlink_file (bakdb) < 0)
1142 	db1_errno = errno;
1143 
1144     /* mv modules.dir .#modules.dir */
1145     if (CVS_RENAME (dotdir, bakdir) < 0)
1146 	dir2_errno = errno;
1147     /* mv modules.pag .#modules.pag */
1148     if (CVS_RENAME (dotpag, bakpag) < 0)
1149 	pag2_errno = errno;
1150     /* mv modules.db .#modules.db */
1151     if (CVS_RENAME (dotdb, bakdb) < 0)
1152 	db2_errno = errno;
1153 
1154     /* mv "temp".dir modules.dir */
1155     if (CVS_RENAME (newdir, dotdir) < 0)
1156 	dir3_errno = errno;
1157     /* mv "temp".pag modules.pag */
1158     if (CVS_RENAME (newpag, dotpag) < 0)
1159 	pag3_errno = errno;
1160     /* mv "temp".db modules.db */
1161     if (CVS_RENAME (newdb, dotdb) < 0)
1162 	db3_errno = errno;
1163 
1164     /* OK -- make my day */
1165     SIG_endCrSect ();
1166 
1167     /* I didn't want to call error() when we had signals blocked
1168        (unnecessary?), but do it now.  */
1169     if (dir1_errno && !existence_error (dir1_errno))
1170 	error (0, dir1_errno, "cannot remove %s", bakdir);
1171     if (pag1_errno && !existence_error (pag1_errno))
1172 	error (0, pag1_errno, "cannot remove %s", bakpag);
1173     if (db1_errno && !existence_error (db1_errno))
1174 	error (0, db1_errno, "cannot remove %s", bakdb);
1175 
1176     if (dir2_errno && !existence_error (dir2_errno))
1177 	error (0, dir2_errno, "cannot remove %s", bakdir);
1178     if (pag2_errno && !existence_error (pag2_errno))
1179 	error (0, pag2_errno, "cannot remove %s", bakpag);
1180     if (db2_errno && !existence_error (db2_errno))
1181 	error (0, db2_errno, "cannot remove %s", bakdb);
1182 
1183     if (dir3_errno && !existence_error (dir3_errno))
1184 	error (0, dir3_errno, "cannot remove %s", bakdir);
1185     if (pag3_errno && !existence_error (pag3_errno))
1186 	error (0, pag3_errno, "cannot remove %s", bakpag);
1187     if (db3_errno && !existence_error (db3_errno))
1188 	error (0, db3_errno, "cannot remove %s", bakdb);
1189 }
1190 
1191 #endif				/* !MY_NDBM */
1192 
1193 static void
1194 rename_rcsfile (char *temp, char *real)
1195 {
1196     char *bak;
1197     struct stat statbuf;
1198     char *rcs;
1199 
1200     /* Set "x" bits if set in original. */
1201     rcs = Xasprintf ("%s%s", real, RCSEXT);
1202     statbuf.st_mode = 0; /* in case rcs file doesn't exist, but it should... */
1203     if (stat (rcs, &statbuf) < 0
1204 	&& !existence_error (errno))
1205 	error (0, errno, "cannot stat %s", rcs);
1206     free (rcs);
1207 
1208     if (chmod (temp, 0444 | (statbuf.st_mode & 0111)) < 0)
1209 	error (0, errno, "warning: cannot chmod %s", temp);
1210     bak = Xasprintf ("%s%s", BAKPREFIX, real);
1211 
1212     /* rm .#loginfo */
1213     if (unlink_file (bak) < 0
1214 	&& !existence_error (errno))
1215 	error (0, errno, "cannot remove %s", bak);
1216 
1217     /* mv loginfo .#loginfo */
1218     if (CVS_RENAME (real, bak) < 0
1219 	&& !existence_error (errno))
1220 	error (0, errno, "cannot rename %s to %s", real, bak);
1221 
1222     /* mv "temp" loginfo */
1223     if (CVS_RENAME (temp, real) < 0
1224 	&& !existence_error (errno))
1225 	error (0, errno, "cannot rename %s to %s", temp, real);
1226 
1227     free (bak);
1228 }
1229 
1230 const char *const init_usage[] = {
1231     "Usage: %s %s\n",
1232     "(Specify the --help global option for a list of other help options)\n",
1233     NULL
1234 };
1235 
1236 int
1237 init (int argc, char **argv)
1238 {
1239     /* Name of CVSROOT directory.  */
1240     char *adm;
1241     /* Name of this administrative file.  */
1242     char *info;
1243     /* Name of ,v file for this administrative file.  */
1244     char *info_v;
1245     /* Exit status.  */
1246     int err = 0;
1247     struct stat st;
1248 
1249     const struct admin_file *fileptr;
1250 
1251     umask (cvsumask);
1252 
1253     if (argc == -1 || argc > 1)
1254 	usage (init_usage);
1255 
1256 #ifdef CLIENT_SUPPORT
1257     if (current_parsed_root->isremote)
1258     {
1259 	start_server ();
1260 
1261 	ign_setup ();
1262 	send_init_command ();
1263 	return get_responses_and_close ();
1264     }
1265 #endif /* CLIENT_SUPPORT */
1266 
1267     if (stat (current_parsed_root->directory, &st) != -1)
1268 	if (!admin_group_member ())
1269 	    error (1, 0, "init to an existing repository is restricted to"
1270 		" members of the group %s", config->UserAdminGroup);
1271 
1272     /* Note: we do *not* create parent directories as needed like the
1273        old cvsinit.sh script did.  Few utilities do that, and a
1274        non-existent parent directory is as likely to be a typo as something
1275        which needs to be created.  */
1276     mkdir_if_needed (current_parsed_root->directory);
1277 
1278     adm = Xasprintf ("%s/%s", current_parsed_root->directory, CVSROOTADM);
1279     mkdir_if_needed (adm);
1280 
1281     /* This is needed because we pass "fileptr->filename" not "info"
1282        to add_rcs_file below.  I think this would be easy to change,
1283        thus nuking the need for CVS_CHDIR here, but I haven't looked
1284        closely (e.g. see wrappers calls within add_rcs_file).  */
1285     if ( CVS_CHDIR (adm) < 0)
1286 	error (1, errno, "cannot change to directory %s", adm);
1287 
1288     /* Make Emptydir so it's there if we need it */
1289     mkdir_if_needed (CVSNULLREPOS);
1290 
1291     /* 80 is long enough for all the administrative file names, plus
1292        "/" and so on.  */
1293     info = xmalloc (strlen (adm) + 80);
1294     info_v = xmalloc (strlen (adm) + 80);
1295     for (fileptr = filelist; fileptr && fileptr->filename; ++fileptr)
1296     {
1297 	if (fileptr->contents == NULL)
1298 	    continue;
1299 	strcpy (info, adm);
1300 	strcat (info, "/");
1301 	strcat (info, fileptr->filename);
1302 	strcpy (info_v, info);
1303 	strcat (info_v, RCSEXT);
1304 	if (isfile (info_v))
1305 	    /* We will check out this file in the mkmodules step.
1306 	       Nothing else is required.  */
1307 	    ;
1308 	else
1309 	{
1310 	    int retcode;
1311 
1312 	    if (!isfile (info))
1313 	    {
1314 		FILE *fp;
1315 		const char * const *p;
1316 
1317 		fp = xfopen (info, "w");
1318 		for (p = fileptr->contents; *p != NULL; ++p)
1319 		    if (fputs (*p, fp) < 0)
1320 			error (1, errno, "cannot write %s", info);
1321 		if (fclose (fp) < 0)
1322 		    error (1, errno, "cannot close %s", info);
1323 	    }
1324 	    /* The message used to say " of " and fileptr->filename after
1325 	       "initial checkin" but I fail to see the point as we know what
1326 	       file it is from the name.  */
1327 	    retcode = add_rcs_file ("initial checkin", info_v,
1328 				    fileptr->filename, "1.1", NULL,
1329 
1330 				    /* No vendor branch.  */
1331 				    NULL, NULL, 0, NULL,
1332 
1333 				    NULL, 0, NULL, 0);
1334 	    if (retcode != 0)
1335 		/* add_rcs_file already printed an error message.  */
1336 		err = 1;
1337 	}
1338     }
1339 
1340     /* Turn on history logging by default.  The user can remove the file
1341        to disable it.  */
1342     strcpy (info, adm);
1343     strcat (info, "/");
1344     strcat (info, CVSROOTADM_HISTORY);
1345     if (!isfile (info))
1346     {
1347 	FILE *fp;
1348 
1349 	fp = xfopen (info, "w");
1350 	if (fclose (fp) < 0)
1351 	    error (1, errno, "cannot close %s", info);
1352 
1353         /* Make the new history file world-writeable, since every CVS
1354            user will need to be able to write to it.  We use chmod()
1355            because xchmod() is too shy. */
1356         chmod (info, 0666);
1357     }
1358 
1359     /* Make an empty val-tags file to prevent problems creating it later.  */
1360     strcpy (info, adm);
1361     strcat (info, "/");
1362     strcat (info, CVSROOTADM_VALTAGS);
1363     if (!isfile (info))
1364     {
1365 	FILE *fp;
1366 
1367 	fp = xfopen (info, "w");
1368 	if (fclose (fp) < 0)
1369 	    error (1, errno, "cannot close %s", info);
1370 
1371         /* Make the new val-tags file world-writeable, since every CVS
1372            user will need to be able to write to it.  We use chmod()
1373            because xchmod() is too shy. */
1374         chmod (info, 0666);
1375     }
1376 
1377     free (info);
1378     free (info_v);
1379 
1380     mkmodules (adm);
1381 
1382     free (adm);
1383     return err;
1384 }
1385