diff options
Diffstat (limited to 'sys/contrib/openzfs/lib/libshare/nfs.c')
-rw-r--r-- | sys/contrib/openzfs/lib/libshare/nfs.c | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/sys/contrib/openzfs/lib/libshare/nfs.c b/sys/contrib/openzfs/lib/libshare/nfs.c new file mode 100644 index 000000000000..e4c5b904f51f --- /dev/null +++ b/sys/contrib/openzfs/lib/libshare/nfs.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: CDDL-1.0 +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or https://opensource.org/licenses/CDDL-1.0. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <fcntl.h> +#include <ctype.h> +#include <stdio.h> +#include <errno.h> +#include <libshare.h> +#include <unistd.h> +#include <libzutil.h> +#include "nfs.h" + + +/* + * nfs_exports_[lock|unlock] are used to guard against conconcurrent + * updates to the exports file. Each protocol is responsible for + * providing the necessary locking to ensure consistency. + */ +static int +nfs_exports_lock(const char *name, int *nfs_lock_fd) +{ + int err; + + *nfs_lock_fd = open(name, O_RDWR | O_CREAT | O_CLOEXEC, 0600); + if (*nfs_lock_fd == -1) { + err = errno; + fprintf(stderr, "failed to lock %s: %s\n", name, + zfs_strerror(err)); + return (err); + } + + while ((err = flock(*nfs_lock_fd, LOCK_EX)) != 0 && errno == EINTR) + ; + if (err != 0) { + err = errno; + fprintf(stderr, "failed to lock %s: %s\n", name, + zfs_strerror(err)); + (void) close(*nfs_lock_fd); + *nfs_lock_fd = -1; + return (err); + } + + return (0); +} + +static void +nfs_exports_unlock(const char *name, int *nfs_lock_fd) +{ + verify(*nfs_lock_fd > 0); + + if (flock(*nfs_lock_fd, LOCK_UN) != 0) + fprintf(stderr, "failed to unlock %s: %s\n", + name, zfs_strerror(errno)); + + (void) close(*nfs_lock_fd); + *nfs_lock_fd = -1; +} + +struct tmpfile { + /* + * This only needs to be as wide as ZFS_EXPORTS_FILE and mktemp suffix, + * 64 is more than enough. + */ + char name[64]; + FILE *fp; +}; + +static boolean_t +nfs_init_tmpfile(const char *prefix, const char *mdir, struct tmpfile *tmpf) +{ + if (mdir != NULL && + mkdir(mdir, 0755) < 0 && + errno != EEXIST) { + fprintf(stderr, "failed to create %s: %s\n", + // cppcheck-suppress uninitvar + mdir, zfs_strerror(errno)); + return (B_FALSE); + } + + strlcpy(tmpf->name, prefix, sizeof (tmpf->name)); + strlcat(tmpf->name, ".XXXXXXXX", sizeof (tmpf->name)); + + int fd = mkostemp(tmpf->name, O_CLOEXEC); + if (fd == -1) { + fprintf(stderr, "Unable to create temporary file: %s", + zfs_strerror(errno)); + return (B_FALSE); + } + + tmpf->fp = fdopen(fd, "w+"); + if (tmpf->fp == NULL) { + fprintf(stderr, "Unable to reopen temporary file: %s", + zfs_strerror(errno)); + close(fd); + return (B_FALSE); + } + + return (B_TRUE); +} + +static void +nfs_abort_tmpfile(struct tmpfile *tmpf) +{ + unlink(tmpf->name); + fclose(tmpf->fp); +} + +static int +nfs_fini_tmpfile(const char *exports, struct tmpfile *tmpf) +{ + if (fflush(tmpf->fp) != 0) { + fprintf(stderr, "Failed to write to temporary file: %s\n", + zfs_strerror(errno)); + nfs_abort_tmpfile(tmpf); + return (SA_SYSTEM_ERR); + } + + if (rename(tmpf->name, exports) == -1) { + fprintf(stderr, "Unable to rename %s -> %s: %s\n", + tmpf->name, exports, zfs_strerror(errno)); + nfs_abort_tmpfile(tmpf); + return (SA_SYSTEM_ERR); + } + + (void) fchmod(fileno(tmpf->fp), 0644); + fclose(tmpf->fp); + return (SA_OK); +} + +int +nfs_escape_mountpoint(const char *mp, char **out, boolean_t *need_free) +{ + if (strpbrk(mp, "\t\n\v\f\r \\") == NULL) { + *out = (char *)mp; + *need_free = B_FALSE; + return (SA_OK); + } else { + size_t len = strlen(mp); + *out = malloc(len * 4 + 1); + if (!*out) + return (SA_NO_MEMORY); + *need_free = B_TRUE; + + char *oc = *out; + for (const char *c = mp; c < mp + len; ++c) + if (memchr("\t\n\v\f\r \\", *c, + strlen("\t\n\v\f\r \\"))) { + sprintf(oc, "\\%03hho", *c); + oc += 4; + } else + *oc++ = *c; + *oc = '\0'; + } + + return (SA_OK); +} + +static int +nfs_process_exports(const char *exports, const char *mountpoint, + boolean_t (*cbk)(void *userdata, char *line, boolean_t found_mountpoint), + void *userdata) +{ + int error = SA_OK; + boolean_t cont = B_TRUE; + + FILE *oldfp = fopen(exports, "re"); + if (oldfp != NULL) { + boolean_t need_mp_free; + char *mp; + if ((error = nfs_escape_mountpoint(mountpoint, + &mp, &need_mp_free)) != SA_OK) { + (void) fclose(oldfp); + return (error); + } + + char *buf = NULL, *sep; + size_t buflen = 0, mplen = strlen(mp); + + while (cont && getline(&buf, &buflen, oldfp) != -1) { + if (buf[0] == '\n' || buf[0] == '#') + continue; + + cont = cbk(userdata, buf, + (sep = strpbrk(buf, "\t \n")) != NULL && + sep - buf == mplen && + strncmp(buf, mp, mplen) == 0); + } + free(buf); + if (need_mp_free) + free(mp); + + if (ferror(oldfp) != 0) + error = ferror(oldfp); + + if (fclose(oldfp) != 0) { + fprintf(stderr, "Unable to close file %s: %s\n", + exports, zfs_strerror(errno)); + error = error != SA_OK ? error : SA_SYSTEM_ERR; + } + } + + return (error); +} + +static boolean_t +nfs_copy_entries_cb(void *userdata, char *line, boolean_t found_mountpoint) +{ + FILE *newfp = userdata; + if (!found_mountpoint) + fputs(line, newfp); + return (B_TRUE); +} + +/* + * Copy all entries from the exports file (if it exists) to newfp, + * omitting any entries for the specified mountpoint. + */ +static int +nfs_copy_entries(FILE *newfp, const char *exports, const char *mountpoint) +{ + fputs(FILE_HEADER, newfp); + + int error = nfs_process_exports( + exports, mountpoint, nfs_copy_entries_cb, newfp); + + if (error == SA_OK && ferror(newfp) != 0) + error = ferror(newfp); + + return (error); +} + +int +nfs_toggle_share(const char *lockfile, const char *exports, + const char *expdir, sa_share_impl_t impl_share, + int(*cbk)(sa_share_impl_t impl_share, FILE *tmpfile)) +{ + int error, nfs_lock_fd = -1; + struct tmpfile tmpf; + + if (!nfs_init_tmpfile(exports, expdir, &tmpf)) + return (SA_SYSTEM_ERR); + + error = nfs_exports_lock(lockfile, &nfs_lock_fd); + if (error != 0) { + nfs_abort_tmpfile(&tmpf); + return (error); + } + + error = nfs_copy_entries(tmpf.fp, exports, impl_share->sa_mountpoint); + if (error != SA_OK) + goto fullerr; + + error = cbk(impl_share, tmpf.fp); + if (error != SA_OK) + goto fullerr; + + error = nfs_fini_tmpfile(exports, &tmpf); + nfs_exports_unlock(lockfile, &nfs_lock_fd); + return (error); + +fullerr: + nfs_abort_tmpfile(&tmpf); + nfs_exports_unlock(lockfile, &nfs_lock_fd); + return (error); +} + +void +nfs_reset_shares(const char *lockfile, const char *exports) +{ + int nfs_lock_fd = -1; + + if (nfs_exports_lock(lockfile, &nfs_lock_fd) == 0) { + (void) ! truncate(exports, 0); + nfs_exports_unlock(lockfile, &nfs_lock_fd); + } +} + +static boolean_t +nfs_is_shared_cb(void *userdata, char *line, boolean_t found_mountpoint) +{ + (void) line; + + boolean_t *found = userdata; + *found = found_mountpoint; + return (!found_mountpoint); +} + +boolean_t +nfs_is_shared_impl(const char *exports, sa_share_impl_t impl_share) +{ + boolean_t found = B_FALSE; + nfs_process_exports(exports, impl_share->sa_mountpoint, + nfs_is_shared_cb, &found); + return (found); +} |