1 /* $OpenBSD: util.c,v 1.90 2006/07/09 01:47:20 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, int fds[3]) 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_ERR, "cannot remove `%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 ret = -1; 466 size_t len; 467 DIR *dirp; 468 struct dirent *ent; 469 char fpath[MAXPATHLEN]; 470 471 if (cvs_server_active == 0) 472 cvs_log(LP_TRACE, "cvs_rmdir(%s)", path); 473 474 if (cvs_noexec == 1) 475 return (0); 476 477 if ((dirp = opendir(path)) == NULL) { 478 cvs_log(LP_ERR, "failed to open '%s'", path); 479 return (-1); 480 } 481 482 while ((ent = readdir(dirp)) != NULL) { 483 if (!strcmp(ent->d_name, ".") || 484 !strcmp(ent->d_name, "..")) 485 continue; 486 487 len = cvs_path_cat(path, ent->d_name, fpath, sizeof(fpath)); 488 if (len >= sizeof(fpath)) 489 fatal("cvs_rmdir: path truncation"); 490 491 if (ent->d_type == DT_DIR) { 492 if (cvs_rmdir(fpath) == -1) 493 goto done; 494 } else if (cvs_unlink(fpath) == -1 && errno != ENOENT) 495 goto done; 496 } 497 498 499 if (rmdir(path) == -1 && errno != ENOENT) { 500 cvs_log(LP_ERR, "failed to remove '%s'", path); 501 goto done; 502 } 503 504 ret = 0; 505 done: 506 closedir(dirp); 507 return (ret); 508 } 509 510 /* 511 * cvs_path_cat() 512 * 513 * Concatenate the two paths <base> and <end> and store the generated path 514 * into the buffer <dst>, which can accept up to <dlen> bytes, including the 515 * NUL byte. The result is guaranteed to be NUL-terminated. 516 * Returns the number of bytes necessary to store the full resulting path, 517 * not including the NUL byte (a value equal to or larger than <dlen> 518 * indicates truncation). 519 */ 520 size_t 521 cvs_path_cat(const char *base, const char *end, char *dst, size_t dlen) 522 { 523 size_t len; 524 525 len = strlcpy(dst, base, dlen); 526 if (len >= dlen - 1) { 527 errno = ENAMETOOLONG; 528 fatal("overflow in cvs_path_cat"); 529 } else { 530 dst[len] = '/'; 531 dst[len + 1] = '\0'; 532 len = strlcat(dst, end, dlen); 533 if (len >= dlen) { 534 errno = ENAMETOOLONG; 535 cvs_log(LP_ERR, "%s", dst); 536 } 537 } 538 539 return (len); 540 } 541 542 /* 543 * a hack to mimic and thus match gnu cvs behaviour. 544 */ 545 time_t 546 cvs_hack_time(time_t oldtime, int togmt) 547 { 548 int l; 549 struct tm *t; 550 char tbuf[32]; 551 552 if (togmt == 1) { 553 t = gmtime(&oldtime); 554 if (t == NULL) 555 fatal("gmtime failed"); 556 557 return (mktime(t)); 558 } 559 560 t = localtime(&oldtime); 561 562 l = snprintf(tbuf, sizeof(tbuf), "%d/%d/%d GMT %d:%d:%d", 563 t->tm_mon + 1, t->tm_mday, t->tm_year + 1900, t->tm_hour, 564 t->tm_min, t->tm_sec); 565 if (l == -1 || l >= (int)sizeof(tbuf)) 566 fatal("cvs_hack_time: overflow"); 567 568 return (cvs_date_parse(tbuf)); 569 } 570 571 void 572 cvs_get_repository_path(const char *dir, char *dst, size_t len) 573 { 574 int l; 575 char buf[MAXPATHLEN]; 576 577 cvs_get_repository_name(dir, buf, sizeof(buf)); 578 l = snprintf(dst, len, "%s/%s", current_cvsroot->cr_dir, buf); 579 if (l == -1 || l >= (int)len) 580 fatal("cvs_get_repository_path: overflow"); 581 } 582 583 void 584 cvs_get_repository_name(const char *dir, char *dst, size_t len) 585 { 586 int l; 587 FILE *fp; 588 char *s, fpath[MAXPATHLEN]; 589 590 l = snprintf(fpath, sizeof(fpath), "%s/%s", dir, CVS_PATH_REPOSITORY); 591 if (l == -1 || l >= (int)sizeof(fpath)) 592 fatal("cvs_get_repository_name: overflow"); 593 594 if ((fp = fopen(fpath, "r")) != NULL) { 595 fgets(dst, len, fp); 596 597 if ((s = strchr(dst, '\n')) != NULL) 598 *s = '\0'; 599 600 (void)fclose(fp); 601 } else { 602 dst[0] = '\0'; 603 604 if (cvs_cmdop == CVS_OP_IMPORT) { 605 if (strlcpy(dst, import_repository, len) >= len) 606 fatal("cvs_get_repository_name: truncation"); 607 if (strlcat(dst, "/", len) >= len) 608 fatal("cvs_get_repository_name: truncation"); 609 610 if (strcmp(dir, ".")) { 611 if (strlcat(dst, dir, len) >= len) { 612 fatal("cvs_get_repository_name: " 613 "truncation"); 614 } 615 } 616 } else { 617 if (strlcat(dst, dir, len) >= len) 618 fatal("cvs_get_repository_name: truncation"); 619 } 620 } 621 } 622 623 void 624 cvs_mkadmin(const char *path, const char *root, const char *repo, 625 char *tag, char *date, int nb) 626 { 627 FILE *fp; 628 size_t len; 629 struct stat st; 630 char buf[MAXPATHLEN]; 631 632 if (cvs_server_active == 0) 633 cvs_log(LP_TRACE, "cvs_mkadmin(%s, %s, %s, %s, %s, %d)", 634 path, root, repo, (tag != NULL) ? tag : "", 635 (date != NULL) ? date : "", nb); 636 637 len = cvs_path_cat(path, CVS_PATH_CVSDIR, buf, sizeof(buf)); 638 if (len >= sizeof(buf)) 639 fatal("cvs_mkadmin: truncation"); 640 641 if (stat(buf, &st) != -1) 642 return; 643 644 if (mkdir(buf, 0755) == -1 && errno != EEXIST) 645 fatal("cvs_mkadmin: %s: %s", buf, strerror(errno)); 646 647 len = cvs_path_cat(path, CVS_PATH_ROOTSPEC, buf, sizeof(buf)); 648 if (len >= sizeof(buf)) 649 fatal("cvs_mkadmin: truncation"); 650 651 if ((fp = fopen(buf, "w")) == NULL) 652 fatal("cvs_mkadmin: %s: %s", buf, strerror(errno)); 653 654 fprintf(fp, "%s\n", root); 655 (void)fclose(fp); 656 657 len = cvs_path_cat(path, CVS_PATH_REPOSITORY, buf, sizeof(buf)); 658 if (len >= sizeof(buf)) 659 fatal("cvs_mkadmin: truncation"); 660 661 if ((fp = fopen(buf, "w")) == NULL) 662 fatal("cvs_mkadmin: %s: %s", buf, strerror(errno)); 663 664 fprintf(fp, "%s\n", repo); 665 (void)fclose(fp); 666 667 len = cvs_path_cat(path, CVS_PATH_ENTRIES, buf, sizeof(buf)); 668 if (len >= sizeof(buf)) 669 fatal("cvs_mkadmin: truncation"); 670 671 if ((fp = fopen(buf, "w")) == NULL) 672 fatal("cvs_mkadmin: %s: %s", buf, strerror(errno)); 673 (void)fclose(fp); 674 675 cvs_write_tagfile(path, tag, date, nb); 676 } 677 678 void 679 cvs_mkpath(const char *path) 680 { 681 FILE *fp; 682 size_t len; 683 char *sp, *dp, *dir, rpath[MAXPATHLEN], repo[MAXPATHLEN]; 684 685 dir = xstrdup(path); 686 687 STRIP_SLASH(dir); 688 689 if (cvs_server_active == 0) 690 cvs_log(LP_TRACE, "cvs_mkpath(%s)", dir); 691 692 repo[0] = '\0'; 693 rpath[0] = '\0'; 694 695 if (cvs_cmdop == CVS_OP_UPDATE) { 696 if ((fp = fopen(CVS_PATH_REPOSITORY, "r")) != NULL) { 697 fgets(repo, sizeof(repo), fp); 698 if (repo[strlen(repo) - 1] == '\n') 699 repo[strlen(repo) - 1] = '\0'; 700 (void)fclose(fp); 701 } 702 } 703 704 for (sp = dir; sp != NULL; sp = dp) { 705 dp = strchr(sp, '/'); 706 if (dp != NULL) 707 *(dp++) = '\0'; 708 709 if (repo[0] != '\0') { 710 len = strlcat(repo, "/", sizeof(repo)); 711 if (len >= (int)sizeof(repo)) 712 fatal("cvs_mkpath: overflow"); 713 } 714 715 len = strlcat(repo, sp, sizeof(repo)); 716 if (len >= (int)sizeof(repo)) 717 fatal("cvs_mkpath: overflow"); 718 719 if (rpath[0] != '\0') { 720 len = strlcat(rpath, "/", sizeof(rpath)); 721 if (len >= (int)sizeof(rpath)) 722 fatal("cvs_mkpath: overflow"); 723 } 724 725 len = strlcat(rpath, sp, sizeof(rpath)); 726 if (len >= (int)sizeof(rpath)) 727 fatal("cvs_mkpath: overflow"); 728 729 if (mkdir(rpath, 0755) == -1 && errno != EEXIST) 730 fatal("cvs_mkpath: %s: %s", rpath, strerror(errno)); 731 732 cvs_mkadmin(rpath, current_cvsroot->cr_dir, repo, 733 NULL, NULL, 0); 734 } 735 736 xfree(dir); 737 } 738 739 /* 740 * Split the contents of a file into a list of lines. 741 */ 742 struct cvs_lines * 743 cvs_splitlines(const char *fcont) 744 { 745 char *dcp; 746 struct cvs_lines *lines; 747 struct cvs_line *lp; 748 749 lines = xmalloc(sizeof(*lines)); 750 TAILQ_INIT(&(lines->l_lines)); 751 lines->l_nblines = 0; 752 lines->l_data = xstrdup(fcont); 753 754 lp = xmalloc(sizeof(*lp)); 755 lp->l_line = NULL; 756 lp->l_lineno = 0; 757 TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list); 758 759 for (dcp = lines->l_data; *dcp != '\0';) { 760 lp = xmalloc(sizeof(*lp)); 761 lp->l_line = dcp; 762 lp->l_lineno = ++(lines->l_nblines); 763 TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list); 764 765 dcp = strchr(dcp, '\n'); 766 if (dcp == NULL) 767 break; 768 *(dcp++) = '\0'; 769 } 770 771 return (lines); 772 } 773 774 void 775 cvs_freelines(struct cvs_lines *lines) 776 { 777 struct cvs_line *lp; 778 779 while ((lp = TAILQ_FIRST(&(lines->l_lines))) != NULL) { 780 TAILQ_REMOVE(&(lines->l_lines), lp, l_list); 781 xfree(lp); 782 } 783 784 xfree(lines->l_data); 785 xfree(lines); 786 } 787 788 BUF * 789 cvs_patchfile(const char *data, const char *patch, 790 int (*p)(struct cvs_lines *, struct cvs_lines *)) 791 { 792 struct cvs_lines *dlines, *plines; 793 struct cvs_line *lp; 794 size_t len; 795 int lineno; 796 BUF *res; 797 798 len = strlen(data); 799 800 if ((dlines = cvs_splitlines(data)) == NULL) 801 return (NULL); 802 803 if ((plines = cvs_splitlines(patch)) == NULL) 804 return (NULL); 805 806 if (p(dlines, plines) < 0) { 807 cvs_freelines(dlines); 808 cvs_freelines(plines); 809 return (NULL); 810 } 811 812 lineno = 0; 813 res = cvs_buf_alloc(len, BUF_AUTOEXT); 814 TAILQ_FOREACH(lp, &dlines->l_lines, l_list) { 815 if (lineno != 0) 816 cvs_buf_fappend(res, "%s\n", lp->l_line); 817 lineno++; 818 } 819 820 cvs_freelines(dlines); 821 cvs_freelines(plines); 822 return (res); 823 } 824 825 /* 826 * cvs_strsplit() 827 * 828 * Split a string <str> of <sep>-separated values and allocate 829 * an argument vector for the values found. 830 */ 831 struct cvs_argvector * 832 cvs_strsplit(char *str, const char *sep) 833 { 834 struct cvs_argvector *av; 835 size_t i = 0; 836 char **nargv; 837 char *cp, *p; 838 839 cp = xstrdup(str); 840 av = xmalloc(sizeof(*av)); 841 av->str = cp; 842 av->argv = xcalloc(i + 1, sizeof(*(av->argv))); 843 844 while ((p = strsep(&cp, sep)) != NULL) { 845 av->argv[i++] = p; 846 nargv = xrealloc(av->argv, 847 i + 1, sizeof(*(av->argv))); 848 av->argv = nargv; 849 } 850 av->argv[i] = NULL; 851 852 return (av); 853 } 854 855 /* 856 * cvs_argv_destroy() 857 * 858 * Free an argument vector previously allocated by cvs_strsplit(). 859 */ 860 void 861 cvs_argv_destroy(struct cvs_argvector *av) 862 { 863 xfree(av->str); 864 xfree(av->argv); 865 xfree(av); 866 } 867 868 u_int 869 cvs_revision_select(RCSFILE *file, char *range) 870 { 871 int i; 872 u_int nrev; 873 char *lstr, *rstr; 874 struct rcs_delta *rdp; 875 struct cvs_argvector *revargv, *revrange; 876 RCSNUM *lnum, *rnum; 877 878 nrev = 0; 879 lnum = rnum = NULL; 880 881 revargv = cvs_strsplit(range, ","); 882 for (i = 0; revargv->argv[i] != NULL; i++) { 883 revrange = cvs_strsplit(revargv->argv[i], ":"); 884 if (revrange->argv[0] == NULL) 885 fatal("invalid revision range: %s", revargv->argv[i]); 886 else if (revrange->argv[1] == NULL) 887 lstr = rstr = revrange->argv[0]; 888 else { 889 if (revrange->argv[2] != NULL) 890 fatal("invalid revision range: %s", 891 revargv->argv[i]); 892 893 lstr = revrange->argv[0]; 894 rstr = revrange->argv[1]; 895 896 if (strcmp(lstr, "") == 0) 897 lstr = NULL; 898 if (strcmp(rstr, "") == 0) 899 rstr = NULL; 900 } 901 902 if (lstr == NULL) 903 lstr = RCS_HEAD_INIT; 904 905 lnum = rcs_translate_tag(lstr, file); 906 907 if (rstr != NULL) { 908 rnum = rcs_translate_tag(rstr, file); 909 } else { 910 rnum = rcsnum_alloc(); 911 rcsnum_cpy(file->rf_head, rnum, 0); 912 } 913 914 cvs_argv_destroy(revrange); 915 916 TAILQ_FOREACH(rdp, &(file->rf_delta), rd_list) { 917 if (rcsnum_cmp(rdp->rd_num, lnum, 0) <= 0 && 918 rcsnum_cmp(rdp->rd_num, rnum, 0) >= 0 && 919 !(rdp->rd_flags & RCS_RD_SELECT)) { 920 rdp->rd_flags |= RCS_RD_SELECT; 921 nrev++; 922 } 923 } 924 } 925 926 cvs_argv_destroy(revargv); 927 928 if (lnum != NULL) 929 rcsnum_free(lnum); 930 if (rnum != NULL) 931 rcsnum_free(rnum); 932 933 return (nrev); 934 } 935