1 /* $NetBSD: ukfs.c,v 1.23 2009/04/06 03:27:39 pooka Exp $ */ 2 3 /* 4 * Copyright (c) 2007, 2008 Antti Kantee. All Rights Reserved. 5 * 6 * Development of this software was supported by the 7 * Finnish Cultural Foundation. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 19 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31 /* 32 * This library enables access to files systems directly without 33 * involving system calls. 34 */ 35 36 #ifdef __linux__ 37 #define _XOPEN_SOURCE 500 38 #define _BSD_SOURCE 39 #define _FILE_OFFSET_BITS 64 40 #endif 41 42 #include <sys/param.h> 43 #include <sys/queue.h> 44 #include <sys/stat.h> 45 #include <sys/sysctl.h> 46 #include <sys/mount.h> 47 48 #include <assert.h> 49 #include <dirent.h> 50 #include <dlfcn.h> 51 #include <err.h> 52 #include <errno.h> 53 #include <fcntl.h> 54 #include <pthread.h> 55 #include <stdio.h> 56 #include <stdlib.h> 57 #include <string.h> 58 #include <unistd.h> 59 #include <stdint.h> 60 61 #include <rump/ukfs.h> 62 63 #include <rump/rump.h> 64 #include <rump/rump_syscalls.h> 65 66 #define UKFS_MODE_DEFAULT 0555 67 68 struct ukfs { 69 struct mount *ukfs_mp; 70 struct vnode *ukfs_rvp; 71 72 pthread_spinlock_t ukfs_spin; 73 pid_t ukfs_nextpid; 74 struct vnode *ukfs_cdir; 75 int ukfs_devfd; 76 }; 77 78 struct mount * 79 ukfs_getmp(struct ukfs *ukfs) 80 { 81 82 return ukfs->ukfs_mp; 83 } 84 85 struct vnode * 86 ukfs_getrvp(struct ukfs *ukfs) 87 { 88 struct vnode *rvp; 89 90 rvp = ukfs->ukfs_rvp; 91 rump_vp_incref(rvp); 92 93 return rvp; 94 } 95 96 #ifdef DONT_WANT_PTHREAD_LINKAGE 97 #define pthread_spin_lock(a) 98 #define pthread_spin_unlock(a) 99 #define pthread_spin_init(a,b) 100 #define pthread_spin_destroy(a) 101 #endif 102 103 static pid_t 104 nextpid(struct ukfs *ukfs) 105 { 106 pid_t npid; 107 108 pthread_spin_lock(&ukfs->ukfs_spin); 109 if (ukfs->ukfs_nextpid == 0) 110 ukfs->ukfs_nextpid++; 111 npid = ukfs->ukfs_nextpid++; 112 pthread_spin_unlock(&ukfs->ukfs_spin); 113 114 return npid; 115 } 116 117 static void 118 precall(struct ukfs *ukfs) 119 { 120 struct vnode *rvp, *cvp; 121 122 rump_setup_curlwp(nextpid(ukfs), 1, 1); 123 rvp = ukfs_getrvp(ukfs); 124 pthread_spin_lock(&ukfs->ukfs_spin); 125 cvp = ukfs->ukfs_cdir; 126 pthread_spin_unlock(&ukfs->ukfs_spin); 127 rump_rcvp_set(rvp, cvp); /* takes refs */ 128 rump_vp_rele(rvp); 129 } 130 131 static void 132 postcall(struct ukfs *ukfs) 133 { 134 struct vnode *rvp; 135 136 rvp = ukfs_getrvp(ukfs); 137 rump_rcvp_set(NULL, rvp); 138 rump_vp_rele(rvp); 139 rump_clear_curlwp(); 140 } 141 142 int 143 _ukfs_init(int version) 144 { 145 int rv; 146 147 if (version != UKFS_VERSION) { 148 printf("incompatible ukfs version, %d vs. %d\n", 149 version, UKFS_VERSION); 150 errno = EPROGMISMATCH; 151 return -1; 152 } 153 154 if ((rv = rump_init()) != 0) { 155 errno = rv; 156 return -1; 157 } 158 159 return 0; 160 } 161 162 struct ukfs * 163 ukfs_mount(const char *vfsname, const char *devpath, const char *mountpath, 164 int mntflags, void *arg, size_t alen) 165 { 166 struct stat sb; 167 struct ukfs *fs = NULL; 168 struct vfsops *vfsops; 169 struct mount *mp = NULL; 170 int rv = 0, devfd = -1, rdonly; 171 172 vfsops = rump_vfs_getopsbyname(vfsname); 173 if (vfsops == NULL) { 174 rv = ENODEV; 175 goto out; 176 } 177 178 /* 179 * Try open and lock the device. if we can't open it, assume 180 * it's a file system which doesn't use a real device and let 181 * it slide. The mount will fail anyway if the fs requires a 182 * device. 183 * 184 * XXX: strictly speaking this is not 100% correct, as virtual 185 * file systems can use a device path which does exist and can 186 * be opened. E.g. tmpfs should be mountable multiple times 187 * with "device" path "/swap", but now isn't. But I think the 188 * chances are so low that it's currently acceptable to let 189 * this one slip. 190 */ 191 rdonly = mntflags & MNT_RDONLY; 192 devfd = open(devpath, rdonly ? O_RDONLY : O_RDWR); 193 if (devfd != -1) { 194 if (fstat(devfd, &sb) == -1) { 195 close(devfd); 196 devfd = -1; 197 rv = errno; 198 goto out; 199 } 200 201 /* 202 * We do this only for non-block device since the 203 * (NetBSD) kernel allows block device open only once. 204 */ 205 if (!S_ISBLK(sb.st_mode)) { 206 if (flock(devfd, LOCK_NB | (rdonly ? LOCK_SH:LOCK_EX)) 207 == -1) { 208 warnx("ukfs_mount: cannot get %s lock on " 209 "device", rdonly ? "shared" : "exclusive"); 210 close(devfd); 211 devfd = -1; 212 rv = errno; 213 goto out; 214 } 215 } else { 216 close(devfd); 217 devfd = -1; 218 } 219 } 220 221 fs = malloc(sizeof(struct ukfs)); 222 if (fs == NULL) { 223 rv = ENOMEM; 224 goto out; 225 } 226 memset(fs, 0, sizeof(struct ukfs)); 227 mp = rump_mnt_init(vfsops, mntflags); 228 229 rump_fakeblk_register(devpath); 230 rv = rump_mnt_mount(mp, mountpath, arg, &alen); 231 rump_fakeblk_deregister(devpath); 232 if (rv) { 233 goto out; 234 } 235 rv = rump_vfs_root(mp, &fs->ukfs_rvp, 0); 236 if (rv) { 237 goto out; 238 } 239 fs->ukfs_cdir = ukfs_getrvp(fs); 240 241 fs->ukfs_mp = mp; 242 pthread_spin_init(&fs->ukfs_spin, PTHREAD_PROCESS_SHARED); 243 fs->ukfs_devfd = devfd; 244 assert(rv == 0); 245 246 out: 247 if (rv) { 248 if (mp) 249 rump_mnt_destroy(mp); 250 if (fs) 251 free(fs); 252 errno = rv; 253 fs = NULL; 254 if (devfd != -1) { 255 flock(devfd, LOCK_UN); 256 close(devfd); 257 } 258 } 259 260 return fs; 261 } 262 263 void 264 ukfs_release(struct ukfs *fs, int flags) 265 { 266 int rv; 267 268 if ((flags & UKFS_RELFLAG_NOUNMOUNT) == 0) { 269 kauth_cred_t cred; 270 271 rump_vp_rele(fs->ukfs_cdir); 272 rump_vp_rele(fs->ukfs_rvp); 273 cred = rump_cred_suserget(); 274 rv = rump_vfs_sync(fs->ukfs_mp, 1, cred); 275 rump_cred_suserput(cred); 276 rump_vp_recycle_nokidding(ukfs_getrvp(fs)); 277 rv |= rump_vfs_unmount(fs->ukfs_mp, 0); 278 assert(rv == 0); 279 } 280 281 rump_vfs_syncwait(fs->ukfs_mp); 282 rump_mnt_destroy(fs->ukfs_mp); 283 284 pthread_spin_destroy(&fs->ukfs_spin); 285 if (fs->ukfs_devfd != -1) { 286 flock(fs->ukfs_devfd, LOCK_UN); 287 close(fs->ukfs_devfd); 288 } 289 free(fs); 290 } 291 292 #define STDCALL(ukfs, thecall) \ 293 int rv = 0; \ 294 \ 295 precall(ukfs); \ 296 rv = thecall; \ 297 postcall(ukfs); \ 298 return rv; 299 300 int 301 ukfs_getdents(struct ukfs *ukfs, const char *dirname, off_t *off, 302 uint8_t *buf, size_t bufsize) 303 { 304 struct uio *uio; 305 struct vnode *vp; 306 size_t resid; 307 kauth_cred_t cred; 308 int rv, eofflag; 309 310 precall(ukfs); 311 rv = rump_namei(RUMP_NAMEI_LOOKUP, RUMP_NAMEI_LOCKLEAF, dirname, 312 NULL, &vp, NULL); 313 postcall(ukfs); 314 if (rv) 315 goto out; 316 317 uio = rump_uio_setup(buf, bufsize, *off, RUMPUIO_READ); 318 cred = rump_cred_suserget(); 319 rv = RUMP_VOP_READDIR(vp, uio, cred, &eofflag, NULL, NULL); 320 rump_cred_suserput(cred); 321 RUMP_VOP_UNLOCK(vp, 0); 322 *off = rump_uio_getoff(uio); 323 resid = rump_uio_free(uio); 324 rump_vp_rele(vp); 325 326 out: 327 if (rv) { 328 errno = rv; 329 return -1; 330 } 331 332 /* LINTED: not totally correct return type, but follows syscall */ 333 return bufsize - resid; 334 } 335 336 ssize_t 337 ukfs_read(struct ukfs *ukfs, const char *filename, off_t off, 338 uint8_t *buf, size_t bufsize) 339 { 340 int fd; 341 ssize_t xfer = -1; /* XXXgcc */ 342 343 precall(ukfs); 344 fd = rump_sys_open(filename, RUMP_O_RDONLY, 0); 345 if (fd == -1) 346 goto out; 347 348 xfer = rump_sys_pread(fd, buf, bufsize, 0, off); 349 rump_sys_close(fd); 350 351 out: 352 postcall(ukfs); 353 if (fd == -1) { 354 return -1; 355 } 356 return xfer; 357 } 358 359 ssize_t 360 ukfs_write(struct ukfs *ukfs, const char *filename, off_t off, 361 uint8_t *buf, size_t bufsize) 362 { 363 int fd; 364 ssize_t xfer = -1; /* XXXgcc */ 365 366 precall(ukfs); 367 fd = rump_sys_open(filename, RUMP_O_WRONLY, 0); 368 if (fd == -1) 369 goto out; 370 371 /* write and commit */ 372 xfer = rump_sys_pwrite(fd, buf, bufsize, 0, off); 373 if (xfer > 0) 374 rump_sys_fsync(fd); 375 376 rump_sys_close(fd); 377 378 out: 379 postcall(ukfs); 380 if (fd == -1) { 381 return -1; 382 } 383 return xfer; 384 } 385 386 int 387 ukfs_create(struct ukfs *ukfs, const char *filename, mode_t mode) 388 { 389 int fd; 390 391 precall(ukfs); 392 fd = rump_sys_open(filename, RUMP_O_WRONLY | RUMP_O_CREAT, mode); 393 if (fd == -1) 394 return -1; 395 rump_sys_close(fd); 396 397 postcall(ukfs); 398 return 0; 399 } 400 401 int 402 ukfs_mknod(struct ukfs *ukfs, const char *path, mode_t mode, dev_t dev) 403 { 404 405 STDCALL(ukfs, rump_sys_mknod(path, mode, dev)); 406 } 407 408 int 409 ukfs_mkfifo(struct ukfs *ukfs, const char *path, mode_t mode) 410 { 411 412 STDCALL(ukfs, rump_sys_mkfifo(path, mode)); 413 } 414 415 int 416 ukfs_mkdir(struct ukfs *ukfs, const char *filename, mode_t mode) 417 { 418 419 STDCALL(ukfs, rump_sys_mkdir(filename, mode)); 420 } 421 422 int 423 ukfs_remove(struct ukfs *ukfs, const char *filename) 424 { 425 426 STDCALL(ukfs, rump_sys_unlink(filename)); 427 } 428 429 int 430 ukfs_rmdir(struct ukfs *ukfs, const char *filename) 431 { 432 433 STDCALL(ukfs, rump_sys_rmdir(filename)); 434 } 435 436 int 437 ukfs_link(struct ukfs *ukfs, const char *filename, const char *f_create) 438 { 439 440 STDCALL(ukfs, rump_sys_link(filename, f_create)); 441 } 442 443 int 444 ukfs_symlink(struct ukfs *ukfs, const char *filename, const char *linkname) 445 { 446 447 STDCALL(ukfs, rump_sys_symlink(filename, linkname)); 448 } 449 450 ssize_t 451 ukfs_readlink(struct ukfs *ukfs, const char *filename, 452 char *linkbuf, size_t buflen) 453 { 454 ssize_t rv; 455 456 precall(ukfs); 457 rv = rump_sys_readlink(filename, linkbuf, buflen); 458 postcall(ukfs); 459 return rv; 460 } 461 462 int 463 ukfs_rename(struct ukfs *ukfs, const char *from, const char *to) 464 { 465 466 STDCALL(ukfs, rump_sys_rename(from, to)); 467 } 468 469 int 470 ukfs_chdir(struct ukfs *ukfs, const char *path) 471 { 472 struct vnode *newvp, *oldvp; 473 int rv; 474 475 precall(ukfs); 476 rv = rump_sys_chdir(path); 477 if (rv == -1) 478 goto out; 479 480 newvp = rump_cdir_get(); 481 pthread_spin_lock(&ukfs->ukfs_spin); 482 oldvp = ukfs->ukfs_cdir; 483 ukfs->ukfs_cdir = newvp; 484 pthread_spin_unlock(&ukfs->ukfs_spin); 485 if (oldvp) 486 rump_vp_rele(oldvp); 487 488 out: 489 postcall(ukfs); 490 return rv; 491 } 492 493 int 494 ukfs_stat(struct ukfs *ukfs, const char *filename, struct stat *file_stat) 495 { 496 497 STDCALL(ukfs, rump_sys_stat(filename, file_stat)); 498 } 499 500 int 501 ukfs_lstat(struct ukfs *ukfs, const char *filename, struct stat *file_stat) 502 { 503 504 STDCALL(ukfs, rump_sys_lstat(filename, file_stat)); 505 } 506 507 int 508 ukfs_chmod(struct ukfs *ukfs, const char *filename, mode_t mode) 509 { 510 511 STDCALL(ukfs, rump_sys_chmod(filename, mode)); 512 } 513 514 int 515 ukfs_lchmod(struct ukfs *ukfs, const char *filename, mode_t mode) 516 { 517 518 STDCALL(ukfs, rump_sys_lchmod(filename, mode)); 519 } 520 521 int 522 ukfs_chown(struct ukfs *ukfs, const char *filename, uid_t uid, gid_t gid) 523 { 524 525 STDCALL(ukfs, rump_sys_chown(filename, uid, gid)); 526 } 527 528 int 529 ukfs_lchown(struct ukfs *ukfs, const char *filename, uid_t uid, gid_t gid) 530 { 531 532 STDCALL(ukfs, rump_sys_lchown(filename, uid, gid)); 533 } 534 535 int 536 ukfs_chflags(struct ukfs *ukfs, const char *filename, u_long flags) 537 { 538 539 STDCALL(ukfs, rump_sys_chflags(filename, flags)); 540 } 541 542 int 543 ukfs_lchflags(struct ukfs *ukfs, const char *filename, u_long flags) 544 { 545 546 STDCALL(ukfs, rump_sys_lchflags(filename, flags)); 547 } 548 549 int 550 ukfs_utimes(struct ukfs *ukfs, const char *filename, const struct timeval *tptr) 551 { 552 553 STDCALL(ukfs, rump_sys_utimes(filename, tptr)); 554 } 555 556 int 557 ukfs_lutimes(struct ukfs *ukfs, const char *filename, 558 const struct timeval *tptr) 559 { 560 561 STDCALL(ukfs, rump_sys_lutimes(filename, tptr)); 562 } 563 564 /* 565 * Dynamic module support 566 */ 567 568 /* load one library */ 569 570 /* 571 * XXX: the dlerror stuff isn't really threadsafe, but then again I 572 * can't protect against other threads calling dl*() outside of ukfs, 573 * so just live with it being flimsy 574 */ 575 #define UFSLIB "librumpfs_ufs.so" 576 int 577 ukfs_modload(const char *fname) 578 { 579 void *handle, *thesym; 580 struct stat sb; 581 const char *p; 582 int error; 583 584 if (stat(fname, &sb) == -1) 585 return -1; 586 587 handle = dlopen(fname, RTLD_GLOBAL); 588 if (handle == NULL) { 589 const char *dlmsg = dlerror(); 590 if (strstr(dlmsg, "Undefined symbol")) 591 return 0; 592 warnx("dlopen %s failed: %s\n", fname, dlmsg); 593 /* XXXerrno */ 594 return -1; 595 } 596 597 /* 598 * XXX: the ufs module is not loaded in the same fashion as the 599 * others. But we can't do dlclose() for it, since that would 600 * lead to not being able to load ffs/ext2fs/lfs. Hence hardcode 601 * and kludge around the issue for now. But this should really 602 * be fixed by fixing sys/ufs/ufs to be a kernel module. 603 */ 604 if ((p = strrchr(fname, '/')) != NULL) 605 p++; 606 else 607 p = fname; 608 if (strcmp(p, UFSLIB) == 0) 609 return 1; 610 611 thesym = dlsym(handle, "__start_link_set_modules"); 612 if (thesym) { 613 error = rump_module_load(thesym); 614 if (error) 615 goto errclose; 616 return 1; 617 } 618 error = EINVAL; 619 620 errclose: 621 dlclose(handle); 622 errno = error; 623 return -1; 624 } 625 626 struct loadfail { 627 char *pname; 628 629 LIST_ENTRY(loadfail) entries; 630 }; 631 632 #define RUMPFSMOD_PREFIX "librumpfs_" 633 #define RUMPFSMOD_SUFFIX ".so" 634 635 int 636 ukfs_modload_dir(const char *dir) 637 { 638 char nbuf[MAXPATHLEN+1], *p; 639 struct dirent entry, *result; 640 DIR *libdir; 641 struct loadfail *lf, *nlf; 642 int error, nloaded = 0, redo; 643 LIST_HEAD(, loadfail) lfs; 644 645 libdir = opendir(dir); 646 if (libdir == NULL) 647 return -1; 648 649 LIST_INIT(&lfs); 650 for (;;) { 651 if ((error = readdir_r(libdir, &entry, &result)) != 0) 652 break; 653 if (!result) 654 break; 655 if (strncmp(result->d_name, RUMPFSMOD_PREFIX, 656 strlen(RUMPFSMOD_PREFIX)) != 0) 657 continue; 658 if (((p = strstr(result->d_name, RUMPFSMOD_SUFFIX)) == NULL) 659 || strlen(p) != strlen(RUMPFSMOD_SUFFIX)) 660 continue; 661 strlcpy(nbuf, dir, sizeof(nbuf)); 662 strlcat(nbuf, "/", sizeof(nbuf)); 663 strlcat(nbuf, result->d_name, sizeof(nbuf)); 664 switch (ukfs_modload(nbuf)) { 665 case 0: 666 lf = malloc(sizeof(*lf)); 667 if (lf == NULL) { 668 error = ENOMEM; 669 break; 670 } 671 lf->pname = strdup(nbuf); 672 if (lf->pname == NULL) { 673 free(lf); 674 error = ENOMEM; 675 break; 676 } 677 LIST_INSERT_HEAD(&lfs, lf, entries); 678 break; 679 case 1: 680 nloaded++; 681 break; 682 default: 683 /* ignore errors */ 684 break; 685 } 686 } 687 closedir(libdir); 688 if (error && nloaded != 0) 689 error = 0; 690 691 /* 692 * El-cheapo dependency calculator. Just try to load the 693 * modules n times in a loop 694 */ 695 for (redo = 1; redo;) { 696 redo = 0; 697 nlf = LIST_FIRST(&lfs); 698 while ((lf = nlf) != NULL) { 699 nlf = LIST_NEXT(lf, entries); 700 if (ukfs_modload(lf->pname) == 1) { 701 nloaded++; 702 redo = 1; 703 LIST_REMOVE(lf, entries); 704 free(lf->pname); 705 free(lf); 706 } 707 } 708 } 709 710 while ((lf = LIST_FIRST(&lfs)) != NULL) { 711 LIST_REMOVE(lf, entries); 712 free(lf->pname); 713 free(lf); 714 } 715 716 if (error && nloaded == 0) { 717 errno = error; 718 return -1; 719 } 720 721 return nloaded; 722 } 723 724 /* XXX: this code uses definitions from NetBSD, needs rumpdefs */ 725 ssize_t 726 ukfs_vfstypes(char *buf, size_t buflen) 727 { 728 int mib[3]; 729 struct sysctlnode q, ans[128]; 730 size_t alen; 731 int i; 732 733 mib[0] = CTL_VFS; 734 mib[1] = VFS_GENERIC; 735 mib[2] = CTL_QUERY; 736 alen = sizeof(ans); 737 738 memset(&q, 0, sizeof(q)); 739 q.sysctl_flags = SYSCTL_VERSION; 740 741 if (rump_sys___sysctl(mib, 3, ans, &alen, &q, sizeof(q)) == -1) { 742 return -1; 743 } 744 745 for (i = 0; i < alen/sizeof(ans[0]); i++) 746 if (strcmp("fstypes", ans[i].sysctl_name) == 0) 747 break; 748 if (i == alen/sizeof(ans[0])) { 749 errno = ENXIO; 750 return -1; 751 } 752 753 mib[0] = CTL_VFS; 754 mib[1] = VFS_GENERIC; 755 mib[2] = ans[i].sysctl_num; 756 757 if (rump_sys___sysctl(mib, 3, buf, &buflen, NULL, 0) == -1) { 758 return -1; 759 } 760 761 return buflen; 762 } 763 764 /* 765 * Utilities 766 */ 767 int 768 ukfs_util_builddirs(struct ukfs *ukfs, const char *pathname, mode_t mode) 769 { 770 char *f1, *f2; 771 int rv; 772 mode_t mask; 773 bool end; 774 775 /*ukfs_umask((mask = ukfs_umask(0)));*/ 776 umask((mask = umask(0))); 777 778 f1 = f2 = strdup(pathname); 779 if (f1 == NULL) { 780 errno = ENOMEM; 781 return -1; 782 } 783 784 end = false; 785 for (;;) { 786 /* find next component */ 787 f2 += strspn(f2, "/"); 788 f2 += strcspn(f2, "/"); 789 if (*f2 == '\0') 790 end = true; 791 else 792 *f2 = '\0'; 793 794 rv = ukfs_mkdir(ukfs, f1, mode & ~mask); 795 if (errno == EEXIST) 796 rv = 0; 797 798 if (rv == -1 || *f2 != '\0' || end) 799 break; 800 801 *f2 = '/'; 802 } 803 804 free(f1); 805 806 return rv; 807 } 808