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