Browse Source

9pfs: Add FreeBSD support

This is largely derived from existing Darwin support.  FreeBSD
apparently has better support for *at() system calls so doesn't require
workarounds for a missing mknodat().  The implementation has a couple of
warts however:
- The extattr(2) system calls don't support anything akin to
  XATTR_CREATE or XATTR_REPLACE, so a racy workaround is implemented.
- Attribute names cannot begin with "user." or "system." on ZFS.
  However FreeBSD's extattr(2) system calls support two dedicated
  namespaces for these two.  So "user." or "system." prefixes are
  trimmed off from attribute names and instead EXTATTR_NAMESPACE_USER or
  EXTATTR_NAMESPACE_SYSTEM are picked and passed to extattr system calls
  accordingly.

The 9pfs tests were verified to pass on the UFS, ZFS and tmpfs
filesystems.

Signed-off-by: Mark Johnston <markj@FreeBSD.org>
Link: https://lore.kernel.org/qemu-devel/aJOWhHB2p-fbueAm@nuc
Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
pull/305/head
Mark Johnston 8 months ago
committed by Christian Schoenebeck
parent
commit
6657f3bb55
  1. 6
      fsdev/file-op-9p.h
  2. 2
      fsdev/meson.build
  3. 2
      hw/9pfs/9p-synth.c
  4. 132
      hw/9pfs/9p-util-freebsd.c
  5. 20
      hw/9pfs/9p-util.h
  6. 16
      hw/9pfs/9p.c
  7. 2
      hw/9pfs/meson.build
  8. 6
      include/qemu/xattr.h
  9. 8
      meson.build

6
fsdev/file-op-9p.h

@ -21,9 +21,11 @@
#ifdef CONFIG_LINUX
# include <sys/vfs.h>
#endif
#ifdef CONFIG_DARWIN
#elif defined(CONFIG_DARWIN) || defined(CONFIG_FREEBSD)
# include <sys/param.h>
# ifdef CONFIG_FREEBSD
# undef MACHINE /* work around some unfortunate namespace pollution */
# endif
# include <sys/mount.h>
#endif

2
fsdev/meson.build

@ -5,6 +5,6 @@ fsdev_ss.add(when: ['CONFIG_FSDEV_9P'], if_true: files(
'9p-marshal.c',
'qemu-fsdev.c',
), if_false: files('qemu-fsdev-dummy.c'))
if host_os in ['linux', 'darwin']
if host_os in ['linux', 'darwin', 'freebsd']
system_ss.add_all(fsdev_ss)
endif

2
hw/9pfs/9p-synth.c

