117721Speter/* 2177404Sobrien * Copyright (C) 1986-2008 The Free Software Foundation, Inc. 3175282Sobrien * 4175282Sobrien * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>, 5175282Sobrien * and others. 6175282Sobrien * 7175282Sobrien * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk 8175282Sobrien * Portions Copyright (C) 1989-1992, Brian Berliner 917721Speter * 1017721Speter * You may distribute under the terms of the GNU General Public License as 11177405Sobrien * specified in the README file that comes with the CVS kit. */ 1217721Speter 13177404Sobrien#include <assert.h> 1417721Speter#include "cvs.h" 15128269Speter#include "getline.h" 16128269Speter#include "history.h" 1717721Speter#include "savecwd.h" 1817721Speter 1917721Speter#ifndef DBLKSIZ 2017721Speter#define DBLKSIZ 4096 /* since GNU ndbm doesn't define it */ 2117721Speter#endif 2217721Speter 2317721Speterstatic int checkout_file PROTO((char *file, char *temp)); 2425843Speterstatic char *make_tempfile PROTO((void)); 2517721Speterstatic void rename_rcsfile PROTO((char *temp, char *real)); 2617721Speter 2717721Speter#ifndef MY_NDBM 2817721Speterstatic void rename_dbmfile PROTO((char *temp)); 2917721Speterstatic void write_dbmfile PROTO((char *temp)); 3017721Speter#endif /* !MY_NDBM */ 3117721Speter 3217721Speter/* Structure which describes an administrative file. */ 3317721Speterstruct admin_file { 3417721Speter /* Name of the file, within the CVSROOT directory. */ 3517721Speter char *filename; 3617721Speter 3717721Speter /* This is a one line description of what the file is for. It is not 3817721Speter currently used, although one wonders whether it should be, somehow. 3925843Speter If NULL, then don't process this file in mkmodules (FIXME?: a bit of 4017721Speter a kludge; probably should replace this with a flags field). */ 4117721Speter char *errormsg; 4217721Speter 4317721Speter /* Contents which the file should have in a new repository. To avoid 4417721Speter problems with brain-dead compilers which choke on long string constants, 4517721Speter this is a pointer to an array of char * terminated by NULL--each of 4625843Speter the strings is concatenated. 4725843Speter 4825843Speter If this field is NULL, the file is not created in a new 4925843Speter repository, but it can be added with "cvs add" (just as if one 5025843Speter had created the repository with a version of CVS which didn't 5125843Speter know about the file) and the checked-out copy will be updated 5225843Speter without having to add it to checkoutlist. */ 5317721Speter const char * const *contents; 5417721Speter}; 5517721Speter 5617721Speterstatic const char *const loginfo_contents[] = { 5725843Speter "# The \"loginfo\" file controls where \"cvs commit\" log information\n", 5825843Speter "# is sent. The first entry on a line is a regular expression which must match\n", 5925843Speter "# the directory that the change is being made to, relative to the\n", 6025843Speter "# $CVSROOT. If a match is found, then the remainder of the line is a filter\n", 6125843Speter "# program that should expect log information on its standard input.\n", 6217721Speter "#\n", 6325843Speter "# If the repository name does not match any of the regular expressions in this\n", 6425843Speter "# file, the \"DEFAULT\" line is used, if it is specified.\n", 6517721Speter "#\n", 6625843Speter "# If the name ALL appears as a regular expression it is always used\n", 6725843Speter "# in addition to the first matching regex or DEFAULT.\n", 6817721Speter "#\n", 6925843Speter "# You may specify a format string as part of the\n", 7025843Speter "# filter. The string is composed of a `%' followed\n", 7125843Speter "# by a single format character, or followed by a set of format\n", 7225843Speter "# characters surrounded by `{' and `}' as separators. The format\n", 7325843Speter "# characters are:\n", 7417721Speter "#\n", 7525843Speter "# s = file name\n", 7625843Speter "# V = old version number (pre-checkin)\n", 7725843Speter "# v = new version number (post-checkin)\n", 7825843Speter "#\n", 7917721Speter "# For example:\n", 8025843Speter "#DEFAULT (echo \"\"; id; echo %s; date; cat) >> $CVSROOT/CVSROOT/commitlog\n", 8125843Speter "# or\n", 8225843Speter "#DEFAULT (echo \"\"; id; echo %{sVv}; date; cat) >> $CVSROOT/CVSROOT/commitlog\n", 8317721Speter NULL 8417721Speter}; 8517721Speter 8617721Speterstatic const char *const rcsinfo_contents[] = { 8717721Speter "# The \"rcsinfo\" file is used to control templates with which the editor\n", 8817721Speter "# is invoked on commit and import.\n", 8917721Speter "#\n", 9017721Speter "# The first entry on a line is a regular expression which is tested\n", 9117721Speter "# against the directory that the change is being made to, relative to the\n", 9217721Speter "# $CVSROOT. For the first match that is found, then the remainder of the\n", 9317721Speter "# line is the name of the file that contains the template.\n", 9417721Speter "#\n", 9517721Speter "# If the repository name does not match any of the regular expressions in this\n", 9617721Speter "# file, the \"DEFAULT\" line is used, if it is specified.\n", 9717721Speter "#\n", 9817721Speter "# If the name \"ALL\" appears as a regular expression it is always used\n", 9917721Speter "# in addition to the first matching regex or \"DEFAULT\".\n", 10017721Speter NULL 10117721Speter}; 10217721Speter 10317721Speterstatic const char *const editinfo_contents[] = { 10417721Speter "# The \"editinfo\" file is used to allow verification of logging\n", 10517721Speter "# information. It works best when a template (as specified in the\n", 10617721Speter "# rcsinfo file) is provided for the logging procedure. Given a\n", 10717721Speter "# template with locations for, a bug-id number, a list of people who\n", 10817721Speter "# reviewed the code before it can be checked in, and an external\n", 10917721Speter "# process to catalog the differences that were code reviewed, the\n", 11017721Speter "# following test can be applied to the code:\n", 11117721Speter "#\n", 11217721Speter "# Making sure that the entered bug-id number is correct.\n", 11317721Speter "# Validating that the code that was reviewed is indeed the code being\n", 11417721Speter "# checked in (using the bug-id number or a seperate review\n", 11517721Speter "# number to identify this particular code set.).\n", 11617721Speter "#\n", 11717721Speter "# If any of the above test failed, then the commit would be aborted.\n", 11817721Speter "#\n", 11917721Speter "# Actions such as mailing a copy of the report to each reviewer are\n", 12017721Speter "# better handled by an entry in the loginfo file.\n", 12117721Speter "#\n", 12225843Speter "# One thing that should be noted is the the ALL keyword is not\n", 12325843Speter "# supported. There can be only one entry that matches a given\n", 12417721Speter "# repository.\n", 12517721Speter NULL 12617721Speter}; 12717721Speter 12825843Speterstatic const char *const verifymsg_contents[] = { 12925843Speter "# The \"verifymsg\" file is used to allow verification of logging\n", 13025843Speter "# information. It works best when a template (as specified in the\n", 13125843Speter "# rcsinfo file) is provided for the logging procedure. Given a\n", 13225843Speter "# template with locations for, a bug-id number, a list of people who\n", 13325843Speter "# reviewed the code before it can be checked in, and an external\n", 13425843Speter "# process to catalog the differences that were code reviewed, the\n", 13525843Speter "# following test can be applied to the code:\n", 13625843Speter "#\n", 13725843Speter "# Making sure that the entered bug-id number is correct.\n", 13825843Speter "# Validating that the code that was reviewed is indeed the code being\n", 13925843Speter "# checked in (using the bug-id number or a seperate review\n", 14025843Speter "# number to identify this particular code set.).\n", 14125843Speter "#\n", 14225843Speter "# If any of the above test failed, then the commit would be aborted.\n", 14325843Speter "#\n", 14425843Speter "# Actions such as mailing a copy of the report to each reviewer are\n", 14525843Speter "# better handled by an entry in the loginfo file.\n", 14625843Speter "#\n", 14725843Speter "# One thing that should be noted is the the ALL keyword is not\n", 14825843Speter "# supported. There can be only one entry that matches a given\n", 14925843Speter "# repository.\n", 15025843Speter NULL 15125843Speter}; 15225843Speter 15317721Speterstatic const char *const commitinfo_contents[] = { 15417721Speter "# The \"commitinfo\" file is used to control pre-commit checks.\n", 15517721Speter "# The filter on the right is invoked with the repository and a list \n", 15617721Speter "# of files to check. A non-zero exit of the filter program will \n", 15717721Speter "# cause the commit to be aborted.\n", 15817721Speter "#\n", 15917721Speter "# The first entry on a line is a regular expression which is tested\n", 16017721Speter "# against the directory that the change is being committed to, relative\n", 16117721Speter "# to the $CVSROOT. For the first match that is found, then the remainder\n", 16217721Speter "# of the line is the name of the filter to run.\n", 16317721Speter "#\n", 16417721Speter "# If the repository name does not match any of the regular expressions in this\n", 16517721Speter "# file, the \"DEFAULT\" line is used, if it is specified.\n", 16617721Speter "#\n", 16717721Speter "# If the name \"ALL\" appears as a regular expression it is always used\n", 16817721Speter "# in addition to the first matching regex or \"DEFAULT\".\n", 16917721Speter NULL 17017721Speter}; 17117721Speter 17217721Speterstatic const char *const taginfo_contents[] = { 17317721Speter "# The \"taginfo\" file is used to control pre-tag checks.\n", 17417721Speter "# The filter on the right is invoked with the following arguments:\n", 17517721Speter "#\n", 17617721Speter "# $1 -- tagname\n", 17717721Speter "# $2 -- operation \"add\" for tag, \"mov\" for tag -F, and \"del\" for tag -d\n", 17817721Speter "# $3 -- repository\n", 17917721Speter "# $4-> file revision [file revision ...]\n", 18017721Speter "#\n", 18117721Speter "# A non-zero exit of the filter program will cause the tag to be aborted.\n", 18217721Speter "#\n", 18317721Speter "# The first entry on a line is a regular expression which is tested\n", 18417721Speter "# against the directory that the change is being committed to, relative\n", 18517721Speter "# to the $CVSROOT. For the first match that is found, then the remainder\n", 18617721Speter "# of the line is the name of the filter to run.\n", 18717721Speter "#\n", 18817721Speter "# If the repository name does not match any of the regular expressions in this\n", 18917721Speter "# file, the \"DEFAULT\" line is used, if it is specified.\n", 19017721Speter "#\n", 19117721Speter "# If the name \"ALL\" appears as a regular expression it is always used\n", 19217721Speter "# in addition to the first matching regex or \"DEFAULT\".\n", 19317721Speter NULL 19417721Speter}; 19517721Speter 19617721Speterstatic const char *const checkoutlist_contents[] = { 19717721Speter "# The \"checkoutlist\" file is used to support additional version controlled\n", 19817721Speter "# administrative files in $CVSROOT/CVSROOT, such as template files.\n", 19917721Speter "#\n", 20017721Speter "# The first entry on a line is a filename which will be checked out from\n", 20117721Speter "# the corresponding RCS file in the $CVSROOT/CVSROOT directory.\n", 20217721Speter "# The remainder of the line is an error message to use if the file cannot\n", 20317721Speter "# be checked out.\n", 20417721Speter "#\n", 20517721Speter "# File format:\n", 20617721Speter "#\n", 207128269Speter "# [<whitespace>]<filename>[<whitespace><error message>]<end-of-line>\n", 20817721Speter "#\n", 20917721Speter "# comment lines begin with '#'\n", 21017721Speter NULL 21117721Speter}; 21217721Speter 21317721Speterstatic const char *const cvswrappers_contents[] = { 21432899Speter "# This file affects handling of files based on their names.\n", 21517721Speter "#\n", 216102843Speter#if 0 /* see comments in wrap_add in wrapper.c */ 21732899Speter "# The -t/-f options allow one to treat directories of files\n", 21832899Speter "# as a single file, or to transform a file in other ways on\n", 21932899Speter "# its way in and out of CVS.\n", 22017721Speter "#\n", 221102843Speter#endif 22232899Speter "# The -m option specifies whether CVS attempts to merge files.\n", 22317721Speter "#\n", 22432899Speter "# The -k option specifies keyword expansion (e.g. -kb for binary).\n", 22532899Speter "#\n", 22617721Speter "# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)\n", 22717721Speter "#\n", 22817721Speter "# wildcard [option value][option value]...\n", 22917721Speter "#\n", 23017721Speter "# where option is one of\n", 23117721Speter "# -f from cvs filter value: path to filter\n", 23217721Speter "# -t to cvs filter value: path to filter\n", 23317721Speter "# -m update methodology value: MERGE or COPY\n", 23432899Speter "# -k expansion mode value: b, o, kkv, &c\n", 23517721Speter "#\n", 23617721Speter "# and value is a single-quote delimited value.\n", 23717721Speter "# For example:\n", 23832899Speter "#*.gif -k 'b'\n", 23917721Speter NULL 24017721Speter}; 24117721Speter 24217721Speterstatic const char *const notify_contents[] = { 24317721Speter "# The \"notify\" file controls where notifications from watches set by\n", 24417721Speter "# \"cvs watch add\" or \"cvs edit\" are sent. The first entry on a line is\n", 24517721Speter "# a regular expression which is tested against the directory that the\n", 24617721Speter "# change is being made to, relative to the $CVSROOT. If it matches,\n", 24717721Speter "# then the remainder of the line is a filter program that should contain\n", 24817721Speter "# one occurrence of %s for the user to notify, and information on its\n", 24917721Speter "# standard input.\n", 25017721Speter "#\n", 25117721Speter "# \"ALL\" or \"DEFAULT\" can be used in place of the regular expression.\n", 25217721Speter "#\n", 25317721Speter "# For example:\n", 254102843Speter "#ALL mail -s \"CVS notification\" %s\n", 25517721Speter NULL 25617721Speter}; 25717721Speter 25817721Speterstatic const char *const modules_contents[] = { 25917721Speter "# Three different line formats are valid:\n", 26017721Speter "# key -a aliases...\n", 26117721Speter "# key [options] directory\n", 26217721Speter "# key [options] directory files...\n", 26317721Speter "#\n", 26417721Speter "# Where \"options\" are composed of:\n", 26517721Speter "# -o prog Run \"prog\" on \"cvs checkout\" of module.\n", 26617721Speter "# -e prog Run \"prog\" on \"cvs export\" of module.\n", 26717721Speter "# -t prog Run \"prog\" on \"cvs rtag\" of module.\n", 26817721Speter "# -u prog Run \"prog\" on \"cvs update\" of module.\n", 26917721Speter "# -d dir Place module in directory \"dir\" instead of module name.\n", 27017721Speter "# -l Top-level directory only -- do not recurse.\n", 27117721Speter "#\n", 27225843Speter "# NOTE: If you change any of the \"Run\" options above, you'll have to\n", 27325843Speter "# release and re-checkout any working directories of these modules.\n", 27425843Speter "#\n", 27517721Speter "# And \"directory\" is a path to a directory relative to $CVSROOT.\n", 27617721Speter "#\n", 27717721Speter "# The \"-a\" option specifies an alias. An alias is interpreted as if\n", 27817721Speter "# everything on the right of the \"-a\" had been typed on the command line.\n", 27917721Speter "#\n", 28017721Speter "# You can encode a module within a module by using the special '&'\n", 28117721Speter "# character to interpose another module into the current module. This\n", 28217721Speter "# can be useful for creating a module that consists of many directories\n", 28317721Speter "# spread out over the entire source repository.\n", 28417721Speter NULL 28517721Speter}; 28617721Speter 28732788Speterstatic const char *const config_contents[] = { 28832788Speter "# Set this to \"no\" if pserver shouldn't check system users/passwords\n", 289175282Sobrien "#SystemAuth=yes\n", 29034467Speter "\n", 291177404Sobrien "# Set `IgnoreUnknownConfigKeys' to `yes' to ignore unknown config\n", 292177404Sobrien "# keys which are supported in a future version of CVS.\n", 293177404Sobrien "# This option is intended to be useful as a transition for read-only\n", 294177404Sobrien "# mirror sites when sites may need to be updated later than the\n", 295177404Sobrien "# primary CVS repository.\n", 296177404Sobrien "#IgnoreUnknownConfigKeys=no\n", 297177404Sobrien "\n", 29866528Speter "# Put CVS lock files in this directory rather than directly in the repository.\n", 29966528Speter "#LockDir=/var/lock/cvs\n", 30066528Speter "\n", 30166528Speter#ifdef PRESERVE_PERMISSIONS_SUPPORT 30234467Speter "# Set `PreservePermissions' to `yes' to save file status information\n", 30334467Speter "# in the repository.\n", 30434467Speter "#PreservePermissions=no\n", 30544856Speter "\n", 30666528Speter#endif 30744856Speter "# Set `TopLevelAdmin' to `yes' to create a CVS directory at the top\n", 30844856Speter "# level of the new working directory when using the `cvs checkout'\n", 30944856Speter "# command.\n", 31044856Speter "#TopLevelAdmin=no\n", 31166528Speter "\n", 312128269Speter "# Set `LogHistory' to `all' or `" ALL_HISTORY_REC_TYPES "' to log all transactions to the\n", 31366528Speter "# history file, or a subset as needed (ie `TMAR' logs all write operations)\n", 314128269Speter "#LogHistory=" ALL_HISTORY_REC_TYPES "\n", 315102843Speter "\n", 316102843Speter "# Set `RereadLogAfterVerify' to `always' (the default) to allow the verifymsg\n", 317175282Sobrien "# script to change the log message. Set it to `stat' to force CVS to verify\n", 318102843Speter "# that the file has changed before reading it (this can take up to an extra\n", 319102843Speter "# second per directory being committed, so it is not recommended for large\n", 320102843Speter "# repositories. Set it to `never' (the previous CVS behavior) to prevent\n", 321102843Speter "# verifymsg scripts from changing the log message.\n", 322102843Speter "#RereadLogAfterVerify=always\n", 32332788Speter NULL 32432788Speter}; 32532788Speter 32617721Speterstatic const struct admin_file filelist[] = { 32717721Speter {CVSROOTADM_LOGINFO, 32817721Speter "no logging of 'cvs commit' messages is done without a %s file", 32917721Speter &loginfo_contents[0]}, 33017721Speter {CVSROOTADM_RCSINFO, 33117721Speter "a %s file can be used to configure 'cvs commit' templates", 33217721Speter rcsinfo_contents}, 33317721Speter {CVSROOTADM_EDITINFO, 33417721Speter "a %s file can be used to validate log messages", 33517721Speter editinfo_contents}, 33625843Speter {CVSROOTADM_VERIFYMSG, 33725843Speter "a %s file can be used to validate log messages", 33825843Speter verifymsg_contents}, 33917721Speter {CVSROOTADM_COMMITINFO, 34017721Speter "a %s file can be used to configure 'cvs commit' checking", 34117721Speter commitinfo_contents}, 34217721Speter {CVSROOTADM_TAGINFO, 34317721Speter "a %s file can be used to configure 'cvs tag' checking", 34417721Speter taginfo_contents}, 34517721Speter {CVSROOTADM_IGNORE, 34617721Speter "a %s file can be used to specify files to ignore", 34717721Speter NULL}, 34817721Speter {CVSROOTADM_CHECKOUTLIST, 34917721Speter "a %s file can specify extra CVSROOT files to auto-checkout", 35017721Speter checkoutlist_contents}, 35117721Speter {CVSROOTADM_WRAPPER, 35217721Speter "a %s file can be used to specify files to treat as wrappers", 35317721Speter cvswrappers_contents}, 35417721Speter {CVSROOTADM_NOTIFY, 35517721Speter "a %s file can be used to specify where notifications go", 35617721Speter notify_contents}, 35717721Speter {CVSROOTADM_MODULES, 35817721Speter /* modules is special-cased in mkmodules. */ 35917721Speter NULL, 36017721Speter modules_contents}, 36125843Speter {CVSROOTADM_READERS, 36225843Speter "a %s file specifies read-only users", 36325843Speter NULL}, 36425843Speter {CVSROOTADM_WRITERS, 36525843Speter "a %s file specifies read/write users", 36625843Speter NULL}, 36732788Speter 36832788Speter /* Some have suggested listing CVSROOTADM_PASSWD here too. This 36932788Speter would mean that CVS commands which operate on the 37032788Speter CVSROOTADM_PASSWD file would transmit hashed passwords over the 37132788Speter net. This might seem to be no big deal, as pserver normally 37232788Speter transmits cleartext passwords, but the difference is that 37332788Speter CVSROOTADM_PASSWD contains *all* passwords, not just the ones 37432788Speter currently being used. For example, it could be too easy to 37532788Speter accidentally give someone readonly access to CVSROOTADM_PASSWD 37632788Speter (e.g. via anonymous CVS or cvsweb), and then if there are any 37732788Speter guessable passwords for read/write access (usually there will be) 37832788Speter they get read/write access. 37932788Speter 38032788Speter Another worry is the implications of storing old passwords--if 38132788Speter someone used a password in the past they might be using it 38232788Speter elsewhere, using a similar password, etc, and so saving old 38332788Speter passwords, even hashed, is probably not a good idea. */ 38432788Speter 38532788Speter {CVSROOTADM_CONFIG, 38632788Speter "a %s file configures various behaviors", 38732788Speter config_contents}, 38854431Speter {NULL, NULL, NULL} 38917721Speter}; 39017721Speter 39117721Speter/* Rebuild the checked out administrative files in directory DIR. */ 39217721Speterint 39317721Spetermkmodules (dir) 39417721Speter char *dir; 39517721Speter{ 39617721Speter struct saved_cwd cwd; 39725843Speter char *temp; 39817721Speter char *cp, *last, *fname; 39917721Speter#ifdef MY_NDBM 40017721Speter DBM *db; 40117721Speter#endif 40217721Speter FILE *fp; 40325843Speter char *line = NULL; 40425843Speter size_t line_allocated = 0; 40517721Speter const struct admin_file *fileptr; 40617721Speter 40766528Speter if (noexec) 40866528Speter return 0; 40966528Speter 41017721Speter if (save_cwd (&cwd)) 41125843Speter error_exit (); 41217721Speter 41325843Speter if ( CVS_CHDIR (dir) < 0) 41417721Speter error (1, errno, "cannot chdir to %s", dir); 41517721Speter 41617721Speter /* 41717721Speter * First, do the work necessary to update the "modules" database. 41817721Speter */ 41925843Speter temp = make_tempfile (); 42017721Speter switch (checkout_file (CVSROOTADM_MODULES, temp)) 42117721Speter { 42217721Speter 42317721Speter case 0: /* everything ok */ 42417721Speter#ifdef MY_NDBM 42517721Speter /* open it, to generate any duplicate errors */ 42617721Speter if ((db = dbm_open (temp, O_RDONLY, 0666)) != NULL) 42717721Speter dbm_close (db); 42817721Speter#else 42917721Speter write_dbmfile (temp); 43017721Speter rename_dbmfile (temp); 43117721Speter#endif 43217721Speter rename_rcsfile (temp, CVSROOTADM_MODULES); 43317721Speter break; 43417721Speter 43517721Speter default: 43617721Speter error (0, 0, 43717721Speter "'cvs checkout' is less functional without a %s file", 43817721Speter CVSROOTADM_MODULES); 43917721Speter break; 44017721Speter } /* switch on checkout_file() */ 44117721Speter 44254431Speter if (unlink_file (temp) < 0 44354431Speter && !existence_error (errno)) 44454431Speter error (0, errno, "cannot remove %s", temp); 44525843Speter free (temp); 44617721Speter 44717721Speter /* Checkout the files that need it in CVSROOT dir */ 44817721Speter for (fileptr = filelist; fileptr && fileptr->filename; fileptr++) { 44917721Speter if (fileptr->errormsg == NULL) 45017721Speter continue; 45125843Speter temp = make_tempfile (); 45217721Speter if (checkout_file (fileptr->filename, temp) == 0) 45317721Speter rename_rcsfile (temp, fileptr->filename); 45417721Speter#if 0 45517721Speter /* 45617721Speter * If there was some problem other than the file not existing, 45717721Speter * checkout_file already printed a real error message. If the 45817721Speter * file does not exist, it is harmless--it probably just means 45917721Speter * that the repository was created with an old version of CVS 46017721Speter * which didn't have so many files in CVSROOT. 46117721Speter */ 46217721Speter else if (fileptr->errormsg) 46317721Speter error (0, 0, fileptr->errormsg, fileptr->filename); 46417721Speter#endif 46554431Speter if (unlink_file (temp) < 0 46654431Speter && !existence_error (errno)) 46754431Speter error (0, errno, "cannot remove %s", temp); 46825843Speter free (temp); 46917721Speter } 47017721Speter 47125843Speter fp = CVS_FOPEN (CVSROOTADM_CHECKOUTLIST, "r"); 47217721Speter if (fp) 47317721Speter { 47417721Speter /* 47517721Speter * File format: 476128269Speter * [<whitespace>]<filename>[<whitespace><error message>]<end-of-line> 47717721Speter * 47817721Speter * comment lines begin with '#' 47917721Speter */ 48025843Speter while (getline (&line, &line_allocated, fp) >= 0) 48117721Speter { 48217721Speter /* skip lines starting with # */ 48317721Speter if (line[0] == '#') 48417721Speter continue; 48517721Speter 48617721Speter if ((last = strrchr (line, '\n')) != NULL) 48717721Speter *last = '\0'; /* strip the newline */ 48817721Speter 48917721Speter /* Skip leading white space. */ 49054431Speter for (fname = line; 49154431Speter *fname && isspace ((unsigned char) *fname); 49254431Speter fname++) 49317721Speter ; 49417721Speter 49517721Speter /* Find end of filename. */ 49654431Speter for (cp = fname; *cp && !isspace ((unsigned char) *cp); cp++) 49717721Speter ; 49817721Speter *cp = '\0'; 49917721Speter 50025843Speter temp = make_tempfile (); 50117721Speter if (checkout_file (fname, temp) == 0) 50217721Speter { 50317721Speter rename_rcsfile (temp, fname); 50417721Speter } 50517721Speter else 50617721Speter { 507128269Speter /* Skip leading white space before the error message. */ 50854431Speter for (cp++; 509128269Speter cp < last && *cp && isspace ((unsigned char) *cp); 51054431Speter cp++) 51117721Speter ; 51217721Speter if (cp < last && *cp) 513128269Speter error (0, 0, "%s", cp); 51417721Speter } 51554431Speter if (unlink_file (temp) < 0 51654431Speter && !existence_error (errno)) 51754431Speter error (0, errno, "cannot remove %s", temp); 51825843Speter free (temp); 51917721Speter } 52025843Speter if (line) 52125843Speter free (line); 52225843Speter if (ferror (fp)) 52325843Speter error (0, errno, "cannot read %s", CVSROOTADM_CHECKOUTLIST); 52425843Speter if (fclose (fp) < 0) 52525843Speter error (0, errno, "cannot close %s", CVSROOTADM_CHECKOUTLIST); 52617721Speter } 52725843Speter else 52825843Speter { 52925843Speter /* Error from CVS_FOPEN. */ 53025843Speter if (!existence_error (errno)) 53125843Speter error (0, errno, "cannot open %s", CVSROOTADM_CHECKOUTLIST); 53225843Speter } 53317721Speter 53417721Speter if (restore_cwd (&cwd, NULL)) 53525843Speter error_exit (); 53617721Speter free_cwd (&cwd); 53717721Speter 53817721Speter return (0); 53917721Speter} 54017721Speter 54117721Speter/* 54217721Speter * Yeah, I know, there are NFS race conditions here. 54317721Speter */ 54425843Speterstatic char * 54525843Spetermake_tempfile () 54617721Speter{ 54717721Speter static int seed = 0; 54817721Speter int fd; 54925843Speter char *temp; 55017721Speter 55117721Speter if (seed == 0) 55217721Speter seed = getpid (); 55325843Speter temp = xmalloc (sizeof (BAKPREFIX) + 40); 55417721Speter while (1) 55517721Speter { 55617721Speter (void) sprintf (temp, "%s%d", BAKPREFIX, seed++); 55725843Speter if ((fd = CVS_OPEN (temp, O_CREAT|O_EXCL|O_RDWR, 0666)) != -1) 55817721Speter break; 55917721Speter if (errno != EEXIST) 56017721Speter error (1, errno, "cannot create temporary file %s", temp); 56117721Speter } 56217721Speter if (close(fd) < 0) 56317721Speter error(1, errno, "cannot close temporary file %s", temp); 56425843Speter return temp; 56517721Speter} 56617721Speter 56754431Speter/* Get a file. If the file does not exist, return 1 silently. If 56854431Speter there is an error, print a message and return 1 (FIXME: probably 56954431Speter not a very clean convention). On success, return 0. */ 57054431Speter 57117721Speterstatic int 57217721Spetercheckout_file (file, temp) 57317721Speter char *file; 57417721Speter char *temp; 57517721Speter{ 57625843Speter char *rcs; 57725843Speter RCSNode *rcsnode; 57817721Speter int retcode = 0; 57917721Speter 58025843Speter if (noexec) 58125843Speter return 0; 58225843Speter 58325843Speter rcs = xmalloc (strlen (file) + 5); 58425843Speter strcpy (rcs, file); 58525843Speter strcat (rcs, RCSEXT); 58617721Speter if (!isfile (rcs)) 58725843Speter { 58825843Speter free (rcs); 58917721Speter return (1); 59025843Speter } 591175282Sobrien 59225843Speter rcsnode = RCS_parsercsfile (rcs); 593175282Sobrien if (!rcsnode) 594175282Sobrien { 595175282Sobrien /* Probably not necessary (?); RCS_parsercsfile already printed a 596175282Sobrien message. */ 597175282Sobrien error (0, 0, "Failed to parse `%s'.", rcs); 598175282Sobrien free (rcs); 599175282Sobrien return 1; 600175282Sobrien } 601175282Sobrien 60225843Speter retcode = RCS_checkout (rcsnode, NULL, NULL, NULL, NULL, temp, 60325843Speter (RCSCHECKOUTPROC) NULL, (void *) NULL); 60425843Speter if (retcode != 0) 60517721Speter { 60654431Speter /* Probably not necessary (?); RCS_checkout already printed a 60754431Speter message. */ 60834467Speter error (0, 0, "failed to check out %s file", 60925843Speter file); 61017721Speter } 61125843Speter freercsnode (&rcsnode); 61225843Speter free (rcs); 61317721Speter return (retcode); 61417721Speter} 61517721Speter 61617721Speter#ifndef MY_NDBM 61717721Speter 61817721Speterstatic void 61917721Speterwrite_dbmfile (temp) 62017721Speter char *temp; 62117721Speter{ 62217721Speter char line[DBLKSIZ], value[DBLKSIZ]; 62317721Speter FILE *fp; 62417721Speter DBM *db; 62517721Speter char *cp, *vp; 62617721Speter datum key, val; 62717721Speter int len, cont, err = 0; 62817721Speter 62917721Speter fp = open_file (temp, "r"); 63017721Speter if ((db = dbm_open (temp, O_RDWR | O_CREAT | O_TRUNC, 0666)) == NULL) 63117721Speter error (1, errno, "cannot open dbm file %s for creation", temp); 63217721Speter for (cont = 0; fgets (line, sizeof (line), fp) != NULL;) 63317721Speter { 63417721Speter if ((cp = strrchr (line, '\n')) != NULL) 63517721Speter *cp = '\0'; /* strip the newline */ 63617721Speter 63717721Speter /* 63817721Speter * Add the line to the value, at the end if this is a continuation 63917721Speter * line; otherwise at the beginning, but only after any trailing 64017721Speter * backslash is removed. 64117721Speter */ 64217721Speter vp = value; 64317721Speter if (cont) 64417721Speter vp += strlen (value); 64517721Speter 64617721Speter /* 64717721Speter * See if the line we read is a continuation line, and strip the 64817721Speter * backslash if so. 64917721Speter */ 65017721Speter len = strlen (line); 65117721Speter if (len > 0) 65217721Speter cp = &line[len - 1]; 65317721Speter else 65417721Speter cp = line; 65517721Speter if (*cp == '\\') 65617721Speter { 65717721Speter cont = 1; 65817721Speter *cp = '\0'; 65917721Speter } 66017721Speter else 66117721Speter { 66217721Speter cont = 0; 66317721Speter } 66417721Speter (void) strcpy (vp, line); 66517721Speter if (value[0] == '#') 66617721Speter continue; /* comment line */ 66717721Speter vp = value; 66854431Speter while (*vp && isspace ((unsigned char) *vp)) 66917721Speter vp++; 67017721Speter if (*vp == '\0') 67117721Speter continue; /* empty line */ 67217721Speter 67317721Speter /* 67417721Speter * If this was not a continuation line, add the entry to the database 67517721Speter */ 67617721Speter if (!cont) 67717721Speter { 67817721Speter key.dptr = vp; 67954431Speter while (*vp && !isspace ((unsigned char) *vp)) 68017721Speter vp++; 68117721Speter key.dsize = vp - key.dptr; 68217721Speter *vp++ = '\0'; /* NULL terminate the key */ 68354431Speter while (*vp && isspace ((unsigned char) *vp)) 68417721Speter vp++; /* skip whitespace to value */ 68517721Speter if (*vp == '\0') 68617721Speter { 68717721Speter error (0, 0, "warning: NULL value for key `%s'", key.dptr); 68817721Speter continue; 68917721Speter } 69017721Speter val.dptr = vp; 69117721Speter val.dsize = strlen (vp); 69217721Speter if (dbm_store (db, key, val, DBM_INSERT) == 1) 69317721Speter { 69417721Speter error (0, 0, "duplicate key found for `%s'", key.dptr); 69517721Speter err++; 69617721Speter } 69717721Speter } 69817721Speter } 69917721Speter dbm_close (db); 70054431Speter if (fclose (fp) < 0) 70154431Speter error (0, errno, "cannot close %s", temp); 70217721Speter if (err) 70317721Speter { 70454431Speter /* I think that the size of the buffer needed here is 70554431Speter just determined by sizeof (CVSROOTADM_MODULES), the 70654431Speter filenames created by make_tempfile, and other things that won't 70754431Speter overflow. */ 70817721Speter char dotdir[50], dotpag[50], dotdb[50]; 70917721Speter 71017721Speter (void) sprintf (dotdir, "%s.dir", temp); 71117721Speter (void) sprintf (dotpag, "%s.pag", temp); 71217721Speter (void) sprintf (dotdb, "%s.db", temp); 71354431Speter if (unlink_file (dotdir) < 0 71454431Speter && !existence_error (errno)) 71554431Speter error (0, errno, "cannot remove %s", dotdir); 71654431Speter if (unlink_file (dotpag) < 0 71754431Speter && !existence_error (errno)) 71854431Speter error (0, errno, "cannot remove %s", dotpag); 71954431Speter if (unlink_file (dotdb) < 0 72054431Speter && !existence_error (errno)) 72154431Speter error (0, errno, "cannot remove %s", dotdb); 72217721Speter error (1, 0, "DBM creation failed; correct above errors"); 72317721Speter } 72417721Speter} 72517721Speter 72617721Speterstatic void 72717721Speterrename_dbmfile (temp) 72817721Speter char *temp; 72917721Speter{ 73054431Speter /* I think that the size of the buffer needed here is 73154431Speter just determined by sizeof (CVSROOTADM_MODULES), the 73254431Speter filenames created by make_tempfile, and other things that won't 73354431Speter overflow. */ 73417721Speter char newdir[50], newpag[50], newdb[50]; 73517721Speter char dotdir[50], dotpag[50], dotdb[50]; 73617721Speter char bakdir[50], bakpag[50], bakdb[50]; 73717721Speter 73854431Speter int dir1_errno = 0, pag1_errno = 0, db1_errno = 0; 73954431Speter int dir2_errno = 0, pag2_errno = 0, db2_errno = 0; 74054431Speter int dir3_errno = 0, pag3_errno = 0, db3_errno = 0; 74154431Speter 74217721Speter (void) sprintf (dotdir, "%s.dir", CVSROOTADM_MODULES); 74317721Speter (void) sprintf (dotpag, "%s.pag", CVSROOTADM_MODULES); 74417721Speter (void) sprintf (dotdb, "%s.db", CVSROOTADM_MODULES); 74517721Speter (void) sprintf (bakdir, "%s%s.dir", BAKPREFIX, CVSROOTADM_MODULES); 74617721Speter (void) sprintf (bakpag, "%s%s.pag", BAKPREFIX, CVSROOTADM_MODULES); 74717721Speter (void) sprintf (bakdb, "%s%s.db", BAKPREFIX, CVSROOTADM_MODULES); 74817721Speter (void) sprintf (newdir, "%s.dir", temp); 74917721Speter (void) sprintf (newpag, "%s.pag", temp); 75017721Speter (void) sprintf (newdb, "%s.db", temp); 75117721Speter 75217721Speter (void) chmod (newdir, 0666); 75317721Speter (void) chmod (newpag, 0666); 75417721Speter (void) chmod (newdb, 0666); 75517721Speter 75617721Speter /* don't mess with me */ 75717721Speter SIG_beginCrSect (); 75817721Speter 75954431Speter /* rm .#modules.dir .#modules.pag */ 76054431Speter if (unlink_file (bakdir) < 0) 76154431Speter dir1_errno = errno; 76254431Speter if (unlink_file (bakpag) < 0) 76354431Speter pag1_errno = errno; 76454431Speter if (unlink_file (bakdb) < 0) 76554431Speter db1_errno = errno; 76617721Speter 76754431Speter /* mv modules.dir .#modules.dir */ 76854431Speter if (CVS_RENAME (dotdir, bakdir) < 0) 76954431Speter dir2_errno = errno; 77054431Speter /* mv modules.pag .#modules.pag */ 77154431Speter if (CVS_RENAME (dotpag, bakpag) < 0) 77254431Speter pag2_errno = errno; 77354431Speter /* mv modules.db .#modules.db */ 77454431Speter if (CVS_RENAME (dotdb, bakdb) < 0) 77554431Speter db2_errno = errno; 77654431Speter 77754431Speter /* mv "temp".dir modules.dir */ 77854431Speter if (CVS_RENAME (newdir, dotdir) < 0) 77954431Speter dir3_errno = errno; 78054431Speter /* mv "temp".pag modules.pag */ 78154431Speter if (CVS_RENAME (newpag, dotpag) < 0) 78254431Speter pag3_errno = errno; 78354431Speter /* mv "temp".db modules.db */ 78454431Speter if (CVS_RENAME (newdb, dotdb) < 0) 78554431Speter db3_errno = errno; 78654431Speter 78717721Speter /* OK -- make my day */ 78817721Speter SIG_endCrSect (); 78954431Speter 79054431Speter /* I didn't want to call error() when we had signals blocked 79154431Speter (unnecessary?), but do it now. */ 79254431Speter if (dir1_errno && !existence_error (dir1_errno)) 79354431Speter error (0, dir1_errno, "cannot remove %s", bakdir); 79454431Speter if (pag1_errno && !existence_error (pag1_errno)) 79554431Speter error (0, pag1_errno, "cannot remove %s", bakpag); 79654431Speter if (db1_errno && !existence_error (db1_errno)) 79754431Speter error (0, db1_errno, "cannot remove %s", bakdb); 79854431Speter 79954431Speter if (dir2_errno && !existence_error (dir2_errno)) 80054431Speter error (0, dir2_errno, "cannot remove %s", bakdir); 80154431Speter if (pag2_errno && !existence_error (pag2_errno)) 80254431Speter error (0, pag2_errno, "cannot remove %s", bakpag); 80354431Speter if (db2_errno && !existence_error (db2_errno)) 80454431Speter error (0, db2_errno, "cannot remove %s", bakdb); 80554431Speter 80654431Speter if (dir3_errno && !existence_error (dir3_errno)) 80754431Speter error (0, dir3_errno, "cannot remove %s", bakdir); 80854431Speter if (pag3_errno && !existence_error (pag3_errno)) 80954431Speter error (0, pag3_errno, "cannot remove %s", bakpag); 81054431Speter if (db3_errno && !existence_error (db3_errno)) 81154431Speter error (0, db3_errno, "cannot remove %s", bakdb); 81217721Speter} 81317721Speter 81417721Speter#endif /* !MY_NDBM */ 81517721Speter 81617721Speterstatic void 81717721Speterrename_rcsfile (temp, real) 81817721Speter char *temp; 81917721Speter char *real; 82017721Speter{ 82125843Speter char *bak; 82217721Speter struct stat statbuf; 82325843Speter char *rcs; 82425843Speter 82517721Speter /* Set "x" bits if set in original. */ 82625843Speter rcs = xmalloc (strlen (real) + sizeof (RCSEXT) + 10); 82717721Speter (void) sprintf (rcs, "%s%s", real, RCSEXT); 82817721Speter statbuf.st_mode = 0; /* in case rcs file doesn't exist, but it should... */ 82954431Speter if (CVS_STAT (rcs, &statbuf) < 0 83054431Speter && !existence_error (errno)) 83154431Speter error (0, errno, "cannot stat %s", rcs); 83225843Speter free (rcs); 83317721Speter 83417721Speter if (chmod (temp, 0444 | (statbuf.st_mode & 0111)) < 0) 83517721Speter error (0, errno, "warning: cannot chmod %s", temp); 83625843Speter bak = xmalloc (strlen (real) + sizeof (BAKPREFIX) + 10); 83717721Speter (void) sprintf (bak, "%s%s", BAKPREFIX, real); 83854431Speter 83954431Speter /* rm .#loginfo */ 84054431Speter if (unlink_file (bak) < 0 84154431Speter && !existence_error (errno)) 84254431Speter error (0, errno, "cannot remove %s", bak); 84354431Speter 84454431Speter /* mv loginfo .#loginfo */ 84554431Speter if (CVS_RENAME (real, bak) < 0 84654431Speter && !existence_error (errno)) 84754431Speter error (0, errno, "cannot rename %s to %s", real, bak); 84854431Speter 84954431Speter /* mv "temp" loginfo */ 85054431Speter if (CVS_RENAME (temp, real) < 0 85154431Speter && !existence_error (errno)) 85254431Speter error (0, errno, "cannot rename %s to %s", temp, real); 85354431Speter 85425843Speter free (bak); 85517721Speter} 856177404Sobrien 857177404Sobrien/* 858177404Sobrien * Walk PATH backwards to the root directory looking for the root of a 859177404Sobrien * repository. 860177404Sobrien */ 861177404Sobrienstatic char * 862177404Sobrienin_repository (const char *path) 863177404Sobrien{ 864177404Sobrien char *cp = xstrdup (path); 865177404Sobrien 866177404Sobrien for (;;) 867177404Sobrien { 868177404Sobrien if (isdir (cp)) 869177404Sobrien { 870177404Sobrien int foundit; 871177404Sobrien char *adm = xmalloc (strlen(cp) + strlen(CVSROOTADM) + 2); 872177404Sobrien sprintf (adm, "%s/%s", cp, CVSROOTADM); 873177404Sobrien foundit = isdir (adm); 874177404Sobrien free (adm); 875177404Sobrien if (foundit) return cp; 876177404Sobrien } 877177404Sobrien 878177404Sobrien /* If last_component() returns the empty string, then cp either 879177404Sobrien * points at the system root or is the empty string itself. 880177404Sobrien */ 881177404Sobrien if (!*last_component (cp) || !strcmp (cp, ".") 882177404Sobrien || last_component(cp) == cp) 883177404Sobrien break; 884177404Sobrien 885177404Sobrien cp[strlen(cp) - strlen(last_component(cp)) - 1] = '\0'; 886177404Sobrien } 887177404Sobrien 888177404Sobrien return NULL; 889177404Sobrien} 890177404Sobrien 89117721Speter 89217721Speterconst char *const init_usage[] = { 89317721Speter "Usage: %s %s\n", 89432788Speter "(Specify the --help global option for a list of other help options)\n", 89517721Speter NULL 89617721Speter}; 89717721Speter 89817721Speterint 89917721Speterinit (argc, argv) 90017721Speter int argc; 90117721Speter char **argv; 90217721Speter{ 90317721Speter /* Name of CVSROOT directory. */ 90417721Speter char *adm; 90517721Speter /* Name of this administrative file. */ 90617721Speter char *info; 90717721Speter /* Name of ,v file for this administrative file. */ 90817721Speter char *info_v; 90926804Speter /* Exit status. */ 910128269Speter int err = 0; 91117721Speter 912177404Sobrien char *root_dir; 91317721Speter const struct admin_file *fileptr; 91417721Speter 915177404Sobrien assert (!server_active); 916177404Sobrien 91717721Speter umask (cvsumask); 91817721Speter 91918592Sjdp if (argc == -1 || argc > 1) 92017721Speter usage (init_usage); 92117721Speter 92225843Speter#ifdef CLIENT_SUPPORT 92381407Speter if (current_parsed_root->isremote) 92417721Speter { 92517721Speter start_server (); 92617721Speter 92717721Speter ign_setup (); 92817721Speter send_init_command (); 92917721Speter return get_responses_and_close (); 93017721Speter } 93125843Speter#endif /* CLIENT_SUPPORT */ 93217721Speter 933177404Sobrien root_dir = in_repository (current_parsed_root->directory); 934177404Sobrien 935177404Sobrien if (root_dir && strcmp (root_dir, current_parsed_root->directory)) 936177404Sobrien error (1, 0, 937177404Sobrien "Cannot initialize repository under existing CVSROOT: `%s'", 938177404Sobrien root_dir); 939177404Sobrien free (root_dir); 940177404Sobrien 94117721Speter /* Note: we do *not* create parent directories as needed like the 94217721Speter old cvsinit.sh script did. Few utilities do that, and a 94317721Speter non-existent parent directory is as likely to be a typo as something 94417721Speter which needs to be created. */ 94581407Speter mkdir_if_needed (current_parsed_root->directory); 94617721Speter 94781407Speter adm = xmalloc (strlen (current_parsed_root->directory) + sizeof (CVSROOTADM) + 2); 94881407Speter sprintf (adm, "%s/%s", current_parsed_root->directory, CVSROOTADM); 94917721Speter mkdir_if_needed (adm); 95017721Speter 95126804Speter /* This is needed because we pass "fileptr->filename" not "info" 95226804Speter to add_rcs_file below. I think this would be easy to change, 95326804Speter thus nuking the need for CVS_CHDIR here, but I haven't looked 95426804Speter closely (e.g. see wrappers calls within add_rcs_file). */ 95525843Speter if ( CVS_CHDIR (adm) < 0) 95617721Speter error (1, errno, "cannot change to directory %s", adm); 95717721Speter 95866528Speter /* Make Emptydir so it's there if we need it */ 95966528Speter mkdir_if_needed (CVSNULLREPOS); 96066528Speter 96117721Speter /* 80 is long enough for all the administrative file names, plus 96217721Speter "/" and so on. */ 96317721Speter info = xmalloc (strlen (adm) + 80); 96417721Speter info_v = xmalloc (strlen (adm) + 80); 96517721Speter for (fileptr = filelist; fileptr && fileptr->filename; ++fileptr) 96617721Speter { 96717721Speter if (fileptr->contents == NULL) 96817721Speter continue; 96917721Speter strcpy (info, adm); 97017721Speter strcat (info, "/"); 97117721Speter strcat (info, fileptr->filename); 97217721Speter strcpy (info_v, info); 97317721Speter strcat (info_v, RCSEXT); 97417721Speter if (isfile (info_v)) 97517721Speter /* We will check out this file in the mkmodules step. 97617721Speter Nothing else is required. */ 97717721Speter ; 97817721Speter else 97917721Speter { 98017721Speter int retcode; 98117721Speter 98217721Speter if (!isfile (info)) 98317721Speter { 98417721Speter FILE *fp; 98517721Speter const char * const *p; 98617721Speter 98717721Speter fp = open_file (info, "w"); 98817721Speter for (p = fileptr->contents; *p != NULL; ++p) 98917721Speter if (fputs (*p, fp) < 0) 99017721Speter error (1, errno, "cannot write %s", info); 99117721Speter if (fclose (fp) < 0) 99217721Speter error (1, errno, "cannot close %s", info); 99317721Speter } 99426804Speter /* The message used to say " of " and fileptr->filename after 99526804Speter "initial checkin" but I fail to see the point as we know what 99626804Speter file it is from the name. */ 99726804Speter retcode = add_rcs_file ("initial checkin", info_v, 99832788Speter fileptr->filename, "1.1", NULL, 99932788Speter 100032788Speter /* No vendor branch. */ 100132788Speter NULL, NULL, 0, NULL, 100232788Speter 100332788Speter NULL, 0, NULL); 100417721Speter if (retcode != 0) 100526804Speter /* add_rcs_file already printed an error message. */ 100626804Speter err = 1; 100717721Speter } 100817721Speter } 100917721Speter 101017721Speter /* Turn on history logging by default. The user can remove the file 101117721Speter to disable it. */ 101217721Speter strcpy (info, adm); 101317721Speter strcat (info, "/"); 101417721Speter strcat (info, CVSROOTADM_HISTORY); 101517721Speter if (!isfile (info)) 101617721Speter { 101717721Speter FILE *fp; 101817721Speter 101917721Speter fp = open_file (info, "w"); 102017721Speter if (fclose (fp) < 0) 102117721Speter error (1, errno, "cannot close %s", info); 102266528Speter 102366528Speter /* Make the new history file world-writeable, since every CVS 102466528Speter user will need to be able to write to it. We use chmod() 102566528Speter because xchmod() is too shy. */ 102666528Speter chmod (info, 0666); 102717721Speter } 102817721Speter 102966528Speter /* Make an empty val-tags file to prevent problems creating it later. */ 103066528Speter strcpy (info, adm); 103166528Speter strcat (info, "/"); 103266528Speter strcat (info, CVSROOTADM_VALTAGS); 103366528Speter if (!isfile (info)) 103466528Speter { 103566528Speter FILE *fp; 103666528Speter 103766528Speter fp = open_file (info, "w"); 103866528Speter if (fclose (fp) < 0) 103966528Speter error (1, errno, "cannot close %s", info); 104066528Speter 104166528Speter /* Make the new val-tags file world-writeable, since every CVS 104266528Speter user will need to be able to write to it. We use chmod() 104366528Speter because xchmod() is too shy. */ 104466528Speter chmod (info, 0666); 104566528Speter } 104666528Speter 104717721Speter free (info); 104817721Speter free (info_v); 104917721Speter 105017721Speter mkmodules (adm); 105117721Speter 105217721Speter free (adm); 1053128269Speter return err; 105417721Speter} 1055