Blob Blame History Raw
From 46ba8eed79fa13b32947b5c5b1bf0bc133b14c41 Mon Sep 17 00:00:00 2001
From: Chris Leech <cleech@redhat.com>
Date: Tue, 3 Feb 2015 16:28:15 -0800
Subject: [PATCH] iscsid safe session logout

Implement a safe logout option, which uses libmount from util-linux to
check for active mounts (and swaps) over devices, their partitions, and
any holders (like LVM and multipath device maps).  When enabled iscsid
will refuse to logout of sessions actively being used for mounts,
returning a status of EBUSY to the ipc request.

I've made it a configuration option (iscsid.safe_logout) that defaults
to "No" to preserve the existing behavior as the default, while making
it available for users that prefer a safety check.

This does add a new dependency on libmount.

Signed-off-by: Chris Leech <cleech@redhat.com>
---
 etc/iscsid.conf |   3 +
 usr/Makefile    |   4 +-
 usr/initiator.c | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 usr/sysfs.c     |  40 ++++++++++++
 usr/sysfs.h     |   4 ++
 5 files changed, 248 insertions(+), 2 deletions(-)

diff --git a/etc/iscsid.conf b/etc/iscsid.conf
index ef76dc0..6d9a5c0 100644
--- a/etc/iscsid.conf
+++ b/etc/iscsid.conf
@@ -22,6 +22,9 @@
 # Default for upstream open-iscsi scripts (uncomment to activate).
 iscsid.startup = /sbin/iscsid
 
+# Check for active mounts on devices reachable through a session
+# and refuse to logout if there are any.  Defaults to "No".
+# iscsid.safe_logout = Yes
 
 #############################
 # NIC/HBA and driver settings
diff --git a/usr/Makefile b/usr/Makefile
index 3d8ee22..b2c1504 100644
--- a/usr/Makefile
+++ b/usr/Makefile
@@ -55,14 +55,14 @@ all: $(PROGRAMS)
 
 iscsid: $(ISCSI_LIB_SRCS) $(INITIATOR_SRCS) $(DISCOVERY_SRCS) \
 	iscsid.o session_mgmt.o discoveryd.o
-	$(CC) $(CFLAGS) $^ -o $@  -L../utils/open-isns -lisns
+	$(CC) $(CFLAGS) $^ -o $@  -L../utils/open-isns -lisns -lmount
 
 iscsiadm: $(ISCSI_LIB_SRCS) $(DISCOVERY_SRCS) iscsiadm.o session_mgmt.o
 	$(CC) $(CFLAGS) $^ -o $@ -L../utils/open-isns -lisns
 
 iscsistart: $(ISCSI_LIB_SRCS) $(INITIATOR_SRCS) $(FW_BOOT_SRCS) \
 		iscsistart.o statics.o
-	$(CC) $(CFLAGS) -static $^ -o $@
+	$(CC) $(CFLAGS) -static $^ -o $@ -lmount
 clean:
 	rm -f *.o $(PROGRAMS) .depend $(LIBSYS)
 
diff --git a/usr/initiator.c b/usr/initiator.c
index 9d02f47..ac1a3ac 100644
--- a/usr/initiator.c
+++ b/usr/initiator.c
@@ -30,6 +30,7 @@
 #include <errno.h>
 #include <dirent.h>
 #include <fcntl.h>
+#include <libmount/libmount.h>
 
 #include "initiator.h"
 #include "transport.h"
@@ -2140,11 +2141,200 @@ static int session_unbind(struct iscsi_session *session)
 	return err;
 }
 
