NetBSD/lib/libukfs/ukfs.c
pooka b405c8d23e mfs is a bit off in the sense that mount(2) doesn't return since
mfs uses the mounting process for the backing store memory.  I
guess mfs could be fixed to just reference the process vmspace and
let it return, but that would probably cause wait() to return for
other worms.  So it's easier to dance according to mfs's tune: if
mounting mfs, create a thread for extra execution context.
2010-03-08 12:38:14 +00:00

1383 lines
27 KiB
C

/* $NetBSD: ukfs.c,v 1.49 2010/03/08 12:38:14 pooka Exp $ */
/*
* Copyright (c) 2007, 2008, 2009 Antti Kantee. All Rights Reserved.
*
* Development of this software was supported by the
* Finnish Cultural Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* This library enables access to files systems directly without
* involving system calls.
*/
#ifdef __linux__
#define _XOPEN_SOURCE 500
#define _BSD_SOURCE
#define _FILE_OFFSET_BITS 64
#endif
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/mount.h>
#include <assert.h>
#include <dirent.h>
#include <dlfcn.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <rump/ukfs.h>
#include <rump/rump.h>
#include <rump/rump_syscalls.h>
#include <rump/rumpuser.h>
#include "ukfs_int_disklabel.h"
#define UKFS_MODE_DEFAULT 0555
struct ukfs {
struct mount *ukfs_mp;
struct vnode *ukfs_rvp;
void *ukfs_specific;
pthread_spinlock_t ukfs_spin;
pid_t ukfs_nextpid;
struct vnode *ukfs_cdir;
int ukfs_devfd;
char *ukfs_devpath;
char *ukfs_mountpath;
struct ukfs_part *ukfs_part;
};
static int builddirs(const char *, mode_t,
int (*mkdirfn)(struct ukfs *, const char *, mode_t), struct ukfs *);
struct mount *
ukfs_getmp(struct ukfs *ukfs)
{
return ukfs->ukfs_mp;
}
struct vnode *
ukfs_getrvp(struct ukfs *ukfs)
{
struct vnode *rvp;
rvp = ukfs->ukfs_rvp;
rump_pub_vp_incref(rvp);
return rvp;
}
void
ukfs_setspecific(struct ukfs *ukfs, void *priv)
{
ukfs->ukfs_specific = priv;
}
void *
ukfs_getspecific(struct ukfs *ukfs)
{
return ukfs->ukfs_specific;
}
#ifdef DONT_WANT_PTHREAD_LINKAGE
#define pthread_spin_lock(a)
#define pthread_spin_unlock(a)
#define pthread_spin_init(a,b)
#define pthread_spin_destroy(a)
#endif
static pid_t
nextpid(struct ukfs *ukfs)
{
pid_t npid;
pthread_spin_lock(&ukfs->ukfs_spin);
if (ukfs->ukfs_nextpid == 0)
ukfs->ukfs_nextpid++;
npid = ukfs->ukfs_nextpid++;
pthread_spin_unlock(&ukfs->ukfs_spin);
return npid;
}
static void
precall(struct ukfs *ukfs)
{
struct vnode *rvp, *cvp;
rump_pub_lwp_alloc_and_switch(nextpid(ukfs), 1);
rvp = ukfs_getrvp(ukfs);
pthread_spin_lock(&ukfs->ukfs_spin);
cvp = ukfs->ukfs_cdir;
pthread_spin_unlock(&ukfs->ukfs_spin);
rump_pub_rcvp_set(rvp, cvp); /* takes refs */
rump_pub_vp_rele(rvp);
}
static void
postcall(struct ukfs *ukfs)
{
struct vnode *rvp;
rvp = ukfs_getrvp(ukfs);
rump_pub_rcvp_set(NULL, rvp);
rump_pub_vp_rele(rvp);
rump_pub_lwp_release(rump_pub_lwp_curlwp());
}
struct ukfs_part {
pthread_spinlock_t part_lck;
int part_refcount;
int part_type;
char part_labelchar;
off_t part_devoff;
off_t part_devsize;
};
enum ukfs_parttype { UKFS_PART_NONE, UKFS_PART_DISKLABEL, UKFS_PART_OFFSET };
static struct ukfs_part ukfs__part_none = {
.part_type = UKFS_PART_NONE,
.part_devoff = 0,
.part_devsize = RUMP_ETFS_SIZE_ENDOFF,
};
static struct ukfs_part ukfs__part_na;
struct ukfs_part *ukfs_part_none = &ukfs__part_none;
struct ukfs_part *ukfs_part_na = &ukfs__part_na;
#define PART2LOCKSIZE(len) ((len) == RUMP_ETFS_SIZE_ENDOFF ? 0 : (len))
int
_ukfs_init(int version)
{
int rv;
if (version != UKFS_VERSION) {
printf("incompatible ukfs version, %d vs. %d\n",
version, UKFS_VERSION);
errno = EPROGMISMATCH;
return -1;
}
if ((rv = rump_init()) != 0) {
errno = rv;
return -1;
}
return 0;
}
/*ARGSUSED*/
static int
rumpmkdir(struct ukfs *dummy, const char *path, mode_t mode)
{
return rump_sys_mkdir(path, mode);
}
int
ukfs_part_probe(char *devpath, struct ukfs_part **partp)
{
struct ukfs_part *part;
char *p;
int error = 0;
int devfd = -1;
if ((p = strstr(devpath, UKFS_PARTITION_SCANMAGIC)) != NULL) {
fprintf(stderr, "ukfs: %%PART is deprecated. use "
"%%DISKLABEL instead\n");
errno = ENODEV;
return -1;
}
part = malloc(sizeof(*part));
if (part == NULL) {
errno = ENOMEM;
return -1;
}
if (pthread_spin_init(&part->part_lck, PTHREAD_PROCESS_PRIVATE) == -1) {
error = errno;
free(part);
errno = error;
return -1;
}
part->part_type = UKFS_PART_NONE;
part->part_refcount = 1;
/*
* Check for magic in pathname:
* disklabel: /regularpath%DISKLABEL:labelchar%\0
* offsets: /regularpath%OFFSET:start,end%\0
*/
#define MAGICADJ_DISKLABEL(p, n) (p+sizeof(UKFS_DISKLABEL_SCANMAGIC)-1+n)
if ((p = strstr(devpath, UKFS_DISKLABEL_SCANMAGIC)) != NULL
&& strlen(p) == UKFS_DISKLABEL_MAGICLEN
&& *(MAGICADJ_DISKLABEL(p,1)) == '%') {
if (*(MAGICADJ_DISKLABEL(p,0)) >= 'a' &&
*(MAGICADJ_DISKLABEL(p,0)) < 'a' + UKFS_MAXPARTITIONS) {
struct ukfs__disklabel dl;
struct ukfs__partition *pp;
char buf[65536];
char labelchar = *(MAGICADJ_DISKLABEL(p,0));
int partition = labelchar - 'a';
*p = '\0';
devfd = open(devpath, O_RDONLY);
if (devfd == -1) {
error = errno;
goto out;
}
/* Locate the disklabel and find the partition. */
if (pread(devfd, buf, sizeof(buf), 0) == -1) {
error = errno;
goto out;
}
if (ukfs__disklabel_scan(&dl, buf, sizeof(buf)) != 0) {
error = ENOENT;
goto out;
}
if (dl.d_npartitions < partition) {
error = ENOENT;
goto out;
}
pp = &dl.d_partitions[partition];
part->part_type = UKFS_PART_DISKLABEL;
part->part_labelchar = labelchar;
part->part_devoff = pp->p_offset << DEV_BSHIFT;
part->part_devsize = pp->p_size << DEV_BSHIFT;
} else {
error = EINVAL;
}
#define MAGICADJ_OFFSET(p, n) (p+sizeof(UKFS_OFFSET_SCANMAGIC)-1+n)
} else if (((p = strstr(devpath, UKFS_OFFSET_SCANMAGIC)) != NULL)
&& (strlen(p) >= UKFS_OFFSET_MINLEN)) {
char *comma, *pers, *ep, *nptr;
u_quad_t val;
comma = strchr(p, ',');
if (comma == NULL) {
error = EINVAL;
goto out;
}
pers = strchr(comma, '%');
if (pers == NULL) {
error = EINVAL;
goto out;
}
*comma = '\0';
*pers = '\0';
*p = '\0';
nptr = MAGICADJ_OFFSET(p,0);
/* check if string is negative */
if (*nptr == '-') {
error = ERANGE;
goto out;
}
val = strtouq(nptr, &ep, 10);
if (val == UQUAD_MAX) {
error = ERANGE;
goto out;
}
if (*ep != '\0') {
error = EADDRNOTAVAIL; /* creative ;) */
goto out;
}
part->part_devoff = val;
/* omstart */
nptr = comma+1;
/* check if string is negative */
if (*nptr == '-') {
error = ERANGE;
goto out;
}
val = strtouq(nptr, &ep, 10);
if (val == UQUAD_MAX) {
error = ERANGE;
goto out;
}
if (*ep != '\0') {
error = EADDRNOTAVAIL; /* creative ;) */
goto out;
}
part->part_devsize = val;
part->part_type = UKFS_PART_OFFSET;
} else {
ukfs_part_release(part);
part = ukfs_part_none;
}
out:
if (devfd != -1)
close(devfd);
if (error) {
free(part);
errno = error;
} else {
*partp = part;
}
return error ? -1 : 0;
}
int
ukfs_part_tostring(struct ukfs_part *part, char *str, size_t strsize)
{
int rv;
*str = '\0';
/* "pseudo" values */
if (part == ukfs_part_na) {
errno = EINVAL;
return -1;
}
if (part == ukfs_part_none)
return 0;
rv = 0;
switch (part->part_type) {
case UKFS_PART_NONE:
break;
case UKFS_PART_DISKLABEL:
snprintf(str, strsize, "%%DISKLABEL:%c%%",part->part_labelchar);
rv = 1;
break;
case UKFS_PART_OFFSET:
snprintf(str, strsize, "[%llu,%llu]",
(unsigned long long)part->part_devoff,
(unsigned long long)(part->part_devoff+part->part_devsize));
rv = 1;
break;
}
return rv;
}
static void
unlockdev(int fd, struct ukfs_part *part)
{
struct flock flarg;
if (part == ukfs_part_na)
return;
memset(&flarg, 0, sizeof(flarg));
flarg.l_type = F_UNLCK;
flarg.l_whence = SEEK_SET;
flarg.l_start = part->part_devoff;
flarg.l_len = PART2LOCKSIZE(part->part_devsize);
if (fcntl(fd, F_SETLK, &flarg) == -1)
warn("ukfs: cannot unlock device file");
}
/*
* Open the disk file and flock it. Also, if we are operation on
* an embedded partition, find the partition offset and size from
* the disklabel.
*
* We hard-fail only in two cases:
* 1) we failed to get the partition info out (don't know what offset
* to mount from)
* 2) we failed to flock the source device (i.e. fcntl() fails,
* not e.g. open() before it)
*
* Otherwise we let the code proceed to mount and let the file system
* throw the proper error. The only questionable bit is that if we
* soft-fail before flock and mount does succeed...
*
* Returns: -1 error (errno reports error code)
* 0 success
*
* dfdp: -1 device is not open
* n device is open
*/
static int
process_diskdevice(const char *devpath, struct ukfs_part *part, int rdonly,
int *dfdp)
{
struct stat sb;
int rv = 0, devfd;
/* defaults */
*dfdp = -1;
devfd = open(devpath, rdonly ? O_RDONLY : O_RDWR);
if (devfd == -1) {
rv = errno;
goto out;
}
if (fstat(devfd, &sb) == -1) {
rv = errno;
goto out;
}
/*
* We do this only for non-block device since the
* (NetBSD) kernel allows block device open only once.
* We also need to close the device for fairly obvious reasons.
*/
if (!S_ISBLK(sb.st_mode)) {
struct flock flarg;
memset(&flarg, 0, sizeof(flarg));
flarg.l_type = rdonly ? F_RDLCK : F_WRLCK;
flarg.l_whence = SEEK_SET;
flarg.l_start = part->part_devoff;
flarg.l_len = PART2LOCKSIZE(part->part_devsize);
if (fcntl(devfd, F_SETLK, &flarg) == -1) {
pid_t holder;
int sverrno;
sverrno = errno;
if (fcntl(devfd, F_GETLK, &flarg) != 1)
holder = flarg.l_pid;
else
holder = -1;
warnx("ukfs_mount: cannot lock device. held by pid %d",
holder);
rv = sverrno;
goto out;
}
} else {
close(devfd);
devfd = -1;
}
*dfdp = devfd;
out:
if (rv) {
if (devfd != -1)
close(devfd);
}
return rv;
}
struct mountinfo {
const char *mi_vfsname;
const char *mi_mountpath;
int mi_mntflags;
void *mi_arg;
size_t mi_alen;
int *mi_error;
};
static void *
mfs_mounter(void *arg)
{
struct mountinfo *mi = arg;
int rv;
rv = rump_sys_mount(mi->mi_vfsname, mi->mi_mountpath, mi->mi_mntflags,
mi->mi_arg, mi->mi_alen);
if (rv) {
warn("mfs mount failed. fix me.");
abort(); /* XXX */
}
return NULL;
}
static struct ukfs *
doukfsmount(const char *vfsname, const char *devpath, struct ukfs_part *part,
const char *mountpath, int mntflags, void *arg, size_t alen)
{
struct ukfs *fs = NULL;
int rv = 0, devfd = -1;
int mounted = 0;
int regged = 0;
pthread_spin_lock(&part->part_lck);
part->part_refcount++;
pthread_spin_unlock(&part->part_lck);
if (part != ukfs_part_na) {
if ((rv = process_diskdevice(devpath, part,
mntflags & MNT_RDONLY, &devfd)) != 0)
goto out;
}
fs = malloc(sizeof(struct ukfs));
if (fs == NULL) {
rv = ENOMEM;
goto out;
}
memset(fs, 0, sizeof(struct ukfs));
/* create our mountpoint. this is never removed. */
if (builddirs(mountpath, 0777, rumpmkdir, NULL) == -1) {
if (errno != EEXIST) {
rv = errno;
goto out;
}
}
if (part != ukfs_part_na) {
/* LINTED */
rv = rump_pub_etfs_register_withsize(devpath, devpath,
RUMP_ETFS_BLK, part->part_devoff, part->part_devsize);
if (rv) {
goto out;
}
regged = 1;
}
/*
* MFS is special since mount(2) doesn't return. Hence, we
* create a thread here. Could fix mfs to return, but there's
* too much history for me to bother.
*/
if (strcmp(vfsname, MOUNT_MFS) == 0) {
pthread_t pt;
struct mountinfo mi;
int i;
mi.mi_vfsname = vfsname;
mi.mi_mountpath = mountpath;
mi.mi_mntflags = mntflags;
mi.mi_arg = arg;
mi.mi_alen = alen;
if (pthread_create(&pt, NULL, mfs_mounter, &mi) == -1) {
rv = errno;
goto out;
}
for (i = 0;i < 100000; i++) {
struct statvfs svfsb;
rv = rump_sys_statvfs1(mountpath, &svfsb, ST_WAIT);
if (rv == -1) {
rv = errno;
goto out;
}
if (strcmp(svfsb.f_mntonname, mountpath) == 0 &&
strcmp(svfsb.f_fstypename, MOUNT_MFS) == 0) {
break;
}
usleep(1);
}
} else {
rv = rump_sys_mount(vfsname, mountpath, mntflags, arg, alen);
if (rv) {
rv = errno;
goto out;
}
}
mounted = 1;
rv = rump_pub_vfs_getmp(mountpath, &fs->ukfs_mp);
if (rv) {
goto out;
}
rv = rump_pub_vfs_root(fs->ukfs_mp, &fs->ukfs_rvp, 0);
if (rv) {
goto out;
}
if (regged) {
fs->ukfs_devpath = strdup(devpath);
}
fs->ukfs_mountpath = strdup(mountpath);
fs->ukfs_cdir = ukfs_getrvp(fs);
pthread_spin_init(&fs->ukfs_spin, PTHREAD_PROCESS_SHARED);
fs->ukfs_devfd = devfd;
fs->ukfs_part = part;
assert(rv == 0);
out:
if (rv) {
if (fs) {
if (fs->ukfs_rvp)
rump_pub_vp_rele(fs->ukfs_rvp);
free(fs);
fs = NULL;
}
if (mounted)
rump_sys_unmount(mountpath, MNT_FORCE);
if (regged)
rump_pub_etfs_remove(devpath);
if (devfd != -1) {
unlockdev(devfd, part);
close(devfd);
}
ukfs_part_release(part);
errno = rv;
}
return fs;
}
struct ukfs *
ukfs_mount(const char *vfsname, const char *devpath,
const char *mountpath, int mntflags, void *arg, size_t alen)
{
return doukfsmount(vfsname, devpath, ukfs_part_na,
mountpath, mntflags, arg, alen);
}
struct ukfs *
ukfs_mount_disk(const char *vfsname, const char *devpath,
struct ukfs_part *part, const char *mountpath, int mntflags,
void *arg, size_t alen)
{
return doukfsmount(vfsname, devpath, part,
mountpath, mntflags, arg, alen);
}
int
ukfs_release(struct ukfs *fs, int flags)
{
if ((flags & UKFS_RELFLAG_NOUNMOUNT) == 0) {
int rv, mntflag, error;
ukfs_chdir(fs, "/");
mntflag = 0;
if (flags & UKFS_RELFLAG_FORCE)
mntflag = MNT_FORCE;
rump_pub_lwp_alloc_and_switch(nextpid(fs), 1);
rump_pub_vp_rele(fs->ukfs_rvp);
fs->ukfs_rvp = NULL;
rv = rump_sys_unmount(fs->ukfs_mountpath, mntflag);
if (rv == -1) {
error = errno;
rump_pub_vfs_root(fs->ukfs_mp, &fs->ukfs_rvp, 0);
rump_pub_lwp_release(rump_pub_lwp_curlwp());
ukfs_chdir(fs, fs->ukfs_mountpath);
errno = error;
return -1;
}
rump_pub_lwp_release(rump_pub_lwp_curlwp());
}
if (fs->ukfs_devpath) {
rump_pub_etfs_remove(fs->ukfs_devpath);
free(fs->ukfs_devpath);
}
free(fs->ukfs_mountpath);
pthread_spin_destroy(&fs->ukfs_spin);
if (fs->ukfs_devfd != -1) {
unlockdev(fs->ukfs_devfd, fs->ukfs_part);
close(fs->ukfs_devfd);
}
ukfs_part_release(fs->ukfs_part);
free(fs);
return 0;
}
void
ukfs_part_release(struct ukfs_part *part)
{
int release;
if (part != ukfs_part_none && part != ukfs_part_na) {
pthread_spin_lock(&part->part_lck);
release = --part->part_refcount == 0;
pthread_spin_unlock(&part->part_lck);
if (release) {
pthread_spin_destroy(&part->part_lck);
free(part);
}
}
}
#define STDCALL(ukfs, thecall) \
int rv = 0; \
\
precall(ukfs); \
rv = thecall; \
postcall(ukfs); \
return rv;
int
ukfs_opendir(struct ukfs *ukfs, const char *dirname, struct ukfs_dircookie **c)
{
struct vnode *vp;
int rv;
precall(ukfs);
rv = rump_pub_namei(RUMP_NAMEI_LOOKUP, RUMP_NAMEI_LOCKLEAF, dirname,
NULL, &vp, NULL);
postcall(ukfs);
if (rv == 0) {
RUMP_VOP_UNLOCK(vp, 0);
} else {
errno = rv;
rv = -1;
}
/*LINTED*/
*c = (struct ukfs_dircookie *)vp;
return rv;
}
static int
getmydents(struct vnode *vp, off_t *off, uint8_t *buf, size_t bufsize)
{
struct uio *uio;
size_t resid;
int rv, eofflag;
kauth_cred_t cred;
uio = rump_pub_uio_setup(buf, bufsize, *off, RUMPUIO_READ);
cred = rump_pub_cred_suserget();
rv = RUMP_VOP_READDIR(vp, uio, cred, &eofflag, NULL, NULL);
rump_pub_cred_put(cred);
RUMP_VOP_UNLOCK(vp, 0);
*off = rump_pub_uio_getoff(uio);
resid = rump_pub_uio_free(uio);
if (rv) {
errno = rv;
return -1;
}
/* LINTED: not totally correct return type, but follows syscall */
return bufsize - resid;
}
/*ARGSUSED*/
int
ukfs_getdents_cookie(struct ukfs *ukfs, struct ukfs_dircookie *c, off_t *off,
uint8_t *buf, size_t bufsize)
{
/*LINTED*/
struct vnode *vp = (struct vnode *)c;
RUMP_VOP_LOCK(vp, RUMP_LK_SHARED);
return getmydents(vp, off, buf, bufsize);
}
int
ukfs_getdents(struct ukfs *ukfs, const char *dirname, off_t *off,
uint8_t *buf, size_t bufsize)
{
struct vnode *vp;
int rv;
precall(ukfs);
rv = rump_pub_namei(RUMP_NAMEI_LOOKUP, RUMP_NAMEI_LOCKLEAF, dirname,
NULL, &vp, NULL);
postcall(ukfs);
if (rv) {
errno = rv;
return -1;
}
rv = getmydents(vp, off, buf, bufsize);
rump_pub_vp_rele(vp);
return rv;
}
/*ARGSUSED*/
int
ukfs_closedir(struct ukfs *ukfs, struct ukfs_dircookie *c)
{
/*LINTED*/
rump_pub_vp_rele((struct vnode *)c);
return 0;
}
int
ukfs_open(struct ukfs *ukfs, const char *filename, int flags)
{
int fd;
precall(ukfs);
fd = rump_sys_open(filename, flags, 0);
postcall(ukfs);
if (fd == -1)
return -1;
return fd;
}
ssize_t
ukfs_read(struct ukfs *ukfs, const char *filename, off_t off,
uint8_t *buf, size_t bufsize)
{
int fd;
ssize_t xfer = -1; /* XXXgcc */
precall(ukfs);
fd = rump_sys_open(filename, RUMP_O_RDONLY, 0);
if (fd == -1)
goto out;
xfer = rump_sys_pread(fd, buf, bufsize, off);
rump_sys_close(fd);
out:
postcall(ukfs);
if (fd == -1) {
return -1;
}
return xfer;
}
/*ARGSUSED*/
ssize_t
ukfs_read_fd(struct ukfs *ukfs, int fd, off_t off, uint8_t *buf, size_t buflen)
{
return rump_sys_pread(fd, buf, buflen, off);
}
ssize_t
ukfs_write(struct ukfs *ukfs, const char *filename, off_t off,
uint8_t *buf, size_t bufsize)
{
int fd;
ssize_t xfer = -1; /* XXXgcc */
precall(ukfs);
fd = rump_sys_open(filename, RUMP_O_WRONLY, 0);
if (fd == -1)
goto out;
/* write and commit */
xfer = rump_sys_pwrite(fd, buf, bufsize, off);
if (xfer > 0)
rump_sys_fsync(fd);
rump_sys_close(fd);
out:
postcall(ukfs);
if (fd == -1) {
return -1;
}
return xfer;
}
/*ARGSUSED*/
ssize_t
ukfs_write_fd(struct ukfs *ukfs, int fd, off_t off, uint8_t *buf, size_t buflen,
int dosync)
{
ssize_t xfer;
xfer = rump_sys_pwrite(fd, buf, buflen, off);
if (xfer > 0 && dosync)
rump_sys_fsync(fd);
return xfer;
}
/*ARGSUSED*/
int
ukfs_close(struct ukfs *ukfs, int fd)
{
rump_sys_close(fd);
return 0;
}
int
ukfs_create(struct ukfs *ukfs, const char *filename, mode_t mode)
{
int fd;
precall(ukfs);
fd = rump_sys_open(filename, RUMP_O_WRONLY | RUMP_O_CREAT, mode);
if (fd == -1)
return -1;
rump_sys_close(fd);
postcall(ukfs);
return 0;
}
int
ukfs_mknod(struct ukfs *ukfs, const char *path, mode_t mode, dev_t dev)
{
STDCALL(ukfs, rump_sys_mknod(path, mode, dev));
}
int
ukfs_mkfifo(struct ukfs *ukfs, const char *path, mode_t mode)
{
STDCALL(ukfs, rump_sys_mkfifo(path, mode));
}
int
ukfs_mkdir(struct ukfs *ukfs, const char *filename, mode_t mode)
{
STDCALL(ukfs, rump_sys_mkdir(filename, mode));
}
int
ukfs_remove(struct ukfs *ukfs, const char *filename)
{
STDCALL(ukfs, rump_sys_unlink(filename));
}
int
ukfs_rmdir(struct ukfs *ukfs, const char *filename)
{
STDCALL(ukfs, rump_sys_rmdir(filename));
}
int
ukfs_link(struct ukfs *ukfs, const char *filename, const char *f_create)
{
STDCALL(ukfs, rump_sys_link(filename, f_create));
}
int
ukfs_symlink(struct ukfs *ukfs, const char *filename, const char *linkname)
{
STDCALL(ukfs, rump_sys_symlink(filename, linkname));
}
ssize_t
ukfs_readlink(struct ukfs *ukfs, const char *filename,
char *linkbuf, size_t buflen)
{
ssize_t rv;
precall(ukfs);
rv = rump_sys_readlink(filename, linkbuf, buflen);
postcall(ukfs);
return rv;
}
int
ukfs_rename(struct ukfs *ukfs, const char *from, const char *to)
{
STDCALL(ukfs, rump_sys_rename(from, to));
}
int
ukfs_chdir(struct ukfs *ukfs, const char *path)
{
struct vnode *newvp, *oldvp;
int rv;
precall(ukfs);
rv = rump_sys_chdir(path);
if (rv == -1)
goto out;
newvp = rump_pub_cdir_get();
pthread_spin_lock(&ukfs->ukfs_spin);
oldvp = ukfs->ukfs_cdir;
ukfs->ukfs_cdir = newvp;
pthread_spin_unlock(&ukfs->ukfs_spin);
if (oldvp)
rump_pub_vp_rele(oldvp);
out:
postcall(ukfs);
return rv;
}
/*
* If we want to use post-time_t file systems on pre-time_t hosts,
* we must translate the stat structure. Since we don't currently
* have a general method for making compat calls in rump, special-case
* this one.
*
* Note that this does not allow making system calls to older rump
* kernels from newer hosts.
*/
#define VERS_TIMECHANGE 599000700
static int
needcompat(void)
{
#ifdef __NetBSD__
/*LINTED*/
return __NetBSD_Version__ < VERS_TIMECHANGE
&& rump_pub_getversion() >= VERS_TIMECHANGE;
#else
return 0;
#endif
}
int
ukfs_stat(struct ukfs *ukfs, const char *filename, struct stat *file_stat)
{
int rv;
precall(ukfs);
if (needcompat())
rv = rump_pub_sys___stat30(filename, file_stat);
else
rv = rump_sys_stat(filename, file_stat);
postcall(ukfs);
return rv;
}
int
ukfs_lstat(struct ukfs *ukfs, const char *filename, struct stat *file_stat)
{
int rv;
precall(ukfs);
if (needcompat())
rv = rump_pub_sys___lstat30(filename, file_stat);
else
rv = rump_sys_lstat(filename, file_stat);
postcall(ukfs);
return rv;
}
int
ukfs_chmod(struct ukfs *ukfs, const char *filename, mode_t mode)
{
STDCALL(ukfs, rump_sys_chmod(filename, mode));
}
int
ukfs_lchmod(struct ukfs *ukfs, const char *filename, mode_t mode)
{
STDCALL(ukfs, rump_sys_lchmod(filename, mode));
}
int
ukfs_chown(struct ukfs *ukfs, const char *filename, uid_t uid, gid_t gid)
{
STDCALL(ukfs, rump_sys_chown(filename, uid, gid));
}
int
ukfs_lchown(struct ukfs *ukfs, const char *filename, uid_t uid, gid_t gid)
{
STDCALL(ukfs, rump_sys_lchown(filename, uid, gid));
}
int
ukfs_chflags(struct ukfs *ukfs, const char *filename, u_long flags)
{
STDCALL(ukfs, rump_sys_chflags(filename, flags));
}
int
ukfs_lchflags(struct ukfs *ukfs, const char *filename, u_long flags)
{
STDCALL(ukfs, rump_sys_lchflags(filename, flags));
}
int
ukfs_utimes(struct ukfs *ukfs, const char *filename, const struct timeval *tptr)
{
STDCALL(ukfs, rump_sys_utimes(filename, tptr));
}
int
ukfs_lutimes(struct ukfs *ukfs, const char *filename,
const struct timeval *tptr)
{
STDCALL(ukfs, rump_sys_lutimes(filename, tptr));
}
/*
* Dynamic module support
*/
/* load one library */
/*
* XXX: the dlerror stuff isn't really threadsafe, but then again I
* can't protect against other threads calling dl*() outside of ukfs,
* so just live with it being flimsy
*/
int
ukfs_modload(const char *fname)
{
void *handle;
const struct modinfo *const *mi_start, *const *mi_end;
int error;
handle = dlopen(fname, RTLD_LAZY|RTLD_GLOBAL);
if (handle == NULL) {
const char *dlmsg = dlerror();
if (strstr(dlmsg, "Undefined symbol"))
return 0;
warnx("dlopen %s failed: %s\n", fname, dlmsg);
/* XXXerrno */
return -1;
}
mi_start = dlsym(handle, "__start_link_set_modules");
mi_end = dlsym(handle, "__stop_link_set_modules");
if (mi_start && mi_end) {
error = rump_pub_module_init(mi_start,
(size_t)(mi_end-mi_start));
if (error)
goto errclose;
return 1;
}
error = EINVAL;
errclose:
dlclose(handle);
errno = error;
return -1;
}
struct loadfail {
char *pname;
LIST_ENTRY(loadfail) entries;
};
#define RUMPFSMOD_PREFIX "librumpfs_"
#define RUMPFSMOD_SUFFIX ".so"
int
ukfs_modload_dir(const char *dir)
{
char nbuf[MAXPATHLEN+1], *p;
struct dirent entry, *result;
DIR *libdir;
struct loadfail *lf, *nlf;
int error, nloaded = 0, redo;
LIST_HEAD(, loadfail) lfs;
libdir = opendir(dir);
if (libdir == NULL)
return -1;
LIST_INIT(&lfs);
for (;;) {
if ((error = readdir_r(libdir, &entry, &result)) != 0)
break;
if (!result)
break;
if (strncmp(result->d_name, RUMPFSMOD_PREFIX,
strlen(RUMPFSMOD_PREFIX)) != 0)
continue;
if (((p = strstr(result->d_name, RUMPFSMOD_SUFFIX)) == NULL)
|| strlen(p) != strlen(RUMPFSMOD_SUFFIX))
continue;
strlcpy(nbuf, dir, sizeof(nbuf));
strlcat(nbuf, "/", sizeof(nbuf));
strlcat(nbuf, result->d_name, sizeof(nbuf));
switch (ukfs_modload(nbuf)) {
case 0:
lf = malloc(sizeof(*lf));
if (lf == NULL) {
error = ENOMEM;
break;
}
lf->pname = strdup(nbuf);
if (lf->pname == NULL) {
free(lf);
error = ENOMEM;
break;
}
LIST_INSERT_HEAD(&lfs, lf, entries);
break;
case 1:
nloaded++;
break;
default:
/* ignore errors */
break;
}
}
closedir(libdir);
if (error && nloaded != 0)
error = 0;
/*
* El-cheapo dependency calculator. Just try to load the
* modules n times in a loop
*/
for (redo = 1; redo;) {
redo = 0;
nlf = LIST_FIRST(&lfs);
while ((lf = nlf) != NULL) {
nlf = LIST_NEXT(lf, entries);
if (ukfs_modload(lf->pname) == 1) {
nloaded++;
redo = 1;
LIST_REMOVE(lf, entries);
free(lf->pname);
free(lf);
}
}
}
while ((lf = LIST_FIRST(&lfs)) != NULL) {
LIST_REMOVE(lf, entries);
free(lf->pname);
free(lf);
}
if (error && nloaded == 0) {
errno = error;
return -1;
}
return nloaded;
}
/* XXX: this code uses definitions from NetBSD, needs rumpdefs */
ssize_t
ukfs_vfstypes(char *buf, size_t buflen)
{
int mib[3];
struct sysctlnode q, ans[128];
size_t alen;
int i;
mib[0] = CTL_VFS;
mib[1] = VFS_GENERIC;
mib[2] = CTL_QUERY;
alen = sizeof(ans);
memset(&q, 0, sizeof(q));
q.sysctl_flags = SYSCTL_VERSION;
if (rump_sys___sysctl(mib, 3, ans, &alen, &q, sizeof(q)) == -1) {
return -1;
}
for (i = 0; i < alen/sizeof(ans[0]); i++)
if (strcmp("fstypes", ans[i].sysctl_name) == 0)
break;
if (i == alen/sizeof(ans[0])) {
errno = ENXIO;
return -1;
}
mib[0] = CTL_VFS;
mib[1] = VFS_GENERIC;
mib[2] = ans[i].sysctl_num;
if (rump_sys___sysctl(mib, 3, buf, &buflen, NULL, 0) == -1) {
return -1;
}
return buflen;
}
/*
* Utilities
*/
static int
builddirs(const char *pathname, mode_t mode,
int (*mkdirfn)(struct ukfs *, const char *, mode_t), struct ukfs *fs)
{
char *f1, *f2;
int rv;
mode_t mask;
bool end;
/*ukfs_umask((mask = ukfs_umask(0)));*/
umask((mask = umask(0)));
f1 = f2 = strdup(pathname);
if (f1 == NULL) {
errno = ENOMEM;
return -1;
}
end = false;
for (;;) {
/* find next component */
f2 += strspn(f2, "/");
f2 += strcspn(f2, "/");
if (*f2 == '\0')
end = true;
else
*f2 = '\0';
rv = mkdirfn(fs, f1, mode & ~mask);
if (errno == EEXIST)
rv = 0;
if (rv == -1 || *f2 != '\0' || end)
break;
*f2 = '/';
}
free(f1);
return rv;
}
int
ukfs_util_builddirs(struct ukfs *ukfs, const char *pathname, mode_t mode)
{
return builddirs(pathname, mode, ukfs_mkdir, ukfs);
}