Path: blob/main/sys/contrib/openzfs/lib/libshare/os/linux/nfs.c
48546 views
// SPDX-License-Identifier: CDDL-1.01/*2* CDDL HEADER START3*4* The contents of this file are subject to the terms of the5* Common Development and Distribution License (the "License").6* You may not use this file except in compliance with the License.7*8* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE9* or https://opensource.org/licenses/CDDL-1.0.10* See the License for the specific language governing permissions11* and limitations under the License.12*13* When distributing Covered Code, include this CDDL HEADER in each14* file and include the License file at usr/src/OPENSOLARIS.LICENSE.15* If applicable, add the following below this CDDL HEADER, with the16* fields enclosed by brackets "[]" replaced with your own identifying17* information: Portions Copyright [yyyy] [name of copyright owner]18*19* CDDL HEADER END20*/2122/*23* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.24* Copyright (c) 2011 Gunnar Beutner25* Copyright (c) 2012 Cyril Plisko. All rights reserved.26* Copyright (c) 2019, 2022 by Delphix. All rights reserved.27*/2829#include <dirent.h>30#include <stdio.h>31#include <string.h>32#include <errno.h>33#include <fcntl.h>34#include <sys/file.h>35#include <sys/stat.h>36#include <sys/types.h>37#include <sys/wait.h>38#include <unistd.h>39#include <libzfs.h>40#include <libshare.h>41#include "libshare_impl.h"42#include "nfs.h"4344#define ZFS_EXPORTS_DIR "/etc/exports.d"45#define ZFS_EXPORTS_FILE ZFS_EXPORTS_DIR"/zfs.exports"46#define ZFS_EXPORTS_LOCK ZFS_EXPORTS_FILE".lock"474849static boolean_t nfs_available(void);50static boolean_t exports_available(void);5152typedef int (*nfs_shareopt_callback_t)(const char *opt, const char *value,53void *cookie);5455typedef int (*nfs_host_callback_t)(FILE *tmpfile, const char *sharepath,56const char *host, const char *security, const char *access, void *cookie);5758/*59* Invokes the specified callback function for each Solaris share option60* listed in the specified string.61*/62static int63foreach_nfs_shareopt(const char *shareopts,64nfs_shareopt_callback_t callback, void *cookie)65{66char *shareopts_dup, *opt, *cur, *value;67int was_nul, error;6869if (shareopts == NULL)70return (SA_OK);7172if (strcmp(shareopts, "on") == 0)73shareopts = "rw,crossmnt";7475shareopts_dup = strdup(shareopts);767778if (shareopts_dup == NULL)79return (SA_NO_MEMORY);8081opt = shareopts_dup;82was_nul = 0;8384while (1) {85cur = opt;8687while (*cur != ',' && *cur != '\0')88cur++;8990if (*cur == '\0')91was_nul = 1;9293*cur = '\0';9495if (cur > opt) {96value = strchr(opt, '=');9798if (value != NULL) {99*value = '\0';100value++;101}102103error = callback(opt, value, cookie);104105if (error != SA_OK) {106free(shareopts_dup);107return (error);108}109}110111opt = cur + 1;112113if (was_nul)114break;115}116117free(shareopts_dup);118119return (SA_OK);120}121122typedef struct nfs_host_cookie_s {123nfs_host_callback_t callback;124const char *sharepath;125void *cookie;126FILE *tmpfile;127const char *security;128} nfs_host_cookie_t;129130/*131* Helper function for foreach_nfs_host. This function checks whether the132* current share option is a host specification and invokes a callback133* function with information about the host.134*/135static int136foreach_nfs_host_cb(const char *opt, const char *value, void *pcookie)137{138int error;139const char *access;140char *host_dup, *host, *next, *v6Literal;141nfs_host_cookie_t *udata = (nfs_host_cookie_t *)pcookie;142int cidr_len;143144#ifdef DEBUG145fprintf(stderr, "foreach_nfs_host_cb: key=%s, value=%s\n", opt, value);146#endif147148if (strcmp(opt, "sec") == 0)149udata->security = value;150151if (strcmp(opt, "rw") == 0 || strcmp(opt, "ro") == 0) {152if (value == NULL)153value = "*";154155access = opt;156157host_dup = strdup(value);158159if (host_dup == NULL)160return (SA_NO_MEMORY);161162host = host_dup;163164do {165if (*host == '[') {166host++;167v6Literal = strchr(host, ']');168if (v6Literal == NULL) {169free(host_dup);170return (SA_SYNTAX_ERR);171}172if (v6Literal[1] == '\0') {173*v6Literal = '\0';174next = NULL;175} else if (v6Literal[1] == '/') {176next = strchr(v6Literal + 2, ':');177if (next == NULL) {178cidr_len =179strlen(v6Literal + 1);180memmove(v6Literal,181v6Literal + 1,182cidr_len);183v6Literal[cidr_len] = '\0';184} else {185cidr_len = next - v6Literal - 1;186memmove(v6Literal,187v6Literal + 1,188cidr_len);189v6Literal[cidr_len] = '\0';190next++;191}192} else if (v6Literal[1] == ':') {193*v6Literal = '\0';194next = v6Literal + 2;195} else {196free(host_dup);197return (SA_SYNTAX_ERR);198}199} else {200next = strchr(host, ':');201if (next != NULL) {202*next = '\0';203next++;204}205}206207error = udata->callback(udata->tmpfile,208udata->sharepath, host, udata->security,209access, udata->cookie);210211if (error != SA_OK) {212free(host_dup);213214return (error);215}216217host = next;218} while (host != NULL);219220free(host_dup);221}222223return (SA_OK);224}225226/*227* Invokes a callback function for all NFS hosts that are set for a share.228*/229static int230foreach_nfs_host(sa_share_impl_t impl_share, FILE *tmpfile,231nfs_host_callback_t callback, void *cookie)232{233nfs_host_cookie_t udata;234235udata.callback = callback;236udata.sharepath = impl_share->sa_mountpoint;237udata.cookie = cookie;238udata.tmpfile = tmpfile;239udata.security = "sys";240241return (foreach_nfs_shareopt(impl_share->sa_shareopts,242foreach_nfs_host_cb, &udata));243}244245/*246* Converts a Solaris NFS host specification to its Linux equivalent.247*/248static const char *249get_linux_hostspec(const char *solaris_hostspec)250{251/*252* For now we just support CIDR masks (e.g. @192.168.0.0/16) and host253* wildcards (e.g. *.example.org).254*/255if (solaris_hostspec[0] == '@') {256/*257* Solaris host specifier, e.g. @192.168.0.0/16; we just need258* to skip the @ in this case259*/260return (solaris_hostspec + 1);261} else {262return (solaris_hostspec);263}264}265266/*267* Adds a Linux share option to an array of NFS options.268*/269static int270add_linux_shareopt(char **plinux_opts, const char *key, const char *value)271{272size_t len = 0;273char *new_linux_opts;274275if (*plinux_opts != NULL)276len = strlen(*plinux_opts);277278new_linux_opts = realloc(*plinux_opts, len + 1 + strlen(key) +279(value ? 1 + strlen(value) : 0) + 1);280281if (new_linux_opts == NULL)282return (SA_NO_MEMORY);283284new_linux_opts[len] = '\0';285286if (len > 0)287strcat(new_linux_opts, ",");288289strcat(new_linux_opts, key);290291if (value != NULL) {292strcat(new_linux_opts, "=");293strcat(new_linux_opts, value);294}295296*plinux_opts = new_linux_opts;297298return (SA_OK);299}300301static int string_cmp(const void *lhs, const void *rhs) {302const char *const *l = lhs, *const *r = rhs;303return (strcmp(*l, *r));304}305306/*307* Validates and converts a single Solaris share option to its Linux308* equivalent.309*/310static int311get_linux_shareopts_cb(const char *key, const char *value, void *cookie)312{313/* This list must remain sorted, since we bsearch() it */314static const char *const valid_keys[] = { "all_squash", "anongid",315"anonuid", "async", "auth_nlm", "crossmnt", "fsid", "fsuid", "hide",316"insecure", "insecure_locks", "mountpoint", "mp", "no_acl",317"no_all_squash", "no_auth_nlm", "no_root_squash",318"no_subtree_check", "no_wdelay", "nohide", "refer", "replicas",319"root_squash", "secure", "secure_locks", "subtree_check", "sync",320"wdelay" };321322char **plinux_opts = (char **)cookie;323char *host, *val_dup, *literal, *next;324325if (strcmp(key, "sec") == 0)326return (SA_OK);327328if (strcmp(key, "ro") == 0 || strcmp(key, "rw") == 0) {329if (value == NULL || strlen(value) == 0)330return (SA_OK);331val_dup = strdup(value);332host = val_dup;333if (host == NULL)334return (SA_NO_MEMORY);335do {336if (*host == '[') {337host++;338literal = strchr(host, ']');339if (literal == NULL) {340free(val_dup);341return (SA_SYNTAX_ERR);342}343if (literal[1] == '\0')344next = NULL;345else if (literal[1] == '/') {346next = strchr(literal + 2, ':');347if (next != NULL)348++next;349} else if (literal[1] == ':')350next = literal + 2;351else {352free(val_dup);353return (SA_SYNTAX_ERR);354}355} else {356next = strchr(host, ':');357if (next != NULL)358++next;359}360host = next;361} while (host != NULL);362free(val_dup);363return (SA_OK);364}365366if (strcmp(key, "anon") == 0)367key = "anonuid";368369if (strcmp(key, "root_mapping") == 0) {370(void) add_linux_shareopt(plinux_opts, "root_squash", NULL);371key = "anonuid";372}373374if (strcmp(key, "nosub") == 0)375key = "subtree_check";376377if (bsearch(&key, valid_keys, ARRAY_SIZE(valid_keys),378sizeof (*valid_keys), string_cmp) == NULL)379return (SA_SYNTAX_ERR);380381(void) add_linux_shareopt(plinux_opts, key, value);382383return (SA_OK);384}385386/*387* Takes a string containing Solaris share options (e.g. "sync,no_acl") and388* converts them to a NULL-terminated array of Linux NFS options.389*/390static int391get_linux_shareopts(const char *shareopts, char **plinux_opts)392{393int error;394395assert(plinux_opts != NULL);396397*plinux_opts = NULL;398399/* no_subtree_check - Default as of nfs-utils v1.1.0 */400(void) add_linux_shareopt(plinux_opts, "no_subtree_check", NULL);401402/* mountpoint - Restrict exports to ZFS mountpoints */403(void) add_linux_shareopt(plinux_opts, "mountpoint", NULL);404405error = foreach_nfs_shareopt(shareopts, get_linux_shareopts_cb,406plinux_opts);407408if (error != SA_OK) {409free(*plinux_opts);410*plinux_opts = NULL;411}412413return (error);414}415416/*417* This function populates an entry into /etc/exports.d/zfs.exports.418* This file is consumed by the linux nfs server so that zfs shares are419* automatically exported upon boot or whenever the nfs server restarts.420*/421static int422nfs_add_entry(FILE *tmpfile, const char *sharepath,423const char *host, const char *security, const char *access_opts,424void *pcookie)425{426const char *linux_opts = (const char *)pcookie;427428if (linux_opts == NULL)429linux_opts = "";430431boolean_t need_free;432char *mp;433int rc = nfs_escape_mountpoint(sharepath, &mp, &need_free);434if (rc != SA_OK)435return (rc);436if (fprintf(tmpfile, "%s %s(sec=%s,%s,%s)\n", mp,437get_linux_hostspec(host), security, access_opts,438linux_opts) < 0) {439fprintf(stderr, "failed to write to temporary file\n");440rc = SA_SYSTEM_ERR;441}442443if (need_free)444free(mp);445return (rc);446}447448/*449* Enables NFS sharing for the specified share.450*/451static int452nfs_enable_share_impl(sa_share_impl_t impl_share, FILE *tmpfile)453{454char *linux_opts = NULL;455int error = get_linux_shareopts(impl_share->sa_shareopts, &linux_opts);456if (error != SA_OK)457return (error);458459error = foreach_nfs_host(impl_share, tmpfile, nfs_add_entry,460linux_opts);461free(linux_opts);462return (error);463}464465static int466nfs_enable_share(sa_share_impl_t impl_share)467{468if (!nfs_available())469return (SA_SYSTEM_ERR);470471return (nfs_toggle_share(472ZFS_EXPORTS_LOCK, ZFS_EXPORTS_FILE, ZFS_EXPORTS_DIR, impl_share,473nfs_enable_share_impl));474}475476/*477* Disables NFS sharing for the specified share.478*/479static int480nfs_disable_share_impl(sa_share_impl_t impl_share, FILE *tmpfile)481{482(void) impl_share, (void) tmpfile;483return (SA_OK);484}485486static int487nfs_disable_share(sa_share_impl_t impl_share)488{489if (!nfs_available())490return (SA_OK);491492return (nfs_toggle_share(493ZFS_EXPORTS_LOCK, ZFS_EXPORTS_FILE, ZFS_EXPORTS_DIR, impl_share,494nfs_disable_share_impl));495}496497static boolean_t498nfs_is_shared(sa_share_impl_t impl_share)499{500if (!nfs_available())501return (SA_SYSTEM_ERR);502503return (nfs_is_shared_impl(ZFS_EXPORTS_FILE, impl_share));504}505506/*507* Checks whether the specified NFS share options are syntactically correct.508*/509static int510nfs_validate_shareopts(const char *shareopts)511{512char *linux_opts = NULL;513514if (strlen(shareopts) == 0)515return (SA_SYNTAX_ERR);516517int error = get_linux_shareopts(shareopts, &linux_opts);518if (error != SA_OK)519return (error);520521free(linux_opts);522return (SA_OK);523}524525static int526nfs_commit_shares(void)527{528if (!nfs_available())529return (SA_SYSTEM_ERR);530531char *argv[] = {532(char *)"/usr/sbin/exportfs",533(char *)"-ra",534NULL535};536537return (libzfs_run_process(argv[0], argv, 0));538}539540static void541nfs_truncate_shares(void)542{543if (!exports_available())544return;545nfs_reset_shares(ZFS_EXPORTS_LOCK, ZFS_EXPORTS_FILE);546}547548const sa_fstype_t libshare_nfs_type = {549.enable_share = nfs_enable_share,550.disable_share = nfs_disable_share,551.is_shared = nfs_is_shared,552553.validate_shareopts = nfs_validate_shareopts,554.commit_shares = nfs_commit_shares,555.truncate_shares = nfs_truncate_shares,556};557558static boolean_t559nfs_available(void)560{561static int avail;562563if (!avail) {564if (access("/usr/sbin/exportfs", F_OK) != 0)565avail = -1;566else567avail = 1;568}569570return (avail == 1);571}572573static boolean_t574exports_available(void)575{576static int avail;577578if (!avail) {579if (access(ZFS_EXPORTS_DIR, F_OK) != 0)580avail = -1;581else582avail = 1;583}584585return (avail == 1);586}587588589