[dm-devel] [PATCH v2 09/11] multipathc: add new interactive client program

mwilck at suse.com mwilck at suse.com
Mon Aug 22 20:41:17 UTC 2022


From: Martin Wilck <mwilck at suse.com>

Add a new small program, multipathc, that acts as interactive
uxsock client for multipathd. multipathc is the only program
that has an interactive mode and can thus link to either libreadline
or libedit for command line history. All code depending on libreadline
is moved from uxclnt.c and cli.c to multipathc.c.

This patch breaks multipathd's interactive mode. It will be restored
in the next patch.

As multipathc doesn't link to libmultipath, it can link to libreadline
without license conflict.

Signed-off-by: Martin Wilck <mwilck at suse.com>
---
 .gitignore              |   1 +
 multipathd/Makefile     |  24 ++--
 multipathd/cli.c        | 130 ++------------------
 multipathd/cli.h        |   5 +-
 multipathd/multipathc.c | 254 ++++++++++++++++++++++++++++++++++++++++
 multipathd/uxclnt.c     | 126 +-------------------
 6 files changed, 293 insertions(+), 247 deletions(-)
 create mode 100644 multipathd/multipathc.c

diff --git a/.gitignore b/.gitignore
index b88608c..821c3e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@ cscope.out
 kpartx/kpartx
 multipath/multipath
 multipathd/multipathd
+multipathd/multipathc
 mpathpersist/mpathpersist
 abi.tar.gz
 abi
diff --git a/multipathd/Makefile b/multipathd/Makefile
index 7128510..19ab2e9 100644
--- a/multipathd/Makefile
+++ b/multipathd/Makefile
@@ -27,12 +27,12 @@ LIBDEPS += -L$(multipathdir) -lmultipath -L$(mpathpersistdir) -lmpathpersist \
 
 
 ifeq ($(READLINE),libedit)
-CPPFLAGS += -DUSE_LIBEDIT
-LIBDEPS += -ledit
+RL_CPPFLAGS = -DUSE_LIBEDIT
+RL_LIBDEPS += -ledit
 endif
 ifeq ($(READLINE),libreadline)
-CPPFLAGS += -DUSE_LIBREADLINE
-LIBDEPS += -lreadline
+RL_CPPFLAGS += -DUSE_LIBREADLINE
+RL_LIBDEPS += -lreadline
 endif
 
 ifdef SYSTEMD
@@ -50,6 +50,8 @@ endif
 OBJS = main.o pidfile.o uxlsnr.o uxclnt.o cli.o cli_handlers.o waiter.o \
        dmevents.o init_unwinder.o
 
+CLI_OBJS = multipathc.o cli.o
+
 ifeq ($(FPIN_SUPPORT),1)
 OBJS += fpin_handlers.o
 endif
@@ -57,18 +59,26 @@ endif
 
 
 EXEC = multipathd
+CLI = multipathc
 
-all : $(EXEC)
+all : $(EXEC) $(CLI)
 
 $(EXEC): $(OBJS) $(multipathdir)/libmultipath.so $(mpathcmddir)/libmpathcmd.so
 	$(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) -o $(EXEC) $(LIBDEPS)
 
+multipathc.o:	multipathc.c
+	$(CC) $(CPPFLAGS) $(RL_CPPFLAGS) $(CFLAGS) -Wno-unused-parameter -c -o $@ $<
+
+$(CLI):  $(CLI_OBJS)
+	$(CC) $(CFLAGS) $(CLI_OBJS) $(LDFLAGS) -o $@ $(CLI_LIBDEPS) $(RL_LIBDEPS)
+
 cli_handlers.o:	cli_handlers.c
 	$(CC) $(CPPFLAGS) $(CFLAGS) -Wno-unused-parameter -c -o $@ $<
 
 install:
 	$(INSTALL_PROGRAM) -d $(DESTDIR)$(bindir)
 	$(INSTALL_PROGRAM) -m 755 $(EXEC) $(DESTDIR)$(bindir)
+	$(INSTALL_PROGRAM) -m 755 $(CLI) $(DESTDIR)$(bindir)
 ifdef SYSTEMD
 	$(INSTALL_PROGRAM) -d $(DESTDIR)$(unitdir)
 	$(INSTALL_PROGRAM) -m 644 $(EXEC).service $(DESTDIR)$(unitdir)
