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