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 2a0c102..cb5bbe2 100644
--- a/multipathd/cli.h
+++ b/multipathd/cli.h
@@ -151,8 +151,9 @@ void free_keys (vector vec);
 void free_handlers (void);
 int cli_init (void);
 void cli_exit(void);
-#if defined(USE_LIBREADLINE) || defined(USE_LIBEDIT)
-char *key_generator (const char * str, int state);
-#endif
+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..a4f9023
--- /dev/null
+++ b/multipathd/multipathc.c
@@ -0,0 +1,259 @@
+/*
+ * 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);
+
+#if defined(USE_LIBREADLINE) || defined(USE_LIBEDIT)
+		if (line && *line)
+			add_history(line);
+#endif
+	}
+}
+
+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 deff565..c3ae5db 100644
--- a/multipathd/uxclnt.c
+++ b/multipathd/uxclnt.c
@@ -5,133 +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);
-
-#if defined(USE_LIBREADLINE) || defined(USE_LIBEDIT)
-		if (line && *line)
-			add_history(line);
-#endif
-	}
-}
-
 static int process_req(int fd, char * inbuf, unsigned int timeout)
 {
 	char *reply;
@@ -163,14 +44,14 @@ int uxclnt(char * inbuf, unsigned int timeout)
 {
 	int fd, ret = 0;
 
+	if (!inbuf)
+		return 1;
 	fd = mpath_connect();
 	if (fd == -1)
-		exit(1);
+		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;
 }