@@ -78,13 +88,13 @@ endif
 	$(INSTALL_PROGRAM) -m 644 $(EXEC).8 $(DESTDIR)$(man8dir)
 
 uninstall:
-	$(RM) $(DESTDIR)$(bindir)/$(EXEC)
+	$(RM) $(DESTDIR)$(bindir)/$(EXEC) $(DESTDIR)$(bindir)/$(CLI)
 	$(RM) $(DESTDIR)$(man8dir)/$(EXEC).8
 	$(RM) $(DESTDIR)$(unitdir)/$(EXEC).service
 	$(RM) $(DESTDIR)$(unitdir)/$(EXEC).socket
 
 clean: dep_clean
-	$(RM) core *.o $(EXEC)
+	$(RM) core *.o $(EXEC) $(CLI)
 
 include $(wildcard $(OBJS:.o=.d))
 
diff --git a/multipathd/cli.c b/multipathd/cli.c
index cc56950..d1bfeee 100644
--- a/multipathd/cli.c
+++ b/multipathd/cli.c
@@ -11,12 +11,6 @@
 #include "parser.h"
 #include "util.h"
 #include "version.h"
-#ifdef USE_LIBEDIT
-#include <editline/readline.h>
-#endif
-#ifdef USE_LIBREADLINE
-#include <readline/readline.h>
-#endif
 
 #include "mpath_cmd.h"
 #include "cli.h"
@@ -26,6 +20,16 @@
 static vector keys;
 static vector handlers;
 
+vector get_keys(void)
+{
+	return keys;
+}
+
+vector get_handlers(void)
+{
+	return handlers;
+}
+
 static struct key *
 alloc_key (void)
 {
@@ -225,8 +229,7 @@ load_keys (void)
 	return 0;
 }
 
-static struct key *
-find_key (const char * str)
+struct key *find_key (const char * str)
 {
 	int i;
 	int len, klen;
@@ -323,8 +326,7 @@ out:
 	return r;
 }
 
-static uint64_t
-fingerprint(const struct _vector *vec)
+uint64_t fingerprint(const struct _vector *vec)
 {
 	int i;
 	uint64_t fp = 0;
@@ -458,111 +460,3 @@ void cli_exit(void)
 	free_keys(keys);
 	keys = NULL;
 }
-
-#if defined(USE_LIBREADLINE) || defined(USE_LIBEDIT)
-static int
-key_match_fingerprint (struct key * kw, uint64_t fp)
-{
-	if (!fp)
-		return 0;
-
-	return ((fp & kw->code) == kw->code);
-}
-
-/*
- * This is the readline completion handler
- */
-char *
-key_generator (const char * str, int state)
-{
-	static int index, len, has_param;
-	static uint64_t rlfp;
-	struct key * kw;
-	int i;
-	struct handler *h;
-	vector v = NULL;
-
-	if (!state) {
-		index = 0;
-		has_param = 0;
-		rlfp = 0;
-		len = strlen(str);
-		int r = get_cmdvec(rl_line_buffer, &v);
-		/*
-		 * If a word completion is in progress, we don't want
-		 * to take an exact keyword match in the fingerprint.
-		 * For ex "show map[tab]" would validate "map" and discard
-		 * "maps" as a valid candidate.
-		 */
-		if (v && len)
-			vector_del_slot(v, VECTOR_SIZE(v) - 1);
-		/*
-		 * Clean up the mess if we dropped the last slot of a 1-slot
-		 * vector
-		 */
-		if (v && !VECTOR_SIZE(v)) {
-			vector_free(v);
-			v = NULL;
-		}
-		/*
-		 * If last keyword takes a param, don't even try to guess
-		 */
-		if (r == EINVAL) {
-			has_param = 1;
-			return (strdup("(value)"));
-		}
-		/*
-		 * Compute a command fingerprint to find out possible completions.
-		 * Once done, the vector is useless. Free it.
-		 */
-		if (v) {
-			rlfp = fingerprint(v);
-			free_keys(v);
-		}
-	}
-	/*
-	 * No more completions for parameter placeholder.
-	 * Brave souls might try to add parameter completion by walking paths and
-	 * multipaths vectors.
-	 */
-	if (has_param)
-		return ((char *)NULL);
-	/*
-	 * Loop through keywords for completion candidates
-	 */
-	vector_foreach_slot_after (keys, kw, index) {
-		if (!strncmp(kw->str, str, len)) {
-			/*
-			 * Discard keywords already in the command line
-			 */
-			if (key_match_fingerprint(kw, rlfp)) {
-				struct key * curkw = find_key(str);
-				if (!curkw || (curkw != kw))
-					continue;
-			}
-			/*
-			 * Discard keywords making syntax errors.
-			 *
-			 * nfp is the candidate fingerprint we try to
-			 * validate against all known command fingerprints.
-			 */
-			uint64_t nfp = rlfp | kw->code;
-			vector_foreach_slot(handlers, h, i) {
-				if (!rlfp || ((h->fingerprint & nfp) == nfp)) {
-					/*
-					 * At least one full command is
-					 * possible with this keyword :
-					 * Consider it validated
-					 */
-					index++;
-					return (strdup(kw->str));
-				}
-			}
-		}
-	}
-	/*
-	 * No more candidates
-	 */
-	return ((char *)NULL);
-}
-#endif
diff --git a/multipathd/cli.h b/multipathd/cli.h
index a6082ac..cb5bbe2 100644
--- a/multipathd/cli.h
+++ b/multipathd/cli.h
@@ -151,6 +151,9 @@ void free_keys (vector vec);
 void free_handlers (void);
 int cli_init (void);
 void cli_exit(void);
