mkmodules.c revision 17721
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 11#ifndef DBLKSIZ 12#define DBLKSIZ 4096 /* since GNU ndbm doesn't define it */ 13#endif 14 15static int checkout_file PROTO((char *file, char *temp)); 16static void make_tempfile PROTO((char *temp)); 17static void rename_rcsfile PROTO((char *temp, char *real)); 18 19#ifndef MY_NDBM 20static void rename_dbmfile PROTO((char *temp)); 21static void write_dbmfile PROTO((char *temp)); 22#endif /* !MY_NDBM */ 23 24/* Structure which describes an administrative file. */ 25struct admin_file { 26 /* Name of the file, within the CVSROOT directory. */ 27 char *filename; 28 29 /* This is a one line description of what the file is for. It is not 30 currently used, although one wonders whether it should be, somehow. 31 If NULL, then don't process this file in mkmodules (FIXME: a bit of 32 a kludge; probably should replace this with a flags field). */ 33 char *errormsg; 34 35 /* Contents which the file should have in a new repository. To avoid 36 problems with brain-dead compilers which choke on long string constants, 37 this is a pointer to an array of char * terminated by NULL--each of 38 the strings is concatenated. */ 39 const char * const *contents; 40}; 41 42static const char *const loginfo_contents[] = { 43 "# The \"loginfo\" file is used to control where \"cvs commit\" log information\n", 44 "# is sent. The first entry on a line is a regular expression which is tested\n", 45 "# against the directory that the change is being made to, relative to the\n", 46 "# $CVSROOT. For the first match that is found, then the remainder of the\n", 47 "# line is a filter program that should expect log information on its standard\n", 48 "# input.\n", 49 "#\n", 50 "# If the repository name does not match any of the regular expressions in the\n", 51 "# first field of this file, the \"DEFAULT\" line is used, if it is specified.\n", 52 "#\n", 53 "# If the name \"ALL\" appears as a regular expression it is always used\n", 54 "# in addition to the first matching regex or \"DEFAULT\".\n", 55 "#\n", 56 "# The filter program may use one and only one \"%s\" modifier (ala printf). If\n", 57 "# such a \"%s\" is specified in the filter program, a brief title is included\n", 58 "# (as one argument, enclosed in single quotes) showing the relative directory\n", 59 "# name and listing the modified file names.\n", 60 "#\n", 61 "# For example:\n", 62 "#DEFAULT (echo \"\"; who am i; date; cat) >> $CVSROOT/CVSROOT/commitlog\n", 63 NULL 64}; 65 66static const char *const rcsinfo_contents[] = { 67 "# The \"rcsinfo\" file is used to control templates with which the editor\n", 68 "# is invoked on commit and import.\n", 69 "#\n", 70 "# The first entry on a line is a regular expression which is tested\n", 71 "# against the directory that the change is being made to, relative to the\n", 72 "# $CVSROOT. For the first match that is found, then the remainder of the\n", 73 "# line is the name of the file that contains the template.\n", 74 "#\n", 75 "# If the repository name does not match any of the regular expressions in this\n", 76 "# file, the \"DEFAULT\" line is used, if it is specified.\n", 77 "#\n", 78 "# If the name \"ALL\" appears as a regular expression it is always used\n", 79 "# in addition to the first matching regex or \"DEFAULT\".\n", 80 NULL 81}; 82 83static const char *const editinfo_contents[] = { 84 "# The \"editinfo\" file is used to allow verification of logging\n", 85 "# information. It works best when a template (as specified in the\n", 86 "# rcsinfo file) is provided for the logging procedure. Given a\n", 87 "# template with locations for, a bug-id number, a list of people who\n", 88 "# reviewed the code before it can be checked in, and an external\n", 89 "# process to catalog the differences that were code reviewed, the\n", 90 "# following test can be applied to the code:\n", 91 "#\n", 92 "# Making sure that the entered bug-id number is correct.\n", 93 "# Validating that the code that was reviewed is indeed the code being\n", 94 "# checked in (using the bug-id number or a seperate review\n", 95 "# number to identify this particular code set.).\n", 96 "#\n", 97 "# If any of the above test failed, then the commit would be aborted.\n", 98 "#\n", 99 "# Actions such as mailing a copy of the report to each reviewer are\n", 100 "# better handled by an entry in the loginfo file.\n", 101 "#\n", 102 "# One thing that should be noted is the the ALL keyword is not\n", 103 "# supported. There can be only one entry that matches a given\n", 104 "# repository.\n", 105 NULL 106}; 107 108static const char *const commitinfo_contents[] = { 109 "# The \"commitinfo\" file is used to control pre-commit checks.\n", 110 "# The filter on the right is invoked with the repository and a list \n", 111 "# of files to check. A non-zero exit of the filter program will \n", 112 "# cause the commit to be aborted.\n", 113 "#\n", 114 "# The first entry on a line is a regular expression which is tested\n", 115 "# against the directory that the change is being committed to, relative\n", 116 "# to the $CVSROOT. For the first match that is found, then the remainder\n", 117 "# of the line is the name of the filter to run.\n", 118 "#\n", 119 "# If the repository name does not match any of the regular expressions in this\n", 120 "# file, the \"DEFAULT\" line is used, if it is specified.\n", 121 "#\n", 122 "# If the name \"ALL\" appears as a regular expression it is always used\n", 123 "# in addition to the first matching regex or \"DEFAULT\".\n", 124 NULL 125}; 126 127static const char *const taginfo_contents[] = { 128 "# The \"taginfo\" file is used to control pre-tag checks.\n", 129 "# The filter on the right is invoked with the following arguments:\n", 130 "#\n", 131 "# $1 -- tagname\n", 132 "# $2 -- operation \"add\" for tag, \"mov\" for tag -F, and \"del\" for tag -d\n", 133 "# $3 -- repository\n", 134 "# $4-> file revision [file revision ...]\n", 135 "#\n", 136 "# A non-zero exit of the filter program will cause the tag to be aborted.\n", 137 "#\n", 138 "# The first entry on a line is a regular expression which is tested\n", 139 "# against the directory that the change is being committed to, relative\n", 140 "# to the $CVSROOT. For the first match that is found, then the remainder\n", 141 "# of the line is the name of the filter to run.\n", 142 "#\n", 143 "# If the repository name does not match any of the regular expressions in this\n", 144 "# file, the \"DEFAULT\" line is used, if it is specified.\n", 145 "#\n", 146 "# If the name \"ALL\" appears as a regular expression it is always used\n", 147 "# in addition to the first matching regex or \"DEFAULT\".\n", 148 NULL 149}; 150 151static const char *const checkoutlist_contents[] = { 152 "# The \"checkoutlist\" file is used to support additional version controlled\n", 153 "# administrative files in $CVSROOT/CVSROOT, such as template files.\n", 154 "#\n", 155 "# The first entry on a line is a filename which will be checked out from\n", 156 "# the corresponding RCS file in the $CVSROOT/CVSROOT directory.\n", 157 "# The remainder of the line is an error message to use if the file cannot\n", 158 "# be checked out.\n", 159 "#\n", 160 "# File format:\n", 161 "#\n", 162 "# [<whitespace>]<filename><whitespace><error message><end-of-line>\n", 163 "#\n", 164 "# comment lines begin with '#'\n", 165 NULL 166}; 167 168static const char *const cvswrappers_contents[] = { 169 "# This file describes wrappers and other binary files to CVS.\n", 170 "#\n", 171 "# Wrappers are the concept where directories of files are to be\n", 172 "# treated as a single file. The intended use is to wrap up a wrapper\n", 173 "# into a single tar such that the tar archive can be treated as a\n", 174 "# single binary file in CVS.\n", 175 "#\n", 176 "# To solve the problem effectively, it was also necessary to be able to\n", 177 "# prevent rcsmerge from merging these files.\n", 178 "#\n", 179 "# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)\n", 180 "#\n", 181 "# wildcard [option value][option value]...\n", 182 "#\n", 183 "# where option is one of\n", 184 "# -f from cvs filter value: path to filter\n", 185 "# -t to cvs filter value: path to filter\n", 186 "# -m update methodology value: MERGE or COPY\n", 187 "#\n", 188 "# and value is a single-quote delimited value.\n", 189 "#\n", 190 "# For example:\n", 191 NULL 192}; 193 194static const char *const notify_contents[] = { 195 "# The \"notify\" file controls where notifications from watches set by\n", 196 "# \"cvs watch add\" or \"cvs edit\" are sent. The first entry on a line is\n", 197 "# a regular expression which is tested against the directory that the\n", 198 "# change is being made to, relative to the $CVSROOT. If it matches,\n", 199 "# then the remainder of the line is a filter program that should contain\n", 200 "# one occurrence of %s for the user to notify, and information on its\n", 201 "# standard input.\n", 202 "#\n", 203 "# \"ALL\" or \"DEFAULT\" can be used in place of the regular expression.\n", 204 "#\n", 205 "# For example:\n", 206 "#ALL mail %s -s \"CVS notification\"\n", 207 NULL 208}; 209 210static const char *const modules_contents[] = { 211 "# Three different line formats are valid:\n", 212 "# key -a aliases...\n", 213 "# key [options] directory\n", 214 "# key [options] directory files...\n", 215 "#\n", 216 "# Where \"options\" are composed of:\n", 217 "# -i prog Run \"prog\" on \"cvs commit\" from top-level of module.\n", 218 "# -o prog Run \"prog\" on \"cvs checkout\" of module.\n", 219 "# -e prog Run \"prog\" on \"cvs export\" of module.\n", 220 "# -t prog Run \"prog\" on \"cvs rtag\" of module.\n", 221 "# -u prog Run \"prog\" on \"cvs update\" of module.\n", 222 "# -d dir Place module in directory \"dir\" instead of module name.\n", 223 "# -l Top-level directory only -- do not recurse.\n", 224 "#\n", 225 "# And \"directory\" is a path to a directory relative to $CVSROOT.\n", 226 "#\n", 227 "# The \"-a\" option specifies an alias. An alias is interpreted as if\n", 228 "# everything on the right of the \"-a\" had been typed on the command line.\n", 229 "#\n", 230 "# You can encode a module within a module by using the special '&'\n", 231 "# character to interpose another module into the current module. This\n", 232 "# can be useful for creating a module that consists of many directories\n", 233 "# spread out over the entire source repository.\n", 234 NULL 235}; 236 237static const struct admin_file filelist[] = { 238 {CVSROOTADM_LOGINFO, 239 "no logging of 'cvs commit' messages is done without a %s file", 240 &loginfo_contents[0]}, 241 {CVSROOTADM_RCSINFO, 242 "a %s file can be used to configure 'cvs commit' templates", 243 rcsinfo_contents}, 244 {CVSROOTADM_EDITINFO, 245 "a %s file can be used to validate log messages", 246 editinfo_contents}, 247 {CVSROOTADM_COMMITINFO, 248 "a %s file can be used to configure 'cvs commit' checking", 249 commitinfo_contents}, 250 {CVSROOTADM_TAGINFO, 251 "a %s file can be used to configure 'cvs tag' checking", 252 taginfo_contents}, 253 {CVSROOTADM_IGNORE, 254 "a %s file can be used to specify files to ignore", 255 NULL}, 256 {CVSROOTADM_CHECKOUTLIST, 257 "a %s file can specify extra CVSROOT files to auto-checkout", 258 checkoutlist_contents}, 259 {CVSROOTADM_WRAPPER, 260 "a %s file can be used to specify files to treat as wrappers", 261 cvswrappers_contents}, 262 {CVSROOTADM_NOTIFY, 263 "a %s file can be used to specify where notifications go", 264 notify_contents}, 265 {CVSROOTADM_MODULES, 266 /* modules is special-cased in mkmodules. */ 267 NULL, 268 modules_contents}, 269 {NULL, NULL} 270}; 271 272/* Rebuild the checked out administrative files in directory DIR. */ 273int 274mkmodules (dir) 275 char *dir; 276{ 277 struct saved_cwd cwd; 278 /* FIXME: arbitrary limit */ 279 char temp[PATH_MAX]; 280 char *cp, *last, *fname; 281#ifdef MY_NDBM 282 DBM *db; 283#endif 284 FILE *fp; 285 /* FIXME: arbitrary limit */ 286 char line[512]; 287 const struct admin_file *fileptr; 288 289 if (save_cwd (&cwd)) 290 exit (EXIT_FAILURE); 291 292 if (chdir (dir) < 0) 293 error (1, errno, "cannot chdir to %s", dir); 294 295 /* 296 * First, do the work necessary to update the "modules" database. 297 */ 298 make_tempfile (temp); 299 switch (checkout_file (CVSROOTADM_MODULES, temp)) 300 { 301 302 case 0: /* everything ok */ 303#ifdef MY_NDBM 304 /* open it, to generate any duplicate errors */ 305 if ((db = dbm_open (temp, O_RDONLY, 0666)) != NULL) 306 dbm_close (db); 307#else 308 write_dbmfile (temp); 309 rename_dbmfile (temp); 310#endif 311 rename_rcsfile (temp, CVSROOTADM_MODULES); 312 break; 313 314 case -1: /* fork failed */ 315 (void) unlink_file (temp); 316 exit (EXIT_FAILURE); 317 /* NOTREACHED */ 318 319 default: 320 error (0, 0, 321 "'cvs checkout' is less functional without a %s file", 322 CVSROOTADM_MODULES); 323 break; 324 } /* switch on checkout_file() */ 325 326 (void) unlink_file (temp); 327 328 /* Checkout the files that need it in CVSROOT dir */ 329 for (fileptr = filelist; fileptr && fileptr->filename; fileptr++) { 330 if (fileptr->errormsg == NULL) 331 continue; 332 make_tempfile (temp); 333 if (checkout_file (fileptr->filename, temp) == 0) 334 rename_rcsfile (temp, fileptr->filename); 335#if 0 336 /* 337 * If there was some problem other than the file not existing, 338 * checkout_file already printed a real error message. If the 339 * file does not exist, it is harmless--it probably just means 340 * that the repository was created with an old version of CVS 341 * which didn't have so many files in CVSROOT. 342 */ 343 else if (fileptr->errormsg) 344 error (0, 0, fileptr->errormsg, fileptr->filename); 345#endif 346 (void) unlink_file (temp); 347 } 348 349 /* Use 'fopen' instead of 'open_file' because we want to ignore error */ 350 fp = fopen (CVSROOTADM_CHECKOUTLIST, "r"); 351 if (fp) 352 { 353 /* 354 * File format: 355 * [<whitespace>]<filename><whitespace><error message><end-of-line> 356 * 357 * comment lines begin with '#' 358 */ 359 while (fgets (line, sizeof (line), fp) != NULL) 360 { 361 /* skip lines starting with # */ 362 if (line[0] == '#') 363 continue; 364 365 if ((last = strrchr (line, '\n')) != NULL) 366 *last = '\0'; /* strip the newline */ 367 368 /* Skip leading white space. */ 369 for (fname = line; *fname && isspace(*fname); fname++) 370 ; 371 372 /* Find end of filename. */ 373 for (cp = fname; *cp && !isspace(*cp); cp++) 374 ; 375 *cp = '\0'; 376 377 make_tempfile (temp); 378 if (checkout_file (fname, temp) == 0) 379 { 380 rename_rcsfile (temp, fname); 381 } 382 else 383 { 384 for (cp++; cp < last && *last && isspace(*last); cp++) 385 ; 386 if (cp < last && *cp) 387 error (0, 0, cp, fname); 388 } 389 } 390 (void) fclose (fp); 391 } 392 393 if (restore_cwd (&cwd, NULL)) 394 exit (EXIT_FAILURE); 395 free_cwd (&cwd); 396 397 return (0); 398} 399 400/* 401 * Yeah, I know, there are NFS race conditions here. 402 */ 403static void 404make_tempfile (temp) 405 char *temp; 406{ 407 static int seed = 0; 408 int fd; 409 410 if (seed == 0) 411 seed = getpid (); 412 while (1) 413 { 414 (void) sprintf (temp, "%s%d", BAKPREFIX, seed++); 415 if ((fd = open (temp, O_CREAT|O_EXCL|O_RDWR, 0666)) != -1) 416 break; 417 if (errno != EEXIST) 418 error (1, errno, "cannot create temporary file %s", temp); 419 } 420 if (close(fd) < 0) 421 error(1, errno, "cannot close temporary file %s", temp); 422} 423 424static int 425checkout_file (file, temp) 426 char *file; 427 char *temp; 428{ 429 char rcs[PATH_MAX]; 430 int retcode = 0; 431 432 (void) sprintf (rcs, "%s%s", file, RCSEXT); 433 if (!isfile (rcs)) 434 return (1); 435 run_setup ("%s%s -x,v/ -q -p", Rcsbin, RCS_CO); 436 run_arg (rcs); 437 if ((retcode = run_exec (RUN_TTY, temp, RUN_TTY, RUN_NORMAL)) != 0) 438 { 439 error (0, retcode == -1 ? errno : 0, "failed to check out %s file", file); 440 } 441 return (retcode); 442} 443 444#ifndef MY_NDBM 445 446static void 447write_dbmfile (temp) 448 char *temp; 449{ 450 char line[DBLKSIZ], value[DBLKSIZ]; 451 FILE *fp; 452 DBM *db; 453 char *cp, *vp; 454 datum key, val; 455 int len, cont, err = 0; 456 457 fp = open_file (temp, "r"); 458 if ((db = dbm_open (temp, O_RDWR | O_CREAT | O_TRUNC, 0666)) == NULL) 459 error (1, errno, "cannot open dbm file %s for creation", temp); 460 for (cont = 0; fgets (line, sizeof (line), fp) != NULL;) 461 { 462 if ((cp = strrchr (line, '\n')) != NULL) 463 *cp = '\0'; /* strip the newline */ 464 465 /* 466 * Add the line to the value, at the end if this is a continuation 467 * line; otherwise at the beginning, but only after any trailing 468 * backslash is removed. 469 */ 470 vp = value; 471 if (cont) 472 vp += strlen (value); 473 474 /* 475 * See if the line we read is a continuation line, and strip the 476 * backslash if so. 477 */ 478 len = strlen (line); 479 if (len > 0) 480 cp = &line[len - 1]; 481 else 482 cp = line; 483 if (*cp == '\\') 484 { 485 cont = 1; 486 *cp = '\0'; 487 } 488 else 489 { 490 cont = 0; 491 } 492 (void) strcpy (vp, line); 493 if (value[0] == '#') 494 continue; /* comment line */ 495 vp = value; 496 while (*vp && isspace (*vp)) 497 vp++; 498 if (*vp == '\0') 499 continue; /* empty line */ 500 501 /* 502 * If this was not a continuation line, add the entry to the database 503 */ 504 if (!cont) 505 { 506 key.dptr = vp; 507 while (*vp && !isspace (*vp)) 508 vp++; 509 key.dsize = vp - key.dptr; 510 *vp++ = '\0'; /* NULL terminate the key */ 511 while (*vp && isspace (*vp)) 512 vp++; /* skip whitespace to value */ 513 if (*vp == '\0') 514 { 515 error (0, 0, "warning: NULL value for key `%s'", key.dptr); 516 continue; 517 } 518 val.dptr = vp; 519 val.dsize = strlen (vp); 520 if (dbm_store (db, key, val, DBM_INSERT) == 1) 521 { 522 error (0, 0, "duplicate key found for `%s'", key.dptr); 523 err++; 524 } 525 } 526 } 527 dbm_close (db); 528 (void) fclose (fp); 529 if (err) 530 { 531 char dotdir[50], dotpag[50], dotdb[50]; 532 533 (void) sprintf (dotdir, "%s.dir", temp); 534 (void) sprintf (dotpag, "%s.pag", temp); 535 (void) sprintf (dotdb, "%s.db", temp); 536 (void) unlink_file (dotdir); 537 (void) unlink_file (dotpag); 538 (void) unlink_file (dotdb); 539 error (1, 0, "DBM creation failed; correct above errors"); 540 } 541} 542 543static void 544rename_dbmfile (temp) 545 char *temp; 546{ 547 char newdir[50], newpag[50], newdb[50]; 548 char dotdir[50], dotpag[50], dotdb[50]; 549 char bakdir[50], bakpag[50], bakdb[50]; 550 551 (void) sprintf (dotdir, "%s.dir", CVSROOTADM_MODULES); 552 (void) sprintf (dotpag, "%s.pag", CVSROOTADM_MODULES); 553 (void) sprintf (dotdb, "%s.db", CVSROOTADM_MODULES); 554 (void) sprintf (bakdir, "%s%s.dir", BAKPREFIX, CVSROOTADM_MODULES); 555 (void) sprintf (bakpag, "%s%s.pag", BAKPREFIX, CVSROOTADM_MODULES); 556 (void) sprintf (bakdb, "%s%s.db", BAKPREFIX, CVSROOTADM_MODULES); 557 (void) sprintf (newdir, "%s.dir", temp); 558 (void) sprintf (newpag, "%s.pag", temp); 559 (void) sprintf (newdb, "%s.db", temp); 560 561 (void) chmod (newdir, 0666); 562 (void) chmod (newpag, 0666); 563 (void) chmod (newdb, 0666); 564 565 /* don't mess with me */ 566 SIG_beginCrSect (); 567 568 (void) unlink_file (bakdir); /* rm .#modules.dir .#modules.pag */ 569 (void) unlink_file (bakpag); 570 (void) unlink_file (bakdb); 571 (void) rename (dotdir, bakdir); /* mv modules.dir .#modules.dir */ 572 (void) rename (dotpag, bakpag); /* mv modules.pag .#modules.pag */ 573 (void) rename (dotdb, bakdb); /* mv modules.db .#modules.db */ 574 (void) rename (newdir, dotdir); /* mv "temp".dir modules.dir */ 575 (void) rename (newpag, dotpag); /* mv "temp".pag modules.pag */ 576 (void) rename (newdb, dotdb); /* mv "temp".db modules.db */ 577 578 /* OK -- make my day */ 579 SIG_endCrSect (); 580} 581 582#endif /* !MY_NDBM */ 583 584static void 585rename_rcsfile (temp, real) 586 char *temp; 587 char *real; 588{ 589 char bak[50]; 590 struct stat statbuf; 591 char rcs[PATH_MAX]; 592 593 /* Set "x" bits if set in original. */ 594 (void) sprintf (rcs, "%s%s", real, RCSEXT); 595 statbuf.st_mode = 0; /* in case rcs file doesn't exist, but it should... */ 596 (void) stat (rcs, &statbuf); 597 598 if (chmod (temp, 0444 | (statbuf.st_mode & 0111)) < 0) 599 error (0, errno, "warning: cannot chmod %s", temp); 600 (void) sprintf (bak, "%s%s", BAKPREFIX, real); 601 (void) unlink_file (bak); /* rm .#loginfo */ 602 (void) rename (real, bak); /* mv loginfo .#loginfo */ 603 (void) rename (temp, real); /* mv "temp" loginfo */ 604} 605 606const char *const init_usage[] = { 607 "Usage: %s %s\n", 608 NULL 609}; 610 611/* Create directory NAME if it does not already exist; fatal error for 612 other errors. FIXME: This should be in filesubr.c or thereabouts, 613 probably. Perhaps it should be further abstracted, though (for example 614 to handle CVSUMASK where appropriate?). */ 615static void 616mkdir_if_needed (name) 617 char *name; 618{ 619 if (CVS_MKDIR (name, 0777) < 0) 620 { 621 if (errno != EEXIST 622#ifdef EACCESS 623 /* OS/2; see longer comment in client.c. */ 624 && errno != EACCESS 625#endif 626 ) 627 error (1, errno, "cannot mkdir %s", name); 628 } 629} 630 631int 632init (argc, argv) 633 int argc; 634 char **argv; 635{ 636 /* Name of CVSROOT directory. */ 637 char *adm; 638 /* Name of this administrative file. */ 639 char *info; 640 /* Name of ,v file for this administrative file. */ 641 char *info_v; 642 643 const struct admin_file *fileptr; 644 645 umask (cvsumask); 646 647 if (argc > 1) 648 usage (init_usage); 649 650 if (client_active) 651 { 652 start_server (); 653 654 ign_setup (); 655 send_init_command (); 656 return get_responses_and_close (); 657 } 658 659 /* Note: we do *not* create parent directories as needed like the 660 old cvsinit.sh script did. Few utilities do that, and a 661 non-existent parent directory is as likely to be a typo as something 662 which needs to be created. */ 663 mkdir_if_needed (CVSroot); 664 665 adm = xmalloc (strlen (CVSroot) + sizeof (CVSROOTADM) + 10); 666 strcpy (adm, CVSroot); 667 strcat (adm, "/"); 668 strcat (adm, CVSROOTADM); 669 mkdir_if_needed (adm); 670 671 /* This is needed by the call to "ci" below. */ 672 if (chdir (adm) < 0) 673 error (1, errno, "cannot change to directory %s", adm); 674 675 /* 80 is long enough for all the administrative file names, plus 676 "/" and so on. */ 677 info = xmalloc (strlen (adm) + 80); 678 info_v = xmalloc (strlen (adm) + 80); 679 for (fileptr = filelist; fileptr && fileptr->filename; ++fileptr) 680 { 681 if (fileptr->contents == NULL) 682 continue; 683 strcpy (info, adm); 684 strcat (info, "/"); 685 strcat (info, fileptr->filename); 686 strcpy (info_v, info); 687 strcat (info_v, RCSEXT); 688 if (isfile (info_v)) 689 /* We will check out this file in the mkmodules step. 690 Nothing else is required. */ 691 ; 692 else 693 { 694 int retcode; 695 696 if (!isfile (info)) 697 { 698 FILE *fp; 699 const char * const *p; 700 701 fp = open_file (info, "w"); 702 for (p = fileptr->contents; *p != NULL; ++p) 703 if (fputs (*p, fp) < 0) 704 error (1, errno, "cannot write %s", info); 705 if (fclose (fp) < 0) 706 error (1, errno, "cannot close %s", info); 707 } 708 /* Now check the file in. FIXME: we could be using 709 add_rcs_file from import.c which is faster (if it were 710 tweaked slightly). */ 711 run_setup ("%s%s -x,v/ -q -u -t-", Rcsbin, RCS_CI); 712 run_args ("-minitial checkin of %s", fileptr->filename); 713 run_arg (fileptr->filename); 714 retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); 715 if (retcode != 0) 716 error (1, retcode == -1 ? errno : 0, 717 "failed to check in %s", info); 718 } 719 } 720 721 /* Turn on history logging by default. The user can remove the file 722 to disable it. */ 723 strcpy (info, adm); 724 strcat (info, "/"); 725 strcat (info, CVSROOTADM_HISTORY); 726 if (!isfile (info)) 727 { 728 FILE *fp; 729 730 fp = open_file (info, "w"); 731 if (fclose (fp) < 0) 732 error (1, errno, "cannot close %s", info); 733 } 734 735 free (info); 736 free (info_v); 737 738 mkmodules (adm); 739 740 free (adm); 741 return 0; 742} 743