ukfs.c revision 1.15 1 /* $NetBSD: ukfs.c,v 1.15 2008/11/24 05:54: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 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 free(fs);
262 }
263
264 /* don't need vn_lock(), since we don't have VXLOCK */
265 #define VLE(a) RUMP_VOP_LOCK(a, RUMP_LK_EXCLUSIVE)
266 #define VLS(a) RUMP_VOP_LOCK(a, RUMP_LK_SHARED)
267 #define VUL(a) RUMP_VOP_UNLOCK(a, 0)
268 #define AUL(a) assert(RUMP_VOP_ISLOCKED(a) == 0)
269
270 #define STDCALL(ukfs, thecall) \
271 int rv = 0; \
272 \
273 precall(ukfs); \
274 thecall; \
275 postcall(ukfs); \
276 if (rv) { \
277 errno = rv; \
278 return -1; \
279 } \
280 return 0;
281
282 int
283 ukfs_getdents(struct ukfs *ukfs, const char *dirname, off_t *off,
284 uint8_t *buf, size_t bufsize)
285 {
286 struct uio *uio;
287 struct vnode *vp;
288 size_t resid;
289 kauth_cred_t cred;
290 int rv, eofflag;
291
292 precall(ukfs);
293 rv = rump_namei(RUMP_NAMEI_LOOKUP, RUMP_NAMEI_LOCKLEAF, dirname,
294 NULL, &vp, NULL);
295 postcall(ukfs);
296 if (rv)
297 goto out;
298
299 uio = rump_uio_setup(buf, bufsize, *off, RUMPUIO_READ);
300 cred = rump_cred_suserget();
301 rv = RUMP_VOP_READDIR(vp, uio, cred, &eofflag, NULL, NULL);
302 rump_cred_suserput(cred);
303 VUL(vp);
304 *off = rump_uio_getoff(uio);
305 resid = rump_uio_free(uio);
306 rump_vp_rele(vp);
307
308 out:
309 if (rv) {
310 errno = rv;
311 return -1;
312 }
313
314 /* LINTED: not totally correct return type, but follows syscall */
315 return bufsize - resid;
316 }
317
318 ssize_t
319 ukfs_read(struct ukfs *ukfs, const char *filename, off_t off,
320 uint8_t *buf, size_t bufsize)
321 {
322 int fd, rv = 0, dummy;
323 ssize_t xfer = -1; /* XXXgcc */
324
325 precall(ukfs);
326 fd = rump_sys_open(filename, RUMP_O_RDONLY, 0, &rv);
327 if (rv)
328 goto out;
329
330 xfer = rump_sys_pread(fd, buf, bufsize, 0, off, &rv);
331 rump_sys_close(fd, &dummy);
332
333 out:
334 postcall(ukfs);
335 if (rv) {
336 errno = rv;
337 return -1;
338 }
339 return xfer;
340 }
341
342 ssize_t
343 ukfs_write(struct ukfs *ukfs, const char *filename, off_t off,
344 uint8_t *buf, size_t bufsize)
345 {
346 int fd, rv = 0, dummy;
347 ssize_t xfer = -1; /* XXXgcc */
348
349 precall(ukfs);
350 fd = rump_sys_open(filename, RUMP_O_WRONLY, 0, &rv);
351 if (rv)
352 goto out;
353
354 /* write and commit */
355 xfer = rump_sys_pwrite(fd, buf, bufsize, 0, off, &rv);
356 if (rv == 0)
357 rump_sys_fsync(fd, &dummy);
358
359 rump_sys_close(fd, &dummy);
360
361 out:
362 postcall(ukfs);
363 if (rv) {
364 errno = rv;
365 return -1;
366 }
367 return xfer;
368 }
369
370 int
371 ukfs_create(struct ukfs *ukfs, const char *filename, mode_t mode)
372 {
373 int rv, fd, dummy;
374
375 precall(ukfs);
376 fd = rump_sys_open(filename, RUMP_O_WRONLY | RUMP_O_CREAT, mode, &rv);
377 rump_sys_close(fd, &dummy);
378
379 postcall(ukfs);
380 if (rv) {
381 errno = rv;
382 return -1;
383 }
384 return 0;
385 }
386
387 int
388 ukfs_mknod(struct ukfs *ukfs, const char *path, mode_t mode, dev_t dev)
389 {
390
391 STDCALL(ukfs, rump_sys_mknod(path, mode, dev, &rv));
392 }
393
394 int
395 ukfs_mkfifo(struct ukfs *ukfs, const char *path, mode_t mode)
396 {
397
398 STDCALL(ukfs, rump_sys_mkfifo(path, mode, &rv));
399 }
400
401 int
402 ukfs_mkdir(struct ukfs *ukfs, const char *filename, mode_t mode)
403 {
404
405 STDCALL(ukfs, rump_sys_mkdir(filename, mode, &rv));
406 }
407
408 int
409 ukfs_remove(struct ukfs *ukfs, const char *filename)
410 {
411
412 STDCALL(ukfs, rump_sys_unlink(filename, &rv));
413 }
414
415 int
416 ukfs_rmdir(struct ukfs *ukfs, const char *filename)
417 {
418
419 STDCALL(ukfs, rump_sys_rmdir(filename, &rv));
420 }
421
422 int
423 ukfs_link(struct ukfs *ukfs, const char *filename, const char *f_create)
424 {
425
426 STDCALL(ukfs, rump_sys_link(filename, f_create, &rv));
427 }
428
429 int
430 ukfs_symlink(struct ukfs *ukfs, const char *filename, const char *linkname)
431 {
432
433 STDCALL(ukfs, rump_sys_symlink(filename, linkname, &rv));
434 }
435
436 ssize_t
437 ukfs_readlink(struct ukfs *ukfs, const char *filename,
438 char *linkbuf, size_t buflen)
439 {
440 ssize_t rv;
441 int myerr = 0;
442
443 precall(ukfs);
444 rv = rump_sys_readlink(filename, linkbuf, buflen, &myerr);
445 postcall(ukfs);
446 if (myerr) {
447 errno = myerr;
448 return -1;
449 }
450 return rv;
451 }
452
453 int
454 ukfs_rename(struct ukfs *ukfs, const char *from, const char *to)
455 {
456
457 STDCALL(ukfs, rump_sys_rename(from, to, &rv));
458 }
459
460 int
461 ukfs_chdir(struct ukfs *ukfs, const char *path)
462 {
463 struct vnode *newvp, *oldvp;
464 int rv;
465
466 precall(ukfs);
467 rump_sys_chdir(path, &rv);
468 if (rv)
469 goto out;
470
471 newvp = rump_cdir_get();
472 pthread_spin_lock(&ukfs->ukfs_spin);
473 oldvp = ukfs->ukfs_cdir;
474 ukfs->ukfs_cdir = newvp;
475 pthread_spin_unlock(&ukfs->ukfs_spin);
476 if (oldvp)
477 rump_vp_rele(oldvp);
478
479 out:
480 postcall(ukfs);
481 if (rv) {
482 errno = rv;
483 return -1;
484 }
485 return 0;
486 }
487
488 int
489 ukfs_stat(struct ukfs *ukfs, const char *filename, struct stat *file_stat)
490 {
491
492 STDCALL(ukfs, rump_sys___stat30(filename, file_stat, &rv));
493 }
494
495 int
496 ukfs_lstat(struct ukfs *ukfs, const char *filename, struct stat *file_stat)
497 {
498
499 STDCALL(ukfs, rump_sys___lstat30(filename, file_stat, &rv));
500 }
501
502 int
503 ukfs_chmod(struct ukfs *ukfs, const char *filename, mode_t mode)
504 {
505
506 STDCALL(ukfs, rump_sys_chmod(filename, mode, &rv));
507 }
508
509 int
510 ukfs_lchmod(struct ukfs *ukfs, const char *filename, mode_t mode)
511 {
512
513 STDCALL(ukfs, rump_sys_lchmod(filename, mode, &rv));
514 }
515
516 int
517 ukfs_chown(struct ukfs *ukfs, const char *filename, uid_t uid, gid_t gid)
518 {
519
520 STDCALL(ukfs, rump_sys_chown(filename, uid, gid, &rv));
521 }
522
523 int
524 ukfs_lchown(struct ukfs *ukfs, const char *filename, uid_t uid, gid_t gid)
525 {
526
527 STDCALL(ukfs, rump_sys_lchown(filename, uid, gid, &rv));
528 }
529
530 int
531 ukfs_chflags(struct ukfs *ukfs, const char *filename, u_long flags)
532 {
533
534 STDCALL(ukfs, rump_sys_chflags(filename, flags, &rv));
535 }
536
537 int
538 ukfs_lchflags(struct ukfs *ukfs, const char *filename, u_long flags)
539 {
540
541 STDCALL(ukfs, rump_sys_lchflags(filename, flags, &rv));
542 }
543
544 int
545 ukfs_utimes(struct ukfs *ukfs, const char *filename, const struct timeval *tptr)
546 {
547
548 STDCALL(ukfs, rump_sys_utimes(filename, tptr, &rv));
549 }
550
551 int
552 ukfs_lutimes(struct ukfs *ukfs, const char *filename,
553 const struct timeval *tptr)
554 {
555
556 STDCALL(ukfs, rump_sys_lutimes(filename, tptr, &rv));
557 }
558
559 /*
560 * Dynamic module support
561 */
562
563 /* load one library */
564
565 /*
566 * XXX: the dlerror stuff isn't really threadsafe, but then again I
567 * can't protect against other threads calling dl*() outside of ukfs,
568 * so just live with it being flimsy
569 */
570 #define UFSLIB "librumpfs_ufs.so"
571 int
572 ukfs_modload(const char *fname)
573 {
574 void *handle, *thesym;
575 struct stat sb;
576 const char *p;
577 int error;
578
579 if (stat(fname, &sb) == -1)
580 return -1;
581
582 handle = dlopen(fname, RTLD_GLOBAL);
583 if (handle == NULL) {
584 const char *dlmsg = dlerror();
585 if (strstr(dlmsg, "Undefined symbol"))
586 return 0;
587 warnx("dlopen %s failed: %s\n", fname, dlmsg);
588 /* XXXerrno */
589 return -1;
590 }
591
592 /*
593 * XXX: the ufs module is not loaded in the same fashion as the
594 * others. But we can't do dlclose() for it, since that would
595 * lead to not being able to load ffs/ext2fs/lfs. Hence hardcode
596 * and kludge around the issue for now. But this should really
597 * be fixed by fixing sys/ufs/ufs to be a kernel module.
598 */
599 if ((p = strrchr(fname, '/')) != NULL)
600 p++;
601 else
602 p = fname;
603 if (strcmp(p, UFSLIB) == 0)
604 return 1;
605
606 thesym = dlsym(handle, "__start_link_set_modules");
607 if (thesym) {
608 error = rump_module_load(thesym);
609 if (error)
610 goto errclose;
611 return 1;
612 }
613 error = EINVAL;
614
615 errclose:
616 dlclose(handle);
617 errno = error;
618 return -1;
619 }
620
621 struct loadfail {
622 char *pname;
623
624 LIST_ENTRY(loadfail) entries;
625 };
626
627 #define RUMPFSMOD_PREFIX "librumpfs_"
628 #define RUMPFSMOD_SUFFIX ".so"
629
630 int
631 ukfs_modload_dir(const char *dir)
632 {
633 char nbuf[MAXPATHLEN+1], *p;
634 struct dirent entry, *result;
635 DIR *libdir;
636 struct loadfail *lf, *nlf;
637 int error, nloaded = 0, redo;
638 LIST_HEAD(, loadfail) lfs;
639
640 libdir = opendir(dir);
641 if (libdir == NULL)
642 return -1;
643
644 LIST_INIT(&lfs);
645 for (;;) {
646 if ((error = readdir_r(libdir, &entry, &result)) != 0)
647 break;
648 if (!result)
649 break;
650 if (strncmp(result->d_name, RUMPFSMOD_PREFIX,
651 strlen(RUMPFSMOD_PREFIX)) != 0)
652 continue;
653 if (((p = strstr(result->d_name, RUMPFSMOD_SUFFIX)) == NULL)
654 || strlen(p) != strlen(RUMPFSMOD_SUFFIX))
655 continue;
656 strlcpy(nbuf, dir, sizeof(nbuf));
657 strlcat(nbuf, "/", sizeof(nbuf));
658 strlcat(nbuf, result->d_name, sizeof(nbuf));
659 switch (ukfs_modload(nbuf)) {
660 case 0:
661 lf = malloc(sizeof(*lf));
662 if (lf == NULL) {
663 error = ENOMEM;
664 break;
665 }
666 lf->pname = strdup(nbuf);
667 if (lf->pname == NULL) {
668 free(lf);
669 error = ENOMEM;
670 break;
671 }
672 LIST_INSERT_HEAD(&lfs, lf, entries);
673 break;
674 case 1:
675 nloaded++;
676 break;
677 default:
678 /* ignore errors */
679 break;
680 }
681 }
682 closedir(libdir);
683 if (error && nloaded != 0)
684 error = 0;
685
686 /*
687 * El-cheapo dependency calculator. Just try to load the
688 * modules n times in a loop
689 */
690 for (redo = 1; redo;) {
691 redo = 0;
692 nlf = LIST_FIRST(&lfs);
693 while ((lf = nlf) != NULL) {
694 nlf = LIST_NEXT(lf, entries);
695 if (ukfs_modload(lf->pname) == 1) {
696 nloaded++;
697 redo = 1;
698 LIST_REMOVE(lf, entries);
699 free(lf->pname);
700 free(lf);
701 }
702 }
703 }
704
705 while ((lf = LIST_FIRST(&lfs)) != NULL) {
706 LIST_REMOVE(lf, entries);
707 free(lf->pname);
708 free(lf);
709 }
710
711 if (error && nloaded == 0) {
712 errno = error;
713 return -1;
714 }
715
716 return nloaded;
717 }
718
719 /* XXX: this code uses definitions from NetBSD, needs rumpdefs */
720 ssize_t
721 ukfs_vfstypes(char *buf, size_t buflen)
722 {
723 int mib[3];
724 struct sysctlnode q, ans[128];
725 size_t alen;
726 int error, i;
727
728 mib[0] = CTL_VFS;
729 mib[1] = VFS_GENERIC;
730 mib[2] = CTL_QUERY;
731 alen = sizeof(ans);
732
733 memset(&q, 0, sizeof(q));
734 q.sysctl_flags = SYSCTL_VERSION;
735
736 if (rump_sys___sysctl(mib, 3, ans, &alen, &q, sizeof(q), &error) == -1){
737 errno = error;
738 return -1;
739 }
740
741 for (i = 0; i < alen/sizeof(ans[0]); i++)
742 if (strcmp("fstypes", ans[i].sysctl_name) == 0)
743 break;
744 if (i == alen/sizeof(ans[0])) {
745 errno = ENXIO;
746 return -1;
747 }
748
749 mib[0] = CTL_VFS;
750 mib[1] = VFS_GENERIC;
751 mib[2] = ans[i].sysctl_num;
752
753 if (rump_sys___sysctl(mib, 3, buf, &buflen, NULL, 0, &error) == -1) {
754 errno = error;
755 return -1;
756 }
757
758 return buflen;
759 }
760
761 /*
762 * Utilities
763 */
764 int
765 ukfs_util_builddirs(struct ukfs *ukfs, const char *pathname, mode_t mode)
766 {
767 char *f1, *f2;
768 int rv;
769 mode_t mask;
770 bool end;
771
772 /*ukfs_umask((mask = ukfs_umask(0)));*/
773 umask((mask = umask(0)));
774
775 f1 = f2 = strdup(pathname);
776 if (f1 == NULL) {
777 errno = ENOMEM;
778 return -1;
779 }
780
781 end = false;
782 for (;;) {
783 /* find next component */
784 f2 += strspn(f2, "/");
785 f2 += strcspn(f2, "/");
786 if (*f2 == '\0')
787 end = true;
788 else
789 *f2 = '\0';
790
791 rv = ukfs_mkdir(ukfs, f1, mode & ~mask);
792 if (errno == EEXIST)
793 rv = 0;
794
795 if (rv == -1 || *f2 != '\0' || end)
796 break;
797
798 *f2 = '/';
799 }
800
801 free(f1);
802
803 return rv;
804 }
805