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