diff mbox series

[1/8] Add reexport helper library

Message ID 20230418093350.4550-2-richard@nod.at (mailing list archive)
State New, archived
Headers show
Series nfs-utils: Improving NFS re-export wrt. crossmnt | expand

Commit Message

Richard Weinberger April 18, 2023, 9:33 a.m. UTC
Add some helper functions which will be used by the reexport
mechanism to create and find fsidnums for re-exported NFS shares.

Signed-off-by: Richard Weinberger <richard@nod.at>
---
 configure.ac                 |   1 +
 support/Makefile.am          |   2 +-
 support/export/Makefile.am   |   2 +
 support/include/nfslib.h     |   1 +
 support/reexport/Makefile.am |   6 +
 support/reexport/reexport.c  | 326 +++++++++++++++++++++++++++++++++++
 support/reexport/reexport.h  |  18 ++
 7 files changed, 355 insertions(+), 1 deletion(-)
 create mode 100644 support/reexport/Makefile.am
 create mode 100644 support/reexport/reexport.c
 create mode 100644 support/reexport/reexport.h
diff mbox series

Patch

diff --git a/configure.ac b/configure.ac
index 7672a760..9f43267c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -717,6 +717,7 @@  AC_CONFIG_FILES([
 	support/nsm/Makefile
 	support/nfsidmap/Makefile
 	support/nfsidmap/libnfsidmap.pc
+	support/reexport/Makefile
 	tools/Makefile
 	tools/locktest/Makefile
 	tools/nlmtest/Makefile
diff --git a/support/Makefile.am b/support/Makefile.am
index c962d4d4..07cfd87e 100644
--- a/support/Makefile.am
+++ b/support/Makefile.am
@@ -10,7 +10,7 @@  if CONFIG_JUNCTION
 OPTDIRS += junction
 endif
 
-SUBDIRS = export include misc nfs nsm $(OPTDIRS)
+SUBDIRS = export include misc nfs nsm reexport $(OPTDIRS)
 
 MAINTAINERCLEANFILES = Makefile.in
 
diff --git a/support/export/Makefile.am b/support/export/Makefile.am
index eec737f6..7338e1c7 100644
--- a/support/export/Makefile.am
+++ b/support/export/Makefile.am
@@ -14,6 +14,8 @@  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
diff --git a/support/include/nfslib.h b/support/include/nfslib.h
index 61c19933..bdbde78d 100644
--- a/support/include/nfslib.h
+++ b/support/include/nfslib.h
@@ -98,6 +98,7 @@  struct exportent {
 	struct xprtsec_entry e_xprtsec[XPRTSECMODE_COUNT + 1];
 	unsigned int	e_ttl;
 	char *		e_realpath;
+	int		e_reexport;
 };
 
 struct rmtabent {
diff --git a/support/reexport/Makefile.am b/support/reexport/Makefile.am
new file mode 100644
index 00000000..9d544a8f
--- /dev/null
+++ b/support/reexport/Makefile.am
@@ -0,0 +1,6 @@ 
+## Process this file with automake to produce Makefile.in
+
+noinst_LIBRARIES = libreexport.a
+libreexport_a_SOURCES = reexport.c
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/support/reexport/reexport.c b/support/reexport/reexport.c
new file mode 100644
index 00000000..eddc9bf4
--- /dev/null
+++ b/support/reexport/reexport.c
@@ -0,0 +1,326 @@ 
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <dlfcn.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/random.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "nfsd_path.h"
+#include "conffile.h"
+#include "nfslib.h"
+#include "reexport.h"
+#include "xcommon.h"
+#include "xlog.h"
+
+static int fsidd_srv = -1;
+
+static bool connect_fsid_service(void)
+{
+	struct sockaddr_un addr;
+	char *sock_file;
+	int ret;
+	int s;
+
+	if (fsidd_srv != -1)
+		return true;
+
+	sock_file = conf_get_str_with_def("reexport", "fsidd_socket", FSID_SOCKET_NAME);
+
+	memset(&addr, 0, sizeof(struct sockaddr_un));
+	addr.sun_family = AF_UNIX;
+	strncpy(addr.sun_path, sock_file, sizeof(addr.sun_path) - 1);
+
+	s = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+	if (s == -1) {
+		xlog(L_WARNING, "Unable to create AF_UNIX socket for %s: %m\n", sock_file);
+		return false;
+	}
+
+	ret = connect(s, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un));
+	if (ret == -1) {
+		xlog(L_WARNING, "Unable to connect %s: %m, is fsidd running?\n", sock_file);
+		return false;
+	}
+
+	fsidd_srv = s;
+
+	return true;
+}
+
+int reexpdb_init(void)
+{
+	int try_count = 3;
+
+	while (try_count > 0 && !connect_fsid_service()) {
+		sleep(1);
+		try_count--;
+	}
+
+	return try_count > 0;
+}
+
+void reexpdb_destroy(void)
+{
+	close(fsidd_srv);
+	fsidd_srv = -1;
+}
+
+static bool parse_fsidd_reply(const char *cmd_info, char *buf, size_t len, char **result)
+{
+	if (len == 0) {
+		xlog(L_WARNING, "Unable to read %s result: server closed the connection", cmd_info);
+		return false;
+	} else if (len < 2) {
+		xlog(L_WARNING, "Unable to read %s result: server sent too few bytes", cmd_info);
+		return false;
+	}
+
+	if (buf[0] == '-') {
+		if (len > 2) {
+			char *reason = buf + 2;
+			xlog(L_WARNING, "Command %s failed, server said: %s", cmd_info, reason);
+		} else {
+			xlog(L_WARNING, "Command %s failed at server side", cmd_info);
+		}
+
+		return false;
+	}
+
+	if (buf[0] != '+') {
+		xlog(L_WARNING, "Unable to read %s result: server sent malformed answer", cmd_info);
+		return false;
+	}
+
+	if (len > 2) {
+		*result = strdup(buf + 2);
+	} else {
+		*result = NULL;
+	}
+
+	return true;
+}
+
+static bool do_fsidd_cmd(const char *cmd_info, char *msg, size_t len, char **result)
+{
+	char recvbuf[1024];
+	int n;
+
+	if (fsidd_srv == -1) {
+		xlog(L_NOTICE, "Reconnecting to fsid services");
+		if (reexpdb_init() == false)
+			return false;
+	}
+
+	xlog(D_GENERAL, "Request to fsidd: msg=\"%s\" len=%zd", msg, len);
+
+	if (write(fsidd_srv, msg, len) == -1) {
+		xlog(L_WARNING, "Unable to send %s command: %m", cmd_info);
+		goto out_close;
+	}
+
+	n = read(fsidd_srv, recvbuf, sizeof(recvbuf) - 1);
+	if (n <= -1) {
+		xlog(L_WARNING, "Unable to recv %s answer: %m", cmd_info);
+		goto out_close;
+	} else if (n == sizeof(recvbuf) - 1) {
+		//TODO: use better way to detect truncation
+		xlog(L_WARNING, "Unable to recv %s answer: answer truncated", cmd_info);
+		goto out_close;
+	}
+	recvbuf[n] = '\0';
+
+	xlog(D_GENERAL, "Answer from fsidd: msg=\"%s\" len=%i", recvbuf, n);
+
+	if (parse_fsidd_reply(cmd_info, recvbuf, n, result) == false) {
+		goto out_close;
+	}
+
+	return true;
+
+out_close:
+	close(fsidd_srv);
+	fsidd_srv = -1;
+	return false;
+}
+
+static bool fsidnum_get_by_path(char *path, uint32_t *fsidnum, bool may_create)
+{
+	char *msg, *result;
+	bool ret = false;
+	int len;
+
+	char *cmd = may_create ? "get_or_create_fsidnum" : "get_fsidnum";
+
+	len = asprintf(&msg, "%s %s", cmd, path);
+	if (len == -1) {
+		xlog(L_WARNING, "Unable to build %s command: %m", cmd);
+		goto out;
+	}
+
+	if (do_fsidd_cmd(cmd, msg, len, &result) == false) {
+		goto out;
+	}
+
+	if (result) {
+		bool bad_input = true;
+		char *endp;
+
+		errno = 0;
+		*fsidnum = strtoul(result, &endp, 10);
+		if (errno == 0 && *endp == '\0') {
+			bad_input = false;
+		}
+
+		free(result);
+
+		if (!bad_input) {
+			ret = true;
+		} else {
+			xlog(L_NOTICE, "Got malformed fsid for path %s", path);
+		}
+	} else {
+		xlog(L_NOTICE, "No fsid found for path %s", path);
+	}
+
+out:
+	free(msg);
+	return ret;
+}
+
+static bool path_by_fsidnum(uint32_t fsidnum, char **path)
+{
+	char *msg, *result;
+	bool ret = false;
+	int len;
+
+	len = asprintf(&msg, "get_path %d", (unsigned int)fsidnum);
+	if (len == -1) {
+		xlog(L_WARNING, "Unable to build get_path command: %m");
+		goto out;
+	}
+
+	if (do_fsidd_cmd("get_path", msg, len, &result) == false) {
+		goto out;
+	}
+
+	if (result) {
+		*path = result;
+		ret = true;
+	} else {
+		xlog(L_NOTICE, "No path found for fsid %u", (unsigned int)fsidnum);
+	}
+
+out:
+	free(msg);
+	return ret;
+}
+
+/*
+ * reexpdb_fsidnum_by_path - Lookup a fsid by path.
+ *
+ * @path: File system path used as lookup key
+ * @fsidnum: Pointer where found fsid is written to
+ * @may_create: If non-zero, allocate new fsid if lookup failed
+ *
+ */
+int reexpdb_fsidnum_by_path(char *path, uint32_t *fsidnum, int may_create)
+{
+	return fsidnum_get_by_path(path, fsidnum, may_create);
+}
+
+/*
+ * reexpdb_uncover_subvolume - Make sure a subvolume is present.
+ *
+ * @fsidnum: Numerical fsid number to look for
+ *
+ * Subvolumes (NFS cross mounts) get automatically mounted upon first
+ * access and can vanish after fs.nfs.nfs_mountpoint_timeout seconds.
+ * Also if the NFS server reboots, clients can still have valid file
+ * handles for such a subvolume.
+ *
+ * If kNFSd asks mountd for the path of a given fsidnum it can
+ * trigger an automount by calling statfs() on the given path.
+ */
+void reexpdb_uncover_subvolume(uint32_t fsidnum)
+{
+	struct statfs st;
+	char *path = NULL;
+	int ret;
+
+	if (path_by_fsidnum(fsidnum, &path)) {
+		ret = nfsd_path_statfs(path, &st);
+		if (ret == -1)
+			xlog(L_WARNING, "statfs() failed");
+	}
+
+	free(path);
+}
+
+/*
+ * reexpdb_apply_reexport_settings - Apply reexport specific settings to an exportent
+ *
+ * @ep: exportent to apply to
+ * @flname: Current export file, only useful for logging
+ * @flline: Current line, only useful for logging
+ *
+ * This is a helper function for applying reexport specific settings to an exportent.
+ * It searches a suitable fsid an sets @ep->e_fsid.
+ */
+int reexpdb_apply_reexport_settings(struct exportent *ep, char *flname, int flline)
+{
+	uint32_t fsidnum;
+	bool found, is_v4root = ((ep->e_flags & NFSEXP_FSID) && !ep->e_fsid);
+	int ret = 0;
+
+	if (ep->e_reexport == REEXP_NONE)
+		goto out;
+
+	if (ep->e_uuid)
+		goto out;
+
+	if (is_v4root)
+		goto out;
+
+	found = reexpdb_fsidnum_by_path(ep->e_path, &fsidnum, 0);
+	if (!found) {
+		if (ep->e_reexport == REEXP_AUTO_FSIDNUM) {
+			found = reexpdb_fsidnum_by_path(ep->e_path, &fsidnum, 1);
+			if (!found) {
+				xlog(L_ERROR, "%s:%i: Unable to generate fsid for %s",
+				     flname, flline, ep->e_path);
+				ret = -1;
+				goto out;
+			}
+		} else {
+			if (!ep->e_fsid) {
+				xlog(L_ERROR, "%s:%i: Selected 'reexport=' mode requires either a UUID 'fsid=' or a numerical 'fsid=' or a reexport db entry %d",
+				     flname, flline, ep->e_fsid);
+				ret = -1;
+			}
+
+			goto out;
+		}
+	}
+
+	if (ep->e_fsid) {
+		if (ep->e_fsid != fsidnum) {
+			xlog(L_ERROR, "%s:%i: Selected 'reexport=' mode requires configured numerical 'fsid=' to agree with reexport db entry",
+			     flname, flline);
+			ret = -1;
+		}
+	} else {
+		ep->e_fsid = fsidnum;
+	}
+
+out:
+	return ret;
+}
diff --git a/support/reexport/reexport.h b/support/reexport/reexport.h
new file mode 100644
index 00000000..3bed03a9
--- /dev/null
+++ b/support/reexport/reexport.h
@@ -0,0 +1,18 @@ 
+#ifndef REEXPORT_H
+#define REEXPORT_H
+
+enum {
+	REEXP_NONE = 0,
+	REEXP_AUTO_FSIDNUM,
+	REEXP_PREDEFINED_FSIDNUM,
+};
+
+int reexpdb_init(void);
+void reexpdb_destroy(void);
+int reexpdb_fsidnum_by_path(char *path, uint32_t *fsidnum, int may_create);
+int reexpdb_apply_reexport_settings(struct exportent *ep, char *flname, int flline);
+void reexpdb_uncover_subvolume(uint32_t fsidnum);
+
+#define FSID_SOCKET_NAME "fsid.sock"
+
+#endif /* REEXPORT_H */