mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2024-11-18 02:20:59 +03:00
9f0a21e641
Add the FreeBSD platform code to the OpenZFS repository. As of this commit the source can be compiled and tested on FreeBSD 11 and 12. Subsequent commits are now required to compile on FreeBSD and Linux. Additionally, they must pass the ZFS Test Suite on FreeBSD which is being run by the CI. As of this commit 1230 tests pass on FreeBSD and there are no unexpected failures. Reviewed-by: Sean Eric Fagan <sef@ixsystems.com> Reviewed-by: Jorgen Lundman <lundman@lundman.net> Reviewed-by: Richard Laager <rlaager@wiktel.com> Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Co-authored-by: Ryan Moeller <ryan@iXsystems.com> Signed-off-by: Matt Macy <mmacy@FreeBSD.org> Signed-off-by: Ryan Moeller <ryan@iXsystems.com> Closes #898 Closes #8987
407 lines
10 KiB
C
407 lines
10 KiB
C
/*
|
|
* Copyright (c) 2007 Pawel Jakub Dawidek <pjd@FreeBSD.org>
|
|
* All rights reserved.
|
|
*
|
|
* 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 AUTHORS AND CONTRIBUTORS ``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 AUTHORS 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.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/vfs.h>
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <libutil.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <libintl.h>
|
|
|
|
#include "libzfs_impl.h"
|
|
|
|
#define _PATH_MOUNTDPID "/var/run/mountd.pid"
|
|
#define FILE_HEADER "# !!! DO NOT EDIT THIS FILE MANUALLY !!!\n\n"
|
|
#define OPTSSIZE 1024
|
|
#define MAXLINESIZE (PATH_MAX + OPTSSIZE)
|
|
|
|
|
|
void
|
|
sa_fini(sa_handle_t handle)
|
|
{
|
|
}
|
|
|
|
int
|
|
sa_parse_legacy_options(sa_group_t group, char *options, char *proto)
|
|
{
|
|
return (SA_OK);
|
|
}
|
|
|
|
|
|
int
|
|
zfs_init_libshare(libzfs_handle_t *zhandle, int service)
|
|
{
|
|
return (SA_OK);
|
|
}
|
|
|
|
/*
|
|
* Share the given filesystem according to the options in the specified
|
|
* protocol specific properties (sharenfs, sharesmb). We rely
|
|
* on "libshare" to do the dirty work for us.
|
|
*/
|
|
int
|
|
zfs_share_proto(zfs_handle_t *zhp, zfs_share_proto_t *proto)
|
|
{
|
|
char mountpoint[ZFS_MAXPROPLEN];
|
|
char shareopts[ZFS_MAXPROPLEN];
|
|
char sourcestr[ZFS_MAXPROPLEN];
|
|
libzfs_handle_t *hdl = zhp->zfs_hdl;
|
|
zfs_share_proto_t *curr_proto;
|
|
zprop_source_t sourcetype;
|
|
int err, ret;
|
|
|
|
if (!zfs_is_mountable(zhp, mountpoint, sizeof (mountpoint), NULL, 0))
|
|
return (0);
|
|
|
|
for (curr_proto = proto; *curr_proto != PROTO_END; curr_proto++) {
|
|
/*
|
|
* Return success if there are no share options.
|
|
*/
|
|
if (zfs_prop_get(zhp, proto_table[*curr_proto].p_prop,
|
|
shareopts, sizeof (shareopts), &sourcetype, sourcestr,
|
|
ZFS_MAXPROPLEN, B_FALSE) != 0 ||
|
|
strcmp(shareopts, "off") == 0)
|
|
continue;
|
|
|
|
ret = zfs_init_libshare(hdl, SA_INIT_SHARE_API);
|
|
if (ret != SA_OK) {
|
|
(void) zfs_error_fmt(hdl, EZFS_SHARENFSFAILED,
|
|
dgettext(TEXT_DOMAIN, "cannot share '%s': %s"),
|
|
zfs_get_name(zhp), sa_errorstr(ret));
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* If the 'zoned' property is set, then zfs_is_mountable()
|
|
* will have already bailed out if we are in the global zone.
|
|
* But local zones cannot be NFS servers, so we ignore it for
|
|
* local zones as well.
|
|
*/
|
|
if (zfs_prop_get_int(zhp, ZFS_PROP_ZONED))
|
|
continue;
|
|
|
|
if (*curr_proto != PROTO_NFS) {
|
|
fprintf(stderr, "Unsupported share protocol: %d.\n",
|
|
*curr_proto);
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(shareopts, "on") == 0)
|
|
err = fsshare(ZFS_EXPORTS_PATH, mountpoint, "");
|
|
else
|
|
err = fsshare(ZFS_EXPORTS_PATH, mountpoint, shareopts);
|
|
if (err != 0) {
|
|
(void) zfs_error_fmt(hdl,
|
|
proto_table[*curr_proto].p_share_err,
|
|
dgettext(TEXT_DOMAIN, "cannot share '%s'"),
|
|
zfs_get_name(zhp));
|
|
return (-1);
|
|
}
|
|
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Unshare a filesystem by mountpoint.
|
|
*/
|
|
int
|
|
unshare_one(libzfs_handle_t *hdl, const char *name, const char *mountpoint,
|
|
zfs_share_proto_t proto)
|
|
{
|
|
int err;
|
|
|
|
if (proto != PROTO_NFS) {
|
|
fprintf(stderr, "No SMB support in FreeBSD yet.\n");
|
|
return (EOPNOTSUPP);
|
|
}
|
|
|
|
err = fsunshare(ZFS_EXPORTS_PATH, mountpoint);
|
|
if (err != 0) {
|
|
zfs_error_aux(hdl, "%s", strerror(err));
|
|
return (zfs_error_fmt(hdl, EZFS_UNSHARENFSFAILED,
|
|
dgettext(TEXT_DOMAIN,
|
|
"cannot unshare '%s'"), name));
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
zfs_share_type_t
|
|
is_shared_impl(libzfs_handle_t *hdl, const char *mountpoint,
|
|
zfs_share_proto_t proto)
|
|
{
|
|
char buf[MAXPATHLEN], *tab;
|
|
|
|
if (hdl->libzfs_sharetab == NULL)
|
|
return (SHARED_NOT_SHARED);
|
|
|
|
(void) fseek(hdl->libzfs_sharetab, 0, SEEK_SET);
|
|
|
|
while (fgets(buf, sizeof (buf), hdl->libzfs_sharetab) != NULL) {
|
|
|
|
/* the mountpoint is the first entry on each line */
|
|
if ((tab = strchr(buf, '\t')) == NULL)
|
|
continue;
|
|
|
|
*tab = '\0';
|
|
if (strcmp(buf, mountpoint) == 0) {
|
|
if (proto == PROTO_NFS)
|
|
return (SHARED_NFS);
|
|
}
|
|
}
|
|
|
|
return (SHARED_NOT_SHARED);
|
|
}
|
|
|
|
static void
|
|
restart_mountd(void)
|
|
{
|
|
struct pidfh *pfh;
|
|
pid_t mountdpid;
|
|
|
|
pfh = pidfile_open(_PATH_MOUNTDPID, 0600, &mountdpid);
|
|
if (pfh != NULL) {
|
|
/* Mountd is not running. */
|
|
pidfile_remove(pfh);
|
|
return;
|
|
}
|
|
if (errno != EEXIST) {
|
|
/* Cannot open pidfile for some reason. */
|
|
return;
|
|
}
|
|
/* We have mountd(8) PID in mountdpid varible. */
|
|
kill(mountdpid, SIGHUP);
|
|
}
|
|
|
|
/*
|
|
* Read one line from a file. Skip comments, empty lines and a line with a
|
|
* mountpoint specified in the 'skip' argument.
|
|
*/
|
|
static char *
|
|
zgetline(FILE *fd, const char *skip)
|
|
{
|
|
static char line[MAXLINESIZE];
|
|
size_t len, skiplen = 0;
|
|
char *s, last;
|
|
|
|
if (skip != NULL)
|
|
skiplen = strlen(skip);
|
|
for (;;) {
|
|
s = fgets(line, sizeof (line), fd);
|
|
if (s == NULL)
|
|
return (NULL);
|
|
/* Skip empty lines and comments. */
|
|
if (line[0] == '\n' || line[0] == '#')
|
|
continue;
|
|
len = strlen(line);
|
|
if (line[len - 1] == '\n')
|
|
line[len - 1] = '\0';
|
|
last = line[skiplen];
|
|
/* Skip the given mountpoint. */
|
|
if (skip != NULL && strncmp(skip, line, skiplen) == 0 &&
|
|
(last == '\t' || last == ' ' || last == '\0')) {
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return (line);
|
|
}
|
|
/* BEGIN CSTYLED */
|
|
/*
|
|
* Function translate options to a format acceptable by exports(5), eg.
|
|
*
|
|
* -ro -network=192.168.0.0 -mask=255.255.255.0 -maproot=0 freefall.freebsd.org 69.147.83.54
|
|
*
|
|
* Accepted input formats:
|
|
*
|
|
* ro,network=192.168.0.0,mask=255.255.255.0,maproot=0,freefall.freebsd.org
|
|
* ro network=192.168.0.0 mask=255.255.255.0 maproot=0 freefall.freebsd.org
|
|
* -ro,-network=192.168.0.0,-mask=255.255.255.0,-maproot=0,freefall.freebsd.org
|
|
* -ro -network=192.168.0.0 -mask=255.255.255.0 -maproot=0 freefall.freebsd.org
|
|
*
|
|
* Recognized keywords:
|
|
*
|
|
* ro, maproot, mapall, mask, network, sec, alldirs, public, webnfs, index, quiet
|
|
*
|
|
*/
|
|
/* END CSTYLED */
|
|
|
|
static const char *known_opts[] = { "ro", "maproot", "mapall", "mask",
|
|
"network", "sec", "alldirs", "public", "webnfs", "index", "quiet",
|
|
NULL };
|
|
static char *
|
|
translate_opts(const char *shareopts)
|
|
{
|
|
static char newopts[OPTSSIZE];
|
|
char oldopts[OPTSSIZE];
|
|
char *o, *s = NULL;
|
|
unsigned int i;
|
|
size_t len;
|
|
|
|
strlcpy(oldopts, shareopts, sizeof (oldopts));
|
|
newopts[0] = '\0';
|
|
s = oldopts;
|
|
while ((o = strsep(&s, "-, ")) != NULL) {
|
|
if (o[0] == '\0')
|
|
continue;
|
|
for (i = 0; known_opts[i] != NULL; i++) {
|
|
len = strlen(known_opts[i]);
|
|
if (strncmp(known_opts[i], o, len) == 0 &&
|
|
(o[len] == '\0' || o[len] == '=')) {
|
|
strlcat(newopts, "-", sizeof (newopts));
|
|
break;
|
|
}
|
|
}
|
|
strlcat(newopts, o, sizeof (newopts));
|
|
strlcat(newopts, " ", sizeof (newopts));
|
|
}
|
|
return (newopts);
|
|
}
|
|
|
|
static int
|
|
fsshare_main(const char *file, const char *mountpoint, const char *shareopts,
|
|
int share)
|
|
{
|
|
char tmpfile[PATH_MAX];
|
|
char *line;
|
|
FILE *newfd, *oldfd;
|
|
int fd, error;
|
|
|
|
newfd = oldfd = NULL;
|
|
error = 0;
|
|
|
|
/*
|
|
* Create temporary file in the same directory, so we can atomically
|
|
* rename it.
|
|
*/
|
|
if (strlcpy(tmpfile, file, sizeof (tmpfile)) >= sizeof (tmpfile))
|
|
return (ENAMETOOLONG);
|
|
if (strlcat(tmpfile, ".XXXXXXXX", sizeof (tmpfile)) >= sizeof (tmpfile))
|
|
return (ENAMETOOLONG);
|
|
fd = mkstemp(tmpfile);
|
|
if (fd == -1)
|
|
return (errno);
|
|
/*
|
|
* File name is random, so we don't really need file lock now, but it
|
|
* will be needed after rename(2).
|
|
*/
|
|
error = flock(fd, LOCK_EX);
|
|
assert(error == 0 || (error == -1 && errno == EOPNOTSUPP));
|
|
newfd = fdopen(fd, "r+");
|
|
assert(newfd != NULL);
|
|
/* Open old exports file. */
|
|
oldfd = fopen(file, "r");
|
|
if (oldfd == NULL) {
|
|
if (share) {
|
|
if (errno != ENOENT) {
|
|
error = errno;
|
|
goto out;
|
|
}
|
|
} else {
|
|
/* If there is no exports file, ignore the error. */
|
|
if (errno == ENOENT)
|
|
errno = 0;
|
|
error = errno;
|
|
goto out;
|
|
}
|
|
} else {
|
|
error = flock(fileno(oldfd), LOCK_EX);
|
|
assert(error == 0 || (error == -1 && errno == EOPNOTSUPP));
|
|
error = 0;
|
|
}
|
|
|
|
/* Place big, fat warning at the begining of the file. */
|
|
fprintf(newfd, "%s", FILE_HEADER);
|
|
while (oldfd != NULL && (line = zgetline(oldfd, mountpoint)) != NULL)
|
|
fprintf(newfd, "%s\n", line);
|
|
if (oldfd != NULL && ferror(oldfd) != 0) {
|
|
error = ferror(oldfd);
|
|
goto out;
|
|
}
|
|
if (ferror(newfd) != 0) {
|
|
error = ferror(newfd);
|
|
goto out;
|
|
}
|
|
if (share) {
|
|
fprintf(newfd, "%s\t%s\n", mountpoint,
|
|
translate_opts(shareopts));
|
|
}
|
|
|
|
out:
|
|
if (error != 0)
|
|
unlink(tmpfile);
|
|
else {
|
|
if (rename(tmpfile, file) == -1) {
|
|
error = errno;
|
|
unlink(tmpfile);
|
|
} else {
|
|
fflush(newfd);
|
|
/*
|
|
* Send SIGHUP to mountd, but unlock exports file later.
|
|
*/
|
|
restart_mountd();
|
|
}
|
|
}
|
|
if (oldfd != NULL) {
|
|
flock(fileno(oldfd), LOCK_UN);
|
|
fclose(oldfd);
|
|
}
|
|
if (newfd != NULL) {
|
|
flock(fileno(newfd), LOCK_UN);
|
|
fclose(newfd);
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Add the given mountpoint to the given exports file.
|
|
*/
|
|
int
|
|
fsshare(const char *file, const char *mountpoint, const char *shareopts)
|
|
{
|
|
|
|
return (fsshare_main(file, mountpoint, shareopts, 1));
|
|
}
|
|
|
|
/*
|
|
* Remove the given mountpoint from the given exports file.
|
|
*/
|
|
int
|
|
fsunshare(const char *file, const char *mountpoint)
|
|
{
|
|
|
|
return (fsshare_main(file, mountpoint, NULL, 0));
|
|
}
|