diff --git a/tools/xenstore/Makefile.common b/tools/xenstore/Makefile.common
index dffab34603..2befede03d 100644
--- a/tools/xenstore/Makefile.common
+++ b/tools/xenstore/Makefile.common
@@ -1,7 +1,7 @@
 # Makefile shared with stubdom
 
 XENSTORED_OBJS-y := xenstored_core.o xenstored_watch.o xenstored_domain.o
-XENSTORED_OBJS-y += xenstored_transaction.o xenstored_control.o
+XENSTORED_OBJS-y += xenstored_transaction.o xenstored_control.o xenstored_lu.o
 XENSTORED_OBJS-y += xs_lib.o talloc.o utils.o tdb.o hashtable.o
 
 XENSTORED_OBJS-$(CONFIG_Linux) += xenstored_posix.o xenstored_lu_daemon.o
diff --git a/tools/xenstore/xenstored_control.c b/tools/xenstore/xenstored_control.c
index c9e5766954..4d66e61b2c 100644
--- a/tools/xenstore/xenstored_control.c
+++ b/tools/xenstore/xenstored_control.c
@@ -16,21 +16,13 @@
     along with this program; If not, see <http://www.gnu.org/licenses/>.
 */
 
-#include <assert.h>
-#include <ctype.h>
 #include <errno.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <syslog.h>
-#include <time.h>
 #include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/mman.h>
-#include <fcntl.h>
 #include <unistd.h>
-#include <xenctrl.h>
 
 #include "utils.h"
 #include "talloc.h"
@@ -39,69 +31,6 @@
 #include "xenstored_control.h"
 #include "xenstored_domain.h"
 #include "xenstored_lu.h"
