mkmodules.c revision 26804
11573Srgrimes/* 21573Srgrimes * Copyright (c) 1992, Brian Berliner and Jeff Polk 31573Srgrimes * Copyright (c) 1989-1992, Brian Berliner 41573Srgrimes * 51573Srgrimes * You may distribute under the terms of the GNU General Public License as 61573Srgrimes * specified in the README file that comes with the CVS kit. */ 71573Srgrimes 81573Srgrimes#include "cvs.h" 91573Srgrimes#include "savecwd.h" 101573Srgrimes#include "getline.h" 111573Srgrimes 121573Srgrimes#ifndef DBLKSIZ 131573Srgrimes#define DBLKSIZ 4096 /* since GNU ndbm doesn't define it */ 141573Srgrimes#endif 151573Srgrimes 16251672Semastestatic int checkout_file PROTO((char *file, char *temp)); 171573Srgrimesstatic char *make_tempfile PROTO((void)); 181573Srgrimesstatic void rename_rcsfile PROTO((char *temp, char *real)); 191573Srgrimes 201573Srgrimes#ifndef MY_NDBM 211573Srgrimesstatic void rename_dbmfile PROTO((char *temp)); 221573Srgrimesstatic void write_dbmfile PROTO((char *temp)); 231573Srgrimes#endif /* !MY_NDBM */ 241573Srgrimes 251573Srgrimes/* Structure which describes an administrative file. */ 261573Srgrimesstruct admin_file { 271573Srgrimes /* Name of the file, within the CVSROOT directory. */ 281573Srgrimes char *filename; 291573Srgrimes 301573Srgrimes /* This is a one line description of what the file is for. It is not 311573Srgrimes currently used, although one wonders whether it should be, somehow. 321573Srgrimes If NULL, then don't process this file in mkmodules (FIXME?: a bit of 3350476Speter a kludge; probably should replace this with a flags field). */ 341573Srgrimes char *errormsg; 3587738Sru 361573Srgrimes /* Contents which the file should have in a new repository. To avoid 371573Srgrimes problems with brain-dead compilers which choke on long string constants, 381573Srgrimes this is a pointer to an array of char * terminated by NULL--each of 3987027Sfenner the strings is concatenated. 4087738Sru 4187738Sru If this field is NULL, the file is not created in a new 4287738Sru repository, but it can be added with "cvs add" (just as if one 4387738Sru had created the repository with a version of CVS which didn't 4487738Sru know about the file) and the checked-out copy will be updated 4559460Sphantom without having to add it to checkoutlist. */ 4659460Sphantom const char * const *contents; 471573Srgrimes}; 4883206Sasmodai 4983206Sasmodaistatic const char *const loginfo_contents[] = { 501573Srgrimes "# The \"loginfo\" file controls where \"cvs commit\" log information\n", 51103012Stjr "# is sent. The first entry on a line is a regular expression which must match\n", 5273152Sobrien "# the directory that the change is being made to, relative to the\n", 53103012Stjr "# $CVSROOT. If a match is found, then the remainder of the line is a filter\n", 5487027Sfenner "# program that should expect log information on its standard input.\n", 5587027Sfenner "#\n", 56103012Stjr "# If the repository name does not match any of the regular expressions in this\n", 5783206Sasmodai "# file, the \"DEFAULT\" line is used, if it is specified.\n", 5883206Sasmodai "#\n", 5983206Sasmodai "# If the name ALL appears as a regular expression it is always used\n", 601573Srgrimes "# in addition to the first matching regex or DEFAULT.\n", 6124880Sbde "#\n", 621573Srgrimes "# You may specify a format string as part of the\n", 631573Srgrimes "# filter. The string is composed of a `%' followed\n", 641573Srgrimes "# by a single format character, or followed by a set of format\n", 651573Srgrimes "# characters surrounded by `{' and `}' as separators. The format\n", 661573Srgrimes "# characters are:\n", 671573Srgrimes "#\n", 681573Srgrimes "# s = file name\n", 6987738Sru "# V = old version number (pre-checkin)\n", 701573Srgrimes "# v = new version number (post-checkin)\n", 711573Srgrimes "#\n", 7273152Sobrien "# For example:\n", 7373152Sobrien "#DEFAULT (echo \"\"; id; echo %s; date; cat) >> $CVSROOT/CVSROOT/commitlog\n", 7473152Sobrien "# or\n", 7573152Sobrien "#DEFAULT (echo \"\"; id; echo %{sVv}; date; cat) >> $CVSROOT/CVSROOT/commitlog\n", 7673152Sobrien NULL 7787738Sru}; 7873152Sobrien 7973152Sobrienstatic const char *const rcsinfo_contents[] = { 8087027Sfenner "# The \"rcsinfo\" file is used to control templates with which the editor\n", 8187027Sfenner "# is invoked on commit and import.\n", 8287027Sfenner "#\n", 8387027Sfenner "# The first entry on a line is a regular expression which is tested\n", 8487027Sfenner "# against the directory that the change is being made to, relative to the\n", 8587738Sru "# $CVSROOT. For the first match that is found, then the remainder of the\n", 8687027Sfenner "# line is the name of the file that contains the template.\n", 8787027Sfenner "#\n", 881573Srgrimes "# If the repository name does not match any of the regular expressions in this\n", 891573Srgrimes "# file, the \"DEFAULT\" line is used, if it is specified.\n", 901573Srgrimes "#\n", 911573Srgrimes "# If the name \"ALL\" appears as a regular expression it is always used\n", 921573Srgrimes "# in addition to the first matching regex or \"DEFAULT\".\n", 9387738Sru NULL 941573Srgrimes}; 951573Srgrimes 961573Srgrimesstatic const char *const editinfo_contents[] = { 971573Srgrimes "# The \"editinfo\" file is used to allow verification of logging\n", 981573Srgrimes "# information. It works best when a template (as specified in the\n", 991573Srgrimes "# rcsinfo file) is provided for the logging procedure. Given a\n", 1001573Srgrimes "# template with locations for, a bug-id number, a list of people who\n", 1011573Srgrimes "# reviewed the code before it can be checked in, and an external\n", 1021573Srgrimes "# process to catalog the differences that were code reviewed, the\n", 1031573Srgrimes "# following test can be applied to the code:\n", 1041573Srgrimes "#\n", 1051573Srgrimes "# Making sure that the entered bug-id number is correct.\n", 1061573Srgrimes "# Validating that the code that was reviewed is indeed the code being\n", 1071573Srgrimes "# checked in (using the bug-id number or a seperate review\n", 1081573Srgrimes "# number to identify this particular code set.).\n", 1091573Srgrimes "#\n", 1101573Srgrimes "# If any of the above test failed, then the commit would be aborted.\n", 1111573Srgrimes "#\n", 11287738Sru "# Actions such as mailing a copy of the report to each reviewer are\n", 1131573Srgrimes "# better handled by an entry in the loginfo file.\n", 1141573Srgrimes "#\n", 1151573Srgrimes "# One thing that should be noted is the the ALL keyword is not\n", 1161573Srgrimes "# supported. There can be only one entry that matches a given\n", 1171573Srgrimes "# repository.\n", 1181573Srgrimes NULL 1191573Srgrimes}; 1201573Srgrimes 12187738Srustatic const char *const verifymsg_contents[] = { 12287027Sfenner "# The \"verifymsg\" file is used to allow verification of logging\n", 12387738Sru "# information. It works best when a template (as specified in the\n", 1241573Srgrimes "# rcsinfo file) is provided for the logging procedure. Given a\n", 1251573Srgrimes "# template with locations for, a bug-id number, a list of people who\n", 1261573Srgrimes "# reviewed the code before it can be checked in, and an external\n", 1271573Srgrimes "# process to catalog the differences that were code reviewed, the\n", 1281573Srgrimes "# following test can be applied to the code:\n", 1291573Srgrimes "#\n", 1301573Srgrimes "# Making sure that the entered bug-id number is correct.\n", 1311573Srgrimes "# Validating that the code that was reviewed is indeed the code being\n", 1321573Srgrimes "# checked in (using the bug-id number or a seperate review\n", 1331573Srgrimes "# number to identify this particular code set.).\n", 1341573Srgrimes "#\n", 1351573Srgrimes "# If any of the above test failed, then the commit would be aborted.\n", 1361573Srgrimes "#\n", 1371573Srgrimes "# Actions such as mailing a copy of the report to each reviewer are\n", 13887738Sru "# better handled by an entry in the loginfo file.\n", 13987738Sru "#\n", 1401573Srgrimes "# One thing that should be noted is the the ALL keyword is not\n", 1411573Srgrimes "# supported. There can be only one entry that matches a given\n", 1421573Srgrimes "# repository.\n", 1431573Srgrimes NULL 1441573Srgrimes}; 1451573Srgrimes 1461573Srgrimesstatic const char *const commitinfo_contents[] = { 1471573Srgrimes "# The \"commitinfo\" file is used to control pre-commit checks.\n", 1481573Srgrimes "# The filter on the right is invoked with the repository and a list \n", 1491573Srgrimes "# of files to check. A non-zero exit of the filter program will \n", 1501573Srgrimes "# cause the commit to be aborted.\n", 1511573Srgrimes "#\n", 1521573Srgrimes "# The first entry on a line is a regular expression which is tested\n", 1531573Srgrimes "# against the directory that the change is being committed to, relative\n", 1541573Srgrimes "# to the $CVSROOT. For the first match that is found, then the remainder\n", 1551573Srgrimes "# of the line is the name of the filter to run.\n", 1561573Srgrimes "#\n", 1571573Srgrimes "# If the repository name does not match any of the regular expressions in this\n", 1581573Srgrimes "# file, the \"DEFAULT\" line is used, if it is specified.\n", 1591573Srgrimes "#\n", 16087027Sfenner "# If the name \"ALL\" appears as a regular expression it is always used\n", 16187027Sfenner "# in addition to the first matching regex or \"DEFAULT\".\n", 16287027Sfenner NULL 16387738Sru}; 16487027Sfenner 16587738Srustatic const char *const taginfo_contents[] = { 16687738Sru "# The \"taginfo\" file is used to control pre-tag checks.\n", 1671573Srgrimes "# The filter on the right is invoked with the following arguments:\n", 16883328Sru "#\n", 16983328Sru "# $1 -- tagname\n", 17082975Sache "# $2 -- operation \"add\" for tag, \"mov\" for tag -F, and \"del\" for tag -d\n", 17183328Sru "# $3 -- repository\n", 172140613Sache "# $4-> file revision [file revision ...]\n", 173140613Sache "#\n", 17487027Sfenner "# A non-zero exit of the filter program will cause the tag to be aborted.\n", 1751573Srgrimes "#\n", 1761573Srgrimes "# The first entry on a line is a regular expression which is tested\n", 17787027Sfenner "# against the directory that the change is being committed to, relative\n", 17887027Sfenner "# to the $CVSROOT. For the first match that is found, then the remainder\n", 17987027Sfenner "# of the line is the name of the filter to run.\n", 180168578Sru "#\n", 181168578Sru "# If the repository name does not match any of the regular expressions in this\n", 18287027Sfenner "# file, the \"DEFAULT\" line is used, if it is specified.\n", 18387027Sfenner "#\n", 18487027Sfenner "# If the name \"ALL\" appears as a regular expression it is always used\n", 18587027Sfenner "# in addition to the first matching regex or \"DEFAULT\".\n", 18687027Sfenner NULL 1871573Srgrimes}; 18860075Sphantom 18982975Sachestatic const char *const checkoutlist_contents[] = { 19083328Sru "# The \"checkoutlist\" file is used to support additional version controlled\n", 19183328Sru "# administrative files in $CVSROOT/CVSROOT, such as template files.\n", 19283328Sru "#\n", 193140613Sache "# The first entry on a line is a filename which will be checked out from\n", 194140613Sache "# the corresponding RCS file in the $CVSROOT/CVSROOT directory.\n", 1951573Srgrimes "# The remainder of the line is an error message to use if the file cannot\n", 1961573Srgrimes "# be checked out.\n", 1971573Srgrimes "#\n", 1981573Srgrimes "# File format:\n", 1991573Srgrimes "#\n", 2001573Srgrimes "# [<whitespace>]<filename><whitespace><error message><end-of-line>\n", 2011573Srgrimes "#\n", 2021573Srgrimes "# comment lines begin with '#'\n", 203158774Smaxim NULL 204104751Stjr}; 205104751Stjr 2061573Srgrimesstatic const char *const cvswrappers_contents[] = { 2071573Srgrimes "# This file describes wrappers and other binary files to CVS.\n", 2081573Srgrimes "#\n", 2091573Srgrimes "# Wrappers are the concept where directories of files are to be\n", 2101573Srgrimes "# treated as a single file. The intended use is to wrap up a wrapper\n", 21173088Sru "# into a single tar such that the tar archive can be treated as a\n", 21273152Sobrien "# single binary file in CVS.\n", 21373152Sobrien "#\n", 21487027Sfenner "# To solve the problem effectively, it was also necessary to be able to\n", 21587027Sfenner "# prevent rcsmerge from merging these files.\n", 21687027Sfenner "#\n", 21787027Sfenner "# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)\n", 21873152Sobrien "#\n", 21973234Sobrien "# wildcard [option value][option value]...\n", 22073234Sobrien "#\n", 22173152Sobrien "# where option is one of\n", 22273152Sobrien "# -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 "#\n", 226 "# and value is a single-quote delimited value.\n", 227 "#\n", 228 "# For example:\n", 229 NULL 230}; 231 232static const char *const notify_contents[] = { 233 "# The \"notify\" file controls where notifications from watches set by\n", 234 "# \"cvs watch add\" or \"cvs edit\" are sent. The first entry on a line is\n", 235 "# a regular expression which is tested against the directory that the\n", 236 "# change is being made to, relative to the $CVSROOT. If it matches,\n", 237 "# then the remainder of the line is a filter program that should contain\n", 238 "# one occurrence of %s for the user to notify, and information on its\n", 239 "# standard input.\n", 240 "#\n", 241 "# \"ALL\" or \"DEFAULT\" can be used in place of the regular expression.\n", 242 "#\n", 243 "# For example:\n", 244 "#ALL mail %s -s \"CVS notification\"\n", 245 NULL 246}; 247 248static const char *const modules_contents[] = { 249 "# Three different line formats are valid:\n", 250 "# key -a aliases...\n", 251 "# key [options] directory\n", 252 "# key [options] directory files...\n", 253 "#\n", 254 "# Where \"options\" are composed of:\n", 255 "# -i prog Run \"prog\" on \"cvs commit\" from top-level of module.\n", 256 "# -o prog Run \"prog\" on \"cvs checkout\" of module.\n", 257 "# -e prog Run \"prog\" on \"cvs export\" of module.\n", 258 "# -t prog Run \"prog\" on \"cvs rtag\" of module.\n", 259 "# -u prog Run \"prog\" on \"cvs update\" of module.\n", 260 "# -d dir Place module in directory \"dir\" instead of module name.\n", 261 "# -l Top-level directory only -- do not recurse.\n", 262 "#\n", 263 "# NOTE: If you change any of the \"Run\" options above, you'll have to\n", 264 "# release and re-checkout any working directories of these modules.\n", 265 "#\n", 266 "# And \"directory\" is a path to a directory relative to $CVSROOT.\n", 267 "#\n", 268 "# The \"-a\" option specifies an alias. An alias is interpreted as if\n", 269 "# everything on the right of the \"-a\" had been typed on the command line.\n", 270 "#\n", 271 "# You can encode a module within a module by using the special '&'\n", 272 "# character to interpose another module into the current module. This\n", 273 "# can be useful for creating a module that consists of many directories\n", 274 "# spread out over the entire source repository.\n", 275 NULL 276}; 277 278static const struct admin_file filelist[] = { 279 {CVSROOTADM_LOGINFO, 280 "no logging of 'cvs commit' messages is done without a %s file", 281 &loginfo_contents[0]}, 282 {CVSROOTADM_RCSINFO, 283 "a %s file can be used to configure 'cvs commit' templates", 284 rcsinfo_contents}, 285 {CVSROOTADM_EDITINFO, 286 "a %s file can be used to validate log messages", 287 editinfo_contents}, 288 {CVSROOTADM_VERIFYMSG, 289 "a %s file can be used to validate log messages", 290 verifymsg_contents}, 291 {CVSROOTADM_COMMITINFO, 292 "a %s file can be used to configure 'cvs commit' checking", 293 commitinfo_contents}, 294 {CVSROOTADM_TAGINFO, 295 "a %s file can be used to configure 'cvs tag' checking", 296 taginfo_contents}, 297 {CVSROOTADM_IGNORE, 298 "a %s file can be used to specify files to ignore", 299 NULL}, 300 {CVSROOTADM_CHECKOUTLIST, 301 "a %s file can specify extra CVSROOT files to auto-checkout", 302 checkoutlist_contents}, 303 {CVSROOTADM_WRAPPER, 304 "a %s file can be used to specify files to treat as wrappers", 305 cvswrappers_contents}, 306 {CVSROOTADM_NOTIFY, 307 "a %s file can be used to specify where notifications go", 308 notify_contents}, 309 {CVSROOTADM_MODULES, 310 /* modules is special-cased in mkmodules. */ 311 NULL, 312 modules_contents}, 313 {CVSROOTADM_READERS, 314 "a %s file specifies read-only users", 315 NULL}, 316 {CVSROOTADM_WRITERS, 317 "a %s file specifies read/write users", 318 NULL}, 319 /* Some have suggested listing CVSROOTADM_PASSWD here too. The 320 security implications of transmitting hashed passwords over the 321 net are no worse than transmitting cleartext passwords which pserver 322 does, so this isn't a problem. But I'm worried about the implications 323 of storing old passwords--if someone used a password in the past 324 they might be using it elsewhere, using a similar password, etc, 325 and so it doesn't seem to me like we should be saving old passwords, 326 even hashed. */ 327 {NULL, NULL} 328}; 329 330/* Rebuild the checked out administrative files in directory DIR. */ 331int 332mkmodules (dir) 333 char *dir; 334{ 335 struct saved_cwd cwd; 336 char *temp; 337 char *cp, *last, *fname; 338#ifdef MY_NDBM 339 DBM *db; 340#endif 341 FILE *fp; 342 char *line = NULL; 343 size_t line_allocated = 0; 344 const struct admin_file *fileptr; 345 346 if (save_cwd (&cwd)) 347 error_exit (); 348 349 if ( CVS_CHDIR (dir) < 0) 350 error (1, errno, "cannot chdir to %s", dir); 351 352 /* 353 * First, do the work necessary to update the "modules" database. 354 */ 355 temp = make_tempfile (); 356 switch (checkout_file (CVSROOTADM_MODULES, temp)) 357 { 358 359 case 0: /* everything ok */ 360#ifdef MY_NDBM 361 /* open it, to generate any duplicate errors */ 362 if ((db = dbm_open (temp, O_RDONLY, 0666)) != NULL) 363 dbm_close (db); 364#else 365 write_dbmfile (temp); 366 rename_dbmfile (temp); 367#endif 368 rename_rcsfile (temp, CVSROOTADM_MODULES); 369 break; 370 371 case -1: /* fork failed */ 372 (void) unlink_file (temp); 373 error (1, errno, "cannot check out %s", CVSROOTADM_MODULES); 374 /* NOTREACHED */ 375 376 default: 377 error (0, 0, 378 "'cvs checkout' is less functional without a %s file", 379 CVSROOTADM_MODULES); 380 break; 381 } /* switch on checkout_file() */ 382 383 (void) unlink_file (temp); 384 free (temp); 385 386 /* Checkout the files that need it in CVSROOT dir */ 387 for (fileptr = filelist; fileptr && fileptr->filename; fileptr++) { 388 if (fileptr->errormsg == NULL) 389 continue; 390 temp = make_tempfile (); 391 if (checkout_file (fileptr->filename, temp) == 0) 392 rename_rcsfile (temp, fileptr->filename); 393#if 0 394 /* 395 * If there was some problem other than the file not existing, 396 * checkout_file already printed a real error message. If the 397 * file does not exist, it is harmless--it probably just means 398 * that the repository was created with an old version of CVS 399 * which didn't have so many files in CVSROOT. 400 */ 401 else if (fileptr->errormsg) 402 error (0, 0, fileptr->errormsg, fileptr->filename); 403#endif 404 (void) unlink_file (temp); 405 free (temp); 406 } 407 408 fp = CVS_FOPEN (CVSROOTADM_CHECKOUTLIST, "r"); 409 if (fp) 410 { 411 /* 412 * File format: 413 * [<whitespace>]<filename><whitespace><error message><end-of-line> 414 * 415 * comment lines begin with '#' 416 */ 417 while (getline (&line, &line_allocated, fp) >= 0) 418 { 419 /* skip lines starting with # */ 420 if (line[0] == '#') 421 continue; 422 423 if ((last = strrchr (line, '\n')) != NULL) 424 *last = '\0'; /* strip the newline */ 425 426 /* Skip leading white space. */ 427 for (fname = line; *fname && isspace(*fname); fname++) 428 ; 429 430 /* Find end of filename. */ 431 for (cp = fname; *cp && !isspace(*cp); cp++) 432 ; 433 *cp = '\0'; 434 435 temp = make_tempfile (); 436 if (checkout_file (fname, temp) == 0) 437 { 438 rename_rcsfile (temp, fname); 439 } 440 else 441 { 442 for (cp++; cp < last && *last && isspace(*last); cp++) 443 ; 444 if (cp < last && *cp) 445 error (0, 0, cp, fname); 446 } 447 free (temp); 448 } 449 if (line) 450 free (line); 451 if (ferror (fp)) 452 error (0, errno, "cannot read %s", CVSROOTADM_CHECKOUTLIST); 453 if (fclose (fp) < 0) 454 error (0, errno, "cannot close %s", CVSROOTADM_CHECKOUTLIST); 455 } 456 else 457 { 458 /* Error from CVS_FOPEN. */ 459 if (!existence_error (errno)) 460 error (0, errno, "cannot open %s", CVSROOTADM_CHECKOUTLIST); 461 } 462 463 if (restore_cwd (&cwd, NULL)) 464 error_exit (); 465 free_cwd (&cwd); 466 467 return (0); 468} 469 470/* 471 * Yeah, I know, there are NFS race conditions here. 472 */ 473static char * 474make_tempfile () 475{ 476 static int seed = 0; 477 int fd; 478 char *temp; 479 480 if (seed == 0) 481 seed = getpid (); 482 temp = xmalloc (sizeof (BAKPREFIX) + 40); 483 while (1) 484 { 485 (void) sprintf (temp, "%s%d", BAKPREFIX, seed++); 486 if ((fd = CVS_OPEN (temp, O_CREAT|O_EXCL|O_RDWR, 0666)) != -1) 487 break; 488 if (errno != EEXIST) 489 error (1, errno, "cannot create temporary file %s", temp); 490 } 491 if (close(fd) < 0) 492 error(1, errno, "cannot close temporary file %s", temp); 493 return temp; 494} 495 496static int 497checkout_file (file, temp) 498 char *file; 499 char *temp; 500{ 501 char *rcs; 502 RCSNode *rcsnode; 503 int retcode = 0; 504 505 if (noexec) 506 return 0; 507 508 rcs = xmalloc (strlen (file) + 5); 509 strcpy (rcs, file); 510 strcat (rcs, RCSEXT); 511 if (!isfile (rcs)) 512 { 513 free (rcs); 514 return (1); 515 } 516 rcsnode = RCS_parsercsfile (rcs); 517 retcode = RCS_checkout (rcsnode, NULL, NULL, NULL, NULL, temp, 518 (RCSCHECKOUTPROC) NULL, (void *) NULL); 519 if (retcode != 0) 520 { 521 error (0, retcode == -1 ? errno : 0, "failed to check out %s file", 522 file); 523 } 524 freercsnode (&rcsnode); 525 free (rcs); 526 return (retcode); 527} 528 529#ifndef MY_NDBM 530 531static void 532write_dbmfile (temp) 533 char *temp; 534{ 535 char line[DBLKSIZ], value[DBLKSIZ]; 536 FILE *fp; 537 DBM *db; 538 char *cp, *vp; 539 datum key, val; 540 int len, cont, err = 0; 541 542 fp = open_file (temp, "r"); 543 if ((db = dbm_open (temp, O_RDWR | O_CREAT | O_TRUNC, 0666)) == NULL) 544 error (1, errno, "cannot open dbm file %s for creation", temp); 545 for (cont = 0; fgets (line, sizeof (line), fp) != NULL;) 546 { 547 if ((cp = strrchr (line, '\n')) != NULL) 548 *cp = '\0'; /* strip the newline */ 549 550 /* 551 * Add the line to the value, at the end if this is a continuation 552 * line; otherwise at the beginning, but only after any trailing 553 * backslash is removed. 554 */ 555 vp = value; 556 if (cont) 557 vp += strlen (value); 558 559 /* 560 * See if the line we read is a continuation line, and strip the 561 * backslash if so. 562 */ 563 len = strlen (line); 564 if (len > 0) 565 cp = &line[len - 1]; 566 else 567 cp = line; 568 if (*cp == '\\') 569 { 570 cont = 1; 571 *cp = '\0'; 572 } 573 else 574 { 575 cont = 0; 576 } 577 (void) strcpy (vp, line); 578 if (value[0] == '#') 579 continue; /* comment line */ 580 vp = value; 581 while (*vp && isspace (*vp)) 582 vp++; 583 if (*vp == '\0') 584 continue; /* empty line */ 585 586 /* 587 * If this was not a continuation line, add the entry to the database 588 */ 589 if (!cont) 590 { 591 key.dptr = vp; 592 while (*vp && !isspace (*vp)) 593 vp++; 594 key.dsize = vp - key.dptr; 595 *vp++ = '\0'; /* NULL terminate the key */ 596 while (*vp && isspace (*vp)) 597 vp++; /* skip whitespace to value */ 598 if (*vp == '\0') 599 { 600 error (0, 0, "warning: NULL value for key `%s'", key.dptr); 601 continue; 602 } 603 val.dptr = vp; 604 val.dsize = strlen (vp); 605 if (dbm_store (db, key, val, DBM_INSERT) == 1) 606 { 607 error (0, 0, "duplicate key found for `%s'", key.dptr); 608 err++; 609 } 610 } 611 } 612 dbm_close (db); 613 (void) fclose (fp); 614 if (err) 615 { 616 char dotdir[50], dotpag[50], dotdb[50]; 617 618 (void) sprintf (dotdir, "%s.dir", temp); 619 (void) sprintf (dotpag, "%s.pag", temp); 620 (void) sprintf (dotdb, "%s.db", temp); 621 (void) unlink_file (dotdir); 622 (void) unlink_file (dotpag); 623 (void) unlink_file (dotdb); 624 error (1, 0, "DBM creation failed; correct above errors"); 625 } 626} 627 628static void 629rename_dbmfile (temp) 630 char *temp; 631{ 632 char newdir[50], newpag[50], newdb[50]; 633 char dotdir[50], dotpag[50], dotdb[50]; 634 char bakdir[50], bakpag[50], bakdb[50]; 635 636 (void) sprintf (dotdir, "%s.dir", CVSROOTADM_MODULES); 637 (void) sprintf (dotpag, "%s.pag", CVSROOTADM_MODULES); 638 (void) sprintf (dotdb, "%s.db", CVSROOTADM_MODULES); 639 (void) sprintf (bakdir, "%s%s.dir", BAKPREFIX, CVSROOTADM_MODULES); 640 (void) sprintf (bakpag, "%s%s.pag", BAKPREFIX, CVSROOTADM_MODULES); 641 (void) sprintf (bakdb, "%s%s.db", BAKPREFIX, CVSROOTADM_MODULES); 642 (void) sprintf (newdir, "%s.dir", temp); 643 (void) sprintf (newpag, "%s.pag", temp); 644 (void) sprintf (newdb, "%s.db", temp); 645 646 (void) chmod (newdir, 0666); 647 (void) chmod (newpag, 0666); 648 (void) chmod (newdb, 0666); 649 650 /* don't mess with me */ 651 SIG_beginCrSect (); 652 653 (void) unlink_file (bakdir); /* rm .#modules.dir .#modules.pag */ 654 (void) unlink_file (bakpag); 655 (void) unlink_file (bakdb); 656 (void) CVS_RENAME (dotdir, bakdir); /* mv modules.dir .#modules.dir */ 657 (void) CVS_RENAME (dotpag, bakpag); /* mv modules.pag .#modules.pag */ 658 (void) CVS_RENAME (dotdb, bakdb); /* mv modules.db .#modules.db */ 659 (void) CVS_RENAME (newdir, dotdir); /* mv "temp".dir modules.dir */ 660 (void) CVS_RENAME (newpag, dotpag); /* mv "temp".pag modules.pag */ 661 (void) CVS_RENAME (newdb, dotdb); /* mv "temp".db modules.db */ 662 663 /* OK -- make my day */ 664 SIG_endCrSect (); 665} 666 667#endif /* !MY_NDBM */ 668 669static void 670rename_rcsfile (temp, real) 671 char *temp; 672 char *real; 673{ 674 char *bak; 675 struct stat statbuf; 676 char *rcs; 677 678 /* Set "x" bits if set in original. */ 679 rcs = xmalloc (strlen (real) + sizeof (RCSEXT) + 10); 680 (void) sprintf (rcs, "%s%s", real, RCSEXT); 681 statbuf.st_mode = 0; /* in case rcs file doesn't exist, but it should... */ 682 (void) CVS_STAT (rcs, &statbuf); 683 free (rcs); 684 685 if (chmod (temp, 0444 | (statbuf.st_mode & 0111)) < 0) 686 error (0, errno, "warning: cannot chmod %s", temp); 687 bak = xmalloc (strlen (real) + sizeof (BAKPREFIX) + 10); 688 (void) sprintf (bak, "%s%s", BAKPREFIX, real); 689 (void) unlink_file (bak); /* rm .#loginfo */ 690 (void) CVS_RENAME (real, bak); /* mv loginfo .#loginfo */ 691 (void) CVS_RENAME (temp, real); /* mv "temp" loginfo */ 692 free (bak); 693} 694 695const char *const init_usage[] = { 696 "Usage: %s %s\n", 697 NULL 698}; 699 700int 701init (argc, argv) 702 int argc; 703 char **argv; 704{ 705 /* Name of CVSROOT directory. */ 706 char *adm; 707 /* Name of this administrative file. */ 708 char *info; 709 /* Name of ,v file for this administrative file. */ 710 char *info_v; 711 /* Exit status. */ 712 int err; 713 714 const struct admin_file *fileptr; 715 716 umask (cvsumask); 717 718 if (argc == -1 || argc > 1) 719 usage (init_usage); 720 721#ifdef CLIENT_SUPPORT 722 if (client_active) 723 { 724 start_server (); 725 726 ign_setup (); 727 send_init_command (); 728 return get_responses_and_close (); 729 } 730#endif /* CLIENT_SUPPORT */ 731 732 /* Note: we do *not* create parent directories as needed like the 733 old cvsinit.sh script did. Few utilities do that, and a 734 non-existent parent directory is as likely to be a typo as something 735 which needs to be created. */ 736 mkdir_if_needed (CVSroot_directory); 737 738 adm = xmalloc (strlen (CVSroot_directory) + sizeof (CVSROOTADM) + 10); 739 strcpy (adm, CVSroot_directory); 740 strcat (adm, "/"); 741 strcat (adm, CVSROOTADM); 742 mkdir_if_needed (adm); 743 744 /* This is needed because we pass "fileptr->filename" not "info" 745 to add_rcs_file below. I think this would be easy to change, 746 thus nuking the need for CVS_CHDIR here, but I haven't looked 747 closely (e.g. see wrappers calls within add_rcs_file). */ 748 if ( CVS_CHDIR (adm) < 0) 749 error (1, errno, "cannot change to directory %s", adm); 750 751 /* 80 is long enough for all the administrative file names, plus 752 "/" and so on. */ 753 info = xmalloc (strlen (adm) + 80); 754 info_v = xmalloc (strlen (adm) + 80); 755 for (fileptr = filelist; fileptr && fileptr->filename; ++fileptr) 756 { 757 if (fileptr->contents == NULL) 758 continue; 759 strcpy (info, adm); 760 strcat (info, "/"); 761 strcat (info, fileptr->filename); 762 strcpy (info_v, info); 763 strcat (info_v, RCSEXT); 764 if (isfile (info_v)) 765 /* We will check out this file in the mkmodules step. 766 Nothing else is required. */ 767 ; 768 else 769 { 770 int retcode; 771 772 if (!isfile (info)) 773 { 774 FILE *fp; 775 const char * const *p; 776 777 fp = open_file (info, "w"); 778 for (p = fileptr->contents; *p != NULL; ++p) 779 if (fputs (*p, fp) < 0) 780 error (1, errno, "cannot write %s", info); 781 if (fclose (fp) < 0) 782 error (1, errno, "cannot close %s", info); 783 } 784 /* The message used to say " of " and fileptr->filename after 785 "initial checkin" but I fail to see the point as we know what 786 file it is from the name. */ 787 retcode = add_rcs_file ("initial checkin", info_v, 788 fileptr->filename, "1.1", NULL, NULL, 789 0, NULL, NULL); 790 if (retcode != 0) 791 /* add_rcs_file already printed an error message. */ 792 err = 1; 793 } 794 } 795 796 /* Turn on history logging by default. The user can remove the file 797 to disable it. */ 798 strcpy (info, adm); 799 strcat (info, "/"); 800 strcat (info, CVSROOTADM_HISTORY); 801 if (!isfile (info)) 802 { 803 FILE *fp; 804 805 fp = open_file (info, "w"); 806 if (fclose (fp) < 0) 807 error (1, errno, "cannot close %s", info); 808 } 809 810 free (info); 811 free (info_v); 812 813 mkmodules (adm); 814 815 free (adm); 816 return 0; 817} 818