diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 06:03:02 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 06:03:02 +0000 |
commit | 4897093455a2bf08f3db3a1132cc2f6f5484d77c (patch) | |
tree | 9e6373544263f003139431fb4b08f9766e1ed530 /support/export | |
parent | Initial commit. (diff) | |
download | nfs-utils-4897093455a2bf08f3db3a1132cc2f6f5484d77c.tar.xz nfs-utils-4897093455a2bf08f3db3a1132cc2f6f5484d77c.zip |
Adding upstream version 1:2.6.4.upstream/1%2.6.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'support/export')
-rw-r--r-- | support/export/Makefile.am | 52 | ||||
-rw-r--r-- | support/export/Makefile.in | 934 | ||||
-rw-r--r-- | support/export/auth.c | 320 | ||||
-rw-r--r-- | support/export/cache.c | 1934 | ||||
-rw-r--r-- | support/export/client.c | 800 | ||||
-rw-r--r-- | support/export/export.c | 471 | ||||
-rw-r--r-- | support/export/export.h | 44 | ||||
-rw-r--r-- | support/export/fsloc.c | 203 | ||||
-rw-r--r-- | support/export/hostname.c | 366 | ||||
-rw-r--r-- | support/export/mount.x | 343 | ||||
-rw-r--r-- | support/export/v4clients.c | 233 | ||||
-rw-r--r-- | support/export/v4root.c | 233 | ||||
-rw-r--r-- | support/export/xtab.c | 256 |
13 files changed, 6189 insertions, 0 deletions
diff --git a/support/export/Makefile.am b/support/export/Makefile.am new file mode 100644 index 0000000..7338e1c --- /dev/null +++ b/support/export/Makefile.am @@ -0,0 +1,52 @@ +## Process this file with automake to produce Makefile.in + + +GENFILES_CLNT = mount_clnt.c +GENFILES_XDR = mount_xdr.c +GENFILES_H = mount.h + +GENFILES = $(GENFILES_CLNT) $(GENFILES_SVC) $(GENFILES_XDR) $(GENFILES_H) + +EXTRA_DIST = mount.x + +noinst_LIBRARIES = libexport.a +libexport_a_SOURCES = client.c export.c hostname.c \ + xtab.c mount_clnt.c mount_xdr.c \ + cache.c auth.c v4root.c fsloc.c \ + v4clients.c +libexport_a_CPPFLAGS = $(AM_CPPFLAGS) $(CPPFLAGS) -I$(top_srcdir)/support/reexport + +BUILT_SOURCES = $(GENFILES) + +noinst_HEADERS = mount.h + +dist-hook: + for f in $(GENFILES); do \ + rm ${distdir}/$$f; \ + done + +if CONFIG_RPCGEN +RPCGEN = $(top_builddir)/tools/rpcgen/rpcgen +$(RPCGEN): + make -C $(top_srcdir)/tools/rpcgen all +else +RPCGEN = @RPCGEN_PATH@ +endif + +$(GENFILES_CLNT): %_clnt.c: %.x $(RPCGEN) + test -f $@ && rm -rf $@ || true + $(RPCGEN) -l -o $@ $< + +$(GENFILES_XDR): %_xdr.c: %.x $(RPCGEN) + test -f $@ && rm -rf $@ || true + $(RPCGEN) -c -i 0 -o $@ $< + +$(GENFILES_H): %.h: %.x $(RPCGEN) + test -f $@ && rm -rf $@ || true + $(RPCGEN) -h -o $@ $< + rm -f $(top_builddir)/support/include/mount.h + $(LN_S) ../export/mount.h $(top_builddir)/support/include/mount.h + +MAINTAINERCLEANFILES = Makefile.in + +CLEANFILES = $(GENFILES) $(top_builddir)/support/include/mount.h diff --git a/support/export/Makefile.in b/support/export/Makefile.in new file mode 100644 index 0000000..6d8a25a --- /dev/null +++ b/support/export/Makefile.in @@ -0,0 +1,934 @@ +# 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@ +subdir = support/export +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 $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/support/include/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LIBRARIES = $(noinst_LIBRARIES) +ARFLAGS = cru +AM_V_AR = $(am__v_AR_@AM_V@) +am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@) +am__v_AR_0 = @echo " AR " $@; +am__v_AR_1 = +libexport_a_AR = $(AR) $(ARFLAGS) +libexport_a_LIBADD = +am_libexport_a_OBJECTS = libexport_a-client.$(OBJEXT) \ + libexport_a-export.$(OBJEXT) libexport_a-hostname.$(OBJEXT) \ + libexport_a-xtab.$(OBJEXT) libexport_a-mount_clnt.$(OBJEXT) \ + libexport_a-mount_xdr.$(OBJEXT) libexport_a-cache.$(OBJEXT) \ + libexport_a-auth.$(OBJEXT) libexport_a-v4root.$(OBJEXT) \ + libexport_a-fsloc.$(OBJEXT) libexport_a-v4clients.$(OBJEXT) +libexport_a_OBJECTS = $(am_libexport_a_OBJECTS) +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)/libexport_a-auth.Po \ + ./$(DEPDIR)/libexport_a-cache.Po \ + ./$(DEPDIR)/libexport_a-client.Po \ + ./$(DEPDIR)/libexport_a-export.Po \ + ./$(DEPDIR)/libexport_a-fsloc.Po \ + ./$(DEPDIR)/libexport_a-hostname.Po \ + ./$(DEPDIR)/libexport_a-mount_clnt.Po \ + ./$(DEPDIR)/libexport_a-mount_xdr.Po \ + ./$(DEPDIR)/libexport_a-v4clients.Po \ + ./$(DEPDIR)/libexport_a-v4root.Po \ + ./$(DEPDIR)/libexport_a-xtab.Po +am__mv = mv -f +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 = +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 = $(libexport_a_SOURCES) +DIST_SOURCES = $(libexport_a_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +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@ +GENFILES_CLNT = mount_clnt.c +GENFILES_XDR = mount_xdr.c +GENFILES_H = mount.h +GENFILES = $(GENFILES_CLNT) $(GENFILES_SVC) $(GENFILES_XDR) $(GENFILES_H) +EXTRA_DIST = mount.x +noinst_LIBRARIES = libexport.a +libexport_a_SOURCES = client.c export.c hostname.c \ + xtab.c mount_clnt.c mount_xdr.c \ + cache.c auth.c v4root.c fsloc.c \ + v4clients.c + +libexport_a_CPPFLAGS = $(AM_CPPFLAGS) $(CPPFLAGS) -I$(top_srcdir)/support/reexport +BUILT_SOURCES = $(GENFILES) +noinst_HEADERS = mount.h +@CONFIG_RPCGEN_FALSE@RPCGEN = @RPCGEN_PATH@ +@CONFIG_RPCGEN_TRUE@RPCGEN = $(top_builddir)/tools/rpcgen/rpcgen +MAINTAINERCLEANFILES = Makefile.in +CLEANFILES = $(GENFILES) $(top_builddir)/support/include/mount.h +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) 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 support/export/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu support/export/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): + +clean-noinstLIBRARIES: + -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) + +libexport.a: $(libexport_a_OBJECTS) $(libexport_a_DEPENDENCIES) $(EXTRA_libexport_a_DEPENDENCIES) + $(AM_V_at)-rm -f libexport.a + $(AM_V_AR)$(libexport_a_AR) libexport.a $(libexport_a_OBJECTS) $(libexport_a_LIBADD) + $(AM_V_at)$(RANLIB) libexport.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libexport_a-auth.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libexport_a-cache.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libexport_a-client.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libexport_a-export.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libexport_a-fsloc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libexport_a-hostname.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libexport_a-mount_clnt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libexport_a-mount_xdr.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libexport_a-v4clients.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libexport_a-v4root.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libexport_a-xtab.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 $@ $< + +libexport_a-client.o: client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-client.o -MD -MP -MF $(DEPDIR)/libexport_a-client.Tpo -c -o libexport_a-client.o `test -f 'client.c' || echo '$(srcdir)/'`client.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-client.Tpo $(DEPDIR)/libexport_a-client.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='client.c' object='libexport_a-client.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-client.o `test -f 'client.c' || echo '$(srcdir)/'`client.c + +libexport_a-client.obj: client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-client.obj -MD -MP -MF $(DEPDIR)/libexport_a-client.Tpo -c -o libexport_a-client.obj `if test -f 'client.c'; then $(CYGPATH_W) 'client.c'; else $(CYGPATH_W) '$(srcdir)/client.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-client.Tpo $(DEPDIR)/libexport_a-client.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='client.c' object='libexport_a-client.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-client.obj `if test -f 'client.c'; then $(CYGPATH_W) 'client.c'; else $(CYGPATH_W) '$(srcdir)/client.c'; fi` + +libexport_a-export.o: export.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-export.o -MD -MP -MF $(DEPDIR)/libexport_a-export.Tpo -c -o libexport_a-export.o `test -f 'export.c' || echo '$(srcdir)/'`export.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-export.Tpo $(DEPDIR)/libexport_a-export.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='export.c' object='libexport_a-export.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-export.o `test -f 'export.c' || echo '$(srcdir)/'`export.c + +libexport_a-export.obj: export.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-export.obj -MD -MP -MF $(DEPDIR)/libexport_a-export.Tpo -c -o libexport_a-export.obj `if test -f 'export.c'; then $(CYGPATH_W) 'export.c'; else $(CYGPATH_W) '$(srcdir)/export.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-export.Tpo $(DEPDIR)/libexport_a-export.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='export.c' object='libexport_a-export.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-export.obj `if test -f 'export.c'; then $(CYGPATH_W) 'export.c'; else $(CYGPATH_W) '$(srcdir)/export.c'; fi` + +libexport_a-hostname.o: hostname.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-hostname.o -MD -MP -MF $(DEPDIR)/libexport_a-hostname.Tpo -c -o libexport_a-hostname.o `test -f 'hostname.c' || echo '$(srcdir)/'`hostname.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-hostname.Tpo $(DEPDIR)/libexport_a-hostname.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='hostname.c' object='libexport_a-hostname.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-hostname.o `test -f 'hostname.c' || echo '$(srcdir)/'`hostname.c + +libexport_a-hostname.obj: hostname.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-hostname.obj -MD -MP -MF $(DEPDIR)/libexport_a-hostname.Tpo -c -o libexport_a-hostname.obj `if test -f 'hostname.c'; then $(CYGPATH_W) 'hostname.c'; else $(CYGPATH_W) '$(srcdir)/hostname.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-hostname.Tpo $(DEPDIR)/libexport_a-hostname.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='hostname.c' object='libexport_a-hostname.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-hostname.obj `if test -f 'hostname.c'; then $(CYGPATH_W) 'hostname.c'; else $(CYGPATH_W) '$(srcdir)/hostname.c'; fi` + +libexport_a-xtab.o: xtab.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-xtab.o -MD -MP -MF $(DEPDIR)/libexport_a-xtab.Tpo -c -o libexport_a-xtab.o `test -f 'xtab.c' || echo '$(srcdir)/'`xtab.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-xtab.Tpo $(DEPDIR)/libexport_a-xtab.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='xtab.c' object='libexport_a-xtab.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-xtab.o `test -f 'xtab.c' || echo '$(srcdir)/'`xtab.c + +libexport_a-xtab.obj: xtab.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-xtab.obj -MD -MP -MF $(DEPDIR)/libexport_a-xtab.Tpo -c -o libexport_a-xtab.obj `if test -f 'xtab.c'; then $(CYGPATH_W) 'xtab.c'; else $(CYGPATH_W) '$(srcdir)/xtab.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-xtab.Tpo $(DEPDIR)/libexport_a-xtab.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='xtab.c' object='libexport_a-xtab.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-xtab.obj `if test -f 'xtab.c'; then $(CYGPATH_W) 'xtab.c'; else $(CYGPATH_W) '$(srcdir)/xtab.c'; fi` + +libexport_a-mount_clnt.o: mount_clnt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-mount_clnt.o -MD -MP -MF $(DEPDIR)/libexport_a-mount_clnt.Tpo -c -o libexport_a-mount_clnt.o `test -f 'mount_clnt.c' || echo '$(srcdir)/'`mount_clnt.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-mount_clnt.Tpo $(DEPDIR)/libexport_a-mount_clnt.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mount_clnt.c' object='libexport_a-mount_clnt.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-mount_clnt.o `test -f 'mount_clnt.c' || echo '$(srcdir)/'`mount_clnt.c + +libexport_a-mount_clnt.obj: mount_clnt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-mount_clnt.obj -MD -MP -MF $(DEPDIR)/libexport_a-mount_clnt.Tpo -c -o libexport_a-mount_clnt.obj `if test -f 'mount_clnt.c'; then $(CYGPATH_W) 'mount_clnt.c'; else $(CYGPATH_W) '$(srcdir)/mount_clnt.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-mount_clnt.Tpo $(DEPDIR)/libexport_a-mount_clnt.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mount_clnt.c' object='libexport_a-mount_clnt.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-mount_clnt.obj `if test -f 'mount_clnt.c'; then $(CYGPATH_W) 'mount_clnt.c'; else $(CYGPATH_W) '$(srcdir)/mount_clnt.c'; fi` + +libexport_a-mount_xdr.o: mount_xdr.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-mount_xdr.o -MD -MP -MF $(DEPDIR)/libexport_a-mount_xdr.Tpo -c -o libexport_a-mount_xdr.o `test -f 'mount_xdr.c' || echo '$(srcdir)/'`mount_xdr.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-mount_xdr.Tpo $(DEPDIR)/libexport_a-mount_xdr.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mount_xdr.c' object='libexport_a-mount_xdr.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-mount_xdr.o `test -f 'mount_xdr.c' || echo '$(srcdir)/'`mount_xdr.c + +libexport_a-mount_xdr.obj: mount_xdr.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-mount_xdr.obj -MD -MP -MF $(DEPDIR)/libexport_a-mount_xdr.Tpo -c -o libexport_a-mount_xdr.obj `if test -f 'mount_xdr.c'; then $(CYGPATH_W) 'mount_xdr.c'; else $(CYGPATH_W) '$(srcdir)/mount_xdr.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-mount_xdr.Tpo $(DEPDIR)/libexport_a-mount_xdr.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mount_xdr.c' object='libexport_a-mount_xdr.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-mount_xdr.obj `if test -f 'mount_xdr.c'; then $(CYGPATH_W) 'mount_xdr.c'; else $(CYGPATH_W) '$(srcdir)/mount_xdr.c'; fi` + +libexport_a-cache.o: cache.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-cache.o -MD -MP -MF $(DEPDIR)/libexport_a-cache.Tpo -c -o libexport_a-cache.o `test -f 'cache.c' || echo '$(srcdir)/'`cache.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-cache.Tpo $(DEPDIR)/libexport_a-cache.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cache.c' object='libexport_a-cache.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-cache.o `test -f 'cache.c' || echo '$(srcdir)/'`cache.c + +libexport_a-cache.obj: cache.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-cache.obj -MD -MP -MF $(DEPDIR)/libexport_a-cache.Tpo -c -o libexport_a-cache.obj `if test -f 'cache.c'; then $(CYGPATH_W) 'cache.c'; else $(CYGPATH_W) '$(srcdir)/cache.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-cache.Tpo $(DEPDIR)/libexport_a-cache.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cache.c' object='libexport_a-cache.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-cache.obj `if test -f 'cache.c'; then $(CYGPATH_W) 'cache.c'; else $(CYGPATH_W) '$(srcdir)/cache.c'; fi` + +libexport_a-auth.o: auth.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-auth.o -MD -MP -MF $(DEPDIR)/libexport_a-auth.Tpo -c -o libexport_a-auth.o `test -f 'auth.c' || echo '$(srcdir)/'`auth.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-auth.Tpo $(DEPDIR)/libexport_a-auth.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='auth.c' object='libexport_a-auth.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-auth.o `test -f 'auth.c' || echo '$(srcdir)/'`auth.c + +libexport_a-auth.obj: auth.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-auth.obj -MD -MP -MF $(DEPDIR)/libexport_a-auth.Tpo -c -o libexport_a-auth.obj `if test -f 'auth.c'; then $(CYGPATH_W) 'auth.c'; else $(CYGPATH_W) '$(srcdir)/auth.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-auth.Tpo $(DEPDIR)/libexport_a-auth.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='auth.c' object='libexport_a-auth.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-auth.obj `if test -f 'auth.c'; then $(CYGPATH_W) 'auth.c'; else $(CYGPATH_W) '$(srcdir)/auth.c'; fi` + +libexport_a-v4root.o: v4root.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-v4root.o -MD -MP -MF $(DEPDIR)/libexport_a-v4root.Tpo -c -o libexport_a-v4root.o `test -f 'v4root.c' || echo '$(srcdir)/'`v4root.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-v4root.Tpo $(DEPDIR)/libexport_a-v4root.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='v4root.c' object='libexport_a-v4root.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-v4root.o `test -f 'v4root.c' || echo '$(srcdir)/'`v4root.c + +libexport_a-v4root.obj: v4root.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-v4root.obj -MD -MP -MF $(DEPDIR)/libexport_a-v4root.Tpo -c -o libexport_a-v4root.obj `if test -f 'v4root.c'; then $(CYGPATH_W) 'v4root.c'; else $(CYGPATH_W) '$(srcdir)/v4root.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-v4root.Tpo $(DEPDIR)/libexport_a-v4root.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='v4root.c' object='libexport_a-v4root.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-v4root.obj `if test -f 'v4root.c'; then $(CYGPATH_W) 'v4root.c'; else $(CYGPATH_W) '$(srcdir)/v4root.c'; fi` + +libexport_a-fsloc.o: fsloc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-fsloc.o -MD -MP -MF $(DEPDIR)/libexport_a-fsloc.Tpo -c -o libexport_a-fsloc.o `test -f 'fsloc.c' || echo '$(srcdir)/'`fsloc.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-fsloc.Tpo $(DEPDIR)/libexport_a-fsloc.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fsloc.c' object='libexport_a-fsloc.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-fsloc.o `test -f 'fsloc.c' || echo '$(srcdir)/'`fsloc.c + +libexport_a-fsloc.obj: fsloc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-fsloc.obj -MD -MP -MF $(DEPDIR)/libexport_a-fsloc.Tpo -c -o libexport_a-fsloc.obj `if test -f 'fsloc.c'; then $(CYGPATH_W) 'fsloc.c'; else $(CYGPATH_W) '$(srcdir)/fsloc.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-fsloc.Tpo $(DEPDIR)/libexport_a-fsloc.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fsloc.c' object='libexport_a-fsloc.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-fsloc.obj `if test -f 'fsloc.c'; then $(CYGPATH_W) 'fsloc.c'; else $(CYGPATH_W) '$(srcdir)/fsloc.c'; fi` + +libexport_a-v4clients.o: v4clients.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-v4clients.o -MD -MP -MF $(DEPDIR)/libexport_a-v4clients.Tpo -c -o libexport_a-v4clients.o `test -f 'v4clients.c' || echo '$(srcdir)/'`v4clients.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-v4clients.Tpo $(DEPDIR)/libexport_a-v4clients.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='v4clients.c' object='libexport_a-v4clients.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-v4clients.o `test -f 'v4clients.c' || echo '$(srcdir)/'`v4clients.c + +libexport_a-v4clients.obj: v4clients.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libexport_a-v4clients.obj -MD -MP -MF $(DEPDIR)/libexport_a-v4clients.Tpo -c -o libexport_a-v4clients.obj `if test -f 'v4clients.c'; then $(CYGPATH_W) 'v4clients.c'; else $(CYGPATH_W) '$(srcdir)/v4clients.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libexport_a-v4clients.Tpo $(DEPDIR)/libexport_a-v4clients.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='v4clients.c' object='libexport_a-v4clients.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libexport_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libexport_a-v4clients.obj `if test -f 'v4clients.c'; then $(CYGPATH_W) 'v4clients.c'; else $(CYGPATH_W) '$(srcdir)/v4clients.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +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 + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$(top_distdir)" distdir="$(distdir)" \ + dist-hook +check-am: all-am +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-am +all-am: Makefile $(LIBRARIES) $(HEADERS) +installdirs: +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-am +install-exec: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) 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: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +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 "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) + -test -z "$(MAINTAINERCLEANFILES)" || rm -f $(MAINTAINERCLEANFILES) +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/libexport_a-auth.Po + -rm -f ./$(DEPDIR)/libexport_a-cache.Po + -rm -f ./$(DEPDIR)/libexport_a-client.Po + -rm -f ./$(DEPDIR)/libexport_a-export.Po + -rm -f ./$(DEPDIR)/libexport_a-fsloc.Po + -rm -f ./$(DEPDIR)/libexport_a-hostname.Po + -rm -f ./$(DEPDIR)/libexport_a-mount_clnt.Po + -rm -f ./$(DEPDIR)/libexport_a-mount_xdr.Po + -rm -f ./$(DEPDIR)/libexport_a-v4clients.Po + -rm -f ./$(DEPDIR)/libexport_a-v4root.Po + -rm -f ./$(DEPDIR)/libexport_a-xtab.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-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +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)/libexport_a-auth.Po + -rm -f ./$(DEPDIR)/libexport_a-cache.Po + -rm -f ./$(DEPDIR)/libexport_a-client.Po + -rm -f ./$(DEPDIR)/libexport_a-export.Po + -rm -f ./$(DEPDIR)/libexport_a-fsloc.Po + -rm -f ./$(DEPDIR)/libexport_a-hostname.Po + -rm -f ./$(DEPDIR)/libexport_a-mount_clnt.Po + -rm -f ./$(DEPDIR)/libexport_a-mount_xdr.Po + -rm -f ./$(DEPDIR)/libexport_a-v4clients.Po + -rm -f ./$(DEPDIR)/libexport_a-v4root.Po + -rm -f ./$(DEPDIR)/libexport_a-xtab.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: + +.MAKE: all check install install-am install-exec install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-noinstLIBRARIES \ + cscopelist-am ctags ctags-am dist-hook 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-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + 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 + +.PRECIOUS: Makefile + + +dist-hook: + for f in $(GENFILES); do \ + rm ${distdir}/$$f; \ + done +@CONFIG_RPCGEN_TRUE@$(RPCGEN): +@CONFIG_RPCGEN_TRUE@ make -C $(top_srcdir)/tools/rpcgen all + +$(GENFILES_CLNT): %_clnt.c: %.x $(RPCGEN) + test -f $@ && rm -rf $@ || true + $(RPCGEN) -l -o $@ $< + +$(GENFILES_XDR): %_xdr.c: %.x $(RPCGEN) + test -f $@ && rm -rf $@ || true + $(RPCGEN) -c -i 0 -o $@ $< + +$(GENFILES_H): %.h: %.x $(RPCGEN) + test -f $@ && rm -rf $@ || true + $(RPCGEN) -h -o $@ $< + rm -f $(top_builddir)/support/include/mount.h + $(LN_S) ../export/mount.h $(top_builddir)/support/include/mount.h + +# 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/support/export/auth.c b/support/export/auth.c new file mode 100644 index 0000000..2d7960f --- /dev/null +++ b/support/export/auth.c @@ -0,0 +1,320 @@ +/* + * utils/mountd/auth.c + * + * Authentication procedures for mountd. + * + * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#include "sockaddr.h" +#include "misc.h" +#include "nfslib.h" +#include "exportfs.h" +#include "export.h" +#include "v4root.h" + +enum auth_error +{ + bad_path, + unknown_host, + no_entry, + not_exported, + illegal_port, + success +}; + +static void auth_fixpath(char *path); +static nfs_export my_exp; +static nfs_client my_client; + +extern int use_ipaddr; + +/* +void +auth_init(void) +{ + auth_reload(); +} +*/ + +/* + * A client can match many different netgroups and it's tough to know + * beforehand whether it will. If the concatenated string of netgroup + * m_hostnames is >512 bytes, then enable the "use_ipaddr" mode. This + * makes mountd change how it matches a client ip address when a mount + * request comes in. It's more efficient at handling netgroups at the + * expense of larger kernel caches. + */ +static void +check_useipaddr(void) +{ + nfs_client *clp; + int old_use_ipaddr = use_ipaddr; + unsigned int len = 0; + + if (use_ipaddr > 1) + /* fixed - don't check */ + return; + + /* add length of m_hostname + 1 for the comma */ + for (clp = clientlist[MCL_NETGROUP]; clp; clp = clp->m_next) + len += (strlen(clp->m_hostname) + 1); + + if (len > (NFSCLNT_IDMAX / 2)) + use_ipaddr = 1; + else + use_ipaddr = 0; + + if (use_ipaddr != old_use_ipaddr) + cache_flush(); +} + +unsigned int +auth_reload(void) +{ + struct stat stb; + static ino_t last_inode; + static int last_fd = -1; + static unsigned int counter; + int fd; + + if ((fd = open(etab.statefn, O_RDONLY)) < 0) { + xlog(L_FATAL, "couldn't open %s", etab.statefn); + } else if (fstat(fd, &stb) < 0) { + xlog(L_FATAL, "couldn't stat %s", etab.statefn); + close(fd); + } else if (last_fd != -1 && stb.st_ino == last_inode) { + /* We opened the etab file before, and its inode + * number hasn't changed since then. + */ + close(fd); + return counter; + } else { + /* Need to process entries from the etab file. Close + * the file descriptor from the previous open (last_fd), + * and keep the current file descriptor open to prevent + * the file system reusing the current inode number + * (last_inode). + */ + if (last_fd != -1) + close(last_fd); + last_fd = fd; + last_inode = stb.st_ino; + } + + export_freeall(); + memset(&my_client, 0, sizeof(my_client)); + xtab_export_read(); + check_useipaddr(); + v4root_set(); + + ++counter; + + return counter; +} + +static char *get_client_ipaddr_name(const struct sockaddr *caller) +{ + char buf[INET6_ADDRSTRLEN + 1]; + + buf[0] = '$'; + host_ntop(caller, buf + 1, sizeof(buf) - 1); + return strdup(buf); +} + +static char * +get_client_hostname(const struct sockaddr *caller, struct addrinfo *ai, + enum auth_error *error) +{ + char *n; + + if (use_ipaddr) + return get_client_ipaddr_name(caller); + n = client_compose(ai); + *error = unknown_host; + if (!n) + return NULL; + if (*n) + return n; + free(n); + return strdup("DEFAULT"); +} + +bool ipaddr_client_matches(nfs_export *exp, struct addrinfo *ai) +{ + return client_check(exp->m_client, ai); +} + +bool namelist_client_matches(nfs_export *exp, char *dom) +{ + return client_member(dom, exp->m_client->m_hostname); +} + +bool client_matches(nfs_export *exp, char *dom, struct addrinfo *ai) +{ + if (is_ipaddr_client(dom)) + return ipaddr_client_matches(exp, ai); + return namelist_client_matches(exp, dom); +} + +/* return static nfs_export with details filled in */ +static nfs_export * +auth_authenticate_newcache(const struct sockaddr *caller, + const char *path, struct addrinfo *ai, + enum auth_error *error) +{ + nfs_export *exp; + int i; + + free(my_client.m_hostname); + + my_client.m_hostname = get_client_hostname(caller, ai, error); + if (my_client.m_hostname == NULL) + return NULL; + + my_client.m_naddr = 1; + set_addrlist(&my_client, 0, caller); + my_exp.m_client = &my_client; + + exp = NULL; + for (i = 0; !exp && i < MCL_MAXTYPES; i++) + for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { + if (strcmp(path, exp->m_export.e_path)) + continue; + if (!client_matches(exp, my_client.m_hostname, ai)) + continue; + if (exp->m_export.e_flags & NFSEXP_V4ROOT) + /* not acceptable for v[23] export */ + continue; + break; + } + *error = not_exported; + if (!exp) + return NULL; + + my_exp.m_export = exp->m_export; + exp = &my_exp; + return exp; +} + +static nfs_export * +auth_authenticate_internal(const struct sockaddr *caller, const char *path, + struct addrinfo *ai, enum auth_error *error) +{ + nfs_export *exp; + + exp = auth_authenticate_newcache(caller, path, ai, error); + if (!exp) + return NULL; + if (!(exp->m_export.e_flags & NFSEXP_INSECURE_PORT) && + nfs_get_port(caller) >= IPPORT_RESERVED) { + *error = illegal_port; + return NULL; + } + *error = success; + + return exp; +} + +nfs_export * +auth_authenticate(const char *what, const struct sockaddr *caller, + const char *path) +{ + nfs_export *exp = NULL; + char epath[MAXPATHLEN+1]; + char *p = NULL; + char buf[INET6_ADDRSTRLEN]; + struct addrinfo *ai = NULL; + enum auth_error error = bad_path; + + if (path[0] != '/') { + xlog(L_WARNING, "Bad path in %s request from %s: \"%s\"", + what, host_ntop(caller, buf, sizeof(buf)), path); + return exp; + } + + strncpy(epath, path, sizeof (epath) - 1); + epath[sizeof (epath) - 1] = '\0'; + auth_fixpath(epath); /* strip duplicate '/' etc */ + + ai = client_resolve(caller); + if (ai == NULL) + return exp; + + /* Try the longest matching exported pathname. */ + while (1) { + exp = auth_authenticate_internal(caller, epath, ai, &error); + if (exp || (error != not_exported && error != no_entry)) + break; + /* We have to treat the root, "/", specially. */ + if (p == &epath[1]) break; + p = strrchr(epath, '/'); + if (p == epath) p++; + *p = '\0'; + } + + host_ntop(caller, buf, sizeof(buf)); + switch (error) { + case bad_path: + xlog(L_WARNING, "bad path in %s request from %s: \"%s\"", + what, buf, path); + break; + + case unknown_host: + xlog(L_WARNING, "refused %s request from %s for %s (%s): unmatched host", + what, buf, path, epath); + break; + + case no_entry: + xlog(L_WARNING, "refused %s request from %s for %s (%s): no export entry", + what, buf, path, epath); + break; + + case not_exported: + xlog(L_WARNING, "refused %s request from %s for %s (%s): not exported", + what, buf, path, epath); + break; + + case illegal_port: + xlog(L_WARNING, "refused %s request from %s for %s (%s): illegal port %u", + what, buf, path, epath, nfs_get_port(caller)); + break; + + case success: + xlog(L_NOTICE, "authenticated %s request from %s:%u for %s (%s)", + what, buf, nfs_get_port(caller), path, epath); + break; + default: + xlog(L_NOTICE, "%s request from %s:%u for %s (%s) gave %d", + what, buf, nfs_get_port(caller), path, epath, error); + } + + nfs_freeaddrinfo(ai); + return exp; +} + +static void +auth_fixpath(char *path) +{ + char *sp, *cp; + + for (sp = cp = path; *sp; sp++) { + if (*sp != '/' || sp[1] != '/') + *cp++ = *sp; + } + while (cp > path+1 && cp[-1] == '/') + cp--; + *cp = '\0'; +} diff --git a/support/export/cache.c b/support/export/cache.c new file mode 100644 index 0000000..6c0a44a --- /dev/null +++ b/support/export/cache.c @@ -0,0 +1,1934 @@ +/* + * Handle communication with knfsd internal cache + * + * We open /proc/net/rpc/{auth.unix.ip,nfsd.export,nfsd.fh}/channel + * and listen for requests (using my_svc_run) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/sysmacros.h> +#include <sys/types.h> +#include <sys/select.h> +#include <sys/stat.h> +#include <sys/vfs.h> +#include <sys/wait.h> +#include <time.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <ctype.h> +#include <pwd.h> +#include <grp.h> +#include <mntent.h> +#include "misc.h" +#include "nfsd_path.h" +#include "nfslib.h" +#include "exportfs.h" +#include "export.h" +#include "pseudoflavors.h" +#include "xcommon.h" +#include "reexport.h" + +#ifdef HAVE_JUNCTION_SUPPORT +#include "fsloc.h" +#endif + +#ifdef USE_BLKID +#include "blkid/blkid.h" +#endif + +enum nfsd_fsid { + FSID_DEV = 0, + FSID_NUM, + FSID_MAJOR_MINOR, + FSID_ENCODE_DEV, + FSID_UUID4_INUM, + FSID_UUID8, + FSID_UUID16, + FSID_UUID16_INUM, +}; + +#undef is_mountpoint +static int is_mountpoint(const char *path) +{ + return check_is_mountpoint(path, nfsd_path_lstat); +} + +static ssize_t cache_read(int fd, char *buf, size_t len) +{ + return nfsd_path_read(fd, buf, len); +} + +static ssize_t cache_write(int fd, const char *buf, size_t len) +{ + return nfsd_path_write(fd, buf, len); +} + +static bool path_lookup_error(int err) +{ + switch (err) { + case ELOOP: + case ENAMETOOLONG: + case ENOENT: + case ENOTDIR: + case EACCES: + return 1; + } + return 0; +} + +/* + * Support routines for text-based upcalls. + * Fields are separated by spaces. + * Fields are either mangled to quote space tab newline slosh with slosh + * or a hexified with a leading \x + * Record is terminated with newline. + * + */ + +#define INITIAL_MANAGED_GROUPS 100 + +extern int use_ipaddr; + +static void auth_unix_ip(int f) +{ + /* requests are + * class IP-ADDR + * Ignore if class != "nfsd" + * Otherwise find domainname and write back: + * + * "nfsd" IP-ADDR expiry domainname + */ + char class[20]; + char ipaddr[INET6_ADDRSTRLEN + 1]; + char *client = NULL; + struct addrinfo *ai = NULL; + struct addrinfo *tmp = NULL; + char buf[RPC_CHAN_BUF_SIZE], *bp; + int blen; + + blen = read(f, buf, sizeof(buf)); + if (blen <= 0 || buf[blen-1] != '\n') return; + buf[blen-1] = 0; + + xlog(D_CALL, "auth_unix_ip: inbuf '%s'", buf); + + bp = buf; + + if (qword_get(&bp, class, 20) <= 0 || + strcmp(class, "nfsd") != 0) + return; + + if (qword_get(&bp, ipaddr, sizeof(ipaddr) - 1) <= 0) + return; + + tmp = host_pton(ipaddr); + if (tmp == NULL) + return; + + auth_reload(); + + /* addr is a valid address, find the domain name... */ + ai = client_resolve(tmp->ai_addr); + if (ai) { + client = client_compose(ai); + nfs_freeaddrinfo(ai); + } + if (!client) + xlog(D_AUTH, "failed authentication for IP %s", ipaddr); + else if (!use_ipaddr) + xlog(D_AUTH, "successful authentication for IP %s as %s", + ipaddr, *client ? client : "DEFAULT"); + else + xlog(D_AUTH, "successful authentication for IP %s", + ipaddr); + + bp = buf; blen = sizeof(buf); + qword_add(&bp, &blen, "nfsd"); + qword_add(&bp, &blen, ipaddr); + qword_adduint(&bp, &blen, time(0) + default_ttl); + if (use_ipaddr && client) { + memmove(ipaddr + 1, ipaddr, strlen(ipaddr) + 1); + ipaddr[0] = '$'; + qword_add(&bp, &blen, ipaddr); + } else if (client) + qword_add(&bp, &blen, *client?client:"DEFAULT"); + qword_addeol(&bp, &blen); + if (blen <= 0 || write(f, buf, bp - buf) != bp - buf) + xlog(L_ERROR, "auth_unix_ip: error writing reply"); + + xlog(D_CALL, "auth_unix_ip: client %p '%s'", client, client?client: "DEFAULT"); + + free(client); + nfs_freeaddrinfo(tmp); + +} + +static void auth_unix_gid(int f) +{ + /* Request are + * uid + * reply is + * uid expiry count list of group ids + */ + uid_t uid; + struct passwd *pw; + static gid_t *groups = NULL; + static int groups_len = 0; + gid_t *more_groups; + int ngroups; + int rv, i; + char buf[RPC_CHAN_BUF_SIZE], *bp; + int blen; + + if (groups_len == 0) { + groups = malloc(sizeof(gid_t) * INITIAL_MANAGED_GROUPS); + if (!groups) + return; + + groups_len = INITIAL_MANAGED_GROUPS; + } + + ngroups = groups_len; + + blen = read(f, buf, sizeof(buf)); + if (blen <= 0 || buf[blen-1] != '\n') return; + buf[blen-1] = 0; + + bp = buf; + if (qword_get_uint(&bp, &uid) != 0) + return; + + pw = getpwuid(uid); + if (!pw) + rv = -1; + else { + rv = getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups); + if (rv == -1 && ngroups >= groups_len) { + more_groups = realloc(groups, sizeof(gid_t)*ngroups); + if (!more_groups) + rv = -1; + else { + groups = more_groups; + groups_len = ngroups; + rv = getgrouplist(pw->pw_name, pw->pw_gid, + groups, &ngroups); + } + } + } + + bp = buf; blen = sizeof(buf); + qword_adduint(&bp, &blen, uid); + qword_adduint(&bp, &blen, time(0) + default_ttl); + if (rv >= 0) { + qword_adduint(&bp, &blen, ngroups); + for (i=0; i<ngroups; i++) + qword_adduint(&bp, &blen, groups[i]); + } else + qword_adduint(&bp, &blen, 0); + qword_addeol(&bp, &blen); + if (blen <= 0 || write(f, buf, bp - buf) != bp - buf) + xlog(L_ERROR, "auth_unix_gid: error writing reply"); +} + +static int match_crossmnt_fsidnum(uint32_t parsed_fsidnum, char *path) +{ + uint32_t fsidnum; + + if (reexpdb_fsidnum_by_path(path, &fsidnum, 0) == 0) + return 0; + + return fsidnum == parsed_fsidnum; +} + +#ifdef USE_BLKID +static const char *get_uuid_blkdev(char *path) +{ + /* We set *safe if we know that we need the + * fsid from statfs too. + */ + static blkid_cache cache = NULL; + struct stat stb; + char *devname; + blkid_tag_iterate iter; + blkid_dev dev; + const char *type; + const char *val, *uuid = NULL; + + if (cache == NULL) + blkid_get_cache(&cache, NULL); + + if (nfsd_path_stat(path, &stb) != 0) + return NULL; + devname = blkid_devno_to_devname(stb.st_dev); + if (!devname) + return NULL; + dev = blkid_get_dev(cache, devname, BLKID_DEV_NORMAL); + free(devname); + if (!dev) + return NULL; + iter = blkid_tag_iterate_begin(dev); + if (!iter) + return NULL; + while (blkid_tag_next(iter, &type, &val) == 0) { + if (strcmp(type, "UUID") == 0) + uuid = val; + if (strcmp(type, "TYPE") == 0 && + strcmp(val, "btrfs") == 0) { + uuid = NULL; + break; + } + } + blkid_tag_iterate_end(iter); + return uuid; +} +#else +#define get_uuid_blkdev(path) (NULL) +#endif + +static int get_uuid(const char *val, size_t uuidlen, char *u) +{ + /* extract hex digits from uuidstr and compose a uuid + * of the given length (max 16), xoring bytes to make + * a smaller uuid. + */ + size_t i = 0; + + memset(u, 0, uuidlen); + for ( ; *val ; val++) { + int c = *val; + if (!isxdigit(c)) + continue; + if (isalpha(c)) { + if (isupper(c)) + c = c - 'A' + 10; + else + c = c - 'a' + 10; + } else + c = c - '0' + 0; + if ((i&1) == 0) + c <<= 4; + u[i/2] ^= (char)c; + i++; + if (i == uuidlen*2) + i = 0; + } + return 1; +} + + +/* + * Don't ask libblkid for these filesystems. Note that BTRF is ignored, because + * we generate the identifier from statfs->f_fsid. The rest are network or + * pseudo filesystems. (See <linux/magic.h> for the basic IDs.) + */ +static const unsigned long nonblkid_filesystems[] = { + 0x2fc12fc1, /* ZFS_SUPER_MAGIC */ + 0x9123683E, /* BTRFS_SUPER_MAGIC */ + 0xFF534D42, /* CIFS_MAGIC_NUMBER */ + 0x1373, /* DEVFS_SUPER_MAGIC */ + 0x73757245, /* CODA_SUPER_MAGIC */ + 0x564C, /* NCP_SUPER_MAGIC */ + 0x6969, /* NFS_SUPER_MAGIC */ + 0x9FA0, /* PROC_SUPER_MAGIC */ + 0x62656572, /* SYSFS_MAGIC */ + 0x517B, /* SMB_SUPER_MAGIC */ + 0x01021994, /* TMPFS_SUPER_MAGIC */ + 0 /* last */ +}; + +static int uuid_by_path(char *path, int type, size_t uuidlen, char *uuid) +{ + /* get a uuid for the filesystem found at 'path'. + * There are several possible ways of generating the + * uuids (types). + * Type 0 is used for new filehandles, while other types + * may be used to interpret old filehandle - to ensure smooth + * forward migration. + * We return 1 if a uuid was found (and it might be worth + * trying the next type) or 0 if no more uuid types can be + * extracted. + */ + + /* Possible sources of uuid are + * - blkid uuid + * - statfs uuid + * + * On some filesystems (e.g. vfat) the statfs uuid is simply an + * encoding of the device that the filesystem is mounted from, so + * it we be very bad to use that (as device numbers change). blkid + * must be preferred. + * On other filesystems (e.g. btrfs) the statfs uuid contains + * important info that the blkid uuid cannot contain: This happens + * when multiple subvolumes are exported (they have the same + * blkid uuid but different statfs uuids). + * We rely on get_uuid_blkdev *knowing* which is which and not returning + * a uuid for filesystems where the statfs uuid is better. + * + */ + struct statfs st; + char fsid_val[17]; + const char *blkid_val = NULL; + const char *val; + int rc; + + rc = nfsd_path_statfs(path, &st); + + if (type == 0 && rc == 0) { + const unsigned long *bad; + for (bad = nonblkid_filesystems; *bad; bad++) { + if (*bad == (unsigned long)st.f_type) + break; + } + if (*bad == 0) + blkid_val = get_uuid_blkdev(path); + } + + if (rc == 0 && + (st.f_fsid.__val[0] || st.f_fsid.__val[1])) + snprintf(fsid_val, 17, "%08x%08x", + st.f_fsid.__val[0], st.f_fsid.__val[1]); + else + fsid_val[0] = 0; + + if (blkid_val && (type--) == 0) + val = blkid_val; + else if (fsid_val[0] && (type--) == 0) + val = fsid_val; + else + return 0; + + get_uuid(val, uuidlen, uuid); + return 1; +} + +/* Iterate through /etc/mtab, finding mountpoints + * at or below a given path + */ +static char *next_mnt(void **v, char *p) +{ + FILE *f; + struct mntent *me; + size_t l = strlen(p); + + if (*v == NULL) { + f = setmntent("/etc/mtab", "r"); + *v = f; + } else + f = *v; + while ((me = getmntent(f)) != NULL && l >= 1) { + char *mnt_dir = nfsd_path_strip_root(me->mnt_dir); + + if (!mnt_dir) + continue; + + /* Everything below "/" is a proper sub-mount */ + if (strcmp(p, "/") == 0) + return mnt_dir; + + if (strncmp(mnt_dir, p, l) == 0 && mnt_dir[l] == '/') + return mnt_dir; + } + endmntent(f); + *v = NULL; + return NULL; +} + +/* same_path() check is two paths refer to the same directory. + * We don't rely on 'strcmp()' as some filesystems support case-insensitive + * names and we might have two different names for the one directory. + * Theoretically the lengths of the names could be different, but the + * number of components must be the same. + * So if the paths have the same number of components (but aren't identical) + * we ask the kernel if they are the same thing. + * By preference we use name_to_handle_at(), as the mntid it returns + * will distinguish between bind-mount points. If that isn't available + * we fall back on lstat, which is usually good enough. + */ +static inline int count_slashes(char *p) +{ + int cnt = 0; + while (*p) + if (*p++ == '/') + cnt++; + return cnt; +} + +#if defined(HAVE_STRUCT_FILE_HANDLE) +static int check_same_path_by_handle(const char *child, const char *parent) +{ + struct { + struct file_handle fh; + unsigned char handle[128]; + } fchild, fparent; + int mnt_child, mnt_parent; + + fchild.fh.handle_bytes = 128; + fparent.fh.handle_bytes = 128; + + /* This process should have the CAP_DAC_READ_SEARCH capability */ + if (nfsd_name_to_handle_at(AT_FDCWD, child, &fchild.fh, &mnt_child, 0) < 0) + return -1; + if (nfsd_name_to_handle_at(AT_FDCWD, parent, &fparent.fh, &mnt_parent, 0) < 0) { + /* If the child resolved, but the parent did not, they differ */ + if (path_lookup_error(errno)) + return 0; + /* Otherwise, we just don't know */ + return -1; + } + + if (mnt_child != mnt_parent || + fchild.fh.handle_bytes != fparent.fh.handle_bytes || + fchild.fh.handle_type != fparent.fh.handle_type || + memcmp(fchild.handle, fparent.handle, + fchild.fh.handle_bytes) != 0) + return 0; + + return 1; +} +#else +static int check_same_path_by_handle(const char *child, const char *parent) +{ + errno = ENOSYS; + return -1; +} +#endif + +static int check_same_path_by_inode(const char *child, const char *parent) +{ + struct stat sc, sp; + + /* This is nearly good enough. However if a directory is + * bind-mounted in two places and both are exported, it + * could give a false positive + */ + if (nfsd_path_lstat(child, &sc) != 0) + return 0; + if (nfsd_path_lstat(parent, &sp) != 0) + return 0; + if (sc.st_dev != sp.st_dev) + return 0; + if (sc.st_ino != sp.st_ino) + return 0; + + return 1; +} + +static int same_path(char *child, char *parent, int len) +{ + static char p[PATH_MAX]; + int err; + + if (len <= 0) + len = strlen(child); + strncpy(p, child, len); + p[len] = 0; + if (strcmp(p, parent) == 0) + return 1; + + /* If number of '/' are different, they must be different */ + if (count_slashes(p) != count_slashes(parent)) + return 0; + + /* Try to use filehandle approach before falling back to stat() */ + err = check_same_path_by_handle(p, parent); + if (err != -1) + return err; + return check_same_path_by_inode(p, parent); +} + +static int is_subdirectory(char *child, char *parent) +{ + /* Check is child is strictly a subdirectory of + * parent or a more distant descendant. + */ + size_t l = strlen(parent); + + if (strcmp(parent, "/") == 0 && child[1] != 0) + return 1; + + return (same_path(child, parent, l) && child[l] == '/'); +} + +static int path_matches(nfs_export *exp, char *path) +{ + /* Does the path match the export? I.e. is it an + * exact match, or does the export have CROSSMOUNT, and path + * is a descendant? + */ + return same_path(path, exp->m_export.e_path, 0) + || ((exp->m_export.e_flags & NFSEXP_CROSSMOUNT) + && is_subdirectory(path, exp->m_export.e_path)); +} + +static int +export_matches(nfs_export *exp, char *dom, char *path, struct addrinfo *ai) +{ + return path_matches(exp, path) && client_matches(exp, dom, ai); +} + +/* True iff e1 is a child of e2 (or descendant) and e2 has crossmnt set: */ +static bool subexport(struct exportent *e1, struct exportent *e2) +{ + char *p1 = e1->e_path, *p2 = e2->e_path; + + return e2->e_flags & NFSEXP_CROSSMOUNT + && is_subdirectory(p1, p2); +} + +struct parsed_fsid { + int fsidtype; + /* We could use a union for this, but it would be more + * complicated; why bother? */ + uint64_t inode; + unsigned int minor; + unsigned int major; + uint32_t fsidnum; + size_t uuidlen; + char *fhuuid; +}; + +static int parse_fsid(int fsidtype, int fsidlen, char *fsid, + struct parsed_fsid *parsed) +{ + uint32_t dev; + uint32_t inode32; + + memset(parsed, 0, sizeof(*parsed)); + parsed->fsidtype = fsidtype; + switch(fsidtype) { + case FSID_DEV: /* 4 bytes: 2 major, 2 minor, 4 inode */ + if (fsidlen != 8) + return -1; + memcpy(&dev, fsid, 4); + memcpy(&inode32, fsid+4, 4); + parsed->inode = inode32; + parsed->major = ntohl(dev)>>16; + parsed->minor = ntohl(dev) & 0xFFFF; + break; + + case FSID_NUM: /* 4 bytes - fsid */ + if (fsidlen != 4) + return -1; + memcpy(&parsed->fsidnum, fsid, 4); + break; + + case FSID_MAJOR_MINOR: /* 12 bytes: 4 major, 4 minor, 4 inode + * This format is never actually used but was + * an historical accident + */ + if (fsidlen != 12) + return -1; + memcpy(&dev, fsid, 4); + parsed->major = ntohl(dev); + memcpy(&dev, fsid+4, 4); + parsed->minor = ntohl(dev); + memcpy(&inode32, fsid+8, 4); + parsed->inode = inode32; + break; + + case FSID_ENCODE_DEV: /* 8 bytes: 4 byte packed device number, 4 inode */ + /* This is *host* endian, not net-byte-order, because + * no-one outside this host has any business interpreting it + */ + if (fsidlen != 8) + return -1; + memcpy(&dev, fsid, 4); + memcpy(&inode32, fsid+4, 4); + parsed->inode = inode32; + parsed->major = (dev & 0xfff00) >> 8; + parsed->minor = (dev & 0xff) | ((dev >> 12) & 0xfff00); + break; + + case FSID_UUID4_INUM: /* 4 byte inode number and 4 byte uuid */ + if (fsidlen != 8) + return -1; + memcpy(&inode32, fsid, 4); + parsed->inode = inode32; + parsed->uuidlen = 4; + parsed->fhuuid = fsid+4; + break; + case FSID_UUID8: /* 8 byte uuid */ + if (fsidlen != 8) + return -1; + parsed->uuidlen = 8; + parsed->fhuuid = fsid; + break; + case FSID_UUID16: /* 16 byte uuid */ + if (fsidlen != 16) + return -1; + parsed->uuidlen = 16; + parsed->fhuuid = fsid; + break; + case FSID_UUID16_INUM: /* 8 byte inode number and 16 byte uuid */ + if (fsidlen != 24) + return -1; + memcpy(&parsed->inode, fsid, 8); + parsed->uuidlen = 16; + parsed->fhuuid = fsid+8; + break; + } + return 0; +} + +static int match_fsid(struct parsed_fsid *parsed, nfs_export *exp, char *path) +{ + struct stat stb; + int type; + char u[16]; + + if (nfsd_path_stat(path, &stb) != 0) + goto path_error; + if (!S_ISDIR(stb.st_mode) && !S_ISREG(stb.st_mode)) + goto nomatch; + + switch (parsed->fsidtype) { + case FSID_DEV: + case FSID_MAJOR_MINOR: + case FSID_ENCODE_DEV: + if (stb.st_ino != parsed->inode) + goto nomatch; + if (parsed->major != major(stb.st_dev) || + parsed->minor != minor(stb.st_dev)) + goto nomatch; + goto match; + case FSID_NUM: + if (((exp->m_export.e_flags & NFSEXP_FSID) == 0 || + exp->m_export.e_fsid != parsed->fsidnum)) { + if ((exp->m_export.e_flags & NFSEXP_CROSSMOUNT) && exp->m_export.e_reexport != REEXP_NONE && + match_crossmnt_fsidnum(parsed->fsidnum, path)) + goto match; + + goto nomatch; + } + goto match; + case FSID_UUID4_INUM: + case FSID_UUID16_INUM: + if (stb.st_ino != parsed->inode) + goto nomatch; + goto check_uuid; + case FSID_UUID8: + case FSID_UUID16: + errno = 0; + if (!is_mountpoint(path)) { + if (!errno) + goto nomatch; + goto path_error; + } + check_uuid: + if (exp->m_export.e_uuid) { + get_uuid(exp->m_export.e_uuid, parsed->uuidlen, u); + if (memcmp(u, parsed->fhuuid, parsed->uuidlen) == 0) + goto match; + } + else + for (type = 0; + uuid_by_path(path, type, parsed->uuidlen, u); + type++) + if (memcmp(u, parsed->fhuuid, parsed->uuidlen) == 0) + goto match; + } +nomatch: + return 0; +match: + return 1; +path_error: + if (path_lookup_error(errno)) + goto nomatch; + return -1; +} + +static struct addrinfo *lookup_client_addr(char *dom) +{ + struct addrinfo *ret; + struct addrinfo *tmp; + + dom++; /* skip initial "$" */ + + tmp = host_pton(dom); + if (tmp == NULL) + return NULL; + ret = client_resolve(tmp->ai_addr); + nfs_freeaddrinfo(tmp); + return ret; +} + +#define RETRY_SEC 120 +struct delayed { + char *message; + time_t last_attempt; + int f; + struct delayed *next; +} *delayed; + +static int nfsd_handle_fh(int f, char *bp, int blen) +{ + /* request are: + * domain fsidtype fsid + * interpret fsid, find export point and options, and write: + * domain fsidtype fsid expiry path + */ + char *dom; + int fsidtype; + int fsidlen; + char fsid[32]; + struct parsed_fsid parsed; + struct exportent *found = NULL; + struct addrinfo *ai = NULL; + char *found_path = NULL; + nfs_export *exp; + int i; + int dev_missing = 0; + char buf[RPC_CHAN_BUF_SIZE]; + int did_uncover = 0; + int ret = 0; + + dom = malloc(blen); + if (dom == NULL) + return ret; + if (qword_get(&bp, dom, blen) <= 0) + goto out; + if (qword_get_int(&bp, &fsidtype) != 0) + goto out; + if (fsidtype < 0 || fsidtype > 7) + goto out; /* unknown type */ + if ((fsidlen = qword_get(&bp, fsid, 32)) <= 0) + goto out; + if (parse_fsid(fsidtype, fsidlen, fsid, &parsed)) + goto out; + + auth_reload(); + + if (is_ipaddr_client(dom)) { + ai = lookup_client_addr(dom); + if (!ai) + goto out; + } + + /* Now determine export point for this fsid/domain */ + for (i=0 ; i < MCL_MAXTYPES; i++) { + nfs_export *next_exp; + for (exp = exportlist[i].p_head; exp; exp = next_exp) { + char *path; + + if (!did_uncover && parsed.fsidnum && parsed.fsidtype == FSID_NUM && exp->m_export.e_reexport != REEXP_NONE) { + reexpdb_uncover_subvolume(parsed.fsidnum); + did_uncover = 1; + } + + if (exp->m_export.e_flags & NFSEXP_CROSSMOUNT) { + static nfs_export *prev = NULL; + static void *mnt = NULL; + + if (prev == exp) { + /* try a submount */ + path = next_mnt(&mnt, exp->m_export.e_path); + if (!path) { + next_exp = exp->m_next; + prev = NULL; + continue; + } + next_exp = exp; + } else { + prev = exp; + mnt = NULL; + path = exp->m_export.e_path; + next_exp = exp; + } + } else { + path = exp->m_export.e_path; + next_exp = exp->m_next; + } + + if (!is_ipaddr_client(dom) + && !namelist_client_matches(exp, dom)) + continue; + if (exp->m_export.e_mountpoint && + !is_mountpoint(exp->m_export.e_mountpoint[0]? + exp->m_export.e_mountpoint: + exp->m_export.e_path)) + dev_missing ++; + + switch(match_fsid(&parsed, exp, path)) { + case 0: + continue; + case -1: + dev_missing ++; + continue; + } + if (is_ipaddr_client(dom) + && !ipaddr_client_matches(exp, ai)) + continue; + if (!found || subexport(&exp->m_export, found)) { + found = &exp->m_export; + free(found_path); + found_path = strdup(path); + if (found_path == NULL) + goto out; + } else if (strcmp(found->e_path, exp->m_export.e_path) != 0 + && !subexport(found, &exp->m_export)) + { + xlog(L_WARNING, "%s and %s have same filehandle for %s, using first", + found_path, path, dom); + } else { + /* same path, if one is V4ROOT, choose the other */ + if (found->e_flags & NFSEXP_V4ROOT) { + found = &exp->m_export; + free(found_path); + found_path = strdup(path); + if (found_path == NULL) + goto out; + } + } + } + } + + if (!found) { + /* The missing dev could be what we want, so just be + * quiet rather than returning stale yet + */ + if (dev_missing) { + ret = 1; + goto out; + } + } else if (found->e_mountpoint && + !is_mountpoint(found->e_mountpoint[0]? + found->e_mountpoint: + found->e_path)) { + /* Cannot export this yet + * should log a warning, but need to rate limit + xlog(L_WARNING, "%s not exported as %d not a mountpoint", + found->e_path, found->e_mountpoint); + */ + ret = 1; + goto out; + } + + bp = buf; blen = sizeof(buf); + qword_add(&bp, &blen, dom); + qword_addint(&bp, &blen, fsidtype); + qword_addhex(&bp, &blen, fsid, fsidlen); + /* The fsid -> path lookup can be quite expensive as it + * potentially stats and reads lots of devices, and some of those + * might have spun-down. The Answer is not likely to + * change underneath us, and an 'exportfs -f' can always + * remove this from the kernel, so use a really log + * timeout. Maybe this should be configurable on the command + * line. + */ + qword_addint(&bp, &blen, 0x7fffffff); + if (found) + qword_add(&bp, &blen, found_path); + qword_addeol(&bp, &blen); + if (blen <= 0 || cache_write(f, buf, bp - buf) != bp - buf) + xlog(L_ERROR, "nfsd_fh: error writing reply"); + if (!found) + xlog(D_AUTH, "denied access to %s", *dom == '$' ? dom+1 : dom); +out: + if (found_path) + free(found_path); + nfs_freeaddrinfo(ai); + free(dom); + if (!ret) + xlog(D_CALL, "nfsd_fh: found %p path %s", + found, found ? found->e_path : NULL); + return ret; +} + +static void nfsd_fh(int f) +{ + struct delayed *d, **dp; + char inbuf[RPC_CHAN_BUF_SIZE]; + int blen; + + blen = cache_read(f, inbuf, sizeof(inbuf)); + if (blen <= 0 || inbuf[blen-1] != '\n') return; + inbuf[blen-1] = 0; + + xlog(D_CALL, "nfsd_fh: inbuf '%s'", inbuf); + + if (nfsd_handle_fh(f, inbuf, blen) == 0) + return; + /* We don't have a definitive answer to give the kernel. + * This is because an export marked "mountpoint" isn't a + * mountpoint, or because a stat of a mountpoint fails with + * a strange error like ETIMEDOUT as is possible with an + * NFS mount marked "softerr" which is being re-exported. + * + * We cannot tell the kernel to retry, so we have to + * retry ourselves. + */ + d = malloc(sizeof(*d)); + + if (!d) + return; + d->message = strndup(inbuf, blen); + if (!d->message) { + free(d); + return; + } + d->f = f; + d->last_attempt = time(NULL); + d->next = NULL; + dp = &delayed; + while (*dp) + dp = &(*dp)->next; + *dp = d; +} + +static void nfsd_retry_fh(struct delayed *d) +{ + struct delayed **dp; + + if (nfsd_handle_fh(d->f, d->message, strlen(d->message)+1) == 0) { + free(d->message); + free(d); + return; + } + d->last_attempt = time(NULL); + d->next = NULL; + dp = &delayed; + while (*dp) + dp = &(*dp)->next; + *dp = d; +} + +#ifdef HAVE_JUNCTION_SUPPORT +static void write_fsloc(char **bp, int *blen, struct exportent *ep) +{ + struct servers *servers; + + if (ep->e_fslocmethod == FSLOC_NONE) + return; + + servers = replicas_lookup(ep->e_fslocmethod, ep->e_fslocdata); + if (!servers) + return; + qword_add(bp, blen, "fsloc"); + qword_addint(bp, blen, servers->h_num); + if (servers->h_num >= 0) { + int i; + for (i=0; i<servers->h_num; i++) { + qword_add(bp, blen, servers->h_mp[i]->h_host); + qword_add(bp, blen, servers->h_mp[i]->h_path); + } + } + qword_addint(bp, blen, servers->h_referral); + release_replicas(servers); +} +#endif + +static void write_secinfo(char **bp, int *blen, struct exportent *ep, int flag_mask, int extra_flag) +{ + struct sec_entry *p; + + for (p = ep->e_secinfo; p->flav; p++) + ; /* Do nothing */ + if (p == ep->e_secinfo) { + /* There was no sec= option */ + return; + } + fix_pseudoflavor_flags(ep); + qword_add(bp, blen, "secinfo"); + qword_addint(bp, blen, p - ep->e_secinfo); + for (p = ep->e_secinfo; p->flav; p++) { + qword_addint(bp, blen, p->flav->fnum); + qword_addint(bp, blen, (p->flags | extra_flag) & flag_mask); + } +} + +static void write_xprtsec(char **bp, int *blen, struct exportent *ep) +{ + struct xprtsec_entry *p; + + for (p = ep->e_xprtsec; p->info; p++); + if (p == ep->e_xprtsec) + return; + + qword_add(bp, blen, "xprtsec"); + qword_addint(bp, blen, p - ep->e_xprtsec); + for (p = ep->e_xprtsec; p->info; p++) + qword_addint(bp, blen, p->info->number); +} + +static int can_reexport_via_fsidnum(struct exportent *exp, struct statfs *st) +{ + if (st->f_type != 0x6969 /* NFS_SUPER_MAGIC */) + return 0; + + return exp->e_reexport == REEXP_PREDEFINED_FSIDNUM || + exp->e_reexport == REEXP_AUTO_FSIDNUM; +} + +static int dump_to_cache(int f, char *buf, int blen, char *domain, + char *path, struct exportent *exp, int ttl) +{ + char *bp = buf; + time_t now = time(0); + size_t buflen; + ssize_t err; + + if (ttl <= 1) + ttl = default_ttl; + + qword_add(&bp, &blen, domain); + qword_add(&bp, &blen, path); + if (exp) { + int different_fs = strcmp(path, exp->e_path) != 0; + int flag_mask = different_fs ? ~NFSEXP_FSID : ~0; + int rc, do_fsidnum = 0; + uint32_t fsidnum = exp->e_fsid; + + if (different_fs) { + struct statfs st; + + rc = nfsd_path_statfs(path, &st); + if (rc) { + xlog(L_WARNING, "unable to statfs %s", path); + errno = EINVAL; + return -1; + } + + if (can_reexport_via_fsidnum(exp, &st)) { + do_fsidnum = 1; + flag_mask = ~0; + } + } + + qword_adduint(&bp, &blen, now + exp->e_ttl); + + if (do_fsidnum) { + uint32_t search_fsidnum = 0; + if (exp->e_reexport != REEXP_NONE && reexpdb_fsidnum_by_path(path, &search_fsidnum, + exp->e_reexport == REEXP_AUTO_FSIDNUM) == 0) { + errno = EINVAL; + return -1; + } + fsidnum = search_fsidnum; + qword_addint(&bp, &blen, exp->e_flags | NFSEXP_FSID); + } else { + qword_addint(&bp, &blen, exp->e_flags & flag_mask); + } + + qword_addint(&bp, &blen, exp->e_anonuid); + qword_addint(&bp, &blen, exp->e_anongid); + qword_addint(&bp, &blen, fsidnum); + +#ifdef HAVE_JUNCTION_SUPPORT + write_fsloc(&bp, &blen, exp); +#endif + write_secinfo(&bp, &blen, exp, flag_mask, do_fsidnum ? NFSEXP_FSID : 0); + if (exp->e_uuid == NULL || different_fs) { + char u[16]; + if ((exp->e_flags & flag_mask & NFSEXP_FSID) == 0 && + uuid_by_path(path, 0, 16, u)) { + qword_add(&bp, &blen, "uuid"); + qword_addhex(&bp, &blen, u, 16); + } + } else { + char u[16]; + get_uuid(exp->e_uuid, 16, u); + qword_add(&bp, &blen, "uuid"); + qword_addhex(&bp, &blen, u, 16); + } + write_xprtsec(&bp, &blen, exp); + xlog(D_AUTH, "granted access to %s for %s", + path, *domain == '$' ? domain+1 : domain); + } else { + qword_adduint(&bp, &blen, now + ttl); + xlog(D_AUTH, "denied access to %s for %s", + path, *domain == '$' ? domain+1 : domain); + } + qword_addeol(&bp, &blen); + if (blen <= 0) { + errno = ENOBUFS; + return -1; + } + buflen = bp - buf; + err = cache_write(f, buf, buflen); + if (err < 0) + return err; + if ((size_t)err != buflen) { + errno = ENOSPC; + return -1; + } + return 0; +} + +static nfs_export * +lookup_export(char *dom, char *path, struct addrinfo *ai) +{ + nfs_export *exp; + nfs_export *found = NULL; + int found_type = 0; + int i; + + for (i=0 ; i < MCL_MAXTYPES; i++) { + for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { + if (!export_matches(exp, dom, path, ai)) + continue; + if (!found) { + found = exp; + found_type = i; + continue; + } + /* Always prefer non-V4ROOT exports */ + if (exp->m_export.e_flags & NFSEXP_V4ROOT) + continue; + if (found->m_export.e_flags & NFSEXP_V4ROOT) { + found = exp; + found_type = i; + continue; + } + + /* If one is a CROSSMOUNT, then prefer the longest path */ + if (((found->m_export.e_flags & NFSEXP_CROSSMOUNT) || + (exp->m_export.e_flags & NFSEXP_CROSSMOUNT)) && + strlen(found->m_export.e_path) != + strlen(exp->m_export.e_path)) { + + if (strlen(exp->m_export.e_path) > + strlen(found->m_export.e_path)) { + found = exp; + found_type = i; + } + continue; + + } else if (found_type == i && found->m_warned == 0) { + xlog(L_WARNING, "%s exported to both %s and %s, " + "arbitrarily choosing options from first", + path, found->m_client->m_hostname, exp->m_client->m_hostname); + found->m_warned = 1; + } + } + } + return found; +} + +#ifdef HAVE_JUNCTION_SUPPORT + +#include <libxml/parser.h> +#include "junction.h" + +struct nfs_fsloc_set { + int ns_ttl; + struct nfs_fsloc *ns_current; + struct nfs_fsloc *ns_list; +}; + +/* + * Find the export entry for the parent of "pathname". + * Caller must not free returned exportent. + */ +static struct exportent *lookup_parent_export(char *dom, + const char *pathname, struct addrinfo *ai) +{ + char *parent, *slash; + nfs_export *result; + + parent = strdup(pathname); + if (parent == NULL) { + xlog(D_GENERAL, "%s: failed to allocate parent path buffer", + __func__); + goto out_default; + } + xlog(D_CALL, "%s: pathname = '%s'", __func__, pathname); + +again: + /* shorten pathname by one component */ + slash = strrchr(parent, '/'); + if (slash == NULL) { + xlog(D_GENERAL, "%s: no slash found in pathname", + __func__); + goto out_default; + } + *slash = '\0'; + + if (strlen(parent) == 0) { + result = lookup_export(dom, "/", ai); + if (result == NULL) { + xlog(L_ERROR, "%s: no root export found.", __func__); + goto out_default; + } + goto out; + } + + result = lookup_export(dom, parent, ai); + if (result == NULL) { + xlog(D_GENERAL, "%s: lookup_export(%s) found nothing", + __func__, parent); + goto again; + } + +out: + xlog(D_CALL, "%s: found export for %s", __func__, parent); + free(parent); + return &result->m_export; + +out_default: + free(parent); + return mkexportent("*", "/", "insecure"); +} + +static int get_next_location(struct nfs_fsloc_set *locset, + char **hostname, char **export_path, int *ttl) +{ + char *hostname_tmp, *export_path_tmp; + struct nfs_fsloc *fsloc; + + if (locset->ns_current == NULL) + return ENOENT; + fsloc = locset->ns_current; + + hostname_tmp = strdup(fsloc->nfl_hostname); + if (hostname_tmp == NULL) + return ENOMEM; + + if (nsdb_path_array_to_posix(fsloc->nfl_rootpath, + &export_path_tmp)) { + free(hostname_tmp); + return EINVAL; + } + + *hostname = hostname_tmp; + *export_path = export_path_tmp; + *ttl = locset->ns_ttl; + locset->ns_current = locset->ns_current->nfl_next; + return 0; +} + +/* + * Walk through a set of FS locations and build an e_fslocdata string. + * Returns true if all went to plan; otherwise, false. + */ +static bool locations_to_fslocdata(struct nfs_fsloc_set *locations, + char *fslocdata, size_t remaining, int *ttl) +{ + char *server, *last_path, *rootpath, *ptr; + _Bool seen = false; + + last_path = NULL; + rootpath = NULL; + server = NULL; + ptr = fslocdata; + *ttl = 0; + + for (;;) { + int len, status; + + status = get_next_location(locations, &server, + &rootpath, ttl); + if (status == ENOENT) + break; + if (status) { + xlog(D_GENERAL, "%s: failed to parse location: %s", + __func__, strerror(status)); + goto out_false; + } + xlog(D_GENERAL, "%s: Location: %s:%s", + __func__, server, rootpath); + + if (last_path && strcmp(rootpath, last_path) == 0) { + len = snprintf(ptr, remaining, "+%s", server); + if (len < 0) { + xlog(D_GENERAL, "%s: snprintf: %m", __func__); + goto out_false; + } + if ((size_t)len >= remaining) { + xlog(D_GENERAL, "%s: fslocdata buffer overflow", __func__); + goto out_false; + } + remaining -= (size_t)len; + ptr += len; + } else { + if (last_path == NULL) + len = snprintf(ptr, remaining, "%s@%s", + rootpath, server); + else + len = snprintf(ptr, remaining, ":%s@%s", + rootpath, server); + if (len < 0) { + xlog(D_GENERAL, "%s: snprintf: %m", __func__); + goto out_false; + } + if ((size_t)len >= remaining) { + xlog(D_GENERAL, "%s: fslocdata buffer overflow", + __func__); + goto out_false; + } + remaining -= (size_t)len; + ptr += len; + last_path = rootpath; + } + + seen = true; + free(rootpath); + free(server); + } + + xlog(D_CALL, "%s: fslocdata='%s', ttl=%d", + __func__, fslocdata, *ttl); + return seen; + +out_false: + free(rootpath); + free(server); + return false; +} + +/* + * Duplicate the junction's parent's export options and graft in + * the fslocdata we constructed from the locations list. + */ +static struct exportent *create_junction_exportent(struct exportent *parent, + const char *junction, const char *fslocdata, int ttl) +{ + static struct exportent *eep; + + eep = (struct exportent *)malloc(sizeof(*eep)); + if (eep == NULL) + goto out_nomem; + + dupexportent(eep, parent); + strcpy(eep->e_path, junction); + eep->e_hostname = strdup(parent->e_hostname); + if (eep->e_hostname == NULL) { + free(eep); + goto out_nomem; + } + free(eep->e_uuid); + eep->e_uuid = NULL; + eep->e_ttl = (unsigned int)ttl; + + free(eep->e_fslocdata); + eep->e_fslocdata = strdup(fslocdata); + if (eep->e_fslocdata == NULL) { + free(eep->e_hostname); + free(eep); + goto out_nomem; + } + eep->e_fslocmethod = FSLOC_REFER; + return eep; + +out_nomem: + xlog(L_ERROR, "%s: No memory", __func__); + return NULL; +} + +/* + * Walk through the set of FS locations and build an exportent. + * Returns pointer to an exportent if "junction" refers to a junction. + */ +static struct exportent *locations_to_export(struct nfs_fsloc_set *locations, + const char *junction, struct exportent *parent) +{ + static char fslocdata[BUFSIZ]; + int ttl; + + fslocdata[0] = '\0'; + if (!locations_to_fslocdata(locations, fslocdata, sizeof(fslocdata), &ttl)) + return NULL; + return create_junction_exportent(parent, junction, fslocdata, ttl); +} + +static int +nfs_get_basic_junction(const char *junct_path, struct nfs_fsloc_set **locset) +{ + struct nfs_fsloc_set *new; + FedFsStatus retval; + + new = calloc(1, sizeof(struct nfs_fsloc_set)); + if (new == NULL) + return ENOMEM; + + retval = nfs_get_locations(junct_path, &new->ns_list); + if (retval) { + nfs_free_locations(new->ns_list); + free(new); + return EINVAL; + } + + new->ns_current = new->ns_list; + new->ns_ttl = 300; + *locset = new; + return 0; +} + +static struct exportent *lookup_junction(char *dom, const char *pathname, + struct addrinfo *ai) +{ + struct exportent *parent, *exp = NULL; + struct nfs_fsloc_set *locations; + int status; + + xmlInitParser(); + + if (nfs_is_junction(pathname)) { + xlog(D_GENERAL, "%s: %s is not a junction", + __func__, pathname); + goto out; + } + status = nfs_get_basic_junction(pathname, &locations); + if (status) { + xlog(L_WARNING, "Dangling junction %s: %s", + pathname, strerror(status)); + goto out; + } + + parent = lookup_parent_export(dom, pathname, ai); + if (parent == NULL) + goto free_locations; + + exp = locations_to_export(locations, pathname, parent); + +free_locations: + nfs_free_locations(locations->ns_list); + free(locations); + +out: + xmlCleanupParser(); + return exp; +} + +static void lookup_nonexport(int f, char *buf, int buflen, char *dom, char *path, + struct addrinfo *ai) +{ + struct exportent *eep; + + eep = lookup_junction(dom, path, ai); + dump_to_cache(f, buf, buflen, dom, path, eep, 0); + if (eep == NULL) + return; + exportent_release(eep); + free(eep); +} + +#else /* !HAVE_JUNCTION_SUPPORT */ + +static void lookup_nonexport(int f, char *buf, int buflen, char *dom, char *path, + struct addrinfo *UNUSED(ai)) +{ + dump_to_cache(f, buf, buflen, dom, path, NULL, 0); +} + +#endif /* !HAVE_JUNCTION_SUPPORT */ + +static void nfsd_export(int f) +{ + /* requests are: + * domain path + * determine export options and return: + * domain path expiry flags anonuid anongid fsid + */ + + char *dom, *path; + nfs_export *found = NULL; + struct addrinfo *ai = NULL; + char buf[RPC_CHAN_BUF_SIZE], *bp; + int blen; + + blen = cache_read(f, buf, sizeof(buf)); + if (blen <= 0 || buf[blen-1] != '\n') return; + buf[blen-1] = 0; + + xlog(D_CALL, "nfsd_export: inbuf '%s'", buf); + + bp = buf; + dom = malloc(blen); + path = malloc(blen); + + if (!dom || !path) + goto out; + + if (qword_get(&bp, dom, blen) <= 0) + goto out; + if (qword_get(&bp, path, blen) <= 0) + goto out; + + auth_reload(); + + if (is_ipaddr_client(dom)) { + ai = lookup_client_addr(dom); + if (!ai) + goto out; + } + + found = lookup_export(dom, path, ai); + + if (found) { + char *mp = found->m_export.e_mountpoint; + + if (mp && !*mp) + mp = found->m_export.e_path; + errno = 0; + if (mp && !is_mountpoint(mp)) { + if (errno != 0 && !path_lookup_error(errno)) + goto out; + /* Exportpoint is not mounted, so tell kernel it is + * not available. + * This will cause it not to appear in the V4 Pseudo-root + * and so a "mount" of this path will fail, just like with + * V3. + * Any filehandle for this mountpoint from an earlier + * mount will block in nfsd.fh lookup. + */ + xlog(L_WARNING, + "Cannot export path '%s': not a mountpoint", + path); + dump_to_cache(f, buf, sizeof(buf), dom, path, + NULL, 60); + } else if (dump_to_cache(f, buf, sizeof(buf), dom, path, + &found->m_export, 0) < 0) { + xlog(L_WARNING, + "Cannot export %s, possibly unsupported filesystem" + " or fsid= required", path); + dump_to_cache(f, buf, sizeof(buf), dom, path, NULL, 0); + } + } else + lookup_nonexport(f, buf, sizeof(buf), dom, path, ai); + + out: + xlog(D_CALL, "nfsd_export: found %p path %s", found, path ? path : NULL); + if (dom) free(dom); + if (path) free(path); + nfs_freeaddrinfo(ai); +} + + +struct { + char *cache_name; + void (*cache_handle)(int f); + int f; +} cachelist[] = { + { "auth.unix.ip", auth_unix_ip, -1 }, + { "auth.unix.gid", auth_unix_gid, -1 }, + { "nfsd.export", nfsd_export, -1 }, + { "nfsd.fh", nfsd_fh, -1 }, + { NULL, NULL, -1 } +}; + +extern int manage_gids; + +/** + * cache_open - prepare communications channels with kernel RPC caches + * + */ +void cache_open(void) +{ + int i; + + for (i=0; cachelist[i].cache_name; i++ ) { + char path[100]; + if (!manage_gids && cachelist[i].cache_handle == auth_unix_gid) + continue; + sprintf(path, "/proc/net/rpc/%s/channel", cachelist[i].cache_name); + cachelist[i].f = open(path, O_RDWR); + } +} + +/** + * cache_set_fds - prepare cache file descriptors for one iteration of the service loop + * @fdset: pointer to fd_set to prepare + */ +void cache_set_fds(fd_set *fdset) +{ + int i; + for (i=0; cachelist[i].cache_name; i++) { + if (cachelist[i].f >= 0) + FD_SET(cachelist[i].f, fdset); + } +} + +/** + * cache_process_req - process any active cache file descriptors during service loop iteration + * @fdset: pointer to fd_set to examine for activity + */ +int cache_process_req(fd_set *readfds) +{ + int i; + int cnt = 0; + for (i=0; cachelist[i].cache_name; i++) { + if (cachelist[i].f >= 0 && + FD_ISSET(cachelist[i].f, readfds)) { + cnt++; + cachelist[i].cache_handle(cachelist[i].f); + FD_CLR(cachelist[i].f, readfds); + } + } + return cnt; +} + +/** + * cache_process - process incoming upcalls + * Returns -ve on error, or number of fds in svc_fds + * that might need processing. + */ +int cache_process(fd_set *readfds) +{ + fd_set fdset; + int selret; + struct timeval tv = { 24*3600, 0 }; + + if (!readfds) { + FD_ZERO(&fdset); + readfds = &fdset; + } + cache_set_fds(readfds); + v4clients_set_fds(readfds); + + if (delayed) { + time_t now = time(NULL); + time_t delay; + if (delayed->last_attempt > now) + /* Clock updated - retry immediately */ + delayed->last_attempt = now - RETRY_SEC; + delay = delayed->last_attempt + RETRY_SEC - now; + if (delay < 0) + delay = 0; + tv.tv_sec = delay; + } + selret = select(FD_SETSIZE, readfds, NULL, NULL, &tv); + + if (delayed) { + time_t now = time(NULL); + struct delayed *d = delayed; + + if (d->last_attempt + RETRY_SEC <= now) { + delayed = d->next; + d->next = NULL; + nfsd_retry_fh(d); + } + } + + switch (selret) { + case -1: + if (errno == EINTR || errno == ECONNREFUSED + || errno == ENETUNREACH || errno == EHOSTUNREACH) + return 0; + return -1; + + default: + selret -= cache_process_req(readfds); + selret -= v4clients_process(readfds); + if (selret < 0) + selret = 0; + } + return selret; +} + +/* + * Give IP->domain and domain+path->options to kernel + * % echo nfsd $IP $[now+DEFAULT_TTL] $domain > /proc/net/rpc/auth.unix.ip/channel + * % echo $domain $path $[now+DEFAULT_TTL] $options $anonuid $anongid $fsid > /proc/net/rpc/nfsd.export/channel + */ + +static int cache_export_ent(char *buf, int buflen, char *domain, struct exportent *exp, char *path) +{ + int f, err; + + f = open("/proc/net/rpc/nfsd.export/channel", O_WRONLY); + if (f < 0) return -1; + + err = dump_to_cache(f, buf, buflen, domain, exp->e_path, exp, 0); + if (err) { + xlog(L_WARNING, + "Cannot export %s, possibly unsupported filesystem or" + " fsid= required", exp->e_path); + } + + while (err == 0 && (exp->e_flags & NFSEXP_CROSSMOUNT) && path) { + /* really an 'if', but we can break out of + * a 'while' more easily */ + /* Look along 'path' for other filesystems + * and export them with the same options + */ + struct stat stb; + size_t l = strlen(exp->e_path); + dev_t dev; + + if (strlen(path) <= l || path[l] != '/' || + strncmp(exp->e_path, path, l) != 0) + break; + if (nfsd_path_stat(exp->e_path, &stb) != 0) + break; + dev = stb.st_dev; + while(path[l] == '/') { + char c; + /* errors for submount should fail whole filesystem */ + int err2; + + l++; + while (path[l] != '/' && path[l]) + l++; + c = path[l]; + path[l] = 0; + err2 = nfsd_path_lstat(path, &stb); + path[l] = c; + if (err2 < 0) + break; + if (stb.st_dev == dev) + continue; + dev = stb.st_dev; + path[l] = 0; + dump_to_cache(f, buf, buflen, domain, path, exp, 0); + path[l] = c; + } + break; + } + + close(f); + return err; +} + +/** + * cache_export - Inform kernel of a new nfs_export + * @exp: target nfs_export + * @path: NUL-terminated C string containing export path + */ +int cache_export(nfs_export *exp, char *path) +{ + char ip[INET6_ADDRSTRLEN]; + char buf[RPC_CHAN_BUF_SIZE], *bp; + int blen, f; + + f = open("/proc/net/rpc/auth.unix.ip/channel", O_WRONLY); + if (f < 0) + return -1; + + bp = buf, blen = sizeof(buf); + qword_add(&bp, &blen, "nfsd"); + qword_add(&bp, &blen, host_ntop(get_addrlist(exp->m_client, 0), ip, sizeof(ip))); + qword_adduint(&bp, &blen, time(0) + exp->m_export.e_ttl); + qword_add(&bp, &blen, exp->m_client->m_hostname); + qword_addeol(&bp, &blen); + if (blen <= 0 || cache_write(f, buf, bp - buf) != bp - buf) blen = -1; + close(f); + if (blen < 0) return -1; + + return cache_export_ent(buf, sizeof(buf), exp->m_client->m_hostname, &exp->m_export, path); +} + +/** + * cache_get_filehandle - given an nfs_export, get its root filehandle + * @exp: target nfs_export + * @len: length of requested file handle + * @p: NUL-terminated C string containing export path + * + * Returns pointer to NFS file handle of root directory of export + * + * { + * echo $domain $path $length + * read filehandle <&0 + * } <> /proc/fs/nfsd/filehandle + */ +struct nfs_fh_len * +cache_get_filehandle(nfs_export *exp, int len, char *p) +{ + static struct nfs_fh_len fh; + char buf[RPC_CHAN_BUF_SIZE], *bp; + int blen, f; + + f = open("/proc/fs/nfsd/filehandle", O_RDWR); + if (f < 0) { + f = open("/proc/fs/nfs/filehandle", O_RDWR); + if (f < 0) return NULL; + } + + bp = buf, blen = sizeof(buf); + qword_add(&bp, &blen, exp->m_client->m_hostname); + qword_add(&bp, &blen, p); + qword_addint(&bp, &blen, len); + qword_addeol(&bp, &blen); + if (blen <= 0 || cache_write(f, buf, bp - buf) != bp - buf) { + close(f); + return NULL; + } + bp = buf; + blen = cache_read(f, buf, sizeof(buf)); + close(f); + + if (blen <= 0 || buf[blen-1] != '\n') + return NULL; + buf[blen-1] = 0; + + memset(fh.fh_handle, 0, sizeof(fh.fh_handle)); + fh.fh_size = qword_get(&bp, (char *)fh.fh_handle, NFS3_FHSIZE); + return &fh; +} + +/* Wait for all worker child processes to exit and reap them */ +void +cache_wait_for_workers(char *prog) +{ + int status; + pid_t pid; + + for (;;) { + + pid = waitpid(0, &status, 0); + + if (pid < 0) { + if (errno == ECHILD) + return; /* no more children */ + xlog(L_FATAL, "%s: can't wait: %s\n", prog, + strerror(errno)); + } + + /* Note: because we SIG_IGN'd SIGCHLD earlier, this + * does not happen on 2.6 kernels, and waitpid() blocks + * until all the children are dead then returns with + * -ECHILD. But, we don't need to do anything on the + * death of individual workers, so we don't care. */ + xlog(L_NOTICE, "%s: reaped child %d, status %d\n", + prog, (int)pid, status); + } +} + +/* Fork num_threads worker children and wait for them */ +int +cache_fork_workers(char *prog, int num_threads) +{ + int i; + pid_t pid; + + if (num_threads <= 1) + return 1; + + xlog(L_NOTICE, "%s: starting %d threads\n", prog, num_threads); + + for (i = 0 ; i < num_threads ; i++) { + pid = fork(); + if (pid < 0) { + xlog(L_FATAL, "%s: cannot fork: %s\n", prog, + strerror(errno)); + } + if (pid == 0) { + /* worker child */ + + /* Re-enable the default action on SIGTERM et al + * so that workers die naturally when sent them. + * Only the parent unregisters with pmap and + * hence needs to do special SIGTERM handling. */ + struct sigaction sa; + sa.sa_handler = SIG_DFL; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + /* fall into my_svc_run in caller */ + return 1; + } + } + + /* in parent */ + cache_wait_for_workers(prog); + return 0; +} diff --git a/support/export/client.c b/support/export/client.c new file mode 100644 index 0000000..79164fe --- /dev/null +++ b/support/export/client.c @@ -0,0 +1,800 @@ +/* + * support/export/client.c + * + * Maintain list of nfsd clients. + * + * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <netdb.h> +#include <errno.h> + +#include "sockaddr.h" +#include "misc.h" +#include "nfslib.h" +#include "exportfs.h" + +/* netgroup stuff never seems to be defined in any header file. Linux is + * not alone in this. + */ +#if !defined(__GLIBC__) || __GLIBC__ < 2 +extern int innetgr(char *netgr, char *host, char *, char *); +#endif + +static char *add_name(char *old, const char *add); + +nfs_client *clientlist[MCL_MAXTYPES] = { NULL, }; + + +static void +init_addrlist(nfs_client *clp, const struct addrinfo *ai) +{ + int i; + + if (ai == NULL) + return; + + for (i = 0; (ai != NULL) && (i < NFSCLNT_ADDRMAX); i++) { + set_addrlist(clp, i, ai->ai_addr); + ai = ai->ai_next; + } + + clp->m_naddr = i; +} + +static void +client_free(nfs_client *clp) +{ + free(clp->m_hostname); + free(clp); +} + +static int +init_netmask4(nfs_client *clp, const char *slash) +{ + struct sockaddr_in sin = { + .sin_family = AF_INET, + }; + uint32_t shift; + + /* + * Decide what kind of netmask was specified. If there's + * no '/' present, assume the netmask is all ones. If + * there is a '/' and at least one '.', look for a spelled- + * out netmask. Otherwise, assume it was a prefixlen. + */ + if (slash == NULL) + shift = 0; + else { + unsigned long prefixlen; + + if (strchr(slash + 1, '.') != NULL) { + if (inet_pton(AF_INET, slash + 1, + &sin.sin_addr.s_addr) == 0) + goto out_badmask; + set_addrlist_in(clp, 1, &sin); + return 1; + } else { + char *endptr; + + prefixlen = strtoul(slash + 1, &endptr, 10); + if (*endptr != '\0' && prefixlen != ULONG_MAX && + errno != ERANGE) + goto out_badprefix; + } + if (prefixlen > 32) + goto out_badprefix; + shift = 32 - (uint32_t)prefixlen; + } + + /* + * Now construct the full netmask bitmask in a sockaddr_in, + * and plant it in the nfs_client record. + */ + sin.sin_addr.s_addr = htonl((uint32_t)~0 << shift); + set_addrlist_in(clp, 1, &sin); + + return 1; + +out_badmask: + xlog(L_ERROR, "Invalid netmask `%s' for %s", slash + 1, clp->m_hostname); + return 0; + +out_badprefix: + xlog(L_ERROR, "Invalid prefix `%s' for %s", slash + 1, clp->m_hostname); + return 0; +} + +#ifdef IPV6_SUPPORTED +static int +init_netmask6(nfs_client *clp, const char *slash) +{ + struct sockaddr_in6 sin6 = { + .sin6_family = AF_INET6, + }; + unsigned long prefixlen; + uint32_t shift; + int i; + + /* + * Decide what kind of netmask was specified. If there's + * no '/' present, assume the netmask is all ones. If + * there is a '/' and at least one ':', look for a spelled- + * out netmask. Otherwise, assume it was a prefixlen. + */ + if (slash == NULL) + prefixlen = 128; + else { + if (strchr(slash + 1, ':') != NULL) { + if (!inet_pton(AF_INET6, slash + 1, &sin6.sin6_addr)) + goto out_badmask; + set_addrlist_in6(clp, 1, &sin6); + return 1; + } else { + char *endptr; + + prefixlen = strtoul(slash + 1, &endptr, 10); + if (*endptr != '\0' && prefixlen != ULONG_MAX && + errno != ERANGE) + goto out_badprefix; + } + if (prefixlen > 128) + goto out_badprefix; + } + + /* + * Now construct the full netmask bitmask in a sockaddr_in6, + * and plant it in the nfs_client record. + */ + for (i = 0; prefixlen > 32; i++) { + sin6.sin6_addr.s6_addr32[i] = 0xffffffff; + prefixlen -= 32; + } + shift = 32 - (uint32_t)prefixlen; + sin6.sin6_addr.s6_addr32[i] = htonl((uint32_t)~0 << shift); + set_addrlist_in6(clp, 1, &sin6); + + return 1; + +out_badmask: + xlog(L_ERROR, "Invalid netmask `%s' for %s", slash + 1, clp->m_hostname); + return 0; + +out_badprefix: + xlog(L_ERROR, "Invalid prefix `%s' for %s", slash + 1, clp->m_hostname); + return 0; +} +#else /* IPV6_SUPPORTED */ +static int +init_netmask6(nfs_client *UNUSED(clp), const char *UNUSED(slash)) +{ + return 0; +} +#endif /* IPV6_SUPPORTED */ + +/* + * Parse the network mask for M_SUBNETWORK type clients. + * + * Return TRUE if successful, or FALSE if some error occurred. + */ +static int +init_subnetwork(nfs_client *clp) +{ + struct addrinfo *ai; + sa_family_t family; + int result = 0; + char *slash; + + slash = strchr(clp->m_hostname, '/'); + if (slash != NULL) { + *slash = '\0'; + ai = host_pton(clp->m_hostname); + *slash = '/'; + } else + ai = host_pton(clp->m_hostname); + if (ai == NULL) { + xlog(L_ERROR, "Invalid IP address %s", clp->m_hostname); + return result; + } + + set_addrlist(clp, 0, ai->ai_addr); + family = ai->ai_addr->sa_family; + + nfs_freeaddrinfo(ai); + + switch (family) { + case AF_INET: + result = init_netmask4(clp, slash); + break; + case AF_INET6: + result = init_netmask6(clp, slash); + break; + default: + xlog(L_ERROR, "Unsupported address family for %s", + clp->m_hostname); + } + + return result; +} + +static int +client_init(nfs_client *clp, const char *hname, const struct addrinfo *ai) +{ + clp->m_hostname = strdup(hname); + if (clp->m_hostname == NULL) + return 0; + + clp->m_exported = 0; + clp->m_count = 0; + clp->m_naddr = 0; + + if (clp->m_type == MCL_SUBNETWORK) + return init_subnetwork(clp); + + init_addrlist(clp, ai); + return 1; +} + +static void +client_add(nfs_client *clp) +{ + nfs_client **cpp; + + cpp = &clientlist[clp->m_type]; + while (*cpp != NULL) + cpp = &((*cpp)->m_next); + clp->m_next = NULL; + *cpp = clp; +} + +/** + * client_lookup - look for @hname in our list of cached nfs_clients + * @hname: '\0'-terminated ASCII string containing hostname to look for + * @canonical: if set, @hname is known to be canonical DNS name + * + * Returns pointer to a matching or freshly created nfs_client. NULL + * is returned if some problem occurs. + */ +nfs_client * +client_lookup(char *hname, int canonical) +{ + nfs_client *clp = NULL; + int htype; + struct addrinfo *ai = NULL; + + htype = client_gettype(hname); + + if (htype == MCL_FQDN && !canonical) { + ai = host_addrinfo(hname); + if (!ai) { + xlog(L_WARNING, "Failed to resolve %s", hname); + goto out; + } + hname = ai->ai_canonname; + + for (clp = clientlist[htype]; clp; clp = clp->m_next) + if (client_check(clp, ai)) + break; + } else { + for (clp = clientlist[htype]; clp; clp = clp->m_next) { + if (strcasecmp(hname, clp->m_hostname)==0) + break; + } + } + + if (clp == NULL) { + clp = calloc(1, sizeof(*clp)); + if (clp == NULL) + goto out; + clp->m_type = htype; + if (!client_init(clp, hname, NULL)) { + client_free(clp); + clp = NULL; + goto out; + } + client_add(clp); + } + + if (htype == MCL_FQDN && clp->m_naddr == 0) + init_addrlist(clp, ai); + +out: + nfs_freeaddrinfo(ai); + return clp; +} + +/** + * client_dup - create a copy of an nfs_client + * @clp: pointer to nfs_client to copy + * @ai: pointer to addrinfo used to initialize the new client's addrlist + * + * Returns a dynamically allocated nfs_client if successful, or + * NULL if some problem occurs. Caller must free the returned + * nfs_client with free(3). + */ +nfs_client * +client_dup(const nfs_client *clp, const struct addrinfo *ai) +{ + nfs_client *new; + + new = (nfs_client *)malloc(sizeof(*new)); + if (new == NULL) + return NULL; + memcpy(new, clp, sizeof(*new)); + new->m_type = MCL_FQDN; + new->m_hostname = NULL; + + if (!client_init(new, ai->ai_canonname, ai)) { + client_free(new); + return NULL; + } + client_add(new); + return new; +} + +/** + * client_release - drop a reference to an nfs_client record + * + */ +void +client_release(nfs_client *clp) +{ + if (clp->m_count <= 0) + xlog(L_FATAL, "client_free: m_count <= 0!"); + clp->m_count--; +} + +/** + * client_freeall - deallocate all nfs_client records + * + */ +void +client_freeall(void) +{ + nfs_client *clp, **head; + int i; + + for (i = 0; i < MCL_MAXTYPES; i++) { + head = clientlist + i; + while (*head) { + *head = (clp = *head)->m_next; + client_free(clp); + } + } +} + +/** + * client_resolve - look up an IP address + * @sap: pointer to socket address to resolve + * + * Returns an addrinfo structure, or NULL if some problem occurred. + * Caller must free the result with freeaddrinfo(3). + */ +struct addrinfo * +client_resolve(const struct sockaddr *sap) +{ + struct addrinfo *ai = NULL; + + if (clientlist[MCL_WILDCARD] || clientlist[MCL_NETGROUP]) + ai = host_reliable_addrinfo(sap); + if (ai == NULL) + ai = host_numeric_addrinfo(sap); + + return ai; +} + +/** + * client_compose - Make a list of cached hostnames that match an IP address + * @ai: pointer to addrinfo containing IP address information to match + * + * Gather all known client hostnames that match the IP address, and sort + * the result into a comma-separated list. + * + * Returns a '\0'-terminated ASCII string containing a comma-separated + * sorted list of client hostnames, or NULL if no client records matched + * the IP address or memory could not be allocated. Caller must free the + * returned string with free(3). + */ +char * +client_compose(const struct addrinfo *ai) +{ + char *name = NULL; + int i; + + for (i = 0 ; i < MCL_MAXTYPES; i++) { + nfs_client *clp; + for (clp = clientlist[i]; clp ; clp = clp->m_next) { + if (!client_check(clp, ai)) + continue; + name = add_name(name, clp->m_hostname); + } + } + return name; +} + +/** + * client_member - check if @name is contained in the list @client + * @client: '\0'-terminated ASCII string containing + * comma-separated list of hostnames + * @name: '\0'-terminated ASCII string containing hostname to look for + * + * Returns 1 if @name was found in @client, otherwise zero is returned. + */ +int +client_member(const char *client, const char *name) +{ + size_t l = strlen(name); + + while (*client) { + if (strncmp(client, name, l) == 0 && + (client[l] == ',' || client[l] == '\0')) + return 1; + client = strchr(client, ','); + if (client == NULL) + return 0; + client++; + } + return 0; +} + +static int +name_cmp(const char *a, const char *b) +{ + /* compare strings a and b, but only upto ',' in a */ + while (*a && *b && *a != ',' && *a == *b) + a++, b++; + if (!*b && (!*a || *a == ',')) + return 0; + if (!*b) return 1; + if (!*a || *a == ',') return -1; + return *a - *b; +} + +static char * +add_name(char *old, const char *add) +{ + size_t len = strlen(add) + 2; + char *new; + char *cp; + if (old) len += strlen(old); + + new = malloc(len); + if (!new) { + free(old); + return NULL; + } + cp = old; + while (cp && *cp && name_cmp(cp, add) < 0) { + /* step cp forward over a name */ + char *e = strchr(cp, ','); + if (e) + cp = e+1; + else + cp = cp + strlen(cp); + } + len = cp-old; + if (old && len > 0) { + strncpy(new, old, len); + new[cp-old] = 0; + } else { + new[0] = 0; + } + if (cp != old && !*cp) + strcat(new, ","); + strcat(new, add); + if (cp && *cp) { + strcat(new, ","); + strcat(new, cp); + } + free(old); + return new; +} + +/* + * Check each address listed in @ai against each address + * stored in @clp. Return 1 if a match is found, otherwise + * zero. + */ +static int +check_fqdn(const nfs_client *clp, const struct addrinfo *ai) +{ + int i; + + for (; ai; ai = ai->ai_next) + for (i = 0; i < clp->m_naddr; i++) + if (nfs_compare_sockaddr(ai->ai_addr, + get_addrlist(clp, i))) + return 1; + + return 0; +} + +static _Bool +mask_match(const uint32_t a, const uint32_t b, const uint32_t m) +{ + return ((a ^ b) & m) == 0; +} + +static int +check_subnet_v4(const struct sockaddr_in *address, + const struct sockaddr_in *mask, const struct addrinfo *ai) +{ + for (; ai; ai = ai->ai_next) { + struct sockaddr_in *sin = (struct sockaddr_in *)ai->ai_addr; + + if (sin->sin_family != AF_INET) + continue; + + if (mask_match(address->sin_addr.s_addr, + sin->sin_addr.s_addr, + mask->sin_addr.s_addr)) + return 1; + } + return 0; +} + +#ifdef IPV6_SUPPORTED +static int +check_subnet_v6(const struct sockaddr_in6 *address, + const struct sockaddr_in6 *mask, const struct addrinfo *ai) +{ + for (; ai; ai = ai->ai_next) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ai->ai_addr; + + if (sin6->sin6_family != AF_INET6) + continue; + + if (mask_match(address->sin6_addr.s6_addr32[0], + sin6->sin6_addr.s6_addr32[0], + mask->sin6_addr.s6_addr32[0]) && + mask_match(address->sin6_addr.s6_addr32[1], + sin6->sin6_addr.s6_addr32[1], + mask->sin6_addr.s6_addr32[1]) && + mask_match(address->sin6_addr.s6_addr32[2], + sin6->sin6_addr.s6_addr32[2], + mask->sin6_addr.s6_addr32[2]) && + mask_match(address->sin6_addr.s6_addr32[3], + sin6->sin6_addr.s6_addr32[3], + mask->sin6_addr.s6_addr32[3])) + return 1; + } + return 0; +} +#else /* !IPV6_SUPPORTED */ +static int +check_subnet_v6(const struct sockaddr_in6 *UNUSED(address), + const struct sockaddr_in6 *UNUSED(mask), + const struct addrinfo *UNUSED(ai)) +{ + return 0; +} +#endif /* !IPV6_SUPPORTED */ + +/* + * Check each address listed in @ai against the subnetwork or + * host address stored in @clp. Return 1 if an address in @hp + * matches the host address stored in @clp, otherwise zero. + */ +static int +check_subnetwork(const nfs_client *clp, const struct addrinfo *ai) +{ + switch (get_addrlist(clp, 0)->sa_family) { + case AF_INET: + return check_subnet_v4(get_addrlist_in(clp, 0), + get_addrlist_in(clp, 1), ai); + case AF_INET6: + return check_subnet_v6(get_addrlist_in6(clp, 0), + get_addrlist_in6(clp, 1), ai); + } + + return 0; +} + +/* + * Check if a wildcard nfs_client record matches the canonical name + * or the aliases of a host. Return 1 if a match is found, otherwise + * zero. + */ +static int +check_wildcard(const nfs_client *clp, const struct addrinfo *ai) +{ + char *hname, *cname = clp->m_hostname; + struct hostent *hp; + char **ap; + int match; + + match = 0; + + hname = host_canonname(ai->ai_addr); + if (hname == NULL) + goto out; + + if (wildmat(hname, cname)) { + match = 1; + goto out; + } + + /* See if hname aliases listed in /etc/hosts or nis[+] + * match the requested wildcard */ + hp = gethostbyname(hname); + if (hp != NULL) { + for (ap = hp->h_aliases; *ap; ap++) + if (wildmat(*ap, cname)) { + match = 1; + goto out; + } + } + +out: + free(hname); + return match; +} + +/* + * Check if @ai's hostname or aliases fall in a given netgroup. + * Return 1 if @ai represents a host in the netgroup, otherwise + * zero. + */ +#ifdef HAVE_INNETGR +static int +check_netgroup(const nfs_client *clp, const struct addrinfo *ai) +{ + const char *netgroup = clp->m_hostname + 1; + struct addrinfo *tmp = NULL; + struct hostent *hp; + char *dot, *hname, *ip; + int i, match; + + match = 0; + + hname = host_canonname(ai->ai_addr); + if (hname == NULL) + goto out; + + /* First, try to match the hostname without + * splitting off the domain */ + if (innetgr(netgroup, hname, NULL, NULL)) { + match = 1; + goto out; + } + + /* See if hname aliases listed in /etc/hosts or nis[+] + * match the requested netgroup */ + hp = gethostbyname(hname); + if (hp != NULL) { + for (i = 0; hp->h_aliases[i]; i++) + if (innetgr(netgroup, hp->h_aliases[i], NULL, NULL)) { + match = 1; + goto out; + } + } + + /* If hname happens to be an IP address, convert it + * to a the canonical DNS name bound to this address. */ + tmp = host_pton(hname); + if (tmp != NULL) { + char *cname = host_canonname(tmp->ai_addr); + nfs_freeaddrinfo(tmp); + + /* The resulting FQDN may be in our netgroup. */ + if (cname != NULL) { + free(hname); + hname = cname; + if (innetgr(netgroup, hname, NULL, NULL)) { + match = 1; + goto out; + } + } + } + + /* check whether the IP itself is in the netgroup */ + ip = calloc(INET6_ADDRSTRLEN, 1); + if (ip == NULL) + goto out; + + if (inet_ntop(ai->ai_family, &(((struct sockaddr_in *)ai->ai_addr)->sin_addr), ip, INET6_ADDRSTRLEN) == ip) { + if (innetgr(netgroup, ip, NULL, NULL)) { + free(hname); + hname = ip; + match = 1; + goto out; + } + } + free(ip); + + /* Okay, strip off the domain (if we have one) */ + dot = strchr(hname, '.'); + if (dot == NULL) + goto out; + + *dot = '\0'; + match = innetgr(netgroup, hname, NULL, NULL); + +out: + free(hname); + return match; +} +#else /* !HAVE_INNETGR */ +static int +check_netgroup(__attribute__((unused)) const nfs_client *clp, + __attribute__((unused)) const struct addrinfo *ai) +{ + return 0; +} +#endif /* !HAVE_INNETGR */ + +/** + * client_check - check if IP address information matches a cached nfs_client + * @clp: pointer to a cached nfs_client record + * @ai: pointer to addrinfo to compare it with + * + * Returns 1 if the address information matches the cached nfs_client, + * otherwise zero. + */ +int +client_check(const nfs_client *clp, const struct addrinfo *ai) +{ + switch (clp->m_type) { + case MCL_FQDN: + return check_fqdn(clp, ai); + case MCL_SUBNETWORK: + return check_subnetwork(clp, ai); + case MCL_WILDCARD: + return check_wildcard(clp, ai); + case MCL_NETGROUP: + return check_netgroup(clp, ai); + case MCL_ANONYMOUS: + return 1; + case MCL_GSS: + return 0; + default: + xlog(D_GENERAL, "%s: unrecognized client type: %d", + __func__, clp->m_type); + } + + return 0; +} + +/** + * client_gettype - determine type of nfs_client given an identifier + * @ident: '\0'-terminated ASCII string containing a client identifier + * + * Returns the type of nfs_client record that would be used for + * this client. + */ +int +client_gettype(char *ident) +{ + char *sp; + + if (ident[0] == '\0' || strcmp(ident, "*")==0) + return MCL_ANONYMOUS; + if (strncmp(ident, "gss/", 4) == 0) + return MCL_GSS; + if (ident[0] == '@') { +#ifndef HAVE_INNETGR + xlog(L_WARNING, "netgroup support not compiled in"); +#endif + return MCL_NETGROUP; + } + for (sp = ident; *sp; sp++) { + if (*sp == '*' || *sp == '?' || *sp == '[') + return MCL_WILDCARD; + if (*sp == '/') + return MCL_SUBNETWORK; + if (*sp == '\\' && sp[1]) + sp++; + } + + return MCL_FQDN; +} diff --git a/support/export/export.c b/support/export/export.c new file mode 100644 index 0000000..3e48c42 --- /dev/null +++ b/support/export/export.c @@ -0,0 +1,471 @@ +/* + * support/export/export.c + * + * Maintain list of exported file systems. + * + * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <string.h> +#include <sys/types.h> +#include <sys/param.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <limits.h> +#include <stdlib.h> +#include <dirent.h> +#include <errno.h> +#include "xmalloc.h" +#include "nfslib.h" +#include "exportfs.h" +#include "nfsd_path.h" +#include "xlog.h" +#include "reexport.h" + +exp_hash_table exportlist[MCL_MAXTYPES] = {{NULL, {{NULL,NULL}, }}, }; +static int export_hash(char *); + +static void export_init(nfs_export *exp, nfs_client *clp, + struct exportent *nep); +static void export_add(nfs_export *exp); +static int export_check(const nfs_export *exp, const struct addrinfo *ai, + const char *path); + +/* Return a real path for the export. */ +static void +exportent_mkrealpath(struct exportent *eep) +{ + const char *chroot = nfsd_path_nfsd_rootdir(); + char *ret = NULL; + + if (chroot) { + char buffer[PATH_MAX]; + if (realpath(chroot, buffer)) + ret = nfsd_path_prepend_dir(buffer, eep->e_path); + else + xlog(D_GENERAL, "%s: failed to resolve path %s: %m", + __func__, chroot); + } + if (!ret) + ret = xstrdup(eep->e_path); + eep->e_realpath = ret; +} + +char * +exportent_realpath(struct exportent *eep) +{ + if (!eep->e_realpath) + exportent_mkrealpath(eep); + return eep->e_realpath; +} + +void +exportent_release(struct exportent *eep) +{ + xfree(eep->e_squids); + xfree(eep->e_sqgids); + free(eep->e_mountpoint); + free(eep->e_fslocdata); + free(eep->e_uuid); + xfree(eep->e_hostname); + xfree(eep->e_realpath); +} + +static void +export_free(nfs_export *exp) +{ + exportent_release(&exp->m_export); + xfree(exp); +} + +static void warn_duplicated_exports(nfs_export *exp, struct exportent *eep) +{ + if (exp->m_export.e_flags != eep->e_flags) { + xlog(L_ERROR, "incompatible duplicated export entries:"); + xlog(L_ERROR, "\t%s:%s (0x%x) [IGNORED]", eep->e_hostname, + eep->e_path, eep->e_flags); + xlog(L_ERROR, "\t%s:%s (0x%x)", exp->m_export.e_hostname, + exp->m_export.e_path, exp->m_export.e_flags); + } else { + xlog(L_ERROR, "duplicated export entries:"); + xlog(L_ERROR, "\t%s:%s", eep->e_hostname, eep->e_path); + xlog(L_ERROR, "\t%s:%s", exp->m_export.e_hostname, + exp->m_export.e_path); + } +} + +/** + * export_read - read entries from /etc/exports + * @fname: name of file to read from + * @ignore_hosts: don't check validity of host names + * + * Returns number of read entries. + * @ignore_hosts can be set when the host names won't be used + * and when getting delays or errors due to problems with + * hostname looking is not acceptable. + */ +int +export_read(char *fname, int ignore_hosts) +{ + struct exportent *eep; + nfs_export *exp; + + int volumes = 0; + int reexport_found = 0; + + setexportent(fname, "r"); + while ((eep = getexportent(0,1)) != NULL) { + exp = export_lookup(eep->e_hostname, eep->e_path, ignore_hosts); + if (!exp) { + if (export_create(eep, 0)) + /* possible complaints already logged */ + volumes++; + } + else + warn_duplicated_exports(exp, eep); + + if (eep->e_reexport) + reexport_found = 1; + } + + if (reexport_found) { + for (int i = 0; i < MCL_MAXTYPES; i++) { + for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { + if (exp->m_export.e_reexport) + continue; + + if (exp->m_export.e_flags & NFSEXP_FSID) { + xlog(L_ERROR, "When a reexport= option is present no manully assigned numerical fsid= options are allowed"); + return -1; + } + } + } + } + + endexportent(); + + return volumes; +} + +/** + * export_d_read - read entries from /etc/exports. + * @fname: name of directory to read from + * @ignore_hosts: don't check validity of host names + * + * Returns number of read entries. + * Based on mnt_table_parse_dir() in + * util-linux-ng/shlibs/mount/src/tab_parse.c + */ +int +export_d_read(const char *dname, int ignore_hosts) +{ + int n = 0, i; + struct dirent **namelist = NULL; + int volumes = 0; + + + n = scandir(dname, &namelist, NULL, versionsort); + if (n < 0) { + if (errno == ENOENT) + /* Silently return */ + return volumes; + xlog(L_NOTICE, "scandir %s: %s", dname, strerror(errno)); + } else if (n == 0) + return volumes; + + for (i = 0; i < n; i++) { + struct dirent *d = namelist[i]; + size_t namesz; + char fname[PATH_MAX + 1]; + int fname_len; + + + if (d->d_type != DT_UNKNOWN + && d->d_type != DT_REG + && d->d_type != DT_LNK) + continue; + if (*d->d_name == '.') + continue; + +#define _EXT_EXPORT_SIZ (sizeof(_EXT_EXPORT) - 1) + namesz = strlen(d->d_name); + if (!namesz + || namesz < _EXT_EXPORT_SIZ + 1 + || strcmp(d->d_name + (namesz - _EXT_EXPORT_SIZ), + _EXT_EXPORT)) + continue; + + fname_len = snprintf(fname, PATH_MAX +1, "%s/%s", dname, d->d_name); + if (fname_len > PATH_MAX) { + xlog(L_WARNING, "Too long file name: %s in %s", d->d_name, dname); + continue; + } + + volumes += export_read(fname, ignore_hosts); + } + + for (i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + + return volumes; +} + +/** + * export_create - create an in-core nfs_export record from an export entry + * @xep: export entry to lookup + * @canonical: if set, e_hostname is known to be canonical DNS name + * + * Returns a freshly instantiated export record, or NULL if + * a problem occurred. + */ +nfs_export * +export_create(struct exportent *xep, int canonical) +{ + nfs_client *clp; + nfs_export *exp; + + if (!(clp = client_lookup(xep->e_hostname, canonical))) { + /* bad export entry; complaint already logged */ + return NULL; + } + exp = (nfs_export *) xmalloc(sizeof(*exp)); + export_init(exp, clp, xep); + export_add(exp); + + return exp; +} + +static void +export_init(nfs_export *exp, nfs_client *clp, struct exportent *nep) +{ + struct exportent *e = &exp->m_export; + + dupexportent(e, nep); + if (nep->e_hostname) + e->e_hostname = xstrdup(nep->e_hostname); + + exp->m_exported = 0; + exp->m_xtabent = 0; + exp->m_mayexport = 0; + exp->m_changed = 0; + exp->m_warned = 0; + exp->m_client = clp; + clp->m_count++; +} + +/* + * Duplicate exports data. The in-core export struct retains the + * original hostname from /etc/exports, while the in-core client struct + * gets the newly found FQDN. + */ +static nfs_export * +export_dup(nfs_export *exp, const struct addrinfo *ai) +{ + nfs_export *new; + nfs_client *clp; + + new = (nfs_export *) xmalloc(sizeof(*new)); + memcpy(new, exp, sizeof(*new)); + dupexportent(&new->m_export, &exp->m_export); + if (exp->m_export.e_hostname) + new->m_export.e_hostname = xstrdup(exp->m_export.e_hostname); + clp = client_dup(exp->m_client, ai); + if (clp == NULL) { + export_free(new); + return NULL; + } + clp->m_count++; + new->m_client = clp; + new->m_mayexport = exp->m_mayexport; + new->m_exported = 0; + new->m_xtabent = 0; + new->m_changed = 0; + new->m_warned = 0; + export_add(new); + + return new; +} + +static void +export_add(nfs_export *exp) +{ + exp_hash_table *p_tbl; + exp_hash_entry *p_hen; + nfs_export *p_next; + + int type = exp->m_client->m_type; + int pos; + + pos = export_hash(exp->m_export.e_path); + p_tbl = &(exportlist[type]); /* pointer to hash table */ + p_hen = &(p_tbl->entries[pos]); /* pointer to hash table entry */ + + if (!(p_hen->p_first)) { /* hash table entry is empty */ + p_hen->p_first = exp; + p_hen->p_last = exp; + + exp->m_next = p_tbl->p_head; + p_tbl->p_head = exp; + } else { /* hash table entry is NOT empty */ + p_next = p_hen->p_last->m_next; + p_hen->p_last->m_next = exp; + exp->m_next = p_next; + p_hen->p_last = exp; + } +} + +/** + * export_find - find or create a suitable nfs_export for @ai and @path + * @ai: pointer to addrinfo for client + * @path: '\0'-terminated ASCII string containing export path + * + * Returns a pointer to nfs_export data matching @ai and @path, + * or NULL if an error occurs. + */ +nfs_export * +export_find(const struct addrinfo *ai, const char *path) +{ + nfs_export *exp; + int i; + + for (i = 0; i < MCL_MAXTYPES; i++) { + for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { + if (!export_check(exp, ai, path)) + continue; + if (exp->m_client->m_type == MCL_FQDN) + return exp; + return export_dup(exp, ai); + } + } + + return NULL; +} + +/** + * export_lookup - search hash table for export entry + * @hname: '\0'-terminated ASCII string containing client hostname to look for + * @path: '\0'-terminated ASCII string containing export path to look for + * @canonical: if set, @hname is known to be canonical DNS name + * + * Returns a pointer to nfs_export record matching @hname and @path, + * or NULL if the export was not found. + */ +nfs_export * +export_lookup(char *hname, char *path, int canonical) +{ + nfs_client *clp; + nfs_export *exp; + exp_hash_entry *p_hen; + + int pos; + + clp = client_lookup(hname, canonical); + if(clp == NULL) + return NULL; + + pos = export_hash(path); + p_hen = &(exportlist[clp->m_type].entries[pos]); + for(exp = p_hen->p_first; exp && (exp != p_hen->p_last->m_next); + exp = exp->m_next) { + if (exp->m_client == clp && !strcmp(exp->m_export.e_path, path)) { + return exp; + } + } + return NULL; +} + +static int +export_check(const nfs_export *exp, const struct addrinfo *ai, const char *path) +{ + if (strcmp(path, exp->m_export.e_path)) + return 0; + + return client_check(exp->m_client, ai); +} + +/** + * export_freeall - deallocate all nfs_export records + * + */ +void +export_freeall(void) +{ + nfs_export *exp, *nxt; + int i, j; + + for (i = 0; i < MCL_MAXTYPES; i++) { + for (exp = exportlist[i].p_head; exp; exp = nxt) { + nxt = exp->m_next; + client_release(exp->m_client); + export_free(exp); + } + for (j = 0; j < HASH_TABLE_SIZE; j++) { + exportlist[i].entries[j].p_first = NULL; + exportlist[i].entries[j].p_last = NULL; + } + exportlist[i].p_head = NULL; + } + client_freeall(); +} + +/* + * Compute and returns integer from string. + * Note: Its understood the smae integers can be same for + * different strings, but it should not matter. + */ +static unsigned int +strtoint(char *str) +{ + int i = 0; + unsigned int n = 0; + + while ( str[i] != '\0') { + n+=((int)str[i])*i; + i++; + } + return n; +} + +/* + * Hash function + */ +static int +export_hash(char *str) +{ + unsigned int num = strtoint(str); + + return num % HASH_TABLE_SIZE; +} + +int export_test(struct exportent *eep, int with_fsid) +{ + char *path = eep->e_path; + int flags = eep->e_flags | (with_fsid ? NFSEXP_FSID : 0); + /* beside max path, buf size should take protocol str into account */ + char buf[NFS_MAXPATHLEN+1+64] = { 0 }; + char *bp = buf; + int len = sizeof(buf); + int fd, n; + + n = snprintf(buf, len, "-test-client- "); + bp += n; + len -= n; + qword_add(&bp, &len, path); + if (len < 1) + return 0; + snprintf(bp, len, " 3 %d 65534 65534 0\n", flags); + fd = open("/proc/net/rpc/nfsd.export/channel", O_WRONLY); + if (fd < 0) + return 0; + n = nfsd_path_write(fd, buf, strlen(buf)); + close(fd); + if (n < 0) + return 0; + return 1; +} diff --git a/support/export/export.h b/support/export/export.h new file mode 100644 index 0000000..e2009cc --- /dev/null +++ b/support/export/export.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 Red Hat <nfs@redhat.com> + * + * support/export/export.h + * + * Declarations for export support + */ + +#ifndef EXPORT_H +#define EXPORT_H + +#include "nfslib.h" +#include "exportfs.h" + +unsigned int auth_reload(void); +nfs_export * auth_authenticate(const char *what, + const struct sockaddr *caller, + const char *path); + +void cache_open(void); +void cache_set_fds(fd_set *fdset); +int cache_process_req(fd_set *readfds); +void cache_process_loop(void); + +void v4clients_init(void); +void v4clients_set_fds(fd_set *fdset); +int v4clients_process(fd_set *fdset); + +struct nfs_fh_len * + cache_get_filehandle(nfs_export *exp, int len, char *p); +int cache_export(nfs_export *exp, char *path); +int cache_fork_workers(char *prog, int num_threads); +void cache_wait_for_workers(char *prog); +int cache_process(fd_set *readfds); + +bool ipaddr_client_matches(nfs_export *exp, struct addrinfo *ai); +bool namelist_client_matches(nfs_export *exp, char *dom); +bool client_matches(nfs_export *exp, char *dom, struct addrinfo *ai); + +static inline bool is_ipaddr_client(char *dom) +{ + return dom[0] == '$'; +} +#endif /* EXPORT__H */ diff --git a/support/export/fsloc.c b/support/export/fsloc.c new file mode 100644 index 0000000..1b869b6 --- /dev/null +++ b/support/export/fsloc.c @@ -0,0 +1,203 @@ +/* + * COPYRIGHT (c) 2006 + * THE REGENTS OF THE UNIVERSITY OF MICHIGAN + * ALL RIGHTS RESERVED + * + * Permission is granted to use, copy, create derivative works + * and redistribute this software and such derivative works + * for any purpose, so long as the name of The University of + * Michigan is not used in any advertising or publicity + * pertaining to the use of distribution of this software + * without specific, written prior authorization. If the + * above copyright notice or any other identification of the + * University of Michigan is included in any copy of any + * portion of this software, then the disclaimer below must + * also be included. + * + * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION + * FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY + * PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF + * MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING + * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE + * REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE + * FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR + * CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING + * OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN + * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGES. + */ + +#include <stdlib.h> +#include <string.h> +#include <syslog.h> + +#include "fsloc.h" +#include "exportfs.h" + +/* Debugging tool: prints out @servers info to syslog */ +static void replicas_print(struct servers *sp) +{ + int i; + if (!sp) { + xlog(L_NOTICE, "NULL replicas pointer"); + return; + } + xlog(L_NOTICE, "replicas listsize=%i", sp->h_num); + for (i=0; i<sp->h_num; i++) { + xlog(L_NOTICE, " %s:%s", + sp->h_mp[i]->h_host, sp->h_mp[i]->h_path); + } +} + +#ifdef DEBUG +/* Called by setting 'Method = stub' in config file. Just returns + * some syntactically correct gibberish for testing purposes. + */ +static struct servers *method_stub(char *key) +{ + struct servers *sp; + struct mount_point *mp; + + xlog(L_NOTICE, "called method_stub\n"); + sp = malloc(sizeof(struct servers)); + if (!sp) + return NULL; + mp = calloc(1, sizeof(struct mount_point)); + if (!mp) { + free(sp); + return NULL; + } + sp->h_num = 1; + sp->h_mp[0] = mp; + mp->h_host = strdup("stub_server"); + mp->h_path = strdup("/my/test/path"); + sp->h_referral = 1; + return sp; +} +#endif /* DEBUG */ + +/* Scan @list, which is a NULL-terminated array of strings of the + * form path@host[+host], and return corresponding servers structure. + */ +static struct servers *parse_list(char **list) +{ + int i; + struct servers *res; + struct mount_point *mp; + char *cp; + + res = malloc(sizeof(struct servers)); + if (!res) + return NULL; + res->h_num = 0; + + /* parse each of the answers in sucession. */ + for (i=0; list[i] && i<FSLOC_MAX_LIST; i++) { + mp = calloc(1, sizeof(struct mount_point)); + if (!mp) { + release_replicas(res); + return NULL; + } + cp = strchr(list[i], '@'); + if ((!cp) || list[i][0] != '/') { + xlog(L_WARNING, "invalid entry '%s'", list[i]); + free(mp); + continue; /* XXX Need better error handling */ + } + res->h_mp[i] = mp; + res->h_num++; + mp->h_path = strndup(list[i], cp - list[i]); + cp++; + mp->h_host = strdup(cp); + /* hosts are '+' separated, kernel expects ':' separated */ + while ( (cp = strchr(mp->h_host, '+')) ) + *cp = ':'; + } + return res; +} + +/* @data is a string of form path@host[+host][:path@host[+host]] + */ +static struct servers *method_list(char *data) +{ + char *copy, *ptr=data, *p; + char **list; + int i, listsize; + struct servers *rv=NULL; + bool v6esc = false; + + xlog(L_NOTICE, "method_list(%s)", data); + for (ptr--, listsize=1; ptr; ptr=strchr(ptr, ':'), listsize++) + ptr++; + list = malloc(listsize * sizeof(char *)); + copy = strdup(data); + if (copy) + xlog(L_NOTICE, "converted to %s", copy); + if (list && copy) { + ptr = copy; + for (p = ptr, i = 0; *p && i < listsize; p++) { + if (*p == '[') + v6esc = true; + else if (*p == ']') + v6esc = false; + + if (!v6esc && *p == ':') { + *p = '\0'; + if (*ptr) + list[i++] = ptr; + ptr = p + 1; + } + } + if (*ptr) + list[i++] = ptr; + list[i] = NULL; + rv = parse_list(list); + } + free(copy); + free(list); + replicas_print(rv); + return rv; +} + +/* Returns appropriately filled struct servers, or NULL if had a problem */ +struct servers *replicas_lookup(int method, char *data) +{ + struct servers *sp=NULL; + switch(method) { + case FSLOC_NONE: + break; + case FSLOC_REFER: + sp = method_list(data); + if (sp) + sp->h_referral = 1; + break; + case FSLOC_REPLICA: + sp = method_list(data); + if (sp) + sp->h_referral = 0; + break; +#ifdef DEBUG + case FSLOC_STUB: + sp = method_stub(data); + break; +#endif + default: + xlog(L_WARNING, "Unknown method = %i", method); + } + replicas_print(sp); + return sp; +} + +void release_replicas(struct servers *server) +{ + int i; + + if (!server) return; + for (i = 0; i < server->h_num; i++) { + free(server->h_mp[i]->h_host); + free(server->h_mp[i]->h_path); + free(server->h_mp[i]); + } + free(server); +} diff --git a/support/export/hostname.c b/support/export/hostname.c new file mode 100644 index 0000000..be4d7f6 --- /dev/null +++ b/support/export/hostname.c @@ -0,0 +1,366 @@ +/* + * Copyright 2010 Oracle. All rights reserved. + * + * This file is part of nfs-utils. + * + * nfs-utils 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. + * + * nfs-utils 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 nfs-utils. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdlib.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <errno.h> + +#include "sockaddr.h" +#include "exportfs.h" + +/** + * host_ntop - generate presentation address given a sockaddr + * @sap: pointer to socket address + * @buf: working storage + * @buflen: size of @buf in bytes + * + * Returns a pointer to a @buf. + */ +#ifdef HAVE_GETNAMEINFO +char * +host_ntop(const struct sockaddr *sap, char *buf, const size_t buflen) +{ + socklen_t salen = nfs_sockaddr_length(sap); + int error; + + memset(buf, 0, buflen); + + if (salen == 0) { + (void)strncpy(buf, "bad family", buflen - 1); + return buf; + } + + error = getnameinfo(sap, salen, buf, (socklen_t)buflen, + NULL, 0, NI_NUMERICHOST); + if (error != 0) { + buf[0] = '\0'; + (void)strncpy(buf, "bad address", buflen - 1); + } + + return buf; +} +#else /* !HAVE_GETNAMEINFO */ +char * +host_ntop(const struct sockaddr *sap, char *buf, const size_t buflen) +{ + const struct sockaddr_in *sin = (const struct sockaddr_in *)(char *)sap; + + memset(buf, 0, buflen); + + if (sin->sin_family != AF_INET) { + (void)strncpy(buf, "bad family", buflen - 1); + return buf; + } + + if (inet_ntop(AF_INET, &sin->sin_addr.s_addr, buf, buflen) != NULL) + return buf; + + buf[0] = '\0'; + (void)strncpy(buf, "bad address", buflen - 1); + return buf; +} +#endif /* !HAVE_GETNAMEINFO */ + +/** + * host_pton - return addrinfo for a given presentation address + * @paddr: pointer to a '\0'-terminated ASCII string containing an + * IP presentation address + * + * Returns address info structure, or NULL if an error occurs. Caller + * must free the returned structure with freeaddrinfo(3). + */ +__attribute__((__malloc__)) +struct addrinfo * +host_pton(const char *paddr) +{ + struct addrinfo *ai = NULL; + struct addrinfo hint = { + /* don't return duplicates */ + .ai_protocol = (int)IPPROTO_UDP, + .ai_flags = AI_NUMERICHOST, + .ai_family = AF_UNSPEC, + }; + struct sockaddr_in sin; + int error, inet4; + + /* + * Although getaddrinfo(3) is easier to use and supports + * IPv6, it recognizes incomplete addresses like "10.4" + * as valid AF_INET addresses. It also accepts presentation + * addresses that end with a blank. + * + * inet_pton(3) is much stricter. Use it to be certain we + * have a real AF_INET presentation address, before invoking + * getaddrinfo(3) to generate the full addrinfo list. + */ + if (paddr == NULL) { + xlog(D_GENERAL, "%s: passed a NULL presentation address", + __func__); + return NULL; + } + inet4 = 1; + if (inet_pton(AF_INET, paddr, &sin.sin_addr) == 0) + inet4 = 0; + + error = getaddrinfo(paddr, NULL, &hint, &ai); + switch (error) { + case 0: + if (!inet4 && ai->ai_addr->sa_family == AF_INET) { + xlog(D_GENERAL, "%s: failed to convert %s", + __func__, paddr); + nfs_freeaddrinfo(ai); + break; + } + return ai; + case EAI_NONAME: + break; + case EAI_SYSTEM: + xlog(L_WARNING, "%s: failed to convert %s: (%d) %m", + __func__, paddr, errno); + break; + default: + xlog(L_WARNING, "%s: failed to convert %s: %s", + __func__, paddr, gai_strerror(error)); + break; + } + + return NULL; +} + +/** + * host_addrinfo - return addrinfo for a given hostname + * @hostname: pointer to a '\0'-terminated ASCII string containing a hostname + * + * Returns address info structure with ai_canonname filled in, or NULL + * if no information is available for @hostname. Caller must free the + * returned structure with freeaddrinfo(3). + */ +__attribute__((__malloc__)) +struct addrinfo * +host_addrinfo(const char *hostname) +{ + struct addrinfo *ai = NULL; + struct addrinfo hint = { +#ifdef IPV6_SUPPORTED + .ai_family = AF_UNSPEC, +#else + .ai_family = AF_INET, +#endif + /* don't return duplicates */ + .ai_protocol = (int)IPPROTO_UDP, + .ai_flags = AI_CANONNAME, + }; + int error; + + error = getaddrinfo(hostname, NULL, &hint, &ai); + switch (error) { + case 0: + return ai; + case EAI_SYSTEM: + xlog(D_PARSE, "%s: failed to resolve %s: (%d) %m", + __func__, hostname, errno); + break; + default: + xlog(D_PARSE, "%s: failed to resolve %s: %s", + __func__, hostname, gai_strerror(error)); + break; + } + + return NULL; +} + +/** + * host_canonname - return canonical hostname bound to an address + * @sap: pointer to socket address to look up + * + * Discover the canonical hostname associated with the given socket + * address. The host's reverse mapping is verified in the process. + * + * Returns a '\0'-terminated ASCII string containing a hostname, or + * NULL if no hostname can be found for @sap. Caller must free + * the string. + */ +#ifdef HAVE_GETNAMEINFO +__attribute__((__malloc__)) +char * +host_canonname(const struct sockaddr *sap) +{ + socklen_t salen = nfs_sockaddr_length(sap); + char buf[NI_MAXHOST]; + int error; + + if (salen == 0) { + xlog(D_GENERAL, "%s: unsupported address family %d", + __func__, sap->sa_family); + return NULL; + } + + memset(buf, 0, sizeof(buf)); + error = getnameinfo(sap, salen, buf, (socklen_t)sizeof(buf), + NULL, 0, NI_NAMEREQD); + switch (error) { + case 0: + break; + case EAI_SYSTEM: + xlog(D_GENERAL, "%s: getnameinfo(3) failed: (%d) %m", + __func__, errno); + return NULL; + default: + (void)getnameinfo(sap, salen, buf, (socklen_t)sizeof(buf), + NULL, 0, NI_NUMERICHOST); + xlog(D_PARSE, "%s: failed to resolve %s: %s", + __func__, buf, gai_strerror(error)); + return NULL; + } + + return strdup(buf); +} +#else /* !HAVE_GETNAMEINFO */ +__attribute__((__malloc__)) +char * +host_canonname(const struct sockaddr *sap) +{ + const struct sockaddr_in *sin = (const struct sockaddr_in *)(char *)sap; + const struct in_addr *addr = &sin->sin_addr; + struct hostent *hp; + + if (sap->sa_family != AF_INET) + return NULL; + + hp = gethostbyaddr(addr, (socklen_t)sizeof(addr), AF_INET); + if (hp == NULL) + return NULL; + + return strdup(hp->h_name); +} +#endif /* !HAVE_GETNAMEINFO */ + +/** + * host_reliable_addrinfo - return addrinfo for a given address + * @sap: pointer to socket address to look up + * + * Reverse and forward lookups are performed to ensure the address has + * matching forward and reverse mappings. + * + * Returns addrinfo structure with just the provided address. If there + * is a problem with resolution or the resolved records don't match up + * properly then returns NULL. + * + * Caller must free the returned structure with freeaddrinfo(3). + */ +__attribute__((__malloc__)) +struct addrinfo * +host_reliable_addrinfo(const struct sockaddr *sap) +{ + struct addrinfo *ai, *a; + char *hostname; + + ai = NULL; + hostname = host_canonname(sap); + if (hostname == NULL) + goto out; + + ai = host_addrinfo(hostname); + free(hostname); + if (!ai) + goto out; + + /* make sure there's a matching address in the list */ + for (a = ai; a; a = a->ai_next) + if (nfs_compare_sockaddr(a->ai_addr, sap)) + break; + + nfs_freeaddrinfo(ai); + ai = NULL; + if (!a) + goto out; + + /* get addrinfo with just the original address */ + ai = host_numeric_addrinfo(sap); + +out: + return ai; +} + +/** + * host_numeric_addrinfo - return addrinfo without doing DNS queries + * @sap: pointer to socket address + * + * Returns address info structure, or NULL if an error occurred. + * Caller must free the returned structure with freeaddrinfo(3). + */ +#ifdef HAVE_GETNAMEINFO +__attribute__((__malloc__)) +struct addrinfo * +host_numeric_addrinfo(const struct sockaddr *sap) +{ + socklen_t salen = nfs_sockaddr_length(sap); + char buf[INET6_ADDRSTRLEN]; + int error; + + if (salen == 0) { + xlog(D_GENERAL, "%s: unsupported address family %d", + __func__, sap->sa_family); + return NULL; + } + + memset(buf, 0, sizeof(buf)); + error = getnameinfo(sap, salen, buf, (socklen_t)sizeof(buf), + NULL, 0, NI_NUMERICHOST); + switch (error) { + case 0: + break; + case EAI_SYSTEM: + xlog(D_GENERAL, "%s: getnameinfo(3) failed: (%d) %m", + __func__, errno); + return NULL; + default: + xlog(D_GENERAL, "%s: getnameinfo(3) failed: %s", + __func__, gai_strerror(error)); + return NULL; + } + + return host_pton(buf); +} +#else /* !HAVE_GETNAMEINFO */ +__attribute__((__malloc__)) +struct addrinfo * +host_numeric_addrinfo(const struct sockaddr *sap) +{ + const struct sockaddr_in *sin = (const struct sockaddr_in *)sap; + const struct in_addr *addr = &sin->sin_addr; + char buf[INET_ADDRSTRLEN]; + + if (sap->sa_family != AF_INET) + return NULL; + + memset(buf, 0, sizeof(buf)); + if (inet_ntop(AF_INET, (char *)addr, buf, + (socklen_t)sizeof(buf)) == NULL) + return NULL; + + return host_pton(buf); +} +#endif /* !HAVE_GETNAMEINFO */ diff --git a/support/export/mount.x b/support/export/mount.x new file mode 100644 index 0000000..12fd841 --- /dev/null +++ b/support/export/mount.x @@ -0,0 +1,343 @@ +%/* +% * Copyright (c) 2009, Sun Microsystems, Inc. +% * All rights reserved. +% * +% * Redistribution and use in source and binary forms, with or without +% * modification, are permitted provided that the following conditions are met: +% * - Redistributions of source code must retain the above copyright notice, +% * this list of conditions and the following disclaimer. +% * - Redistributions in binary form must reproduce the above copyright notice, +% * this list of conditions and the following disclaimer in the documentation +% * and/or other materials provided with the distribution. +% * - Neither the name of Sun Microsystems, Inc. nor the names of its +% * contributors may be used to endorse or promote products derived +% * from this software without specific prior written permission. +% * +% * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +% * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +% * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +% * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +% * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +% * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +% * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +% * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +% * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +% * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +% * POSSIBILITY OF SUCH DAMAGE. +% */ + +%/* +% * Copyright (c) 1985, 1990 by Sun Microsystems, Inc. +% */ +% +%/* from @(#)mount.x 1.3 91/03/11 TIRPC 1.0 */ + +/* + * Protocol description for the mount program + */ + +#ifdef RPC_HDR +%#ifndef _rpcsvc_mount_h +%#define _rpcsvc_mount_h +%#include <memory.h> +#endif + +const MNTPATHLEN = 1024; /* maximum bytes in a pathname argument */ +const MNTNAMLEN = 255; /* maximum bytes in a name argument */ +const FHSIZE = 32; /* size in bytes of a file handle */ + +/* + * The fhandle is the file handle that the server passes to the client. + * All file operations are done using the file handles to refer to a file + * or a directory. The file handle can contain whatever information the + * server needs to distinguish an individual file. + */ +typedef opaque fhandle[FHSIZE]; + +/* + * If a status of zero is returned, the call completed successfully, and + * a file handle for the directory follows. A non-zero status indicates + * some sort of error. The status corresponds with UNIX error numbers. + */ +union fhstatus switch (unsigned fhs_status) { +case 0: + fhandle fhs_fhandle; +default: + void; +}; + +/* + * The type dirpath is the pathname of a directory + */ +typedef string dirpath<MNTPATHLEN>; + +/* + * The type name is used for arbitrary names (hostnames, groupnames) + */ +typedef string name<MNTNAMLEN>; + +/* + * A list of who has what mounted + */ +typedef struct mountbody *mountlist; +struct mountbody { + name ml_hostname; + dirpath ml_directory; + mountlist ml_next; +}; + +/* + * A list of netgroups + */ +typedef struct groupnode *groups; +struct groupnode { + name gr_name; + groups gr_next; +}; + +/* + * A list of what is exported and to whom + */ +typedef struct exportnode *exports; +struct exportnode { + dirpath ex_dir; + groups ex_groups; + exports ex_next; +}; + +/* + * POSIX pathconf information + */ +struct ppathcnf { + int pc_link_max; /* max links allowed */ + short pc_max_canon; /* max line len for a tty */ + short pc_max_input; /* input a tty can eat all at once */ + short pc_name_max; /* max file name length (dir entry) */ + short pc_path_max; /* max path name length (/x/y/x/.. ) */ + short pc_pipe_buf; /* size of a pipe (bytes) */ + u_char pc_vdisable; /* safe char to turn off c_cc[i] */ + char pc_xxx; /* alignment padding; cc_t == char */ + short pc_mask[2]; /* validity and boolean bits */ +}; + +/* + * NFSv3 file handle + */ +const FHSIZE3 = 64; /* max size of NFSv3 file handle in bytes */ +typedef opaque fhandle3<FHSIZE3>; + +/* + * NFSv3 mount status + */ +enum mountstat3 { + MNT_OK = 0, /* no error */ + MNT3ERR_PERM = 1, /* not owner */ + MNT3ERR_NOENT = 2, /* no such file or directory */ + MNT3ERR_IO = 5, /* I/O error */ + MNT3ERR_ACCES = 13, /* Permission denied */ + MNT3ERR_NOTDIR = 20, /* Not a directory */ + MNT3ERR_INVAL = 22, /* Invalid argument */ + MNT3ERR_NAMETOOLONG = 63, /* File name too long */ + MNT3ERR_NOTSUPP = 10004,/* Operation not supported */ + MNT3ERR_SERVERFAULT = 10006 /* A failure on the server */ +}; + +/* + * NFSv3 mount result + */ +struct mountres3_ok { + fhandle3 fhandle; + int auth_flavors<>; +}; + +union mountres3 switch (mountstat3 fhs_status) { +case MNT_OK: + mountres3_ok mountinfo; /* File handle and supported flavors */ +default: + void; +}; + +program MOUNTPROG { + /* + * Version one of the mount protocol communicates with version two + * of the NFS protocol. The only connecting point is the fhandle + * structure, which is the same for both protocols. + */ + version MOUNTVERS { + /* + * Does no work. It is made available in all RPC services + * to allow server reponse testing and timing + */ + void + MOUNTPROC_NULL(void) = 0; + + /* + * If fhs_status is 0, then fhs_fhandle contains the + * file handle for the directory. This file handle may + * be used in the NFS protocol. This procedure also adds + * a new entry to the mount list for this client mounting + * the directory. + * Unix authentication required. + */ + fhstatus + MOUNTPROC_MNT(dirpath) = 1; + + /* + * Returns the list of remotely mounted filesystems. The + * mountlist contains one entry for each hostname and + * directory pair. + */ + mountlist + MOUNTPROC_DUMP(void) = 2; + + /* + * Removes the mount list entry for the directory + * Unix authentication required. + */ + void + MOUNTPROC_UMNT(dirpath) = 3; + + /* + * Removes all of the mount list entries for this client + * Unix authentication required. + */ + void + MOUNTPROC_UMNTALL(void) = 4; + + /* + * Returns a list of all the exported filesystems, and which + * machines are allowed to import it. + */ + exports + MOUNTPROC_EXPORT(void) = 5; + + /* + * Identical to MOUNTPROC_EXPORT above + */ + exports + MOUNTPROC_EXPORTALL(void) = 6; + } = 1; + + /* + * Version two of the mount protocol communicates with version two + * of the NFS protocol. + * The only difference from version one is the addition of a POSIX + * pathconf call. + */ + version MOUNTVERS_POSIX { + /* + * Does no work. It is made available in all RPC services + * to allow server reponse testing and timing + */ + void + MOUNTPROC_NULL(void) = 0; + + /* + * If fhs_status is 0, then fhs_fhandle contains the + * file handle for the directory. This file handle may + * be used in the NFS protocol. This procedure also adds + * a new entry to the mount list for this client mounting + * the directory. + * Unix authentication required. + */ + fhstatus + MOUNTPROC_MNT(dirpath) = 1; + + /* + * Returns the list of remotely mounted filesystems. The + * mountlist contains one entry for each hostname and + * directory pair. + */ + mountlist + MOUNTPROC_DUMP(void) = 2; + + /* + * Removes the mount list entry for the directory + * Unix authentication required. + */ + void + MOUNTPROC_UMNT(dirpath) = 3; + + /* + * Removes all of the mount list entries for this client + * Unix authentication required. + */ + void + MOUNTPROC_UMNTALL(void) = 4; + + /* + * Returns a list of all the exported filesystems, and which + * machines are allowed to import it. + */ + exports + MOUNTPROC_EXPORT(void) = 5; + + /* + * Identical to MOUNTPROC_EXPORT above + */ + exports + MOUNTPROC_EXPORTALL(void) = 6; + + /* + * POSIX pathconf info (Sun hack) + */ + ppathcnf + MOUNTPROC_PATHCONF(dirpath) = 7; + } = 2; + + /* + * Version 3 of the protocol is for NFSv3 + */ + version MOUNTVERS_NFSV3 { + /* + * Does no work. It is made available in all RPC services + * to allow server reponse testing and timing + */ + void + MOUNTPROC3_NULL(void) = 0; + + /* + * If fhs_status is 0, then fhs_fhandle contains the + * file handle for the directory. This file handle may + * be used in the NFS protocol. This procedure also adds + * a new entry to the mount list for this client mounting + * the directory. + * Unix authentication required. + */ + mountres3 + MOUNTPROC3_MNT(dirpath) = 1; + + /* + * Returns the list of remotely mounted filesystems. The + * mountlist contains one entry for each hostname and + * directory pair. + */ + mountlist + MOUNTPROC3_DUMP(void) = 2; + + /* + * Removes the mount list entry for the directory + * Unix authentication required. + */ + void + MOUNTPROC3_UMNT(dirpath) = 3; + + /* + * Removes all of the mount list entries for this client + * Unix authentication required. + */ + void + MOUNTPROC3_UMNTALL(void) = 4; + + /* + * Returns a list of all the exported filesystems, and which + * machines are allowed to import it. + */ + exports + MOUNTPROC3_EXPORT(void) = 5; + } = 3; +} = 100005; + +#ifdef RPC_HDR +%#endif /*!_rpcsvc_mount_h*/ +#endif diff --git a/support/export/v4clients.c b/support/export/v4clients.c new file mode 100644 index 0000000..3230251 --- /dev/null +++ b/support/export/v4clients.c @@ -0,0 +1,233 @@ +/* + * support/export/v4clients.c + * + * Montior clients appearing in, and disappearing from, /proc/fs/nfsd/clients + * and log relevant information. + */ + +#include <unistd.h> +#include <stdlib.h> +#include <sys/inotify.h> +#include <sys/stat.h> +#include <errno.h> +#include "export.h" + +/* search.h declares 'struct entry' and nfs_prot.h + * does too. Easiest fix is to trick search.h into + * calling its struct "struct Entry". + */ +#define entry Entry +#include <search.h> +#undef entry + +static int clients_fd = -1; + +void v4clients_init(void) +{ + struct stat sb; + + if (stat("/proc/fs/nfsd/clients", &sb) != 0 || + !S_ISDIR(sb.st_mode)) + return; + if (clients_fd >= 0) + return; + clients_fd = inotify_init1(IN_NONBLOCK); + if (clients_fd < 0) { + xlog_err("Unable to initialise v4clients watcher: %s\n", + strerror(errno)); + return; + } + if (inotify_add_watch(clients_fd, "/proc/fs/nfsd/clients", + IN_CREATE | IN_DELETE) < 0) { + xlog_err("Unable to watch /proc/fs/nfsd/clients: %s\n", + strerror(errno)); + close(clients_fd); + clients_fd = -1; + return; + } +} + +void v4clients_set_fds(fd_set *fdset) +{ + if (clients_fd >= 0) + FD_SET(clients_fd, fdset); +} + +static void *tree_root; +static int have_unconfirmed; + +struct ent { + unsigned long num; + char *clientid; + char *addr; + int vers; + int unconfirmed; + int wid; +}; + +static int ent_cmp(const void *av, const void *bv) +{ + const struct ent *a = av; + const struct ent *b = bv; + + if (a->num < b->num) + return -1; + if (a->num > b->num) + return 1; + return 0; +} + +static void free_ent(struct ent *ent) +{ + free(ent->clientid); + free(ent->addr); + free(ent); +} + +static char *dup_line(char *line) +{ + char *ret; + char *e = strchr(line, '\n'); + if (!e) + e = line + strlen(line); + ret = malloc(e - line + 1); + if (ret) { + memcpy(ret, line, e - line); + ret[e-line] = 0; + } + return ret; +} + +static void read_info(struct ent *key) +{ + char buf[2048]; + char *path; + int was_unconfirmed = key->unconfirmed; + FILE *f; + + if (asprintf(&path, "/proc/fs/nfsd/clients/%lu/info", key->num) < 0) + return; + + f = fopen(path, "r"); + if (!f) { + free(path); + return; + } + if (key->wid < 0) + key->wid = inotify_add_watch(clients_fd, path, IN_MODIFY); + + while (fgets(buf, sizeof(buf), f)) { + if (strncmp(buf, "clientid: ", 10) == 0) { + free(key->clientid); + key->clientid = dup_line(buf+10); + } + if (strncmp(buf, "address: ", 9) == 0) { + free(key->addr); + key->addr = dup_line(buf+9); + } + if (strncmp(buf, "minor version: ", 15) == 0) + key->vers = atoi(buf+15); + if (strncmp(buf, "status: ", 8) == 0 && + strstr(buf, " unconfirmed") != NULL) { + key->unconfirmed = 1; + have_unconfirmed = 1; + } + if (strncmp(buf, "status: ", 8) == 0 && + strstr(buf, " confirmed") != NULL) + key->unconfirmed = 0; + } + fclose(f); + free(path); + + if (was_unconfirmed && !key->unconfirmed) + xlog(L_NOTICE, "v4.%d client attached: %s from %s", + key->vers, key->clientid ?: "-none-", + key->addr ?: "-none-"); + if (!key->unconfirmed && key->wid >= 0) { + inotify_rm_watch(clients_fd, key->wid); + key->wid = -1; + } +} + +static void add_id(int id) +{ + struct ent **ent; + struct ent *key; + + key = calloc(1, sizeof(*key)); + if (!key) { + return; + } + key->num = id; + key->wid = -1; + + ent = tsearch(key, &tree_root, ent_cmp); + + if (!ent || *ent != key) + /* Already existed, or insertion failed */ + free_ent(key); + else + read_info(key); +} + +static void del_id(unsigned long id) +{ + struct ent key = {.num = id}; + struct ent **e, *ent; + + e = tfind(&key, &tree_root, ent_cmp); + if (!e || !*e) + return; + ent = *e; + tdelete(ent, &tree_root, ent_cmp); + if (!ent->unconfirmed) + xlog(L_NOTICE, "v4.%d client detached: %s from %s", + ent->vers, ent->clientid, ent->addr); + if (ent->wid >= 0) + inotify_rm_watch(clients_fd, ent->wid); + free_ent(ent); +} + +static void check_id(unsigned long id) +{ + struct ent key = {.num = id}; + struct ent **e, *ent; + + e = tfind(&key, &tree_root, ent_cmp); + if (!e || !*e) + return; + ent = *e; + if (ent->unconfirmed) + read_info(ent); +} + +int v4clients_process(fd_set *fdset) +{ + char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event)))); + const struct inotify_event *ev; + ssize_t len; + char *ptr; + + if (clients_fd < 0 || + !FD_ISSET(clients_fd, fdset)) + return 0; + + while ((len = read(clients_fd, buf, sizeof(buf))) > 0) { + for (ptr = buf; ptr < buf + len; + ptr += sizeof(struct inotify_event) + ev->len) { + int id; + ev = (const struct inotify_event *)ptr; + + id = atoi(ev->name); + if (id <= 0) + continue; + if (ev->mask & IN_CREATE) + add_id(id); + if (ev->mask & IN_DELETE) + del_id(id); + if (ev->mask & IN_MODIFY) + check_id(id); + } + } + return 1; +} diff --git a/support/export/v4root.c b/support/export/v4root.c new file mode 100644 index 0000000..03805dc --- /dev/null +++ b/support/export/v4root.c @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2009 Red Hat <nfs@redhat.com> + * + * support/export/v4root.c + * + * Routines used to support NFSv4 pseudo roots + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/queue.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> + +#include <unistd.h> +#include <errno.h> +#include <uuid/uuid.h> + +#include "xlog.h" +#include "exportfs.h" +#include "nfslib.h" +#include "misc.h" +#include "v4root.h" +#include "pseudoflavors.h" + +static nfs_export pseudo_root = { + .m_next = NULL, + .m_client = NULL, + .m_export = { + .e_hostname = "*", + .e_path = "/", + .e_flags = NFSEXP_READONLY + | NFSEXP_NOSUBTREECHECK | NFSEXP_FSID + | NFSEXP_V4ROOT | NFSEXP_INSECURE_PORT, + .e_anonuid = 65534, + .e_anongid = 65534, + .e_squids = NULL, + .e_nsquids = 0, + .e_sqgids = NULL, + .e_nsqgids = 0, + .e_fsid = 0, + .e_mountpoint = NULL, + .e_ttl = 0, + }, + .m_exported = 0, + .m_xtabent = 1, + .m_mayexport = 1, + .m_changed = 0, + .m_warned = 0, +}; + +static void +set_pseudofs_security(struct exportent *pseudo) +{ + struct flav_info *flav; + int i; + + for (flav = flav_map; flav < flav_map + flav_map_size; flav++) { + struct sec_entry *new; + + if (!flav->fnum) + continue; + if (flav->need_krb5 && access("/etc/krb5.keytab", F_OK) != 0) + continue; + + i = secinfo_addflavor(flav, pseudo); + new = &pseudo->e_secinfo[i]; + + new->flags |= NFSEXP_INSECURE_PORT; + } +} + +/* + * Create a pseudo export + */ +static struct exportent * +v4root_create(char *path, nfs_export *export) +{ + nfs_export *exp; + struct exportent eep; + struct exportent *curexp = &export->m_export; + + dupexportent(&eep, &pseudo_root.m_export); + eep.e_ttl = default_ttl; + eep.e_hostname = curexp->e_hostname; + strncpy(eep.e_path, path, sizeof(eep.e_path)-1); + if (strcmp(path, "/") != 0) + eep.e_flags &= ~NFSEXP_FSID; + + if (strcmp(path, "/") != 0 && + !export_test(&eep, 0)) { + /* Need a uuid - base it on path using a fixed seed that + * was generated randomly. + */ + const char seed_s[] = "39c6b5c1-3f24-4f4e-977c-7fe6546b8a25"; + uuid_t seed, uuid; + char uuid_s[UUID_STR_LEN]; + unsigned int i, j; + + uuid_parse(seed_s, seed); + uuid_generate_sha1(uuid, seed, path, strlen(path)); + uuid_unparse_upper(uuid, uuid_s); + /* strip hyhens */ + for (i = j = 0; uuid_s[i]; i++) + if (uuid_s[i] != '-') + uuid_s[j++] = uuid_s[i]; + eep.e_uuid = uuid_s; + } + set_pseudofs_security(&eep); + exp = export_create(&eep, 0); + if (exp == NULL) + return NULL; + xlog(D_CALL, "v4root_create: path '%s' flags 0x%x", + exp->m_export.e_path, exp->m_export.e_flags); + return &exp->m_export; +} + +/* + * Make sure the kernel has pseudo root support. + */ +static int +v4root_support(void) +{ + struct export_features *ef; + static int warned = 0; + + ef = get_export_features(); + + if (ef->flags & NFSEXP_V4ROOT) + return 1; + if (!warned) { + xlog(L_WARNING, "Kernel does not have pseudo root support."); + xlog(L_WARNING, "NFS v4 mounts will be disabled unless fsid=0"); + xlog(L_WARNING, "is specfied in /etc/exports file."); + warned++; + } + return 0; +} + +static int +pseudofs_update(char *hostname, char *path, nfs_export *source) +{ + nfs_export *exp; + + exp = export_lookup(hostname, path, 0); + if (exp && !(exp->m_export.e_flags & NFSEXP_V4ROOT)) + return 0; + if (!exp) { + if (v4root_create(path, source) == NULL) { + xlog(L_WARNING, "v4root_set: Unable to create " + "pseudo export for '%s'", path); + return -ENOMEM; + } + return 0; + } + /* Update an existing V4ROOT export: */ + set_pseudofs_security(&exp->m_export); + return 0; +} + +static int v4root_add_parents(nfs_export *exp) +{ + char *hostname = exp->m_export.e_hostname; + char *path; + char *ptr; + int ret = 0; + + path = strdup(exp->m_export.e_path); + if (!path) { + xlog(L_WARNING, "v4root_add_parents: Unable to create " + "pseudo export for '%s'", exp->m_export.e_path); + return -ENOMEM; + } + for (ptr = path; ptr; ptr = strchr(ptr, '/')) { + char saved; + + saved = *ptr; + *ptr = '\0'; + ret = pseudofs_update(hostname, *path ? path : "/", exp); + if (ret) + break; + *ptr = saved; + ptr++; + } + free(path); + return ret; +} + +/* + * Create pseudo exports by running through the real export + * looking at the components of the path that make up the export. + * Those path components, if not exported, will become pseudo + * exports allowing them to be found when the kernel does an upcall + * looking for components of the v4 mount. + */ +void +v4root_set(void) +{ + nfs_export *exp; + int i; + + if (!v4root_needed) + return; + if (!v4root_support()) + return; + + for (i = 0; i < MCL_MAXTYPES; i++) { + for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { + if (exp->m_export.e_flags & NFSEXP_V4ROOT) + /* + * We just added this one, so its + * parents are already dealt with! + */ + continue; + + if (strcmp(exp->m_export.e_path, "/") == 0 && + !(exp->m_export.e_flags & NFSEXP_FSID)) { + /* Force '/' to be exported as fsid == 0*/ + exp->m_export.e_flags |= NFSEXP_FSID; + exp->m_export.e_fsid = 0; + } + + v4root_add_parents(exp); + /* XXX: error handling! */ + } + } +} diff --git a/support/export/xtab.c b/support/export/xtab.c new file mode 100644 index 0000000..e210ca9 --- /dev/null +++ b/support/export/xtab.c @@ -0,0 +1,256 @@ +/* + * support/export/xtab.c + * + * Interface to the etab/exports file. + * + * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <libgen.h> + +#include "nfslib.h" +#include "exportfs.h" +#include "xio.h" +#include "xlog.h" +#include "v4root.h" +#include "misc.h" + +static char state_base_dirname[PATH_MAX] = NFS_STATEDIR; +struct state_paths etab; + +int v4root_needed; +static void cond_rename(char *newfile, char *oldfile); + +static int +xtab_read(char *xtab, char *lockfn, int is_export) +{ + /* is_export == 0 => reading /proc/fs/nfs/exports - we know these things are exported to kernel + * is_export == 1 => reading /var/lib/nfs/etab - these things are allowed to be exported + */ + struct exportent *xp; + nfs_export *exp; + int lockid; + + if ((lockid = xflock(lockfn, "r")) < 0) + return 0; + setexportent(xtab, "r"); + if (is_export == 1) + v4root_needed = 1; + while ((xp = getexportent(is_export==0, 0)) != NULL) { + if (!(exp = export_lookup(xp->e_hostname, xp->e_path, is_export != 1)) && + !(exp = export_create(xp, is_export!=1))) { + if(xp->e_hostname) { + free(xp->e_hostname); + xp->e_hostname=NULL; + } + if(xp->e_uuid) { + free(xp->e_uuid); + xp->e_uuid=NULL; + } + continue; + } + switch (is_export) { + case 0: + exp->m_exported = 1; + break; + case 1: + exp->m_xtabent = 1; + exp->m_mayexport = 1; + if ((xp->e_flags & NFSEXP_FSID) && xp->e_fsid == 0) + v4root_needed = 0; + break; + } + if(xp->e_hostname) { + free(xp->e_hostname); + xp->e_hostname=NULL; + } + if(xp->e_uuid) { + free(xp->e_uuid); + xp->e_uuid=NULL; + } + + } + endexportent(); + xfunlock(lockid); + + return 0; +} + +int +xtab_export_read(void) +{ + return xtab_read(etab.statefn, etab.lockfn, 1); +} + +/* + * mountd now keeps an open fd for the etab at all times to make sure that the + * inode number changes when the xtab_export_write is done. If you change the + * routine below such that the files are edited in place, then you'll need to + * fix the auth_reload logic as well... + */ +static int +xtab_write(char *xtab, char *xtabtmp, char *lockfn, int is_export) +{ + struct exportent xe; + nfs_export *exp; + int lockid, i; + + if ((lockid = xflock(lockfn, "w")) < 0) { + xlog(L_ERROR, "can't lock %s for writing", xtab); + return 0; + } + setexportent(xtabtmp, "w"); + + for (i = 0; i < MCL_MAXTYPES; i++) { + for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { + if (is_export && !exp->m_xtabent) + continue; + if (!is_export && ! exp->m_exported) + continue; + + /* write out the export entry using the FQDN */ + xe = exp->m_export; + xe.e_hostname = exp->m_client->m_hostname; + putexportent(&xe); + } + } + endexportent(); + + cond_rename(xtabtmp, xtab); + + xfunlock(lockid); + + return 1; +} + +int +xtab_export_write(void) +{ + return xtab_write(etab.statefn, etab.tmpfn, etab.lockfn, 1); +} + +/* + * rename newfile onto oldfile unless + * they are identical + */ +static void cond_rename(char *newfile, char *oldfile) +{ + int nfd, ofd; + char nbuf[4096], obuf[4096]; + int ncnt, ocnt; + + nfd = open(newfile, 0); + if (nfd < 0) + return; + ofd = open(oldfile, 0); + if (ofd < 0) { + close(nfd); + rename(newfile, oldfile); + return; + } + + do { + ncnt = read(nfd, nbuf, sizeof(nbuf)); + if (ncnt < 0) + break; + ocnt = read(ofd, obuf, sizeof(obuf)); + if (ocnt < 0) + break; + if (ncnt != ocnt) + break; + if (ncnt == 0) { + close(nfd); + close(ofd); + unlink(newfile); + return; + } + } while (memcmp(obuf, nbuf, ncnt) == 0); + + /* some mis-match */ + close(nfd); + close(ofd); + rename(newfile, oldfile); + return; +} + +/* + * Returns a dynamically allocated, '\0'-terminated buffer + * containing an appropriate pathname, or NULL if an error + * occurs. Caller must free the returned result with free(3). + */ +static char * +state_make_pathname(const char *tabname) +{ + return generic_make_pathname(state_base_dirname, tabname); +} + +/** + * state_setup_basedir - set up basedir + * @progname: C string containing name of program, for error messages + * @parentdir: C string containing pathname to on-disk state, or NULL + * + * This runs before logging is set up, so error messages are directed + * to stderr. + * + * Returns true and sets up our basedir, if @parentdir was valid + * and usable; otherwise false is returned. + */ +_Bool +state_setup_basedir(const char *progname, const char *parentdir) +{ + return generic_setup_basedir(progname, parentdir, state_base_dirname, + PATH_MAX); +} + +int +setup_state_path_names(const char *progname, const char *statefn, + const char *tmpfn, const char *lockfn, + struct state_paths *paths) +{ + paths->statefn = state_make_pathname(statefn); + if (!paths->statefn) { + fprintf(stderr, "%s: state_make_pathname(%s) failed\n", + progname, statefn); + goto out_err; + } + paths->tmpfn = state_make_pathname(tmpfn); + if (!paths->tmpfn) { + fprintf(stderr, "%s: state_make_pathname(%s) failed\n", + progname, tmpfn); + goto out_free_statefn; + } + paths->lockfn = state_make_pathname(lockfn); + if (!paths->lockfn) { + fprintf(stderr, "%s: state_make_pathname(%s) failed\n", + progname, lockfn); + goto out_free_tmpfn; + } + return 1; + +out_free_tmpfn: + free(paths->tmpfn); +out_free_statefn: + free(paths->statefn); +out_err: + return 0; + +} + +void +free_state_path_names(struct state_paths *paths) +{ + free(paths->statefn); + free(paths->tmpfn); + free(paths->lockfn); +} |