1 /* $OpenBSD: util.c,v 1.144 2008/06/10 01:00:35 joris Exp $ */ 2 /* 3 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org> 4 * Copyright (c) 2005, 2006 Joris Vink <joris@openbsd.org> 5 * Copyright (c) 2005, 2006 Xavier Santolaria <xsa@openbsd.org> 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. The name of the author may not be used to endorse or promote products 15 * derived from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 18 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 19 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 20 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 23 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 24 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 25 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 26 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <sys/stat.h> 30 #include <sys/types.h> 31 #include <sys/wait.h> 32 33 #include <atomicio.h> 34 #include <errno.h> 35 #include <fcntl.h> 36 #include <md5.h> 37 #include <stdlib.h> 38 #include <string.h> 39 #include <paths.h> 40 #include <unistd.h> 41 42 #include "cvs.h" 43 #include "remote.h" 44 45 extern int print_stdout; 46 extern int build_dirs; 47 48 /* letter -> mode type map */ 49 static const int cvs_modetypes[26] = { 50 -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, 51 -1, 2, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, 52 }; 53 54 /* letter -> mode map */ 55 static const mode_t cvs_modes[3][26] = { 56 { 57 0, 0, 0, 0, 0, 0, 0, /* a - g */ 58 0, 0, 0, 0, 0, 0, 0, /* h - m */ 59 0, 0, 0, S_IRUSR, 0, 0, 0, /* n - u */ 60 0, S_IWUSR, S_IXUSR, 0, 0 /* v - z */ 61 }, 62 { 63 0, 0, 0, 0, 0, 0, 0, /* a - g */ 64 0, 0, 0, 0, 0, 0, 0, /* h - m */ 65 0, 0, 0, S_IRGRP, 0, 0, 0, /* n - u */ 66 0, S_IWGRP, S_IXGRP, 0, 0 /* v - z */ 67 }, 68 { 69 0, 0, 0, 0, 0, 0, 0, /* a - g */ 70 0, 0, 0, 0, 0, 0, 0, /* h - m */ 71 0, 0, 0, S_IROTH, 0, 0, 0, /* n - u */ 72 0, S_IWOTH, S_IXOTH, 0, 0 /* v - z */ 73 } 74 }; 75 76 77 /* octal -> string */ 78 static const char *cvs_modestr[8] = { 79 "", "x", "w", "wx", "r", "rx", "rw", "rwx" 80 }; 81 82 /* 83 * cvs_strtomode() 84 * 85 * Read the contents of the string <str> and generate a permission mode from 86 * the contents of <str>, which is assumed to have the mode format of CVS. 87 * The CVS protocol specification states that any modes or mode types that are 88 * not recognized should be silently ignored. This function does not return 89 * an error in such cases, but will issue warnings. 90 */ 91 void 92 cvs_strtomode(const char *str, mode_t *mode) 93 { 94 char type; 95 size_t l; 96 mode_t m; 97 char buf[32], ms[4], *sp, *ep; 98 99 m = 0; 100 l = strlcpy(buf, str, sizeof(buf)); 101 if (l >= sizeof(buf)) 102 fatal("cvs_strtomode: string truncation"); 103 104 sp = buf; 105 ep = sp; 106 107 for (sp = buf; ep != NULL; sp = ep + 1) { 108 ep = strchr(sp, ','); 109 if (ep != NULL) 110 *ep = '\0'; 111 112 memset(ms, 0, sizeof ms); 113 if (sscanf(sp, "%c=%3s", &type, ms) != 2 && 114 sscanf(sp, "%c=", &type) != 1) { 115 fatal("failed to scan mode string `%s'", sp); 116 } 117 118 if (type <= 'a' || type >= 'z' || 119 cvs_modetypes[type - 'a'] == -1) { 120 cvs_log(LP_ERR, 121 "invalid mode type `%c'" 122 " (`u', `g' or `o' expected), ignoring", type); 123 continue; 124 } 125 126 /* make type contain the actual mode index */ 127 type = cvs_modetypes[type - 'a']; 128 129 for (sp = ms; *sp != '\0'; sp++) { 130 if (*sp <= 'a' || *sp >= 'z' || 131 cvs_modes[(int)type][*sp - 'a'] == 0) { 132 fatal("invalid permission bit `%c'", *sp); 133 } else 134 m |= cvs_modes[(int)type][*sp - 'a']; 135 } 136 } 137 138 *mode = m; 139 } 140 141 /* 142 * cvs_modetostr() 143 * 144 * Generate a CVS-format string to represent the permissions mask on a file 145 * from the mode <mode> and store the result in <buf>, which can accept up to 146 * <len> bytes (including the terminating NUL byte). The result is guaranteed 147 * to be NUL-terminated. 148 */ 149 void 150 cvs_modetostr(mode_t mode, char *buf, size_t len) 151 { 152 char tmp[16], *bp; 153 mode_t um, gm, om; 154 155 um = (mode & S_IRWXU) >> 6; 156 gm = (mode & S_IRWXG) >> 3; 157 om = mode & S_IRWXO; 158 159 bp = buf; 160 *bp = '\0'; 161 162 if (um) { 163 if (strlcpy(tmp, "u=", sizeof(tmp)) >= sizeof(tmp) || 164 strlcat(tmp, cvs_modestr[um], sizeof(tmp)) >= sizeof(tmp)) 165 fatal("cvs_modetostr: overflow for user mode"); 166 167 if (strlcat(buf, tmp, len) >= len) 168 fatal("cvs_modetostr: string truncation"); 169 } 170 171 if (gm) { 172 if (um) { 173 if (strlcat(buf, ",", len) >= len) 174 fatal("cvs_modetostr: string truncation"); 175 } 176 177 if (strlcpy(tmp, "g=", sizeof(tmp)) >= sizeof(tmp) || 178 strlcat(tmp, cvs_modestr[gm], sizeof(tmp)) >= sizeof(tmp)) 179 fatal("cvs_modetostr: overflow for group mode"); 180 181 if (strlcat(buf, tmp, len) >= len) 182 fatal("cvs_modetostr: string truncation"); 183 } 184 185 if (om) { 186 if (um || gm) { 187 if (strlcat(buf, ",", len) >= len) 188 fatal("cvs_modetostr: string truncation"); 189 } 190 191 if (strlcpy(tmp, "o=", sizeof(tmp)) >= sizeof(tmp) || 192 strlcat(tmp, cvs_modestr[gm], sizeof(tmp)) >= sizeof(tmp)) 193 fatal("cvs_modetostr: overflow for others mode"); 194 195 if (strlcat(buf, tmp, len) >= len) 196 fatal("cvs_modetostr: string truncation"); 197 } 198 } 199 200 /* 201 * cvs_cksum() 202 * 203 * Calculate the MD5 checksum of the file whose path is <file> and generate 204 * a CVS-format 32 hex-digit string, which is stored in <dst>, whose size is 205 * given in <len> and must be at least 33. 206 * Returns 0 on success, or -1 on failure. 207 */ 208 int 209 cvs_cksum(const char *file, char *dst, size_t len) 210 { 211 if (len < CVS_CKSUM_LEN) { 212 cvs_log(LP_ERR, "buffer too small for checksum"); 213 return (-1); 214 } 215 if (MD5File(file, dst) == NULL) { 216 cvs_log(LP_ERR, "failed to generate checksum for %s", file); 217 return (-1); 218 } 219 220 return (0); 221 } 222 223 /* 224 * cvs_getargv() 225 * 226 * Parse a line contained in <line> and generate an argument vector by 227 * splitting the line on spaces and tabs. The resulting vector is stored in 228 * <argv>, which can accept up to <argvlen> entries. 229 * Returns the number of arguments in the vector, or -1 if an error occurred. 230 */ 231 int 232 cvs_getargv(const char *line, char **argv, int argvlen) 233 { 234 u_int i; 235 int argc, error; 236 char *linebuf, *lp, *cp; 237 238 linebuf = xstrdup(line); 239 240 memset(argv, 0, argvlen * sizeof(char *)); 241 argc = 0; 242 243 /* build the argument vector */ 244 error = 0; 245 for (lp = linebuf; lp != NULL;) { 246 cp = strsep(&lp, " \t"); 247 if (cp == NULL) 248 break; 249 else if (*cp == '\0') 250 continue; 251 252 if (argc == argvlen) { 253 error++; 254 break; 255 } 256 257 argv[argc] = xstrdup(cp); 258 argc++; 259 } 260 261 if (error != 0) { 262 /* ditch the argument vector */ 263 for (i = 0; i < (u_int)argc; i++) 264 xfree(argv[i]); 265 argc = -1; 266 } 267 268 xfree(linebuf); 269 return (argc); 270 } 271 272 /* 273 * cvs_makeargv() 274 * 275 * Allocate an argument vector large enough to accommodate for all the 276 * arguments found in <line> and return it. 277 */ 278 char ** 279 cvs_makeargv(const char *line, int *argc) 280 { 281 int i, ret; 282 char *argv[1024], **copy; 283 284 ret = cvs_getargv(line, argv, 1024); 285 if (ret == -1) 286 return (NULL); 287 288 copy = xcalloc(ret + 1, sizeof(char *)); 289 290 for (i = 0; i < ret; i++) 291 copy[i] = argv[i]; 292 copy[ret] = NULL; 293 294 *argc = ret; 295 return (copy); 296 } 297 298 /* 299 * cvs_freeargv() 300 * 301 * Free an argument vector previously generated by cvs_getargv(). 302 */ 303 void 304 cvs_freeargv(char **argv, int argc) 305 { 306 int i; 307 308 for (i = 0; i < argc; i++) 309 if (argv[i] != NULL) 310 xfree(argv[i]); 311 } 312 313 /* 314 * cvs_chdir() 315 * 316 * Change to directory <path>. 317 * If <rm> is equal to `1', <path> is removed if chdir() fails so we 318 * do not have temporary directories leftovers. 319 * Returns 0 on success. 320 */ 321 int 322 cvs_chdir(const char *path, int rm) 323 { 324 if (chdir(path) == -1) { 325 if (rm == 1) 326 cvs_unlink(path); 327 fatal("cvs_chdir: `%s': %s", path, strerror(errno)); 328 } 329 330 return (0); 331 } 332 333 /* 334 * cvs_rename() 335 * Change the name of a file. 336 * rename() wrapper with an error message. 337 * Returns 0 on success. 338 */ 339 int 340 cvs_rename(const char *from, const char *to) 341 { 342 if (cvs_server_active == 0) 343 cvs_log(LP_TRACE, "cvs_rename(%s,%s)", from, to); 344 345 if (cvs_noexec == 1) 346 return (0); 347 348 if (rename(from, to) == -1) 349 fatal("cvs_rename: `%s'->`%s': %s", from, to, strerror(errno)); 350 351 return (0); 352 } 353 354 /* 355 * cvs_unlink() 356 * 357 * Removes the link named by <path>. 358 * unlink() wrapper with an error message. 359 * Returns 0 on success, or -1 on failure. 360 */ 361 int 362 cvs_unlink(const char *path) 363 { 364 if (cvs_server_active == 0) 365 cvs_log(LP_TRACE, "cvs_unlink(%s)", path); 366 367 if (cvs_noexec == 1) 368 return (0); 369 370 if (unlink(path) == -1 && errno != ENOENT) { 371 cvs_log(LP_ERRNO, "%s", path); 372 return (-1); 373 } 374 375 return (0); 376 } 377 378 /* 379 * cvs_rmdir() 380 * 381 * Remove a directory tree from disk. 382 * Returns 0 on success, or -1 on failure. 383 */ 384 int 385 cvs_rmdir(const char *path) 386 { 387 int type, ret = -1; 388 DIR *dirp; 389 struct dirent *ent; 390 struct stat st; 391 char fpath[MAXPATHLEN]; 392 393 if (cvs_server_active == 0) 394 cvs_log(LP_TRACE, "cvs_rmdir(%s)", path); 395 396 if (cvs_noexec == 1) 397 return (0); 398 399 if ((dirp = opendir(path)) == NULL) { 400 cvs_log(LP_ERR, "failed to open '%s'", path); 401 return (-1); 402 } 403 404 while ((ent = readdir(dirp)) != NULL) { 405 if (!strcmp(ent->d_name, ".") || 406 !strcmp(ent->d_name, "..")) 407 continue; 408 409 (void)xsnprintf(fpath, sizeof(fpath), "%s/%s", 410 path, ent->d_name); 411 412 if (ent->d_type == DT_UNKNOWN) { 413 if (lstat(fpath, &st) == -1) 414 fatal("'%s': %s", fpath, strerror(errno)); 415 416 switch (st.st_mode & S_IFMT) { 417 case S_IFDIR: 418 type = CVS_DIR; 419 break; 420 case S_IFREG: 421 type = CVS_FILE; 422 break; 423 default: 424 fatal("'%s': Unknown file type in copy", 425 fpath); 426 } 427 } else { 428 switch (ent->d_type) { 429 case DT_DIR: 430 type = CVS_DIR; 431 break; 432 case DT_REG: 433 type = CVS_FILE; 434 break; 435 default: 436 fatal("'%s': Unknown file type in copy", 437 fpath); 438 } 439 } 440 switch (type) { 441 case CVS_DIR: 442 if (cvs_rmdir(fpath) == -1) 443 goto done; 444 break; 445 case CVS_FILE: 446 if (cvs_unlink(fpath) == -1 && errno != ENOENT) 447 goto done; 448 break; 449 default: 450 fatal("type %d unknown, shouldn't happen", type); 451 } 452 } 453 454 455 if (rmdir(path) == -1 && errno != ENOENT) { 456 cvs_log(LP_ERRNO, "%s", path); 457 goto done; 458 } 459 460 ret = 0; 461 done: 462 closedir(dirp); 463 return (ret); 464 } 465 466 void 467 cvs_get_repository_path(const char *dir, char *dst, size_t len) 468 { 469 char buf[MAXPATHLEN]; 470 471 cvs_get_repository_name(dir, buf, sizeof(buf)); 472 (void)xsnprintf(dst, len, "%s/%s", current_cvsroot->cr_dir, buf); 473 cvs_validate_directory(dst); 474 } 475 476 void 477 cvs_get_repository_name(const char *dir, char *dst, size_t len) 478 { 479 FILE *fp; 480 char fpath[MAXPATHLEN]; 481 482 dst[0] = '\0'; 483 484 if (!(cmdp->cmd_flags & CVS_USE_WDIR)) { 485 if (strlcpy(dst, dir, len) >= len) 486 fatal("cvs_get_repository_name: truncation"); 487 return; 488 } 489 490 switch (cvs_cmdop) { 491 case CVS_OP_EXPORT: 492 if (strcmp(dir, ".")) 493 if (strlcpy(dst, dir, len) >= len) 494 fatal("cvs_get_repository_name: truncation"); 495 break; 496 case CVS_OP_IMPORT: 497 if (strlcpy(dst, import_repository, len) >= len) 498 fatal("cvs_get_repository_name: truncation"); 499 if (strlcat(dst, "/", len) >= len) 500 fatal("cvs_get_repository_name: truncation"); 501 502 if (strcmp(dir, ".")) 503 if (strlcat(dst, dir, len) >= len) 504 fatal("cvs_get_repository_name: truncation"); 505 break; 506 default: 507 (void)xsnprintf(fpath, sizeof(fpath), "%s/%s", 508 dir, CVS_PATH_REPOSITORY); 509 if ((fp = fopen(fpath, "r")) != NULL) { 510 if ((fgets(dst, len, fp)) == NULL) 511 fatal("%s: bad repository file", fpath); 512 dst[strcspn(dst, "\n")] = '\0'; 513 (void)fclose(fp); 514 } else if (cvs_cmdop != CVS_OP_CHECKOUT) 515 fatal("%s is missing", fpath); 516 break; 517 } 518 } 519 520 void 521 cvs_mkadmin(const char *path, const char *root, const char *repo, 522 char *tag, char *date) 523 { 524 FILE *fp; 525 int fd; 526 char buf[MAXPATHLEN]; 527 528 if (cvs_server_active == 0) 529 cvs_log(LP_TRACE, "cvs_mkadmin(%s, %s, %s, %s, %s)", 530 path, root, repo, (tag != NULL) ? tag : "", 531 (date != NULL) ? date : ""); 532 533 (void)xsnprintf(buf, sizeof(buf), "%s/%s", path, CVS_PATH_CVSDIR); 534 535 if (mkdir(buf, 0755) == -1 && errno != EEXIST) 536 fatal("cvs_mkadmin: %s: %s", buf, strerror(errno)); 537 538 if (cvs_cmdop == CVS_OP_CHECKOUT || cvs_cmdop == CVS_OP_ADD || 539 (cvs_cmdop == CVS_OP_UPDATE && build_dirs == 1)) { 540 (void)xsnprintf(buf, sizeof(buf), "%s/%s", 541 path, CVS_PATH_ROOTSPEC); 542 543 if ((fp = fopen(buf, "w")) == NULL) 544 fatal("cvs_mkadmin: %s: %s", buf, strerror(errno)); 545 546 fprintf(fp, "%s\n", root); 547 (void)fclose(fp); 548 } 549 550 (void)xsnprintf(buf, sizeof(buf), "%s/%s", path, CVS_PATH_REPOSITORY); 551 552 if ((fp = fopen(buf, "w")) == NULL) 553 fatal("cvs_mkadmin: %s: %s", buf, strerror(errno)); 554 555 fprintf(fp, "%s\n", repo); 556 (void)fclose(fp); 557 558 cvs_write_tagfile(path, tag, date); 559 560 (void)xsnprintf(buf, sizeof(buf), "%s/%s", path, CVS_PATH_ENTRIES); 561 562 if ((fd = open(buf, O_WRONLY|O_CREAT|O_EXCL, 0666 & ~cvs_umask)) 563 == -1) { 564 if (errno == EEXIST) 565 return; 566 fatal("cvs_mkadmin: %s: %s", buf, strerror(errno)); 567 } 568 569 if (atomicio(vwrite, fd, "D\n", 2) != 2) 570 fatal("cvs_mkadmin: %s", strerror(errno)); 571 close(fd); 572 } 573 574 void 575 cvs_mkpath(const char *path, char *tag) 576 { 577 CVSENTRIES *ent; 578 FILE *fp; 579 size_t len; 580 char *entry, sticky[CVS_REV_BUFSZ]; 581 char *sp, *dp, *dir, *p, rpath[MAXPATHLEN], repo[MAXPATHLEN]; 582 583 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL || 584 cvs_server_active == 1) 585 cvs_validate_directory(path); 586 587 dir = xstrdup(path); 588 589 STRIP_SLASH(dir); 590 591 if (cvs_server_active == 0) 592 cvs_log(LP_TRACE, "cvs_mkpath(%s)", dir); 593 594 repo[0] = '\0'; 595 rpath[0] = '\0'; 596 597 if (cvs_cmdop == CVS_OP_UPDATE || cvs_cmdop == CVS_OP_COMMIT) { 598 if ((fp = fopen(CVS_PATH_REPOSITORY, "r")) != NULL) { 599 if ((fgets(repo, sizeof(repo), fp)) == NULL) 600 fatal("cvs_mkpath: bad repository file"); 601 repo[strcspn(repo, "\n")] = '\0'; 602 (void)fclose(fp); 603 } 604 } 605 606 for (sp = dir; sp != NULL; sp = dp) { 607 dp = strchr(sp, '/'); 608 if (dp != NULL) 609 *(dp++) = '\0'; 610 611 if (sp == dir && module_repo_root != NULL) { 612 len = strlcpy(repo, module_repo_root, sizeof(repo)); 613 if (len >= (int)sizeof(repo)) 614 fatal("cvs_mkpath: overflow"); 615 } else if (strcmp(sp, ".")) { 616 if (repo[0] != '\0') { 617 len = strlcat(repo, "/", sizeof(repo)); 618 if (len >= (int)sizeof(repo)) 619 fatal("cvs_mkpath: overflow"); 620 } 621 622 len = strlcat(repo, sp, sizeof(repo)); 623 if (len >= (int)sizeof(repo)) 624 fatal("cvs_mkpath: overflow"); 625 } 626 627 if (rpath[0] != '\0') { 628 len = strlcat(rpath, "/", sizeof(rpath)); 629 if (len >= (int)sizeof(rpath)) 630 fatal("cvs_mkpath: overflow"); 631 } 632 633 len = strlcat(rpath, sp, sizeof(rpath)); 634 if (len >= (int)sizeof(rpath)) 635 fatal("cvs_mkpath: overflow"); 636 637 if (mkdir(rpath, 0755) == -1 && errno != EEXIST) 638 fatal("cvs_mkpath: %s: %s", rpath, strerror(errno)); 639 640 if (cvs_cmdop == CVS_OP_EXPORT && !cvs_server_active) 641 continue; 642 643 cvs_mkadmin(rpath, current_cvsroot->cr_str, repo, 644 tag, NULL); 645 646 if (dp != NULL) { 647 if ((p = strchr(dp, '/')) != NULL) 648 *p = '\0'; 649 650 entry = xmalloc(CVS_ENT_MAXLINELEN); 651 cvs_ent_line_str(dp, NULL, NULL, NULL, NULL, 1, 0, 652 entry, CVS_ENT_MAXLINELEN); 653 654 ent = cvs_ent_open(rpath); 655 cvs_ent_add(ent, entry); 656 cvs_ent_close(ent, ENT_SYNC); 657 xfree(entry); 658 659 if (p != NULL) 660 *p = '/'; 661 } 662 663 if (cvs_server_active == 1 && strcmp(rpath, ".")) { 664 if (tag != NULL) { 665 (void)xsnprintf(sticky, sizeof(sticky), 666 "T%s", tag); 667 cvs_server_set_sticky(rpath, sticky); 668 } 669 } 670 } 671 672 xfree(dir); 673 } 674 675 /* 676 * Split the contents of a file into a list of lines. 677 */ 678 struct cvs_lines * 679 cvs_splitlines(u_char *data, size_t len) 680 { 681 u_char *p, *c; 682 size_t i, tlen; 683 struct cvs_lines *lines; 684 struct cvs_line *lp; 685 686 lines = xcalloc(1, sizeof(*lines)); 687 TAILQ_INIT(&(lines->l_lines)); 688 689 lp = xcalloc(1, sizeof(*lp)); 690 TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list); 691 692 p = c = data; 693 for (i = 0; i < len; i++) { 694 if (*p == '\n' || (i == len - 1)) { 695 tlen = p - c + 1; 696 lp = xcalloc(1, sizeof(*lp)); 697 lp->l_line = c; 698 lp->l_len = tlen; 699 lp->l_lineno = ++(lines->l_nblines); 700 TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list); 701 c = p + 1; 702 } 703 p++; 704 } 705 706 return (lines); 707 } 708 709 void 710 cvs_freelines(struct cvs_lines *lines) 711 { 712 struct cvs_line *lp; 713 714 while ((lp = TAILQ_FIRST(&(lines->l_lines))) != NULL) { 715 TAILQ_REMOVE(&(lines->l_lines), lp, l_list); 716 if (lp->l_needsfree == 1) 717 xfree(lp->l_line); 718 xfree(lp); 719 } 720 721 xfree(lines); 722 } 723 724 /* 725 * cvs_strsplit() 726 * 727 * Split a string <str> of <sep>-separated values and allocate 728 * an argument vector for the values found. 729 */ 730 struct cvs_argvector * 731 cvs_strsplit(char *str, const char *sep) 732 { 733 struct cvs_argvector *av; 734 size_t i = 0; 735 char *cp, *p; 736 737 cp = xstrdup(str); 738 av = xmalloc(sizeof(*av)); 739 av->str = cp; 740 av->argv = xmalloc(sizeof(*(av->argv))); 741 742 while ((p = strsep(&cp, sep)) != NULL) { 743 av->argv[i++] = p; 744 av->argv = xrealloc(av->argv, 745 i + 1, sizeof(*(av->argv))); 746 } 747 av->argv[i] = NULL; 748 749 return (av); 750 } 751 752 /* 753 * cvs_argv_destroy() 754 * 755 * Free an argument vector previously allocated by cvs_strsplit(). 756 */ 757 void 758 cvs_argv_destroy(struct cvs_argvector *av) 759 { 760 xfree(av->str); 761 xfree(av->argv); 762 xfree(av); 763 } 764 765 u_int 766 cvs_revision_select(RCSFILE *file, char *range) 767 { 768 int i; 769 u_int nrev; 770 char *lstr, *rstr; 771 struct rcs_delta *rdp; 772 struct cvs_argvector *revargv, *revrange; 773 RCSNUM *lnum, *rnum; 774 775 nrev = 0; 776 lnum = rnum = NULL; 777 778 revargv = cvs_strsplit(range, ","); 779 for (i = 0; revargv->argv[i] != NULL; i++) { 780 revrange = cvs_strsplit(revargv->argv[i], ":"); 781 if (revrange->argv[0] == NULL) 782 fatal("invalid revision range: %s", revargv->argv[i]); 783 else if (revrange->argv[1] == NULL) 784 lstr = rstr = revrange->argv[0]; 785 else { 786 if (revrange->argv[2] != NULL) 787 fatal("invalid revision range: %s", 788 revargv->argv[i]); 789 790 lstr = revrange->argv[0]; 791 rstr = revrange->argv[1]; 792 793 if (strcmp(lstr, "") == 0) 794 lstr = NULL; 795 if (strcmp(rstr, "") == 0) 796 rstr = NULL; 797 } 798 799 if (lstr == NULL) 800 lstr = RCS_HEAD_INIT; 801 802 if ((lnum = rcs_translate_tag(lstr, file)) == NULL) 803 fatal("cvs_revision_select: could not translate tag `%s'", lstr); 804 805 if (rstr != NULL) { 806 if ((rnum = rcs_translate_tag(rstr, file)) == NULL) 807 fatal("cvs_revision_select: could not translate tag `%s'", rstr); 808 } else { 809 rnum = rcsnum_alloc(); 810 rcsnum_cpy(file->rf_head, rnum, 0); 811 } 812 813 cvs_argv_destroy(revrange); 814 815 TAILQ_FOREACH(rdp, &(file->rf_delta), rd_list) { 816 if (rcsnum_cmp(rdp->rd_num, lnum, 0) <= 0 && 817 rcsnum_cmp(rdp->rd_num, rnum, 0) >= 0 && 818 !(rdp->rd_flags & RCS_RD_SELECT)) { 819 rdp->rd_flags |= RCS_RD_SELECT; 820 nrev++; 821 } 822 } 823 824 rcsnum_free(lnum); 825 rcsnum_free(rnum); 826 } 827 828 cvs_argv_destroy(revargv); 829 830 return (nrev); 831 } 832 833 int 834 cvs_yesno(void) 835 { 836 int c, ret; 837 838 ret = 0; 839 840 fflush(stderr); 841 fflush(stdout); 842 843 if ((c = getchar()) != 'y' && c != 'Y') 844 ret = -1; 845 else 846 while (c != EOF && c != '\n') 847 c = getchar(); 848 849 return (ret); 850 } 851 852 /* 853 * cvs_exec() 854 * 855 * Execute <prog> and send <in> to the STDIN if not NULL. 856 * If <needwait> == 1, return the result of <prog>, 857 * else, 0 or -1 if an error occur. 858 */ 859 int 860 cvs_exec(const char *prog, const char *in, int needwait) 861 { 862 pid_t pid; 863 int fds[2], size, st; 864 char *argp[4] = { "sh", "-c", prog, NULL }; 865 866 if (in != NULL && pipe(fds) < 0) { 867 cvs_log(LP_ERR, "cvs_exec: pipe failed"); 868 return (-1); 869 } 870 871 if ((pid = fork()) == -1) { 872 cvs_log(LP_ERR, "cvs_exec: fork failed"); 873 return (-1); 874 } else if (pid == 0) { 875 if (in != NULL) { 876 close(fds[1]); 877 dup2(fds[0], STDIN_FILENO); 878 } 879 880 setenv("CVSROOT", current_cvsroot->cr_dir, 1); 881 execv(_PATH_BSHELL, argp); 882 cvs_log(LP_ERR, "cvs_exec: failed to run '%s'", prog); 883 _exit(127); 884 } 885 886 if (in != NULL) { 887 close(fds[0]); 888 size = strlen(in); 889 if (atomicio(vwrite, fds[1], in, size) != size) 890 cvs_log(LP_ERR, "cvs_exec: failed to write on STDIN"); 891 close(fds[1]); 892 } 893 894 if (needwait == 1) { 895 while (waitpid(pid, &st, 0) == -1) 896 ; 897 if (!WIFEXITED(st)) { 898 errno = EINTR; 899 return (-1); 900 } 901 return (WEXITSTATUS(st)); 902 } 903 904 return (0); 905 } 906