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 16 static int checkout_file PROTO((char *file, char *temp)); 17 static char *make_tempfile PROTO((void)); 18 static void rename_rcsfile PROTO((char *temp, char *real)); 19 20 #ifndef MY_NDBM 21 static void rename_dbmfile PROTO((char *temp)); 22 static void write_dbmfile PROTO((char *temp)); 23 #endif /* !MY_NDBM */ 24 25 /* Structure which describes an administrative file. */ 26 struct 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 49 static const char *const loginfo_contents[] = { 50 "# The \"loginfo\" file controls where \"cvs commit\" log information\n", 51 "# is sent. The first entry on a line is a regular expression which must match\n", 52 "# the directory that the change is being made to, relative to the\n", 53 "# $CVSROOT. If a match is found, then the remainder of the line is a filter\n", 54 "# program that should expect log information on its standard input.\n", 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 79 static const char *const rcsinfo_contents[] = { 80 "# The \"rcsinfo\" file is used to control templates with which the editor\n", 81 "# is invoked on commit and import.\n", 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 96 static const char *const editinfo_contents[] = { 97 "# The \"editinfo\" file is used to allow verification of logging\n", 98 "# information. It works best when a template (as specified in the\n", 99 "# rcsinfo file) is provided for the logging procedure. Given a\n", 100 "# template with locations for, a bug-id number, a list of people who\n", 101 "# reviewed the code before it can be checked in, and an external\n", 102 "# process to catalog the differences that were code reviewed, the\n", 103 "# following test can be applied to the code:\n", 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 121 static const char *const verifymsg_contents[] = { 122 "# The \"verifymsg\" file is used to allow verification of logging\n", 123 "# information. It works best when a template (as specified in the\n", 124 "# rcsinfo file) is provided for the logging procedure. Given a\n", 125 "# template with locations for, a bug-id number, a list of people who\n", 126 "# reviewed the code before it can be checked in, and an external\n", 127 "# process to catalog the differences that were code reviewed, the\n", 128 "# following test can be applied to the code:\n", 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 146 static const char *const commitinfo_contents[] = { 147 "# The \"commitinfo\" file is used to control pre-commit checks.\n", 148 "# The filter on the right is invoked with the repository and a list \n", 149 "# of files to check. A non-zero exit of the filter program will \n", 150 "# cause the commit to be aborted.\n", 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 165 static const char *const taginfo_contents[] = { 166 "# The \"taginfo\" file is used to control pre-tag checks.\n", 167 "# The filter on the right is invoked with the following arguments:\n", 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 189 static const char *const checkoutlist_contents[] = { 190 "# The \"checkoutlist\" file is used to support additional version controlled\n", 191 "# administrative files in $CVSROOT/CVSROOT, such as template files.\n", 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 206 static 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 233 static 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 249 static 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 279 static const char *const config_contents[] = { 280 "# Set this to \"no\" if pserver shouldn't check system users/passwords\n", 281 "#SystemAuth=no\n", 282 "\n", 283 "# Set `PreservePermissions' to `yes' to save file status information\n", 284 "# in the repository.\n", 285 "#PreservePermissions=no\n", 286 "\n", 287 "# Set `TopLevelAdmin' to `yes' to create a CVS directory at the top\n", 288 "# level of the new working directory when using the `cvs checkout'\n", 289 "# command.\n", 290 "#TopLevelAdmin=no\n", 291 "\n", 292 "# Set this to the name of a local tag to use in addition to Id\n", 293 "#tag=OurTag\n", 294 "\n", 295 "# Set this to the default umask to use when creating files and directories\n", 296 "#umask=002\n", 297 "\n", 298 "# Set this to the default data resource limit to use\n", 299 "#dlimit=65536\n", 300 NULL 301 }; 302 303 static const struct admin_file filelist[] = { 304 {CVSROOTADM_LOGINFO, 305 "no logging of 'cvs commit' messages is done without a %s file", 306 &loginfo_contents[0]}, 307 {CVSROOTADM_RCSINFO, 308 "a %s file can be used to configure 'cvs commit' templates", 309 rcsinfo_contents}, 310 {CVSROOTADM_EDITINFO, 311 "a %s file can be used to validate log messages", 312 editinfo_contents}, 313 {CVSROOTADM_VERIFYMSG, 314 "a %s file can be used to validate log messages", 315 verifymsg_contents}, 316 {CVSROOTADM_COMMITINFO, 317 "a %s file can be used to configure 'cvs commit' checking", 318 commitinfo_contents}, 319 {CVSROOTADM_TAGINFO, 320 "a %s file can be used to configure 'cvs tag' checking", 321 taginfo_contents}, 322 {CVSROOTADM_IGNORE, 323 "a %s file can be used to specify files to ignore", 324 NULL}, 325 {CVSROOTADM_CHECKOUTLIST, 326 "a %s file can specify extra CVSROOT files to auto-checkout", 327 checkoutlist_contents}, 328 {CVSROOTADM_WRAPPER, 329 "a %s file can be used to specify files to treat as wrappers", 330 cvswrappers_contents}, 331 {CVSROOTADM_NOTIFY, 332 "a %s file can be used to specify where notifications go", 333 notify_contents}, 334 {CVSROOTADM_MODULES, 335 /* modules is special-cased in mkmodules. */ 336 NULL, 337 modules_contents}, 338 {CVSROOTADM_READERS, 339 "a %s file specifies read-only users", 340 NULL}, 341 {CVSROOTADM_WRITERS, 342 "a %s file specifies read/write users", 343 NULL}, 344 345 /* Some have suggested listing CVSROOTADM_PASSWD here too. This 346 would mean that CVS commands which operate on the 347 CVSROOTADM_PASSWD file would transmit hashed passwords over the 348 net. This might seem to be no big deal, as pserver normally 349 transmits cleartext passwords, but the difference is that 350 CVSROOTADM_PASSWD contains *all* passwords, not just the ones 351 currently being used. For example, it could be too easy to 352 accidentally give someone readonly access to CVSROOTADM_PASSWD 353 (e.g. via anonymous CVS or cvsweb), and then if there are any 354 guessable passwords for read/write access (usually there will be) 355 they get read/write access. 356 357 Another worry is the implications of storing old passwords--if 358 someone used a password in the past they might be using it 359 elsewhere, using a similar password, etc, and so saving old 360 passwords, even hashed, is probably not a good idea. */ 361 362 {CVSROOTADM_CONFIG, 363 "a %s file configures various behaviors", 364 config_contents}, 365 {NULL, NULL} 366 }; 367 368 /* Rebuild the checked out administrative files in directory DIR. */ 369 int 370 mkmodules (dir) 371 char *dir; 372 { 373 struct saved_cwd cwd; 374 char *temp; 375 char *cp, *last, *fname; 376 #ifdef MY_NDBM 377 DBM *db; 378 #endif 379 FILE *fp; 380 char *line = NULL; 381 size_t line_allocated = 0; 382 const struct admin_file *fileptr; 383 384 if (save_cwd (&cwd)) 385 error_exit (); 386 387 if ( CVS_CHDIR (dir) < 0) 388 error (1, errno, "cannot chdir to %s", dir); 389 390 /* 391 * First, do the work necessary to update the "modules" database. 392 */ 393 temp = make_tempfile (); 394 switch (checkout_file (CVSROOTADM_MODULES, temp)) 395 { 396 397 case 0: /* everything ok */ 398 #ifdef MY_NDBM 399 /* open it, to generate any duplicate errors */ 400 if ((db = dbm_open (temp, O_RDONLY, 0666)) != NULL) 401 dbm_close (db); 402 #else 403 write_dbmfile (temp); 404 rename_dbmfile (temp); 405 #endif 406 rename_rcsfile (temp, CVSROOTADM_MODULES); 407 break; 408 409 default: 410 error (0, 0, 411 "'cvs checkout' is less functional without a %s file", 412 CVSROOTADM_MODULES); 413 break; 414 } /* switch on checkout_file() */ 415 416 if (unlink_file (temp) < 0 417 && !existence_error (errno)) 418 error (0, errno, "cannot remove %s", temp); 419 free (temp); 420 421 /* Checkout the files that need it in CVSROOT dir */ 422 for (fileptr = filelist; fileptr && fileptr->filename; fileptr++) { 423 if (fileptr->errormsg == NULL) 424 continue; 425 temp = make_tempfile (); 426 if (checkout_file (fileptr->filename, temp) == 0) 427 rename_rcsfile (temp, fileptr->filename); 428 #if 0 429 /* 430 * If there was some problem other than the file not existing, 431 * checkout_file already printed a real error message. If the 432 * file does not exist, it is harmless--it probably just means 433 * that the repository was created with an old version of CVS 434 * which didn't have so many files in CVSROOT. 435 */ 436 else if (fileptr->errormsg) 437 error (0, 0, fileptr->errormsg, fileptr->filename); 438 #endif 439 if (unlink_file (temp) < 0 440 && !existence_error (errno)) 441 error (0, errno, "cannot remove %s", temp); 442 free (temp); 443 } 444 445 fp = CVS_FOPEN (CVSROOTADM_CHECKOUTLIST, "r"); 446 if (fp) 447 { 448 /* 449 * File format: 450 * [<whitespace>]<filename><whitespace><error message><end-of-line> 451 * 452 * comment lines begin with '#' 453 */ 454 while (getline (&line, &line_allocated, fp) >= 0) 455 { 456 /* skip lines starting with # */ 457 if (line[0] == '#') 458 continue; 459 460 if ((last = strrchr (line, '\n')) != NULL) 461 *last = '\0'; /* strip the newline */ 462 463 /* Skip leading white space. */ 464 for (fname = line; 465 *fname && isspace ((unsigned char) *fname); 466 fname++) 467 ; 468 469 /* Find end of filename. */ 470 for (cp = fname; *cp && !isspace ((unsigned char) *cp); cp++) 471 ; 472 *cp = '\0'; 473 474 temp = make_tempfile (); 475 if (checkout_file (fname, temp) == 0) 476 { 477 rename_rcsfile (temp, fname); 478 } 479 else 480 { 481 for (cp++; 482 cp < last && *last && isspace ((unsigned char) *last); 483 cp++) 484 ; 485 if (cp < last && *cp) 486 error (0, 0, cp, fname); 487 } 488 if (unlink_file (temp) < 0 489 && !existence_error (errno)) 490 error (0, errno, "cannot remove %s", temp); 491 free (temp); 492 } 493 if (line) 494 free (line); 495 if (ferror (fp)) 496 error (0, errno, "cannot read %s", CVSROOTADM_CHECKOUTLIST); 497 if (fclose (fp) < 0) 498 error (0, errno, "cannot close %s", CVSROOTADM_CHECKOUTLIST); 499 } 500 else 501 { 502 /* Error from CVS_FOPEN. */ 503 if (!existence_error (errno)) 504 error (0, errno, "cannot open %s", CVSROOTADM_CHECKOUTLIST); 505 } 506 507 if (restore_cwd (&cwd, NULL)) 508 error_exit (); 509 free_cwd (&cwd); 510 511 return (0); 512 } 513 514 /* 515 * Yeah, I know, there are NFS race conditions here. 516 */ 517 static char * 518 make_tempfile () 519 { 520 static int seed = 0; 521 int fd; 522 char *temp; 523 524 if (seed == 0) 525 seed = getpid (); 526 temp = xmalloc (sizeof (BAKPREFIX) + 40); 527 while (1) 528 { 529 (void) sprintf (temp, "%s%d", BAKPREFIX, seed++); 530 if ((fd = CVS_OPEN (temp, O_CREAT|O_EXCL|O_RDWR, 0666)) != -1) 531 break; 532 if (errno != EEXIST) 533 error (1, errno, "cannot create temporary file %s", temp); 534 } 535 if (close(fd) < 0) 536 error(1, errno, "cannot close temporary file %s", temp); 537 return temp; 538 } 539 540 /* Get a file. If the file does not exist, return 1 silently. If 541 there is an error, print a message and return 1 (FIXME: probably 542 not a very clean convention). On success, return 0. */ 543 544 static int 545 checkout_file (file, temp) 546 char *file; 547 char *temp; 548 { 549 char *rcs; 550 RCSNode *rcsnode; 551 int retcode = 0; 552 553 if (noexec) 554 return 0; 555 556 rcs = xmalloc (strlen (file) + 5); 557 strcpy (rcs, file); 558 strcat (rcs, RCSEXT); 559 if (!isfile (rcs)) 560 { 561 free (rcs); 562 return (1); 563 } 564 rcsnode = RCS_parsercsfile (rcs); 565 retcode = RCS_checkout (rcsnode, NULL, NULL, NULL, NULL, temp, 566 (RCSCHECKOUTPROC) NULL, (void *) NULL); 567 if (retcode != 0) 568 { 569 /* Probably not necessary (?); RCS_checkout already printed a 570 message. */ 571 error (0, 0, "failed to check out %s file", 572 file); 573 } 574 freercsnode (&rcsnode); 575 free (rcs); 576 return (retcode); 577 } 578 579 #ifndef MY_NDBM 580 581 static void 582 write_dbmfile (temp) 583 char *temp; 584 { 585 char line[DBLKSIZ], value[DBLKSIZ]; 586 FILE *fp; 587 DBM *db; 588 char *cp, *vp; 589 datum key, val; 590 int len, cont, err = 0; 591 592 fp = open_file (temp, "r"); 593 if ((db = dbm_open (temp, O_RDWR | O_CREAT | O_TRUNC, 0666)) == NULL) 594 error (1, errno, "cannot open dbm file %s for creation", temp); 595 for (cont = 0; fgets (line, sizeof (line), fp) != NULL;) 596 { 597 if ((cp = strrchr (line, '\n')) != NULL) 598 *cp = '\0'; /* strip the newline */ 599 600 /* 601 * Add the line to the value, at the end if this is a continuation 602 * line; otherwise at the beginning, but only after any trailing 603 * backslash is removed. 604 */ 605 vp = value; 606 if (cont) 607 vp += strlen (value); 608 609 /* 610 * See if the line we read is a continuation line, and strip the 611 * backslash if so. 612 */ 613 len = strlen (line); 614 if (len > 0) 615 cp = &line[len - 1]; 616 else 617 cp = line; 618 if (*cp == '\\') 619 { 620 cont = 1; 621 *cp = '\0'; 622 } 623 else 624 { 625 cont = 0; 626 } 627 (void) strcpy (vp, line); 628 if (value[0] == '#') 629 continue; /* comment line */ 630 vp = value; 631 while (*vp && isspace ((unsigned char) *vp)) 632 vp++; 633 if (*vp == '\0') 634 continue; /* empty line */ 635 636 /* 637 * If this was not a continuation line, add the entry to the database 638 */ 639 if (!cont) 640 { 641 key.dptr = vp; 642 while (*vp && !isspace ((unsigned char) *vp)) 643 vp++; 644 key.dsize = vp - key.dptr; 645 *vp++ = '\0'; /* NULL terminate the key */ 646 while (*vp && isspace ((unsigned char) *vp)) 647 vp++; /* skip whitespace to value */ 648 if (*vp == '\0') 649 { 650 error (0, 0, "warning: NULL value for key `%s'", key.dptr); 651 continue; 652 } 653 val.dptr = vp; 654 val.dsize = strlen (vp); 655 if (dbm_store (db, key, val, DBM_INSERT) == 1) 656 { 657 error (0, 0, "duplicate key found for `%s'", key.dptr); 658 err++; 659 } 660 } 661 } 662 dbm_close (db); 663 if (fclose (fp) < 0) 664 error (0, errno, "cannot close %s", temp); 665 if (err) 666 { 667 /* I think that the size of the buffer needed here is 668 just determined by sizeof (CVSROOTADM_MODULES), the 669 filenames created by make_tempfile, and other things that won't 670 overflow. */ 671 char dotdir[50], dotpag[50], dotdb[50]; 672 673 (void) sprintf (dotdir, "%s.dir", temp); 674 (void) sprintf (dotpag, "%s.pag", temp); 675 (void) sprintf (dotdb, "%s.db", temp); 676 if (unlink_file (dotdir) < 0 677 && !existence_error (errno)) 678 error (0, errno, "cannot remove %s", dotdir); 679 if (unlink_file (dotpag) < 0 680 && !existence_error (errno)) 681 error (0, errno, "cannot remove %s", dotpag); 682 if (unlink_file (dotdb) < 0 683 && !existence_error (errno)) 684 error (0, errno, "cannot remove %s", dotdb); 685 error (1, 0, "DBM creation failed; correct above errors"); 686 } 687 } 688 689 static void 690 rename_dbmfile (temp) 691 char *temp; 692 { 693 /* I think that the size of the buffer needed here is 694 just determined by sizeof (CVSROOTADM_MODULES), the 695 filenames created by make_tempfile, and other things that won't 696 overflow. */ 697 char newdir[50], newpag[50], newdb[50]; 698 char dotdir[50], dotpag[50], dotdb[50]; 699 char bakdir[50], bakpag[50], bakdb[50]; 700 701 int dir1_errno = 0, pag1_errno = 0, db1_errno = 0; 702 int dir2_errno = 0, pag2_errno = 0, db2_errno = 0; 703 int dir3_errno = 0, pag3_errno = 0, db3_errno = 0; 704 705 (void) sprintf (dotdir, "%s.dir", CVSROOTADM_MODULES); 706 (void) sprintf (dotpag, "%s.pag", CVSROOTADM_MODULES); 707 (void) sprintf (dotdb, "%s.db", CVSROOTADM_MODULES); 708 (void) sprintf (bakdir, "%s%s.dir", BAKPREFIX, CVSROOTADM_MODULES); 709 (void) sprintf (bakpag, "%s%s.pag", BAKPREFIX, CVSROOTADM_MODULES); 710 (void) sprintf (bakdb, "%s%s.db", BAKPREFIX, CVSROOTADM_MODULES); 711 (void) sprintf (newdir, "%s.dir", temp); 712 (void) sprintf (newpag, "%s.pag", temp); 713 (void) sprintf (newdb, "%s.db", temp); 714 715 (void) chmod (newdir, 0666); 716 (void) chmod (newpag, 0666); 717 (void) chmod (newdb, 0666); 718 719 /* don't mess with me */ 720 SIG_beginCrSect (); 721 722 /* rm .#modules.dir .#modules.pag */ 723 if (unlink_file (bakdir) < 0) 724 dir1_errno = errno; 725 if (unlink_file (bakpag) < 0) 726 pag1_errno = errno; 727 if (unlink_file (bakdb) < 0) 728 db1_errno = errno; 729 730 /* mv modules.dir .#modules.dir */ 731 if (CVS_RENAME (dotdir, bakdir) < 0) 732 dir2_errno = errno; 733 /* mv modules.pag .#modules.pag */ 734 if (CVS_RENAME (dotpag, bakpag) < 0) 735 pag2_errno = errno; 736 /* mv modules.db .#modules.db */ 737 if (CVS_RENAME (dotdb, bakdb) < 0) 738 db2_errno = errno; 739 740 /* mv "temp".dir modules.dir */ 741 if (CVS_RENAME (newdir, dotdir) < 0) 742 dir3_errno = errno; 743 /* mv "temp".pag modules.pag */ 744 if (CVS_RENAME (newpag, dotpag) < 0) 745 pag3_errno = errno; 746 /* mv "temp".db modules.db */ 747 if (CVS_RENAME (newdb, dotdb) < 0) 748 db3_errno = errno; 749 750 /* OK -- make my day */ 751 SIG_endCrSect (); 752 753 /* I didn't want to call error() when we had signals blocked 754 (unnecessary?), but do it now. */ 755 if (dir1_errno && !existence_error (dir1_errno)) 756 error (0, dir1_errno, "cannot remove %s", bakdir); 757 if (pag1_errno && !existence_error (pag1_errno)) 758 error (0, pag1_errno, "cannot remove %s", bakpag); 759 if (db1_errno && !existence_error (db1_errno)) 760 error (0, db1_errno, "cannot remove %s", bakdb); 761 762 if (dir2_errno && !existence_error (dir2_errno)) 763 error (0, dir2_errno, "cannot remove %s", bakdir); 764 if (pag2_errno && !existence_error (pag2_errno)) 765 error (0, pag2_errno, "cannot remove %s", bakpag); 766 if (db2_errno && !existence_error (db2_errno)) 767 error (0, db2_errno, "cannot remove %s", bakdb); 768 769 if (dir3_errno && !existence_error (dir3_errno)) 770 error (0, dir3_errno, "cannot remove %s", bakdir); 771 if (pag3_errno && !existence_error (pag3_errno)) 772 error (0, pag3_errno, "cannot remove %s", bakpag); 773 if (db3_errno && !existence_error (db3_errno)) 774 error (0, db3_errno, "cannot remove %s", bakdb); 775 } 776 777 #endif /* !MY_NDBM */ 778 779 static void 780 rename_rcsfile (temp, real) 781 char *temp; 782 char *real; 783 { 784 char *bak; 785 struct stat statbuf; 786 char *rcs; 787 788 /* Set "x" bits if set in original. */ 789 rcs = xmalloc (strlen (real) + sizeof (RCSEXT) + 10); 790 (void) sprintf (rcs, "%s%s", real, RCSEXT); 791 statbuf.st_mode = 0; /* in case rcs file doesn't exist, but it should... */ 792 if (CVS_STAT (rcs, &statbuf) < 0 793 && !existence_error (errno)) 794 error (0, errno, "cannot stat %s", rcs); 795 free (rcs); 796 797 if (chmod (temp, 0444 | (statbuf.st_mode & 0111)) < 0) 798 error (0, errno, "warning: cannot chmod %s", temp); 799 bak = xmalloc (strlen (real) + sizeof (BAKPREFIX) + 10); 800 (void) sprintf (bak, "%s%s", BAKPREFIX, real); 801 802 /* rm .#loginfo */ 803 if (unlink_file (bak) < 0 804 && !existence_error (errno)) 805 error (0, errno, "cannot remove %s", bak); 806 807 /* mv loginfo .#loginfo */ 808 if (CVS_RENAME (real, bak) < 0 809 && !existence_error (errno)) 810 error (0, errno, "cannot rename %s to %s", real, bak); 811 812 /* mv "temp" loginfo */ 813 if (CVS_RENAME (temp, real) < 0 814 && !existence_error (errno)) 815 error (0, errno, "cannot rename %s to %s", temp, real); 816 817 free (bak); 818 } 819 820 const char *const init_usage[] = { 821 "Usage: %s %s\n", 822 "(Specify the --help global option for a list of other help options)\n", 823 NULL 824 }; 825 826 int 827 init (argc, argv) 828 int argc; 829 char **argv; 830 { 831 /* Name of CVSROOT directory. */ 832 char *adm; 833 /* Name of this administrative file. */ 834 char *info; 835 /* Name of ,v file for this administrative file. */ 836 char *info_v; 837 /* Exit status. */ 838 int err; 839 840 const struct admin_file *fileptr; 841 842 umask (cvsumask); 843 844 if (argc == -1 || argc > 1) 845 usage (init_usage); 846 847 #ifdef CLIENT_SUPPORT 848 if (client_active) 849 { 850 start_server (); 851 852 ign_setup (); 853 send_init_command (); 854 return get_responses_and_close (); 855 } 856 #endif /* CLIENT_SUPPORT */ 857 858 /* Note: we do *not* create parent directories as needed like the 859 old cvsinit.sh script did. Few utilities do that, and a 860 non-existent parent directory is as likely to be a typo as something 861 which needs to be created. */ 862 mkdir_if_needed (CVSroot_directory); 863 864 adm = xmalloc (strlen (CVSroot_directory) + sizeof (CVSROOTADM) + 10); 865 strcpy (adm, CVSroot_directory); 866 strcat (adm, "/"); 867 strcat (adm, CVSROOTADM); 868 mkdir_if_needed (adm); 869 870 /* This is needed because we pass "fileptr->filename" not "info" 871 to add_rcs_file below. I think this would be easy to change, 872 thus nuking the need for CVS_CHDIR here, but I haven't looked 873 closely (e.g. see wrappers calls within add_rcs_file). */ 874 if ( CVS_CHDIR (adm) < 0) 875 error (1, errno, "cannot change to directory %s", adm); 876 877 /* 80 is long enough for all the administrative file names, plus 878 "/" and so on. */ 879 info = xmalloc (strlen (adm) + 80); 880 info_v = xmalloc (strlen (adm) + 80); 881 for (fileptr = filelist; fileptr && fileptr->filename; ++fileptr) 882 { 883 if (fileptr->contents == NULL) 884 continue; 885 strcpy (info, adm); 886 strcat (info, "/"); 887 strcat (info, fileptr->filename); 888 strcpy (info_v, info); 889 strcat (info_v, RCSEXT); 890 if (isfile (info_v)) 891 /* We will check out this file in the mkmodules step. 892 Nothing else is required. */ 893 ; 894 else 895 { 896 int retcode; 897 898 if (!isfile (info)) 899 { 900 FILE *fp; 901 const char * const *p; 902 903 fp = open_file (info, "w"); 904 for (p = fileptr->contents; *p != NULL; ++p) 905 if (fputs (*p, fp) < 0) 906 error (1, errno, "cannot write %s", info); 907 if (fclose (fp) < 0) 908 error (1, errno, "cannot close %s", info); 909 } 910 /* The message used to say " of " and fileptr->filename after 911 "initial checkin" but I fail to see the point as we know what 912 file it is from the name. */ 913 retcode = add_rcs_file ("initial checkin", info_v, 914 fileptr->filename, "1.1", NULL, 915 916 /* No vendor branch. */ 917 NULL, NULL, 0, NULL, 918 919 NULL, 0, NULL); 920 if (retcode != 0) 921 /* add_rcs_file already printed an error message. */ 922 err = 1; 923 } 924 } 925 926 /* Turn on history logging by default. The user can remove the file 927 to disable it. */ 928 strcpy (info, adm); 929 strcat (info, "/"); 930 strcat (info, CVSROOTADM_HISTORY); 931 if (!isfile (info)) 932 { 933 FILE *fp; 934 935 fp = open_file (info, "w"); 936 if (fclose (fp) < 0) 937 error (1, errno, "cannot close %s", info); 938 } 939 940 free (info); 941 free (info_v); 942 943 mkmodules (adm); 944 945 free (adm); 946 return 0; 947 } 948