From 4897093455a2bf08f3db3a1132cc2f6f5484d77c Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 08:03:02 +0200 Subject: Adding upstream version 1:2.6.4. Signed-off-by: Daniel Baumann --- utils/mount/Makefile.am | 82 ++ utils/mount/Makefile.in | 976 ++++++++++++++++++++ utils/mount/configfile.c | 347 ++++++++ utils/mount/error.c | 360 ++++++++ utils/mount/error.h | 35 + utils/mount/fstab.c | 653 ++++++++++++++ utils/mount/fstab.h | 32 + utils/mount/mount.c | 558 ++++++++++++ utils/mount/mount.nfs.man | 93 ++ utils/mount/mount_config.h | 55 ++ utils/mount/mount_constants.h | 71 ++ utils/mount/mount_libmount.c | 461 ++++++++++ utils/mount/network.c | 1801 +++++++++++++++++++++++++++++++++++++ utils/mount/network.h | 96 ++ utils/mount/nfs.man | 1968 +++++++++++++++++++++++++++++++++++++++++ utils/mount/nfs4_mount.h | 73 ++ utils/mount/nfs4mount.c | 481 ++++++++++ utils/mount/nfs_mount.h | 83 ++ utils/mount/nfsmount.c | 873 ++++++++++++++++++ utils/mount/nfsmount.conf | 144 +++ utils/mount/nfsmount.conf.man | 131 +++ utils/mount/nfsumount.c | 351 ++++++++ utils/mount/parse_dev.c | 233 +++++ utils/mount/parse_dev.h | 28 + utils/mount/parse_opt.c | 610 +++++++++++++ utils/mount/parse_opt.h | 60 ++ utils/mount/stropts.c | 1310 +++++++++++++++++++++++++++ utils/mount/stropts.h | 30 + utils/mount/token.c | 157 ++++ utils/mount/token.h | 34 + utils/mount/umount.nfs.man | 70 ++ utils/mount/utils.c | 175 ++++ utils/mount/utils.h | 36 + utils/mount/version.h | 53 ++ 34 files changed, 12520 insertions(+) create mode 100644 utils/mount/Makefile.am create mode 100644 utils/mount/Makefile.in create mode 100644 utils/mount/configfile.c create mode 100644 utils/mount/error.c create mode 100644 utils/mount/error.h create mode 100644 utils/mount/fstab.c create mode 100644 utils/mount/fstab.h create mode 100644 utils/mount/mount.c create mode 100644 utils/mount/mount.nfs.man create mode 100644 utils/mount/mount_config.h create mode 100644 utils/mount/mount_constants.h create mode 100644 utils/mount/mount_libmount.c create mode 100644 utils/mount/network.c create mode 100644 utils/mount/network.h create mode 100644 utils/mount/nfs.man create mode 100644 utils/mount/nfs4_mount.h create mode 100644 utils/mount/nfs4mount.c create mode 100644 utils/mount/nfs_mount.h create mode 100644 utils/mount/nfsmount.c create mode 100644 utils/mount/nfsmount.conf create mode 100644 utils/mount/nfsmount.conf.man create mode 100644 utils/mount/nfsumount.c create mode 100644 utils/mount/parse_dev.c create mode 100644 utils/mount/parse_dev.h create mode 100644 utils/mount/parse_opt.c create mode 100644 utils/mount/parse_opt.h create mode 100644 utils/mount/stropts.c create mode 100644 utils/mount/stropts.h create mode 100644 utils/mount/token.c create mode 100644 utils/mount/token.h create mode 100644 utils/mount/umount.nfs.man create mode 100644 utils/mount/utils.c create mode 100644 utils/mount/utils.h create mode 100644 utils/mount/version.h (limited to 'utils/mount') diff --git a/utils/mount/Makefile.am b/utils/mount/Makefile.am new file mode 100644 index 0000000..5ff1148 --- /dev/null +++ b/utils/mount/Makefile.am @@ -0,0 +1,82 @@ +## Process this file with automake to produce Makefile.in + +# These binaries go in /sbin (not /usr/sbin), unless CONFIG_SBIN_OVERRIDE +# is disabled as may be appropriate when /sbin is a symlink. +# Note that we don't use "if CONFIG_SBIN_OVERRIDE" as that +# causes autotools to notice the override and disable it. +@CONFIG_SBIN_OVERRIDE_TRUE@sbindir = /sbin + +man8_MANS = mount.nfs.man umount.nfs.man +man5_MANS = nfs.man + +sbin_PROGRAMS = mount.nfs +EXTRA_DIST = nfsmount.conf $(man8_MANS) $(man5_MANS) +mount_common = error.c network.c token.c \ + parse_opt.c parse_dev.c \ + nfsmount.c nfs4mount.c stropts.c\ + mount_constants.h error.h network.h token.h \ + parse_opt.h parse_dev.h \ + nfs4_mount.h stropts.h version.h \ + mount_config.h utils.c utils.h \ + nfs_mount.h + +if MOUNT_CONFIG +mount_common += configfile.c +man5_MANS += nfsmount.conf.man +else +EXTRA_DIST += nfsmount.conf.man +endif + +mount_nfs_LDADD = ../../support/nfs/libnfs.la \ + ../../support/export/libexport.a \ + ../../support/reexport/libreexport.a \ + ../../support/misc/libmisc.a \ + $(LIBTIRPC) $(LIBPTHREAD) + +mount_nfs_SOURCES = $(mount_common) + +if CONFIG_LIBMOUNT +mount_nfs_SOURCES += mount_libmount.c +mount_nfs_LDADD += $(LIBMOUNT) +else +mount_nfs_SOURCES += mount.c fstab.c nfsumount.c fstab.h + +endif + +MAINTAINERCLEANFILES = Makefile.in + +install-exec-hook: + (cd $(DESTDIR)$(sbindir) && \ + ln -sf mount.nfs mount.nfs4 && \ + ln -sf mount.nfs umount.nfs && \ + ln -sf mount.nfs umount.nfs4 && \ + chmod 4511 mount.nfs ) +uninstall-hook: + (cd $(DESTDIR)$(sbindir) && \ + rm -f mount.nfs4 umount.nfs umount.nfs4) + + +install-man-links: + (cd $(DESTDIR)$(man8dir) && \ + for m in $(man8_MANS) $(dist_man8_MANS) $(nodist_man8_MANS); do \ + inst=`echo $$m | sed -e 's/man$$/8/'`; \ + rm -f $$inst ; \ + done) + (cd $(DESTDIR)$(man5dir) && \ + for m in $(man5_MANS) $(dist_man5_MANS) $(nodist_man5_MANS); do \ + inst=`echo $$m | sed -e 's/man$$/5/'`; \ + rm -f $$inst ; \ + done) + +uninstall-man-links: + (cd $(DESTDIR)$(man8dir) && \ + for m in $(man8_MANS) $(dist_man8_MANS) $(nodist_man8_MANS); do \ + inst=`echo $$m | sed -e 's/man$$/8/'`; \ + rm -f $$inst ; \ + done) + (cd $(DESTDIR)$(man5dir) && \ + for m in $(man5_MANS) $(dist_man5_MANS) $(nodist_man5_MANS); do \ + inst=`echo $$m | sed -e 's/man$$/5/'`; \ + rm -f $$inst ; \ + done) + diff --git a/utils/mount/Makefile.in b/utils/mount/Makefile.in new file mode 100644 index 0000000..5bbbb5d --- /dev/null +++ b/utils/mount/Makefile.in @@ -0,0 +1,976 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +sbin_PROGRAMS = mount.nfs$(EXEEXT) +@MOUNT_CONFIG_TRUE@am__append_1 = configfile.c +@MOUNT_CONFIG_TRUE@am__append_2 = nfsmount.conf.man +@MOUNT_CONFIG_FALSE@am__append_3 = nfsmount.conf.man +@CONFIG_LIBMOUNT_TRUE@am__append_4 = mount_libmount.c +@CONFIG_LIBMOUNT_TRUE@am__append_5 = $(LIBMOUNT) +@CONFIG_LIBMOUNT_FALSE@am__append_6 = mount.c fstab.c nfsumount.c fstab.h +subdir = utils/mount +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/aclocal/ax_gcc_func_attribute.m4 \ + $(top_srcdir)/aclocal/bsdsignals.m4 \ + $(top_srcdir)/aclocal/getrandom.m4 \ + $(top_srcdir)/aclocal/ipv6.m4 \ + $(top_srcdir)/aclocal/kerberos5.m4 \ + $(top_srcdir)/aclocal/keyutils.m4 \ + $(top_srcdir)/aclocal/libblkid.m4 \ + $(top_srcdir)/aclocal/libcap.m4 \ + $(top_srcdir)/aclocal/libevent.m4 \ + $(top_srcdir)/aclocal/libpthread.m4 \ + $(top_srcdir)/aclocal/libsqlite3.m4 \ + $(top_srcdir)/aclocal/libtirpc.m4 \ + $(top_srcdir)/aclocal/libxml2.m4 \ + $(top_srcdir)/aclocal/nfs-utils.m4 \ + $(top_srcdir)/aclocal/rpcsec_vers.m4 \ + $(top_srcdir)/aclocal/tcp-wrappers.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/support/include/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(man5dir)" \ + "$(DESTDIR)$(man8dir)" +PROGRAMS = $(sbin_PROGRAMS) +am__mount_nfs_SOURCES_DIST = error.c network.c token.c parse_opt.c \ + parse_dev.c nfsmount.c nfs4mount.c stropts.c mount_constants.h \ + error.h network.h token.h parse_opt.h parse_dev.h nfs4_mount.h \ + stropts.h version.h mount_config.h utils.c utils.h nfs_mount.h \ + configfile.c mount_libmount.c mount.c fstab.c nfsumount.c \ + fstab.h +@MOUNT_CONFIG_TRUE@am__objects_1 = configfile.$(OBJEXT) +am__objects_2 = error.$(OBJEXT) network.$(OBJEXT) token.$(OBJEXT) \ + parse_opt.$(OBJEXT) parse_dev.$(OBJEXT) nfsmount.$(OBJEXT) \ + nfs4mount.$(OBJEXT) stropts.$(OBJEXT) utils.$(OBJEXT) \ + $(am__objects_1) +@CONFIG_LIBMOUNT_TRUE@am__objects_3 = mount_libmount.$(OBJEXT) +@CONFIG_LIBMOUNT_FALSE@am__objects_4 = mount.$(OBJEXT) fstab.$(OBJEXT) \ +@CONFIG_LIBMOUNT_FALSE@ nfsumount.$(OBJEXT) +am_mount_nfs_OBJECTS = $(am__objects_2) $(am__objects_3) \ + $(am__objects_4) +mount_nfs_OBJECTS = $(am_mount_nfs_OBJECTS) +am__DEPENDENCIES_1 = +@CONFIG_LIBMOUNT_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) +mount_nfs_DEPENDENCIES = ../../support/nfs/libnfs.la \ + ../../support/export/libexport.a \ + ../../support/reexport/libreexport.a \ + ../../support/misc/libmisc.a $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/support/include +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/configfile.Po ./$(DEPDIR)/error.Po \ + ./$(DEPDIR)/fstab.Po ./$(DEPDIR)/mount.Po \ + ./$(DEPDIR)/mount_libmount.Po ./$(DEPDIR)/network.Po \ + ./$(DEPDIR)/nfs4mount.Po ./$(DEPDIR)/nfsmount.Po \ + ./$(DEPDIR)/nfsumount.Po ./$(DEPDIR)/parse_dev.Po \ + ./$(DEPDIR)/parse_opt.Po ./$(DEPDIR)/stropts.Po \ + ./$(DEPDIR)/token.Po ./$(DEPDIR)/utils.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(mount_nfs_SOURCES) +DIST_SOURCES = $(am__mount_nfs_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +man5dir = $(mandir)/man5 +man8dir = $(mandir)/man8 +NROFF = nroff +MANS = $(man5_MANS) $(man8_MANS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +ALLOCA = @ALLOCA@ +AMTAR = @AMTAR@ +AM_CFLAGS = @AM_CFLAGS@ +AM_CPPFLAGS = @AM_CPPFLAGS@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CC_FOR_BUILD = @CC_FOR_BUILD@ +CFLAGS = @CFLAGS@ +CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CPPFLAGS_FOR_BUILD = @CPPFLAGS_FOR_BUILD@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CXXFLAGS_FOR_BUILD = @CXXFLAGS_FOR_BUILD@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FILECMD = @FILECMD@ +GREP = @GREP@ +GSSAPI_CFLAGS = @GSSAPI_CFLAGS@ +GSSAPI_LIBS = @GSSAPI_LIBS@ +GSSD = @GSSD@ +GSSGLUE_CFLAGS = @GSSGLUE_CFLAGS@ +GSSGLUE_LIBS = @GSSGLUE_LIBS@ +GSSKRB_CFLAGS = @GSSKRB_CFLAGS@ +GSSKRB_LIBS = @GSSKRB_LIBS@ +HAVE_GETRANDOM = @HAVE_GETRANDOM@ +HAVE_LIBWRAP = @HAVE_LIBWRAP@ +HAVE_TCP_WRAPPER = @HAVE_TCP_WRAPPER@ +IDMAPD = @IDMAPD@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +K5VERS = @K5VERS@ +KRBCFLAGS = @KRBCFLAGS@ +KRBDIR = @KRBDIR@ +KRBLDFLAGS = @KRBLDFLAGS@ +KRBLIBS = @KRBLIBS@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LDFLAGS_FOR_BUILD = @LDFLAGS_FOR_BUILD@ +LIBBLKID = @LIBBLKID@ +LIBBSD = @LIBBSD@ +LIBCAP = @LIBCAP@ +LIBCRYPT = @LIBCRYPT@ +LIBEVENT = @LIBEVENT@ +LIBKEYUTILS = @LIBKEYUTILS@ +LIBMOUNT = @LIBMOUNT@ +LIBMOUNT_CFLAGS = @LIBMOUNT_CFLAGS@ +LIBMOUNT_LIBS = @LIBMOUNT_LIBS@ +LIBNSL = @LIBNSL@ +LIBOBJS = @LIBOBJS@ +LIBPTHREAD = @LIBPTHREAD@ +LIBS = @LIBS@ +LIBSOCKET = @LIBSOCKET@ +LIBSQLITE = @LIBSQLITE@ +LIBTIRPC = @LIBTIRPC@ +LIBTOOL = @LIBTOOL@ +LIBWRAP = @LIBWRAP@ +LIBXML2 = @LIBXML2@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_PLUGINS = @PATH_PLUGINS@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +RELEASE = @RELEASE@ +RPCGEN_PATH = @RPCGEN_PATH@ +RPCSECGSS_CFLAGS = @RPCSECGSS_CFLAGS@ +RPCSECGSS_LIBS = @RPCSECGSS_LIBS@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SVCGSSD = @SVCGSSD@ +TIRPC_CFLAGS = @TIRPC_CFLAGS@ +TIRPC_LIBS = @TIRPC_LIBS@ +VERSION = @VERSION@ +XML2_CFLAGS = @XML2_CFLAGS@ +XML2_LIBS = @XML2_LIBS@ +_rpc_pipefsmount = @_rpc_pipefsmount@ +_statedir = @_statedir@ +_sysconfdir = @_sysconfdir@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +enable_gss = @enable_gss@ +enable_ipv6 = @enable_ipv6@ +enable_mountconfig = @enable_mountconfig@ +enable_nfsv4 = @enable_nfsv4@ +enable_nfsv41 = @enable_nfsv41@ +enable_svcgss = @enable_svcgss@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +kprefix = @kprefix@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +mountfile = @mountfile@ +nfsconfig = @nfsconfig@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rpc_pipefsmount = @rpc_pipefsmount@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +startstatd = @startstatd@ +statdpath = @statdpath@ +statduser = @statduser@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +unitdir = @unitdir@ + +# These binaries go in /sbin (not /usr/sbin), unless CONFIG_SBIN_OVERRIDE +# is disabled as may be appropriate when /sbin is a symlink. +# Note that we don't use "if CONFIG_SBIN_OVERRIDE" as that +# causes autotools to notice the override and disable it. +@CONFIG_SBIN_OVERRIDE_TRUE@sbindir = /sbin +man8_MANS = mount.nfs.man umount.nfs.man +man5_MANS = nfs.man $(am__append_2) +EXTRA_DIST = nfsmount.conf $(man8_MANS) $(man5_MANS) $(am__append_3) +mount_common = error.c network.c token.c parse_opt.c parse_dev.c \ + nfsmount.c nfs4mount.c stropts.c mount_constants.h error.h \ + network.h token.h parse_opt.h parse_dev.h nfs4_mount.h \ + stropts.h version.h mount_config.h utils.c utils.h nfs_mount.h \ + $(am__append_1) +mount_nfs_LDADD = ../../support/nfs/libnfs.la \ + ../../support/export/libexport.a \ + ../../support/reexport/libreexport.a \ + ../../support/misc/libmisc.a $(LIBTIRPC) $(LIBPTHREAD) \ + $(am__append_5) +mount_nfs_SOURCES = $(mount_common) $(am__append_4) $(am__append_6) +MAINTAINERCLEANFILES = Makefile.in +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu utils/mount/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu utils/mount/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-sbinPROGRAMS: $(sbin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(sbindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sbindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-sbinPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(sbindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(sbindir)" && rm -f $$files + +clean-sbinPROGRAMS: + @list='$(sbin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +mount.nfs$(EXEEXT): $(mount_nfs_OBJECTS) $(mount_nfs_DEPENDENCIES) $(EXTRA_mount_nfs_DEPENDENCIES) + @rm -f mount.nfs$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(mount_nfs_OBJECTS) $(mount_nfs_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/configfile.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/error.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fstab.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mount.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mount_libmount.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nfs4mount.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nfsmount.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nfsumount.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse_dev.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse_opt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stropts.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/token.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utils.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-man5: $(man5_MANS) + @$(NORMAL_INSTALL) + @list1='$(man5_MANS)'; \ + list2=''; \ + test -n "$(man5dir)" \ + && test -n "`echo $$list1$$list2`" \ + || exit 0; \ + echo " $(MKDIR_P) '$(DESTDIR)$(man5dir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(man5dir)" || exit 1; \ + { for i in $$list1; do echo "$$i"; done; \ + if test -n "$$list2"; then \ + for i in $$list2; do echo "$$i"; done \ + | sed -n '/\.5[a-z]*$$/p'; \ + fi; \ + } | while read p; do \ + if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; echo "$$p"; \ + done | \ + sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^5][0-9a-z]*$$,5,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \ + sed 'N;N;s,\n, ,g' | { \ + list=; while read file base inst; do \ + if test "$$base" = "$$inst"; then list="$$list $$file"; else \ + echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man5dir)/$$inst'"; \ + $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man5dir)/$$inst" || exit $$?; \ + fi; \ + done; \ + for i in $$list; do echo "$$i"; done | $(am__base_list) | \ + while read files; do \ + test -z "$$files" || { \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man5dir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(man5dir)" || exit $$?; }; \ + done; } + +uninstall-man5: + @$(NORMAL_UNINSTALL) + @list='$(man5_MANS)'; test -n "$(man5dir)" || exit 0; \ + files=`{ for i in $$list; do echo "$$i"; done; \ + } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^5][0-9a-z]*$$,5,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \ + dir='$(DESTDIR)$(man5dir)'; $(am__uninstall_files_from_dir) +install-man8: $(man8_MANS) + @$(NORMAL_INSTALL) + @list1='$(man8_MANS)'; \ + list2=''; \ + test -n "$(man8dir)" \ + && test -n "`echo $$list1$$list2`" \ + || exit 0; \ + echo " $(MKDIR_P) '$(DESTDIR)$(man8dir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(man8dir)" || exit 1; \ + { for i in $$list1; do echo "$$i"; done; \ + if test -n "$$list2"; then \ + for i in $$list2; do echo "$$i"; done \ + | sed -n '/\.8[a-z]*$$/p'; \ + fi; \ + } | while read p; do \ + if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; echo "$$p"; \ + done | \ + sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \ + sed 'N;N;s,\n, ,g' | { \ + list=; while read file base inst; do \ + if test "$$base" = "$$inst"; then list="$$list $$file"; else \ + echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man8dir)/$$inst'"; \ + $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man8dir)/$$inst" || exit $$?; \ + fi; \ + done; \ + for i in $$list; do echo "$$i"; done | $(am__base_list) | \ + while read files; do \ + test -z "$$files" || { \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man8dir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(man8dir)" || exit $$?; }; \ + done; } + +uninstall-man8: + @$(NORMAL_UNINSTALL) + @list='$(man8_MANS)'; test -n "$(man8dir)" || exit 0; \ + files=`{ for i in $$list; do echo "$$i"; done; \ + } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \ + dir='$(DESTDIR)$(man8dir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) $(MANS) +installdirs: + for dir in "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(man5dir)" "$(DESTDIR)$(man8dir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(MAINTAINERCLEANFILES)" || rm -f $(MAINTAINERCLEANFILES) +clean: clean-am + +clean-am: clean-generic clean-libtool clean-sbinPROGRAMS \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/configfile.Po + -rm -f ./$(DEPDIR)/error.Po + -rm -f ./$(DEPDIR)/fstab.Po + -rm -f ./$(DEPDIR)/mount.Po + -rm -f ./$(DEPDIR)/mount_libmount.Po + -rm -f ./$(DEPDIR)/network.Po + -rm -f ./$(DEPDIR)/nfs4mount.Po + -rm -f ./$(DEPDIR)/nfsmount.Po + -rm -f ./$(DEPDIR)/nfsumount.Po + -rm -f ./$(DEPDIR)/parse_dev.Po + -rm -f ./$(DEPDIR)/parse_opt.Po + -rm -f ./$(DEPDIR)/stropts.Po + -rm -f ./$(DEPDIR)/token.Po + -rm -f ./$(DEPDIR)/utils.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-man + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-sbinPROGRAMS + @$(NORMAL_INSTALL) + $(MAKE) $(AM_MAKEFLAGS) install-exec-hook +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: install-man5 install-man8 + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/configfile.Po + -rm -f ./$(DEPDIR)/error.Po + -rm -f ./$(DEPDIR)/fstab.Po + -rm -f ./$(DEPDIR)/mount.Po + -rm -f ./$(DEPDIR)/mount_libmount.Po + -rm -f ./$(DEPDIR)/network.Po + -rm -f ./$(DEPDIR)/nfs4mount.Po + -rm -f ./$(DEPDIR)/nfsmount.Po + -rm -f ./$(DEPDIR)/nfsumount.Po + -rm -f ./$(DEPDIR)/parse_dev.Po + -rm -f ./$(DEPDIR)/parse_opt.Po + -rm -f ./$(DEPDIR)/stropts.Po + -rm -f ./$(DEPDIR)/token.Po + -rm -f ./$(DEPDIR)/utils.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-man uninstall-sbinPROGRAMS + @$(NORMAL_INSTALL) + $(MAKE) $(AM_MAKEFLAGS) uninstall-hook +uninstall-man: uninstall-man5 uninstall-man8 + +.MAKE: install-am install-exec-am install-strip uninstall-am + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-sbinPROGRAMS cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-exec-hook install-html install-html-am \ + install-info install-info-am install-man install-man5 \ + install-man8 install-pdf install-pdf-am install-ps \ + install-ps-am install-sbinPROGRAMS install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-hook \ + uninstall-man uninstall-man5 uninstall-man8 \ + uninstall-sbinPROGRAMS + +.PRECIOUS: Makefile + + +install-exec-hook: + (cd $(DESTDIR)$(sbindir) && \ + ln -sf mount.nfs mount.nfs4 && \ + ln -sf mount.nfs umount.nfs && \ + ln -sf mount.nfs umount.nfs4 && \ + chmod 4511 mount.nfs ) +uninstall-hook: + (cd $(DESTDIR)$(sbindir) && \ + rm -f mount.nfs4 umount.nfs umount.nfs4) + +install-man-links: + (cd $(DESTDIR)$(man8dir) && \ + for m in $(man8_MANS) $(dist_man8_MANS) $(nodist_man8_MANS); do \ + inst=`echo $$m | sed -e 's/man$$/8/'`; \ + rm -f $$inst ; \ + done) + (cd $(DESTDIR)$(man5dir) && \ + for m in $(man5_MANS) $(dist_man5_MANS) $(nodist_man5_MANS); do \ + inst=`echo $$m | sed -e 's/man$$/5/'`; \ + rm -f $$inst ; \ + done) + +uninstall-man-links: + (cd $(DESTDIR)$(man8dir) && \ + for m in $(man8_MANS) $(dist_man8_MANS) $(nodist_man8_MANS); do \ + inst=`echo $$m | sed -e 's/man$$/8/'`; \ + rm -f $$inst ; \ + done) + (cd $(DESTDIR)$(man5dir) && \ + for m in $(man5_MANS) $(dist_man5_MANS) $(nodist_man5_MANS); do \ + inst=`echo $$m | sed -e 's/man$$/5/'`; \ + rm -f $$inst ; \ + done) + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/utils/mount/configfile.c b/utils/mount/configfile.c new file mode 100644 index 0000000..1d88cbf --- /dev/null +++ b/utils/mount/configfile.c @@ -0,0 +1,347 @@ +/* + * configfile.c -- mount configuration file manipulation + * Copyright (C) 2008 Red Hat, Inc + * + * - Routines use to create mount options from the mount + * configuration file. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "xlog.h" +#include "mount.h" +#include "parse_opt.h" +#include "network.h" +#include "conffile.h" +#include "mount_config.h" + +#define KBYTES(x) ((x) * (1024)) +#define MEGABYTES(x) ((x) * (1048576)) +#define GIGABYTES(x) ((x) * (1073741824)) + +#ifndef NFSMOUNT_GLOBAL_OPTS +#define NFSMOUNT_GLOBAL_OPTS "NFSMount_Global_Options" +#endif + +#ifndef NFSMOUNT_MOUNTPOINT +#define NFSMOUNT_MOUNTPOINT "MountPoint" +#endif + +#ifndef NFSMOUNT_SERVER +#define NFSMOUNT_SERVER "Server" +#endif + +enum { + MNT_NOARG=0, + MNT_INTARG, + MNT_STRARG, + MNT_SPEC, + MNT_UNSET +}; +struct mnt_alias { + char *alias; + char *opt; + int argtype; +} mnt_alias_tab[] = { + {"background", "bg", MNT_NOARG}, + {"foreground", "fg", MNT_NOARG}, + {"sloppy", "sloppy", MNT_NOARG}, +}; +int mnt_alias_sz = (sizeof(mnt_alias_tab)/sizeof(mnt_alias_tab[0])); + +static const char *version_keys[] = { + "v3", "v4", "vers", "nfsvers", "minorversion", NULL +}; + +static int strict; + +static int is_version(const char *field) +{ + int i; + for (i = 0; version_keys[i] ; i++) + if (strcmp(version_keys[i], field) == 0) + return 1; + if (strncmp(field, "v4.", 3) == 0) + return 1; + return 0; +} + +/* + * See if the option is an alias, if so return the + * real mount option along with the argument type. + */ +inline static +char *mountopts_alias(char *opt, int *argtype) +{ + int i; + + *argtype = MNT_UNSET; + for (i=0; i < mnt_alias_sz; i++) { + if (strcasecmp(opt, mnt_alias_tab[i].alias) != 0) + continue; + *argtype = mnt_alias_tab[i].argtype; + return mnt_alias_tab[i].opt; + } + /* Make option names case-insensitive */ + upper2lower(opt); + + return opt; +} +/* + * Convert numeric strings that end with 'k', 'm' or 'g' + * into numeric strings with the real value. + * Meaning '8k' becomes '8094'. + */ +static char *mountopts_convert(char *value) +{ + unsigned long long factor, num; + static char buf[64]; + char *ch; + + ch = &value[strlen(value)-1]; + switch (tolower(*ch)) { + case 'k': + factor = KBYTES(1); + break; + case 'm': + factor = MEGABYTES(1); + break; + case 'g': + factor = GIGABYTES(1); + break; + default: + return value; + } + *ch = '\0'; + if (strncmp(value, "0x", 2) == 0) { + num = strtol(value, (char **)NULL, 16); + } else if (strncmp(value, "0", 1) == 0) { + num = strtol(value, (char **)NULL, 8); + } else { + num = strtol(value, (char **)NULL, 10); + } + num *= factor; + snprintf(buf, 64, "%lld", num); + + return buf; +} + +struct nfs_version config_default_vers; +unsigned long config_default_proto; +extern sa_family_t config_default_family; + +/* + * Check to see if a default value is being set. + * If so, set the appropriate global value which will + * be used as the initial value in the server negation. + */ +static int +default_value(char *mopt) +{ + struct mount_options *options = NULL; + int dftlen = strlen("default"); + char *field; + + if (strncasecmp(mopt, "default", dftlen) != 0) + return 0; + + field = mopt + dftlen; + if (strncasecmp(field, "proto", strlen("proto")) == 0) { + if ((options = po_split(field)) != NULL) { + if (!nfs_nfs_protocol(options, &config_default_proto)) { + xlog_warn("Unable to set default protocol : %s", + strerror(errno)); + } + if (!nfs_nfs_proto_family(options, &config_default_family)) { + xlog_warn("Unable to set default family : %s", + strerror(errno)); + } + } else { + xlog_warn("Unable to alloc memory for default protocol"); + } + } else if (strncasecmp(field, "vers", strlen("vers")) == 0) { + if ((options = po_split(field)) != NULL) { + if (!nfs_nfs_version("nfs", options, &config_default_vers)) { + xlog_warn("Unable to set default version: %s", + strerror(errno)); + } + } else { + xlog_warn("Unable to alloc memory for default version"); + } + } else + xlog_warn("Invalid default setting: '%s'", mopt); + + if (options) + po_destroy(options); + + return 1; +} +/* + * Parse the given section of the configuration + * file to if there are any mount options set. + * If so, added them to link list. + */ +static void +conf_parse_mntopts(char *section, char *arg, struct mount_options *options) +{ + struct conf_list *list; + struct conf_list_node *node; + char buf[BUFSIZ], *value, *field; + char *nvalue, *ptr; + int argtype; + int have_version = 0; + + if (po_rightmost(options, version_keys) >= 0 || + po_contains_prefix(options, "v4.", NULL, 0) == PO_FOUND) + have_version = 1; + + list = conf_get_tag_list(section, arg); + TAILQ_FOREACH(node, &list->fields, link) { + /* + * Do not overwrite options if already exists + */ + field = mountopts_alias(node->field, &argtype); + if (po_contains(options, field) == PO_FOUND) + continue; + /* Some options can be inverted by a "no" prefix. + * Check for these. + * "no" prefixes are unlikely in the config file as + * "option=false" is preferred, but still possible. + */ + if (strncmp(field, "no", 2) == 0 && + po_contains(options, field+2) == PO_FOUND) + continue; + if (strlen(field) < BUFSIZ-3) { + strcat(strcpy(buf, "no"), field); + if (po_contains(options, buf) == PO_FOUND) + continue; + } + + /* If fg or bg already present, ignore bg or fg */ + if (strcmp(field, "fg") == 0 && + po_contains(options, "bg") == PO_FOUND) + continue; + if (strcmp(field, "bg") == 0 && + po_contains(options, "fg") == PO_FOUND) + continue; + + if (is_version(field)) { + if (have_version) + continue; + have_version = 1; + } + + buf[0] = '\0'; + value = conf_get_section(section, arg, node->field); + if (value == NULL) + continue; + if (strcasecmp(value, "false") == 0) { + if (argtype != MNT_NOARG) + snprintf(buf, BUFSIZ, "no%s", field); + else if (strcasecmp(field, "bg") == 0) + snprintf(buf, BUFSIZ, "fg"); + else if (strcasecmp(field, "fg") == 0) + snprintf(buf, BUFSIZ, "bg"); + else if (strcasecmp(field, "sloppy") == 0) + strict = 1; + } else if (strcasecmp(value, "true") == 0) { + if ((strcasecmp(field, "sloppy") == 0) && strict) + continue; + snprintf(buf, BUFSIZ, "%s", field); + } else { + nvalue = strdup(value); + ptr = mountopts_convert(nvalue); + snprintf(buf, BUFSIZ, "%s=%s", field, ptr); + free(nvalue); + } + if (buf[0] == '\0') + continue; + + po_append(options, buf); + default_value(buf); + } + conf_free_list(list); +} + +/* + * Concatenate options from the configuration file with the + * given options by building a link list of options from the + * different sections in the conf file. Options that exists + * in the either the given options or link list are not + * overwritten so it matter which when each section is + * parsed. + */ +char *conf_get_mntopts(char *spec, char *mount_point, + char *mount_opts) +{ + struct mount_options *options; + char *ptr, *server; + + strict = 0; + options = po_split(mount_opts); + if (!options) { + xlog_warn("conf_get_mountops: Unable calloc memory for options"); + return mount_opts; + } + /* + * First see if there are any mount options relative + * to the mount point. + */ + conf_parse_mntopts(NFSMOUNT_MOUNTPOINT, mount_point, options); + + /* + * Next, see if there are any mount options relative + * to the server + */ + server = strdup(spec); + if (server == NULL) { + xlog_warn("conf_get_mountops: Unable calloc memory for server"); + po_destroy(options); + return mount_opts; + } + if ((ptr = strchr(server, ':')) != NULL) + *ptr='\0'; + conf_parse_mntopts(NFSMOUNT_SERVER, server, options); + free(server); + + /* + * Finally process all the global mount options. + */ + conf_parse_mntopts(NFSMOUNT_GLOBAL_OPTS, NULL, options); + + /* + * Strip out defaults, which have already been handled, + * then join the rest and return. + */ + while (po_contains_prefix(options, "default", &ptr, 0) == PO_FOUND) { + ptr = strdup(ptr); + po_remove_all(options, ptr); + free(ptr); + } + + po_join(options, &mount_opts); + po_destroy(options); + + return mount_opts; +} diff --git a/utils/mount/error.c b/utils/mount/error.c new file mode 100644 index 0000000..9ddbcc0 --- /dev/null +++ b/utils/mount/error.c @@ -0,0 +1,360 @@ +/* + * error.c -- Common error handling functions + * + * Copyright (C) 2007 Oracle. All rights reserved. + * Copyright (C) 2007 Chuck Lever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA + * + * To Do: + * + Proper support for internationalization + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xcommon.h" +#include "nls.h" +#include "mount.h" +#include "error.h" + +#ifdef HAVE_RPCSVC_NFS_PROT_H +#include +#else +#include +#define nfsstat nfs_stat +#endif + +extern char *progname; + +static char errbuf[PATH_MAX]; +static char *erreob = &errbuf[PATH_MAX]; + +/* Convert RPC errors into strings */ +static int rpc_strerror(int spos) +{ + int cf_stat = rpc_createerr.cf_stat; + int pos = 0, cf_errno = rpc_createerr.cf_error.re_errno; + char *ptr, *estr = clnt_sperrno(cf_stat); + char *tmp; + + if (estr) { + if ((ptr = strchr(estr, ':'))) + estr = ++ptr; + + tmp = &errbuf[spos]; + if (cf_stat == RPC_SYSTEMERROR) + pos = snprintf(tmp, (erreob - tmp), + _("System Error: %s"), + strerror(cf_errno)); + else { + if (cf_errno) + pos = snprintf(tmp, (erreob - tmp), + _("RPC Error:%s; errno = %s"), + estr, strerror(cf_errno)); + else + pos = snprintf(tmp, (erreob - tmp), + _("RPC Error:%s"), estr); + } + } + return pos; +} + +/** + * rpc_mount_errors - log an RPC error that occurred during a user-space mount + * @server: C string containing name of server we are attempting to mount + * @will_retry: one indicates mount will retry at some later point + * @bg: one indicates this is a background mount + * + * Extracts the error code from the user-space RPC library, and reports it + * on stderr (fg mount) or in the system log (bg mount). + */ +void rpc_mount_errors(char *server, int will_retry, int bg) +{ + int pos = 0; + char *tmp; + static int onlyonce = 0; + + tmp = &errbuf[pos]; + if (bg) + pos = snprintf(tmp, (erreob - tmp), + _("mount to NFS server '%s' failed: "), + server); + else + pos = snprintf(tmp, (erreob - tmp), + _("%s: mount to NFS server '%s' failed: "), + progname, server); + + tmp = &errbuf[pos]; + if (rpc_createerr.cf_stat == RPC_TIMEDOUT) { + if (will_retry) + pos = snprintf(tmp, (erreob - tmp), + _("timed out, retrying")); + else + pos = snprintf(tmp, (erreob - tmp), + _("timed out, giving up")); + } else { + pos += rpc_strerror(pos); + tmp = &errbuf[pos]; + if (bg) { + if (will_retry) + pos = snprintf(tmp, (erreob - tmp), + _(", retrying")); + else + pos = snprintf(tmp, (erreob - tmp), + _(", giving up")); + } + } + + if (bg) { + if (onlyonce++ < 1) + openlog("mount", LOG_CONS|LOG_PID, LOG_AUTH); + syslog(LOG_ERR, "%s", errbuf); + } else + fprintf(stderr, "%s\n", errbuf); +} + +/** + * sys_mount_errors - log an error that occurred during a mount system call + * @server: C string containing name of server we are attempting to mount + * @error: errno value to report + * @will_retry: one indicates mount will retry at some later point + * @bg: one indicates this is a background mount + * + * Passed an errno value generated by a mount system call, and reports it + * on stderr (fg mount) or in the system log (bg mount). + */ +void sys_mount_errors(char *server, int error, int will_retry, int bg) +{ + int pos = 0; + char *tmp; + static int onlyonce = 0; + + tmp = &errbuf[pos]; + if (bg) + pos = snprintf(tmp, (erreob - tmp), + _("mount to NFS server '%s' failed: "), + server); + else + pos = snprintf(tmp, (erreob - tmp), + _("%s: mount to NFS server '%s' failed: "), + progname, server); + + tmp = &errbuf[pos]; + if (error == ETIMEDOUT) { + if (will_retry) + pos = snprintf(tmp, (erreob - tmp), + _("timed out, retrying")); + else + pos = snprintf(tmp, (erreob - tmp), + _("timed out, giving up")); + } else { + if (bg) { + if (will_retry) + pos = snprintf(tmp, (erreob - tmp), + _("%s, retrying"), + strerror(error)); + else + pos = snprintf(tmp, (erreob - tmp), + _("%s, giving up"), + strerror(error)); + } + } + + if (bg) { + if (onlyonce++ < 1) + openlog("mount", LOG_CONS|LOG_PID, LOG_AUTH); + syslog(LOG_ERR, "%s", errbuf); + } else + fprintf(stderr, "%s\n", errbuf); +} + +/** + * mount_error - report a foreground mount error + * @spec: C string containing the device name being mounted + * @mount_point: C string containing the pathname of the local mounted on dir + * @error: errno value to report + * + */ +void mount_error(const char *spec, const char *mount_point, int error) +{ + switch(error) { + case EACCES: + nfs_error(_("%s: access denied by server while mounting %s"), + progname, spec); + break; + case EINVAL: + nfs_error(_("%s: an incorrect mount option was specified for %s"), + progname, mount_point); + break; + case EOPNOTSUPP: + nfs_error(_("%s: requested NFS version or transport protocol is not supported for %s"), + progname, mount_point); + break; + case ENOTDIR: + if (spec) + nfs_error(_("%s: mount spec %s or point %s is not a directory"), + progname, spec, mount_point); + else + nfs_error(_("%s: mount point %s is not a directory"), + progname, mount_point); + break; + case EBUSY: + nfs_error(_("%s: %s is busy or already mounted or sharecache fail"), + progname, mount_point); + break; + case ENOENT: + if (spec) + nfs_error(_("%s: mounting %s failed, reason given by server: %s"), + progname, spec, strerror(error)); + else + nfs_error(_("%s: mount point %s does not exist"), + progname, mount_point); + break; + case ESPIPE: + rpc_mount_errors((char *)spec, 0, 0); + break; + case EIO: + nfs_error(_("%s: mount system call failed for %s"), + progname, mount_point); + break; + case EFAULT: + nfs_error(_("%s: encountered unexpected error condition for %s."), + progname, mount_point); + nfs_error(_("%s: please report the error to" PACKAGE_BUGREPORT), + progname); + break; + case EALREADY: + /* Error message has already been provided */ + break; + default: + nfs_error(_("%s: %s for %s on %s"), + progname, strerror(error), spec, mount_point); + } +} + +/* + * umount_error - report a failed umount request + * @err: errno value to report + * @dev: C string containing the pathname of the local mounted on dir + * + */ +void umount_error(int err, const char *dev) +{ + switch (err) { + case ENXIO: + nfs_error(_("%s: %s: invalid block device"), + progname, dev); + break; + case EINVAL: + nfs_error(_("%s: %s: not mounted"), + progname, dev); + break; + case EIO: + nfs_error(_("%s: %s: can't write superblock"), + progname, dev); + break; + case EBUSY: + nfs_error(_("%s: %s: device is busy"), + progname, dev); + break; + case ENOENT: + nfs_error(_("%s: %s: not found"), + progname, dev); + break; + case EPERM: + nfs_error(_("%s: %s: must be superuser to umount"), + progname, dev); + break; + case EACCES: + nfs_error(_("%s: %s: block devices not permitted on fs"), + progname, dev); + break; + default: + nfs_error(_("%s: %s: %s"), + progname, dev, strerror(err)); + break; + } +} + +/* + * We need to translate between nfs status return values and + * the local errno values which may not be the same. + * + * Andreas Schwab : change errno: + * "after #include the symbol errno is reserved for any use, + * it cannot even be used as a struct tag or field name". + */ + +#ifndef EDQUOT +#define EDQUOT ENOSPC +#endif + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +static struct { + enum nfsstat stat; + int errnum; +} nfs_errtbl[] = { + { NFS_OK, 0 }, + { NFSERR_PERM, EPERM }, + { NFSERR_NOENT, ENOENT }, + { NFSERR_IO, EIO }, + { NFSERR_NXIO, ENXIO }, + { NFSERR_ACCES, EACCES }, + { NFSERR_EXIST, EEXIST }, + { NFSERR_NODEV, ENODEV }, + { NFSERR_NOTDIR, ENOTDIR }, + { NFSERR_ISDIR, EISDIR }, +#ifdef NFSERR_INVAL + { NFSERR_INVAL, EINVAL }, /* that Sun forgot */ +#endif + { NFSERR_FBIG, EFBIG }, + { NFSERR_NOSPC, ENOSPC }, + { NFSERR_ROFS, EROFS }, + { NFSERR_NAMETOOLONG, ENAMETOOLONG }, + { NFSERR_NOTEMPTY, ENOTEMPTY }, + { NFSERR_DQUOT, EDQUOT }, + { NFSERR_STALE, ESTALE }, +#ifdef EWFLUSH + { NFSERR_WFLUSH, EWFLUSH }, +#endif + /* Throw in some NFSv3 values for even more fun (HP returns these) */ + { 71, EREMOTE }, +}; + +char *nfs_strerror(unsigned int stat) +{ + unsigned int i; + static char buf[256]; + + for (i = 0; i < ARRAY_SIZE(nfs_errtbl); i++) { + if (nfs_errtbl[i].stat == stat) + return strerror(nfs_errtbl[i].errnum); + } + sprintf(buf, _("unknown nfs status return value: %u"), stat); + return buf; +} diff --git a/utils/mount/error.h b/utils/mount/error.h new file mode 100644 index 0000000..ef80fd0 --- /dev/null +++ b/utils/mount/error.h @@ -0,0 +1,35 @@ +/* + * error.h: Common error handling functions + * + * Copyright (C) 2007 Oracle. All rights reserved. + * Copyright (C) 2007 Chuck Lever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA + * + */ + +#ifndef _NFS_UTILS_MOUNT_ERROR_H +#define _NFS_UTILS_MOUNT_ERROR_H + +char *nfs_strerror(unsigned int); + +void mount_error(const char *, const char *, int); +void rpc_mount_errors(char *, int, int); +void sys_mount_errors(char *, int, int, int); + +void umount_error(int, const char *); + +#endif /* _NFS_UTILS_MOUNT_ERROR_H */ diff --git a/utils/mount/fstab.c b/utils/mount/fstab.c new file mode 100644 index 0000000..146d8f4 --- /dev/null +++ b/utils/mount/fstab.c @@ -0,0 +1,653 @@ +/* 1999-02-22 Arkadiusz Miskiewicz + * - added Native Language Support + * Sun Mar 21 1999 - Arnaldo Carvalho de Melo + * - fixed strerr(errno) in gettext calls + * + * 2006-06-08 Amit Gud + * - Moved code to nfs-utils/support/nfs from util-linux/mount. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "fstab.h" +#include "xcommon.h" +#include "nfs_mntent.h" +#include "nfs_paths.h" +#include "nls.h" + +#define LOCK_TIMEOUT 10 +#define streq(s, t) (strcmp ((s), (t)) == 0) +#define PROC_MOUNTS "/proc/mounts" + +extern char *progname; +extern int verbose; + +/* Information about mtab. ------------------------------------*/ +static int have_mtab_info = 0; +static int var_mtab_does_not_exist = 0; +static int var_mtab_is_a_symlink = 0; + +static void +get_mtab_info(void) { + struct stat mtab_stat; + + if (!have_mtab_info) { + if (lstat(MOUNTED, &mtab_stat)) + var_mtab_does_not_exist = 1; + else if (S_ISLNK(mtab_stat.st_mode)) + var_mtab_is_a_symlink = 1; + have_mtab_info = 1; + } +} + +void +reset_mtab_info(void) { + have_mtab_info = 0; +} + +int +mtab_does_not_exist(void) { + get_mtab_info(); + return var_mtab_does_not_exist; +} + +int +mtab_is_a_symlink(void) { + get_mtab_info(); + return var_mtab_is_a_symlink; +} + +int +mtab_is_writable() { + int fd; + + /* Should we write to /etc/mtab upon an update? + Probably not if it is a symlink to /proc/mounts, since that + would create a file /proc/mounts in case the proc filesystem + is not mounted. */ + if (mtab_is_a_symlink()) + return 0; + + fd = open(MOUNTED, O_RDWR | O_CREAT, 0644); + if (fd >= 0) { + close(fd); + return 1; + } else + return 0; +} + +/* Contents of mtab and fstab ---------------------------------*/ + +struct mntentchn mounttable; +static int got_mtab = 0; +struct mntentchn procmounts; +static int got_procmounts = 0; +struct mntentchn fstab; +static int got_fstab = 0; + +static void read_mounttable(void); +static void read_procmounts(void); +static void read_fstab(void); + +static struct mntentchn * +mtab_head(void) +{ + if (!got_mtab) + read_mounttable(); + return &mounttable; +} + +static struct mntentchn * +procmounts_head(void) +{ + if (!got_procmounts) + read_procmounts(); + return &procmounts; +} + +static struct mntentchn * +fstab_head(void) +{ + if (!got_fstab) + read_fstab(); + return &fstab; +} + +#if 0 +static void +my_free(const void *s) { + if (s) + free((void *) s); +} + +static void +discard_mntentchn(struct mntentchn *mc0) { + struct mntentchn *mc, *mc1; + + for (mc = mc0->nxt; mc && mc != mc0; mc = mc1) { + mc1 = mc->nxt; + my_free(mc->m.mnt_fsname); + my_free(mc->m.mnt_dir); + my_free(mc->m.mnt_type); + my_free(mc->m.mnt_opts); + free(mc); + } +} +#endif + +static void +read_mntentchn(mntFILE *mfp, const char *fnam, struct mntentchn *mc0) { + struct mntentchn *mc = mc0; + struct mntent *mnt; + + while ((mnt = nfs_getmntent(mfp)) != NULL) { + if (!streq(mnt->mnt_type, MNTTYPE_IGNORE)) { + mc->nxt = (struct mntentchn *) xmalloc(sizeof(*mc)); + mc->nxt->prev = mc; + mc = mc->nxt; + mc->m = *mnt; + mc->nxt = mc0; + } + } + mc0->prev = mc; + if (ferror(mfp->mntent_fp)) { + int errsv = errno; + nfs_error(_("warning: error reading %s: %s"), + fnam, strerror (errsv)); + mc0->nxt = mc0->prev = NULL; + } + nfs_endmntent(mfp); +} + +/* + * Read /etc/mtab. If that fails, try /proc/mounts. + * This produces a linked list. The list head mounttable is a dummy. + * Return 0 on success. + */ +static void +read_mounttable() { + mntFILE *mfp; + const char *fnam; + struct mntentchn *mc = &mounttable; + + got_mtab = 1; + mc->nxt = mc->prev = NULL; + + fnam = MOUNTED; + mfp = nfs_setmntent (fnam, "r"); + if (mfp == NULL || mfp->mntent_fp == NULL) { + int errsv = errno; + fnam = PROC_MOUNTS; + mfp = nfs_setmntent (fnam, "r"); + if (mfp == NULL || mfp->mntent_fp == NULL) { + nfs_error(_("warning: can't open %s: %s"), + MOUNTED, strerror (errsv)); + return; + } + if (verbose) + printf(_("%s: could not open %s; using %s instead\n"), + progname, MOUNTED, PROC_MOUNTS); + } + read_mntentchn(mfp, fnam, mc); +} + +/* + * Read /proc/mounts. + * This produces a linked list. The list head procmounts is a dummy. + * Return 0 on success. + */ +static void +read_procmounts() { + mntFILE *mfp; + const char *fnam; + struct mntentchn *mc = &procmounts; + + got_procmounts = 1; + mc->nxt = mc->prev = NULL; + + fnam = PROC_MOUNTS; + mfp = nfs_setmntent(fnam, "r"); + if (mfp == NULL || mfp->mntent_fp == NULL) { + nfs_error(_("warning: can't open %s: %s"), + PROC_MOUNTS, strerror (errno)); + return; + } + read_mntentchn(mfp, fnam, mc); +} + +static void +read_fstab() +{ + mntFILE *mfp = NULL; + const char *fnam; + struct mntentchn *mc = &fstab; + + got_fstab = 1; + mc->nxt = mc->prev = NULL; + + fnam = _PATH_FSTAB; + mfp = nfs_setmntent (fnam, "r"); + if (mfp == NULL || mfp->mntent_fp == NULL) { + int errsv = errno; + nfs_error(_("warning: can't open %s: %s"), + _PATH_FSTAB, strerror (errsv)); + return; + } + read_mntentchn(mfp, fnam, mc); +} + +/* + * Given the directory name NAME, and the place MCPREV we found it last time, + * try to find more occurrences. + */ +struct mntentchn * +getmntdirbackward (const char *name, struct mntentchn *mcprev) { + struct mntentchn *mc, *mc0; + + mc0 = mtab_head(); + if (!mcprev) + mcprev = mc0; + for (mc = mcprev->prev; mc && mc != mc0; mc = mc->prev) + if (streq(mc->m.mnt_dir, name)) + return mc; + return NULL; +} + +/* + * Given the directory name NAME, and the place MCPREV we found it last time, + * try to find more occurrences. + */ +struct mntentchn * +getprocmntdirbackward (const char *name, struct mntentchn *mcprev) { + struct mntentchn *mc, *mc0; + + mc0 = procmounts_head(); + if (!mcprev) + mcprev = mc0; + for (mc = mcprev->prev; mc && mc != mc0; mc = mc->prev) + if (streq(mc->m.mnt_dir, name)) + return mc; + return NULL; +} + +/* + * Given the device name NAME, and the place MCPREV we found it last time, + * try to find more occurrences. + */ +struct mntentchn * +getmntdevbackward (const char *name, struct mntentchn *mcprev) { + struct mntentchn *mc, *mc0; + + mc0 = mtab_head(); + if (!mcprev) + mcprev = mc0; + for (mc = mcprev->prev; mc && mc != mc0; mc = mc->prev) + if (streq(mc->m.mnt_fsname, name)) + return mc; + return NULL; +} + +/* Find the dir FILE in fstab. */ +struct mntentchn * +getfsfile (const char *file) +{ + struct mntentchn *mc, *mc0; + + mc0 = fstab_head(); + for (mc = mc0->nxt; mc && mc != mc0; mc = mc->nxt) + if (streq(mc->m.mnt_dir, file)) + return mc; + return NULL; +} + +/* Find the device SPEC in fstab. */ +struct mntentchn * +getfsspec (const char *spec) +{ + struct mntentchn *mc, *mc0; + + mc0 = fstab_head(); + for (mc = mc0->nxt; mc && mc != mc0; mc = mc->nxt) + if (streq(mc->m.mnt_fsname, spec)) + return mc; + return NULL; +} + +/* Updating mtab ----------------------------------------------*/ + +/* Flag for already existing lock file. */ +static int we_created_lockfile = 0; +static int lockfile_fd = -1; + +/* Flag to indicate that signals have been set up. */ +static int signals_have_been_setup = 0; + +/* Ensure that the lock is released if we are interrupted. */ +extern char *strsignal(int sig); /* not always in */ + +static void +handler (int sig) { + die(EX_USER, "%s", strsignal(sig)); +} + +static void +setlkw_timeout (__attribute__((unused)) int sig) { + /* nothing, fcntl will fail anyway */ +} + +/* Remove lock file. */ +void +unlock_mtab (void) { + if (we_created_lockfile) { + close(lockfile_fd); + lockfile_fd = -1; + unlink (MOUNTED_LOCK); + we_created_lockfile = 0; + } +} + +/* Create the lock file. + The lock file will be removed if we catch a signal or when we exit. */ +/* The old code here used flock on a lock file /etc/mtab~ and deleted + this lock file afterwards. However, as rgooch remarks, that has a + race: a second mount may be waiting on the lock and proceed as + soon as the lock file is deleted by the first mount, and immediately + afterwards a third mount comes, creates a new /etc/mtab~, applies + flock to that, and also proceeds, so that the second and third mount + now both are scribbling in /etc/mtab. + The new code uses a link() instead of a creat(), where we proceed + only if it was us that created the lock, and hence we always have + to delete the lock afterwards. Now the use of flock() is in principle + superfluous, but avoids an arbitrary sleep(). */ + +/* Where does the link point to? Obvious choices are mtab and mtab~~. + HJLu points out that the latter leads to races. Right now we use + mtab~. instead. Use 20 as upper bound for the length of %d. */ +#define MOUNTLOCK_LINKTARGET MOUNTED_LOCK "%d" +#define MOUNTLOCK_LINKTARGET_LTH (sizeof(MOUNTED_LOCK)+20) + +void +lock_mtab (void) { + int tries = 100000, i; + char linktargetfile[MOUNTLOCK_LINKTARGET_LTH]; + + at_die = unlock_mtab; + + if (!signals_have_been_setup) { + int sig = 0; + struct sigaction sa; + + sa.sa_flags = 0; + sigfillset (&sa.sa_mask); + + while (sigismember (&sa.sa_mask, ++sig) != -1) { + switch(sig) { + case SIGCHLD: + case SIGKILL: + case SIGCONT: + case SIGSTOP: + /* The cannot be caught, or should not, + * so don't even try. + */ + continue; + case SIGALRM: + sa.sa_handler = setlkw_timeout; + break; + case SIGHUP: + case SIGINT: + case SIGQUIT: + case SIGWINCH: + case SIGTSTP: + case SIGTTIN: + case SIGTTOU: + case SIGPIPE: + case SIGXFSZ: + case SIGXCPU: + /* non-priv user can cause these to be + * generated, so ignore them. + */ + sa.sa_handler = SIG_IGN; + break; + default: + /* The rest should not be possible, so just + * print a message and unlock mtab. + */ + sa.sa_handler = handler; + } + sigaction (sig, &sa, (struct sigaction *) 0); + } + signals_have_been_setup = 1; + } + + sprintf(linktargetfile, MOUNTLOCK_LINKTARGET, getpid ()); + + i = open (linktargetfile, O_WRONLY|O_CREAT, 0); + if (i < 0) { + int errsv = errno; + /* linktargetfile does not exist (as a file) + and we cannot create it. Read-only filesystem? + Too many files open in the system? + Filesystem full? */ + die (EX_FILEIO, _("can't create lock file %s: %s " + "(use -n flag to override)"), + linktargetfile, strerror (errsv)); + } + close(i); + + /* Repeat until it was us who made the link */ + while (!we_created_lockfile) { + struct flock flock; + int j; + + j = link(linktargetfile, MOUNTED_LOCK); + + { + int errsv = errno; + + if (j == 0) + we_created_lockfile = 1; + + if (j < 0 && errsv != EEXIST) { + (void) unlink(linktargetfile); + die (EX_FILEIO, _("can't link lock file %s: %s " + "(use -n flag to override)"), + MOUNTED_LOCK, strerror (errsv)); + } + } + + lockfile_fd = open (MOUNTED_LOCK, O_WRONLY); + + if (lockfile_fd < 0) { + int errsv = errno; + /* Strange... Maybe the file was just deleted? */ + if (errno == ENOENT && tries-- > 0) { + if (tries % 200 == 0) + usleep(30); + continue; + } + (void) unlink(linktargetfile); + die (EX_FILEIO, _("can't open lock file %s: %s " + "(use -n flag to override)"), + MOUNTED_LOCK, strerror (errsv)); + } + + flock.l_type = F_WRLCK; + flock.l_whence = SEEK_SET; + flock.l_start = 0; + flock.l_len = 0; + + if (j == 0) { + /* We made the link. Now claim the lock. */ + if (fcntl (lockfile_fd, F_SETLK, &flock) == -1) { + if (verbose) { + int errsv = errno; + nfs_error(_("%s: Can't lock lock file " + "%s: %s"), progname, + MOUNTED_LOCK, + strerror (errsv)); + } + /* proceed anyway */ + } + (void) unlink(linktargetfile); + } else { + static int retries = 0; + + /* Someone else made the link. Wait. */ + alarm(LOCK_TIMEOUT); + if (fcntl (lockfile_fd, F_SETLKW, &flock) == -1) { + int errsv = errno; + (void) unlink(linktargetfile); + die (EX_FILEIO, _("can't lock lock file %s: %s"), + MOUNTED_LOCK, (errno == EINTR) ? + _("timed out") : strerror (errsv)); + } + alarm(0); + /* Limit the number of iterations - maybe there + still is some old /etc/mtab~ */ + ++retries; + if (retries % 200 == 0) + usleep(30); + if (retries > 100000) { + (void) unlink(linktargetfile); + close(lockfile_fd); + die (EX_FILEIO, _("Cannot create link %s\n" + "Perhaps there is a stale lock file?\n"), + MOUNTED_LOCK); + } + close(lockfile_fd); + } + } +} + +/* + * Update the mtab. + * Used by umount with null INSTEAD: remove the last DIR entry. + * Used by mount upon a remount: update option part, + * and complain if a wrong device or type was given. + * [Note that often a remount will be a rw remount of / + * where there was no entry before, and we'll have to believe + * the values given in INSTEAD.] + */ + +void +update_mtab (const char *dir, struct mntent *instead) +{ + mntFILE *mfp, *mftmp; + const char *fnam = MOUNTED; + struct mntentchn mtabhead; /* dummy */ + struct mntentchn *mc, *mc0, *absent = NULL; + + if (mtab_does_not_exist() || !mtab_is_writable()) + return; + + lock_mtab(); + + /* having locked mtab, read it again */ + mc0 = mc = &mtabhead; + mc->nxt = mc->prev = NULL; + + mfp = nfs_setmntent(fnam, "r"); + if (mfp == NULL || mfp->mntent_fp == NULL) { + int errsv = errno; + nfs_error (_("cannot open %s (%s) - mtab not updated"), + fnam, strerror (errsv)); + goto leave; + } + + read_mntentchn(mfp, fnam, mc); + + /* find last occurrence of dir */ + for (mc = mc0->prev; mc && mc != mc0; mc = mc->prev) + if (streq(mc->m.mnt_dir, dir)) + break; + if (mc && mc != mc0) { + if (instead == NULL) { + /* An umount - remove entry */ + if (mc && mc != mc0) { + mc->prev->nxt = mc->nxt; + mc->nxt->prev = mc->prev; + free(mc); + } + } else { + /* A remount */ + mc->m.mnt_opts = instead->mnt_opts; + } + } else if (instead) { + /* not found, add a new entry */ + absent = xmalloc(sizeof(*absent)); + absent->m = *instead; + absent->nxt = mc0; + absent->prev = mc0->prev; + mc0->prev = absent; + if (mc0->nxt == NULL) + mc0->nxt = absent; + } + + /* write chain to mtemp */ + mftmp = nfs_setmntent (MOUNTED_TEMP, "w"); + if (mftmp == NULL || mftmp->mntent_fp == NULL) { + int errsv = errno; + nfs_error (_("cannot open %s (%s) - mtab not updated"), + MOUNTED_TEMP, strerror (errsv)); + goto leave; + } + + for (mc = mc0->nxt; mc && mc != mc0; mc = mc->nxt) { + if (nfs_addmntent(mftmp, &(mc->m)) == 1) { + int errsv = errno; + die (EX_FILEIO, _("error writing %s: %s"), + MOUNTED_TEMP, strerror (errsv)); + } + } + +#if 0 + /* the chain might have strings copied from 'instead', + * so we cannot safely free it. + * And there is no need anyway because we are going to exit + * shortly. So just don't call discard_mntentchn.... + */ + discard_mntentchn(mc0); +#endif + if (fchmod (fileno (mftmp->mntent_fp), + S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) < 0) { + int errsv = errno; + nfs_error(_("%s: error changing mode of %s: %s"), + progname, MOUNTED_TEMP, strerror (errsv)); + } + nfs_endmntent (mftmp); + + { /* + * If mount is setuid and some non-root user mounts sth, + * then mtab.tmp might get the group of this user. Copy uid/gid + * from the present mtab before renaming. + */ + struct stat sbuf; + if (stat (MOUNTED, &sbuf) == 0) { + if (chown (MOUNTED_TEMP, sbuf.st_uid, sbuf.st_gid) < 0) { + nfs_error(_("%s: error changing owner of %s: %s"), + progname, MOUNTED_TEMP, strerror (errno)); + } + } + } + + /* rename mtemp to mtab */ + if (rename (MOUNTED_TEMP, MOUNTED) < 0) { + int errsv = errno; + nfs_error(_("%s: can't rename %s to %s: %s\n"), + progname, MOUNTED_TEMP, MOUNTED, + strerror(errsv)); + } + + leave: + unlock_mtab(); +} diff --git a/utils/mount/fstab.h b/utils/mount/fstab.h new file mode 100644 index 0000000..8676c8c --- /dev/null +++ b/utils/mount/fstab.h @@ -0,0 +1,32 @@ +#ifndef _NFS_UTILS_MOUNT_FSTAB_H +#define _NFS_UTILS_MOUNT_FSTAB_H + +#include "nfs_mntent.h" + +#ifndef _PATH_FSTAB +#define _PATH_FSTAB "/etc/fstab" +#endif + +int mtab_is_a_symlink(void); +int mtab_is_writable(void); +int mtab_does_not_exist(void); +void reset_mtab_info(void); + +struct mntentchn { + struct mntentchn *nxt, *prev; + struct mntent m; +}; + +struct mntentchn *getmntoptfile (const char *file); +struct mntentchn *getmntdirbackward (const char *dir, struct mntentchn *mc); +struct mntentchn *getprocmntdirbackward (const char *name, struct mntentchn *mc); +struct mntentchn *getmntdevbackward (const char *dev, struct mntentchn *mc); + +struct mntentchn *getfsfile (const char *file); +struct mntentchn *getfsspec (const char *spec); + +void lock_mtab (void); +void unlock_mtab (void); +void update_mtab (const char *special, struct mntent *with); + +#endif /* _NFS_UTILS_MOUNT_FSTAB_H */ diff --git a/utils/mount/mount.c b/utils/mount/mount.c new file mode 100644 index 0000000..b98f9e0 --- /dev/null +++ b/utils/mount/mount.c @@ -0,0 +1,558 @@ +/* + * mount.c -- Linux NFS mount + * + * Copyright (C) 2006 Amit Gud + * + * - Basic code and wrapper around mount and umount code of NFS. + * Based on util-linux/mount/mount.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fstab.h" +#include "xcommon.h" +#include "nls.h" +#include "mount_constants.h" +#include "mount_config.h" +#include "nfs_paths.h" +#include "nfs_mntent.h" + +#include "nfs_mount.h" +#include "nfs4_mount.h" +#include "mount.h" +#include "error.h" +#include "stropts.h" +#include "utils.h" + +char *progname; +int nfs_mount_data_version; +int nomtab; +int verbose; +int sloppy; +int string; + +#define FOREGROUND (0) +#define BACKGROUND (1) + +static struct option longopts[] = { + { "fake", 0, 0, 'f' }, + { "help", 0, 0, 'h' }, + { "no-mtab", 0, 0, 'n' }, + { "read-only", 0, 0, 'r' }, + { "ro", 0, 0, 'r' }, + { "verbose", 0, 0, 'v' }, + { "version", 0, 0, 'V' }, + { "read-write", 0, 0, 'w' }, + { "rw", 0, 0, 'w' }, + { "options", 1, 0, 'o' }, + { NULL, 0, 0, 0 } +}; + +/* + * Map from -o and fstab option strings to the flag argument to mount(2). + */ +struct opt_map { + const char *opt; /* option name */ + int skip; /* skip in mtab option string */ + int inv; /* true if flag value should be inverted */ + int mask; /* flag mask value */ +}; + +static const struct opt_map opt_map[] = { + { "defaults", 0, 0, 0 }, /* default options */ + { "ro", 1, 0, MS_RDONLY }, /* read-only */ + { "rw", 1, 1, MS_RDONLY }, /* read-write */ + { "exec", 0, 1, MS_NOEXEC }, /* permit execution of binaries */ + { "noexec", 0, 0, MS_NOEXEC }, /* don't execute binaries */ + { "suid", 0, 1, MS_NOSUID }, /* honor suid executables */ + { "nosuid", 0, 0, MS_NOSUID }, /* don't honor suid executables */ + { "dev", 0, 1, MS_NODEV }, /* interpret device files */ + { "nodev", 0, 0, MS_NODEV }, /* don't interpret devices */ + { "sync", 0, 0, MS_SYNCHRONOUS}, /* synchronous I/O */ + { "async", 0, 1, MS_SYNCHRONOUS}, /* asynchronous I/O */ + { "dirsync", 0, 0, MS_DIRSYNC}, /* synchronous directory modifications */ + { "remount", 0, 0, MS_REMOUNT}, /* Alter flags of mounted FS */ + { "bind", 0, 0, MS_BIND }, /* Remount part of tree elsewhere */ + { "rbind", 0, 0, MS_BIND|MS_REC }, /* Idem, plus mounted subtrees */ + { "auto", 0, 0, MS_DUMMY }, /* Can be mounted using -a */ + { "noauto", 0, 0, MS_DUMMY }, /* Can only be mounted explicitly */ + { "users", 1, 0, MS_USERS }, /* Allow ordinary user to mount */ + { "nousers", 0, 1, MS_DUMMY }, /* Forbid ordinary user to mount */ + { "user", 1, 0, MS_USER }, /* Allow ordinary user to mount */ + { "nouser", 0, 1, MS_DUMMY }, /* Forbid ordinary user to mount */ + { "owner", 0, 0, MS_DUMMY }, /* Let the owner of the device mount */ + { "noowner", 0, 0, MS_DUMMY }, /* Device owner has no special privs */ + { "group", 0, 0, MS_DUMMY }, /* Let the group of the device mount */ + { "nogroup", 0, 0, MS_DUMMY }, /* Device group has no special privs */ + { "_netdev", 0, 0, MS_DUMMY}, /* Device requires network */ + { "comment", 0, 0, MS_DUMMY}, /* fstab comment only (kudzu,_netdev)*/ + + /* add new options here */ +#ifdef MS_NOSUB + { "sub", 0, 1, MS_NOSUB }, /* allow submounts */ + { "nosub", 0, 0, MS_NOSUB }, /* don't allow submounts */ +#endif +#ifdef MS_SILENT + { "quiet", 0, 0, MS_SILENT }, /* be quiet */ + { "loud", 0, 1, MS_SILENT }, /* print out messages. */ +#endif +#ifdef MS_MANDLOCK + { "mand", 0, 0, MS_MANDLOCK }, /* Allow mandatory locks on this FS */ + { "nomand", 0, 1, MS_MANDLOCK }, /* Forbid mandatory locks on this FS */ +#endif + { "loop", 1, 0, MS_DUMMY }, /* use a loop device */ +#ifdef MS_NOATIME + { "atime", 0, 1, MS_NOATIME }, /* Update access time */ + { "noatime", 0, 0, MS_NOATIME }, /* Do not update access time */ +#endif +#ifdef MS_NODIRATIME + { "diratime", 0, 1, MS_NODIRATIME }, /* Update dir access times */ + { "nodiratime", 0, 0, MS_NODIRATIME },/* Do not update dir access times */ +#endif +#ifdef MS_RELATIME + { "relatime", 0, 0, MS_RELATIME }, /* Update access times relative to + mtime/ctime */ + { "norelatime", 0, 1, MS_RELATIME }, /* Update access time without regard + to mtime/ctime */ +#endif + { "noquota", 0, 0, MS_DUMMY }, /* Don't enforce quota */ + { "quota", 0, 0, MS_DUMMY }, /* Enforce user quota */ + { "usrquota", 0, 0, MS_DUMMY }, /* Enforce user quota */ + { "grpquota", 0, 0, MS_DUMMY }, /* Enforce group quota */ + { NULL, 0, 0, 0 } +}; + +static void parse_opts(const char *options, int *flags, char **extra_opts); + +/* + * Build a canonical mount option string for /etc/mtab. + */ +static char *fix_opts_string(int flags, const char *extra_opts) +{ + const struct opt_map *om; + char *new_opts; + + new_opts = xstrdup((flags & MS_RDONLY) ? "ro" : "rw"); + if (flags & MS_USER) { + /* record who mounted this so they can unmount */ + struct passwd *pw = getpwuid(getuid()); + if(pw) + new_opts = xstrconcat3(new_opts, ",user=", pw->pw_name); + } + if (flags & MS_USERS) + new_opts = xstrconcat3(new_opts, ",users", ""); + + for (om = opt_map; om->opt != NULL; om++) { + if (om->skip) + continue; + if (om->inv || !om->mask || (flags & om->mask) != om->mask) + continue; + new_opts = xstrconcat3(new_opts, ",", om->opt); + flags &= ~om->mask; + } + if (extra_opts && *extra_opts) { + new_opts = xstrconcat3(new_opts, ",", extra_opts); + } + return new_opts; +} + +static void +init_mntent(struct mntent *mnt, char *fsname, char *dir, char *type, + int flags, char *opts) +{ + mnt->mnt_fsname = fsname; + mnt->mnt_dir = dir; + mnt->mnt_type = type; + mnt->mnt_opts = fix_opts_string(flags & ~MS_NOMTAB, opts); + + /* these are always zero for NFS */ + mnt->mnt_freq = 0; + mnt->mnt_passno = 0; +} + +/* Create mtab with a root entry. */ +static void +create_mtab (void) { + struct mntentchn *fstab; + struct mntent mnt; + int flags; + mntFILE *mfp; + + /* Avoid writing if the mtab is a symlink to /proc/mounts, since + that would create a file /proc/mounts in case the proc filesystem + is not mounted, and the fchmod below would also fail. */ + if (mtab_is_a_symlink()) { + return; + } + + lock_mtab(); + + mfp = nfs_setmntent (MOUNTED, "a+"); + if (mfp == NULL || mfp->mntent_fp == NULL) { + int errsv = errno; + die (EX_FILEIO, _("mount: can't open %s for writing: %s"), + MOUNTED, strerror (errsv)); + } + + /* Find the root entry by looking it up in fstab */ + if ((fstab = getfsfile ("/")) || (fstab = getfsfile ("root"))) { + char *extra_opts; + parse_opts (fstab->m.mnt_opts, &flags, &extra_opts); + init_mntent(&mnt, xstrdup(fstab->m.mnt_fsname), "/", + fstab->m.mnt_type, flags, extra_opts); + free(extra_opts); + + if (nfs_addmntent (mfp, &mnt) == 1) { + int errsv = errno; + die (EX_FILEIO, _("mount: error writing %s: %s"), + _PATH_MOUNTED, strerror (errsv)); + } + } + if (fchmod (fileno (mfp->mntent_fp), 0644) < 0) + if (errno != EROFS) { + int errsv = errno; + die (EX_FILEIO, + _("mount: error changing mode of %s: %s"), + _PATH_MOUNTED, strerror (errsv)); + } + nfs_endmntent (mfp); + + unlock_mtab(); + + reset_mtab_info(); +} + +static int add_mtab(char *spec, char *mount_point, char *fstype, + int flags, char *opts) +{ + struct mntent ment; + int result = EX_SUCCESS; + + init_mntent(&ment, spec, mount_point, fstype, flags, opts); + + if (!nomtab && mtab_does_not_exist()) { + if (verbose > 1) + printf(_("mount: no %s found - creating it..\n"), + MOUNTED); + create_mtab (); + } + + if (!nomtab && mtab_is_writable()) { + if (flags & MS_REMOUNT) + update_mtab(ment.mnt_dir, &ment); + else { + mntFILE *mtab; + + lock_mtab(); + mtab = nfs_setmntent(MOUNTED, "a+"); + if (mtab == NULL || mtab->mntent_fp == NULL) { + nfs_error(_("Can't open mtab: %s"), + strerror(errno)); + result = EX_FILEIO; + } else { + if (nfs_addmntent(mtab, &ment) == 1) { + nfs_error(_("Can't write mount entry to mtab: %s"), + strerror(errno)); + result = EX_FILEIO; + } + } + nfs_endmntent(mtab); + unlock_mtab(); + } + } + + free(ment.mnt_opts); + + return result; +} + +static void parse_opt(const char *opt, int *mask, char *extra_opts, size_t len) +{ + const struct opt_map *om; + + for (om = opt_map; om->opt != NULL; om++) { + if (!strcmp (opt, om->opt)) { + if (om->inv) + *mask &= ~om->mask; + else + *mask |= om->mask; + return; + } + } + + len -= strlen(extra_opts); + + if (*extra_opts && --len > 0) + strcat(extra_opts, ","); + + if ((len -= strlen(opt)) > 0) + strcat(extra_opts, opt); +} + +/* + * Convert the provided mount command-line options into the 4th & + * 5th arguments to mount(2). Output parameter "@flags" gets the + * standard options (indicated by MS_ bits), and output parameter + * "@extra_opts" gets all the filesystem-specific options. + */ +static void parse_opts(const char *options, int *flags, char **extra_opts) +{ + if (options != NULL) { + char *opts = xstrdup(options); + char *opt, *p; + size_t len = strlen(opts) + 1; /* include room for a null */ + int open_quote = 0; + + *extra_opts = xmalloc(len); + **extra_opts = '\0'; + + for (p = opts, opt = NULL; p && *p; p++) { + if (!opt) + opt = p; /* begin of the option item */ + if (*p == '"') + open_quote ^= 1; /* reverse the status */ + if (open_quote) + continue; /* still in a quoted block */ + if (*p == ',') + *p = '\0'; /* terminate the option item */ + + /* end of option item or last item */ + if (*p == '\0' || *(p + 1) == '\0') { + parse_opt(opt, flags, *extra_opts, len); + opt = NULL; + } + } + free(opts); + } +} + +static int try_mount(char *spec, char *mount_point, int flags, + char *fs_type, char **extra_opts, char *mount_opts, + int fake, int bg) +{ + int ret; + + if (string) + ret = nfsmount_string(spec, mount_point, fs_type, flags, + extra_opts, fake, bg); + else { + if (strcmp(fs_type, "nfs4") == 0) + ret = nfs4mount(spec, mount_point, flags, + extra_opts, fake, bg); + else + ret = nfsmount(spec, mount_point, flags, + extra_opts, fake, bg); + } + + if (ret) + return ret; + + if (!fake) + print_one(spec, mount_point, fs_type, mount_opts); + + return add_mtab(spec, mount_point, fs_type, flags, *extra_opts); +} + +int main(int argc, char *argv[]) +{ + int c, flags = 0, mnt_err = 1, fake = 0; + char *spec = NULL, *mount_point = NULL, *fs_type = "nfs"; + char *extra_opts = NULL, *mount_opts = NULL; + uid_t uid = getuid(); + + progname = basename(argv[0]); + + nfs_mount_data_version = discover_nfs_mount_data_version(&string); + + if(!strncmp(progname, "umount", strlen("umount"))) + exit(nfsumount(argc, argv)); + + mount_config_init(progname); + + while ((c = getopt_long(argc, argv, "rvVwfno:hs", + longopts, NULL)) != -1) { + switch (c) { + case 'r': + flags |= MS_RDONLY; + break; + case 'v': + ++verbose; + break; + case 'V': + printf("%s: ("PACKAGE_STRING")\n", progname); + exit(EX_SUCCESS); + case 'w': + flags &= ~MS_RDONLY; + break; + case 'f': + ++fake; + break; + case 'n': + ++nomtab; + break; + case 'o': /* specify mount options */ + if (mount_opts) + mount_opts = xstrconcat3(mount_opts, ",", optarg); + else + mount_opts = xstrdup(optarg); + break; + case 's': + ++sloppy; + break; + case 'h': + default: + mount_usage(); + goto out_usage; + } + } + + if ((argc < 3)) { + mount_usage(); + exit(EX_USAGE); + } + + /* + * Extra non-option words at the end are bogus... + */ + if (optind != argc - 2) { + mount_usage(); + goto out_usage; + } else { + while (optind < argc) { + if (!spec) + spec = argv[optind]; + else + mount_point = argv[optind]; + optind++; + } + } + + if (strcmp(progname, "mount.nfs4") == 0) + fs_type = "nfs4"; + + /* + * If a non-root user is attempting to mount, make sure the + * user's requested options match the options specified in + * /etc/fstab; otherwise, don't allow the mount. + */ + if (uid != 0) { + struct mntentchn *mc; + + if ((mc = getfsfile(mount_point)) == NULL || + strcmp(mc->m.mnt_fsname, spec) != 0 || + strcmp(mc->m.mnt_type, fs_type) != 0) { + nfs_error(_("%s: permission denied: no match for %s " + "found in /etc/fstab"), progname, mount_point); + goto out_usage; + } + + /* + * 'mount' munges the options from fstab before passing them + * to us, so it is non-trivial to test that we have the correct + * set of options and we don't want to trust what the user + * gave us, so just take whatever is in /etc/fstab. + */ + mount_opts = strdup(mc->m.mnt_opts); + } + + mount_point = canonicalize(mount_point); + if (!mount_point) { + nfs_error(_("%s: no mount point provided"), progname); + goto out_usage; + } + if (mount_point[0] != '/') { + nfs_error(_("%s: unrecognized mount point %s"), + progname, mount_point); + mnt_err = EX_USAGE; + goto out; + } + /* + * Concatenate mount options from the configuration file + */ + mount_opts = mount_config_opts(spec, mount_point, mount_opts); + + parse_opts(mount_opts, &flags, &extra_opts); + + if (uid != 0) { + if (!(flags & (MS_USERS|MS_USER))) { + nfs_error(_("%s: permission denied"), progname); + mnt_err = EX_USAGE; + goto out; + } + + if (geteuid() != 0) { + nfs_error(_("%s: not installed setuid - " + "\"user\" NFS mounts not supported."), progname); + exit(EX_FAIL); + } + } + + if (chk_mountpoint(mount_point)) { + mnt_err = EX_USAGE; + goto out; + } + + mnt_err = try_mount(spec, mount_point, flags, fs_type, &extra_opts, + mount_opts, fake, FOREGROUND); + if (mnt_err == EX_BG) { + printf(_("%s: backgrounding \"%s\"\n"), + progname, spec); + printf(_("%s: mount options: \"%s\"\n"), + progname, extra_opts); + + fflush(stdout); + + /* + * Parent exits immediately with success. + */ + if (daemon(0, 0)) { + nfs_error(_("%s: failed to start " + "background process: %s\n"), + progname, strerror(errno)); + exit(EX_FAIL); + } + + mnt_err = try_mount(spec, mount_point, flags, fs_type, + &extra_opts, mount_opts, fake, + BACKGROUND); + if (verbose && mnt_err) + printf(_("%s: giving up \"%s\"\n"), + progname, spec); + } + +out: + free(mount_opts); + free(extra_opts); + free(mount_point); + exit(mnt_err); + +out_usage: + free(mount_opts); + exit(EX_USAGE); +} diff --git a/utils/mount/mount.nfs.man b/utils/mount/mount.nfs.man new file mode 100644 index 0000000..a78a3b0 --- /dev/null +++ b/utils/mount/mount.nfs.man @@ -0,0 +1,93 @@ +.\"@(#)mount.nfs.8" +.TH MOUNT.NFS 8 "5 Jun 2006" +.SH NAME +mount.nfs, mount.nfs4 \- mount a Network File System +.SH SYNOPSIS +.BI "mount.nfs" " remotetarget dir" " [\-rvVwfnsh ] [\-o " options "] +.SH DESCRIPTION +.BR mount.nfs +is a part of +.BR nfs (5) +utilities package, which provides NFS client functionality. + +.BR mount.nfs +is meant to be used by the +.BR mount (8) +command for mounting NFS shares. This subcommand, however, can also be used as a standalone command with limited functionality. + +.I remotetarget +is a server share usually in the form of +.BR servername:/path/to/share. +.I dir +is the directory on which the file system is to be mounted. + +Under Linux 2.6.32 and later kernel versions, +.BR mount.nfs +can mount all NFS file system versions. Under earlier Linux kernel versions, +.BR mount.nfs4 +must be used for mounting NFSv4 file systems while +.BR mount.nfs +must be used for NFSv3. + +.SH OPTIONS +.TP +.BI "\-r" +Mount file system readonly. +.TP +.BI "\-v" +Be verbose. +.TP +.BI "\-V" +Print version. +.TP +.BI "\-w" +Mount file system read-write. +.TP +.BI "\-f" +Fake mount. Don't actually call the mount system call. +.TP +.BI "\-n" +Do not update +.I /etc/mtab. +By default, an entry is created in +.I /etc/mtab +for every mounted file system. Use this option to skip making an entry. +.TP +.BI "\-s" +Tolerate sloppy mount options rather than fail. +.TP +.BI "\-h" +Print help message. +.TP +.BI "nfsoptions" +Refer to +.BR nfs (5) +or +.BR mount (8) +manual pages. + +.SH NOTE +For further information please refer +.BR nfs (5) +and +.BR mount (8) +manual pages. + +.SH FILES +.TP 18n +.I /etc/fstab +file system table +.TP +.I /etc/mtab +table of mounted file systems +.TP +.I /etc/nfsmount.conf +Configuration file for NFS mounts +.PD +.SH "SEE ALSO" +.BR nfs (5), +.BR nfsmount.conf (5), +.BR mount (8), + +.SH "AUTHOR" +Amit Gud diff --git a/utils/mount/mount_config.h b/utils/mount/mount_config.h new file mode 100644 index 0000000..7cc72fc --- /dev/null +++ b/utils/mount/mount_config.h @@ -0,0 +1,55 @@ +#ifndef _LINUX_MOUNT_CONFIG_H +#define _LINUX_MOUNT_CONFIG_H +/* + * mount_config.h -- mount configuration file routines + * Copyright (C) 2008 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifdef MOUNT_CONFIG +#include "conffile.h" +#include "xlog.h" + +#ifndef MOUNTOPTS_CONFFILE +#define MOUNTOPTS_CONFFILE "/etc/nfsmount.conf" +#endif + +extern char *conf_get_mntopts(char *, char *, char *); + +static inline void mount_config_init(char *program) +{ + xlog_open(program); + /* + * Read the the default mount options + */ + conf_init_file(MOUNTOPTS_CONFFILE); +} + +static inline char *mount_config_opts(char *spec, + char *mount_point, char *mount_opts) +{ + return conf_get_mntopts(spec, mount_point, mount_opts); +} + +#else /* MOUNT_CONFIG */ + +static inline void mount_config_init(__attribute__ ((unused)) char *program) { } + +static inline char *mount_config_opts(__attribute__ ((unused)) char *spec, + __attribute__ ((unused)) char *mount_point, char *mount_opts) +{ + return mount_opts; +} +#endif /* MOUNT_CONFIG */ + +#endif /* _LINUX_MOUNT_CONFIG_H */ diff --git a/utils/mount/mount_constants.h b/utils/mount/mount_constants.h new file mode 100644 index 0000000..4d050d8 --- /dev/null +++ b/utils/mount/mount_constants.h @@ -0,0 +1,71 @@ +#ifndef _NFS_UTILS_MOUNT_CONSTANTS_H +#define _NFS_UTILS_MOUNT_CONSTANTS_H + +#ifndef MS_DIRSYNC +#define MS_DIRSYNC 128 /* Directory modifications are synchronous */ +#endif + +#ifndef MS_ACTION_MASK +#define MS_ACTION_MASK 0x380 +/* Remount, but new filesystem may be different from old. Atomic + (i.e. there is no interval when nothing is mounted at the mountpoint). + If new fs differs from the old one and old is busy - -EBUSY. */ +#define MS_REPLACE 0x080 /* 128 */ +/* After, Before: as soon as we get unions these will add a new member + in the end or beginning of the chain. Fail if there is a stack + on the mountpoint. */ +#define MS_AFTER 0x100 /* 256 */ +#define MS_BEFORE 0x180 +/* Over: if nothing mounted on a mountpoint - same as if none of these +flags had been set; if we have a union with more than one element - fail; +if we have a stack or plain mount - mount atop of it, forming a stack. */ +#define MS_OVER 0x200 /* 512 */ +#endif +#ifndef MS_NOATIME +#define MS_NOATIME 0x400 /* 1024: Do not update access times. */ +#endif +#ifndef MS_NODIRATIME +#define MS_NODIRATIME 0x800 /* 2048: Don't update directory access times */ +#endif +#ifndef MS_BIND +#define MS_BIND 0x1000 /* 4096: Mount existing tree also elsewhere */ +#endif +#ifndef MS_MOVE +#define MS_MOVE 0x2000 /* 8192: Atomically move tree */ +#endif +#ifndef MS_REC +#define MS_REC 0x4000 /* 16384: Recursive loopback */ +#endif +#ifndef MS_VERBOSE +#define MS_VERBOSE 0x8000 /* 32768 */ +#endif +#ifndef MS_RELATIME +#define MS_RELATIME 0x200000 /* 200000: Update access times relative + to mtime/ctime */ +#endif +/* + * NFS fs-specific mount option flags + * + * MS_DUMMY is assigned to mount options that are not to be + * passed to the kernel via the "flags" argument. These are + * generally ignored or handled entirely in user space. + */ +#define MS_DUMMY 0x00000000 +#define MS_USERS 0x40000000 +#define MS_USER 0x80000000 + +/* + * Magic mount flag number. Had to be or-ed to the flag values. + */ +#ifndef MS_MGC_VAL +#define MS_MGC_VAL 0xC0ED0000 /* magic flag number to indicate "new" flags */ +#endif +#ifndef MS_MGC_MSK +#define MS_MGC_MSK 0xffff0000 /* magic flag number mask */ +#endif + +/* Generic options that are prevented from appearing + * in the options field in /etc/mtab. */ +#define MS_NOMTAB (MS_REMOUNT) + +#endif /* _NFS_UTILS_MOUNT_CONSTANTS_H */ diff --git a/utils/mount/mount_libmount.c b/utils/mount/mount_libmount.c new file mode 100644 index 0000000..aa4ac5c --- /dev/null +++ b/utils/mount/mount_libmount.c @@ -0,0 +1,461 @@ +/* + * mount_libmount.c -- Linux NFS [u]mount based on libmount + * + * Copyright (C) 2011 Karel Zak + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#include "nls.h" +#include "mount_config.h" + +#include "nfs_mount.h" +#include "nfs4_mount.h" +#include "stropts.h" +#include "version.h" +#include "xcommon.h" + +#include "error.h" +#include "utils.h" + +char *retrieve_mount_options(struct libmnt_fs *fs); + +char *progname; +int nfs_mount_data_version; +int verbose; +int sloppy; +int string; +int nomtab; + +#define FOREGROUND (0) +#define BACKGROUND (1) + +/* + * Store mount options to mtab (or /dev/.mount/utab), called from mount.nfs. + * + * Note that on systems without /etc/mtab the fs-specific options are not + * managed by libmount at all. We have to use "mount attributes" that are + * private for mount. helpers. + */ +static void store_mount_options(struct libmnt_fs *fs, const char *nfs_opts) +{ + char *o = NULL; + + mnt_fs_set_attributes(fs, nfs_opts); /* for non-mtab systems */ + + /* for mtab create a new options list */ + mnt_optstr_append_option(&o, mnt_fs_get_vfs_options(fs), NULL); + mnt_optstr_append_option(&o, nfs_opts, NULL); + mnt_optstr_append_option(&o, mnt_fs_get_user_options(fs), NULL); + + mnt_fs_set_options(fs, o); + free(o); +} + +/* + * Retrieve mount options from mtab (or /dev/.mount/utab) called from umount.nfs. + * + * The result can passed to free(). + */ +char *retrieve_mount_options(struct libmnt_fs *fs) +{ + const char *opts; + + if (!fs) + return NULL; + + opts = mnt_fs_get_attributes(fs); /* /dev/.mount/utab */ + if (opts) + return strdup(opts); + + return mnt_fs_strdup_options(fs); /* /etc/mtab */ +} + +static int try_mount(struct libmnt_context *cxt, int bg) +{ + struct libmnt_fs *fs; + const char *p; + char *src = NULL, *tgt = NULL, *type = NULL, *opts = NULL; + unsigned long flags = 0; + int fake, ret = 0; + + fs = mnt_context_get_fs(cxt); + + /* libmount returns read-only pointers (const char) + * so, reallocate for nfsmount() functions. + */ + if ((p = mnt_fs_get_source(fs))) /* spec */ + src = strdup(p); + if ((p = mnt_fs_get_target(fs))) /* mountpoint */ + tgt = strdup(p); + if ((p = mnt_fs_get_fstype(fs))) /* FS type */ + type = strdup(p); + if ((p = mnt_fs_get_fs_options(fs))) /* mount options */ + opts = strdup(p); + + mnt_context_get_mflags(cxt, &flags); /* mount(2) flags */ + fake = mnt_context_is_fake(cxt); + + if (string) + ret = nfsmount_string(src, tgt, type, flags, &opts, fake, bg); + + else if (strcmp(type, "nfs4") == 0) + ret = nfs4mount(src, tgt, flags, &opts, fake, bg); + else + ret = nfsmount(src, tgt, flags, &opts, fake, bg); + + /* Store mount options if not called with mount --no-mtab */ + if (!ret && !mnt_context_is_nomtab(cxt)) + store_mount_options(fs, opts); + + free(src); + free(tgt); + free(type); + free(opts); + + return ret; +} + +/* returns: error = -1, success = 1 , not vers4 == 0 */ +static int is_vers4(struct libmnt_context *cxt) +{ + struct libmnt_fs *fs = mnt_context_get_fs(cxt); + struct libmnt_table *tb = NULL; + const char *src = mnt_context_get_source(cxt), + *tgt = mnt_context_get_target(cxt); + int rc = 0; + + if (!src || !tgt) + return -1; + + if (!mnt_fs_is_kernel(fs)) { + struct libmnt_table *tb = mnt_new_table_from_file("/proc/mounts"); + + if (!tb) + return -1; + fs = mnt_table_find_pair(tb, src, tgt, MNT_ITER_BACKWARD); + } + + if (fs) { + const char *type = mnt_fs_get_fstype(fs); + if (type && strcmp(type, "nfs4") == 0) + rc = 1; + } + mnt_free_table(tb); + return rc; +} + +static int umount_main(struct libmnt_context *cxt, int argc, char **argv) +{ + int rc, c; + char *spec = NULL, *opts = NULL; + int ret = EX_FAIL, verbose = 0; + + static const struct option longopts[] = { + { "force", 0, 0, 'f' }, + { "help", 0, 0, 'h' }, + { "no-mtab", 0, 0, 'n' }, + { "verbose", 0, 0, 'v' }, + { "read-only", 0, 0, 'r' }, + { "lazy", 0, 0, 'l' }, + { "types", 1, 0, 't' }, + { NULL, 0, 0, 0 } + }; + + mnt_context_init_helper(cxt, MNT_ACT_UMOUNT, 0); + mnt_context_disable_canonicalize(cxt, 1); + + while ((c = getopt_long (argc, argv, "fvnrlh", longopts, NULL)) != -1) { + + rc = mnt_context_helper_setopt(cxt, c, optarg); + if (rc == 0) /* valid option */ + continue; + if (rc < 0) /* error (probably ENOMEM) */ + goto err; + /* rc==1 means unknow option */ + umount_usage(); + return EX_USAGE; + } + + verbose = mnt_context_is_verbose(cxt); + + if (optind < argc) + spec = argv[optind++]; + + if (!spec || (*spec != '/' && strchr(spec,':') == NULL)) { + nfs_error(_("%s: no mount point provided"), progname); + umount_usage(); + return EX_USAGE; + } + + if (mnt_context_set_target(cxt, spec)) + goto err; + + /* read mtab/fstab, evaluate permissions, etc. */ + rc = mnt_context_prepare_umount(cxt); + if (rc) { + nfs_error(_("%s: failed to prepare umount: %s\n"), + progname, strerror(-rc)); + goto err; + } + + if (mnt_context_get_fstype(cxt) && + !mnt_match_fstype(mnt_context_get_fstype(cxt), "nfs,nfs4")) { + + nfs_error(_("%s: %s: is not an NFS filesystem"), progname, spec); + ret = EX_USAGE; + goto err; + } + + if (verbose) + printf(_("%s: %s mount point detected\n"), spec, + mnt_context_get_fstype(cxt)); + + opts = retrieve_mount_options(mnt_context_get_fs(cxt)); + + if (!mnt_context_is_lazy(cxt)) { + if (opts) { + /* we have full FS description (e.g. from mtab or /proc) */ + switch (is_vers4(cxt)) { + case 0: + /* We ignore the error from nfs_umount23. + * If the actual umount succeeds (in del_mtab), + * we don't want to signal an error, as that + * could cause /sbin/mount to retry! + */ + nfs_umount23(mnt_context_get_source(cxt), opts); + break; + case 1: /* unknown */ + break; + default: /* error */ + goto err; + } + } else + /* strange, no entry in mtab or /proc not mounted */ + nfs_umount23(spec, "tcp,v3"); + } + + ret = EX_FILEIO; + rc = mnt_context_do_umount(cxt); /* call umount(2) syscall */ + mnt_context_finalize_mount(cxt); /* mtab update */ + + if (rc && !mnt_context_get_status(cxt)) { + /* mnt_context_do_umount() returns errno if umount(2) failed */ + umount_error(rc, spec); + goto err; + } + ret = EX_SUCCESS; +err: + if (verbose) { + if (ret == EX_SUCCESS) + printf(_("%s: umounted\n"), spec); + else + printf(_("%s: umount failed\n"), spec); + } + free(opts); + return ret; +} + +static int mount_main(struct libmnt_context *cxt, int argc, char **argv) +{ + int rc, c; + struct libmnt_fs *fs; + char *spec = NULL, *mount_point = NULL, *opts = NULL; + + static const struct option longopts[] = { + { "fake", 0, 0, 'f' }, + { "help", 0, 0, 'h' }, + { "no-mtab", 0, 0, 'n' }, + { "read-only", 0, 0, 'r' }, + { "ro", 0, 0, 'r' }, + { "verbose", 0, 0, 'v' }, + { "version", 0, 0, 'V' }, + { "read-write", 0, 0, 'w' }, + { "rw", 0, 0, 'w' }, + { "options", 1, 0, 'o' }, + { "sloppy", 0, 0, 's' }, + { NULL, 0, 0, 0 } + }; + + mount_config_init(progname); + mnt_context_init_helper(cxt, MNT_ACT_MOUNT, 0); + + while ((c = getopt_long(argc, argv, "fhnrVvwo:s", longopts, NULL)) != -1) { + + rc = mnt_context_helper_setopt(cxt, c, optarg); + if (rc == 0) /* valid option */ + continue; + if (rc < 0) /* error (probably ENOMEM) */ + goto err; + /* rc==1 means unknow option */ + switch (c) { + case 'V': + printf("%s: ("PACKAGE_STRING")\n", progname); + return EX_SUCCESS; + case 'h': + default: + mount_usage(); + return EX_USAGE; + } + } + + if (optind < argc) + spec = argv[optind++]; + if (optind < argc) + mount_point = argv[optind++]; + + if (!mount_point) { + nfs_error(_("%s: no mount point provided"), progname); + mount_usage(); + goto err; + } + if (!spec) { + nfs_error(_("%s: no mount spec provided"), progname); + goto err; + } + + if (geteuid() != 0) { + nfs_error(_("%s: not installed setuid - " + "\"user\" NFS mounts not supported."), progname); + goto err; + } + + verbose = mnt_context_is_verbose(cxt); + sloppy = mnt_context_is_sloppy(cxt); + nomtab = mnt_context_is_nomtab(cxt); + + if (strcmp(progname, "mount.nfs4") == 0) + mnt_context_set_fstype(cxt, "nfs4"); + else + mnt_context_set_fstype(cxt, "nfs"); /* default */ + + rc = mnt_context_set_source(cxt, spec); + if (!rc) + mnt_context_set_target(cxt, mount_point); + if (rc) { + nfs_error(_("%s: failed to set spec or mountpoint: %s"), + progname, strerror(errno)); + goto err; + } + + mount_point = mnt_resolve_path(mount_point, + mnt_context_get_cache(cxt)); + + if (chk_mountpoint(mount_point)) + goto err; + + /* + * The libmount strictly uses only options from fstab if running in + * restricted mode (suid, non-root user). This is done in + * mnt_context_prepare_mount() by default. + * + * We have to read fstab before nfsmount.conf, otherwise the options + * from nfsmount.conf will be ignored (overwrited). + */ + rc = mnt_context_apply_fstab(cxt); + if (rc) { + nfs_error(_("%s: failed to apply fstab options\n"), progname); + goto err; + } + + /* + * Concatenate mount options from the configuration file + */ + fs = mnt_context_get_fs(cxt); + if (fs) { + opts = mnt_fs_strdup_options(fs); + + opts = mount_config_opts(spec, mount_point, opts); + mnt_fs_set_options(fs, opts); + } + + rc = mnt_context_prepare_mount(cxt); + if (rc) { + nfs_error(_("%s: failed to prepare mount: %s\n"), + progname, strerror(-rc)); + goto err; + } + + rc = try_mount(cxt, FOREGROUND); + + if (rc == EX_BG) { + printf(_("%s: backgrounding \"%s\"\n"), + progname, mnt_context_get_source(cxt)); + printf(_("%s: mount options: \"%s\"\n"), + progname, opts); + + fflush(stdout); + + if (daemon(0, 0)) { + nfs_error(_("%s: failed to start " + "background process: %s\n"), + progname, strerror(errno)); + exit(EX_FAIL); + } + + rc = try_mount(cxt, BACKGROUND); + + if (verbose && rc) + printf(_("%s: giving up \"%s\"\n"), + progname, mnt_context_get_source(cxt)); + } + + mnt_context_set_syscall_status(cxt, rc == EX_SUCCESS ? 0 : -1); + mnt_context_finalize_mount(cxt); /* mtab update */ + return rc; +err: + return EX_FAIL; +} + +int main(int argc, char *argv[]) +{ + struct libmnt_context *cxt; + int rc; + + mnt_init_debug(0); + cxt = mnt_new_context(); + if (!cxt) { + nfs_error(_("Can't initilize libmount: %s"), + strerror(errno)); + rc = EX_FAIL; + goto done; + } + + progname = basename(argv[0]); + nfs_mount_data_version = discover_nfs_mount_data_version(&string); + + if(strncmp(progname, "umount", 6) == 0) + rc = umount_main(cxt, argc, argv); + else + rc = mount_main(cxt, argc, argv); +done: + mnt_free_context(cxt); + return rc; +} diff --git a/utils/mount/network.c b/utils/mount/network.c new file mode 100644 index 0000000..01ead49 --- /dev/null +++ b/utils/mount/network.c @@ -0,0 +1,1801 @@ +/* + * network.c -- Provide common network functions for NFS mount/umount + * + * Copyright (C) 2007 Oracle. All rights reserved. + * Copyright (C) 2007 Chuck Lever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#if defined(__GLIBC__) && ((__GLIBC__ < 2) || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 24)) +/* Cannot safely include linux/in6.h in old glibc, so hardcode the needed values */ +# define IPV6_PREFER_SRC_PUBLIC 2 +# define IPV6_ADDR_PREFERENCES 72 +#else +# include +#endif +#include +#include +#include +#include +#include +#include + +#include "sockaddr.h" +#include "xcommon.h" +#include "mount.h" +#include "nls.h" +#include "nfs_mount.h" +#include "mount_constants.h" +#include "nfsrpc.h" +#include "parse_opt.h" +#include "network.h" +#include "conffile.h" +#include "nfslib.h" + +#define PMAP_TIMEOUT (10) +#define CONNECT_TIMEOUT (20) +#define MOUNT_TIMEOUT (30) +#define STATD_TIMEOUT (10) + +#define SAFE_SOCKADDR(x) (struct sockaddr *)(char *)(x) + +extern int nfs_mount_data_version; +extern char *progname; +extern int verbose; + +static const char *nfs_mnt_pgmtbl[] = { + "mount", + "mountd", + NULL, +}; + +static const char *nfs_nfs_pgmtbl[] = { + "nfs", + "nfsprog", + NULL, +}; + +static const char *nfs_transport_opttbl[] = { + "udp", + "tcp", + "rdma", + "proto", + NULL, +}; + +static const char *nfs_version_opttbl[] = { + "v2", /* no longer supported */ + "v3", + "v4", + "vers", + "nfsvers", + NULL, +}; + +static const unsigned long nfs_to_mnt[] = { + 0, + 0, + 1, + 3, +}; + +static const unsigned long mnt_to_nfs[] = { + 0, + 2, + 2, + 3, +}; + +/* + * Map an NFS version into the corresponding Mountd version + */ +unsigned long nfsvers_to_mnt(const unsigned long vers) +{ + if (vers <= 3) + return nfs_to_mnt[vers]; + return 0; +} + +/* + * Map a Mountd version into the corresponding NFS version + */ +static unsigned long mntvers_to_nfs(const unsigned long vers) +{ + if (vers <= 3) + return mnt_to_nfs[vers]; + return 0; +} + +static const unsigned int probe_udp_only[] = { + IPPROTO_UDP, + 0, +}; + +static const unsigned int probe_udp_first[] = { + IPPROTO_UDP, + IPPROTO_TCP, + 0, +}; + +static const unsigned int probe_tcp_first[] = { + IPPROTO_TCP, + IPPROTO_UDP, + 0, +}; + +static const unsigned long probe_nfs2_only[] = { + 2, + 0, +}; + +static const unsigned long probe_nfs3_only[] = { + 3, + 0, +}; + +static const unsigned long probe_mnt1_first[] = { + 1, + 2, + 0, +}; + +static const unsigned long probe_mnt3_only[] = { + 3, + 0, +}; + +static const unsigned int *nfs_default_proto(void); +#ifdef MOUNT_CONFIG +static const unsigned int *nfs_default_proto(void) +{ + extern unsigned long config_default_proto; + /* + * If the default proto has been set and + * its not TCP, start with UDP + */ + if (config_default_proto && config_default_proto != IPPROTO_TCP) + return probe_udp_first; + + return probe_tcp_first; +} +#else +static const unsigned int *nfs_default_proto() +{ + return probe_tcp_first; +} +#endif /* MOUNT_CONFIG */ + +/** + * nfs_lookup - resolve hostname to an IPv4 or IPv6 socket address + * @hostname: pointer to C string containing DNS hostname to resolve + * @family: address family hint + * @sap: pointer to buffer to fill with socket address + * @len: IN: size of buffer to fill; OUT: size of socket address + * + * Returns 1 and places a socket address at @sap if successful; + * otherwise zero. + */ +int nfs_lookup(const char *hostname, const sa_family_t family, + struct sockaddr *sap, socklen_t *salen) +{ + struct addrinfo *gai_results; + struct addrinfo gai_hint = { + .ai_family = family, + }; + socklen_t len = *salen; + int error, ret = 0; + + *salen = 0; + + error = getaddrinfo(hostname, NULL, &gai_hint, &gai_results); + switch (error) { + case 0: + break; + case EAI_SYSTEM: + nfs_error(_("%s: DNS resolution failed for %s: %s"), + progname, hostname, strerror(errno)); + return ret; + default: + nfs_error(_("%s: DNS resolution failed for %s: %s"), + progname, hostname, gai_strerror(error)); + return ret; + } + + switch (gai_results->ai_addr->sa_family) { + case AF_INET: + case AF_INET6: + if (len >= gai_results->ai_addrlen) { + *salen = gai_results->ai_addrlen; + memcpy(sap, gai_results->ai_addr, *salen); + ret = 1; + } + break; + default: + /* things are really broken if we get here, so warn */ + nfs_error(_("%s: unrecognized DNS resolution results for %s"), + progname, hostname); + break; + } + + nfs_freeaddrinfo(gai_results); + return ret; +} + +/** + * nfs_gethostbyname - resolve a hostname to an IPv4 address + * @hostname: pointer to a C string containing a DNS hostname + * @sin: returns an IPv4 address + * + * Returns 1 if successful, otherwise zero. + */ +int nfs_gethostbyname(const char *hostname, struct sockaddr_in *sin) +{ + socklen_t len = sizeof(*sin); + + return nfs_lookup(hostname, AF_INET, (struct sockaddr *)sin, &len); +} + +/** + * nfs_string_to_sockaddr - convert string address to sockaddr + * @address: pointer to presentation format address to convert + * @sap: pointer to socket address buffer to fill in + * @salen: IN: length of address buffer + * OUT: length of converted socket address + * + * Convert a presentation format address string to a socket address. + * Similar to nfs_lookup(), but the DNS query is squelched, and it + * won't make any noise if the getaddrinfo() call fails. + * + * Returns 1 and fills in @sap and @salen if successful; otherwise zero. + * + * See RFC 4038 section 5.1 or RFC 3513 section 2.2 for more details + * on presenting IPv6 addresses as text strings. + */ +int nfs_string_to_sockaddr(const char *address, struct sockaddr *sap, + socklen_t *salen) +{ + struct addrinfo *gai_results; + struct addrinfo gai_hint = { + .ai_flags = AI_NUMERICHOST, + }; + socklen_t len = *salen; + int ret = 0; + + *salen = 0; + + if (getaddrinfo(address, NULL, &gai_hint, &gai_results) == 0) { + switch (gai_results->ai_addr->sa_family) { + case AF_INET: + case AF_INET6: + if (len >= gai_results->ai_addrlen) { + *salen = gai_results->ai_addrlen; + memcpy(sap, gai_results->ai_addr, *salen); + ret = 1; + } + break; + } + nfs_freeaddrinfo(gai_results); + } + + return ret; +} + +/** + * nfs_present_sockaddr - convert sockaddr to string + * @sap: pointer to socket address to convert + * @salen: length of socket address + * @buf: pointer to buffer to fill in + * @buflen: length of buffer + * + * Convert the passed-in sockaddr-style address to presentation format. + * The presentation format address is placed in @buf and is + * '\0'-terminated. + * + * Returns 1 if successful; otherwise zero. + * + * See RFC 4038 section 5.1 or RFC 3513 section 2.2 for more details + * on presenting IPv6 addresses as text strings. + */ +int nfs_present_sockaddr(const struct sockaddr *sap, const socklen_t salen, + char *buf, const size_t buflen) +{ +#ifdef HAVE_GETNAMEINFO + int result; + + result = getnameinfo(sap, salen, buf, buflen, + NULL, 0, NI_NUMERICHOST); + if (!result) + return 1; + + nfs_error(_("%s: invalid server address: %s"), progname, + gai_strerror(result)); + return 0; +#else /* HAVE_GETNAMEINFO */ + char *addr; + + if (sap->sa_family == AF_INET) { + addr = inet_ntoa(((struct sockaddr_in *)sap)->sin_addr); + if (addr && strlen(addr) < buflen) { + strcpy(buf, addr); + return 1; + } + } + + nfs_error(_("%s: invalid server address"), progname); + return 0; +#endif /* HAVE_GETNAMEINFO */ +} + +/* + * Attempt to connect a socket, but time out after "timeout" seconds. + * + * On error return, caller closes the socket. + */ +static int connect_to(int fd, struct sockaddr *addr, + socklen_t addrlen, int timeout) +{ + int ret, saved; + fd_set rset, wset; + struct timeval tv = { + .tv_sec = timeout, + }; + + saved = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, saved | O_NONBLOCK); + + ret = connect(fd, addr, addrlen); + if (ret < 0 && errno != EINPROGRESS) + return -1; + if (ret == 0) + goto out; + + FD_ZERO(&rset); + FD_SET(fd, &rset); + wset = rset; + ret = select(fd + 1, &rset, &wset, NULL, &tv); + if (ret == 0) { + errno = ETIMEDOUT; + return -1; + } + if (FD_ISSET(fd, &rset) || FD_ISSET(fd, &wset)) { + int error; + socklen_t len = sizeof(error); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) + return -1; + if (error) { + errno = error; + return -1; + } + } else + return -1; + +out: + fcntl(fd, F_SETFL, saved); + return 0; +} + +/* + * Create a socket that is locally bound to a reserved or non-reserved port. + * + * The caller should check rpc_createerr to determine the cause of any error. + */ +static int get_socket(struct sockaddr_in *saddr, unsigned int p_prot, + unsigned int timeout, int resvp, int conn) +{ + int so, cc, type; + struct sockaddr_in laddr; + socklen_t namelen = sizeof(laddr); + + type = (p_prot == IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM); + if ((so = socket (AF_INET, type, p_prot)) < 0) + goto err_socket; + + laddr.sin_family = AF_INET; + laddr.sin_port = 0; + laddr.sin_addr.s_addr = htonl(INADDR_ANY); + if (resvp) { + if (bindresvport(so, &laddr) < 0) + goto err_bindresvport; + } + if (type == SOCK_STREAM || (conn && type == SOCK_DGRAM)) { + cc = connect_to(so, SAFE_SOCKADDR(saddr), namelen, + timeout); + if (cc < 0) + goto err_connect; + } + return so; + +err_socket: + rpc_createerr.cf_stat = RPC_SYSTEMERROR; + rpc_createerr.cf_error.re_errno = errno; + if (verbose) { + nfs_error(_("%s: Unable to create %s socket: errno %d (%s)\n"), + progname, p_prot == IPPROTO_UDP ? _("UDP") : _("TCP"), + errno, strerror(errno)); + } + return RPC_ANYSOCK; + +err_bindresvport: + rpc_createerr.cf_stat = RPC_SYSTEMERROR; + rpc_createerr.cf_error.re_errno = errno; + if (verbose) { + nfs_error(_("%s: Unable to bindresvport %s socket: errno %d" + " (%s)\n"), + progname, p_prot == IPPROTO_UDP ? _("UDP") : _("TCP"), + errno, strerror(errno)); + } + close(so); + return RPC_ANYSOCK; + +err_connect: + rpc_createerr.cf_stat = RPC_SYSTEMERROR; + rpc_createerr.cf_error.re_errno = errno; + if (verbose) { + nfs_error(_("%s: Unable to connect to %s:%d, errno %d (%s)\n"), + progname, inet_ntoa(saddr->sin_addr), + ntohs(saddr->sin_port), errno, strerror(errno)); + } + close(so); + return RPC_ANYSOCK; +} + +static void nfs_pp_debug(const struct sockaddr *sap, const socklen_t salen, + const rpcprog_t program, const rpcvers_t version, + const unsigned short protocol, + const unsigned short port) +{ + char buf[NI_MAXHOST]; + + if (!verbose) + return; + + if (nfs_present_sockaddr(sap, salen, buf, sizeof(buf)) == 0) { + buf[0] = '\0'; + strcat(buf, "unknown host"); + } + + fprintf(stderr, _("%s: trying %s prog %lu vers %lu prot %s port %d\n"), + progname, buf, (unsigned long)program, + (unsigned long)version, + (protocol == IPPROTO_UDP ? _("UDP") : _("TCP")), + port); +} + +static void nfs_pp_debug2(const char *str) +{ + if (!verbose) + return; + + if (rpc_createerr.cf_error.re_status == RPC_CANTRECV || + rpc_createerr.cf_error.re_status == RPC_CANTSEND) + nfs_error(_("%s: portmap query %s%s - %s"), + progname, str, clnt_spcreateerror(""), + strerror(rpc_createerr.cf_error.re_errno)); + else + nfs_error(_("%s: portmap query %s%s"), + progname, str, clnt_spcreateerror("")); +} + +/* + * Use the portmapper to discover whether or not the service we want is + * available. The lists 'versions' and 'protos' define ordered sequences + * of service versions and udp/tcp protocols to probe for. + * + * Returns 1 if the requested service port is unambiguous and pingable; + * @pmap is filled in with the version, port, and transport protocol used + * during the successful ping. Note that if a port is already specified + * in @pmap and it matches the rpcbind query result, nfs_probe_port() does + * not perform an RPC ping. + * + * If an error occurs or the requested service isn't available, zero is + * returned; rpccreateerr.cf_stat is set to reflect the nature of the error. + */ +static int nfs_probe_port(const struct sockaddr *sap, const socklen_t salen, + struct pmap *pmap, const unsigned long *versions, + const unsigned int *protos) +{ + union nfs_sockaddr address; + struct sockaddr *saddr = &address.sa; + const unsigned long prog = pmap->pm_prog, *p_vers; + const unsigned int prot = (u_int)pmap->pm_prot, *p_prot; + const u_short port = (u_short) pmap->pm_port; + unsigned long vers = pmap->pm_vers; + unsigned short p_port; + + memcpy(saddr, sap, salen); + p_prot = prot ? &prot : protos; + p_vers = vers ? &vers : versions; + + for (;;) { + if (verbose) + printf(_("%s: prog %lu, trying vers=%lu, prot=%u\n"), + progname, prog, *p_vers, *p_prot); + p_port = nfs_getport(saddr, salen, prog, *p_vers, *p_prot); + if (p_port) { + if (!port || port == p_port) { + nfs_set_port(saddr, p_port); + nfs_pp_debug(saddr, salen, prog, *p_vers, + *p_prot, p_port); + if (nfs_rpc_ping(saddr, salen, prog, + *p_vers, *p_prot, NULL)) + goto out_ok; + } else + rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED; + } + if (rpc_createerr.cf_stat != RPC_PROGNOTREGISTERED && + rpc_createerr.cf_stat != RPC_TIMEDOUT && + rpc_createerr.cf_stat != RPC_CANTRECV && + rpc_createerr.cf_stat != RPC_PROGVERSMISMATCH) + break; + + if (!prot) { + if (*++p_prot) { + nfs_pp_debug2("retrying"); + continue; + } + p_prot = protos; + } + if (rpc_createerr.cf_stat == RPC_TIMEDOUT || + rpc_createerr.cf_stat == RPC_CANTRECV) + break; + + if (vers || !*++p_vers) + break; + } + + nfs_pp_debug2("failed"); + return 0; + +out_ok: + if (!vers) + pmap->pm_vers = *p_vers; + if (!prot) + pmap->pm_prot = *p_prot; + if (!port) + pmap->pm_port = p_port; + nfs_clear_rpc_createerr(); + return 1; +} +/* + * Probe a server's NFS service to determine which versions and + * transport protocols are supported. + * + * Returns 1 if the requested service port is unambiguous and pingable; + * @pmap is filled in with the version, port, and transport protocol used + * during the successful ping. If all three are already specified, simply + * return success without an rpcbind query or RPC ping (we may be trying + * to mount an NFS service that is not advertised via rpcbind). + * + * If an error occurs or the requested service isn't available, zero is + * returned; rpccreateerr.cf_stat is set to reflect the nature of the error. + */ +static int nfs_probe_nfsport(const struct sockaddr *sap, const socklen_t salen, + struct pmap *pmap, int checkv4) +{ + if (pmap->pm_vers && pmap->pm_prot && pmap->pm_port) + return 1; + + if (nfs_mount_data_version >= 4) { + const unsigned int *probe_proto; + int ret; + struct sockaddr_storage save_sa; + + probe_proto = nfs_default_proto(); + memcpy(&save_sa, sap, salen); + + ret = nfs_probe_port(sap, salen, pmap, + probe_nfs3_only, probe_proto); + if (!ret || !checkv4 || probe_proto != probe_tcp_first) + return ret; + + nfs_set_port((struct sockaddr *)&save_sa, NFS_PORT); + ret = nfs_rpc_ping((struct sockaddr *)&save_sa, salen, + NFS_PROGRAM, 4, IPPROTO_TCP, NULL); + if (ret) { + rpc_createerr.cf_stat = RPC_FAILED; + rpc_createerr.cf_error.re_errno = EAGAIN; + return 0; + } + return 1; + } else + return nfs_probe_port(sap, salen, pmap, + probe_nfs2_only, probe_udp_only); +} + +/* + * Probe a server's mountd service to determine which versions and + * transport protocols are supported. + * + * Returns 1 if the requested service port is unambiguous and pingable; + * @pmap is filled in with the version, port, and transport protocol used + * during the successful ping. If all three are already specified, simply + * return success without an rpcbind query or RPC ping (we may be trying + * to mount an NFS service that is not advertised via rpcbind). + * + * If an error occurs or the requested service isn't available, zero is + * returned; rpccreateerr.cf_stat is set to reflect the nature of the error. + */ +static int nfs_probe_mntport(const struct sockaddr *sap, const socklen_t salen, + struct pmap *pmap) +{ + if (pmap->pm_vers && pmap->pm_prot && pmap->pm_port) + return 1; + + if (nfs_mount_data_version >= 4) + return nfs_probe_port(sap, salen, pmap, + probe_mnt3_only, probe_udp_first); + else + return nfs_probe_port(sap, salen, pmap, + probe_mnt1_first, probe_udp_only); +} + +/* + * Probe a server's mountd service to determine which versions and + * transport protocols are supported. Invoked when the protocol + * version is already known for both the NFS and mountd service. + * + * Returns 1 and fills in both @pmap structs if the requested service + * ports are unambiguous and pingable. Otherwise zero is returned; + * rpccreateerr.cf_stat is set to reflect the nature of the error. + */ +static int nfs_probe_version_fixed(const struct sockaddr *mnt_saddr, + const socklen_t mnt_salen, + struct pmap *mnt_pmap, + const struct sockaddr *nfs_saddr, + const socklen_t nfs_salen, + struct pmap *nfs_pmap) +{ + if (!nfs_probe_nfsport(nfs_saddr, nfs_salen, nfs_pmap, 0)) + return 0; + return nfs_probe_mntport(mnt_saddr, mnt_salen, mnt_pmap); +} + +/** + * nfs_probe_bothports - discover the RPC endpoints of mountd and NFS server + * @mnt_saddr: pointer to socket address of mountd server + * @mnt_salen: length of mountd server's address + * @mnt_pmap: IN: partially filled-in mountd RPC service tuple; + * OUT: fully filled-in mountd RPC service tuple + * @nfs_saddr: pointer to socket address of NFS server + * @nfs_salen: length of NFS server's address + * @nfs_pmap: IN: partially filled-in NFS RPC service tuple; + * OUT: fully filled-in NFS RPC service tuple + * @checkv4: Flag indicating that if v3 is available we must also + * check v4, and if that is available, set re_errno to EAGAIN. + * + * Returns 1 and fills in both @pmap structs if the requested service + * ports are unambiguous and pingable. Otherwise zero is returned; + * rpccreateerr.cf_stat is set to reflect the nature of the error. + */ +int nfs_probe_bothports(const struct sockaddr *mnt_saddr, + const socklen_t mnt_salen, + struct pmap *mnt_pmap, + const struct sockaddr *nfs_saddr, + const socklen_t nfs_salen, + struct pmap *nfs_pmap, + int checkv4) +{ + struct pmap save_nfs, save_mnt; + const unsigned long *probe_vers; + + if (mnt_pmap->pm_vers && !nfs_pmap->pm_vers) + nfs_pmap->pm_vers = mntvers_to_nfs(mnt_pmap->pm_vers); + else if (nfs_pmap->pm_vers && !mnt_pmap->pm_vers) + mnt_pmap->pm_vers = nfsvers_to_mnt(nfs_pmap->pm_vers); + + if (nfs_pmap->pm_vers) + return nfs_probe_version_fixed(mnt_saddr, mnt_salen, mnt_pmap, + nfs_saddr, nfs_salen, nfs_pmap); + + memcpy(&save_nfs, nfs_pmap, sizeof(save_nfs)); + memcpy(&save_mnt, mnt_pmap, sizeof(save_mnt)); + probe_vers = (nfs_mount_data_version >= 4) ? + probe_mnt3_only : probe_mnt1_first; + + for (; *probe_vers; probe_vers++) { + nfs_pmap->pm_vers = mntvers_to_nfs(*probe_vers); + if (nfs_probe_nfsport(nfs_saddr, nfs_salen, nfs_pmap, checkv4) != 0) { + mnt_pmap->pm_vers = *probe_vers; + if (nfs_probe_mntport(mnt_saddr, mnt_salen, mnt_pmap) != 0) + return 1; + memcpy(mnt_pmap, &save_mnt, sizeof(*mnt_pmap)); + } + switch (rpc_createerr.cf_stat) { + case RPC_PROGVERSMISMATCH: + case RPC_PROGNOTREGISTERED: + break; + default: + return 0; + } + memcpy(nfs_pmap, &save_nfs, sizeof(*nfs_pmap)); + } + + return 0; +} + +/** + * probe_bothports - discover the RPC endpoints of mountd and NFS server + * @mnt_server: pointer to address and pmap argument for mountd results + * @nfs_server: pointer to address and pmap argument for NFS server + * + * This is the legacy API that takes "clnt_addr_t" for both servers, + * but supports only AF_INET addresses. + * + * Returns 1 and fills in the pmap field in both clnt_addr_t structs + * if the requested service ports are unambiguous and pingable. + * Otherwise zero is returned; rpccreateerr.cf_stat is set to reflect + * the nature of the error. + */ +int probe_bothports(clnt_addr_t *mnt_server, clnt_addr_t *nfs_server) +{ + struct sockaddr *mnt_addr = SAFE_SOCKADDR(&mnt_server->saddr); + struct sockaddr *nfs_addr = SAFE_SOCKADDR(&nfs_server->saddr); + + return nfs_probe_bothports(mnt_addr, sizeof(mnt_server->saddr), + &mnt_server->pmap, + nfs_addr, sizeof(nfs_server->saddr), + &nfs_server->pmap, 0); +} + +/** + * start_statd - attempt to start rpc.statd + * + * Returns 1 if statd is running; otherwise zero. + */ +int start_statd(void) +{ +#ifdef START_STATD + struct stat stb; +#endif + + if (nfs_probe_statd()) + return 1; + +#ifdef START_STATD + if (stat(START_STATD, &stb) == 0) { + if (S_ISREG(stb.st_mode) && (stb.st_mode & S_IXUSR)) { + int cnt = STATD_TIMEOUT * 10; + int status = 0; + char * const envp[1] = { NULL }; + const struct timespec ts = { + .tv_sec = 0, + .tv_nsec = 100000000, + }; + pid_t pid = fork(); + switch (pid) { + case 0: /* child */ + setgroups(0, NULL); + if (setgid(0) < 0) + nfs_error(_("%s: setgid(0) failed: %s"), + progname, strerror(errno)); + if (setuid(0) < 0) + nfs_error(_("%s: setuid(0) failed: %s"), + progname, strerror(errno)); + execle(START_STATD, START_STATD, NULL, envp); + exit(1); + case -1: /* error */ + nfs_error(_("%s: fork failed: %s"), + progname, strerror(errno)); + break; + default: /* parent */ + if (waitpid(pid, &status,0) == pid && + status == 0) + /* assume it worked */ + return 1; + break; + } + while (1) { + if (nfs_probe_statd()) + return 1; + if (! cnt--) + return 0; + nanosleep(&ts, NULL); + } + } + } +#endif + + return 0; +} + +/** + * nfs_advise_umount - ask the server to remove a share from it's rmtab + * @sap: pointer to IP address of server to call + * @salen: length of server address + * @pmap: partially filled-in mountd RPC service tuple + * @argp: directory path of share to "unmount" + * + * Returns one if the unmount call succeeded; zero if the unmount + * failed for any reason; rpccreateerr.cf_stat is set to reflect + * the nature of the error. + * + * We use a fast timeout since this call is advisory only. + */ +int nfs_advise_umount(const struct sockaddr *sap, const socklen_t salen, + const struct pmap *pmap, const dirpath *argp) +{ + union nfs_sockaddr address; + struct sockaddr *saddr = &address.sa; + struct pmap mnt_pmap = *pmap; + struct timeval timeout = { + .tv_sec = MOUNT_TIMEOUT >> 3, + }; + CLIENT *client; + enum clnt_stat res = 0; + + memcpy(saddr, sap, salen); + if (nfs_probe_mntport(saddr, salen, &mnt_pmap) == 0) { + if (verbose) + nfs_error(_("%s: Failed to discover mountd port%s"), + progname, clnt_spcreateerror("")); + return 0; + } + nfs_set_port(saddr, mnt_pmap.pm_port); + + client = nfs_get_priv_rpcclient(saddr, salen, mnt_pmap.pm_prot, + mnt_pmap.pm_prog, mnt_pmap.pm_vers, + &timeout); + if (client == NULL) { + if (verbose) + nfs_error(_("%s: Failed to create RPC client%s"), + progname, clnt_spcreateerror("")); + return 0; + } + + client->cl_auth = nfs_authsys_create(); + if (client->cl_auth == NULL) { + if (verbose) + nfs_error(_("%s: Failed to create RPC auth handle"), + progname); + CLNT_DESTROY(client); + return 0; + } + + res = CLNT_CALL(client, MOUNTPROC_UMNT, + (xdrproc_t)xdr_dirpath, (caddr_t)argp, + (xdrproc_t)xdr_void, NULL, + timeout); + if (res != RPC_SUCCESS) { + rpc_createerr.cf_stat = res; + CLNT_GETERR(client, &rpc_createerr.cf_error); + if (verbose) + nfs_error(_("%s: UMNT call failed: %s"), + progname, clnt_sperrno(res)); + + } + auth_destroy(client->cl_auth); + CLNT_DESTROY(client); + + if (res != RPC_SUCCESS) + return 0; + return 1; +} + +/** + * nfs_call_umount - ask the server to remove a share from it's rmtab + * @mnt_server: address of RPC MNT program server + * @argp: directory path of share to "unmount" + * + * Returns one if the unmount call succeeded; zero if the unmount + * failed for any reason. + * + * Note that a side effect of calling this function is that rpccreateerr + * is set. + */ +int nfs_call_umount(clnt_addr_t *mnt_server, dirpath *argp) +{ + struct sockaddr *sap = SAFE_SOCKADDR(&mnt_server->saddr); + socklen_t salen = sizeof(mnt_server->saddr); + struct pmap *pmap = &mnt_server->pmap; + CLIENT *clnt; + enum clnt_stat res = 0; + int msock; + + if (!nfs_probe_mntport(sap, salen, pmap)) + return 0; + clnt = mnt_openclnt(mnt_server, &msock); + if (!clnt) + return 0; + res = clnt_call(clnt, MOUNTPROC_UMNT, + (xdrproc_t)xdr_dirpath, (caddr_t)argp, + (xdrproc_t)xdr_void, NULL, + TIMEOUT); + mnt_closeclnt(clnt, msock); + + if (res == RPC_SUCCESS) + return 1; + return 0; +} + +/** + * mnt_openclnt - get a handle for a remote mountd service + * @mnt_server: address and pmap arguments of mountd service + * @msock: returns a file descriptor of the underlying transport socket + * + * Returns an active handle for the remote's mountd service + */ +CLIENT *mnt_openclnt(clnt_addr_t *mnt_server, int *msock) +{ + struct sockaddr_in *mnt_saddr = &mnt_server->saddr; + struct pmap *mnt_pmap = &mnt_server->pmap; + CLIENT *clnt = NULL; + + mnt_saddr->sin_port = htons((u_short)mnt_pmap->pm_port); + *msock = get_socket(mnt_saddr, mnt_pmap->pm_prot, MOUNT_TIMEOUT, + TRUE, FALSE); + if (*msock == RPC_ANYSOCK) { + if (rpc_createerr.cf_error.re_errno == EADDRINUSE) + /* + * Probably in-use by a TIME_WAIT connection, + * It is worth waiting a while and trying again. + */ + rpc_createerr.cf_stat = RPC_TIMEDOUT; + return NULL; + } + + switch (mnt_pmap->pm_prot) { + case IPPROTO_UDP: + clnt = clntudp_bufcreate(mnt_saddr, + mnt_pmap->pm_prog, mnt_pmap->pm_vers, + RETRY_TIMEOUT, msock, + MNT_SENDBUFSIZE, MNT_RECVBUFSIZE); + break; + case IPPROTO_TCP: + clnt = clnttcp_create(mnt_saddr, + mnt_pmap->pm_prog, mnt_pmap->pm_vers, + msock, + MNT_SENDBUFSIZE, MNT_RECVBUFSIZE); + break; + } + if (clnt) { + /* try to mount hostname:dirname */ + clnt->cl_auth = nfs_authsys_create(); + if (clnt->cl_auth) + return clnt; + CLNT_DESTROY(clnt); + } + return NULL; +} + +/** + * mnt_closeclnt - terminate a handle for a remote mountd service + * @clnt: pointer to an active handle for a remote mountd service + * @msock: file descriptor of the underlying transport socket + * + */ +void mnt_closeclnt(CLIENT *clnt, int msock) +{ + auth_destroy(clnt->cl_auth); + clnt_destroy(clnt); + close(msock); +} + +/** + * clnt_ping - send an RPC ping to the remote RPC service endpoint + * @saddr: server's address + * @prog: target RPC program number + * @vers: target RPC version number + * @prot: target RPC protocol + * @caddr: filled in with our network address + * + * Sigh... GETPORT queries don't actually check the version number. + * In order to make sure that the server actually supports the service + * we're requesting, we open an RPC client, and fire off a NULL + * RPC call. + * + * caddr is the network address that the server will use to call us back. + * On multi-homed clients, this address depends on which NIC we use to + * route requests to the server. + * + * Returns one if successful, otherwise zero. + */ +int clnt_ping(struct sockaddr_in *saddr, const unsigned long prog, + const unsigned long vers, const unsigned int prot, + struct sockaddr_in *caddr) +{ + CLIENT *clnt = NULL; + int sock, status; + static char clnt_res; + struct sockaddr dissolve; + + rpc_createerr.cf_stat = status = 0; + sock = get_socket(saddr, prot, CONNECT_TIMEOUT, FALSE, TRUE); + if (sock == RPC_ANYSOCK) { + if (rpc_createerr.cf_error.re_errno == ETIMEDOUT) { + /* + * TCP timeout. Bubble up the error to see + * how it should be handled. + */ + rpc_createerr.cf_stat = RPC_TIMEDOUT; + } + return 0; + } + + if (caddr) { + /* Get the address of our end of this connection */ + socklen_t len = sizeof(*caddr); + if (getsockname(sock, (struct sockaddr *) caddr, &len) != 0) + caddr->sin_family = 0; + } + + switch(prot) { + case IPPROTO_UDP: + /* The socket is connected (so we could getsockname successfully), + * but some servers on multi-homed hosts reply from + * the wrong address, so if we stay connected, we lose the reply. + */ + dissolve.sa_family = AF_UNSPEC; + connect(sock, &dissolve, sizeof(dissolve)); + + clnt = clntudp_bufcreate(saddr, prog, vers, + RETRY_TIMEOUT, &sock, + RPCSMALLMSGSIZE, RPCSMALLMSGSIZE); + break; + case IPPROTO_TCP: + clnt = clnttcp_create(saddr, prog, vers, &sock, + RPCSMALLMSGSIZE, RPCSMALLMSGSIZE); + break; + } + if (!clnt) { + close(sock); + return 0; + } + memset(&clnt_res, 0, sizeof(clnt_res)); + status = clnt_call(clnt, NULLPROC, + (xdrproc_t)xdr_void, (caddr_t)NULL, + (xdrproc_t)xdr_void, (caddr_t)&clnt_res, + TIMEOUT); + if (status) { + clnt_geterr(clnt, &rpc_createerr.cf_error); + rpc_createerr.cf_stat = status; + } + clnt_destroy(clnt); + close(sock); + + if (status == RPC_SUCCESS) + return 1; + else + return 0; +} + +/* + * Try a getsockname() on a connected datagram socket. + * + * Returns 1 and fills in @buf if successful; otherwise, zero. + * + * A connected datagram socket prevents leaving a socket in TIME_WAIT. + * This conserves the ephemeral port number space, helping reduce failed + * socket binds during mount storms. + */ +static int nfs_ca_sockname(const struct sockaddr *sap, const socklen_t salen, + struct sockaddr *buf, socklen_t *buflen) +{ + struct sockaddr_in sin = { + .sin_family = AF_INET, + .sin_addr.s_addr = htonl(INADDR_ANY), + }; + struct sockaddr_in6 sin6 = { + .sin6_family = AF_INET6, + .sin6_addr = IN6ADDR_ANY_INIT, + }; + int sock, result = 0; + int val; + + sock = socket(sap->sa_family, SOCK_DGRAM, IPPROTO_UDP); + if (sock < 0) + return 0; + + switch (sap->sa_family) { + case AF_INET: + if (bind(sock, SAFE_SOCKADDR(&sin), sizeof(sin)) < 0) + goto out; + break; + case AF_INET6: + /* Make sure the call-back address is public/permanent */ + val = IPV6_PREFER_SRC_PUBLIC; + setsockopt(sock, SOL_IPV6, IPV6_ADDR_PREFERENCES, &val, sizeof(val)); + if (bind(sock, SAFE_SOCKADDR(&sin6), sizeof(sin6)) < 0) + goto out; + break; + default: + errno = EAFNOSUPPORT; + goto out; + } + + if (connect(sock, sap, salen) < 0) + goto out; + + result = !getsockname(sock, buf, buflen); + +out: + close(sock); + return result; +} + +/* + * Try to generate an address that prevents the server from calling us. + * + * Returns 1 and fills in @buf if successful; otherwise, zero. + */ +static int nfs_ca_gai(const struct sockaddr *sap, + struct sockaddr *buf, socklen_t *buflen) +{ + struct addrinfo *gai_results; + struct addrinfo gai_hint = { + .ai_family = sap->sa_family, + .ai_flags = AI_PASSIVE, /* ANYADDR */ + }; + + if (getaddrinfo(NULL, "", &gai_hint, &gai_results)) + return 0; + + *buflen = gai_results->ai_addrlen; + memcpy(buf, gai_results->ai_addr, *buflen); + + nfs_freeaddrinfo(gai_results); + + return 1; +} + +/** + * nfs_callback_address - acquire our local network address + * @sap: pointer to address of remote + * @sap_len: length of address + * @buf: pointer to buffer to be filled in with local network address + * @buflen: IN: length of buffer to fill in; OUT: length of filled-in address + * + * Discover a network address that an NFSv4 server can use to call us back. + * On multi-homed clients, this address depends on which NIC we use to + * route requests to the server. + * + * Returns 1 and fills in @buf if an unambiguous local address is + * available; returns 1 and fills in an appropriate ANYADDR address + * if a local address isn't available; otherwise, returns zero. + */ +int nfs_callback_address(const struct sockaddr *sap, const socklen_t salen, + struct sockaddr *buf, socklen_t *buflen) +{ + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)buf; + + if (nfs_ca_sockname(sap, salen, buf, buflen) == 0) + if (nfs_ca_gai(sap, buf, buflen) == 0) + goto out_failed; + + /* + * The server can't use an interface ID that was generated + * here on the client, so always clear sin6_scope_id. + */ + if (sin6->sin6_family == AF_INET6) + sin6->sin6_scope_id = 0; + + return 1; + +out_failed: + *buflen = 0; + if (verbose) + nfs_error(_("%s: failed to construct callback address"), + progname); + return 0; +} + +/* + * "nfsprog" is supported only by the legacy mount command. The + * kernel mount client does not support this option. + * + * Returns TRUE if @program contains a valid value for this option, + * or FALSE if the option was specified with an invalid value. + */ +static int +nfs_nfs_program(struct mount_options *options, unsigned long *program) +{ + long tmp; + + switch (po_get_numeric(options, "nfsprog", &tmp)) { + case PO_NOT_FOUND: + break; + case PO_FOUND: + if (tmp > 0) { + *program = tmp; + return 1; + } + /* FALLTHRU */ + case PO_BAD_VALUE: + nfs_error(_("%s: invalid value for 'nfsprog=' option"), + progname); + return 0; + } + + /* + * NFS RPC program wasn't specified. The RPC program + * cannot be determined via an rpcbind query. + */ + *program = nfs_getrpcbyname(NFSPROG, nfs_nfs_pgmtbl); + return 1; +} + +/* + * Returns TRUE if @version contains a valid value for this option, + * or FALSE if the option was specified with an invalid value. + */ +int +nfs_nfs_version(char *type, struct mount_options *options, struct nfs_version *version) +{ + char *version_key, *version_val = NULL, *cptr; + int i, found = -1; + + version->v_mode = V_DEFAULT; + + for (i = 0; nfs_version_opttbl[i]; i++) { + if (po_contains_prefix(options, nfs_version_opttbl[i], + &version_key, 0) == PO_FOUND) { + if (found >= 0) + goto ret_error_multiple; + if (po_contains_prefix(options, nfs_version_opttbl[i], + NULL, 1) == PO_FOUND) + goto ret_error_multiple; + found = i; + } + } + + if (found < 0 && strcmp(type, "nfs4") == 0) + version_val = type + 3; + else if (found < 0) + return 1; + else if (found <= 2 ) { + /* v3, v4 */ + version_val = version_key + 1; + version->v_mode = V_SPECIFIC; + } else if (found > 2 ) { + /* vers=, nfsvers= */ + version_val = po_get(options, version_key); + } + + if (!version_val) + goto ret_error; + + version->major = strtol(version_val, &cptr, 10); + if (cptr == version_val || (*cptr && *cptr != '.')) + goto ret_error; + if (version->major == 4 && *cptr != '.' && + (version_val = po_get(options, "minorversion")) != NULL) { + version->minor = strtol(version_val, &cptr, 10); + found = -1; + if (*cptr) + goto ret_error; + version->v_mode = V_SPECIFIC; + } else if (version->major < 4) + version->v_mode = V_SPECIFIC; + else if (*cptr == '.') { + version_val = ++cptr; + if (!(version->minor = strtol(version_val, &cptr, 10)) && cptr == version_val) + goto ret_error; + version->v_mode = V_SPECIFIC; + } else if (version->major > 3 && *cptr == '\0') { + version_val = po_get(options, "minorversion"); + if (version_val != NULL) { + version->minor = strtol(version_val, &cptr, 10); + version->v_mode = V_SPECIFIC; + } else + version->v_mode = V_GENERAL; + } + if (*cptr != '\0') + goto ret_error; + + return 1; + +ret_error_multiple: + nfs_error(_("%s: multiple version options not permitted"), + progname); + found = 10; /* avoid other errors */ +ret_error: + if (found < 0) { + nfs_error(_("%s: parsing error on 'minorversion=' option"), + progname); + } else if (found <= 2 ) { + nfs_error(_("%s: parsing error on 'v' option"), + progname); + } else if (found == 3 ) { + nfs_error(_("%s: parsing error on 'vers=' option"), + progname); + } else if (found == 4) { + nfs_error(_("%s: parsing error on 'nfsvers=' option"), + progname); + } + version->v_mode = V_PARSE_ERR; + errno = EINVAL; + return 0; +} + +/* + * Returns TRUE if @protocol contains a valid value for this option, + * or FALSE if the option was specified with an invalid value. On + * error, errno is set. + */ +int +nfs_nfs_protocol(struct mount_options *options, unsigned long *protocol) +{ + sa_family_t family; + char *option; + + switch (po_rightmost(options, nfs_transport_opttbl)) { + case 0: /* udp */ + *protocol = IPPROTO_UDP; + return 1; + case 1: /* tcp */ + *protocol = IPPROTO_TCP; + return 1; + case 2: /* rdma */ + *protocol = NFSPROTO_RDMA; + return 1; + case 3: /* proto */ + option = po_get(options, "proto"); + if (option != NULL) { + if (!nfs_get_proto(option, &family, protocol)) { + errno = EPROTONOSUPPORT; + nfs_error(_("%s: Failed to find '%s' protocol"), + progname, option); + return 0; + } + return 1; + } + } + + /* + * NFS transport protocol wasn't specified. The pmap + * protocol value will be filled in later by an rpcbind + * query in this case. + */ + *protocol = 0; + return 1; +} + +/* + * Returns TRUE if @port contains a valid value for this option, + * or FALSE if the option was specified with an invalid value. + */ +static int +nfs_nfs_port(struct mount_options *options, unsigned long *port) +{ + long tmp; + + switch (po_get_numeric(options, "port", &tmp)) { + case PO_NOT_FOUND: + break; + case PO_FOUND: + if (tmp >= 0 && tmp <= 65535) { + *port = tmp; + return 1; + } + /* FALLTHRU */ + case PO_BAD_VALUE: + nfs_error(_("%s: invalid value for 'port=' option"), + progname); + return 0; + } + + /* + * NFS service port wasn't specified. The pmap port value + * will be filled in later by an rpcbind query in this case. + */ + *port = 0; + return 1; +} + +#ifdef IPV6_SUPPORTED +sa_family_t config_default_family = AF_UNSPEC; + +static int +nfs_verify_family(sa_family_t UNUSED(family)) +{ + return 1; +} +#else /* IPV6_SUPPORTED */ +sa_family_t config_default_family = AF_INET; + +static int +nfs_verify_family(sa_family_t family) +{ + if (family != AF_INET) + return 0; + + return 1; +} +#endif /* IPV6_SUPPORTED */ + +/* + * Returns TRUE and fills in @family if a valid NFS protocol option + * is found, or FALSE if the option was specified with an invalid value + * or if the protocol family isn't supported. On error, errno is set. + */ +int nfs_nfs_proto_family(struct mount_options *options, + sa_family_t *family) +{ + unsigned long protocol; + char *option; + sa_family_t tmp_family = config_default_family; + + switch (po_rightmost(options, nfs_transport_opttbl)) { + case 0: /* udp */ + case 1: /* tcp */ + case 2: /* rdma */ + /* for compatibility; these are always AF_INET */ + *family = AF_INET; + return 1; + case 3: /* proto */ + option = po_get(options, "proto"); + if (option != NULL && + !nfs_get_proto(option, &tmp_family, &protocol)) { + + nfs_error(_("%s: Failed to find '%s' protocol"), + progname, option); + errno = EPROTONOSUPPORT; + return 0; + } + } + + if (!nfs_verify_family(tmp_family)) + goto out_err; + *family = tmp_family; + return 1; +out_err: + errno = EAFNOSUPPORT; + return 0; +} + +/* + * "mountprog" is supported only by the legacy mount command. The + * kernel mount client does not support this option. + * + * Returns TRUE if @program contains a valid value for this option, + * or FALSE if the option was specified with an invalid value. + */ +static int +nfs_mount_program(struct mount_options *options, unsigned long *program) +{ + long tmp; + + switch (po_get_numeric(options, "mountprog", &tmp)) { + case PO_NOT_FOUND: + break; + case PO_FOUND: + if (tmp > 0) { + *program = tmp; + return 1; + } + /* FALLTHRU */ + case PO_BAD_VALUE: + nfs_error(_("%s: invalid value for 'mountprog=' option"), + progname); + return 0; + } + + /* + * MNT RPC program wasn't specified. The RPC program + * cannot be determined via an rpcbind query. + */ + *program = nfs_getrpcbyname(MOUNTPROG, nfs_mnt_pgmtbl); + return 1; +} + +/* + * Returns TRUE if @version contains a valid value for this option, + * or FALSE if the option was specified with an invalid value. + */ +static int +nfs_mount_version(struct mount_options *options, unsigned long *version) +{ + long tmp; + + switch (po_get_numeric(options, "mountvers", &tmp)) { + case PO_NOT_FOUND: + break; + case PO_FOUND: + if (tmp >= 1 && tmp <= 4) { + *version = tmp; + return 1; + } + /* FALLTHRU */ + case PO_BAD_VALUE: + nfs_error(_("%s: invalid value for 'mountvers=' option"), + progname); + return 0; + } + + /* + * MNT version wasn't specified. The pmap version value + * will be filled in later by an rpcbind query in this case. + */ + *version = 0; + return 1; +} + +/* + * Returns TRUE if @protocol contains a valid value for this option, + * or FALSE if the option was specified with an invalid value. On + * error, errno is set. + */ +static int +nfs_mount_protocol(struct mount_options *options, unsigned long *protocol) +{ + sa_family_t family; + char *option; + + option = po_get(options, "mountproto"); + if (option != NULL) { + if (!nfs_get_proto(option, &family, protocol)) { + errno = EPROTONOSUPPORT; + nfs_error(_("%s: Failed to find '%s' protocol"), + progname, option); + return 0; + } + return 1; + } + + /* + * MNT transport protocol wasn't specified. If the NFS + * transport protocol was specified, use that; otherwise + * set @protocol to zero. The pmap protocol value will + * be filled in later by an rpcbind query in this case. + */ + if (!nfs_nfs_protocol(options, protocol)) + return 0; + if (*protocol == NFSPROTO_RDMA) + *protocol = IPPROTO_TCP; + return 1; +} + +/* + * Returns TRUE if @port contains a valid value for this option, + * or FALSE if the option was specified with an invalid value. + */ +static int +nfs_mount_port(struct mount_options *options, unsigned long *port) +{ + long tmp; + + switch (po_get_numeric(options, "mountport", &tmp)) { + case PO_NOT_FOUND: + break; + case PO_FOUND: + if (tmp >= 0 && tmp <= 65535) { + *port = tmp; + return 1; + } + /* FALLTHRU */ + case PO_BAD_VALUE: + nfs_error(_("%s: invalid value for 'mountport=' option"), + progname); + return 0; + } + + /* + * MNT service port wasn't specified. The pmap port value + * will be filled in later by an rpcbind query in this case. + */ + *port = 0; + return 1; +} + +/* + * Returns TRUE and fills in @family if a valid MNT protocol option + * is found, or FALSE if the option was specified with an invalid value + * or if the protocol family isn't supported. On error, errno is set. + */ +int nfs_mount_proto_family(struct mount_options *options, + sa_family_t *family) +{ + unsigned long protocol; + char *option; + sa_family_t tmp_family = config_default_family; + + option = po_get(options, "mountproto"); + if (option != NULL) { + if (!nfs_get_proto(option, &tmp_family, &protocol)) { + nfs_error(_("%s: Failed to find '%s' protocol"), + progname, option); + errno = EPROTONOSUPPORT; + goto out_err; + } + if (!nfs_verify_family(tmp_family)) + goto out_err; + *family = tmp_family; + return 1; + } + + /* + * MNT transport protocol wasn't specified. If the NFS + * transport protocol was specified, derive the family + * from that; otherwise, return the default family for + * NFS. + */ + return nfs_nfs_proto_family(options, family); +out_err: + errno = EAFNOSUPPORT; + return 0; +} + +/** + * nfs_options2pmap - set up pmap structs based on mount options + * @options: pointer to mount options + * @nfs_pmap: OUT: pointer to pmap arguments for NFS server + * @mnt_pmap: OUT: pointer to pmap arguments for mountd server + * + * Returns TRUE if the pmap options specified in @options have valid + * values; otherwise FALSE is returned. + */ +int nfs_options2pmap(struct mount_options *options, + struct pmap *nfs_pmap, struct pmap *mnt_pmap) +{ + struct nfs_version version; + memset(&version, 0, sizeof(version)); + + if (!nfs_nfs_program(options, &nfs_pmap->pm_prog)) + return 0; + if (!nfs_nfs_version("nfs", options, &version)) + return 0; + if (version.v_mode == V_DEFAULT) + nfs_pmap->pm_vers = 0; + else + nfs_pmap->pm_vers = version.major; + if (!nfs_nfs_protocol(options, &nfs_pmap->pm_prot)) + return 0; + if (!nfs_nfs_port(options, &nfs_pmap->pm_port)) + return 0; + + if (!nfs_mount_program(options, &mnt_pmap->pm_prog)) + return 0; + if (!nfs_mount_version(options, &mnt_pmap->pm_vers)) + return 0; + if (!nfs_mount_protocol(options, &mnt_pmap->pm_prot)) + return 0; + if (!nfs_mount_port(options, &mnt_pmap->pm_port)) + return 0; + + return 1; +} + +/* + * Discover mount server's hostname/address by examining mount options + * + * Returns a pointer to a string that the caller must free, on + * success; otherwise NULL is returned. + */ +static char *nfs_umount_hostname(struct mount_options *options, + char *hostname) +{ + char *option; + + option = po_get(options, "mountaddr"); + if (option) + goto out; + option = po_get(options, "mounthost"); + if (option) + goto out; + option = po_get(options, "addr"); + if (option) + goto out; + + return hostname; + +out: + free(hostname); + return strdup(option); +} + + +/* + * Returns EX_SUCCESS if mount options and device name have been + * parsed successfully; otherwise EX_FAIL. + */ +int nfs_umount_do_umnt(struct mount_options *options, + char **hostname, char **dirname) +{ + union nfs_sockaddr address; + struct sockaddr *sap = &address.sa; + socklen_t salen = sizeof(address); + struct pmap nfs_pmap, mnt_pmap; + sa_family_t family; + + if (!nfs_options2pmap(options, &nfs_pmap, &mnt_pmap)) + return EX_FAIL; + + /* Skip UMNT call for vers=4 mounts */ + if (nfs_pmap.pm_vers == 4) + return EX_SUCCESS; + + *hostname = nfs_umount_hostname(options, *hostname); + if (!*hostname) { + nfs_error(_("%s: out of memory"), progname); + return EX_FAIL; + } + + if (!nfs_mount_proto_family(options, &family)) + return 0; + if (!nfs_lookup(*hostname, family, sap, &salen)) + /* nfs_lookup reports any errors */ + return EX_FAIL; + + if (nfs_advise_umount(sap, salen, &mnt_pmap, dirname) == 0) + /* nfs_advise_umount reports any errors */ + return EX_FAIL; + + return EX_SUCCESS; +} + +int nfs_is_inaddr_any(struct sockaddr *nfs_saddr) +{ + switch (nfs_saddr->sa_family) { + case AF_INET: { + if (((struct sockaddr_in *)nfs_saddr)->sin_addr.s_addr == + INADDR_ANY) + return 1; + break; + } + case AF_INET6: + if (!memcmp(&((struct sockaddr_in6 *)nfs_saddr)->sin6_addr, + &in6addr_any, sizeof(in6addr_any))) + return 1; + break; + } + return 0; +} + +int nfs_addr_matches_localips(struct sockaddr *nfs_saddr) +{ + struct ifaddrs *myaddrs, *ifa; + int found = 0; + + /* acquire exiting network interfaces */ + if (getifaddrs(&myaddrs) != 0) + return 0; + + /* interate over the available interfaces and check if we + * we find a match to the supplied clientaddr value + */ + for (ifa = myaddrs; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) + continue; + if (!(ifa->ifa_flags & IFF_UP)) + continue; + if (!memcmp(ifa->ifa_addr, nfs_saddr, + sizeof(struct sockaddr))) { + found = 1; + break; + } + } + freeifaddrs(myaddrs); + return found; +} diff --git a/utils/mount/network.h b/utils/mount/network.h new file mode 100644 index 0000000..0fc98ac --- /dev/null +++ b/utils/mount/network.h @@ -0,0 +1,96 @@ +/* + * network.h -- Provide common network functions for NFS mount/umount + * + * Copyright (C) 2007 Oracle. All rights reserved. + * Copyright (C) 2007 Chuck Lever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA + * + */ + +#ifndef _NFS_UTILS_MOUNT_NETWORK_H +#define _NFS_UTILS_MOUNT_NETWORK_H + +#include + +#define MNT_SENDBUFSIZE (2048U) +#define MNT_RECVBUFSIZE (1024U) + +typedef struct { + char **hostname; + struct sockaddr_in saddr; + struct pmap pmap; +} clnt_addr_t; + +/* RPC call timeout values */ +static const struct timeval TIMEOUT = { 20, 0 }; +static const struct timeval RETRY_TIMEOUT = { 3, 0 }; + +int probe_bothports(clnt_addr_t *, clnt_addr_t *); +int nfs_probe_bothports(const struct sockaddr *, const socklen_t, + struct pmap *, const struct sockaddr *, + const socklen_t, struct pmap *, int); +int nfs_gethostbyname(const char *, struct sockaddr_in *); +int nfs_lookup(const char *hostname, const sa_family_t family, + struct sockaddr *sap, socklen_t *salen); +int nfs_string_to_sockaddr(const char *, struct sockaddr *, socklen_t *); +int nfs_present_sockaddr(const struct sockaddr *, + const socklen_t, char *, const size_t); +int nfs_callback_address(const struct sockaddr *, const socklen_t, + struct sockaddr *, socklen_t *); +int clnt_ping(struct sockaddr_in *, const unsigned long, + const unsigned long, const unsigned int, + struct sockaddr_in *); +int nfs_is_inaddr_any(struct sockaddr *); +int nfs_addr_matches_localips(struct sockaddr *); + +struct mount_options; + +enum { + V_DEFAULT = 0, + V_GENERAL, + V_SPECIFIC, + V_PARSE_ERR, +}; + +struct nfs_version { + unsigned long major; + unsigned long minor; + int v_mode; +}; + +int nfs_nfs_proto_family(struct mount_options *options, sa_family_t *family); +int nfs_mount_proto_family(struct mount_options *options, sa_family_t *family); +int nfs_nfs_version(char *type, struct mount_options *options, struct nfs_version *version); +int nfs_nfs_protocol(struct mount_options *options, unsigned long *protocol); + +int nfs_options2pmap(struct mount_options *, + struct pmap *, struct pmap *); + +int start_statd(void); + +unsigned long nfsvers_to_mnt(const unsigned long); + +int nfs_call_umount(clnt_addr_t *, dirpath *); +int nfs_advise_umount(const struct sockaddr *, const socklen_t, + const struct pmap *, const dirpath *); +CLIENT *mnt_openclnt(clnt_addr_t *, int *); +void mnt_closeclnt(CLIENT *, int); + +int nfs_umount_do_umnt(struct mount_options *options, + char **hostname, char **dirname); + +#endif /* _NFS_UTILS_MOUNT_NETWORK_H */ diff --git a/utils/mount/nfs.man b/utils/mount/nfs.man new file mode 100644 index 0000000..c0ba4d0 --- /dev/null +++ b/utils/mount/nfs.man @@ -0,0 +1,1968 @@ +.\"@(#)nfs.5" +.TH NFS 5 "9 October 2012" +.SH NAME +nfs \- fstab format and options for the +.B nfs +file systems +.SH SYNOPSIS +.I /etc/fstab +.SH DESCRIPTION +NFS is an Internet Standard protocol +created by Sun Microsystems in 1984. NFS was developed +to allow file sharing between systems residing +on a local area network. +Depending on kernel configuration, the Linux NFS client may +support NFS versions 3, 4.0, 4.1, or 4.2. +.P +The +.BR mount (8) +command attaches a file system to the system's +name space hierarchy at a given mount point. +The +.I /etc/fstab +file describes how +.BR mount (8) +should assemble a system's file name hierarchy +from various independent file systems +(including file systems exported by NFS servers). +Each line in the +.I /etc/fstab +file describes a single file system, its mount point, +and a set of default mount options for that mount point. +.P +For NFS file system mounts, a line in the +.I /etc/fstab +file specifies the server name, +the path name of the exported server directory to mount, +the local directory that is the mount point, +the type of file system that is being mounted, +and a list of mount options that control +the way the filesystem is mounted and +how the NFS client behaves when accessing +files on this mount point. +The fifth and sixth fields on each line are not used +by NFS, thus conventionally each contain the digit zero. For example: +.P +.nf +.ta 8n +14n +14n +9n +20n + server:path /mountpoint fstype option,option,... 0 0 +.fi +.P +The server's hostname and export pathname +are separated by a colon, while +the mount options are separated by commas. The remaining fields +are separated by blanks or tabs. +.P +The server's hostname can be an unqualified hostname, +a fully qualified domain name, +a dotted quad IPv4 address, or +an IPv6 address enclosed in square brackets. +Link-local and site-local IPv6 addresses must be accompanied by an +interface identifier. +See +.BR ipv6 (7) +for details on specifying raw IPv6 addresses. +.P +The +.I fstype +field contains "nfs". Use of the "nfs4" fstype in +.I /etc/fstab +is deprecated. +.SH "MOUNT OPTIONS" +Refer to +.BR mount (8) +for a description of generic mount options +available for all file systems. If you do not need to +specify any mount options, use the generic option +.B defaults +in +.IR /etc/fstab . +.DT +.SS "Options supported by all versions" +These options are valid to use with any NFS version. +.TP 1.5i +.BI nfsvers= n +The NFS protocol version number used to contact the server's NFS service. +If the server does not support the requested version, the mount request +fails. +If this option is not specified, the client tries version 4.2 first, +then negotiates down until it finds a version supported by the server. +.TP 1.5i +.BI vers= n +This option is an alternative to the +.B nfsvers +option. +It is included for compatibility with other operating systems +.TP 1.5i +.BR soft " / " softerr " / " hard +Determines the recovery behavior of the NFS client +after an NFS request times out. +If no option is specified (or if the +.B hard +option is specified), NFS requests are retried indefinitely. +If either the +.BR soft " or " softerr +option is specified, then the NFS client fails an NFS request +after +.B retrans +retransmissions have been sent, +causing the NFS client to return either the error +.B EIO +(for the +.B soft +option) or +.B ETIMEDOUT +(for the +.B softerr +option) to the calling application. +.IP +.I NB: +A so-called "soft" timeout can cause +silent data corruption in certain cases. As such, use the +.BR soft " or " softerr +option only when client responsiveness +is more important than data integrity. +Using NFS over TCP or increasing the value of the +.B retrans +option may mitigate some of the risks of using the +.BR soft " or " softerr +option. +.TP 1.5i +.BR softreval " / " nosoftreval +In cases where the NFS server is down, it may be useful to +allow the NFS client to continue to serve up paths and +attributes from cache after +.B retrans +attempts to revalidate that cache have timed out. +This may, for instance, be helpful when trying to unmount a +filesystem tree from a server that is permanently down. +.IP +It is possible to combine +.BR softreval +with the +.B soft +mount option, in which case operations that cannot be served up +from cache will time out and return an error after +.B retrans +attempts. The combination with the default +.B hard +mount option implies those uncached operations will continue to +retry until a response is received from the server. +.IP +Note: the default mount option is +.BR nosoftreval +which disallows fallback to cache when revalidation fails, and +instead follows the behavior dictated by the +.B hard +or +.B soft +mount option. +.TP 1.5i +.BR intr " / " nointr +This option is provided for backward compatibility. +It is ignored after kernel 2.6.25. +.TP 1.5i +.BI timeo= n +The time in deciseconds (tenths of a second) the NFS client waits for a +response before it retries an NFS request. +.IP +For NFS over TCP the default +.B timeo +value is 600 (60 seconds). +The NFS client performs linear backoff: After each retransmission the +timeout is increased by +.BR timeo +up to the maximum of 600 seconds. +.IP +However, for NFS over UDP, the client uses an adaptive +algorithm to estimate an appropriate timeout value for frequently used +request types (such as READ and WRITE requests), but uses the +.B timeo +setting for infrequently used request types (such as FSINFO requests). +If the +.B timeo +option is not specified, +infrequently used request types are retried after 1.1 seconds. +After each retransmission, the NFS client doubles the timeout for +that request, +up to a maximum timeout length of 60 seconds. +.TP 1.5i +.BI retrans= n +The number of times the NFS client retries a request before +it attempts further recovery action. If the +.B retrans +option is not specified, the NFS client tries each UDP request +three times and each TCP request twice. +.IP +The NFS client generates a "server not responding" message +after +.B retrans +retries, then attempts further recovery (depending on whether the +.B hard +mount option is in effect). +.TP 1.5i +.BI rsize= n +The maximum number of bytes in each network READ request +that the NFS client can receive when reading data from a file +on an NFS server. +The actual data payload size of each NFS READ request is equal to +or smaller than the +.B rsize +setting. The largest read payload supported by the Linux NFS client +is 1,048,576 bytes (one megabyte). +.IP +The +.B rsize +value is a positive integral multiple of 1024. +Specified +.B rsize +values lower than 1024 are replaced with 4096; values larger than +1048576 are replaced with 1048576. If a specified value is within the supported +range but not a multiple of 1024, it is rounded down to the nearest +multiple of 1024. +.IP +If an +.B rsize +value is not specified, or if the specified +.B rsize +value is larger than the maximum that either client or server can support, +the client and server negotiate the largest +.B rsize +value that they can both support. +.IP +The +.B rsize +mount option as specified on the +.BR mount (8) +command line appears in the +.I /etc/mtab +file. However, the effective +.B rsize +value negotiated by the client and server is reported in the +.I /proc/mounts +file. +.TP 1.5i +.BI wsize= n +The maximum number of bytes per network WRITE request +that the NFS client can send when writing data to a file +on an NFS server. The actual data payload size of each +NFS WRITE request is equal to +or smaller than the +.B wsize +setting. The largest write payload supported by the Linux NFS client +is 1,048,576 bytes (one megabyte). +.IP +Similar to +.B rsize +, the +.B wsize +value is a positive integral multiple of 1024. +Specified +.B wsize +values lower than 1024 are replaced with 4096; values larger than +1048576 are replaced with 1048576. If a specified value is within the supported +range but not a multiple of 1024, it is rounded down to the nearest +multiple of 1024. +.IP +If a +.B wsize +value is not specified, or if the specified +.B wsize +value is larger than the maximum that either client or server can support, +the client and server negotiate the largest +.B wsize +value that they can both support. +.IP +The +.B wsize +mount option as specified on the +.BR mount (8) +command line appears in the +.I /etc/mtab +file. However, the effective +.B wsize +value negotiated by the client and server is reported in the +.I /proc/mounts +file. +.TP 1.5i +.BR ac " / " noac +Selects whether the client may cache file attributes. If neither +option is specified (or if +.B ac +is specified), the client caches file +attributes. +.IP +To improve performance, NFS clients cache file +attributes. Every few seconds, an NFS client checks the server's version of each +file's attributes for updates. Changes that occur on the server in +those small intervals remain undetected until the client checks the +server again. The +.B noac +option prevents clients from caching file +attributes so that applications can more quickly detect file changes +on the server. +.IP +In addition to preventing the client from caching file attributes, +the +.B noac +option forces application writes to become synchronous so +that local changes to a file become visible on the server +immediately. That way, other clients can quickly detect recent +writes when they check the file's attributes. +.IP +Using the +.B noac +option provides greater cache coherence among NFS clients +accessing the same files, +but it extracts a significant performance penalty. +As such, judicious use of file locking is encouraged instead. +The DATA AND METADATA COHERENCE section contains a detailed discussion +of these trade-offs. +.TP 1.5i +.BI acregmin= n +The minimum time (in seconds) that the NFS client caches +attributes of a regular file before it requests +fresh attribute information from a server. +If this option is not specified, the NFS client uses +a 3-second minimum. +See the DATA AND METADATA COHERENCE section +for a full discussion of attribute caching. +.TP 1.5i +.BI acregmax= n +The maximum time (in seconds) that the NFS client caches +attributes of a regular file before it requests +fresh attribute information from a server. +If this option is not specified, the NFS client uses +a 60-second maximum. +See the DATA AND METADATA COHERENCE section +for a full discussion of attribute caching. +.TP 1.5i +.BI acdirmin= n +The minimum time (in seconds) that the NFS client caches +attributes of a directory before it requests +fresh attribute information from a server. +If this option is not specified, the NFS client uses +a 30-second minimum. +See the DATA AND METADATA COHERENCE section +for a full discussion of attribute caching. +.TP 1.5i +.BI acdirmax= n +The maximum time (in seconds) that the NFS client caches +attributes of a directory before it requests +fresh attribute information from a server. +If this option is not specified, the NFS client uses +a 60-second maximum. +See the DATA AND METADATA COHERENCE section +for a full discussion of attribute caching. +.TP 1.5i +.BI actimeo= n +Using +.B actimeo +sets all of +.BR acregmin , +.BR acregmax , +.BR acdirmin , +and +.B acdirmax +to the same value. +If this option is not specified, the NFS client uses +the defaults for each of these options listed above. +.TP 1.5i +.BR bg " / " fg +Determines how the +.BR mount (8) +command behaves if an attempt to mount an export fails. +The +.B fg +option causes +.BR mount (8) +to exit with an error status if any part of the mount request +times out or fails outright. +This is called a "foreground" mount, +and is the default behavior if neither the +.B fg +nor +.B bg +mount option is specified. +.IP +If the +.B bg +option is specified, a timeout or failure causes the +.BR mount (8) +command to fork a child which continues to attempt +to mount the export. +The parent immediately returns with a zero exit code. +This is known as a "background" mount. +.IP +If the local mount point directory is missing, the +.BR mount (8) +command acts as if the mount request timed out. +This permits nested NFS mounts specified in +.I /etc/fstab +to proceed in any order during system initialization, +even if some NFS servers are not yet available. +Alternatively these issues can be addressed +using an automounter (refer to +.BR automount (8) +for details). +.TP 1.5i +.BR nconnect= n +When using a connection oriented protocol such as TCP, it may +sometimes be advantageous to set up multiple connections between +the client and server. For instance, if your clients and/or servers +are equipped with multiple network interface cards (NICs), using multiple +connections to spread the load may improve overall performance. +In such cases, the +.BR nconnect +option allows the user to specify the number of connections +that should be established between the client and server up to +a limit of 16. +.IP +Note that the +.BR nconnect +option may also be used by some pNFS drivers to decide how many +connections to set up to the data servers. +.TP 1.5i +.BR rdirplus " / " nordirplus +Selects whether to use NFS v3 or v4 READDIRPLUS requests. +If this option is not specified, the NFS client uses READDIRPLUS requests +on NFS v3 or v4 mounts to read small directories. +Some applications perform better if the client uses only READDIR requests +for all directories. +.TP 1.5i +.BI retry= n +The number of minutes that the +.BR mount (8) +command retries an NFS mount operation +in the foreground or background before giving up. +If this option is not specified, the default value for foreground mounts +is 2 minutes, and the default value for background mounts is 10000 minutes +(80 minutes shy of one week). +If a value of zero is specified, the +.BR mount (8) +command exits immediately after the first failure. +.IP +Note that this only affects how many retries are made and doesn't +affect the delay caused by each retry. For UDP each retry takes the +time determined by the +.BR timeo +and +.BR retrans +options, which by default will be about 7 seconds. For TCP the +default is 3 minutes, but system TCP connection timeouts will +sometimes limit the timeout of each retransmission to around 2 minutes. +.TP 1.5i +.BI sec= flavors +A colon-separated list of one or more security flavors to use for accessing +files on the mounted export. If the server does not support any of these +flavors, the mount operation fails. +If +.B sec= +is not specified, the client attempts to find +a security flavor that both the client and the server supports. +Valid +.I flavors +are +.BR none , +.BR sys , +.BR krb5 , +.BR krb5i , +and +.BR krb5p . +Refer to the SECURITY CONSIDERATIONS section for details. +.TP 1.5i +.BR sharecache " / " nosharecache +Determines how the client's data cache and attribute cache are shared +when mounting the same export more than once concurrently. Using the +same cache reduces memory requirements on the client and presents +identical file contents to applications when the same remote file is +accessed via different mount points. +.IP +If neither option is specified, or if the +.B sharecache +option is +specified, then a single cache is used for all mount points that +access the same export. If the +.B nosharecache +option is specified, +then that mount point gets a unique cache. Note that when data and +attribute caches are shared, the mount options from the first mount +point take effect for subsequent concurrent mounts of the same export. +.IP +As of kernel 2.6.18, the behavior specified by +.B nosharecache +is legacy caching behavior. This +is considered a data risk since multiple cached copies +of the same file on the same client can become out of sync +following a local update of one of the copies. +.TP 1.5i +.BR resvport " / " noresvport +Specifies whether the NFS client should use a privileged source port +when communicating with an NFS server for this mount point. +If this option is not specified, or the +.B resvport +option is specified, the NFS client uses a privileged source port. +If the +.B noresvport +option is specified, the NFS client uses a non-privileged source port. +This option is supported in kernels 2.6.28 and later. +.IP +Using non-privileged source ports helps increase the maximum number of +NFS mount points allowed on a client, but NFS servers must be configured +to allow clients to connect via non-privileged source ports. +.IP +Refer to the SECURITY CONSIDERATIONS section for important details. +.TP 1.5i +.BI lookupcache= mode +Specifies how the kernel manages its cache of directory entries +for a given mount point. +.I mode +can be one of +.BR all , +.BR none , +.BR pos , +or +.BR positive . +This option is supported in kernels 2.6.28 and later. +.IP +The Linux NFS client caches the result of all NFS LOOKUP requests. +If the requested directory entry exists on the server, +the result is referred to as +.IR positive . +If the requested directory entry does not exist on the server, +the result is referred to as +.IR negative . +.IP +If this option is not specified, or if +.B all +is specified, the client assumes both types of directory cache entries +are valid until their parent directory's cached attributes expire. +.IP +If +.BR pos " or " positive +is specified, the client assumes positive entries are valid +until their parent directory's cached attributes expire, but +always revalidates negative entires before an application +can use them. +.IP +If +.B none +is specified, +the client revalidates both types of directory cache entries +before an application can use them. +This permits quick detection of files that were created or removed +by other clients, but can impact application and server performance. +.IP +The DATA AND METADATA COHERENCE section contains a +detailed discussion of these trade-offs. +.TP 1.5i +.BR fsc " / " nofsc +Enable/Disables the cache of (read-only) data pages to the local disk +using the FS-Cache facility. See cachefilesd(8) +and /Documentation/filesystems/caching +for detail on how to configure the FS-Cache facility. +Default value is nofsc. +.TP 1.5i +.B sloppy +The +.B sloppy +option is an alternative to specifying +.BR mount.nfs " -s " option. +.TP 1.5i +.BI xprtsec= policy +Specifies the use of transport layer security to protect NFS network +traffic on behalf of this mount point. +.I policy +can be one of +.BR none , +.BR tls , +or +.BR mtls . +.IP +If +.B none +is specified, +transport layer security is forced off, even if the NFS server supports +transport layer security. +.IP +If +.B tls +is specified, the client uses RPC-with-TLS to provide in-transit +confidentiality. +.IP +If +.B mtls +is specified, the client uses RPC-with-TLS to authenticate itself and +to provide in-transit confidentiality. +.IP +If either +.B tls +or +.B mtls +is specified and the server does not support RPC-with-TLS or peer +authentication fails, the mount attempt fails. +.IP +If the +.B xprtsec= +option is not specified, +the default behavior depends on the kernel version, +but is usually equivalent to +.BR "xprtsec=none" . +.SS "Options for NFS versions 2 and 3 only" +Use these options, along with the options in the above subsection, +for NFS versions 2 and 3 only. +.TP 1.5i +.BI proto= netid +The +.I netid +determines the transport that is used to communicate with the NFS +server. Available options are +.BR udp ", " udp6 ", "tcp ", " tcp6 ", " rdma ", and " rdma6 . +Those which end in +.B 6 +use IPv6 addresses and are only available if support for TI-RPC is +built in. Others use IPv4 addresses. +.IP +Each transport protocol uses different default +.B retrans +and +.B timeo +settings. +Refer to the description of these two mount options for details. +.IP +In addition to controlling how the NFS client transmits requests to +the server, this mount option also controls how the +.BR mount (8) +command communicates with the server's rpcbind and mountd services. +Specifying a netid that uses TCP forces all traffic from the +.BR mount (8) +command and the NFS client to use TCP. +Specifying a netid that uses UDP forces all traffic types to use UDP. +.IP +.B Before using NFS over UDP, refer to the TRANSPORT METHODS section. +.IP +If the +.B proto +mount option is not specified, the +.BR mount (8) +command discovers which protocols the server supports +and chooses an appropriate transport for each service. +Refer to the TRANSPORT METHODS section for more details. +.TP 1.5i +.B udp +The +.B udp +option is an alternative to specifying +.BR proto=udp. +It is included for compatibility with other operating systems. +.IP +.B Before using NFS over UDP, refer to the TRANSPORT METHODS section. +.TP 1.5i +.B tcp +The +.B tcp +option is an alternative to specifying +.BR proto=tcp. +It is included for compatibility with other operating systems. +.TP 1.5i +.B rdma +The +.B rdma +option is an alternative to specifying +.BR proto=rdma. +.TP 1.5i +.BI port= n +The numeric value of the server's NFS service port. +If the server's NFS service is not available on the specified port, +the mount request fails. +.IP +If this option is not specified, or if the specified port value is 0, +then the NFS client uses the NFS service port number +advertised by the server's rpcbind service. +The mount request fails if the server's rpcbind service is not available, +the server's NFS service is not registered with its rpcbind service, +or the server's NFS service is not available on the advertised port. +.TP 1.5i +.BI mountport= n +The numeric value of the server's mountd port. +If the server's mountd service is not available on the specified port, +the mount request fails. +.IP +If this option is not specified, +or if the specified port value is 0, then the +.BR mount (8) +command uses the mountd service port number +advertised by the server's rpcbind service. +The mount request fails if the server's rpcbind service is not available, +the server's mountd service is not registered with its rpcbind service, +or the server's mountd service is not available on the advertised port. +.IP +This option can be used when mounting an NFS server +through a firewall that blocks the rpcbind protocol. +.TP 1.5i +.BI mountproto= netid +The transport the NFS client uses +to transmit requests to the NFS server's mountd service when performing +this mount request, and when later unmounting this mount point. +.IP +.I netid +may be one of +.BR udp ", and " tcp +which use IPv4 address or, if TI-RPC is built into the +.B mount.nfs +command, +.BR udp6 ", and " tcp6 +which use IPv6 addresses. +.IP +This option can be used when mounting an NFS server +through a firewall that blocks a particular transport. +When used in combination with the +.B proto +option, different transports for mountd requests and NFS requests +can be specified. +If the server's mountd service is not available via the specified +transport, the mount request fails. +.IP +Refer to the TRANSPORT METHODS section for more on how the +.B mountproto +mount option interacts with the +.B proto +mount option. +.TP 1.5i +.BI mounthost= name +The hostname of the host running mountd. +If this option is not specified, the +.BR mount (8) +command assumes that the mountd service runs +on the same host as the NFS service. +.TP 1.5i +.BI mountvers= n +The RPC version number used to contact the server's mountd. +If this option is not specified, the client uses a version number +appropriate to the requested NFS version. +This option is useful when multiple NFS services +are running on the same remote server host. +.TP 1.5i +.BI namlen= n +The maximum length of a pathname component on this mount. +If this option is not specified, the maximum length is negotiated +with the server. In most cases, this maximum length is 255 characters. +.IP +Some early versions of NFS did not support this negotiation. +Using this option ensures that +.BR pathconf (3) +reports the proper maximum component length to applications +in such cases. +.TP 1.5i +.BR lock " / " nolock +Selects whether to use the NLM sideband protocol to lock files on the server. +If neither option is specified (or if +.B lock +is specified), NLM locking is used for this mount point. +When using the +.B nolock +option, applications can lock files, +but such locks provide exclusion only against other applications +running on the same client. +Remote applications are not affected by these locks. +.IP +NLM locking must be disabled with the +.B nolock +option when using NFS to mount +.I /var +because +.I /var +contains files used by the NLM implementation on Linux. +Using the +.B nolock +option is also required when mounting exports on NFS servers +that do not support the NLM protocol. +.TP 1.5i +.BR cto " / " nocto +Selects whether to use close-to-open cache coherence semantics. +If neither option is specified (or if +.B cto +is specified), the client uses close-to-open +cache coherence semantics. If the +.B nocto +option is specified, the client uses a non-standard heuristic to determine when +files on the server have changed. +.IP +Using the +.B nocto +option may improve performance for read-only mounts, +but should be used only if the data on the server changes only occasionally. +The DATA AND METADATA COHERENCE section discusses the behavior +of this option in more detail. +.TP 1.5i +.BR acl " / " noacl +Selects whether to use the NFSACL sideband protocol on this mount point. +The NFSACL sideband protocol is a proprietary protocol +implemented in Solaris that manages Access Control Lists. NFSACL was never +made a standard part of the NFS protocol specification. +.IP +If neither +.B acl +nor +.B noacl +option is specified, +the NFS client negotiates with the server +to see if the NFSACL protocol is supported, +and uses it if the server supports it. +Disabling the NFSACL sideband protocol may be necessary +if the negotiation causes problems on the client or server. +Refer to the SECURITY CONSIDERATIONS section for more details. +.TP 1.5i +.BR local_lock= mechanism +Specifies whether to use local locking for any or both of the flock and the +POSIX locking mechanisms. +.I mechanism +can be one of +.BR all , +.BR flock , +.BR posix , +or +.BR none . +This option is supported in kernels 2.6.37 and later. +.IP +The Linux NFS client provides a way to make locks local. This means, the +applications can lock files, but such locks provide exclusion only against +other applications running on the same client. Remote applications are not +affected by these locks. +.IP +If this option is not specified, or if +.B none +is specified, the client assumes that the locks are not local. +.IP +If +.BR all +is specified, the client assumes that both flock and POSIX locks are local. +.IP +If +.BR flock +is specified, the client assumes that only flock locks are local and uses +NLM sideband protocol to lock files when POSIX locks are used. +.IP +If +.BR posix +is specified, the client assumes that POSIX locks are local and uses NLM +sideband protocol to lock files when flock locks are used. +.IP +To support legacy flock behavior similar to that of NFS clients < 2.6.12, +use 'local_lock=flock'. This option is required when exporting NFS mounts via +Samba as Samba maps Windows share mode locks as flock. Since NFS clients > +2.6.12 implement flock by emulating POSIX locks, this will result in +conflicting locks. +.IP +NOTE: When used together, the 'local_lock' mount option will be overridden +by 'nolock'/'lock' mount option. +.SS "Options for NFS version 4 only" +Use these options, along with the options in the first subsection above, +for NFS version 4.0 and newer. +.TP 1.5i +.BI proto= netid +The +.I netid +determines the transport that is used to communicate with the NFS +server. Supported options are +.BR tcp ", " tcp6 ", " rdma ", and " rdma6 . +.B tcp6 +use IPv6 addresses and is only available if support for TI-RPC is +built in. Both others use IPv4 addresses. +.IP +All NFS version 4 servers are required to support TCP, +so if this mount option is not specified, the NFS version 4 client +uses the TCP protocol. +Refer to the TRANSPORT METHODS section for more details. +.TP 1.5i +.BI minorversion= n +Specifies the protocol minor version number. +NFSv4 introduces "minor versioning," where NFS protocol enhancements can +be introduced without bumping the NFS protocol version number. +Before kernel 2.6.38, the minor version is always zero, and this +option is not recognized. +After this kernel, specifying "minorversion=1" enables a number of +advanced features, such as NFSv4 sessions. +.IP +Recent kernels allow the minor version to be specified using the +.B vers= +option. +For example, specifying +.B vers=4.1 +is the same as specifying +.BR vers=4,minorversion=1 . +.TP 1.5i +.BI port= n +The numeric value of the server's NFS service port. +If the server's NFS service is not available on the specified port, +the mount request fails. +.IP +If this mount option is not specified, +the NFS client uses the standard NFS port number of 2049 +without first checking the server's rpcbind service. +This allows an NFS version 4 client to contact an NFS version 4 +server through a firewall that may block rpcbind requests. +.IP +If the specified port value is 0, +then the NFS client uses the NFS service port number +advertised by the server's rpcbind service. +The mount request fails if the server's rpcbind service is not available, +the server's NFS service is not registered with its rpcbind service, +or the server's NFS service is not available on the advertised port. +.TP 1.5i +.BR cto " / " nocto +Selects whether to use close-to-open cache coherence semantics +for NFS directories on this mount point. +If neither +.B cto +nor +.B nocto +is specified, +the default is to use close-to-open cache coherence +semantics for directories. +.IP +File data caching behavior is not affected by this option. +The DATA AND METADATA COHERENCE section discusses +the behavior of this option in more detail. +.TP 1.5i +.BI clientaddr= n.n.n.n +.TP 1.5i +.BI clientaddr= n:n: ... :n +Specifies a single IPv4 address (in dotted-quad form), +or a non-link-local IPv6 address, +that the NFS client advertises to allow servers +to perform NFS version 4.0 callback requests against +files on this mount point. If the server is unable to +establish callback connections to clients, performance +may degrade, or accesses to files may temporarily hang. +Can specify a value of IPv4_ANY (0.0.0.0) or equivalent +IPv6 any address which will signal to the NFS server that +this NFS client does not want delegations. +.IP +If this option is not specified, the +.BR mount (8) +command attempts to discover an appropriate callback address automatically. +The automatic discovery process is not perfect, however. +In the presence of multiple client network interfaces, +special routing policies, +or atypical network topologies, +the exact address to use for callbacks may be nontrivial to determine. +.IP +NFS protocol versions 4.1 and 4.2 use the client-established +TCP connection for callback requests, so do not require the server to +connect to the client. This option is therefore only affect NFS version +4.0 mounts. +.TP 1.5i +.BR migration " / " nomigration +Selects whether the client uses an identification string that is compatible +with NFSv4 Transparent State Migration (TSM). +If the mounted server supports NFSv4 migration with TSM, specify the +.B migration +option. +.IP +Some server features misbehave in the face of a migration-compatible +identification string. +The +.B nomigration +option retains the use of a traditional client indentification string +which is compatible with legacy NFS servers. +This is also the behavior if neither option is specified. +A client's open and lock state cannot be migrated transparently +when it identifies itself via a traditional identification string. +.IP +This mount option has no effect with NFSv4 minor versions newer than zero, +which always use TSM-compatible client identification strings. +.TP 1.5i +.BR max_connect= n +While +.BR nconnect +option sets a limit on the number of connections that can be established +to a given server IP, +.BR max_connect +option allows the user to specify maximum number of connections to different +server IPs that belong to the same NFSv4.1+ server (session trunkable +connections) up to a limit of 16. When client discovers that it established +a client ID to an already existing server, instead of dropping the newly +created network transport, the client will add this new connection to the +list of available transports for that RPC client. +.TP 1.5i +.BR trunkdiscovery " / " notrunkdiscovery +When the client discovers a new filesystem on a NFSv4.1+ server, the +.BR trunkdiscovery +mount option will cause it to send a GETATTR for the fs_locations attribute. +If is receives a non-zero length reply, it will iterate through the response, +and for each server location it will establish a connection, send an +EXCHANGE_ID, and test for session trunking. If the trunking test succeeds, +the connection will be added to the existing set of transports for the server, +subject to the limit specified by the +.BR max_connect +option. The default is +.BR notrunkdiscovery . +.SH nfs4 FILE SYSTEM TYPE +The +.BR nfs4 +file system type is an old syntax for specifying NFSv4 usage. It can still +be used with all NFSv4-specific and common options, excepted the +.B nfsvers +mount option. +.SH MOUNT CONFIGURATION FILE +If the mount command is configured to do so, all of the mount options +described in the previous section can also be configured in the +.I /etc/nfsmount.conf +file. See +.BR nfsmount.conf(5) +for details. +.SH EXAMPLES +To mount using NFS version 3, +use the +.B nfs +file system type and specify the +.B nfsvers=3 +mount option. +To mount using NFS version 4, +use either the +.B nfs +file system type, with the +.B nfsvers=4 +mount option, or the +.B nfs4 +file system type. +.P +The following example from an +.I /etc/fstab +file causes the mount command to negotiate +reasonable defaults for NFS behavior. +.P +.nf +.ta 8n +16n +6n +6n +30n + server:/export /mnt nfs defaults 0 0 +.fi +.P +This example shows how to mount using NFS version 4 over TCP +with Kerberos 5 mutual authentication. +.P +.nf +.ta 8n +16n +6n +6n +30n + server:/export /mnt nfs4 sec=krb5 0 0 +.fi +.P +This example shows how to mount using NFS version 4 over TCP +with Kerberos 5 privacy or data integrity mode. +.P +.nf +.ta 8n +16n +6n +6n +30n + server:/export /mnt nfs4 sec=krb5p:krb5i 0 0 +.fi +.P +This example can be used to mount /usr over NFS. +.P +.nf +.ta 8n +16n +6n +6n +30n + server:/export /usr nfs ro,nolock,nocto,actimeo=3600 0 0 +.fi +.P +This example shows how to mount an NFS server +using a raw IPv6 link-local address. +.P +.nf +.ta 8n +40n +5n +4n +9n + [fe80::215:c5ff:fb3e:e2b1%eth0]:/export /mnt nfs defaults 0 0 +.fi +.SH "TRANSPORT METHODS" +NFS clients send requests to NFS servers via +Remote Procedure Calls, or +.IR RPCs . +The RPC client discovers remote service endpoints automatically, +handles per-request authentication, +adjusts request parameters for different byte endianness on client and server, +and retransmits requests that may have been lost by the network or server. +RPC requests and replies flow over a network transport. +.P +In most cases, the +.BR mount (8) +command, NFS client, and NFS server +can automatically negotiate proper transport +and data transfer size settings for a mount point. +In some cases, however, it pays to specify +these settings explicitly using mount options. +.P +Traditionally, NFS clients used the UDP transport exclusively for +transmitting requests to servers. Though its implementation is +simple, NFS over UDP has many limitations that prevent smooth +operation and good performance in some common deployment +environments. Even an insignificant packet loss rate results in the +loss of whole NFS requests; as such, retransmit timeouts are usually +in the subsecond range to allow clients to recover quickly from +dropped requests, but this can result in extraneous network traffic +and server load. +.P +However, UDP can be quite effective in specialized settings where +the networks MTU is large relative to NFSs data transfer size (such +as network environments that enable jumbo Ethernet frames). In such +environments, trimming the +.B rsize +and +.B wsize +settings so that each +NFS read or write request fits in just a few network frames (or even +in a single frame) is advised. This reduces the probability that +the loss of a single MTU-sized network frame results in the loss of +an entire large read or write request. +.P +TCP is the default transport protocol used for all modern NFS +implementations. It performs well in almost every conceivable +network environment and provides excellent guarantees against data +corruption caused by network unreliability. TCP is often a +requirement for mounting a server through a network firewall. +.P +Under normal circumstances, networks drop packets much more +frequently than NFS servers drop requests. As such, an aggressive +retransmit timeout setting for NFS over TCP is unnecessary. Typical +timeout settings for NFS over TCP are between one and ten minutes. +After the client exhausts its retransmits (the value of the +.B retrans +mount option), it assumes a network partition has occurred, +and attempts to reconnect to the server on a fresh socket. Since +TCP itself makes network data transfer reliable, +.B rsize +and +.B wsize +can safely be allowed to default to the largest values supported by +both client and server, independent of the network's MTU size. +.SS "Using the mountproto mount option" +This section applies only to NFS version 3 mounts +since NFS version 4 does not use a separate protocol for mount +requests. +.P +The Linux NFS client can use a different transport for +contacting an NFS server's rpcbind service, its mountd service, +its Network Lock Manager (NLM) service, and its NFS service. +The exact transports employed by the Linux NFS client for +each mount point depends on the settings of the transport +mount options, which include +.BR proto , +.BR mountproto , +.BR udp ", and " tcp . +.P +The client sends Network Status Manager (NSM) notifications +via UDP no matter what transport options are specified, but +listens for server NSM notifications on both UDP and TCP. +The NFS Access Control List (NFSACL) protocol shares the same +transport as the main NFS service. +.P +If no transport options are specified, the Linux NFS client +uses UDP to contact the server's mountd service, and TCP to +contact its NLM and NFS services by default. +.P +If the server does not support these transports for these services, the +.BR mount (8) +command attempts to discover what the server supports, and then retries +the mount request once using the discovered transports. +If the server does not advertise any transport supported by the client +or is misconfigured, the mount request fails. +If the +.B bg +option is in effect, the mount command backgrounds itself and continues +to attempt the specified mount request. +.P +When the +.B proto +option, the +.B udp +option, or the +.B tcp +option is specified but the +.B mountproto +option is not, the specified transport is used to contact +both the server's mountd service and for the NLM and NFS services. +.P +If the +.B mountproto +option is specified but none of the +.BR proto ", " udp " or " tcp +options are specified, then the specified transport is used for the +initial mountd request, but the mount command attempts to discover +what the server supports for the NFS protocol, preferring TCP if +both transports are supported. +.P +If both the +.BR mountproto " and " proto +(or +.BR udp " or " tcp ) +options are specified, then the transport specified by the +.B mountproto +option is used for the initial mountd request, and the transport +specified by the +.B proto +option (or the +.BR udp " or " tcp " options)" +is used for NFS, no matter what order these options appear. +No automatic service discovery is performed if these options are +specified. +.P +If any of the +.BR proto ", " udp ", " tcp ", " +or +.B mountproto +options are specified more than once on the same mount command line, +then the value of the rightmost instance of each of these options +takes effect. +.SS "Using NFS over UDP on high-speed links" +Using NFS over UDP on high-speed links such as Gigabit +.BR "can cause silent data corruption" . +.P +The problem can be triggered at high loads, and is caused by problems in +IP fragment reassembly. NFS read and writes typically transmit UDP packets +of 4 Kilobytes or more, which have to be broken up into several fragments +in order to be sent over the Ethernet link, which limits packets to 1500 +bytes by default. This process happens at the IP network layer and is +called fragmentation. +.P +In order to identify fragments that belong together, IP assigns a 16bit +.I IP ID +value to each packet; fragments generated from the same UDP packet +will have the same IP ID. The receiving system will collect these +fragments and combine them to form the original UDP packet. This process +is called reassembly. The default timeout for packet reassembly is +30 seconds; if the network stack does not receive all fragments of +a given packet within this interval, it assumes the missing fragment(s) +got lost and discards those it already received. +.P +The problem this creates over high-speed links is that it is possible +to send more than 65536 packets within 30 seconds. In fact, with +heavy NFS traffic one can observe that the IP IDs repeat after about +5 seconds. +.P +This has serious effects on reassembly: if one fragment gets lost, +another fragment +.I from a different packet +but with the +.I same IP ID +will arrive within the 30 second timeout, and the network stack will +combine these fragments to form a new packet. Most of the time, network +layers above IP will detect this mismatched reassembly - in the case +of UDP, the UDP checksum, which is a 16 bit checksum over the entire +packet payload, will usually not match, and UDP will discard the +bad packet. +.P +However, the UDP checksum is 16 bit only, so there is a chance of 1 in +65536 that it will match even if the packet payload is completely +random (which very often isn't the case). If that is the case, +silent data corruption will occur. +.P +This potential should be taken seriously, at least on Gigabit +Ethernet. +Network speeds of 100Mbit/s should be considered less +problematic, because with most traffic patterns IP ID wrap around +will take much longer than 30 seconds. +.P +It is therefore strongly recommended to use +.BR "NFS over TCP where possible" , +since TCP does not perform fragmentation. +.P +If you absolutely have to use NFS over UDP over Gigabit Ethernet, +some steps can be taken to mitigate the problem and reduce the +probability of corruption: +.TP +1.5i +.I Jumbo frames: +Many Gigabit network cards are capable of transmitting +frames bigger than the 1500 byte limit of traditional Ethernet, typically +9000 bytes. Using jumbo frames of 9000 bytes will allow you to run NFS over +UDP at a page size of 8K without fragmentation. Of course, this is +only feasible if all involved stations support jumbo frames. +.IP +To enable a machine to send jumbo frames on cards that support it, +it is sufficient to configure the interface for a MTU value of 9000. +.TP +1.5i +.I Lower reassembly timeout: +By lowering this timeout below the time it takes the IP ID counter +to wrap around, incorrect reassembly of fragments can be prevented +as well. To do so, simply write the new timeout value (in seconds) +to the file +.BR /proc/sys/net/ipv4/ipfrag_time . +.IP +A value of 2 seconds will greatly reduce the probability of IPID clashes on +a single Gigabit link, while still allowing for a reasonable timeout +when receiving fragmented traffic from distant peers. +.SH "DATA AND METADATA COHERENCE" +Some modern cluster file systems provide +perfect cache coherence among their clients. +Perfect cache coherence among disparate NFS clients +is expensive to achieve, especially on wide area networks. +As such, NFS settles for weaker cache coherence that +satisfies the requirements of most file sharing types. +.SS "Close-to-open cache consistency" +Typically file sharing is completely sequential. +First client A opens a file, writes something to it, then closes it. +Then client B opens the same file, and reads the changes. +.P +When an application opens a file stored on an NFS version 3 server, +the NFS client checks that the file exists on the server +and is permitted to the opener by sending a GETATTR or ACCESS request. +The NFS client sends these requests +regardless of the freshness of the file's cached attributes. +.P +When the application closes the file, +the NFS client writes back any pending changes +to the file so that the next opener can view the changes. +This also gives the NFS client an opportunity to report +write errors to the application via the return code from +.BR close (2). +.P +The behavior of checking at open time and flushing at close time +is referred to as +.IR "close-to-open cache consistency" , +or +.IR CTO . +It can be disabled for an entire mount point using the +.B nocto +mount option. +.SS "Weak cache consistency" +There are still opportunities for a client's data cache +to contain stale data. +The NFS version 3 protocol introduced "weak cache consistency" +(also known as WCC) which provides a way of efficiently checking +a file's attributes before and after a single request. +This allows a client to help identify changes +that could have been made by other clients. +.P +When a client is using many concurrent operations +that update the same file at the same time +(for example, during asynchronous write behind), +it is still difficult to tell whether it was +that client's updates or some other client's updates +that altered the file. +.SS "Attribute caching" +Use the +.B noac +mount option to achieve attribute cache coherence +among multiple clients. +Almost every file system operation checks +file attribute information. +The client keeps this information cached +for a period of time to reduce network and server load. +When +.B noac +is in effect, a client's file attribute cache is disabled, +so each operation that needs to check a file's attributes +is forced to go back to the server. +This permits a client to see changes to a file very quickly, +at the cost of many extra network operations. +.P +Be careful not to confuse the +.B noac +option with "no data caching." +The +.B noac +mount option prevents the client from caching file metadata, +but there are still races that may result in data cache incoherence +between client and server. +.P +The NFS protocol is not designed to support +true cluster file system cache coherence +without some type of application serialization. +If absolute cache coherence among clients is required, +applications should use file locking. Alternatively, applications +can also open their files with the O_DIRECT flag +to disable data caching entirely. +.SS "File timestamp maintenance" +NFS servers are responsible for managing file and directory timestamps +.RB ( atime , +.BR ctime ", and" +.BR mtime ). +When a file is accessed or updated on an NFS server, +the file's timestamps are updated just like they would be on a filesystem +local to an application. +.P +NFS clients cache file attributes, including timestamps. +A file's timestamps are updated on NFS clients when its attributes +are retrieved from the NFS server. +Thus there may be some delay before timestamp updates +on an NFS server appear to applications on NFS clients. +.P +To comply with the POSIX filesystem standard, the Linux NFS client +relies on NFS servers to keep a file's +.B mtime +and +.B ctime +timestamps properly up to date. +It does this by flushing local data changes to the server +before reporting +.B mtime +to applications via system calls such as +.BR stat (2). +.P +The Linux client handles +.B atime +updates more loosely, however. +NFS clients maintain good performance by caching data, +but that means that application reads, which normally update +.BR atime , +are not reflected to the server where a file's +.B atime +is actually maintained. +.P +Because of this caching behavior, +the Linux NFS client does not support generic atime-related mount options. +See +.BR mount (8) +for details on these options. +.P +In particular, the +.BR atime / noatime , +.BR diratime / nodiratime , +.BR relatime / norelatime , +and +.BR strictatime / nostrictatime +mount options have no effect on NFS mounts. +.P +.I /proc/mounts +may report that the +.B relatime +mount option is set on NFS mounts, but in fact the +.B atime +semantics are always as described here, and are not like +.B relatime +semantics. +.SS "Directory entry caching" +The Linux NFS client caches the result of all NFS LOOKUP requests. +If the requested directory entry exists on the server, +the result is referred to as a +.IR positive " lookup result. +If the requested directory entry does not exist on the server +(that is, the server returned ENOENT), +the result is referred to as +.IR negative " lookup result. +.P +To detect when directory entries have been added or removed +on the server, +the Linux NFS client watches a directory's mtime. +If the client detects a change in a directory's mtime, +the client drops all cached LOOKUP results for that directory. +Since the directory's mtime is a cached attribute, it may +take some time before a client notices it has changed. +See the descriptions of the +.BR acdirmin ", " acdirmax ", and " noac +mount options for more information about +how long a directory's mtime is cached. +.P +Caching directory entries improves the performance of applications that +do not share files with applications on other clients. +Using cached information about directories can interfere +with applications that run concurrently on multiple clients and +need to detect the creation or removal of files quickly, however. +The +.B lookupcache +mount option allows some tuning of directory entry caching behavior. +.P +Before kernel release 2.6.28, +the Linux NFS client tracked only positive lookup results. +This permitted applications to detect new directory entries +created by other clients quickly while still providing some of the +performance benefits of caching. +If an application depends on the previous lookup caching behavior +of the Linux NFS client, you can use +.BR lookupcache=positive . +.P +If the client ignores its cache and validates every application +lookup request with the server, +that client can immediately detect when a new directory +entry has been either created or removed by another client. +You can specify this behavior using +.BR lookupcache=none . +The extra NFS requests needed if the client does not +cache directory entries can exact a performance penalty. +Disabling lookup caching +should result in less of a performance penalty than using +.BR noac , +and has no effect on how the NFS client caches the attributes of files. +.P +.SS "The sync mount option" +The NFS client treats the +.B sync +mount option differently than some other file systems +(refer to +.BR mount (8) +for a description of the generic +.B sync +and +.B async +mount options). +If neither +.B sync +nor +.B async +is specified (or if the +.B async +option is specified), +the NFS client delays sending application +writes to the server +until any of these events occur: +.IP +Memory pressure forces reclamation of system memory resources. +.IP +An application flushes file data explicitly with +.BR sync (2), +.BR msync (2), +or +.BR fsync (3). +.IP +An application closes a file with +.BR close (2). +.IP +The file is locked/unlocked via +.BR fcntl (2). +.P +In other words, under normal circumstances, +data written by an application may not immediately appear +on the server that hosts the file. +.P +If the +.B sync +option is specified on a mount point, +any system call that writes data to files on that mount point +causes that data to be flushed to the server +before the system call returns control to user space. +This provides greater data cache coherence among clients, +but at a significant performance cost. +.P +Applications can use the O_SYNC open flag to force application +writes to individual files to go to the server immediately without +the use of the +.B sync +mount option. +.SS "Using file locks with NFS" +The Network Lock Manager protocol is a separate sideband protocol +used to manage file locks in NFS version 3. +To support lock recovery after a client or server reboot, +a second sideband protocol -- +known as the Network Status Manager protocol -- +is also required. +In NFS version 4, +file locking is supported directly in the main NFS protocol, +and the NLM and NSM sideband protocols are not used. +.P +In most cases, NLM and NSM services are started automatically, +and no extra configuration is required. +Configure all NFS clients with fully-qualified domain names +to ensure that NFS servers can find clients to notify them of server reboots. +.P +NLM supports advisory file locks only. +To lock NFS files, use +.BR fcntl (2) +with the F_GETLK and F_SETLK commands. +The NFS client converts file locks obtained via +.BR flock (2) +to advisory locks. +.P +When mounting servers that do not support the NLM protocol, +or when mounting an NFS server through a firewall +that blocks the NLM service port, +specify the +.B nolock +mount option. NLM locking must be disabled with the +.B nolock +option when using NFS to mount +.I /var +because +.I /var +contains files used by the NLM implementation on Linux. +.P +Specifying the +.B nolock +option may also be advised to improve the performance +of a proprietary application which runs on a single client +and uses file locks extensively. +.SS "NFS version 4 caching features" +The data and metadata caching behavior of NFS version 4 +clients is similar to that of earlier versions. +However, NFS version 4 adds two features that improve +cache behavior: +.I change attributes +and +.IR "file delegation" . +.P +The +.I change attribute +is a new part of NFS file and directory metadata +which tracks data changes. +It replaces the use of a file's modification +and change time stamps +as a way for clients to validate the content +of their caches. +Change attributes are independent of the time stamp +resolution on either the server or client, however. +.P +A +.I file delegation +is a contract between an NFS version 4 client +and server that allows the client to treat a file temporarily +as if no other client is accessing it. +The server promises to notify the client (via a callback request) if another client +attempts to access that file. +Once a file has been delegated to a client, the client can +cache that file's data and metadata aggressively without +contacting the server. +.P +File delegations come in two flavors: +.I read +and +.IR write . +A +.I read +delegation means that the server notifies the client +about any other clients that want to write to the file. +A +.I write +delegation means that the client gets notified about +either read or write accessors. +.P +Servers grant file delegations when a file is opened, +and can recall delegations at any time when another +client wants access to the file that conflicts with +any delegations already granted. +Delegations on directories are not supported. +.P +In order to support delegation callback, the server +checks the network return path to the client during +the client's initial contact with the server. +If contact with the client cannot be established, +the server simply does not grant any delegations to +that client. +.SH "SECURITY CONSIDERATIONS" +NFS servers control access to file data, +but they depend on their RPC implementation +to provide authentication of NFS requests. +Traditional NFS access control mimics +the standard mode bit access control provided in local file systems. +Traditional RPC authentication uses a number +to represent each user +(usually the user's own uid), +a number to represent the user's group (the user's gid), +and a set of up to 16 auxiliary group numbers +to represent other groups of which the user may be a member. +.P +Typically, file data and user ID values appear unencrypted +(i.e. "in the clear") on the network. +Moreover, NFS versions 2 and 3 use +separate sideband protocols for mounting, +locking and unlocking files, +and reporting system status of clients and servers. +These auxiliary protocols use no authentication. +.P +In addition to combining these sideband protocols with the main NFS protocol, +NFS version 4 introduces more advanced forms of access control, +authentication, and in-transit data protection. +The NFS version 4 specification mandates support for +strong authentication and security flavors +that provide per-RPC integrity checking and encryption. +Because NFS version 4 combines the +function of the sideband protocols into the main NFS protocol, +the new security features apply to all NFS version 4 operations +including mounting, file locking, and so on. +RPCGSS authentication can also be used with NFS versions 2 and 3, +but it does not protect their sideband protocols. +.P +The +.B sec +mount option specifies the security flavor used for operations +on behalf of users on that NFS mount point. +Specifying +.B sec=krb5 +provides cryptographic proof of a user's identity in each RPC request. +This provides strong verification of the identity of users +accessing data on the server. +Note that additional configuration besides adding this mount option +is required in order to enable Kerberos security. +Refer to the +.BR rpc.gssd (8) +man page for details. +.P +Two additional flavors of Kerberos security are supported: +.B krb5i +and +.BR krb5p . +The +.B krb5i +security flavor provides a cryptographically strong guarantee +that the data in each RPC request has not been tampered with. +The +.B krb5p +security flavor encrypts every RPC request +to prevent data exposure during network transit; however, +expect some performance impact +when using integrity checking or encryption. +Similar support for other forms of cryptographic security +is also available. +.SS "NFS version 4 filesystem crossing" +The NFS version 4 protocol allows +a client to renegotiate the security flavor +when the client crosses into a new filesystem on the server. +The newly negotiated flavor effects only accesses of the new filesystem. +.P +Such negotiation typically occurs when a client crosses +from a server's pseudo-fs +into one of the server's exported physical filesystems, +which often have more restrictive security settings than the pseudo-fs. +.SS "NFS version 4 Leases" +In NFS version 4, a lease is a period during which a server +irrevocably grants a client file locks. +Once the lease expires, the server may revoke those locks. +Clients periodically renew their leases to prevent lock revocation. +.P +After an NFS version 4 server reboots, each client tells the +server about existing file open and lock state under its lease +before operation can continue. +If a client reboots, the server frees all open and lock state +associated with that client's lease. +.P +When establishing a lease, therefore, +a client must identify itself to a server. +Each client presents an arbitrary string +to distinguish itself from other clients. +The client administrator can +supplement the default identity string using the +.I nfs4.nfs4_unique_id +module parameter to avoid collisions +with other client identity strings. +.P +A client also uses a unique security flavor and principal +when it establishes its lease. +If two clients present the same identity string, +a server can use client principals to distinguish between them, +thus securely preventing one client from interfering with the other's lease. +.P +The Linux NFS client establishes one lease on each NFS version 4 server. +Lease management operations, such as lease renewal, are not +done on behalf of a particular file, lock, user, or mount +point, but on behalf of the client that owns that lease. +A client uses a consistent identity string, security flavor, +and principal across client reboots to ensure that the server +can promptly reap expired lease state. +.P +When Kerberos is configured on a Linux NFS client +(i.e., there is a +.I /etc/krb5.keytab +on that client), the client attempts to use a Kerberos +security flavor for its lease management operations. +Kerberos provides secure authentication of each client. +By default, the client uses the +.I host/ +or +.I nfs/ +service principal in its +.I /etc/krb5.keytab +for this purpose, as described in +.BR rpc.gssd (8). +.P +If the client has Kerberos configured, but the server +does not, or if the client does not have a keytab or +the requisite service principals, the client uses +.I AUTH_SYS +and UID 0 for lease management. +.SS "Using non-privileged source ports" +NFS clients usually communicate with NFS servers via network sockets. +Each end of a socket is assigned a port value, which is simply a number +between 1 and 65535 that distinguishes socket endpoints at the same +IP address. +A socket is uniquely defined by a tuple that includes the transport +protocol (TCP or UDP) and the port values and IP addresses of both +endpoints. +.P +The NFS client can choose any source port value for its sockets, +but usually chooses a +.I privileged +port. +A privileged port is a port value less than 1024. +Only a process with root privileges may create a socket +with a privileged source port. +.P +The exact range of privileged source ports that can be chosen is +set by a pair of sysctls to avoid choosing a well-known port, such as +the port used by ssh. +This means the number of source ports available for the NFS client, +and therefore the number of socket connections that can be used +at the same time, +is practically limited to only a few hundred. +.P +As described above, the traditional default NFS authentication scheme, +known as AUTH_SYS, relies on sending local UID and GID numbers to identify +users making NFS requests. +An NFS server assumes that if a connection comes from a privileged port, +the UID and GID numbers in the NFS requests on this connection have been +verified by the client's kernel or some other local authority. +This is an easy system to spoof, but on a trusted physical network between +trusted hosts, it is entirely adequate. +.P +Roughly speaking, one socket is used for each NFS mount point. +If a client could use non-privileged source ports as well, +the number of sockets allowed, +and thus the maximum number of concurrent mount points, +would be much larger. +.P +Using non-privileged source ports may compromise server security somewhat, +since any user on AUTH_SYS mount points can now pretend to be any other +when making NFS requests. +Thus NFS servers do not support this by default. +They explicitly allow it usually via an export option. +.P +To retain good security while allowing as many mount points as possible, +it is best to allow non-privileged client connections only if the server +and client both require strong authentication, such as Kerberos. +.SS "Mounting through a firewall" +A firewall may reside between an NFS client and server, +or the client or server may block some of its own ports via IP +filter rules. +It is still possible to mount an NFS server through a firewall, +though some of the +.BR mount (8) +command's automatic service endpoint discovery mechanisms may not work; this +requires you to provide specific endpoint details via NFS mount options. +.P +NFS servers normally run a portmapper or rpcbind daemon to advertise +their service endpoints to clients. Clients use the rpcbind daemon to determine: +.IP +What network port each RPC-based service is using +.IP +What transport protocols each RPC-based service supports +.P +The rpcbind daemon uses a well-known port number (111) to help clients find a service endpoint. +Although NFS often uses a standard port number (2049), +auxiliary services such as the NLM service can choose +any unused port number at random. +.P +Common firewall configurations block the well-known rpcbind port. +In the absense of an rpcbind service, +the server administrator fixes the port number +of NFS-related services so that the firewall +can allow access to specific NFS service ports. +Client administrators then specify the port number +for the mountd service via the +.BR mount (8) +command's +.B mountport +option. +It may also be necessary to enforce the use of TCP or UDP +if the firewall blocks one of those transports. +.SS "NFS Access Control Lists" +Solaris allows NFS version 3 clients direct access +to POSIX Access Control Lists stored in its local file systems. +This proprietary sideband protocol, known as NFSACL, +provides richer access control than mode bits. +Linux implements this protocol +for compatibility with the Solaris NFS implementation. +The NFSACL protocol never became a standard part +of the NFS version 3 specification, however. +.P +The NFS version 4 specification mandates a new version +of Access Control Lists that are semantically richer than POSIX ACLs. +NFS version 4 ACLs are not fully compatible with POSIX ACLs; as such, +some translation between the two is required +in an environment that mixes POSIX ACLs and NFS version 4. +.SH "THE REMOUNT OPTION" +Generic mount options such as +.BR rw " and " sync +can be modified on NFS mount points using the +.BR remount +option. +See +.BR mount (8) +for more information on generic mount options. +.P +With few exceptions, NFS-specific options +are not able to be modified during a remount. +The underlying transport or NFS version +cannot be changed by a remount, for example. +.P +Performing a remount on an NFS file system mounted with the +.B noac +option may have unintended consequences. +The +.B noac +option is a combination of the generic option +.BR sync , +and the NFS-specific option +.BR actimeo=0 . +.SS "Unmounting after a remount" +For mount points that use NFS versions 2 or 3, the NFS umount subcommand +depends on knowing the original set of mount options used to perform the +MNT operation. +These options are stored on disk by the NFS mount subcommand, +and can be erased by a remount. +.P +To ensure that the saved mount options are not erased during a remount, +specify either the local mount directory, or the server hostname and +export pathname, but not both, during a remount. For example, +.P +.nf +.ta 8n + mount -o remount,ro /mnt +.fi +.P +merges the mount option +.B ro +with the mount options already saved on disk for the NFS server mounted at /mnt. +.SH FILES +.TP 1.5i +.I /etc/fstab +file system table +.TP 1.5i +.I /etc/nfsmount.conf +Configuration file for NFS mounts +.SH NOTES +Before 2.4.7, the Linux NFS client did not support NFS over TCP. +.P +Before 2.4.20, the Linux NFS client used a heuristic +to determine whether cached file data was still valid +rather than using the standard close-to-open cache coherency method +described above. +.P +Starting with 2.4.22, the Linux NFS client employs +a Van Jacobsen-based RTT estimator to determine retransmit +timeout values when using NFS over UDP. +.P +Before 2.6.0, the Linux NFS client did not support NFS version 4. +.P +Before 2.6.8, the Linux NFS client used only synchronous reads and writes +when the +.BR rsize " and " wsize +settings were smaller than the system's page size. +.P +The Linux client's support for protocol versions depend on whether the +kernel was built with options CONFIG_NFS_V2, CONFIG_NFS_V3, +CONFIG_NFS_V4, CONFIG_NFS_V4_1, and CONFIG_NFS_V4_2. +.SH "SEE ALSO" +.BR fstab (5), +.BR mount (8), +.BR umount (8), +.BR mount.nfs (5), +.BR umount.nfs (5), +.BR exports (5), +.BR nfsmount.conf (5), +.BR netconfig (5), +.BR ipv6 (7), +.BR nfsd (8), +.BR sm-notify (8), +.BR rpc.statd (8), +.BR rpc.idmapd (8), +.BR rpc.gssd (8), +.BR rpc.svcgssd (8), +.BR kerberos (1) +.sp +RFC 768 for the UDP specification. +.br +RFC 793 for the TCP specification. +.br +RFC 1813 for the NFS version 3 specification. +.br +RFC 1832 for the XDR specification. +.br +RFC 1833 for the RPC bind specification. +.br +RFC 2203 for the RPCSEC GSS API protocol specification. +.br +RFC 7530 for the NFS version 4.0 specification. +.br +RFC 5661 for the NFS version 4.1 specification. +.br +RFC 7862 for the NFS version 4.2 specification. diff --git a/utils/mount/nfs4_mount.h b/utils/mount/nfs4_mount.h new file mode 100644 index 0000000..b03792e --- /dev/null +++ b/utils/mount/nfs4_mount.h @@ -0,0 +1,73 @@ +#ifndef _LINUX_NFS4_MOUNT_H +#define _LINUX_NFS4_MOUNT_H + +/* + * linux/include/linux/nfs4_mount.h + * + * Copyright (C) 2002 Trond Myklebust + * + * structure passed from user-space to kernel-space during an nfsv4 mount + */ + +/* + * WARNING! Do not delete or change the order of these fields. If + * a new field is required then add it to the end. The version field + * tracks which fields are present. This will ensure some measure of + * mount-to-kernel version compatibility. Some of these aren't used yet + * but here they are anyway. + */ +#define NFS4_MOUNT_VERSION 1 + +struct nfs_string { + unsigned int len; + const char* data; +}; + +struct nfs4_mount_data { + int version; /* 1 */ + int flags; /* 1 */ + int rsize; /* 1 */ + int wsize; /* 1 */ + int timeo; /* 1 */ + int retrans; /* 1 */ + int acregmin; /* 1 */ + int acregmax; /* 1 */ + int acdirmin; /* 1 */ + int acdirmax; /* 1 */ + + /* see the definition of 'struct clientaddr4' in RFC3010 */ + struct nfs_string client_addr; /* 1 */ + + /* Mount path */ + struct nfs_string mnt_path; /* 1 */ + + /* Server details */ + struct nfs_string hostname; /* 1 */ + /* Server IP address */ + unsigned int host_addrlen; /* 1 */ + struct sockaddr* host_addr; /* 1 */ + + /* Transport protocol to use */ + int proto; /* 1 */ + + /* Pseudo-flavours to use for authentication. See RFC2623 */ + int auth_flavourlen; /* 1 */ + int *auth_flavours; /* 1 */ +}; + +/* bits in the flags field */ +/* Note: the fields that correspond to existing NFSv2/v3 mount options + * should mirror the values from include/linux/nfs_mount.h + */ + +#define NFS4_MOUNT_SOFT 0x0001 /* 1 */ +#define NFS4_MOUNT_INTR 0x0002 /* 1 */ +#define NFS4_MOUNT_NOCTO 0x0010 /* 1 */ +#define NFS4_MOUNT_NOAC 0x0020 /* 1 */ +#define NFS4_MOUNT_STRICTLOCK 0x1000 /* 1 */ +#define NFS4_MOUNT_UNSHARED 0x8000 /* 5 */ +#define NFS4_MOUNT_FLAGMASK 0xFFFF + +int nfs4mount(const char *, const char *, int, char **, int, int); + +#endif diff --git a/utils/mount/nfs4mount.c b/utils/mount/nfs4mount.c new file mode 100644 index 0000000..3e4f1e2 --- /dev/null +++ b/utils/mount/nfs4mount.c @@ -0,0 +1,481 @@ +/* + * nfs4mount.c -- Linux NFS mount + * Copyright (C) 2002 Trond Myklebust + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Note: this file based on the original nfsmount.c + * + * 2006-06-06 Amit Gud + * - Moved to nfs-utils/utils/mount from util-linux/mount. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_RPCSVC_NFS_PROT_H +#include +#else +#include +#define nfsstat nfs_stat +#endif + +#include "pseudoflavors.h" +#include "nls.h" +#include "xcommon.h" + +#include "mount.h" +#include "mount_constants.h" +#include "nfs4_mount.h" +#include "nfs_mount.h" +#include "error.h" +#include "network.h" + +#if defined(VAR_LOCK_DIR) +#define DEFAULT_DIR VAR_LOCK_DIR +#else +#define DEFAULT_DIR "/var/lock/subsys" +#endif + +extern char *progname; +extern int verbose; +extern int sloppy; + +char *IDMAPLCK = DEFAULT_DIR "/rpcidmapd"; +#define idmapd_check() do { \ + if (access(IDMAPLCK, F_OK)) { \ + printf(_("Warning: rpc.idmapd appears not to be running.\n" \ + " All uids will be mapped to the nobody uid.\n")); \ + } \ +} while(0); + +char *GSSDLCK = DEFAULT_DIR "/rpcgssd"; +#define gssd_check() do { \ + if (access(GSSDLCK, F_OK)) { \ + printf(_("Warning: rpc.gssd appears not to be running.\n")); \ + } \ +} while(0); + +#ifndef NFS_PORT +#define NFS_PORT 2049 +#endif + +#define MAX_USER_FLAVOUR 16 + +static int parse_sec(char *sec, int *pseudoflavour) +{ + int i, num_flavour = 0; + + for (sec = strtok(sec, ":"); sec; sec = strtok(NULL, ":")) { + if (num_flavour >= MAX_USER_FLAVOUR) { + nfs_error(_("%s: maximum number of security flavors " + "exceeded"), progname); + return 0; + } + for (i = 0; i < flav_map_size; i++) { + if (strcmp(sec, flav_map[i].flavour) == 0) { + pseudoflavour[num_flavour++] = flav_map[i].fnum; + break; + } + } + if (i == flav_map_size) { + nfs_error(_("%s: unknown security type %s\n"), + progname, sec); + return 0; + } + } + if (!num_flavour) + nfs_error(_("%s: no security flavors passed to sec= option"), + progname); + return num_flavour; +} + +static int parse_devname(char *hostdir, char **hostname, char **dirname) +{ + char *s; + + if (!(s = strchr(hostdir, ':'))) { + nfs_error(_("%s: directory to mount not in host:dir format"), + progname); + return -1; + } + *hostname = hostdir; + *dirname = s + 1; + *s = '\0'; + /* Ignore all but first hostname in replicated mounts + until they can be fully supported. (mack@sgi.com) */ + if ((s = strchr(hostdir, ','))) { + *s = '\0'; + nfs_error(_("%s: warning: multiple hostnames not supported"), + progname); + } + return 0; +} + +static int fill_ipv4_sockaddr(const char *hostname, struct sockaddr_in *addr) +{ + struct hostent *hp; + addr->sin_family = AF_INET; + + if (inet_aton(hostname, &addr->sin_addr)) + return 0; + if ((hp = gethostbyname(hostname)) == NULL) { + nfs_error(_("%s: can't get address for %s\n"), + progname, hostname); + return -1; + } + if (hp->h_length > (int)sizeof(struct in_addr)) { + nfs_error(_("%s: got bad hp->h_length"), progname); + hp->h_length = sizeof(struct in_addr); + } + memcpy(&addr->sin_addr, hp->h_addr, hp->h_length); + return 0; +} + +static int get_my_ipv4addr(char *ip_addr, int len) +{ + char myname[1024]; + struct sockaddr_in myaddr; + + if (gethostname(myname, sizeof(myname))) { + nfs_error(_("%s: can't determine client address\n"), + progname); + return -1; + } + if (fill_ipv4_sockaddr(myname, &myaddr)) + return -1; + snprintf(ip_addr, len, "%s", inet_ntoa(myaddr.sin_addr)); + ip_addr[len-1] = '\0'; + return 0; +} + +int nfs4mount(const char *spec, const char *node, int flags, + char **extra_opts, int fake, int running_bg) +{ + static struct nfs4_mount_data data; + static char hostdir[1024]; + static char ip_addr[16] = "127.0.0.1"; + static struct sockaddr_in server_addr, client_addr; + static int pseudoflavour[MAX_USER_FLAVOUR]; + int num_flavour = 0; + int ip_addr_in_opts = 0; + + char *hostname, *dirname, *old_opts; + char new_opts[1024]; + char *opt, *opteq; + char *s; + int val; + int bg, soft, intr; + int nocto, noac, unshared; + int retry; + int retval = EX_FAIL; + time_t timeout, t; + + if (strlen(spec) >= sizeof(hostdir)) { + nfs_error(_("%s: excessively long host:dir argument\n"), + progname); + goto fail; + } + strcpy(hostdir, spec); + if (parse_devname(hostdir, &hostname, &dirname)) + goto fail; + + if (fill_ipv4_sockaddr(hostname, &server_addr)) + goto fail; + if (get_my_ipv4addr(ip_addr, sizeof(ip_addr))) + goto fail; + + /* add IP address to mtab options for use when unmounting */ + s = inet_ntoa(server_addr.sin_addr); + old_opts = *extra_opts; + if (!old_opts) + old_opts = ""; + if (strlen(old_opts) + strlen(s) + 10 >= sizeof(new_opts)) { + nfs_error(_("%s: excessively long option argument\n"), + progname); + goto fail; + } + if (running_bg) + strncpy(new_opts, old_opts, sizeof(new_opts)-1); + else + snprintf(new_opts, sizeof(new_opts), "%s%saddr=%s", + old_opts, *old_opts ? "," : "", s); + *extra_opts = xstrdup(new_opts); + + /* Set default options. + * rsize/wsize and timeo are left 0 in order to + * let the kernel decide. + */ + memset(&data, 0, sizeof(data)); + data.retrans = 3; + data.acregmin = 3; + data.acregmax = 60; + data.acdirmin = 30; + data.acdirmax = 60; + data.proto = IPPROTO_TCP; + + bg = 0; + soft = 0; + intr = NFS4_MOUNT_INTR; + nocto = 0; + noac = 0; + unshared = 0; + retry = -1; + + /* + * NFSv4 specifies that the default port should be 2049 + */ + server_addr.sin_port = htons(NFS_PORT); + + /* parse options */ + + for (opt = strtok(old_opts, ","); opt; opt = strtok(NULL, ",")) { + if ((opteq = strchr(opt, '='))) { + val = atoi(opteq + 1); + *opteq = '\0'; + if (!strcmp(opt, "rsize")) + data.rsize = val; + else if (!strcmp(opt, "wsize")) + data.wsize = val; + else if (!strcmp(opt, "timeo")) + data.timeo = val; + else if (!strcmp(opt, "retrans")) + data.retrans = val; + else if (!strcmp(opt, "acregmin")) + data.acregmin = val; + else if (!strcmp(opt, "acregmax")) + data.acregmax = val; + else if (!strcmp(opt, "acdirmin")) + data.acdirmin = val; + else if (!strcmp(opt, "acdirmax")) + data.acdirmax = val; + else if (!strcmp(opt, "actimeo")) { + data.acregmin = val; + data.acregmax = val; + data.acdirmin = val; + data.acdirmax = val; + } + else if (!strcmp(opt, "retry")) + retry = val; + else if (!strcmp(opt, "port")) + server_addr.sin_port = htons(val); + else if (!strcmp(opt, "proto")) { + if (!strncmp(opteq+1, "tcp", 3)) + data.proto = IPPROTO_TCP; + else if (!strncmp(opteq+1, "udp", 3)) + data.proto = IPPROTO_UDP; + else + printf(_("Warning: Unrecognized proto= option.\n")); + } else if (!strcmp(opt, "clientaddr")) { + if (strlen(opteq+1) >= sizeof(ip_addr)) + printf(_("Invalid client address %s"), + opteq+1); + strncpy(ip_addr,opteq+1, sizeof(ip_addr)); + ip_addr[sizeof(ip_addr)-1] = '\0'; + ip_addr_in_opts = 1; + } else if (!strcmp(opt, "sec")) { + num_flavour = parse_sec(opteq+1, pseudoflavour); + if (!num_flavour) + goto fail; + } else if (!strcmp(opt, "addr") || sloppy) { + /* ignore */; + } else { + printf(_("unknown nfs mount parameter: " + "%s=%d\n"), opt, val); + goto fail; + } + } else { + val = 1; + if (!strncmp(opt, "no", 2)) { + val = 0; + opt += 2; + } + if (!strcmp(opt, "bg")) + bg = val; + else if (!strcmp(opt, "fg")) + bg = !val; + else if (!strcmp(opt, "soft")) + soft = val; + else if (!strcmp(opt, "hard")) + soft = !val; + else if (!strcmp(opt, "intr")) + intr = val; + else if (!strcmp(opt, "cto")) + nocto = !val; + else if (!strcmp(opt, "ac")) + noac = !val; + else if (!strcmp(opt, "sharecache")) + unshared = !val; + else if (!sloppy) { + printf(_("unknown nfs mount option: %s%s\n"), + val ? "" : "no", opt); + goto fail; + } + } + } + + /* if retry is still -1, then it wasn't set via an option */ + if (retry == -1) { + if (bg) + retry = 10000; /* 10000 mins == ~1 week */ + else + retry = 2; /* 2 min default on fg mounts */ + } + + data.flags = (soft ? NFS4_MOUNT_SOFT : 0) + | (intr ? NFS4_MOUNT_INTR : 0) + | (nocto ? NFS4_MOUNT_NOCTO : 0) + | (noac ? NFS4_MOUNT_NOAC : 0) + | (unshared ? NFS4_MOUNT_UNSHARED : 0); + + /* + * Give a warning if the rpc.idmapd daemon is not running + */ +#if 0 + /* We shouldn't have these checks as nothing in this package + * creates the files that are checked + */ + idmapd_check(); + + if (num_flavour == 0) + pseudoflavour[num_flavour++] = AUTH_UNIX; + else { + /* + * ditto with rpc.gssd daemon + */ + gssd_check(); + } +#endif + data.auth_flavourlen = num_flavour; + data.auth_flavours = pseudoflavour; + + data.client_addr.data = ip_addr; + data.client_addr.len = strlen(ip_addr); + + data.mnt_path.data = dirname; + data.mnt_path.len = strlen(dirname); + + data.hostname.data = hostname; + data.hostname.len = strlen(hostname); + data.host_addr = (struct sockaddr *)&server_addr; + data.host_addrlen = sizeof(server_addr); + +#ifdef NFS_MOUNT_DEBUG + printf(_("rsize = %d, wsize = %d, timeo = %d, retrans = %d\n"), + data.rsize, data.wsize, data.timeo, data.retrans); + printf(_("acreg (min, max) = (%d, %d), acdir (min, max) = (%d, %d)\n"), + data.acregmin, data.acregmax, data.acdirmin, data.acdirmax); + printf(_("port = %d, bg = %d, retry = %d, flags = %.8x\n"), + ntohs(server_addr.sin_port), bg, retry, data.flags); + printf(_("soft = %d, intr = %d, nocto = %d, noac = %d, " + "nosharecache = %d\n"), + (data.flags & NFS4_MOUNT_SOFT) != 0, + (data.flags & NFS4_MOUNT_INTR) != 0, + (data.flags & NFS4_MOUNT_NOCTO) != 0, + (data.flags & NFS4_MOUNT_NOAC) != 0, + (data.flags & NFS4_MOUNT_UNSHARED) != 0); + + if (num_flavour > 0) { + int pf_cnt, i; + + printf(_("sec = ")); + for (pf_cnt = 0; pf_cnt < num_flavour; pf_cnt++) { + for (i = 0; i < flav_map_size; i++) { + if (flav_map[i].fnum == pseudoflavour[pf_cnt]) { + printf("%s", flav_map[i].flavour); + break; + } + } + printf("%s", (pf_cnt < num_flavour-1) ? ":" : "\n"); + } + } + printf(_("proto = %s\n"), (data.proto == IPPROTO_TCP) ? _("tcp") : _("udp")); +#endif + + timeout = time(NULL) + 60 * retry; + data.version = NFS4_MOUNT_VERSION; + for (;;) { + if (verbose) { + printf(_("%s: pinging: prog %d vers %d prot %s port %d\n"), + progname, NFS_PROGRAM, 4, + data.proto == IPPROTO_UDP ? "udp" : "tcp", + ntohs(server_addr.sin_port)); + } + client_addr.sin_family = 0; + client_addr.sin_addr.s_addr = 0; + clnt_ping(&server_addr, NFS_PROGRAM, 4, data.proto, &client_addr); + if (rpc_createerr.cf_stat == RPC_SUCCESS) { + if (!ip_addr_in_opts && + client_addr.sin_family != 0 && + client_addr.sin_addr.s_addr != 0) { + snprintf(ip_addr, sizeof(ip_addr), "%s", + inet_ntoa(client_addr.sin_addr)); + data.client_addr.len = strlen(ip_addr); + } + break; + } + + if (!bg) { + switch(rpc_createerr.cf_stat) { + case RPC_TIMEDOUT: + break; + case RPC_SYSTEMERROR: + if (errno == ETIMEDOUT) + break; + /* FALLTHRU */ + default: + rpc_mount_errors(hostname, 0, bg); + goto fail; + } + } + + if (bg && !running_bg) { + if (retry > 0) + retval = EX_BG; + goto fail; + } + + t = time(NULL); + if (t >= timeout) { + rpc_mount_errors(hostname, 0, bg); + goto fail; + } + rpc_mount_errors(hostname, 1, bg); + continue; + } + + if (!fake) { + if (mount(spec, node, "nfs4", + flags & ~(MS_USER|MS_USERS), &data)) { + mount_error(spec, node, errno); + goto fail; + } + } + + return EX_SUCCESS; + +fail: + return retval; +} diff --git a/utils/mount/nfs_mount.h b/utils/mount/nfs_mount.h new file mode 100644 index 0000000..ec30c9b --- /dev/null +++ b/utils/mount/nfs_mount.h @@ -0,0 +1,83 @@ +/* + * We want to be able to compile mount on old kernels in such a way + * that the binary will work well on more recent kernels. + * Thus, if necessary we teach nfsmount.c the structure of new fields + * that will come later. + * + * Moreover, the new kernel includes conflict with glibc includes + * so it is easiest to ignore the kernel altogether (at compile time). + */ + +#ifndef _NFS_UTILS_MOUNT_NFS_MOUNT_H +#define _NFS_UTILS_MOUNT_NFS_MOUNT_H + +#include +#include + +#define NFS_MOUNT_VERSION 6 +#define NFS_MAX_CONTEXT_LEN 256 + +struct nfs2_fh { + char data[32]; +}; +struct nfs3_fh { + unsigned short size; + unsigned char data[64]; +}; + +struct nfs_mount_data { + int version; /* 1 */ + int fd; /* 1 */ + struct nfs2_fh old_root; /* 1 */ + int flags; /* 1 */ + int rsize; /* 1 */ + int wsize; /* 1 */ + int timeo; /* 1 */ + int retrans; /* 1 */ + int acregmin; /* 1 */ + int acregmax; /* 1 */ + int acdirmin; /* 1 */ + int acdirmax; /* 1 */ + struct sockaddr_in addr; /* 1 */ + char hostname[256]; /* 1 */ + int namlen; /* 2 */ + unsigned int bsize; /* 3 */ + struct nfs3_fh root; /* 4 */ + int pseudoflavor; /* 5 */ + char context[NFS_MAX_CONTEXT_LEN + 1]; /* 6 */ + +}; + +/* bits in the flags field */ + +#define NFS_MOUNT_SOFT 0x0001 /* 1 */ +#define NFS_MOUNT_INTR 0x0002 /* 1 */ +#define NFS_MOUNT_SECURE 0x0004 /* 1 */ +#define NFS_MOUNT_POSIX 0x0008 /* 1 */ +#define NFS_MOUNT_NOCTO 0x0010 /* 1 */ +#define NFS_MOUNT_NOAC 0x0020 /* 1 */ +#define NFS_MOUNT_TCP 0x0040 /* 2 */ +#define NFS_MOUNT_VER3 0x0080 /* 3 */ +#define NFS_MOUNT_KERBEROS 0x0100 /* 3 */ +#define NFS_MOUNT_NONLM 0x0200 /* 3 */ +#define NFS_MOUNT_BROKEN_SUID 0x0400 /* 4 */ +#define NFS_MOUNT_NOACL 0x0800 /* 4 */ +#define NFS_MOUNT_SECFLAVOUR 0x2000 /* 5 */ +#define NFS_MOUNT_NORDIRPLUS 0x4000 /* 5 */ +#define NFS_MOUNT_UNSHARED 0x8000 /* 5 */ + +/* security pseudoflavors */ + +#ifndef AUTH_GSS_KRB5 +#define AUTH_GSS_KRB5 390003 +#define AUTH_GSS_KRB5I 390004 +#define AUTH_GSS_KRB5P 390005 +#define AUTH_GSS_LKEY 390006 +#define AUTH_GSS_LKEYI 390007 +#define AUTH_GSS_LKEYP 390008 +#endif + +int nfsmount(const char *, const char *, int , char **, int, int); +int nfsumount(int, char **); + +#endif /* _NFS_UTILS_MOUNT_NFS_MOUNT_H */ diff --git a/utils/mount/nfsmount.c b/utils/mount/nfsmount.c new file mode 100644 index 0000000..3d95da9 --- /dev/null +++ b/utils/mount/nfsmount.c @@ -0,0 +1,873 @@ +/* + * nfsmount.c -- Linux NFS mount + * Copyright (C) 1993 Rick Sladkey + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Wed Feb 8 12:51:48 1995, biro@yggdrasil.com (Ross Biro): allow all port + * numbers to be specified on the command line. + * + * Fri, 8 Mar 1996 18:01:39, Swen Thuemmler : + * Omit the call to connect() for Linux version 1.3.11 or later. + * + * Wed Oct 1 23:55:28 1997: Dick Streefland + * Implemented the "bg", "fg" and "retry" mount options for NFS. + * + * 1999-02-22 Arkadiusz Miskiewicz + * - added Native Language Support + * + * Modified by Olaf Kirch and Trond Myklebust for new NFS code, + * plus NFSv3 stuff. + * + * 2006-06-06 Amit Gud + * - Moved with modifcations to nfs-utils/utils/mount from util-linux/mount. + */ + +/* + * nfsmount.c,v 1.1.1.1 1993/11/18 08:40:51 jrs Exp + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xcommon.h" +#include "mount.h" +#include "nfs_mount.h" +#include "mount_constants.h" +#include "nls.h" +#include "error.h" +#include "network.h" +#include "version.h" + +#ifdef HAVE_RPCSVC_NFS_PROT_H +#include +#else +#include +#define nfsstat nfs_stat +#endif + +#ifndef NFS_PORT +#define NFS_PORT 2049 +#endif +#ifndef NFS_FHSIZE +#define NFS_FHSIZE 32 +#endif + +#ifndef HAVE_INET_ATON +#define inet_aton(a,b) (0) +#endif + +typedef dirpath mnt2arg_t; +typedef dirpath mnt3arg_t; +typedef dirpath mntarg_t; + +typedef struct fhstatus mnt2res_t; +typedef struct mountres3 mnt3res_t; +typedef union { + mnt2res_t nfsv2; + mnt3res_t nfsv3; +} mntres_t; + +extern int nfs_mount_data_version; +extern char *progname; +extern int verbose; +extern int sloppy; + +static inline enum clnt_stat +nfs3_mount(CLIENT *clnt, mnt3arg_t *mnt3arg, mnt3res_t *mnt3res) +{ + return clnt_call(clnt, MOUNTPROC3_MNT, + (xdrproc_t) xdr_dirpath, (caddr_t) mnt3arg, + (xdrproc_t) xdr_mountres3, (caddr_t) mnt3res, + TIMEOUT); +} + +static inline enum clnt_stat +nfs2_mount(CLIENT *clnt, mnt2arg_t *mnt2arg, mnt2res_t *mnt2res) +{ + return clnt_call(clnt, MOUNTPROC_MNT, + (xdrproc_t) xdr_dirpath, (caddr_t) mnt2arg, + (xdrproc_t) xdr_fhstatus, (caddr_t) mnt2res, + TIMEOUT); +} + +static int +nfs_call_mount(clnt_addr_t *mnt_server, clnt_addr_t *nfs_server, + mntarg_t *mntarg, mntres_t *mntres) +{ + CLIENT *clnt; + enum clnt_stat stat; + int msock; + + if (!probe_bothports(mnt_server, nfs_server)) + goto out_bad; + + clnt = mnt_openclnt(mnt_server, &msock); + if (!clnt) + goto out_bad; + /* make pointers in xdr_mountres3 NULL so + * that xdr_array allocates memory for us + */ + memset(mntres, 0, sizeof(*mntres)); + switch (mnt_server->pmap.pm_vers) { + case 3: + stat = nfs3_mount(clnt, mntarg, &mntres->nfsv3); + break; + case 2: + case 1: + stat = nfs2_mount(clnt, mntarg, &mntres->nfsv2); + break; + default: + goto out_bad; + } + if (stat != RPC_SUCCESS) { + clnt_geterr(clnt, &rpc_createerr.cf_error); + rpc_createerr.cf_stat = stat; + } + mnt_closeclnt(clnt, msock); + if (stat == RPC_SUCCESS) + return 1; + out_bad: + return 0; +} + +static int +parse_options(char *old_opts, struct nfs_mount_data *data, + int *bg, int *retry, clnt_addr_t *mnt_server, + clnt_addr_t *nfs_server, char *new_opts, const int opt_size) +{ + struct sockaddr_in *mnt_saddr = &mnt_server->saddr; + struct pmap *mnt_pmap = &mnt_server->pmap; + struct pmap *nfs_pmap = &nfs_server->pmap; + int len; + char *opt, *opteq, *p, *opt_b, *tmp_opts; + char *mounthost = NULL; + char cbuf[128]; + int open_quote = 0; + + data->flags = 0; + *bg = 0; + + len = strlen(new_opts); + tmp_opts = xstrdup(old_opts); + for (p=tmp_opts, opt_b=NULL; p && *p; p++) { + if (!opt_b) + opt_b = p; /* begin of the option item */ + if (*p == '"') + open_quote ^= 1; /* reverse the status */ + if (open_quote) + continue; /* still in a quoted block */ + if (*p == ',') + *p = '\0'; /* terminate the option item */ + if (*p == '\0' || *(p+1) == '\0') { + opt = opt_b; /* opt is useful now */ + opt_b = NULL; + } + else + continue; /* still somewhere in the option item */ + + if (strlen(opt) >= sizeof(cbuf)) + goto bad_parameter; + if ((opteq = strchr(opt, '=')) && isdigit(opteq[1])) { + int val = atoi(opteq + 1); + *opteq = '\0'; + if (!strcmp(opt, "rsize")) + data->rsize = val; + else if (!strcmp(opt, "wsize")) + data->wsize = val; + else if (!strcmp(opt, "timeo")) + data->timeo = val; + else if (!strcmp(opt, "retrans")) + data->retrans = val; + else if (!strcmp(opt, "acregmin")) + data->acregmin = val; + else if (!strcmp(opt, "acregmax")) + data->acregmax = val; + else if (!strcmp(opt, "acdirmin")) + data->acdirmin = val; + else if (!strcmp(opt, "acdirmax")) + data->acdirmax = val; + else if (!strcmp(opt, "actimeo")) { + data->acregmin = val; + data->acregmax = val; + data->acdirmin = val; + data->acdirmax = val; + } + else if (!strcmp(opt, "retry")) + *retry = val; + else if (!strcmp(opt, "port")) + nfs_pmap->pm_port = val; + else if (!strcmp(opt, "mountport")) + mnt_pmap->pm_port = val; + else if (!strcmp(opt, "mountprog")) + mnt_pmap->pm_prog = val; + else if (!strcmp(opt, "mountvers")) + mnt_pmap->pm_vers = val; + else if (!strcmp(opt, "mounthost")) + mounthost=xstrndup(opteq+1, strcspn(opteq+1," \t\n\r,")); + else if (!strcmp(opt, "nfsprog")) + nfs_pmap->pm_prog = val; + else if (!strcmp(opt, "nfsvers") || + !strcmp(opt, "vers")) { + nfs_pmap->pm_vers = val; + opt = "nfsvers"; +#if NFS_MOUNT_VERSION >= 2 + } else if (!strcmp(opt, "namlen")) { + if (nfs_mount_data_version >= 2) + data->namlen = val; + else if (sloppy) + continue; + else + goto bad_parameter; +#endif + } else if (!strcmp(opt, "addr")) { + /* ignore */; + continue; + } else if (sloppy) + continue; + else + goto bad_parameter; + sprintf(cbuf, "%s=%s,", opt, opteq+1); + } else if (opteq) { + *opteq = '\0'; + if (!strcmp(opt, "proto")) { + if (!strcmp(opteq+1, "udp")) { + nfs_pmap->pm_prot = IPPROTO_UDP; + mnt_pmap->pm_prot = IPPROTO_UDP; +#if NFS_MOUNT_VERSION >= 2 + data->flags &= ~NFS_MOUNT_TCP; + } else if (!strcmp(opteq+1, "tcp") && + nfs_mount_data_version > 2) { + nfs_pmap->pm_prot = IPPROTO_TCP; + mnt_pmap->pm_prot = IPPROTO_TCP; + data->flags |= NFS_MOUNT_TCP; +#endif + } else if (sloppy) + continue; + else + goto bad_parameter; +#if NFS_MOUNT_VERSION >= 5 + } else if (!strcmp(opt, "sec")) { + char *secflavor = opteq+1; + /* see RFC 2623 */ + if (nfs_mount_data_version < 5) { + printf(_("Warning: ignoring sec=%s option\n"), + secflavor); + continue; + } else if (!strcmp(secflavor, "none")) + data->pseudoflavor = AUTH_NONE; + else if (!strcmp(secflavor, "sys")) + data->pseudoflavor = AUTH_SYS; + else if (!strcmp(secflavor, "krb5")) + data->pseudoflavor = AUTH_GSS_KRB5; + else if (!strcmp(secflavor, "krb5i")) + data->pseudoflavor = AUTH_GSS_KRB5I; + else if (!strcmp(secflavor, "krb5p")) + data->pseudoflavor = AUTH_GSS_KRB5P; + else if (sloppy) + continue; + else { + printf(_("Warning: Unrecognized security flavor %s.\n"), + secflavor); + goto bad_parameter; + } + data->flags |= NFS_MOUNT_SECFLAVOUR; +#endif + } else if (!strcmp(opt, "mounthost")) + mounthost=xstrndup(opteq+1, + strcspn(opteq+1," \t\n\r,")); + else if (!strcmp(opt, "context")) { + char *context = opteq + 1; + int ctxlen = strlen(context); + + if (ctxlen > NFS_MAX_CONTEXT_LEN) { + nfs_error(_("context parameter exceeds" + " limit of %d"), + NFS_MAX_CONTEXT_LEN); + goto bad_parameter; + } + /* The context string is in the format of + * "system_u:object_r:...". We only want + * the context str between the quotes. + */ + if (*context == '"') + strncpy(data->context, context+1, + ctxlen-2); + else + strncpy(data->context, context, + NFS_MAX_CONTEXT_LEN); + } else if (sloppy) + continue; + else + goto bad_parameter; + sprintf(cbuf, "%s=%s,", opt, opteq+1); + } else { + int val = 1; + if (!strncmp(opt, "no", 2)) { + val = 0; + opt += 2; + } + if (!strcmp(opt, "bg")) + *bg = val; + else if (!strcmp(opt, "fg")) + *bg = !val; + else if (!strcmp(opt, "soft")) { + data->flags &= ~NFS_MOUNT_SOFT; + if (val) + data->flags |= NFS_MOUNT_SOFT; + } else if (!strcmp(opt, "hard")) { + data->flags &= ~NFS_MOUNT_SOFT; + if (!val) + data->flags |= NFS_MOUNT_SOFT; + } else if (!strcmp(opt, "intr")) { + data->flags &= ~NFS_MOUNT_INTR; + if (val) + data->flags |= NFS_MOUNT_INTR; + } else if (!strcmp(opt, "posix")) { + data->flags &= ~NFS_MOUNT_POSIX; + if (val) + data->flags |= NFS_MOUNT_POSIX; + } else if (!strcmp(opt, "cto")) { + data->flags &= ~NFS_MOUNT_NOCTO; + if (!val) + data->flags |= NFS_MOUNT_NOCTO; + } else if (!strcmp(opt, "ac")) { + data->flags &= ~NFS_MOUNT_NOAC; + if (!val) + data->flags |= NFS_MOUNT_NOAC; +#if NFS_MOUNT_VERSION >= 2 + } else if (!strcmp(opt, "tcp")) { + data->flags &= ~NFS_MOUNT_TCP; + if (val) { + if (nfs_mount_data_version < 2) + goto bad_option; + nfs_pmap->pm_prot = IPPROTO_TCP; + mnt_pmap->pm_prot = IPPROTO_TCP; + data->flags |= NFS_MOUNT_TCP; + } else { + mnt_pmap->pm_prot = IPPROTO_UDP; + nfs_pmap->pm_prot = IPPROTO_UDP; + } + } else if (!strcmp(opt, "udp")) { + data->flags &= ~NFS_MOUNT_TCP; + if (!val) { + if (nfs_mount_data_version < 2) + goto bad_option; + nfs_pmap->pm_prot = IPPROTO_TCP; + mnt_pmap->pm_prot = IPPROTO_TCP; + data->flags |= NFS_MOUNT_TCP; + } else { + nfs_pmap->pm_prot = IPPROTO_UDP; + mnt_pmap->pm_prot = IPPROTO_UDP; + } +#endif +#if NFS_MOUNT_VERSION >= 3 + } else if (!strcmp(opt, "lock")) { + data->flags &= ~NFS_MOUNT_NONLM; + if (!val) { + if (nfs_mount_data_version < 3) + goto bad_option; + data->flags |= NFS_MOUNT_NONLM; + } +#endif +#if NFS_MOUNT_VERSION >= 4 + } else if (!strcmp(opt, "broken_suid")) { + data->flags &= ~NFS_MOUNT_BROKEN_SUID; + if (val) { + if (nfs_mount_data_version < 4) + goto bad_option; + data->flags |= NFS_MOUNT_BROKEN_SUID; + } + } else if (!strcmp(opt, "acl")) { + data->flags &= ~NFS_MOUNT_NOACL; + if (!val) + data->flags |= NFS_MOUNT_NOACL; + } else if (!strcmp(opt, "rdirplus")) { + data->flags &= ~NFS_MOUNT_NORDIRPLUS; + if (!val) + data->flags |= NFS_MOUNT_NORDIRPLUS; + } else if (!strcmp(opt, "sharecache")) { + data->flags &= ~NFS_MOUNT_UNSHARED; + if (!val) + data->flags |= NFS_MOUNT_UNSHARED; +#endif + } else { + bad_option: + if (sloppy) + continue; + nfs_error(_("%s: Unsupported nfs mount option:" + " %s%s"), progname, + val ? "" : "no", opt); + goto out_bad; + } + sprintf(cbuf, val ? "%s," : "no%s,", opt); + } + len += strlen(cbuf); + if (len >= opt_size) { + nfs_error(_("%s: excessively long option argument"), + progname); + goto out_bad; + } + strcat(new_opts, cbuf); + } + /* See if the nfs host = mount host. */ + if (mounthost) { + if (!nfs_gethostbyname(mounthost, mnt_saddr)) + goto out_bad; + *mnt_server->hostname = mounthost; + } + free(tmp_opts); + return 1; + bad_parameter: + nfs_error(_("%s: Bad nfs mount parameter: %s\n"), progname, opt); + out_bad: + free(tmp_opts); + free(mounthost); + return 0; +} + +static int nfsmnt_check_compat(const struct pmap *nfs_pmap, + const struct pmap *mnt_pmap) +{ + unsigned int max_nfs_vers = (nfs_mount_data_version >= 4) ? 3 : 2; + unsigned int max_mnt_vers = (nfs_mount_data_version >= 4) ? 3 : 2; + + if (nfs_pmap->pm_vers == 4) { + nfs_error(_("%s: Please use '-t nfs4' " + "instead of '-o vers=4'"), progname); + goto out_bad; + } + + if (nfs_pmap->pm_vers) { + if (nfs_pmap->pm_vers > max_nfs_vers || nfs_pmap->pm_vers < 2) { + nfs_error(_("%s: NFS version %ld is not supported"), + progname, nfs_pmap->pm_vers); + goto out_bad; + } + } + + if (mnt_pmap->pm_vers > max_mnt_vers) { + nfs_error(_("%s: NFS mount version %ld is not supported"), + progname, mnt_pmap->pm_vers); + goto out_bad; + } + + return 1; + +out_bad: + return 0; +} + +int +nfsmount(const char *spec, const char *node, int flags, + char **extra_opts, int fake, int running_bg) +{ + char hostdir[1024]; + char *hostname, *dirname, *old_opts, *mounthost = NULL; + char new_opts[1024], cbuf[1024]; + static struct nfs_mount_data data; + int val; + static int doonce = 0; + + clnt_addr_t mnt_server = { + .hostname = &mounthost + }; + clnt_addr_t nfs_server = { + .hostname = &hostname + }; + struct sockaddr_in *nfs_saddr = &nfs_server.saddr; + struct pmap *mnt_pmap = &mnt_server.pmap, + *nfs_pmap = &nfs_server.pmap; + struct pmap save_mnt, save_nfs; + + int fsock = -1; + + mntres_t mntres; + + struct stat statbuf; + char *s; + int bg, retry; + int retval = EX_FAIL; + time_t t; + time_t prevt; + time_t timeout; + + if (strlen(spec) >= sizeof(hostdir)) { + nfs_error(_("%s: excessively long host:dir argument"), + progname); + goto fail; + } + strcpy(hostdir, spec); + if ((s = strchr(hostdir, ':'))) { + hostname = hostdir; + dirname = s + 1; + *s = '\0'; + /* Ignore all but first hostname in replicated mounts + until they can be fully supported. (mack@sgi.com) */ + if ((s = strchr(hostdir, ','))) { + *s = '\0'; + nfs_error(_("%s: warning: " + "multiple hostnames not supported"), + progname); + } + } else { + nfs_error(_("%s: directory to mount not in host:dir format"), + progname); + goto fail; + } + + if (!nfs_gethostbyname(hostname, nfs_saddr)) + goto fail; + mounthost = hostname; + memcpy (&mnt_server.saddr, nfs_saddr, sizeof (mnt_server.saddr)); + + /* add IP address to mtab options for use when unmounting */ + + s = inet_ntoa(nfs_saddr->sin_addr); + old_opts = *extra_opts; + if (!old_opts) + old_opts = ""; + + /* Set default options. + * rsize/wsize (and bsize, for ver >= 3) are left 0 in order to + * let the kernel decide. + * timeo is filled in after we know whether it'll be TCP or UDP. */ + memset(&data, 0, sizeof(data)); + data.acregmin = 3; + data.acregmax = 60; + data.acdirmin = 30; + data.acdirmax = 60; +#if NFS_MOUNT_VERSION >= 2 + data.namlen = NAME_MAX; +#endif + + bg = 0; + retry = -1; + + memset(mnt_pmap, 0, sizeof(*mnt_pmap)); + mnt_pmap->pm_prog = MOUNTPROG; + memset(nfs_pmap, 0, sizeof(*nfs_pmap)); + nfs_pmap->pm_prog = NFS_PROGRAM; + + /* parse options */ + new_opts[0] = 0; + if (!parse_options(old_opts, &data, &bg, &retry, &mnt_server, &nfs_server, + new_opts, sizeof(new_opts))) + goto fail; + if (!nfsmnt_check_compat(nfs_pmap, mnt_pmap)) + goto fail; + + if (retry == -1) { + if (bg) + retry = 10000; /* 10000 mins == ~1 week*/ + else + retry = 2; /* 2 min default on fg mounts */ + } + +#ifdef NFS_MOUNT_DEBUG + printf(_("rsize = %d, wsize = %d, timeo = %d, retrans = %d\n"), + data.rsize, data.wsize, data.timeo, data.retrans); + printf(_("acreg (min, max) = (%d, %d), acdir (min, max) = (%d, %d)\n"), + data.acregmin, data.acregmax, data.acdirmin, data.acdirmax); + printf(_("port = %lu, bg = %d, retry = %d, flags = %.8x\n"), + nfs_pmap->pm_port, bg, retry, data.flags); + printf(_("mountprog = %lu, mountvers = %lu, nfsprog = %lu, nfsvers = %lu\n"), + mnt_pmap->pm_prog, mnt_pmap->pm_vers, + nfs_pmap->pm_prog, nfs_pmap->pm_vers); + printf(_("soft = %d, intr = %d, posix = %d, nocto = %d, noac = %d"), + (data.flags & NFS_MOUNT_SOFT) != 0, + (data.flags & NFS_MOUNT_INTR) != 0, + (data.flags & NFS_MOUNT_POSIX) != 0, + (data.flags & NFS_MOUNT_NOCTO) != 0, + (data.flags & NFS_MOUNT_NOAC) != 0); +#if NFS_MOUNT_VERSION >= 2 + printf(_(", tcp = %d"), + (data.flags & NFS_MOUNT_TCP) != 0); +#endif +#if NFS_MOUNT_VERSION >= 4 + printf(_(", noacl = %d"), (data.flags & NFS_MOUNT_NOACL) != 0); +#endif +#if NFS_MOUNT_VERSION >= 5 + printf(_(", sec = %u"), data.pseudoflavor); + printf(_(", readdirplus = %d"), (data.flags & NFS_MOUNT_NORDIRPLUS) != 0); +#endif + printf("\n"); +#endif + + data.version = nfs_mount_data_version; + + if (flags & MS_REMOUNT) + goto out_ok; + + /* create mount deamon client */ + + /* + * The following loop implements the mount retries. On the first + * call, "running_bg" is 0. When the mount times out, and the + * "bg" option is set, the exit status EX_BG will be returned. + * For a backgrounded mount, there will be a second call by the + * child process with "running_bg" set to 1. + * + * The case where the mount point is not present and the "bg" + * option is set, is treated as a timeout. This is done to + * support nested mounts. + * + * The "retry" count specified by the user is the number of + * minutes to retry before giving up. + * + * Only the first error message will be displayed. + */ + timeout = time(NULL) + 60 * retry; + prevt = 0; + t = 30; + val = 1; + + memcpy(&save_nfs, nfs_pmap, sizeof(save_nfs)); + memcpy(&save_mnt, mnt_pmap, sizeof(save_mnt)); + for (;;) { + if (bg && stat(node, &statbuf) == -1) { + /* no mount point yet - sleep */ + if (running_bg) { + sleep(val); /* 1, 2, 4, 8, 16, 30, ... */ + val *= 2; + if (val > 30) + val = 30; + } + } else { + int stat; + /* be careful not to use too many CPU cycles */ + if (t - prevt < 30) + sleep(30); + + stat = nfs_call_mount(&mnt_server, &nfs_server, + &dirname, &mntres); + if (stat) + break; + memcpy(nfs_pmap, &save_nfs, sizeof(*nfs_pmap)); + memcpy(mnt_pmap, &save_mnt, sizeof(*mnt_pmap)); + prevt = t; + } + if (!bg) { + switch(rpc_createerr.cf_stat){ + case RPC_TIMEDOUT: + break; + case RPC_SYSTEMERROR: + if (errno == ETIMEDOUT) + break; + /* FALLTHRU */ + default: + rpc_mount_errors(*nfs_server.hostname, 0, bg); + goto fail; + } + t = time(NULL); + if (t >= timeout) { + rpc_mount_errors(*nfs_server.hostname, 0, bg); + goto fail; + } + rpc_mount_errors(*nfs_server.hostname, 1, bg); + continue; + } + if (!running_bg) { + if (retry > 0) + retval = EX_BG; + goto fail; + } + t = time(NULL); + if (t >= timeout) { + rpc_mount_errors(*nfs_server.hostname, 0, bg); + goto fail; + } + if (doonce++ < 1) + rpc_mount_errors(*nfs_server.hostname, 1, bg); + } + + if (mnt_pmap->pm_vers <= 2) { + if (mntres.nfsv2.fhs_status != 0) { + nfs_error(_("%s: %s:%s failed, reason given by server: %s"), + progname, hostname, dirname, + nfs_strerror(mntres.nfsv2.fhs_status)); + goto fail; + } + memcpy(data.root.data, + (char *) mntres.nfsv2.fhstatus_u.fhs_fhandle, + NFS_FHSIZE); +#if NFS_MOUNT_VERSION >= 4 + data.root.size = NFS_FHSIZE; + memcpy(data.old_root.data, + (char *) mntres.nfsv2.fhstatus_u.fhs_fhandle, + NFS_FHSIZE); +#endif + } else { +#if NFS_MOUNT_VERSION >= 4 + mountres3_ok *mountres; + fhandle3 *fhandle; + int i, n_flavors, *flavor, yum = 0; + if (mntres.nfsv3.fhs_status != 0) { + nfs_error(_("%s: %s:%s failed, reason given by server: %s"), + progname, hostname, dirname, + nfs_strerror(mntres.nfsv3.fhs_status)); + goto fail; + } +#if NFS_MOUNT_VERSION >= 5 + mountres = &mntres.nfsv3.mountres3_u.mountinfo; + n_flavors = mountres->auth_flavors.auth_flavors_len; + if (n_flavors <= 0) + goto noauth_flavors; + + flavor = mountres->auth_flavors.auth_flavors_val; + for (i = 0; i < n_flavors; ++i) { + /* + * Per RFC2623, section 2.7, we should prefer the + * flavour listed first. + * If no flavour requested, use the first simple + * flavour that is offered. + */ + if (! (data.flags & NFS_MOUNT_SECFLAVOUR) && + (flavor[i] == AUTH_SYS || + flavor[i] == AUTH_NONE)) { + data.pseudoflavor = flavor[i]; + data.flags |= NFS_MOUNT_SECFLAVOUR; + } + if (flavor[i] == data.pseudoflavor) + yum = 1; +#ifdef NFS_MOUNT_DEBUG + printf(_("auth flavor %d: %d\n"), i, flavor[i]); +#endif + } + if (!yum) { + nfs_error(_("%s: %s:%s failed, security flavor " + "not supported"), + progname, hostname, dirname); + /* server has registered us in rmtab, send umount */ + nfs_call_umount(&mnt_server, &dirname); + goto fail; + } +noauth_flavors: +#endif + fhandle = &mntres.nfsv3.mountres3_u.mountinfo.fhandle; + memset(data.old_root.data, 0, NFS_FHSIZE); + memset(&data.root, 0, sizeof(data.root)); + data.root.size = fhandle->fhandle3_len; + memcpy(data.root.data, + (char *) fhandle->fhandle3_val, + fhandle->fhandle3_len); + + data.flags |= NFS_MOUNT_VER3; +#endif + } + + if (nfs_mount_data_version == 1) { + /* create nfs socket for kernel */ + if (nfs_pmap->pm_prot == IPPROTO_TCP) + fsock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + else + fsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fsock < 0) { + perror(_("nfs socket")); + goto fail; + } + if (bindresvport(fsock, 0) < 0) { + perror(_("nfs bindresvport")); + goto fail; + } + } + +#ifdef NFS_MOUNT_DEBUG + printf(_("using port %lu for nfs deamon\n"), nfs_pmap->pm_port); +#endif + nfs_saddr->sin_port = htons(nfs_pmap->pm_port); + /* + * connect() the socket for kernels 1.3.10 and below only, + * to avoid problems with multihomed hosts. + * --Swen + */ + if (linux_version_code() <= MAKE_VERSION(1, 3, 10) && fsock != -1 + && connect(fsock, (struct sockaddr *) nfs_saddr, + sizeof (*nfs_saddr)) < 0) { + perror(_("nfs connect")); + goto fail; + } + +#if NFS_MOUNT_VERSION >= 2 + if (nfs_pmap->pm_prot == IPPROTO_TCP) + data.flags |= NFS_MOUNT_TCP; + else + data.flags &= ~NFS_MOUNT_TCP; +#endif + + /* prepare data structure for kernel */ + + data.fd = fsock; + memcpy((char *) &data.addr, (char *) nfs_saddr, sizeof(data.addr)); + strncpy(data.hostname, hostname, sizeof(data.hostname)-1); + + out_ok: + /* Ensure we have enough padding for the following strcat()s */ + if (strlen(new_opts) + strlen(s) + 30 >= sizeof(new_opts)) { + nfs_error(_("%s: excessively long option argument"), + progname); + goto fail; + } + + snprintf(cbuf, sizeof(cbuf)-1, "addr=%s", s); + strcat(new_opts, cbuf); + + *extra_opts = xstrdup(new_opts); + + if (!fake && !(data.flags & NFS_MOUNT_NONLM)) { + if (!start_statd()) { + nfs_error(_("%s: rpc.statd is not running but is " + "required for remote locking.\n" + " Either use '-o nolock' to keep " + "locks local, or start statd."), + progname); + goto fail; + } + } + + if (!fake) { + if (mount(spec, node, "nfs", + flags & ~(MS_USER|MS_USERS), &data)) { + mount_error(spec, node, errno); + goto fail; + } + } + + return EX_SUCCESS; + + /* abort */ + fail: + if (fsock != -1) + close(fsock); + return retval; +} diff --git a/utils/mount/nfsmount.conf b/utils/mount/nfsmount.conf new file mode 100644 index 0000000..c498eb8 --- /dev/null +++ b/utils/mount/nfsmount.conf @@ -0,0 +1,144 @@ +# +# /etc/nfsmount.conf - see nfsmount.conf(5) for details +# +# This is an NFS mount configuration file. This file can be broken +# up into three different sections: Mount, Server and Global +# +# [ MountPoint "Mount_point" ] +# This section defines all the mount options that +# should be used on a particular mount point. The '' +# string need to be an exact match of the path in the mount +# command. Example: +# [ MountPoint "/export/home" ] +# background=True +# Would cause all mount to /export/home would be done in +# the background +# +# [ Server "Server_Name" ] +# This section defines all the mount options that +# should be used on mounts to a particular NFS server. +# Example: +# [ Server "nfsserver.foo.com" ] +# rsize=32k +# wsize=32k +# All reads and writes to the 'nfsserver.foo.com' server +# will be done with 32k (32768 bytes) block sizes. +# +[ NFSMount_Global_Options ] +# This statically named section defines global mount +# options that can be applied on all NFS mount. +# +# Protocol Version [3,4] +# This defines the default protocol version which will +# be used to start the negotiation with the server. +# Defaultvers=4 +# +# Setting this option makes it mandatory the server supports the +# given version. The mount will fail if the given version is +# not support by the server. +# Nfsvers=4 +# +# Network Protocol [udp,tcp,rdma] (Note: values are case sensitive) +# This defines the default network protocol which will +# be used to start the negotiation with the server. +# Defaultproto=tcp +# +# Setting this option makes it mandatory the server supports the +# given network protocol. The mount will fail if the given network +# protocol is not supported by the server. +# Proto=tcp +# +# The number of times a request will be retired before +# generating a timeout +# Retrans=2 +# +# The number of minutes that will retry mount +# Retry=2 +# +# The minimum time (in seconds) file attributes are cached +# acregmin=30 +# +# The Maximum time (in seconds) file attributes are cached +# acregmax=60 +# +# The minimum time (in seconds) directory attributes are cached +# acdirmin=30 +# +# The Maximum time (in seconds) directory attributes are cached +# acdirmax=60 +# +# Enable Access Control Lists +# Acl=False +# +# Enable Attribute Caching +# Ac=True +# +# Do mounts in background (i.e. asynchronously) +# Background=False +# +# Close-To-Open cache coherence +# Cto=True +# +# Do mounts in foreground (i.e. synchronously) +# Foreground=True +# +# How to handle times out from servers (Hard is STRONGLY suggested) +# Hard=True +# Soft=False +# +# Enable File Locking +# Lock=True +# +# Enable READDIRPLUS on NFS version 3 mounts +# Rdirplus=True +# +# Maximum Read Size (in Bytes) +# Rsize=8k +# +# Maximum Write Size (in Bytes) +# Wsize=8k +# +# Maximum Server Block Size (in Bytes) +# Bsize=8k +# +# Ignore unknown mount options +# Sloppy=False +# +# Share Data and Attribute Caches +# Sharecache=True +# +# The amount of time, in tenths of a seconds, the client +# will wait for a response from the server before retransmitting +# the request. +# Timeo=600 +# +# Sets all attributes times to the same time (in seconds) +# actimeo=30 +# +# Server Mountd port mountport +# mountport=4001 +# +# Server Mountd Protocol +# mountproto=tcp +# +# Server Mountd Version +# mountvers=3 +# +# Server Mountd Host +# mounthost=hostname +# +# Server Port +# Port=2049 +# +# RPCGSS security flavors +# [none, sys, krb5, krb5i, krb5p ] +# Sec=sys +# +# Allow Signals to interrupt file operations +# Intr=True +# +# Specifies how the kernel manages its cache of directory +# Lookupcache=all|none|pos|positive +# +# Turn of the caching of that access time +# noatime=True diff --git a/utils/mount/nfsmount.conf.man b/utils/mount/nfsmount.conf.man new file mode 100644 index 0000000..34879c8 --- /dev/null +++ b/utils/mount/nfsmount.conf.man @@ -0,0 +1,131 @@ +.\" @(#)nfsmount.conf.5" +.TH NFSMOUNT.CONF 5 "16 December 2020" +.SH NAME +nfsmount.conf - Configuration file for NFS mounts +.SH SYNOPSIS +Configuration file for NFS mounts that allows options +to be set globally, per server or per mount point. +.SH DESCRIPTION +The configuration file is made up of multiple section headers +followed by variable assignments associated with that section. +A section header is defined by a string enclosed by +.BR [ +and +.BR ] +brackets. +Variable assignments are assignment statements that assign values +to particular variables using the +.BR = +operator, as in +.BR Proto=Tcp . +The variables that can be assigned are the set of NFS specific +mount options listed in +.BR nfs (5) +together with the filesystem-independant mount options listed in +.BR mount (8) +and three additions: +.B Sloppy=True +has the same effect as the +.B -s +option to +.IR mount , +and +.B Foreground=True +and +.B Background=True +have the same effect as +.B bg +and +.BR fg . +.PP +Options in the config file may be given in upper, lower, or mixed case +and will be shifted to lower case before being passed to the filesystem. +.PP +Boolean mount options which do not need an equals sign must be given as +.RI \[dq] option =True". +Instead of preceeding such an option with +.RB \[dq] no \[dq] +its negation must be given as +.RI \[dq] option =False". +.PP +Sections are broken up into three basic categories: +Global options, Server options and Mount Point options. +.HP +.B [ NFSMount_Global_Options ] +- This statically named section +defines all of the global mount options that can be +applied to every NFS mount. +.HP +.B [ Server \[dq]Server_Name\[dq] ] +- This section defines all the mount options that should +be used on mounts to a particular NFS server. The +.I \[dq]Server_Name\[dq] +strings needs to be surrounded by '\[dq]' and be an exact match +(ignoring case) of the server name used in the +.B mount +command. +.HP +.B [ MountPoint \[dq]Mount_Point\[dq] ] +- This section defines all the mount options that +should be used on a particular mount point. +The +.I \[dq]Mount_Point\[dq] +string needs to be surrounded by '\[dq]' and be an +exact match of the mount point used in the +.BR mount +command. Though path names are usually case-sensitive, the Mount_Point +name is matched insensitive to case. +.PP +The sections are processed in the reverse of the order listed above, and +any options already seen, either in a previous section or on the +command line, will be ignored when seen again. +.SH EXAMPLES +.PP +These are some example lines of how sections and variables +are defined in the configuration file. +.PP +[ NFSMount_Global_Options ] +.br + Proto=Tcp +.RS +.PP +The TCP/IPv4 protocol will be used on every NFS mount. +.RE +.PP +[ Server \[dq]nfsserver.foo.com\[dq] ] +.br + rsize=32k +.br + wsize=32k +.br + proto=udp6 +.RS +.PP +A 32k (32768 bytes) block size will be used as the read and write +size on all mounts to the 'nfsserver.foo.com' server. UDP/IPv6 +is the protocol to be used. +.RE +.PP +[ MountPoint \[dq]/export/home\[dq] ] +.br + Background=True +.RS +.PP +All mounts to the '/export/home' export will be performed in +the background (i.e. done asynchronously). +.RE +.SH FILES +.TP 10n +.I /etc/nfsmount.conf +Default NFS mount configuration file +.TP 10n +.I /etc/nfsmount.conf.d +When this directory exists and files ending +with ".conf" exist, those files will be +used to set configuration variables. These +files will override variables set +in /etc/nfsmount.conf +.PD +.SH SEE ALSO +.BR nfs (5), +.BR mount (8), diff --git a/utils/mount/nfsumount.c b/utils/mount/nfsumount.c new file mode 100644 index 0000000..e16fb6a --- /dev/null +++ b/utils/mount/nfsumount.c @@ -0,0 +1,351 @@ +/* + * nfsumount.c -- Linux NFS umount + * Copyright (C) 2006 Amit Gud + * + * - Basic code and wrapper around NFS umount code originally + * in util-linux/mount/nfsmount.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xcommon.h" +#include "fstab.h" +#include "nls.h" + +#include "mount_constants.h" +#include "nfs_mount.h" +#include "mount.h" +#include "error.h" +#include "network.h" +#include "parse_opt.h" +#include "parse_dev.h" +#include "utils.h" + +#define MOUNTSFILE "/proc/mounts" +#define LINELEN (4096) + +#if !defined(MNT_FORCE) +/* dare not try to include -- lots of errors */ +#define MNT_FORCE 1 +#endif + +#if !defined(MNT_DETACH) +#define MNT_DETACH 2 +#endif + +extern char *progname; +extern int nomtab; +extern int verbose; +int force; +int lazy; +int remount; + + +static int try_remount(const char *spec, const char *node) +{ + int res; + + res = mount(spec, node, NULL, + MS_MGC_VAL | MS_REMOUNT | MS_RDONLY, NULL); + if (res == 0) { + struct mntent remnt; + nfs_error(_("%s: %s busy - remounted read-only"), + progname, spec); + remnt.mnt_type = remnt.mnt_fsname = NULL; + remnt.mnt_dir = xstrdup(node); + remnt.mnt_opts = xstrdup("ro"); + if (!nomtab) + update_mtab(node, &remnt); + } else if (errno != EBUSY) { /* hmm ... */ + perror(_("remount")); + nfs_error(_("%s: could not remount %s read-only"), + progname, spec); + } + return res; +} + +static int del_mtab(const char *spec, const char *node) +{ + int umnt_err, res; + + umnt_err = 0; + if (lazy) { + res = umount2 (node, MNT_DETACH); + if (res < 0) + umnt_err = errno; + goto writemtab; + } + + if (force) { + res = umount2 (node, MNT_FORCE); + if (res == -1) { + int errsv = errno; + perror(_("umount2")); + errno = errsv; + if (errno == ENOSYS) { + if (verbose) + printf(_("no umount2, trying umount...\n")); + res = umount (node); + } + } + } else + res = umount (node); + + if (res < 0) { + if (remount && errno == EBUSY && spec) { + res = try_remount(spec, node); + if (res) + goto writemtab; + return EX_SUCCESS; + } else + umnt_err = errno; + } + + if (res >= 0) { + /* Umount succeeded */ + if (verbose) + printf(_("%s umounted\n"), spec ? spec : node); + } + + writemtab: + if (!nomtab && + (umnt_err == 0 || umnt_err == EINVAL || umnt_err == ENOENT)) { + update_mtab(node, NULL); + } + + if (res >= 0) + return EX_SUCCESS; + + if (umnt_err) + umount_error(umnt_err, node); + return EX_FILEIO; +} + +/* + * Detect NFSv4 mounts. + * + * Consult /proc/mounts to determine if the mount point + * is an NFSv4 mount. The kernel is authoritative about + * what type of mount this is. + * + * Returns 1 if "mc" is an NFSv4 mount, zero if not, and + * -1 if some error occurred. + */ +static int nfs_umount_is_vers4(const struct mntentchn *mc) +{ + struct mntentchn *pmc; + struct mount_options *options; + int retval; + + retval = -1; + pmc = getprocmntdirbackward(mc->m.mnt_dir, NULL); + if (!pmc) + goto not_found; + + do { + size_t nlen = strlen(pmc->m.mnt_fsname); + + /* + * It's possible the mount location string in /proc/mounts + * ends with a '/'. In this case, if the entry came from + * /etc/mtab, it won't have the trailing '/' so deal with + * it. + */ + while (pmc->m.mnt_fsname[nlen - 1] == '/') + nlen--; + if (strncmp(pmc->m.mnt_fsname, mc->m.mnt_fsname, nlen) != 0) + continue; + + if (strcmp(pmc->m.mnt_type, "nfs4") == 0) + goto out_nfs4; + + options = po_split(pmc->m.mnt_opts); + if (options != NULL) { + struct nfs_version version; + int rc = nfs_nfs_version("nfs", options, &version); + po_destroy(options); + if (rc && version.major == 4) + goto out_nfs4; + } + + if (strcmp(pmc->m.mnt_type, "nfs") == 0) + goto out_nfs; + } while ((pmc = getprocmntdirbackward(mc->m.mnt_dir, pmc)) != NULL); + + if (retval == -1) { +not_found: + fprintf(stderr, "%s was not found in %s\n", + mc->m.mnt_dir, MOUNTSFILE); + goto out; + } + +out_nfs4: + if (verbose) + fprintf(stderr, "NFSv4 mount point detected\n"); + retval = 1; + goto out; + +out_nfs: + if (verbose) + fprintf(stderr, "Legacy NFS mount point detected\n"); + retval = 0; + +out: + return retval; +} + +static struct option umount_longopts[] = +{ + { "force", 0, 0, 'f' }, + { "help", 0, 0, 'h' }, + { "no-mtab", 0, 0, 'n' }, + { "verbose", 0, 0, 'v' }, + { "read-only", 0, 0, 'r' }, + { NULL, 0, 0, 0 } +}; + +int nfsumount(int argc, char *argv[]) +{ + int c, ret; + char *spec; + struct mntentchn *mc; + + if (argc < 2) { + umount_usage(); + return EX_USAGE; + } + + spec = argv[1]; + + argv += 1; + argc -= 1; + + argv[0] = argv[-1]; /* So that getopt error messages are correct */ + while ((c = getopt_long (argc, argv, "fvnrlh", + umount_longopts, NULL)) != -1) { + + switch (c) { + case 'f': + ++force; + break; + case 'v': + ++verbose; + break; + case 'n': + ++nomtab; + break; + case 'r': + ++remount; + break; + case 'l': + ++lazy; + break; + case 'h': + default: + umount_usage(); + return EX_USAGE; + } + } + if (optind != argc) { + umount_usage(); + return EX_USAGE; + } + + if (spec == NULL || (*spec != '/' && strchr(spec,':') == NULL)) { + nfs_error(_("%s: %s: not found\n"), progname, spec); + return EX_USAGE; + } + + if (*spec == '/') + mc = getmntdirbackward(spec, NULL); + else + mc = getmntdevbackward(spec, NULL); + if (!mc && verbose) + printf(_("Could not find %s in mtab\n"), spec); + + if (mc && strcmp(mc->m.mnt_type, "nfs") != 0 && + strcmp(mc->m.mnt_type, "nfs4") != 0) { + nfs_error(_("%s: %s on %s is not an NFS filesystem"), + progname, mc->m.mnt_fsname, mc->m.mnt_dir); + return EX_USAGE; + } + + if (getuid() != 0) { + /* only permitted if "user=" or "users" is in mount options */ + if (!mc) { + /* umount might call us twice. The second time there will + * be no entry in mtab and we should just exit quietly + */ + return EX_SUCCESS; + + only_root: + nfs_error(_("%s: You are not permitted to unmount %s"), + progname, spec); + return EX_USAGE; + } + if (hasmntopt(&mc->m, "users") == NULL) { + char *opt = hasmntopt(&mc->m, "user"); + struct passwd *pw; + char *comma; + size_t len; + if (!opt) + goto only_root; + if (opt[4] != '=') + goto only_root; + comma = strchr(opt, ','); + if (comma) + len = comma - (opt + 5); + else + len = strlen(opt+5); + pw = getpwuid(getuid()); + if (pw == NULL || strlen(pw->pw_name) != len + || strncmp(pw->pw_name, opt+5, len) != 0) + goto only_root; + } + } + + ret = EX_SUCCESS; + if (mc) { + if (!lazy) { + switch (nfs_umount_is_vers4(mc)) { + case 0: + /* We ignore the error from nfs_umount23. + * If the actual umount succeeds (in del_mtab), + * we don't want to signal an error, as that + * could cause /sbin/mount to retry! + */ + nfs_umount23(mc->m.mnt_fsname, mc->m.mnt_opts); + break; + case 1: + break; + default: + return EX_FAIL; + } + } + ret = del_mtab(mc->m.mnt_fsname, mc->m.mnt_dir); + } else if (*spec != '/') { + if (!lazy) + ret = nfs_umount23(spec, "tcp,v3"); + } else + ret = del_mtab(NULL, spec); + + return ret; +} diff --git a/utils/mount/parse_dev.c b/utils/mount/parse_dev.c new file mode 100644 index 0000000..2ade5d5 --- /dev/null +++ b/utils/mount/parse_dev.c @@ -0,0 +1,233 @@ +/* + * parse_dev.c -- parse device name into hostname and export path + * + * Copyright (C) 2008 Oracle. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "xcommon.h" +#include "nls.h" +#include "parse_dev.h" + +#ifndef NFS_MAXHOSTNAME +#define NFS_MAXHOSTNAME (255) +#endif + +#ifndef NFS_MAXPATHNAME +#define NFS_MAXPATHNAME (1024) +#endif + +extern char *progname; +extern int verbose; + +static int nfs_pdn_no_devname_err(void) +{ + nfs_error(_("%s: no device name was provided"), progname); + return 0; +} + +static int nfs_pdn_hostname_too_long_err(void) +{ + nfs_error(_("%s: server hostname is too long"), progname); + return 0; +} + +static int nfs_pdn_pathname_too_long_err(void) +{ + nfs_error(_("%s: export pathname is too long"), progname); + return 0; +} + +static int nfs_pdn_bad_format_err(void) +{ + nfs_error(_("%s: remote share not in 'host:dir' format"), progname); + return 0; +} + +static int nfs_pdn_nomem_err(void) +{ + nfs_error(_("%s: no memory available to parse devname"), progname); + return 0; +} + +static int nfs_pdn_missing_brace_err(void) +{ + nfs_error(_("%s: closing bracket missing from server address"), + progname); + return 0; +} + +/* + * Standard hostname:path format + */ +static int nfs_parse_simple_hostname(const char *dev, + char **hostname, char **pathname) +{ + size_t host_len, path_len; + char *colon, *comma; + + /* Must have a colon */ + colon = strchr(dev, ':'); + if (colon == NULL) + return nfs_pdn_bad_format_err(); + *colon = '\0'; + host_len = colon - dev; + + if (host_len > NFS_MAXHOSTNAME) + return nfs_pdn_hostname_too_long_err(); + + /* If there's a comma before the colon, take only the + * first name in list */ + comma = strchr(dev, ','); + if (comma != NULL) { + *comma = '\0'; + host_len = comma - dev; + nfs_error(_("%s: warning: multiple hostnames not supported"), + progname); + } else + + colon++; + path_len = strlen(colon); + if (path_len > NFS_MAXPATHNAME) + return nfs_pdn_pathname_too_long_err(); + + if (hostname) { + *hostname = strndup(dev, host_len); + if (*hostname == NULL) + return nfs_pdn_nomem_err(); + } + if (pathname) { + *pathname = strndup(colon, path_len); + if (*pathname == NULL) { + if (hostname) + free(*hostname); + return nfs_pdn_nomem_err(); + } + } + return 1; +} + +/* + * To handle raw IPv6 addresses (which contain colons), the + * server's address is enclosed in square brackets. Return + * what's between the brackets. + * + * There could be anything in between the brackets, but we'll + * let DNS resolution sort it out later. + */ +static int nfs_parse_square_bracket(const char *dev, + char **hostname, char **pathname) +{ + size_t host_len, path_len; + char *cbrace; + + dev++; + + /* Must have a closing square bracket */ + cbrace = strchr(dev, ']'); + if (cbrace == NULL) + return nfs_pdn_missing_brace_err(); + *cbrace = '\0'; + host_len = cbrace - dev; + + /* Must have a colon just after the closing bracket */ + cbrace++; + if (*cbrace != ':') + return nfs_pdn_bad_format_err(); + + if (host_len > NFS_MAXHOSTNAME) + return nfs_pdn_hostname_too_long_err(); + + cbrace++; + path_len = strlen(cbrace); + if (path_len > NFS_MAXPATHNAME) + return nfs_pdn_pathname_too_long_err(); + + if (hostname) { + *hostname = strndup(dev, host_len); + if (*hostname == NULL) + return nfs_pdn_nomem_err(); + } + if (pathname) { + *pathname = strndup(cbrace, path_len); + if (*pathname == NULL) { + if (hostname) + free(*hostname); + return nfs_pdn_nomem_err(); + } + } + return 1; +} + +/* + * RFC 2224 says an NFS client must grok "public file handles" to + * support NFS URLs. Linux doesn't do that yet. Print a somewhat + * helpful error message in this case instead of pressing forward + * with the mount request and failing with a cryptic error message + * later. + */ +static int nfs_parse_nfs_url(__attribute__((unused)) const char *dev, + __attribute__((unused)) char **hostname, + __attribute__((unused)) char **pathname) +{ + nfs_error(_("%s: NFS URLs are not supported"), progname); + return 0; +} + +/** + * nfs_parse_devname - Determine the server's hostname by looking at "devname". + * @devname: pointer to mounted device name (first argument of mount command) + * @hostname: OUT: pointer to server's hostname + * @pathname: OUT: pointer to export path on server + * + * Returns 1 if succesful, or zero if some error occurred. On success, + * @hostname and @pathname point to dynamically allocated buffers containing + * the hostname of the server and the export pathname (both '\0'-terminated). + * + * @hostname or @pathname may be NULL if caller doesn't want a copy of those + * parts of @devname. + * + * Note that this will not work if @devname is a wide-character string. + */ +int nfs_parse_devname(const char *devname, + char **hostname, char **pathname) +{ + char *dev; + int result; + + if (devname == NULL) + return nfs_pdn_no_devname_err(); + + /* Parser is destructive, so operate on a copy of the device name. */ + dev = strdup(devname); + if (dev == NULL) + return nfs_pdn_nomem_err(); + if (*dev == '[') + result = nfs_parse_square_bracket(dev, hostname, pathname); + else if (strncmp(dev, "nfs://", 6) == 0) + result = nfs_parse_nfs_url(dev, hostname, pathname); + else + result = nfs_parse_simple_hostname(dev, hostname, pathname); + + free(dev); + return result; +} diff --git a/utils/mount/parse_dev.h b/utils/mount/parse_dev.h new file mode 100644 index 0000000..f9857bc --- /dev/null +++ b/utils/mount/parse_dev.h @@ -0,0 +1,28 @@ +/* + * parse_dev.c -- parse device name into hostname and export path + * + * Copyright (C) 2008 Oracle. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA + * + */ + +#ifndef __NFS_UTILS_PARSE_DEV_HEADER +#define __NFS_UTILS_PARSE_DEV_HEADER + +extern int nfs_parse_devname(const char *, char **, char **); + +#endif /* __NFS_UTILS_PARSE_DEV */ diff --git a/utils/mount/parse_opt.c b/utils/mount/parse_opt.c new file mode 100644 index 0000000..d2d0b65 --- /dev/null +++ b/utils/mount/parse_opt.c @@ -0,0 +1,610 @@ +/* + * parse_opt.c -- mount option string parsing helpers + * + * Copyright (C) 2007 Oracle. All rights reserved. + * Copyright (C) 2007 Chuck Lever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA + * + */ + +/* + * Converting a C string containing mount options to a data object + * and manipulating that object is cleaner in C than manipulating + * the C string itself. This is similar to the way Python handles + * string manipulation. + * + * The current implementation uses a linked list as the data object + * since lists are simple, and we don't need to worry about more + * than ten or twenty options at a time. + * + * Hopefully the interface is abstract enough that the underlying + * data structure can be replaced if needed without changing the API. + */ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "parse_opt.h" +#include "token.h" + + +struct mount_option { + struct mount_option *next, *prev; + char *keyword; + char *value; +}; + +struct mount_options { + struct mount_option *head, *tail; + unsigned int count; +}; + +static struct mount_option *option_create(char *str) +{ + struct mount_option *option; + char *opteq; + + if (!str) + return NULL; + + option = malloc(sizeof(*option)); + if (!option) + return NULL; + + option->next = NULL; + option->prev = NULL; + + opteq = strchr(str, '='); + if (opteq) { + option->keyword = strndup(str, opteq - str); + if (!option->keyword) + goto fail; + option->value = strdup(opteq + 1); + if (!option->value) { + free(option->keyword); + goto fail; + } + } else { + option->keyword = strdup(str); + if (!option->keyword) + goto fail; + option->value = NULL; + } + + return option; + +fail: + free(option); + return NULL; +} + +static struct mount_option *option_dup(const struct mount_option *option) +{ + struct mount_option *new; + + new = malloc(sizeof(*new)); + if (!new) + return NULL; + + new->next = NULL; + new->prev = NULL; + + new->keyword = strdup(option->keyword); + if (!new->keyword) + goto fail; + + new->value = NULL; + if (option->value) { + new->value = strdup(option->value); + if (!new->value) { + free(new->keyword); + goto fail; + } + } + + return new; + +fail: + free(new); + return NULL; +} + +static void option_destroy(struct mount_option *option) +{ + free(option->keyword); + free(option->value); + free(option); +} + +static void options_init(struct mount_options *options) +{ + options->head = options->tail = NULL; + options->count = 0; +} + +static struct mount_options *options_create(void) +{ + struct mount_options *options; + + options = malloc(sizeof(*options)); + if (options) + options_init(options); + + return options; +} + +static int options_empty(struct mount_options *options) +{ + return options->count == 0; +} + +static void options_tail_insert(struct mount_options *options, + struct mount_option *option) +{ + struct mount_option *prev = options->tail; + + option->next = NULL; + option->prev = prev; + + if (prev) + prev->next = option; + else + options->head = option; + options->tail = option; + + options->count++; +} + +static void options_head_insert(struct mount_options *options, + struct mount_option *option) +{ + struct mount_option *ohead = options->head; + + option->prev = NULL; + option->next = ohead; + if (ohead) + ohead->prev = option; + else + options->tail = option; + options->head = option; + + options->count++; +} + +static void options_delete(struct mount_options *options, + struct mount_option *option) +{ + struct mount_option *prev = option->prev; + struct mount_option *next = option->next; + + if (!options_empty(options)) { + if (prev) + prev->next = option->next; + if (next) + next->prev = option->prev; + + if (options->head == option) + options->head = option->next; + if (options->tail == option) + options->tail = prev; + + options->count--; + + option_destroy(option); + } +} + + +/** + * po_destroy - deallocate a group of mount options + * @options: pointer to mount options to free + * + */ +void po_destroy(struct mount_options *options) +{ + if (options) { + while (!options_empty(options)) + options_delete(options, options->head); + free(options); + } +} + +/** + * po_split - split options string into group of options + * @options: pointer to C string containing zero or more comma-delimited options + * + * Convert our mount options string to a list to make it easier + * to adjust the options as we go. This is just an exercise in + * lexical parsing -- this function doesn't pay attention to the + * meaning of the options themselves. + * + * Returns a new group of mount options if successful; otherwise NULL + * is returned if some failure occurred. + */ +struct mount_options *po_split(char *str) +{ + struct mount_options *options; + struct tokenizer_state *tstate; + char *opt; + + if (!str) + return options_create(); + + options = options_create(); + if (options) { + tstate = init_tokenizer(str, ','); + for (opt = next_token(tstate); opt; opt = next_token(tstate)) { + struct mount_option *option = option_create(opt); + free(opt); + if (!option) + goto fail; + options_tail_insert(options, option); + } + if (tokenizer_error(tstate)) + goto fail; + end_tokenizer(tstate); + } + return options; + +fail: + end_tokenizer(tstate); + po_destroy(options); + return NULL; +} + +/** + * po_dup - duplicate an existing list of options + * @options: pointer to mount options + * + */ +struct mount_options *po_dup(struct mount_options *source) +{ + struct mount_options *target; + struct mount_option *current; + + if (!source) + return NULL; + + target = options_create(); + if (options_empty(source) || target == NULL) + return target; + + current = source->head; + while (target->count < source->count) { + struct mount_option *option; + + option = option_dup(current); + if (!option) { + po_destroy(target); + return NULL; + } + + options_tail_insert(target, option); + current = current->next; + } + + return target; +} + +/** + * po_replace - replace mount options in one mount_options object with another + * @target: pointer to previously instantiated object to replace + * @source: pointer to object containing source mount options + * + * Side effect: the object referred to by source is emptied. + */ +void po_replace(struct mount_options *target, struct mount_options *source) +{ + if (target) { + while (!options_empty(target)) + options_delete(target, target->head); + + if (source) { + target->head = source->head; + target->tail = source->tail; + target->count = source->count; + + options_init(source); + } + } +} + +/** + * po_join - recombine group of mount options into a C string + * @options: pointer to mount options to recombine + * @str: handle on string to replace (input and output) + * + * Convert our mount options object back into a string that the + * rest of the world can use. + * + * Upon return, @string contains the address of a replacement + * C string containing a comma-delimited list of mount options + * and values; or the passed-in string is freed and NULL is + * returned if some failure occurred. + */ +po_return_t po_join(struct mount_options *options, char **str) +{ + size_t len = 0; + struct mount_option *option; + + if (!str || !options) + return PO_FAILED; + + free(*str); + *str = NULL; + + if (options_empty(options)) { + *str = strdup(""); + return *str ? PO_SUCCEEDED : PO_FAILED; + } + + for (option = options->head; option; option = option->next) { + len += strlen(option->keyword); + if (option->value) + len +=strlen(option->value) + 1; /* equals sign */ + if (option->next) + len++; /* comma */ + } + + len++; /* NULL on the end */ + + *str = malloc(len); + if (!*str) + return PO_FAILED; + *str[0] = '\0'; + + for (option = options->head; option; option = option->next) { + strcat(*str, option->keyword); + if (option->value) { + strcat(*str, "="); + strcat(*str, option->value); + } + if (option->next) + strcat(*str, ","); + } + + return PO_SUCCEEDED; +} + +/** + * po_insert - insert an option into a group of options + * @options: pointer to mount options + * @option: pointer to a C string containing the option to add + * + */ +po_return_t po_insert(struct mount_options *options, char *str) +{ + struct mount_option *option = option_create(str); + + if (option) { + options_head_insert(options, option); + return PO_SUCCEEDED; + } + return PO_FAILED; +} + +/** + * po_append - concatenate an option onto a group of options + * @options: pointer to mount options + * @option: pointer to a C string containing the option to add + * + */ +po_return_t po_append(struct mount_options *options, char *str) +{ + struct mount_option *option = option_create(str); + + if (option) { + options_tail_insert(options, option); + return PO_SUCCEEDED; + } + return PO_FAILED; +} + +/** + * po_contains - check for presence of an option in a group + * @options: pointer to mount options + * @keyword: pointer to a C string containing option keyword for which to search + * + */ +po_found_t po_contains(struct mount_options *options, char *keyword) +{ + struct mount_option *option; + + if (options && keyword) { + for (option = options->head; option; option = option->next) + if (strcmp(option->keyword, keyword) == 0) + return PO_FOUND; + } + + return PO_NOT_FOUND; +} + +/** + * po_contains_prefix - check for presence of an option matching a prefix + * @options: pointer to mount options + * @prefix: pointer to prefix to match against a keyword + * @keyword: pointer to a C string containing the option keyword if found + * @n: number of instances to skip, so '0' returns the first. + * + * On success, *keyword contains the pointer of the matching option's keyword. + */ +po_found_t po_contains_prefix(struct mount_options *options, + const char *prefix, char **keyword, int n) +{ + struct mount_option *option; + + if (options && prefix) { + for (option = options->head; option; option = option->next) + if (strncmp(option->keyword, prefix, strlen(prefix)) == 0) { + if (n > 0) { + n -= 1; + } else { + if (keyword) + *keyword = option->keyword; + return PO_FOUND; + } + } + } + + return PO_NOT_FOUND; +} + +/** + * po_get - return the value of the rightmost instance of an option + * @options: pointer to mount options + * @keyword: pointer to a C string containing option keyword for which to search + * + * If multiple instances of the same option are present in a mount option + * list, the rightmost instance is always the effective one. + * + * Returns pointer to C string containing the value of the option. + * Returns NULL if the option isn't found, or if the option doesn't + * have a value. + */ +char *po_get(struct mount_options *options, char *keyword) +{ + struct mount_option *option; + + if (options && keyword) { + for (option = options->tail; option; option = option->prev) + if (strcmp(option->keyword, keyword) == 0) + return option->value; + } + + return NULL; +} + +/** + * po_get_numeric - return numeric value of rightmost instance of keyword option + * @options: pointer to mount options + * @keyword: pointer to a C string containing option keyword for which to search + * @value: OUT: set to the value of the keyword + * + * This is specifically for parsing keyword options that take only a numeric + * value. If multiple instances of the same option are present in a mount + * option list, the rightmost instance is always the effective one. + * + * Returns: + * * PO_FOUND if the keyword was found and the value is numeric; @value is + * set to the keyword's value + * * PO_NOT_FOUND if the keyword was not found + * * PO_BAD_VALUE if the keyword was found, but the value is not numeric + * + * These last two are separate in case the caller wants to warn about bad mount + * options instead of silently using a default. + */ +#ifdef HAVE_STRTOL +po_found_t po_get_numeric(struct mount_options *options, char *keyword, long *value) +{ + char *option, *endptr; + long tmp; + + option = po_get(options, keyword); + if (option == NULL) + return PO_NOT_FOUND; + + errno = 0; + tmp = strtol(option, &endptr, 10); + if (errno == 0 && endptr != option) { + *value = tmp; + return PO_FOUND; + } + return PO_BAD_VALUE; +} +#else /* HAVE_STRTOL */ +po_found_t po_get_numeric(struct mount_options *options, char *keyword, long *value) +{ + char *option; + + option = po_get(options, keyword); + if (option == NULL) + return PO_NOT_FOUND; + + *value = atoi(option); + return PO_FOUND; +} +#endif /* HAVE_STRTOL */ + +/** + * po_rightmost - determine the relative position of several options + * @options: pointer to mount options + * @keys: pointer to an array of C strings containing option keywords + * + * This function can be used to determine which of several similar + * options will be the one to take effect. + * + * The kernel parses the mount option string from left to right. + * If an option is specified more than once (for example, "intr" + * and "nointr", the rightmost option is the last to be parsed, + * and it therefore takes precedence over previous similar options. + * + * This can also distinguish among multiple synonymous options, such + * as "proto=," "udp" and "tcp." + * + * Returns the index into @keys of the option that is rightmost. + * If none of the options listed in @keys is present in @options, or + * if @options is NULL, returns -1. + */ +int po_rightmost(struct mount_options *options, const char *keys[]) +{ + struct mount_option *option; + int i; + + if (options) { + for (option = options->tail; option; option = option->prev) { + for (i = 0; keys[i] != NULL; i++) + if (strcmp(option->keyword, keys[i]) == 0) + return i; + } + } + + return -1; +} + +/** + * po_remove_all - remove instances of an option from a group + * @options: pointer to mount options + * @keyword: pointer to a C string containing an option keyword to remove + * + * Side-effect: the passed-in list is truncated on success. + */ +po_found_t po_remove_all(struct mount_options *options, char *keyword) +{ + struct mount_option *option, *next; + int found = PO_NOT_FOUND; + + if (options && keyword) { + for (option = options->head; option; option = next) { + next = option->next; + if (strcmp(option->keyword, keyword) == 0) { + options_delete(options, option); + found = PO_FOUND; + } + } + } + + return found; +} diff --git a/utils/mount/parse_opt.h b/utils/mount/parse_opt.h new file mode 100644 index 0000000..181e7bf --- /dev/null +++ b/utils/mount/parse_opt.h @@ -0,0 +1,60 @@ +/* + * parse_opt.h -- mount option string parsing helpers + * + * Copyright (C) 2007 Oracle. All rights reserved. + * Copyright (C) 2007 Chuck Lever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA + * + */ + +#ifndef _NFS_UTILS_PARSE_OPT_H +#define _NFS_UTILS_PARSE_OPT_H + +typedef enum { + PO_FAILED = 0, + PO_SUCCEEDED = 1, +} po_return_t; + +typedef enum { + PO_NOT_FOUND = 0, + PO_FOUND = 1, + PO_BAD_VALUE = 2, +} po_found_t; + +struct mount_options; + +struct mount_options * po_split(char *); +struct mount_options * po_dup(struct mount_options *); +void po_replace(struct mount_options *, + struct mount_options *); +po_return_t po_join(struct mount_options *, char **); + +po_return_t po_insert(struct mount_options *, char *); +po_return_t po_append(struct mount_options *, char *); +po_found_t po_contains(struct mount_options *, char *); +po_found_t po_contains_prefix(struct mount_options *options, + const char *prefix, char **keyword, + int n); +char * po_get(struct mount_options *, char *); +po_found_t po_get_numeric(struct mount_options *, + char *, long *); +int po_rightmost(struct mount_options *, + const char *keys[]); +po_found_t po_remove_all(struct mount_options *, char *); +void po_destroy(struct mount_options *); + +#endif /* _NFS_UTILS_PARSE_OPT_H */ diff --git a/utils/mount/stropts.c b/utils/mount/stropts.c new file mode 100644 index 0000000..dbdd11e --- /dev/null +++ b/utils/mount/stropts.c @@ -0,0 +1,1310 @@ +/* + * stropts.c -- NFS mount using C string to pass options to kernel + * + * Copyright (C) 2007 Oracle. All rights reserved. + * Copyright (C) 2007 Chuck Lever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "nfslib.h" +#include "sockaddr.h" +#include "xcommon.h" +#include "mount.h" +#include "nls.h" +#include "nfsrpc.h" +#include "mount_constants.h" +#include "stropts.h" +#include "error.h" +#include "network.h" +#include "parse_opt.h" +#include "version.h" +#include "parse_dev.h" +#include "conffile.h" +#include "misc.h" + +#ifndef NFS_PROGRAM +#define NFS_PROGRAM (100003) +#endif + +#ifndef NFS_PORT +#define NFS_PORT (2049) +#endif + +#ifndef NFS_MAXHOSTNAME +#define NFS_MAXHOSTNAME (255) +#endif + +#ifndef NFS_MAXPATHNAME +#define NFS_MAXPATHNAME (1024) +#endif + +#ifndef NFS_DEF_FG_TIMEOUT_MINUTES +#define NFS_DEF_FG_TIMEOUT_MINUTES (2u) +#endif + +#ifndef NFS_DEF_BG_TIMEOUT_MINUTES +#define NFS_DEF_BG_TIMEOUT_MINUTES (10000u) +#endif + +#ifndef NFS_DEFAULT_MAJOR +#define NFS_DEFAULT_MAJOR 4 +#endif +#ifndef NFS_DEFAULT_MINOR +#define NFS_DEFAULT_MINOR 2 +#endif + +extern int nfs_mount_data_version; +extern char *progname; +extern int verbose; +extern int sloppy; + +struct nfsmount_info { + const char *spec, /* server:/path */ + *node; /* mounted-on dir */ + char *type; /* "nfs" or "nfs4" */ + char *hostname; /* server's hostname */ + struct addrinfo *address; /* server's addresses */ + sa_family_t family; /* Address family */ + + struct mount_options *options; /* parsed mount options */ + char **extra_opts; /* string for /etc/mtab */ + + struct nfs_version version; /* NFS version */ + int flags, /* MS_ flags */ + fake, /* actually do the mount? */ + child; /* forked bg child? */ +}; + + +static void nfs_default_version(struct nfsmount_info *mi) +{ +#ifdef MOUNT_CONFIG + extern struct nfs_version config_default_vers; + /* + * Use the default value set in the config file when + * the version has not been explicitly set. + */ + if (config_default_vers.v_mode == V_PARSE_ERR) { + mi->version.v_mode = V_PARSE_ERR; + return; + } + + if (mi->version.v_mode == V_DEFAULT && + config_default_vers.v_mode != V_DEFAULT) { + mi->version.major = config_default_vers.major; + if (config_default_vers.v_mode == V_SPECIFIC) + mi->version.minor = config_default_vers.minor; + else + mi->version.minor = NFS_DEFAULT_MINOR; + return; + } + + if (mi->version.v_mode == V_GENERAL) { + if (config_default_vers.v_mode != V_DEFAULT && + mi->version.major == config_default_vers.major) { + if (config_default_vers.v_mode == V_SPECIFIC) + mi->version.minor = config_default_vers.minor; + else + mi->version.minor = NFS_DEFAULT_MINOR; + } else + mi->version.minor = NFS_DEFAULT_MINOR; + return; + } + +#endif /* MOUNT_CONFIG */ + mi->version.major = NFS_DEFAULT_MAJOR; + mi->version.minor = NFS_DEFAULT_MINOR; +} + +/* + * Obtain a retry timeout value based on the value of the "retry=" option. + * + * Returns a time_t timeout timestamp, in seconds. + */ +static time_t nfs_parse_retry_option(struct mount_options *options, + const time_t default_timeout) +{ + time_t timeout_minutes; + long tmp; + + timeout_minutes = default_timeout; + switch (po_get_numeric(options, "retry", &tmp)) { + case PO_NOT_FOUND: + break; + case PO_FOUND: + if (tmp >= 0) { + timeout_minutes = tmp; + break; + } + /*FALLTHROUGH*/ + case PO_BAD_VALUE: + if (verbose) + nfs_error(_("%s: invalid retry timeout was specified; " + "using default timeout"), progname); + break; + } + + return time(NULL) + (timeout_minutes * 60); +} + +/* + * Convert the passed-in sockaddr-style address to presentation + * format, then append an option of the form "keyword=address". + * + * Returns 1 if the option was appended successfully; otherwise zero. + */ +static int nfs_append_generic_address_option(const struct sockaddr *sap, + const socklen_t salen, + const char *keyword, + struct mount_options *options) +{ + char address[NI_MAXHOST]; + char new_option[512]; + int len; + + if (!nfs_present_sockaddr(sap, salen, address, sizeof(address))) + goto out_err; + + len = snprintf(new_option, sizeof(new_option), "%s=%s", + keyword, address); + if (len < 0 || (size_t)len >= sizeof(new_option)) + goto out_err; + + if (po_append(options, new_option) != PO_SUCCEEDED) + goto out_err; + + return 1; + +out_err: + nfs_error(_("%s: failed to construct %s option"), progname, keyword); + return 0; +} + +/* + * Append the 'addr=' option to the options string to pass a resolved + * server address to the kernel. After a successful mount, this address + * is also added to /etc/mtab for use when unmounting. + * + * If 'addr=' is already present, we strip it out. This prevents users + * from setting a bogus 'addr=' option themselves, and also allows bg + * retries to recompute the server's address, in case it has changed. + * + * Returns 1 if 'addr=' option appended successfully; + * otherwise zero. + */ +static int nfs_append_addr_option(const struct sockaddr *sap, + socklen_t salen, + struct mount_options *options) +{ + po_remove_all(options, "addr"); + return nfs_append_generic_address_option(sap, salen, "addr", options); +} + +/* + * Called to discover our address and append an appropriate 'clientaddr=' + * option to the options string. If the supplied 'clientaddr=' value does + * not match either IPV4/IPv6 any or a local address, then fail the mount. + * + * Returns 1 if 'clientaddr=' option created successfully or if + * 'clientaddr=' option is already present; otherwise zero. + */ +static int nfs_append_clientaddr_option(const struct sockaddr *sap, + socklen_t salen, + struct mount_options *options) +{ + union nfs_sockaddr address; + struct sockaddr *my_addr = &address.sa; + socklen_t my_len = sizeof(address); + + if (po_contains(options, "clientaddr") == PO_FOUND) { + char *addr = po_get(options, "clientaddr"); + union nfs_sockaddr nfs_address; + struct sockaddr *nfs_saddr = &nfs_address.sa; + socklen_t nfs_salen = sizeof(nfs_address); + + /* translate the input for clientaddr to nfs_sockaddr */ + if (!nfs_string_to_sockaddr(addr, nfs_saddr, &nfs_salen)) + return 0; + + /* check for IPV4_ANY and IPV6_ANY */ + if (nfs_is_inaddr_any(nfs_saddr)) + return 1; + + /* check if ip matches local network addresses */ + if (!nfs_addr_matches_localips(nfs_saddr)) + nfs_error(_("%s: [warning] supplied clientaddr=%s " + "does not match any existing network " + "addresses"), progname, addr); + return 1; + } + + nfs_callback_address(sap, salen, my_addr, &my_len); + + return nfs_append_generic_address_option(my_addr, my_len, + "clientaddr", options); +} + +/* + * Determine whether to append a 'mountaddr=' option. The option is needed if: + * + * 1. "mounthost=" was specified, or + * 2. The address families for proto= and mountproto= are different. + */ +static int nfs_fix_mounthost_option(struct mount_options *options, + const char *nfs_hostname) +{ + union nfs_sockaddr address; + struct sockaddr *sap = &address.sa; + socklen_t salen = sizeof(address); + sa_family_t nfs_family, mnt_family; + char *mounthost; + + if (!nfs_nfs_proto_family(options, &nfs_family)) + return 0; + if (!nfs_mount_proto_family(options, &mnt_family)) + return 0; + + mounthost = po_get(options, "mounthost"); + if (mounthost == NULL) { + if (nfs_family == mnt_family) + return 1; + mounthost = (char *)nfs_hostname; + } + + if (!nfs_lookup(mounthost, mnt_family, sap, &salen)) { + nfs_error(_("%s: unable to determine mount server's address"), + progname); + return 0; + } + + return nfs_append_generic_address_option(sap, salen, + "mountaddr", options); +} + +/* + * Returns zero if the "lock" option is in effect, but statd + * can't be started. Otherwise, returns 1. + */ +static const char *nfs_lock_opttbl[] = { + "nolock", + "lock", + NULL, +}; + +static int nfs_verify_lock_option(struct mount_options *options) +{ + if (po_rightmost(options, nfs_lock_opttbl) == 0) + return 1; + + if (!start_statd()) { + nfs_error(_("%s: rpc.statd is not running but is " + "required for remote locking."), progname); + nfs_error(_("%s: Either use '-o nolock' to keep " + "locks local, or start statd."), progname); + errno = EALREADY; /* Don't print further error message */ + return 0; + } + + return 1; +} + +static int nfs_insert_sloppy_option(struct mount_options *options) +{ + if (linux_version_code() < MAKE_VERSION(2, 6, 27)) + return 1; + + if (po_contains(options, "sloppy")) { + po_remove_all(options, "sloppy"); + sloppy++; + } + + if (sloppy) { + if (po_insert(options, "sloppy") == PO_FAILED) + return 0; + } + + return 1; +} + +static int nfs_set_version(struct nfsmount_info *mi) +{ + + if (!nfs_nfs_version(mi->type, mi->options, &mi->version)) + return 0; + + /* + * Before 2.6.32, the kernel NFS client didn't + * support "-t nfs vers=4" mounts, so NFS version + * 4 cannot be included when autonegotiating + * while running on those kernels. + */ + if (mi->version.v_mode == V_DEFAULT && + linux_version_code() <= MAKE_VERSION(2, 6, 31)) { + mi->version.major = 3; + mi->version.v_mode = V_SPECIFIC; + } + + /* + * If we still don't know, check for version-specific + * mount options. + */ + if (mi->version.v_mode == V_DEFAULT) { + if (po_contains(mi->options, "mounthost") || + po_contains(mi->options, "mountaddr") || + po_contains(mi->options, "mountvers") || + po_contains(mi->options, "mountproto")) { + mi->version.major = 3; + mi->version.v_mode = V_SPECIFIC; + } + } + + /* + * If enabled, see if the default version was + * set in the config file + */ + if (mi->version.v_mode != V_SPECIFIC) { + nfs_default_version(mi); + /* + * If the version was not specifically set, it will + * be set by autonegotiation later, so remove it now: + */ + po_remove_all(mi->options, "v4"); + po_remove_all(mi->options, "vers"); + po_remove_all(mi->options, "nfsvers"); + } + + if (mi->version.v_mode == V_PARSE_ERR) + return 0; + + return 1; +} + +/* + * Set up mandatory non-version specific NFS mount options. + * + * Returns 1 if successful; otherwise zero. + */ +static int nfs_validate_options(struct nfsmount_info *mi) +{ + /* For remount, ignore mi->spec: the kernel will. */ + if (!(mi->flags & MS_REMOUNT) && + !nfs_parse_devname(mi->spec, &mi->hostname, NULL)) + return 0; + + if (!nfs_nfs_proto_family(mi->options, &mi->family)) + return 0; + + /* + * A remount is not going to be able to change the server's address, + * nor should we try to resolve another address for the server as we + * may end up with a different address. + * A non-remount will set 'addr' from ->hostname + */ + po_remove_all(mi->options, "addr"); + + if (!nfs_set_version(mi)) + return 0; + + if (!nfs_insert_sloppy_option(mi->options)) + return 0; + + return 1; +} + +/* + * Get NFS/mnt server addresses from mount options + * + * Returns 1 and fills in @nfs_saddr, @nfs_salen, @mnt_saddr, and @mnt_salen + * if all goes well; otherwise zero. + */ +static int nfs_extract_server_addresses(struct mount_options *options, + struct sockaddr *nfs_saddr, + socklen_t *nfs_salen, + struct sockaddr *mnt_saddr, + socklen_t *mnt_salen) +{ + char *option; + + option = po_get(options, "addr"); + if (option == NULL) + return 0; + if (!nfs_string_to_sockaddr(option, nfs_saddr, nfs_salen)) + return 0; + + option = po_get(options, "mountaddr"); + if (option == NULL) { + memcpy(mnt_saddr, nfs_saddr, *nfs_salen); + *mnt_salen = *nfs_salen; + } else if (!nfs_string_to_sockaddr(option, mnt_saddr, mnt_salen)) + return 0; + + return 1; +} + +static int nfs_construct_new_options(struct mount_options *options, + struct sockaddr *nfs_saddr, + struct pmap *nfs_pmap, + struct sockaddr *mnt_saddr, + struct pmap *mnt_pmap) +{ + char new_option[64]; + char *netid; + + po_remove_all(options, "nfsprog"); + po_remove_all(options, "mountprog"); + + po_remove_all(options, "v2"); + po_remove_all(options, "v3"); + po_remove_all(options, "vers"); + po_remove_all(options, "nfsvers"); + snprintf(new_option, sizeof(new_option) - 1, + "vers=%lu", nfs_pmap->pm_vers); + if (po_append(options, new_option) == PO_FAILED) + return 0; + + po_remove_all(options, "proto"); + po_remove_all(options, "udp"); + po_remove_all(options, "tcp"); + netid = nfs_get_netid(nfs_saddr->sa_family, nfs_pmap->pm_prot); + if (netid == NULL) + return 0; + snprintf(new_option, sizeof(new_option) - 1, + "proto=%s", netid); + free(netid); + if (po_append(options, new_option) == PO_FAILED) + return 0; + + if(po_remove_all(options, "port") == PO_FOUND || + nfs_pmap->pm_port != NFS_PORT) { + snprintf(new_option, sizeof(new_option) - 1, + "port=%lu", nfs_pmap->pm_port); + if (po_append(options, new_option) == PO_FAILED) + return 0; + } + + po_remove_all(options, "mountvers"); + snprintf(new_option, sizeof(new_option) - 1, + "mountvers=%lu", mnt_pmap->pm_vers); + if (po_append(options, new_option) == PO_FAILED) + return 0; + + po_remove_all(options, "mountproto"); + netid = nfs_get_netid(mnt_saddr->sa_family, mnt_pmap->pm_prot); + if (netid == NULL) + return 0; + snprintf(new_option, sizeof(new_option) - 1, + "mountproto=%s", netid); + free(netid); + if (po_append(options, new_option) == PO_FAILED) + return 0; + + po_remove_all(options, "mountport"); + snprintf(new_option, sizeof(new_option) - 1, + "mountport=%lu", mnt_pmap->pm_port); + if (po_append(options, new_option) == PO_FAILED) + return 0; + + return 1; +} + +/* + * Reconstruct the mount option string based on a portmapper probe + * of the server. Returns one if the server's portmapper returned + * something we can use, otherwise zero. + * + * To handle version and transport protocol fallback properly, we + * need to parse some of the mount options in order to set up a + * portmap probe. Mount options that nfs_rewrite_pmap_mount_options() + * doesn't recognize are left alone. + * + * Returns TRUE if rewriting was successful; otherwise + * FALSE is returned if some failure occurred. + */ +static int +nfs_rewrite_pmap_mount_options(struct mount_options *options, int checkv4) +{ + union nfs_sockaddr nfs_address; + struct sockaddr *nfs_saddr = &nfs_address.sa; + socklen_t nfs_salen = sizeof(nfs_address); + struct pmap nfs_pmap; + union nfs_sockaddr mnt_address; + struct sockaddr *mnt_saddr = &mnt_address.sa; + socklen_t mnt_salen = sizeof(mnt_address); + unsigned long protocol; + struct pmap mnt_pmap; + + /* initialize structs */ + memset(&nfs_pmap, 0, sizeof(struct pmap)); + memset(&mnt_pmap, 0, sizeof(struct pmap)); + + /* + * Version and transport negotiation is not required + * and does not work for RDMA mounts. + */ + if (!nfs_nfs_protocol(options, &protocol)) { + errno = EINVAL; + return 0; + } + if (protocol == NFSPROTO_RDMA) + goto out; + + /* + * Extract just the options needed to contact server. + * Bail now if any of these have bad values. + */ + if (!nfs_extract_server_addresses(options, nfs_saddr, &nfs_salen, + mnt_saddr, &mnt_salen)) { + errno = EINVAL; + return 0; + } + if (!nfs_options2pmap(options, &nfs_pmap, &mnt_pmap)) { + errno = EINVAL; + return 0; + } + + /* + * The kernel NFS client doesn't support changing the RPC + * program number for these services, so force the value of + * these fields before probing the server's ports. + */ + nfs_pmap.pm_prog = NFS_PROGRAM; + mnt_pmap.pm_prog = MOUNTPROG; + + /* + * If the server's rpcbind service isn't available, we can't + * negotiate. Bail now if we can't contact it. + */ + if (!nfs_probe_bothports(mnt_saddr, mnt_salen, &mnt_pmap, + nfs_saddr, nfs_salen, &nfs_pmap, checkv4)) { + errno = ESPIPE; + if (rpc_createerr.cf_stat == RPC_PROGNOTREGISTERED) + errno = EOPNOTSUPP; + else if (rpc_createerr.cf_stat == RPC_AUTHERROR) + errno = EACCES; + else if (rpc_createerr.cf_stat == RPC_TIMEDOUT) + errno = ETIMEDOUT; + else if (rpc_createerr.cf_stat == RPC_PROGVERSMISMATCH) + errno = EPROTONOSUPPORT; + else if (rpc_createerr.cf_error.re_errno != 0) + errno = rpc_createerr.cf_error.re_errno; + return 0; + } + + if (!nfs_construct_new_options(options, nfs_saddr, &nfs_pmap, + mnt_saddr, &mnt_pmap)) { + if (rpc_createerr.cf_stat == RPC_UNKNOWNPROTO) + errno = EPROTONOSUPPORT; + else + errno = EINVAL; + return 0; + } + +out: + errno = 0; + return 1; +} + +/* + * Do the mount(2) system call. + * + * Returns TRUE if successful, otherwise FALSE. + * "errno" is set to reflect the individual error. + */ +static int nfs_sys_mount(struct nfsmount_info *mi, struct mount_options *opts) +{ + char *options = NULL; + int result; + + if (mi->fake) + return 1; + + if (po_join(opts, &options) == PO_FAILED) { + errno = EIO; + return 0; + } + + result = mount(mi->spec, mi->node, mi->type, + mi->flags & ~(MS_USER|MS_USERS), options); + free(options); + + if (verbose && result) { + int save = errno; + nfs_error(_("%s: mount(2): %s"), progname, strerror(save)); + errno = save; + } + return !result; +} + +static int nfs_do_mount_v3v2(struct nfsmount_info *mi, + struct sockaddr *sap, socklen_t salen, + int checkv4) +{ + struct mount_options *options = po_dup(mi->options); + int result = 0; + + if (!options) { + errno = ENOMEM; + return result; + } + errno = 0; + if (!nfs_append_addr_option(sap, salen, options)) { + if (errno == 0) + errno = EINVAL; + goto out_fail; + } + + if (!nfs_fix_mounthost_option(options, mi->hostname)) { + if (errno == 0) + errno = EINVAL; + goto out_fail; + } + if (!mi->fake && !nfs_verify_lock_option(options)) { + if (errno == 0) + errno = EINVAL; + goto out_fail; + } + + /* + * Options we negotiate below may be stale by the time this + * file system is unmounted. In order to force umount.nfs + * to renegotiate with the server, only write the user- + * specified options, and not negotiated options, to /etc/mtab. + */ + if (po_join(options, mi->extra_opts) == PO_FAILED) { + errno = ENOMEM; + goto out_fail; + } + + if (verbose) + printf(_("%s: trying text-based options '%s'\n"), + progname, *mi->extra_opts); + + if (!nfs_rewrite_pmap_mount_options(options, checkv4)) + goto out_fail; + + result = nfs_sys_mount(mi, options); + +out_fail: + po_destroy(options); + return result; +} + +/* + * Attempt a "-t nfs vers=2" or "-t nfs vers=3" mount. + * + * Returns TRUE if successful, otherwise FALSE. + * "errno" is set to reflect the individual error. + */ +static int nfs_try_mount_v3v2(struct nfsmount_info *mi, int checkv4) +{ + struct addrinfo *ai; + int ret = 0; + + for (ai = mi->address; ai != NULL; ai = ai->ai_next) { + ret = nfs_do_mount_v3v2(mi, ai->ai_addr, ai->ai_addrlen, checkv4); + if (ret != 0) + return ret; + + switch (errno) { + case ECONNREFUSED: + case EOPNOTSUPP: + case EHOSTUNREACH: + case ETIMEDOUT: + case EACCES: + continue; + default: + goto out; + } + } +out: + return ret; +} + +static int nfs_do_mount_v4(struct nfsmount_info *mi, + struct sockaddr *sap, socklen_t salen) +{ + struct mount_options *options = po_dup(mi->options); + int result = 0; + char version_opt[32]; + char *extra_opts = NULL; + + if (!options) { + errno = ENOMEM; + return result; + } + + if (po_contains(options, "mounthost") || + po_contains(options, "mountaddr") || + po_contains(options, "mountvers") || + po_contains(options, "mountport") || + po_contains(options, "mountproto")) { + /* + * Since these mountd options are set assume version 3 + * is wanted so error out with EPROTONOSUPPORT so the + * protocol negation starts with v3. + */ + if (verbose) { + printf(_("%s: Unsupported nfs4 mount option(s) passed '%s'\n"), + progname, *mi->extra_opts); + } + errno = EPROTONOSUPPORT; + goto out_fail; + } + + if (mi->version.v_mode != V_SPECIFIC) { + char *fmt; + switch (mi->version.minor) { + /* Old kernels don't support the new "vers=x.y" + * option, but do support old versions of NFS4. + * So use the format that is most widely understood. + */ + case 0: + fmt = "vers=%lu"; + break; + case 1: + fmt = "vers=%lu,minorversion=%lu"; + break; + default: + fmt = "vers=%lu.%lu"; + break; + } +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + snprintf(version_opt, sizeof(version_opt) - 1, + fmt, mi->version.major, + mi->version.minor); +#pragma GCC diagnostic warning "-Wformat-nonliteral" + + if (po_append(options, version_opt) == PO_FAILED) { + errno = EINVAL; + goto out_fail; + } + } else if (po_get(options, "minorversion") && + linux_version_code() > MAKE_VERSION(3, 4, 0)) { + /* + * convert minorversion= into vers=4.x + */ + po_remove_all(options, "minorversion"); + + snprintf(version_opt, sizeof(version_opt) - 1, + "vers=%lu.%lu", mi->version.major, + mi->version.minor); + + if (po_append(options, version_opt) == PO_FAILED) { + errno = EINVAL; + goto out_fail; + } + } + + if (!nfs_append_addr_option(sap, salen, options)) { + errno = EINVAL; + goto out_fail; + } + + if (!nfs_append_clientaddr_option(sap, salen, options)) { + errno = EINVAL; + goto out_fail; + } + + if (po_join(options, &extra_opts) == PO_FAILED) { + errno = ENOMEM; + goto out_fail; + } + + if (verbose) + printf(_("%s: trying text-based options '%s'\n"), + progname, extra_opts); + + result = nfs_sys_mount(mi, options); + + /* + * If success, update option string to be recorded in /etc/mtab. + */ + if (result) { + free(*mi->extra_opts); + *mi->extra_opts = extra_opts; + } else + free(extra_opts); + +out_fail: + po_destroy(options); + return result; +} + +/* + * Attempt a "-t nfs -o vers=4" or "-t nfs4" mount. + * + * Returns TRUE if successful, otherwise FALSE. + * "errno" is set to reflect the individual error. + */ +static int nfs_try_mount_v4(struct nfsmount_info *mi) +{ + struct addrinfo *ai; + int ret = 0; + + for (ai = mi->address; ai != NULL; ai = ai->ai_next) { + ret = nfs_do_mount_v4(mi, ai->ai_addr, ai->ai_addrlen); + if (ret != 0) + return ret; + + switch (errno) { + case ECONNREFUSED: + case EHOSTUNREACH: + case ETIMEDOUT: + case EACCES: + continue; + default: + goto out; + } + } +out: + return ret; +} + +/* + * Handle NFS version and transport protocol + * autonegotiation. + * + * When no version or protocol is specified on the + * command line, mount.nfs negotiates with the server + * to determine appropriate settings for the new + * mount point. + * + * Returns TRUE if successful, otherwise FALSE. + * "errno" is set to reflect the individual error. + */ +static int nfs_autonegotiate(struct nfsmount_info *mi) +{ + int result, olderrno; + + result = nfs_try_mount_v4(mi); +check_result: + if (result) + return result; + + switch (errno) { + case EPROTONOSUPPORT: + /* A clear indication that the server or our + * client does not support NFS version 4 and minor */ + case EINVAL: + /* A less clear indication that our client + * does not support NFSv4 minor version. */ + case EACCES: + /* An unclear indication that the server + * may not support NFSv4 minor version. */ + if (mi->version.v_mode != V_SPECIFIC) { + if (mi->version.minor > 0) { + mi->version.minor--; + result = nfs_try_mount_v4(mi); + goto check_result; + } + } + + goto fall_back; + case ENOENT: + /* Legacy Linux servers don't export an NFS + * version 4 pseudoroot. */ + goto fall_back; + case EPERM: + /* Linux servers prior to 2.6.25 may return + * EPERM when NFS version 4 is not supported. */ + goto fall_back; + case ECONNREFUSED: + /* UDP-Only servers won't support v4, but maybe it + * just isn't ready yet. So try v3, but double-check + * with rpcbind for v4. */ + if (mi->version.v_mode == V_GENERAL) + /* Mustn't try v2,v3 */ + return result; + result = nfs_try_mount_v3v2(mi, TRUE); + if (result == 0 && errno == EAGAIN) { + /* v4 server seems to be registered now. */ + result = nfs_try_mount_v4(mi); + if (result == 0 && errno != ECONNREFUSED) + goto check_result; + } else if (result == 0) + /* Restore original errno with v3 failures */ + errno = ECONNREFUSED; + + return result; + default: + return result; + } + +fall_back: + if (mi->version.v_mode == V_GENERAL) + /* v2,3 fallback not allowed */ + return result; + + /* + * Save the original errno in case the v3 + * mount fails from one of the fall_back cases. + * Report the first failure not the v3 mount failure + */ + olderrno = errno; + if ((result = nfs_try_mount_v3v2(mi, FALSE))) + return result; + + if (errno != EBUSY && errno != EACCES) + errno = olderrno; + + return result; +} + +/* + * This is a single pass through the fg/bg loop. + * + * Returns TRUE if successful, otherwise FALSE. + * "errno" is set to reflect the individual error. + */ +static int nfs_try_mount(struct nfsmount_info *mi) +{ + int result = 0; + + if (mi->address == NULL) { + struct addrinfo hint = { + .ai_protocol = (int)IPPROTO_UDP, + }; + int error; + struct addrinfo *address; + + hint.ai_family = (int)mi->family; + error = getaddrinfo(mi->hostname, NULL, &hint, &address); + if (error != 0) { + if (error == EAI_AGAIN) + errno = EAGAIN; + else { + nfs_error(_("%s: Failed to resolve server %s: %s"), + progname, mi->hostname, gai_strerror(error)); + errno = EALREADY; + } + return 0; + } + + if (!nfs_append_addr_option(address->ai_addr, + address->ai_addrlen, mi->options)) { + nfs_freeaddrinfo(address); + errno = ENOMEM; + return 0; + } + mi->address = address; + } + + switch (mi->version.major) { + case 3: + result = nfs_try_mount_v3v2(mi, FALSE); + break; + case 4: + if (mi->version.v_mode != V_SPECIFIC) + result = nfs_autonegotiate(mi); + else + result = nfs_try_mount_v4(mi); + break; + default: + errno = EIO; + } + + return result; +} + +/* + * Distinguish between permanent and temporary errors. + * + * Basically, we retry if communication with the server has + * failed so far, but fail immediately if there is a local + * error (like a bad mount option). + * + * If there is a remote error, like ESTALE or RPC_PROGNOTREGISTERED + * then it is probably permanent, but there is a small chance + * the it is temporary can we caught the server at an awkward + * time during start-up. So require that we see three of those + * before treating them as permanent. + * For ECONNREFUSED, wait a bit longer as there is often a longer + * gap between the network being ready and the NFS server starting. + * + * Returns 1 if we should fail immediately, or 0 if we + * should retry. + */ +static int nfs_is_permanent_error(int error) +{ + static int prev_error; + static int rpt_cnt; + + if (error == prev_error) + rpt_cnt += 1; + else + rpt_cnt = 1; + prev_error = error; + + switch (error) { + case ESTALE: + case EOPNOTSUPP: /* aka RPC_PROGNOTREGISTERED */ + /* If two in a row, assume permanent */ + return rpt_cnt >= 3; + case ECONNREFUSED: + /* Like the above, this can be temporary during a + * small window. However it is typically a larger + * window than for the others, and we have historically + * treated this as a temporary (i.e. long timeout) + * error with no complaints, so continue to treat + * it as temporary. + */ + return 0; /* temporary */ + case ETIMEDOUT: + case EHOSTUNREACH: + case EAGAIN: + return 0; /* temporary */ + default: + return 1; /* permanent */ + } +} + +/* + * Handle "foreground" NFS mounts. + * + * Retry the mount request for as long as the 'retry=' option says. + * + * Returns a valid mount command exit code. + */ +static int nfsmount_fg(struct nfsmount_info *mi) +{ + unsigned int secs = 1; + time_t timeout; + + timeout = nfs_parse_retry_option(mi->options, + NFS_DEF_FG_TIMEOUT_MINUTES); + if (verbose) + printf(_("%s: timeout set for %s"), + progname, ctime(&timeout)); + + for (;;) { + if (nfs_try_mount(mi)) + return EX_SUCCESS; + + if (errno == EBUSY && is_mountpoint(mi->node)) { + /* + * EBUSY can happen when mounting a filesystem that + * is already mounted or when the context= are + * different when using the -o sharecache + * + * Only error out in the latter case. + */ + return EX_SUCCESS; + } + + if (nfs_is_permanent_error(errno)) + break; + + if (time(NULL) > timeout) + break; + + if (errno != ETIMEDOUT) { + if (sleep(secs)) + break; + secs <<= 1; + if (secs > 10) + secs = 10; + } + } + + mount_error(mi->spec, mi->node, errno); + return EX_FAIL; +} + +/* + * Handle "background" NFS mount [first try] + * + * Returns a valid mount command exit code. + * + * EX_BG should cause the caller to fork and invoke nfsmount_child. + */ +static int nfsmount_parent(struct nfsmount_info *mi) +{ + if (nfs_try_mount(mi)) + return EX_SUCCESS; + + if (nfs_is_permanent_error(errno)) { + mount_error(mi->spec, mi->node, errno); + return EX_FAIL; + } + + sys_mount_errors(mi->hostname, errno, 1, 1); + return EX_BG; +} + +/* + * Handle "background" NFS mount [retry daemon] + * + * Returns a valid mount command exit code: EX_SUCCESS if successful, + * EX_FAIL if a failure occurred. There's nothing to catch the + * error return, though, so we use sys_mount_errors to log the + * failure. + */ +static int nfsmount_child(struct nfsmount_info *mi) +{ + unsigned int secs = 1; + time_t timeout; + + timeout = nfs_parse_retry_option(mi->options, + NFS_DEF_BG_TIMEOUT_MINUTES); + + for (;;) { + if (sleep(secs)) + break; + secs <<= 1; + if (secs > 120) + secs = 120; + + if (nfs_try_mount(mi)) + return EX_SUCCESS; + + if (nfs_is_permanent_error(errno)) + break; + + if (time(NULL) > timeout) + break; + + sys_mount_errors(mi->hostname, errno, 1, 1); + }; + + sys_mount_errors(mi->hostname, errno, 1, 0); + return EX_FAIL; +} + +/* + * Handle "background" NFS mount + * + * Returns a valid mount command exit code. + */ +static int nfsmount_bg(struct nfsmount_info *mi) +{ + if (!mi->child) + return nfsmount_parent(mi); + else + return nfsmount_child(mi); +} + +/* + * Usually all that is needed for an NFS remount is to change + * generic mount options like "sync" or "ro". These generic + * options are controlled by mi->flags, not by text-based + * options, and no contact with the server is needed. + * + * Take care with the /etc/mtab entry for this mount; just + * calling update_mtab() will change an "-t nfs -o vers=4" + * mount to an "-t nfs -o remount" mount, and that will + * confuse umount.nfs. + * + * Returns a valid mount command exit code. + */ +static int nfs_remount(struct nfsmount_info *mi) +{ + if (nfs_sys_mount(mi, mi->options)) + return EX_SUCCESS; + mount_error(mi->spec, mi->node, errno); + return EX_FAIL; +} + +/* + * Process mount options and try a mount system call. + * + * Returns a valid mount command exit code. + */ +static const char *nfs_background_opttbl[] = { + "bg", + "fg", + NULL, +}; + +static int nfsmount_start(struct nfsmount_info *mi) +{ + if (!nfs_validate_options(mi)) + return EX_FAIL; + + /* + * NFS v2 has been deprecated + */ + if (mi->version.major == 2) { + mount_error(mi->spec, mi->node, EOPNOTSUPP); + return EX_FAIL; + } + + /* + * Avoid retry and negotiation logic when remounting + */ + if (mi->flags & MS_REMOUNT) + return nfs_remount(mi); + + if (po_rightmost(mi->options, nfs_background_opttbl) == 0) + return nfsmount_bg(mi); + else + return nfsmount_fg(mi); +} + +/** + * nfsmount_string - Mount an NFS file system using C string options + * @spec: C string specifying remote share to mount ("hostname:path") + * @node: C string pathname of local mounted-on directory + * @type: C string that represents file system type ("nfs" or "nfs4") + * @flags: MS_ style mount flags + * @extra_opts: pointer to C string containing fs-specific mount options + * (input and output argument) + * @fake: flag indicating whether to carry out the whole operation + * @child: one if this is a mount daemon (bg) + * + * Returns a valid mount command exit code. + */ +int nfsmount_string(const char *spec, const char *node, char *type, + int flags, char **extra_opts, int fake, int child) +{ + struct nfsmount_info mi = { + .spec = spec, + .node = node, + .address = NULL, + .type = type, + .extra_opts = extra_opts, + .flags = flags, + .fake = fake, + .child = child, + }; + int retval = EX_FAIL; + + mi.options = po_split(*extra_opts); + if (mi.options) { + retval = nfsmount_start(&mi); + po_destroy(mi.options); + } else + nfs_error(_("%s: internal option parsing error"), progname); + + nfs_freeaddrinfo(mi.address); + free(mi.hostname); + return retval; +} diff --git a/utils/mount/stropts.h b/utils/mount/stropts.h new file mode 100644 index 0000000..6acd2ac --- /dev/null +++ b/utils/mount/stropts.h @@ -0,0 +1,30 @@ +/* + * stropts.h -- Provide common network functions for NFS mount/umount + * + * Copyright (C) 2007 Oracle. All rights reserved. + * Copyright (C) 2007 Chuck Lever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA + * + */ + +#ifndef _NFS_UTILS_MOUNT_STROPTS_H +#define _NFS_UTILS_MOUNT_STROPTS_H + +int nfsmount_string(const char *, const char *, char *, int, + char **, int, int); + +#endif /* _NFS_UTILS_MOUNT_STROPTS_H */ diff --git a/utils/mount/token.c b/utils/mount/token.c new file mode 100644 index 0000000..d7e2f4a --- /dev/null +++ b/utils/mount/token.c @@ -0,0 +1,157 @@ +/* + * token.c -- tokenize strings, a la strtok(3) + * + * Copyright (C) 2007 Oracle. All rights reserved. + * Copyright (C) 2007 Chuck Lever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA + * + */ + +/* + * We've constructed a simple string tokenizer that is better than + * strtok(3) in several ways: + * + * 1. It doesn't interfere with ongoing tokenizations using strtok(3). + * 2. It's re-entrant so we can nest tokenizations, if needed. + * 3. It can handle double-quoted delimiters (needed for 'context="sd,fslj"'). + * 4. It doesn't alter the string we're tokenizing, so it can work + * on write-protected strings as well as writable strings. + */ + + +#include +#include +#include +#include +#include +#include + +#include "token.h" + + +struct tokenizer_state { + char *pos; + char delimiter; + int error; +}; + +static void find_next_nondelimiter(struct tokenizer_state *tstate) +{ + while (*tstate->pos != '\0' && *tstate->pos == tstate->delimiter) + tstate->pos++; +} + +static size_t find_next_delimiter(struct tokenizer_state *tstate) +{ + size_t len = 0; + int quote_seen = 0; + + while (*tstate->pos != '\0') { + if (*tstate->pos == '"') + quote_seen ^= 1; + + if (!quote_seen && *tstate->pos == tstate->delimiter) + break; + + len++; + tstate->pos++; + } + + /* did the string terminate before the close quote? */ + if (quote_seen) { + tstate->error = EINVAL; + return 0; + } + + return len; +} + +/** + * next_token - find the next token in a string and return it + * @tstate: pointer to tokenizer context object + * + * Returns the next token found in the current string. + * Returns NULL if there are no more tokens in the string, + * or if an error occurs. + * + * Side effect: tstate is updated + */ +char *next_token(struct tokenizer_state *tstate) +{ + char *token; + size_t len; + + if (!tstate || !tstate->pos || tstate->error) + return NULL; + + find_next_nondelimiter(tstate); + if (*tstate->pos == '\0') + goto fail; + token = tstate->pos; + + len = find_next_delimiter(tstate); + if (len) { + token = strndup(token, len); + if (token) + return token; + tstate->error = ENOMEM; + } + +fail: + tstate->pos = NULL; + return NULL; /* no tokens found in this string */ +} + +/** + * init_tokenizer - return an initialized tokenizer context object + * @string: pointer to C string + * @delimiter: single character that delimits tokens in @string + * + * Returns an initialized tokenizer context object + */ +struct tokenizer_state *init_tokenizer(char *string, char delimiter) +{ + struct tokenizer_state *tstate; + + tstate = malloc(sizeof(*tstate)); + if (tstate) { + tstate->pos = string; + tstate->delimiter = delimiter; + tstate->error = 0; + } + return tstate; +} + +/** + * tokenizer_error - digs error value out of tokenizer context + * @tstate: pointer to tokenizer context object + * + */ +int tokenizer_error(struct tokenizer_state *tstate) +{ + return tstate ? tstate->error : 0; +} + +/** + * end_tokenizer - free a tokenizer context object + * @tstate: pointer to tokenizer context object + * + */ +void end_tokenizer(struct tokenizer_state *tstate) +{ + free(tstate); +} diff --git a/utils/mount/token.h b/utils/mount/token.h new file mode 100644 index 0000000..17a9c15 --- /dev/null +++ b/utils/mount/token.h @@ -0,0 +1,34 @@ +/* + * token.h -- tokenize strings, a la strtok(3) + * + * Copyright (C) 2007 Oracle. All rights reserved. + * Copyright (C) 2007 Chuck Lever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA + * + */ + +#ifndef _NFS_UTILS_MOUNT_TOKEN_H +#define _NFS_UTILS_MOUNT_TOKEN_H + +struct tokenizer_state; + +char *next_token(struct tokenizer_state *); +struct tokenizer_state *init_tokenizer(char *, char); +int tokenizer_error(struct tokenizer_state *); +void end_tokenizer(struct tokenizer_state *); + +#endif /* _NFS_UTILS_MOUNT_TOKEN_H */ diff --git a/utils/mount/umount.nfs.man b/utils/mount/umount.nfs.man new file mode 100644 index 0000000..15addfa --- /dev/null +++ b/utils/mount/umount.nfs.man @@ -0,0 +1,70 @@ +.\"@(#)umount.nfs.8" +.TH UMOUNT.NFS 8 "6 Jun 2006" +.SH NAME +umount.nfs, umount.nfs4 \- unmount a Network File System +.SH SYNOPSIS +.BI "umount.nfs" " dir" " [\-fvnrlh ]" +.SH DESCRIPTION +.BR umount.nfs +and +.BR umount.nfs4 +are a part of +.BR nfs (5) +utilities package, which provides NFS client functionality. + +.BR umount.nfs4 +and +.BR umount.nfs +are meant to be used by the +.BR umount (8) +command for unmounting NFS shares. This subcommand, however, can also be used as a standalone command with limited functionality. + +.I dir +is the directory on which the file system is mounted. + +.SH OPTIONS +.TP +.BI "\-f" +Force unmount the file system in case of unreachable NFS system. +.TP +.BI "\-v" +Be verbose. +.TP +.BI "\-n" +Do not update +.I /etc/mtab. +By default, an entry is created in +.I /etc/mtab +for every mounted file system. Use this option to skip deleting an entry. +.TP +.BI "\-r" +In case unmounting fails, try to mount read-only. +.TP +.BI "\-l" +Lazy unmount. Detach the file system from the file system hierarchy now, and cleanup all references to the file system as soon as it is not busy anymore. +.TP +.BI "\-h" +Print help message. + +.SH NOTE +For further information please refer +.BR nfs (5) +and +.BR umount (8) +manual pages. + +.SH FILES +.TP 18n +.I /etc/fstab +file system table +.TP +.I /etc/mtab +table of mounted file systems + +.PD +.SH "SEE ALSO" +.BR nfs (5), +.BR umount (8), + +.SH "AUTHOR" +Amit Gud diff --git a/utils/mount/utils.c b/utils/mount/utils.c new file mode 100644 index 0000000..865a4a0 --- /dev/null +++ b/utils/mount/utils.c @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2010 Karel Zak + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "sockaddr.h" +#include "nfs_mount.h" +#include "nls.h" +#include "xcommon.h" +#include "version.h" +#include "error.h" +#include "utils.h" +#include "mount.h" +#include "network.h" +#include "parse_dev.h" + +extern int verbose; +extern char *progname; + +/* + * Choose the version of the nfs_mount_data structure that is appropriate + * for the kernel that is doing the mount. + * + * NFS_MOUNT_VERSION: maximum version supported by these sources + * nfs_mount_data_version: maximum version supported by the running kernel + */ +int discover_nfs_mount_data_version(int *string_ver) +{ + unsigned int kernel_version = linux_version_code(); + int ver = 0; + + *string_ver = 0; + + if (kernel_version) { + if (kernel_version < MAKE_VERSION(2, 1, 32)) + ver = 1; + else if (kernel_version < MAKE_VERSION(2, 2, 18)) + ver = 3; + else if (kernel_version < MAKE_VERSION(2, 3, 0)) + ver = 4; + else if (kernel_version < MAKE_VERSION(2, 3, 99)) + ver = 3; + else if (kernel_version < MAKE_VERSION(2, 6, 3)) + ver = 4; + else + ver = 6; + } + if (ver > NFS_MOUNT_VERSION) + ver = NFS_MOUNT_VERSION; + else + if (kernel_version > MAKE_VERSION(2, 6, 22)) + (*string_ver)++; + + return ver; +} + +void print_one(char *spec, char *node, char *type, char *opts) +{ + if (!verbose) + return; + + if (opts) + printf(_("%s on %s type %s (%s)\n"), spec, node, type, opts); + else + printf(_("%s on %s type %s\n"), spec, node, type); +} + +void mount_usage(void) +{ + printf(_("usage: %s remotetarget dir [-rvVwfnsh] [-o nfsoptions]\n"), + progname); + printf(_("options:\n")); + printf(_("\t-r\t\tMount file system readonly\n")); + printf(_("\t-v\t\tVerbose\n")); + printf(_("\t-V\t\tPrint version\n")); + printf(_("\t-w\t\tMount file system read-write\n")); + printf(_("\t-f\t\tFake mount, do not actually mount\n")); + printf(_("\t-n\t\tDo not update /etc/mtab\n")); + printf(_("\t-s\t\tTolerate sloppy mount options rather than fail\n")); + printf(_("\t-h\t\tPrint this help\n")); + printf(_("\tnfsoptions\tRefer to mount.nfs(8) or nfs(5)\n\n")); +} + +void umount_usage(void) +{ + printf(_("usage: %s dir [-fvnrlh]\n"), progname); + printf(_("options:\n\t-f\tforce unmount\n")); + printf(_("\t-v\tverbose\n")); + printf(_("\t-n\tDo not update /etc/mtab\n")); + printf(_("\t-r\tremount\n")); + printf(_("\t-l\tlazy unmount\n")); + printf(_("\t-h\tprint this help\n\n")); +} + +int chk_mountpoint(const char *mount_point) +{ + struct stat sb; + + if (stat(mount_point, &sb) < 0){ + mount_error(NULL, mount_point, errno); + return 1; + } + if (S_ISDIR(sb.st_mode) == 0){ + mount_error(NULL, mount_point, ENOTDIR); + return 1; + } + if (getuid() != 0 && geteuid() != 0 && access(mount_point, X_OK) < 0) { + mount_error(NULL, mount_point, errno); + return 1; + } + + return 0; +} + +/* + * Pick up certain mount options used during the original mount + * from /etc/mtab. The basics include the server's IP address and + * the server pathname of the share to unregister. + * + * These options might also describe the mount port, mount protocol + * version, and transport protocol used to punch through a firewall. + * We will need this information to get through the firewall again + * to do the umount. + * + * Note that option parsing failures won't necessarily cause the + * umount request to fail. Those values will be left zero in the + * pmap tuple. If the GETPORT call later fails to disambiguate them, + * then we fail. + */ +int nfs_umount23(const char *devname, char *string) +{ + char *hostname = NULL, *dirname = NULL; + struct mount_options *options; + int result = EX_FAIL; + + if (!nfs_parse_devname(devname, &hostname, &dirname)) + return EX_USAGE; + + options = po_split(string); + if (options) { + result = nfs_umount_do_umnt(options, &hostname, &dirname); + po_destroy(options); + } else + nfs_error(_("%s: option parsing error"), progname); + + free(hostname); + free(dirname); + return result; +} diff --git a/utils/mount/utils.h b/utils/mount/utils.h new file mode 100644 index 0000000..224918a --- /dev/null +++ b/utils/mount/utils.h @@ -0,0 +1,36 @@ +/* + * utils.h -- misc utils for mount and umount + * + * Copyright (C) 2010 Karel Zak + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA + * + */ + +#ifndef _NFS_UTILS_MOUNT_UTILS_H +#define _NFS_UTILS_MOUNT_UTILS_H + +#include "parse_opt.h" + +int discover_nfs_mount_data_version(int *string_ver); +void print_one(char *spec, char *node, char *type, char *opts); +void mount_usage(void); +void umount_usage(void); +int chk_mountpoint(const char *mount_point); + +int nfs_umount23(const char *devname, char *string); + +#endif /* !_NFS_UTILS_MOUNT_UTILS_H */ diff --git a/utils/mount/version.h b/utils/mount/version.h new file mode 100644 index 0000000..d7cf680 --- /dev/null +++ b/utils/mount/version.h @@ -0,0 +1,53 @@ +/* + * version.h -- get running kernel version + * + * Copyright (C) 2008 Oracle. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA + * + */ + +#ifndef _NFS_UTILS_MOUNT_VERSION_H +#define _NFS_UTILS_MOUNT_VERSION_H + +#include +#include + +#include + +static inline unsigned int MAKE_VERSION(unsigned int p, unsigned int q, + unsigned int r) +{ + return (65536 * p) + (256 * q) + r; +} + +static inline unsigned int linux_version_code(void) +{ + struct utsname my_utsname; + unsigned int p, q = 0, r = 0; + + /* UINT_MAX as backward compatibility code should not be run */ + if (uname(&my_utsname)) + return UINT_MAX; + + /* UINT_MAX as future versions might not start with an integer */ + if (sscanf(my_utsname.release, "%u.%u.%u", &p, &q, &r) < 1) + return UINT_MAX; + + return MAKE_VERSION(p, q, r); +} + +#endif /* _NFS_UTILS_MOUNT_VERSION_H */ -- cgit v1.2.3