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