ukfs.c revision 1.23 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