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