+static struct libmnt_table *mtab, *swaps;
+
+static void libmount_cleanup(void)
+{
+	mnt_free_table(mtab);
+	mnt_free_table(swaps);
+	mtab = swaps = NULL;
+}
+
+static int libmount_init(void)
+{
+	mnt_init_debug(0);
+	mtab = mnt_new_table();
+	swaps = mnt_new_table();
+	if (!mtab || !swaps) {
+		libmount_cleanup();
+		return -ENOMEM;
+	}
+	mnt_table_parse_mtab(mtab, NULL);
+	mnt_table_parse_swaps(swaps, NULL);
+	return 0;
+}
+
+static int trans_filter(const struct dirent *d)
+{
+	if (!strcmp(".", d->d_name) || !strcmp("..", d->d_name))
+		return 0;
+	return 1;
+}
+
+static int subdir_filter(const struct dirent *d)
+{
+	if (!(d->d_type & DT_DIR))
+		return 0;
+	return trans_filter(d);
+}
+
+static int is_partition(const char *path)
+{
+	char *devtype;
+	int rc = 0;
+
+	devtype = sysfs_get_uevent_devtype(path);
+	if (!devtype)
+		return 0;
+	if (strcmp(devtype, "partition") == 0)
+		rc = 1;
+	free(devtype);
+	return rc;
+}
+
+static int blockdev_check_mnts(char *syspath)
+{
+	struct libmnt_fs *fs;
+	char *devname = NULL;
+	char *_devname = NULL;
+	int rc = 0;
+
+	devname = sysfs_get_uevent_devname(syspath);
+	if (!devname)
+		goto out;
+
+	_devname = calloc(1, PATH_MAX);
+	if (!_devname)
+		goto out;
+	snprintf(_devname, PATH_MAX, "/dev/%s", devname);
+
+	fs = mnt_table_find_source(mtab, _devname, MNT_ITER_FORWARD);
+	if (fs) {
+		rc = 1;
+		goto out;
+	}
+	fs = mnt_table_find_source(swaps, _devname, MNT_ITER_FORWARD);
+	if (fs)
+		rc = 1;
+out:
+	free(devname);
+	free(_devname);
+	return rc;
+}
+
+static int count_device_users(char *syspath);
+
+static int blockdev_get_partitions(char *syspath)
+{
+	struct dirent **parts = NULL;
+	int n, i;
+	int count = 0;
+
+	n = scandir(syspath, &parts, subdir_filter, alphasort);
+	for (i = 0; i < n; i++) {
+		char *newpath;
+
+		newpath = calloc(1, PATH_MAX);
+		if (!newpath)
+			continue;
+		snprintf(newpath, PATH_MAX, "%s/%s", syspath, parts[i]->d_name);
+		free(parts[i]);
+		if (is_partition(newpath)) {
+			count += count_device_users(newpath);
+		}
+		free(newpath);
+	}
+	free(parts);
+	return count;
+}
+
+static int blockdev_get_holders(char *syspath)
+{
+	char *path = NULL;
+	struct dirent **holds = NULL;
+	int n, i;
+	int count = 0;
+
+	path = calloc(1, PATH_MAX);
+	if (!path)
+		return 0;
+	snprintf(path, PATH_MAX, "%s/holders", syspath);
+
+	n = scandir(path, &holds, trans_filter, alphasort);
+	for (i = 0; i < n; i++) {
+		char *newpath;
+		char *rp;
+
+		newpath = calloc(1, PATH_MAX);
+		if (!newpath)
+			continue;
+		snprintf(newpath, PATH_MAX, "%s/%s", path, holds[i]->d_name);
+
+		free(holds[i]);
+		rp = realpath(newpath, NULL);
+		if (rp)
+			count += count_device_users(rp);
+		free(newpath);
+		free(rp);
+	}
+	free(path);
+	free(holds);
+	return count;
+}
+
+static int count_device_users(char *syspath)
+{
+	int count = 0;
+	count += blockdev_check_mnts(syspath);
+	count += blockdev_get_partitions(syspath);
+	count += blockdev_get_holders(syspath);
+	return count;
+};
+
+static void device_in_use(void *data, int host_no, int target, int lun)
+{
+	char *syspath = NULL;
+	char *devname = NULL;
+	int *count = data;
+
+	devname = iscsi_sysfs_get_blockdev_from_lun(host_no, target, lun);
+	if (!devname)
+		goto out;
+	syspath = calloc(1, PATH_MAX);
+	if (!syspath)
+		goto out;
+	snprintf(syspath, PATH_MAX, "/sys/class/block/%s", devname);
+	*count += count_device_users(syspath);
+out:
+	free(syspath);
+	free(devname);
+}
+
+static int session_in_use(int sid)
+{
+	int host_no = -1, err = 0;
+	int count = 0;
+
+	if (libmount_init()) {
+		log_error("Failed to initialize libmount, "
+			  "not checking for active mounts on session [%d].\n", sid);
+		return 0;
+	}
+
+	host_no = iscsi_sysfs_get_host_no_from_sid(sid, &err);
+	if (!err)
+		iscsi_sysfs_for_each_device(&count, host_no, sid, device_in_use);
+
+	libmount_cleanup();
+	return count;
+}
+
 int session_logout_task(int sid, queue_task_t *qtask)
 {
 	iscsi_session_t *session;
 	iscsi_conn_t *conn;
 	int rc = ISCSI_SUCCESS;
+	char *safe;
 
 	session = session_find_by_sid(sid);
 	if (!session) {
@@ -2167,6 +2357,15 @@ invalid_state:
 		return ISCSI_ERR_INTERNAL;
 	}
 
+	safe = cfg_get_string_param(dconfig->config_file, "iscsid.safe_logout");
+	if (safe && !strcmp(safe, "Yes") && session_in_use(sid)) {
+		log_error("Session is actively in use for mounted storage, "
+			  "and iscsid.safe_logout is configured.\n");
+		free(safe);
+		return ISCSI_ERR_BUSY;
+	}
+	free(safe);
+
 	/* FIXME: logout all active connections */
 	conn = &session->conn[0];
 	if (conn->logout_qtask)
diff --git a/usr/sysfs.c b/usr/sysfs.c
index d00c925..bbb00c0 100644
--- a/usr/sysfs.c
+++ b/usr/sysfs.c
@@ -709,3 +709,43 @@ int sysfs_set_param(char *id, char *subsys, char *attr_name,
 	close(fd);
 	return rc;
 }
+
+char *sysfs_get_uevent_field(const char *path, const char *field)
+{
+	char *uevent_path = NULL;
+	FILE *f = NULL;
+	char *line, buffer[1024];
+	char *ff, *d;
+	char *out = NULL;
+
+	uevent_path = calloc(1, PATH_MAX);
+	if (!uevent_path)
+		return NULL;
+	snprintf(uevent_path, PATH_MAX, "%s/uevent", path);
+
+	f = fopen(uevent_path, "r");
+	if (!f)
+		goto out;
+	while ((line = fgets(buffer, sizeof (buffer), f))) {
+		ff = strtok(line, "=");
+		d = strtok(NULL, "\n");
+		if (strcmp(ff, field))
+			continue;
+		out = strdup(d);
+		break;
+	}
+	fclose(f);
+out:
+	free(uevent_path);
+	return out;
+}
+
+char *sysfs_get_uevent_devtype(const char *path)
+{
+	return sysfs_get_uevent_field(path, "DEVTYPE");
+}
+
+char *sysfs_get_uevent_devname(const char *path)
+{
+	return sysfs_get_uevent_field(path, "DEVNAME");
+}
diff --git a/usr/sysfs.h b/usr/sysfs.h
index 304dbbf..462060e 100644
--- a/usr/sysfs.h
+++ b/usr/sysfs.h
@@ -66,4 +66,8 @@ extern int sysfs_get_uint16(char *id, char *subsys, char *param,
 extern int sysfs_set_param(char *id, char *subsys, char *attr_name,
 			   char *write_buf, ssize_t buf_size);
 
+extern char *sysfs_get_uevent_field(const char *path, const char *field);
+extern char *sysfs_get_uevent_devtype(const char *path);
+extern char *sysfs_get_uevent_devname(const char *path);
+
 #endif
-- 
2.1.0