-char * key_generator (const char * str, int state);
+uint64_t fingerprint(const struct _vector *vec);
+vector get_keys(void);
+vector get_handlers(void);
+struct key *find_key (const char * str);
 
 #endif /* _CLI_H_ */
diff --git a/multipathd/multipathc.c b/multipathd/multipathc.c
new file mode 100644
index 0000000..571a182
--- /dev/null
+++ b/multipathd/multipathc.c
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2022 SUSE LLC
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+#include <string.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "mpath_cmd.h"
+#include "uxclnt.h"
+#include "vector.h"
+#include "uxsock.h"
+#include "util.h"
+#include "cli.h"
+
+#ifdef USE_LIBEDIT
+#include <editline/readline.h>
+#endif
+#ifdef USE_LIBREADLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
+/*
+ * Versions of libedit prior to 2016 were using a wrong
+ * prototype for rl_completion_entry_function in readline.h.
+ * Internally, libedit casts this to the correct type
+ * (char *)(*)(const char *, int).
+ * So we simply cast to the wrong prototype here.
+ * See http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libedit/readline/readline.h.diff?r1=1.34&r2=1.35
+ * Unfortunately, this change isn't reflected in the libedit version.
+ */
+#ifdef BROKEN_RL_COMPLETION_FUNC
+#define RL_COMP_ENTRY_CAST(x) ((int (*)(const char *, int)) (x))
+#else
+#define RL_COMP_ENTRY_CAST(x) (x)
+#endif
+
+#if defined(USE_LIBREADLINE) || defined(USE_LIBEDIT)
+static int
+key_match_fingerprint (struct key * kw, uint64_t fp)
+{
+	if (!fp)
+		return 0;
+
+	return ((fp & kw->code) == kw->code);
+}
+
+/*
+ * This is the readline completion handler
+ */
+char *
+key_generator (const char * str, int state)
+{
+	static int index, len, has_param;
+	static uint64_t rlfp;
+	struct key * kw;
+	int i;
+	struct handler *h;
+	vector v = NULL;
+	const vector keys = get_keys();
+	const vector handlers = get_handlers();
+
+	if (!state) {
+		index = 0;
+		has_param = 0;
+		rlfp = 0;
+		len = strlen(str);
+		int r = get_cmdvec(rl_line_buffer, &v);
+		/*
+		 * If a word completion is in progress, we don't want
+		 * to take an exact keyword match in the fingerprint.
+		 * For ex "show map[tab]" would validate "map" and discard
+		 * "maps" as a valid candidate.
+		 */
+		if (v && len)
+			vector_del_slot(v, VECTOR_SIZE(v) - 1);
+		/*
+		 * Clean up the mess if we dropped the last slot of a 1-slot
+		 * vector
+		 */
+		if (v && !VECTOR_SIZE(v)) {
+			vector_free(v);
+			v = NULL;
+		}
+		/*
+		 * If last keyword takes a param, don't even try to guess
+		 */
+		if (r == EINVAL) {
+			has_param = 1;
+			return (strdup("(value)"));
+		}
+		/*
+		 * Compute a command fingerprint to find out possible completions.
+		 * Once done, the vector is useless. Free it.
+		 */
+		if (v) {
+			rlfp = fingerprint(v);
+			free_keys(v);
+		}
+	}
+	/*
+	 * No more completions for parameter placeholder.
+	 * Brave souls might try to add parameter completion by walking paths and
+	 * multipaths vectors.
+	 */
+	if (has_param)
+		return ((char *)NULL);
+	/*
+	 * Loop through keywords for completion candidates
+	 */
+	vector_foreach_slot_after (keys, kw, index) {
+		if (!strncmp(kw->str, str, len)) {
+			/*
+			 * Discard keywords already in the command line
+			 */
+			if (key_match_fingerprint(kw, rlfp)) {
+				struct key * curkw = find_key(str);
+				if (!curkw || (curkw != kw))
+					continue;
+			}
+			/*
+			 * Discard keywords making syntax errors.
+			 *
+			 * nfp is the candidate fingerprint we try to
+			 * validate against all known command fingerprints.
+			 */
+			uint64_t nfp = rlfp | kw->code;
+			vector_foreach_slot(handlers, h, i) {
+				if (!rlfp || ((h->fingerprint & nfp) == nfp)) {
+					/*
+					 * At least one full command is
+					 * possible with this keyword :
+					 * Consider it validated
+					 */
+					index++;
+					return (strdup(kw->str));
+				}
+			}
+		}
+	}
+	/*
+	 * No more candidates
+	 */
+	return ((char *)NULL);
+}
+#endif
+
+static void print_reply(char *s)
+{
+	if (!s)
+		return;
+
+	if (isatty(1)) {
+		printf("%s", s);
+		return;
+	}
+	/* strip ANSI color markers */
+	while (*s != '\0') {
+		if ((*s == 0x1b) && (*(s+1) == '['))
+			while ((*s++ != 'm') && (*s != '\0')) {};
+		putchar(*s++);
+	}
+}
+
+static int need_quit(char *str, size_t len)
+{
+	char *ptr, *start;
+	size_t trimed_len = len;
+
+	for (ptr = str; trimed_len && isspace(*ptr);
+	     trimed_len--, ptr++)
+		;
+
+	start = ptr;
+
+	for (ptr = str + len - 1; trimed_len && isspace(*ptr);
+	     trimed_len--, ptr--)
+		;
+
+	if ((trimed_len == 4 && !strncmp(start, "exit", 4)) ||
+	    (trimed_len == 4 && !strncmp(start, "quit", 4)))
+		return 1;
+
+	return 0;
+}
+
+/*
+ * process the client
+ */
+static void process(int fd, unsigned int timeout)
+{
+
+#if defined(USE_LIBREADLINE) || defined(USE_LIBEDIT)
+	rl_readline_name = "multipathd";
+	rl_completion_entry_function = RL_COMP_ENTRY_CAST(key_generator);
+#endif
+
+	cli_init();
+	for(;;)
+	{
+		char *line __attribute__((cleanup(cleanup_charp))) = NULL;
+		char *reply __attribute__((cleanup(cleanup_charp))) = NULL;
+		ssize_t llen;
+		int ret;
+
+#if defined(USE_LIBREADLINE) || defined(USE_LIBEDIT)
+		line = readline("multipathd> ");
+		if (!line)
+			break;
+		llen = strlen(line);
+		if (!llen)
+			continue;
+#else
+		size_t lsize = 0;
+
+		fputs("multipathd> ", stdout);
+		errno = 0;
+		llen = getline(&line, &lsize, stdin);
+		if (llen == -1) {
+			if (errno != 0)
+				fprintf(stderr, "Error in getline: %m");
+			break;
+		}
+		if (!llen || !strcmp(line, "\n"))
+			continue;
+#endif
+
+		if (need_quit(line, llen))
+			break;
+
+		if (send_packet(fd, line) != 0)
+			break;
+		ret = recv_packet(fd, &reply, timeout);
+		if (ret != 0)
+			break;
+
+		print_reply(reply);
+	}
+}
+
+int main (void)
+{
+	int fd = mpath_connect();
+
+	if (fd == -1)
+		return 1;
+
+	process(fd, DEFAULT_REPLY_TIMEOUT + 100);
+	mpath_disconnect(fd);
+	return 0;
+}
diff --git a/multipathd/uxclnt.c b/multipathd/uxclnt.c
index b817bea..15e1293 100644
--- a/multipathd/uxclnt.c
+++ b/multipathd/uxclnt.c
@@ -5,128 +5,14 @@
  * Copyright (c) 2005 Benjamin Marzinski, Redhat
  */
 #include <stdio.h>