@ -451,7 +451,7 @@ static int synth_statfs(FsContext *s, V9fsPath *fs_path,
stbuf->f_bsize = 512;
stbuf->f_blocks = 0;
stbuf->f_files = synth_node_count;
#ifndef CONFIG_DARWIN
#if !defined(CONFIG_DARWIN) && !defined(CONFIG_FREEBSD)
stbuf->f_namelen = NAME_MAX;
#endif
return 0;

132
hw/9pfs/9p-util-freebsd.c

@ -0,0 +1,132 @@
/*
* 9p utilities (FreeBSD Implementation)
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
/*
* Not so fast! You might want to read the 9p developer docs first:
* https://wiki.qemu.org/Documentation/9p
*/
#include "qemu/osdep.h"
#include "qemu/xattr.h"
#include "9p-util.h"
static int mangle_xattr_name(const char **namep)
{
const char *name = *namep;
/*
* ZFS forbids attributes in starting with "user." or "system.".
*/
if (strncmp(name, "system.", 7) == 0) {
*namep = name + 7;
return EXTATTR_NAMESPACE_SYSTEM;
}
if (strncmp(name, "user.", 5) == 0) {
*namep = name + 5;
}
return EXTATTR_NAMESPACE_USER;
}
ssize_t fgetxattr(int fd, const char *name, void *value, size_t size)
{
int namespace;
namespace = mangle_xattr_name(&name);
return extattr_get_fd(fd, namespace, name, value, size);
}
ssize_t fgetxattrat_nofollow(int dirfd, const char *filename, const char *name,
void *value, size_t size)
{
ssize_t ret;
int fd, namespace;
fd = openat_file(dirfd, filename,
O_RDONLY | O_PATH_9P_UTIL | O_NOFOLLOW, 0);
if (fd == -1) {
return -1;
}
namespace = mangle_xattr_name(&name);
ret = extattr_get_fd(fd, namespace, name, value, size);
close_preserve_errno(fd);
return ret;
}
ssize_t flistxattrat_nofollow(int dirfd, const char *filename,
char *list, size_t size)
{
ssize_t ret;
int fd;
fd = openat_file(dirfd, filename,
O_RDONLY | O_PATH_9P_UTIL | O_NOFOLLOW, 0);
if (fd == -1) {
return -1;
}
ret = extattr_list_fd(fd, EXTATTR_NAMESPACE_USER, list, size);
close_preserve_errno(fd);
return ret;
}
ssize_t fremovexattrat_nofollow(int dirfd, const char *filename,
const char *name)
{
int fd, namespace, ret;
fd = openat_file(dirfd, filename,
O_RDONLY | O_PATH_9P_UTIL | O_NOFOLLOW, 0);
if (fd == -1) {
return -1;
}
namespace = mangle_xattr_name(&name);
ret = extattr_delete_fd(fd, namespace, name);
close_preserve_errno(fd);
return ret;
}
int fsetxattrat_nofollow(int dirfd, const char *filename, const char *name,
void *value, size_t size, int flags)
{
ssize_t ret;
int fd, namespace;
namespace = mangle_xattr_name(&name);
if (flags == (XATTR_CREATE | XATTR_REPLACE)) {
errno = EINVAL;
return -1;
}
fd = openat_file(dirfd, filename,
O_RDONLY | O_PATH_9P_UTIL | O_NOFOLLOW, 0);
if (fd == -1) {
return -1;
}
if (flags & (XATTR_CREATE | XATTR_REPLACE)) {
ret = extattr_get_fd(fd, namespace, name, NULL, 0);
if (ret == -1 && errno != ENOATTR) {
close_preserve_errno(fd);
return -1;
}
if (ret >= 0 && (flags & XATTR_CREATE)) {
errno = EEXIST;
close_preserve_errno(fd);
return -1;
}
if (ret == -1 && (flags & XATTR_REPLACE)) {
errno = ENOATTR;
close_preserve_errno(fd);
return -1;
}
}
ret = extattr_set_fd(fd, namespace, name, value, size);
close_preserve_errno(fd);
return ret;
}
int qemu_mknodat(int dirfd, const char *filename, mode_t mode, dev_t dev)
{
return mknodat(dirfd, filename, mode, dev);
}

20
hw/9pfs/9p-util.h

@ -21,6 +21,15 @@
#define O_PATH_9P_UTIL 0
#endif
#ifdef CONFIG_FREEBSD
/*
* FreeBSD does not have these flags, so we can only emulate their intended
* behaviour (racily).
*/
#define XATTR_CREATE 0x1
#define XATTR_REPLACE 0x2
#endif
#if !defined(CONFIG_LINUX)
/*
@ -64,9 +73,9 @@ static inline uint64_t host_dev_to_dotl_dev(dev_t dev)
static inline int errno_to_dotl(int err) {
#if defined(CONFIG_LINUX)
/* nothing to translate (Linux -> Linux) */
#elif defined(CONFIG_DARWIN)
#elif defined(CONFIG_DARWIN) || defined(CONFIG_FREEBSD)
/*
* translation mandatory for macOS hosts
* translation mandatory for non-Linux hosts
*
* FIXME: Only most important errnos translated here yet, this should be
* extended to as many errnos being translated as possible in future.
@ -155,13 +164,13 @@ static inline int openat_file(int dirfd, const char *name, int flags,
{
int fd, serrno, ret;
#ifndef CONFIG_DARWIN
#if !defined(CONFIG_DARWIN) && !defined(CONFIG_FREEBSD)
again:
#endif
fd = qemu_openat(dirfd, name, flags | O_NOFOLLOW | O_NOCTTY | O_NONBLOCK,
mode);
if (fd == -1) {
#ifndef CONFIG_DARWIN
#if !defined(CONFIG_DARWIN) && !defined(CONFIG_FREEBSD)
if (errno == EPERM && (flags & O_NOATIME)) {
/*
* The client passed O_NOATIME but we lack permissions to honor it.
@ -202,6 +211,9 @@ again:
return fd;
}
#ifdef CONFIG_FREEBSD
ssize_t fgetxattr(int dirfd, const char *name, void *value, size_t size);
#endif
ssize_t fgetxattrat_nofollow(int dirfd, const char *path, const char *name,
void *value, size_t size);
int fsetxattrat_nofollow(int dirfd, const char *path, const char *name,

16
hw/9pfs/9p.c

@ -136,8 +136,10 @@ static int dotl_to_open_flags(int flags)
{ P9_DOTL_NONBLOCK, O_NONBLOCK } ,
{ P9_DOTL_DSYNC, O_DSYNC },
{ P9_DOTL_FASYNC, FASYNC },
#ifndef CONFIG_DARWIN
#if !defined(CONFIG_DARWIN) && !defined(CONFIG_FREEBSD)
{ P9_DOTL_NOATIME, O_NOATIME },
#endif
#ifndef CONFIG_DARWIN
/*
* On Darwin, we could map to F_NOCACHE, which is
* similar, but doesn't quite have the same
@ -3658,7 +3660,7 @@ static int v9fs_fill_statfs(V9fsState *s, V9fsPDU *pdu, struct statfs *stbuf)
f_bavail = stbuf->f_bavail / bsize_factor;
f_files = stbuf->f_files;
f_ffree = stbuf->f_ffree;
#ifdef CONFIG_DARWIN
#if defined(CONFIG_DARWIN) || defined(CONFIG_FREEBSD)
fsid_val = (unsigned int)stbuf->f_fsid.val[0] |
(unsigned long long)stbuf->f_fsid.val[1] << 32;
f_namelen = NAME_MAX;
@ -4050,6 +4052,16 @@ out_nofid:
* Linux guests.
*/
#define P9_XATTR_SIZE_MAX 65536
#elif defined(CONFIG_FREEBSD)
/*
* FreeBSD similarly doesn't define a maximum xattr size, the limit is
* filesystem dependent. On UFS filesystems it's 2 times the filesystem block
* size, typically 32KB. On ZFS it depends on the value of the xattr property;
* with the default value there is no limit, and with xattr=sa it is 64KB.
*
* So, a limit of 64k seems reasonable here too.
*/
#define P9_XATTR_SIZE_MAX 65536
#else
#error Missing definition for P9_XATTR_SIZE_MAX for this host system
#endif

2
hw/9pfs/meson.build

@ -15,6 +15,8 @@ fs_ss.add(files(
))
if host_os == 'darwin'
fs_ss.add(files('9p-util-darwin.c'))
elif host_os == 'freebsd'
fs_ss.add(files('9p-util-freebsd.c'))
elif host_os == 'linux'
fs_ss.add(files('9p-util-linux.c'))
endif

6
include/qemu/xattr.h

@ -26,7 +26,11 @@
# define ENOATTR ENODATA
# endif
# ifndef CONFIG_WIN32
# include <sys/xattr.h>
# ifdef CONFIG_FREEBSD
# include <sys/extattr.h>
# else
# include <sys/xattr.h>
# endif
# endif
#endif

8
meson.build

@ -2384,11 +2384,11 @@ dbus_display = get_option('dbus_display') \
.allowed()
have_virtfs = get_option('virtfs') \
.require(host_os == 'linux' or host_os == 'darwin',
error_message: 'virtio-9p (virtfs) requires Linux or macOS') \
.require(host_os == 'linux' or cc.has_function('pthread_fchdir_np'),
.require(host_os == 'linux' or host_os == 'darwin' or host_os == 'freebsd',
error_message: 'virtio-9p (virtfs) requires Linux or macOS or FreeBSD') \
.require(host_os != 'darwin' or cc.has_function('pthread_fchdir_np'),
error_message: 'virtio-9p (virtfs) on macOS requires the presence of pthread_fchdir_np') \
.require(host_os == 'darwin' or libattr.found(),
.require(host_os != 'linux' or libattr.found(),
error_message: 'virtio-9p (virtfs) on Linux requires libattr-devel') \
.disable_auto_if(not have_tools and not have_system) \
.allowed()

Loading…
Cancel
Save