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