+#include <string.h>
 #include <stdlib.h>
-#include <unistd.h>
-#include <stdarg.h>
-#include <ctype.h>
-#include <fcntl.h>
 #include <errno.h>
-#include <sys/ioctl.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <poll.h>
-
-#ifdef USE_LIBEDIT
-#include <editline/readline.h>
-#endif
-#ifdef USE_LIBREADLINE
-#include <readline/readline.h>
-#include <readline/history.h>
-#endif
 
 #include "mpath_cmd.h"
 #include "uxsock.h"
-#include "defaults.h"
-
-#include "vector.h"
-#include "util.h"
-#include "cli.h"
 #include "uxclnt.h"
 
-static void print_reply(char *s)
-{
-	if (!s)
-		return;
-
-	if (isatty(1)) {
-		printf("%s", s);
-		return;
-	}
-	/* strip ANSI color markers */
-	while (*s != '\0') {
-		if ((*s == 0x1b) && (*(s+1) == '['))
-			while ((*s++ != 'm') && (*s != '\0')) {};
-		putchar(*s++);
-	}
-}
-
-static int need_quit(char *str, size_t len)
-{
-	char *ptr, *start;
-	size_t trimed_len = len;
-
-	for (ptr = str; trimed_len && isspace(*ptr);
-	     trimed_len--, ptr++)
-		;
-
-	start = ptr;
-
-	for (ptr = str + len - 1; trimed_len && isspace(*ptr);
-	     trimed_len--, ptr--)
-		;
-
-	if ((trimed_len == 4 && !strncmp(start, "exit", 4)) ||
-	    (trimed_len == 4 && !strncmp(start, "quit", 4)))
-		return 1;
-
-	return 0;
-}
-
-/*
- * process the client
- */
-static void process(int fd, unsigned int timeout)
-{
-
-#if defined(USE_LIBREADLINE) || defined(USE_LIBEDIT)
-	rl_readline_name = "multipathd";
-	rl_completion_entry_function = key_generator;
-#endif
-
-	cli_init();
-	for(;;)
-	{
-		char *line __attribute__((cleanup(cleanup_charp))) = NULL;
-		char *reply __attribute__((cleanup(cleanup_charp))) = NULL;
-		ssize_t llen;
-		int ret;
-
-#if defined(USE_LIBREADLINE) || defined(USE_LIBEDIT)
-		line = readline("multipathd> ");
-		if (!line)
-			break;
-		llen = strlen(line);
-		if (!llen)
-			continue;
-#else
-		size_t lsize = 0;
-
-		fputs("multipathd> ", stdout);
-		errno = 0;
-		llen = getline(&line, &lsize, stdin);
-		if (llen == -1) {
-			if (errno != 0)
-				fprintf(stderr, "Error in getline: %m");
-			break;
-		}
-		if (!llen || !strcmp(line, "\n"))
-			continue;
-#endif
-
-		if (need_quit(line, llen))
-			break;
-
-		if (send_packet(fd, line) != 0)
-			break;
-		ret = recv_packet(fd, &reply, timeout);
-		if (ret != 0)
-			break;
-
-		print_reply(reply);
-	}
-}
-
 static int process_req(int fd, char * inbuf, unsigned int timeout)
 {
 	char *reply;
@@ -159,13 +45,11 @@ int uxclnt(char * inbuf, unsigned int timeout)
 	int fd, ret = 0;
 
 	fd = mpath_connect();
-	if (fd == -1)
-		exit(1);
+	if (fd == -1 || !inbuf)
+		return 1;
+
+	ret = process_req(fd, inbuf, timeout);
 
-	if (inbuf)
-		ret = process_req(fd, inbuf, timeout);
-	else
-		process(fd, timeout);
 	mpath_disconnect(fd);
 	return ret;
 }
-- 
2.37.1



More information about the dm-devel mailing list