-#include "xenstored_watch.h"
-
-#ifndef NO_LIVE_UPDATE
-struct live_update *lu_status;
-
-const char *lu_begin(struct connection *conn)
-{
-	if (lu_status)
-		return "live-update session already active.";
-
-	lu_status = talloc_zero(conn, struct live_update);
-	if (!lu_status)
-		return "Allocation failure.";
-	lu_status->conn = conn;
-	talloc_set_destructor(lu_status, lu_destroy);
-
-	return NULL;
-}
-
-struct connection *lu_get_connection(void)
-{
-	return lu_status ? lu_status->conn : NULL;
-}
-
-unsigned int lu_write_response(FILE *fp)
-{
-	struct xsd_sockmsg msg;
-
-	assert(lu_status);
-
-	msg = lu_status->in->hdr.msg;
-
-	msg.len = sizeof("OK");
-	if (fp && fwrite(&msg, sizeof(msg), 1, fp) != 1)
-		return 0;
-	if (fp && fwrite("OK", msg.len, 1, fp) != 1)
-		return 0;
-
-	return sizeof(msg) + msg.len;
-}
-
-bool lu_is_pending(void)
-{
-	return lu_status != NULL;
-}
-
-#else
-struct connection *lu_get_connection(void)
-{
-	return NULL;
-}
-
-unsigned int lu_write_response(FILE *fp)
-{
-	/* Unsupported */
-	return 0;
-}
-
-bool lu_is_pending(void)
-{
-	return false;
-}
-#endif
 
 struct cmd_s {
 	char *cmd;
@@ -352,344 +281,6 @@ static int do_control_print(const void *ctx, struct connection *conn,
 	return 0;
 }
 
-#ifndef NO_LIVE_UPDATE
-static const char *lu_abort(const void *ctx, struct connection *conn)
-{
-	syslog(LOG_INFO, "live-update: abort\n");
-
-	if (!lu_status)
-		return "No live-update session active.";
-
-	/* Destructor will do the real abort handling. */
-	talloc_free(lu_status);
-
-	return NULL;
-}
-
-static const char *lu_cmdline(const void *ctx, struct connection *conn,
-			      const char *cmdline)
-{
-	syslog(LOG_INFO, "live-update: cmdline %s\n", cmdline);
-
-	if (!lu_status || lu_status->conn != conn)
-		return "Not in live-update session.";
-
-	lu_status->cmdline = talloc_strdup(lu_status, cmdline);
-	if (!lu_status->cmdline)
-		return "Allocation failure.";
-
-	return NULL;
-}
-
-static bool lu_check_lu_allowed(void)
-{
-	struct connection *conn;
-	time_t now = time(NULL);
-	unsigned int ta_total = 0, ta_long = 0;
-
-	list_for_each_entry(conn, &connections, list) {
-		if (conn->ta_start_time) {
-			ta_total++;
-			if (now - conn->ta_start_time >= lu_status->timeout)
-				ta_long++;
-		}
-	}
-
-	/*
-	 * Allow LiveUpdate if one of the following conditions is met:
-	 *	- There is no active transactions
-	 *	- All transactions are long running (e.g. they have been
-	 *	active for more than lu_status->timeout sec) and the admin as
-	 *	requested to force the operation.
-	 */
-	return ta_total ? (lu_status->force && ta_long == ta_total) : true;
-}
-
-static const char *lu_reject_reason(const void *ctx)
-{
-	char *ret = NULL;
-	struct connection *conn;
-	time_t now = time(NULL);
-
-	list_for_each_entry(conn, &connections, list) {
-		if (conn->ta_start_time &&
-		    (now - conn->ta_start_time >= lu_status->timeout)) {
-			ret = talloc_asprintf(ctx, "%s\nDomain %u: %ld s",
-					      ret ? : "Domains with long running transactions:",
-					      conn->id,
-					      now - conn->ta_start_time);
-		}
-	}
-
-	return ret ? (const char *)ret : "Overlapping transactions";
-}
-
-static const char *lu_dump_state(const void *ctx, struct connection *conn)
-{
-	FILE *fp;
-	const char *ret;
-	struct xs_state_record_header end;
-	struct xs_state_preamble pre;
-
-	fp = lu_dump_open(ctx);
-	if (!fp)
-		return "Dump state open error";
-
-	memcpy(pre.ident, XS_STATE_IDENT, sizeof(pre.ident));
-	pre.version = htobe32(XS_STATE_VERSION);
-	pre.flags = XS_STATE_FLAGS;
-	if (fwrite(&pre, sizeof(pre), 1, fp) != 1) {
-		ret = "Dump write error";
-		goto out;
-	}
-
-	ret = dump_state_global(fp);
-	if (ret)
-		goto out;
-	ret = dump_state_connections(fp);
-	if (ret)
-		goto out;
-	ret = dump_state_nodes(fp, ctx);
-	if (ret)
-		goto out;
-
-	end.type = XS_STATE_TYPE_END;
-	end.length = 0;
-	if (fwrite(&end, sizeof(end), 1, fp) != 1)
-		ret = "Dump write error";
-
- out:
-	lu_dump_close(fp);
-
-	return ret;
-}
-
-void lu_read_state(void)
-{
-	struct lu_dump_state state = {};
-	struct xs_state_record_header *head;
-	void *ctx = talloc_new(NULL); /* Work context for subfunctions. */
-	struct xs_state_preamble *pre;
-
-	syslog(LOG_INFO, "live-update: read state\n");
-	lu_get_dump_state(&state);
-	if (state.size == 0)
-		barf_perror("No state found after live-update");
-
-	pre = state.buf;
-	if (memcmp(pre->ident, XS_STATE_IDENT, sizeof(pre->ident)) ||
-	    pre->version != htobe32(XS_STATE_VERSION) ||
-	    pre->flags != XS_STATE_FLAGS)
-		barf("Unknown record identifier");
-	for (head = state.buf + sizeof(*pre);
-	     head->type != XS_STATE_TYPE_END &&
-		(void *)head - state.buf < state.size;
-	     head = (void *)head + sizeof(*head) + head->length) {
-		switch (head->type) {
-		case XS_STATE_TYPE_GLOBAL:
-			read_state_global(ctx, head + 1);
-			break;
-		case XS_STATE_TYPE_CONN:
-			read_state_connection(ctx, head + 1);
-			break;
-		case XS_STATE_TYPE_WATCH:
-			read_state_watch(ctx, head + 1);
-			break;
-		case XS_STATE_TYPE_TA:
-			xprintf("live-update: ignore transaction record\n");
-			break;
-		case XS_STATE_TYPE_NODE:
-			read_state_node(ctx, head + 1);
-			break;
-		default:
-			xprintf("live-update: unknown state record %08x\n",
-				head->type);
-			break;
-		}
-	}
-
-	lu_close_dump_state(&state);
-
-	talloc_free(ctx);
-
-	/*
-	 * We may have missed the VIRQ_DOM_EXC notification and a domain may
-	 * have died while we were live-updating. So check all the domains are
-	 * still alive.
-	 */
-	check_domains();
-}
-
-static const char *lu_activate_binary(const void *ctx)
-{
-	int argc;
-	char **argv;
-	unsigned int i;
-
-	if (lu_status->cmdline) {
-		argc = 4;   /* At least one arg + progname + "-U" + NULL. */
-		for (i = 0; lu_status->cmdline[i]; i++)
-			if (isspace(lu_status->cmdline[i]))
-				argc++;
-		argv = talloc_array(ctx, char *, argc);
-		if (!argv)
-			return "Allocation failure.";
-
-		i = 0;
-		argc = 1;
-		argv[1] = strtok(lu_status->cmdline, " \t");
-		while (argv[argc]) {
-			if (!strcmp(argv[argc], "-U"))
-				i = 1;
-			argc++;
-			argv[argc] = strtok(NULL, " \t");
-		}
-
-		if (!i) {
-			argv[argc++] = "-U";
-			argv[argc] = NULL;
-		}
-	} else {
-		for (i = 0; i < orig_argc; i++)
-			if (!strcmp(orig_argv[i], "-U"))
-				break;
-
-		argc = orig_argc;
-		argv = talloc_array(ctx, char *, orig_argc + 2);
-		if (!argv)
-			return "Allocation failure.";
-
-		memcpy(argv, orig_argv, orig_argc * sizeof(*argv));
-		if (i == orig_argc)
-			argv[argc++] = "-U";
-		argv[argc] = NULL;
-	}
-
-	domain_deinit();
-
-	return lu_exec(ctx, argc, argv);
-}
-
-static bool do_lu_start(struct delayed_request *req)
-{
-	time_t now = time(NULL);
-	const char *ret;
-	struct buffered_data *saved_in;
-	struct connection *conn = req->data;
-
-	/*
-	 * Cancellation may have been requested asynchronously. In this
-	 * case, lu_status will be NULL.
-	 */
-	if (!lu_status) {
-		ret = "Cancellation was requested";
-		goto out;
-	}
-
-	assert(lu_status->conn == conn);
-
-	if (!lu_check_lu_allowed()) {
-		if (now < lu_status->started_at + lu_status->timeout)
-			return false;
-		if (!lu_status->force) {
-			ret = lu_reject_reason(req);
-			goto out;
-		}
-	}
-
-	assert(req->in == lu_status->in);
-	/* Dump out internal state, including "OK" for live update. */
-	ret = lu_dump_state(req->in, conn);
-	if (!ret) {
-		/* Perform the activation of new binary. */
-		ret = lu_activate_binary(req->in);
-	}
-
-	/* We will reach this point only in case of failure. */
- out:
-	/*
-	 * send_reply() will send the response for conn->in. Save the current
-	 * conn->in and restore it afterwards.
-	 */
-	saved_in = conn->in;
-	conn->in = req->in;
-	send_reply(conn, XS_CONTROL, ret, strlen(ret) + 1);
-	conn->in = saved_in;
-	talloc_free(lu_status);
-
-	return true;
-}
-
-static const char *lu_start(const void *ctx, struct connection *conn,
-			    bool force, unsigned int to)
-{
-	syslog(LOG_INFO, "live-update: start, force=%d, to=%u\n", force, to);
-
-	if (!lu_status || lu_status->conn != conn)
-		return "Not in live-update session.";
-
-#ifdef __MINIOS__
-	if (lu_status->kernel_size != lu_status->kernel_off)
-		return "Kernel not complete.";
-#endif
-
-	lu_status->force = force;
-	lu_status->timeout = to;
-	lu_status->started_at = time(NULL);
-	lu_status->in = conn->in;
-
-	errno = delay_request(conn, conn->in, do_lu_start, conn, false);
-
-	return NULL;
-}
-
-static int do_control_lu(const void *ctx, struct connection *conn,
-			 char **vec, int num)
-{
-	const char *ret = NULL;
-	unsigned int i;
-	bool force = false;
-	unsigned int to = 0;
-
-	if (num < 1)
-		return EINVAL;
-
-	if (!strcmp(vec[0], "-a")) {
-		if (num == 1)
-			ret = lu_abort(ctx, conn);
-		else
-			return EINVAL;
-	} else if (!strcmp(vec[0], "-c")) {
-		if (num == 2)
-			ret = lu_cmdline(ctx, conn, vec[1]);
-		else
-			return EINVAL;
-	} else if (!strcmp(vec[0], "-s")) {
-		for (i = 1; i < num; i++) {
-			if (!strcmp(vec[i], "-F"))
-				force = true;
-			else if (!strcmp(vec[i], "-t") && i < num - 1) {
-				i++;
-				to = atoi(vec[i]);
-			} else
-				return EINVAL;
-		}
-		ret = lu_start(ctx, conn, force, to);
-		if (!ret)
-			return errno;
-	} else {
-		ret = lu_arch(ctx, conn, vec, num);
-		if (!ret && errno)
-			return errno;
-	}
-
-	if (!ret)
-		ret = "OK";
-	send_reply(conn, XS_CONTROL, ret, strlen(ret) + 1);
-	return 0;
-}
-#endif
-
 static int do_control_help(const void *, struct connection *, char **, int);
 
 static struct cmd_s cmds[] = {
diff --git a/tools/xenstore/xenstored_control.h b/tools/xenstore/xenstored_control.h
index a8cb76559b..faa955968d 100644
--- a/tools/xenstore/xenstored_control.h
+++ b/tools/xenstore/xenstored_control.h
@@ -18,11 +18,3 @@
 
 int do_control(const void *ctx, struct connection *conn,
 	       struct buffered_data *in);
-void lu_read_state(void);
-
-struct connection *lu_get_connection(void);
-
-/* Write the "OK" response for the live-update command */
-unsigned int lu_write_response(FILE *fp);
-
-bool lu_is_pending(void);
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
index e16d0a6d53..3842e122ce 100644
--- a/tools/xenstore/xenstored_core.c
+++ b/tools/xenstore/xenstored_core.c
@@ -52,6 +52,7 @@
 #include "xenstored_transaction.h"
 #include "xenstored_domain.h"
 #include "xenstored_control.h"
+#include "xenstored_lu.h"
 #include "tdb.h"
 
 #ifndef NO_SOCKETS
diff --git a/tools/xenstore/xenstored_lu.c b/tools/xenstore/xenstored_lu.c
new file mode 100644
index 0000000000..cfd6d5cee2
--- /dev/null
+++ b/tools/xenstore/xenstored_lu.c
@@ -0,0 +1,401 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/*
+ * Live Update interfaces for Xen Store Daemon.
+ * Copyright (C) 2022 Juergen Gross, SUSE LLC
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <time.h>
+
+#include "talloc.h"
+#include "xs_lib.h"
+#include "xenstored_core.h"
+#include "xenstored_domain.h"
+#include "xenstored_lu.h"
+#include "xenstored_watch.h"
+
+#ifndef NO_LIVE_UPDATE
+struct live_update *lu_status;
+
+const char *lu_begin(struct connection *conn)
+{
+	if (lu_status)
+		return "live-update session already active.";
+
+	lu_status = talloc_zero(conn, struct live_update);
+	if (!lu_status)
+		return "Allocation failure.";
+	lu_status->conn = conn;
+	talloc_set_destructor(lu_status, lu_destroy);
+
+	return NULL;
+}
+
+struct connection *lu_get_connection(void)
+{
+	return lu_status ? lu_status->conn : NULL;
+}
+
+unsigned int lu_write_response(FILE *fp)
+{
+	struct xsd_sockmsg msg;
+
+	assert(lu_status);
+
+	msg = lu_status->in->hdr.msg;
+
+	msg.len = sizeof("OK");
+	if (fp && fwrite(&msg, sizeof(msg), 1, fp) != 1)
+		return 0;
+	if (fp && fwrite("OK", msg.len, 1, fp) != 1)
+		return 0;
+
+	return sizeof(msg) + msg.len;
+}
+
+bool lu_is_pending(void)
+{
+	return lu_status != NULL;
+}
+
+void lu_read_state(void)
+{
+	struct lu_dump_state state = {};
+	struct xs_state_record_header *head;
+	void *ctx = talloc_new(NULL); /* Work context for subfunctions. */
+	struct xs_state_preamble *pre;
+
+	syslog(LOG_INFO, "live-update: read state\n");
+	lu_get_dump_state(&state);
+	if (state.size == 0)
+		barf_perror("No state found after live-update");
+
+	pre = state.buf;
+	if (memcmp(pre->ident, XS_STATE_IDENT, sizeof(pre->ident)) ||
+	    pre->version != htobe32(XS_STATE_VERSION) ||
+	    pre->flags != XS_STATE_FLAGS)
+		barf("Unknown record identifier");
+	for (head = state.buf + sizeof(*pre);
+	     head->type != XS_STATE_TYPE_END &&
+		(void *)head - state.buf < state.size;
+	     head = (void *)head + sizeof(*head) + head->length) {
+		switch (head->type) {
+		case XS_STATE_TYPE_GLOBAL:
+			read_state_global(ctx, head + 1);
+			break;
+		case XS_STATE_TYPE_CONN:
+			read_state_connection(ctx, head + 1);
+			break;
+		case XS_STATE_TYPE_WATCH:
+			read_state_watch(ctx, head + 1);
+			break;
+		case XS_STATE_TYPE_TA:
+			xprintf("live-update: ignore transaction record\n");
+			break;
+		case XS_STATE_TYPE_NODE:
+			read_state_node(ctx, head + 1);
+			break;
+		default:
+			xprintf("live-update: unknown state record %08x\n",
+				head->type);
+			break;
+		}
+	}
+
+	lu_close_dump_state(&state);
+
+	talloc_free(ctx);
+
+	/*
+	 * We may have missed the VIRQ_DOM_EXC notification and a domain may
+	 * have died while we were live-updating. So check all the domains are
+	 * still alive.
+	 */
+	check_domains();
+}
+
+static const char *lu_abort(const void *ctx, struct connection *conn)
+{
+	syslog(LOG_INFO, "live-update: abort\n");
+
+	if (!lu_status)
+		return "No live-update session active.";
+
+	/* Destructor will do the real abort handling. */
+	talloc_free(lu_status);
+
+	return NULL;
+}
+
+static const char *lu_cmdline(const void *ctx, struct connection *conn,
+			      const char *cmdline)
+{
+	syslog(LOG_INFO, "live-update: cmdline %s\n", cmdline);
+
+	if (!lu_status || lu_status->conn != conn)
+		return "Not in live-update session.";
+
+	lu_status->cmdline = talloc_strdup(lu_status, cmdline);
+	if (!lu_status->cmdline)
+		return "Allocation failure.";
+
+	return NULL;
+}
+
+static bool lu_check_lu_allowed(void)
+{
+	struct connection *conn;
+	time_t now = time(NULL);
+	unsigned int ta_total = 0, ta_long = 0;
+
+	list_for_each_entry(conn, &connections, list) {
+		if (conn->ta_start_time) {
+			ta_total++;
+			if (now - conn->ta_start_time >= lu_status->timeout)
+				ta_long++;
+		}
+	}
+
+	/*
+	 * Allow LiveUpdate if one of the following conditions is met:
+	 *	- There is no active transactions
+	 *	- All transactions are long running (e.g. they have been
+	 *	active for more than lu_status->timeout sec) and the admin as
+	 *	requested to force the operation.
+	 */
+	return ta_total ? (lu_status->force && ta_long == ta_total) : true;
+}
+
+static const char *lu_reject_reason(const void *ctx)
+{
+	char *ret = NULL;
+	struct connection *conn;
+	time_t now = time(NULL);
+
+	list_for_each_entry(conn, &connections, list) {
+		if (conn->ta_start_time &&
+		    (now - conn->ta_start_time >= lu_status->timeout)) {
+			ret = talloc_asprintf(ctx, "%s\nDomain %u: %ld s",
+					      ret ? : "Domains with long running transactions:",
+					      conn->id,
+					      now - conn->ta_start_time);
+		}
+	}
+
+	return ret ? (const char *)ret : "Overlapping transactions";
+}
+
+static const char *lu_dump_state(const void *ctx, struct connection *conn)
+{
+	FILE *fp;
+	const char *ret;
+	struct xs_state_record_header end;
+	struct xs_state_preamble pre;
+
+	fp = lu_dump_open(ctx);
+	if (!fp)
+		return "Dump state open error";
+
+	memcpy(pre.ident, XS_STATE_IDENT, sizeof(pre.ident));
+	pre.version = htobe32(XS_STATE_VERSION);
+	pre.flags = XS_STATE_FLAGS;
+	if (fwrite(&pre, sizeof(pre), 1, fp) != 1) {
+		ret = "Dump write error";
+		goto out;
+	}
+
+	ret = dump_state_global(fp);
+	if (ret)
+		goto out;
+	ret = dump_state_connections(fp);
+	if (ret)
+		goto out;
+	ret = dump_state_nodes(fp, ctx);
+	if (ret)
+		goto out;
+
+	end.type = XS_STATE_TYPE_END;
+	end.length = 0;
+	if (fwrite(&end, sizeof(end), 1, fp) != 1)
+		ret = "Dump write error";
+
+ out:
+	lu_dump_close(fp);
+
+	return ret;
+}
+
+static const char *lu_activate_binary(const void *ctx)
+{
+	int argc;
+	char **argv;
+	unsigned int i;
+
+	if (lu_status->cmdline) {
+		argc = 4;   /* At least one arg + progname + "-U" + NULL. */
+		for (i = 0; lu_status->cmdline[i]; i++)
+			if (isspace(lu_status->cmdline[i]))
+				argc++;
+		argv = talloc_array(ctx, char *, argc);
+		if (!argv)
+			return "Allocation failure.";
+
+		i = 0;
+		argc = 1;
+		argv[1] = strtok(lu_status->cmdline, " \t");
+		while (argv[argc]) {
+			if (!strcmp(argv[argc], "-U"))
+				i = 1;
+			argc++;
+			argv[argc] = strtok(NULL, " \t");
+		}
+
+		if (!i) {
+			argv[argc++] = "-U";
+			argv[argc] = NULL;
+		}
+	} else {
+		for (i = 0; i < orig_argc; i++)
+			if (!strcmp(orig_argv[i], "-U"))
+				break;
+
+		argc = orig_argc;
+		argv = talloc_array(ctx, char *, orig_argc + 2);
+		if (!argv)
+			return "Allocation failure.";
+
+		memcpy(argv, orig_argv, orig_argc * sizeof(*argv));
+		if (i == orig_argc)
+			argv[argc++] = "-U";
+		argv[argc] = NULL;
+	}
+
+	domain_deinit();
+
+	return lu_exec(ctx, argc, argv);
+}
+
+static bool do_lu_start(struct delayed_request *req)
+{
+	time_t now = time(NULL);
+	const char *ret;
+	struct buffered_data *saved_in;
+	struct connection *conn = req->data;
+
+	/*
+	 * Cancellation may have been requested asynchronously. In this
+	 * case, lu_status will be NULL.
+	 */
+	if (!lu_status) {
+		ret = "Cancellation was requested";
+		goto out;
+	}
+
+	assert(lu_status->conn == conn);
+
+	if (!lu_check_lu_allowed()) {
+		if (now < lu_status->started_at + lu_status->timeout)
+			return false;
+		if (!lu_status->force) {
+			ret = lu_reject_reason(req);
+			goto out;
+		}
+	}
+
+	assert(req->in == lu_status->in);
+	/* Dump out internal state, including "OK" for live update. */
+	ret = lu_dump_state(req->in, conn);
+	if (!ret) {
+		/* Perform the activation of new binary. */
+		ret = lu_activate_binary(req->in);
+	}
+
+	/* We will reach this point only in case of failure. */
+ out:
+	/*
+	 * send_reply() will send the response for conn->in. Save the current
+	 * conn->in and restore it afterwards.
+	 */
+	saved_in = conn->in;
+	conn->in = req->in;
+	send_reply(conn, XS_CONTROL, ret, strlen(ret) + 1);
+	conn->in = saved_in;
+	talloc_free(lu_status);
+
+	return true;
+}
+
+static const char *lu_start(const void *ctx, struct connection *conn,
+			    bool force, unsigned int to)
+{
+	syslog(LOG_INFO, "live-update: start, force=%d, to=%u\n", force, to);
+
+	if (!lu_status || lu_status->conn != conn)
+		return "Not in live-update session.";
+
+#ifdef __MINIOS__
+	if (lu_status->kernel_size != lu_status->kernel_off)
+		return "Kernel not complete.";
+#endif
+
+	lu_status->force = force;
+	lu_status->timeout = to;
+	lu_status->started_at = time(NULL);
+	lu_status->in = conn->in;
+
+	errno = delay_request(conn, conn->in, do_lu_start, conn, false);
+
+	return NULL;
+}
+
+int do_control_lu(const void *ctx, struct connection *conn, char **vec,
+		  int num)
+{
+	const char *ret = NULL;
+	unsigned int i;
+	bool force = false;
+	unsigned int to = 0;
+
+	if (num < 1)
+		return EINVAL;
+
+	if (!strcmp(vec[0], "-a")) {
+		if (num == 1)
+			ret = lu_abort(ctx, conn);
+		else
+			return EINVAL;
+	} else if (!strcmp(vec[0], "-c")) {
+		if (num == 2)
+			ret = lu_cmdline(ctx, conn, vec[1]);
+		else
+			return EINVAL;
+	} else if (!strcmp(vec[0], "-s")) {
+		for (i = 1; i < num; i++) {
+			if (!strcmp(vec[i], "-F"))
+				force = true;
+			else if (!strcmp(vec[i], "-t") && i < num - 1) {
+				i++;
+				to = atoi(vec[i]);
+			} else
+				return EINVAL;
+		}
+		ret = lu_start(ctx, conn, force, to);
+		if (!ret)
+			return errno;
+	} else {
+		ret = lu_arch(ctx, conn, vec, num);
+		if (!ret && errno)
+			return errno;
+	}
+
+	if (!ret)
+		ret = "OK";
+	send_reply(conn, XS_CONTROL, ret, strlen(ret) + 1);
+	return 0;
+}
+#endif
diff --git a/tools/xenstore/xenstored_lu.h b/tools/xenstore/xenstored_lu.h
index d2f8e4e57c..7aa524a07e 100644
--- a/tools/xenstore/xenstored_lu.h
+++ b/tools/xenstore/xenstored_lu.h
@@ -43,6 +43,16 @@ struct lu_dump_state {
 
 extern struct live_update *lu_status;
 
+struct connection *lu_get_connection(void);
+bool lu_is_pending(void);
+void lu_read_state(void);
+
+/* Write the "OK" response for the live-update command */
+unsigned int lu_write_response(FILE *fp);
+
+int do_control_lu(const void *ctx, struct connection *conn, char **vec,
+		  int num);
+
 /* Live update private interfaces. */
 void lu_get_dump_state(struct lu_dump_state *state);
 void lu_close_dump_state(struct lu_dump_state *state);
@@ -53,4 +63,19 @@ const char *lu_arch(const void *ctx, struct connection *conn, char **vec,
 		    int num);
 const char *lu_begin(struct connection *conn);
 int lu_destroy(void *data);
+#else
+static inline struct connection *lu_get_connection(void)
+{
+	return NULL;
+}
+
+static inline unsigned int lu_write_response(FILE *fp)
+{
+	return 0;
+}
+
+static inline bool lu_is_pending(void)
+{
+	return false;
+}
 #endif
