Index: uspace/app/meson.build
===================================================================
--- uspace/app/meson.build	(revision a72f3b87bcdb8b8f14877d2ce9c68a09661f54c3)
+++ uspace/app/meson.build	(revision ad9e2257215db1d8451e4a51b25573aa07918d4b)
@@ -77,4 +77,5 @@
 	'redir',
 	'sbi',
+	'shutdown',
 	'sportdmp',
 	'stats',
Index: uspace/app/shutdown/doc/doxygroups.h
===================================================================
--- uspace/app/shutdown/doc/doxygroups.h	(revision ad9e2257215db1d8451e4a51b25573aa07918d4b)
+++ uspace/app/shutdown/doc/doxygroups.h	(revision ad9e2257215db1d8451e4a51b25573aa07918d4b)
@@ -0,0 +1,4 @@
+/** @addtogroup shutdown shutdown
+ * @brief Shut the system down
+ * @ingroup apps
+ */
Index: uspace/app/shutdown/meson.build
===================================================================
--- uspace/app/shutdown/meson.build	(revision ad9e2257215db1d8451e4a51b25573aa07918d4b)
+++ uspace/app/shutdown/meson.build	(revision ad9e2257215db1d8451e4a51b25573aa07918d4b)
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2024 Jiri Svoboda
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# - Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+# - The name of the author may not be used to endorse or promote products
+#   derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+deps = [ 'clui', 'system' ]
+src = files('shutdown.c')
Index: uspace/app/shutdown/shutdown.c
===================================================================
--- uspace/app/shutdown/shutdown.c	(revision ad9e2257215db1d8451e4a51b25573aa07918d4b)
+++ uspace/app/shutdown/shutdown.c	(revision ad9e2257215db1d8451e4a51b25573aa07918d4b)
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2024 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup lprint
+ * @{
+ */
+
+/**
+ * @file
+ * @brief Shut the system down
+ *
+ */
+
+#include <fibril_synch.h>
+#include <nchoice.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <str.h>
+#include <system.h>
+#include "shutdown.h"
+
+#define NAME "shutdown"
+
+static void syntax_print(void);
+
+static sd_action_t action;
+
+static void sd_shutdown_complete(void *);
+static void sd_shutdown_failed(void *);
+
+static system_cb_t sd_system_cb = {
+	.shutdown_complete = sd_shutdown_complete,
+	.shutdown_failed = sd_shutdown_failed
+};
+
+/** System shutdown complete.
+ *
+ * @param arg Argument (shutdown_t *)
+ */
+static void sd_shutdown_complete(void *arg)
+{
+	shutdown_t *shutdown = (shutdown_t *)arg;
+
+	fibril_mutex_lock(&shutdown->lock);
+	shutdown->stopped = true;
+	shutdown->failed = false;
+	fibril_condvar_broadcast(&shutdown->cv);
+	fibril_mutex_unlock(&shutdown->lock);
+}
+
+/** System shutdown failed.
+ *
+ * @param arg Argument (not used)
+ */
+static void sd_shutdown_failed(void *arg)
+{
+	shutdown_t *shutdown = (shutdown_t *)arg;
+
+	fibril_mutex_lock(&shutdown->lock);
+	shutdown->stopped = true;
+	shutdown->failed = true;
+	fibril_condvar_broadcast(&shutdown->cv);
+	fibril_mutex_unlock(&shutdown->lock);
+}
+
+/** Interactively choose the shutdown action to perform
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t choose_action(void)
+{
+	errno_t rc;
+	nchoice_t *nchoice = NULL;
+	void *choice;
+
+	rc = nchoice_create(&nchoice);
+	if (rc != EOK) {
+		printf(NAME ": Out of memory.\n");
+		goto error;
+	}
+
+	rc = nchoice_set_prompt(nchoice, "Select action");
+	if (rc != EOK) {
+		printf(NAME ": Out of memory.\n");
+		goto error;
+	}
+
+	rc = nchoice_add(nchoice, "Power off", (void *)(uintptr_t)sd_poweroff,
+	    0);
+	if (rc != EOK) {
+		printf(NAME ": Out of memory.\n");
+		goto error;
+	}
+
+	rc = nchoice_add(nchoice, "Cancel", (void *)(uintptr_t)sd_cancel,
+	    ncf_default);
+	if (rc != EOK) {
+		printf(NAME ": Out of memory.\n");
+		goto error;
+	}
+
+	rc = nchoice_get(nchoice, &choice);
+	if (rc != EOK) {
+		if (rc != ENOENT)
+			printf(NAME ": Error getting user choice.\n");
+		goto error;
+	}
+
+	action = (sd_action_t)choice;
+	nchoice_destroy(nchoice);
+	return EOK;
+error:
+	if (nchoice != NULL)
+		nchoice_destroy(nchoice);
+	return rc;
+}
+
+int main(int argc, char **argv)
+{
+	errno_t rc;
+	system_t *system = NULL;
+	shutdown_t shutdown;
+
+	--argc;
+	++argv;
+
+	while (*argv != NULL && *argv[0] == '-') {
+		if (str_cmp(*argv, "-p") == 0) {
+			--argc;
+			++argv;
+			action = sd_poweroff;
+			continue;
+		}
+
+		printf(NAME ": Error, invalid option.\n");
+		syntax_print();
+		return 1;
+	}
+
+	if (argc >= 1) {
+		printf(NAME ": Error, unexpected argument.\n");
+		syntax_print();
+		return 1;
+	}
+
+	if (action == 0)
+		choose_action();
+
+	if (action == sd_cancel)
+		return 0;
+
+	fibril_mutex_initialize(&shutdown.lock);
+	fibril_condvar_initialize(&shutdown.cv);
+	shutdown.stopped = false;
+	shutdown.failed = false;
+
+	rc = system_open(SYSTEM_DEFAULT, &sd_system_cb, &shutdown, &system);
+	if (rc != EOK) {
+		printf(NAME ": Failed opening system control service.\n");
+		return 1;
+	}
+
+	rc = system_shutdown(system);
+	if (rc != EOK) {
+		system_close(system);
+		printf(NAME ": Failed requesting system shutdown.\n");
+		return 1;
+	}
+
+	fibril_mutex_lock(&shutdown.lock);
+	printf("System is shutting down...\n");
+	while (!shutdown.stopped)
+		fibril_condvar_wait(&shutdown.cv, &shutdown.lock);
+
+	if (shutdown.failed) {
+		printf("Shutdown failed.\n");
+		system_close(system);
+		return 1;
+	}
+
+	printf("Shutdown complete. It is now safe to remove power.\n");
+
+	/* Sleep forever */
+	while (true)
+		fibril_condvar_wait(&shutdown.cv, &shutdown.lock);
+
+	fibril_mutex_unlock(&shutdown.lock);
+
+	system_close(system);
+	return 0;
+}
+
+/** Print syntax help. */
+static void syntax_print(void)
+{
+	printf("syntax:\n"
+	    "\tshutdown [<options>]\n"
+	    "options:\n"
+	    "\t-p Power off\n");
+}
+
+/**
+ * @}
+ */
Index: uspace/app/shutdown/shutdown.h
===================================================================
--- uspace/app/shutdown/shutdown.h	(revision ad9e2257215db1d8451e4a51b25573aa07918d4b)
+++ uspace/app/shutdown/shutdown.h	(revision ad9e2257215db1d8451e4a51b25573aa07918d4b)
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2024 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup shutdown
+ * @{
+ */
+/**
+ * @file Shut the system down
+ */
+
+#ifndef SHUTDOWN_H
+#define SHUTDOWN_H
+
+#include <fibril_synch.h>
+#include <stdbool.h>
+
+/** Shutdown action */
+typedef enum {
+	sd_poweroff = 1,
+	sd_cancel
+} sd_action_t;
+
+/** Shutdown state */
+typedef struct {
+	fibril_mutex_t lock;
+	fibril_condvar_t cv;
+	bool stopped;
+	bool failed;
+} shutdown_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/system/meson.build
===================================================================
--- uspace/srv/system/meson.build	(revision a72f3b87bcdb8b8f14877d2ce9c68a09661f54c3)
+++ uspace/srv/system/meson.build	(revision ad9e2257215db1d8451e4a51b25573aa07918d4b)
@@ -27,4 +27,4 @@
 #
 
-deps = [ 'device', 'futil' ]
+deps = [ 'device', 'futil', 'system' ]
 src = files('system.c')
Index: uspace/srv/system/system.c
===================================================================
--- uspace/srv/system/system.c	(revision a72f3b87bcdb8b8f14877d2ce9c68a09661f54c3)
+++ uspace/srv/system/system.c	(revision ad9e2257215db1d8451e4a51b25573aa07918d4b)
@@ -37,4 +37,5 @@
 #include <fibril.h>
 #include <futil.h>
+#include <io/log.h>
 #include <stdio.h>
 #include <stdarg.h>
@@ -52,4 +53,6 @@
 #include <vfs/vfs.h>
 #include <vol.h>
+#include <system.h>
+#include <system_srv.h>
 #include "system.h"
 
@@ -78,4 +81,11 @@
 	"/w/data",
 	NULL,
+};
+
+static void system_srv_conn(ipc_call_t *, void *);
+static errno_t system_srv_shutdown(void *);
+
+system_ops_t system_srv_ops = {
+	.shutdown = system_srv_shutdown
 };
 
@@ -420,9 +430,11 @@
 }
 
-int main(int argc, char *argv[])
+/** Perform sytem startup tasks.
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t system_startup(void)
 {
 	errno_t rc;
-
-	info_print();
 
 	/* Make sure file systems are running. */
@@ -441,5 +453,5 @@
 	if (!mount_locfs()) {
 		printf("%s: Exiting\n", NAME);
-		return 2;
+		return EIO;
 	}
 
@@ -491,4 +503,158 @@
 	}
 
+	return EOK;
+}
+
+/** Perform sytem shutdown tasks.
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t system_sys_shutdown(void)
+{
+	vol_t *vol = NULL;
+	service_id_t *part_ids = NULL;
+	size_t nparts;
+	size_t i;
+	errno_t rc;
+
+	/* Eject all volumes. */
+
+	rc = vol_create(&vol);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Error contacting volume "
+		    "service.");
+		goto error;
+	}
+
+	rc = vol_get_parts(vol, &part_ids, &nparts);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Error getting volume list.");
+		goto error;
+	}
+
+	for (i = 0; i < nparts; i++) {
+		rc = vol_part_eject(vol, part_ids[i]);
+		if (rc != EOK) {
+			log_msg(LOG_DEFAULT, LVL_ERROR, "Error ejecting "
+			    "volume %zu", (size_t)part_ids[i]);
+			goto error;
+		}
+	}
+
+	free(part_ids);
+	vol_destroy(vol);
+	return EOK;
+error:
+	if (part_ids != NULL)
+		free(part_ids);
+	if (vol != NULL)
+		vol_destroy(vol);
+	return rc;
+}
+
+/** Initialize system control service. */
+static errno_t system_srv_init(sys_srv_t *syssrv)
+{
+	port_id_t port;
+	loc_srv_t *srv = NULL;
+	service_id_t sid = 0;
+	errno_t rc;
+
+	(void)system;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "system_srv_init()");
+
+	rc = async_create_port(INTERFACE_SYSTEM, system_srv_conn, syssrv,
+	    &port);
+	if (rc != EOK)
+		goto error;
+
+	rc = loc_server_register(NAME, &srv);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR,
+		    "Failed registering server: %s.", str_error(rc));
+		rc = EEXIST;
+		goto error;
+	}
+
+	rc = loc_service_register(srv, SYSTEM_DEFAULT, &sid);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR,
+		    "Failed registering service: %s.", str_error(rc));
+		rc = EEXIST;
+		goto error;
+	}
+
+	return EOK;
+error:
+	if (sid != 0)
+		loc_service_unregister(srv, sid);
+	if (srv != NULL)
+		loc_server_unregister(srv);
+	// XXX destroy port
+	return rc;
+}
+
+/** Handle connection to system server. */
+static void system_srv_conn(ipc_call_t *icall, void *arg)
+{
+	sys_srv_t *syssrv = (sys_srv_t *)arg;
+
+	/* Set up protocol structure */
+	system_srv_initialize(&syssrv->srv);
+	syssrv->srv.ops = &system_srv_ops;
+	syssrv->srv.arg = syssrv;
+
+	/* Handle connection */
+	system_conn(icall, &syssrv->srv);
+}
+
+/** System shutdown request.
+ *
+ * @param arg Argument (sys_srv_t *)
+ */
+static errno_t system_srv_shutdown(void *arg)
+{
+	sys_srv_t *syssrv = (sys_srv_t *)arg;
+	errno_t rc;
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "system_srv_shutdown");
+
+	rc = system_sys_shutdown();
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_NOTE, "system_srv_shutdown failed");
+		system_srv_shutdown_failed(&syssrv->srv);
+	}
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "system_srv_shutdown complete");
+	system_srv_shutdown_complete(&syssrv->srv);
+	return EOK;
+}
+
+int main(int argc, char *argv[])
+{
+	errno_t rc;
+	sys_srv_t srv;
+
+	info_print();
+
+	if (log_init(NAME) != EOK) {
+		printf(NAME ": Failed to initialize logging.\n");
+		return 1;
+	}
+
+	/* Perform startup tasks. */
+	rc = system_startup();
+	if (rc != EOK)
+		return 1;
+
+	rc = system_srv_init(&srv);
+	if (rc != EOK)
+		return 1;
+
+	printf(NAME ": Accepting connections.\n");
+	task_retval(0);
+	async_manager();
+
 	return 0;
 }
Index: uspace/srv/system/system.h
===================================================================
--- uspace/srv/system/system.h	(revision a72f3b87bcdb8b8f14877d2ce9c68a09661f54c3)
+++ uspace/srv/system/system.h	(revision ad9e2257215db1d8451e4a51b25573aa07918d4b)
@@ -38,5 +38,11 @@
 #define SYSTEM_H
 
+#include <system_srv.h>
+
 #define NAME  "system"
+
+typedef struct {
+	system_srv_t srv;
+} sys_srv_t;
 
 #endif
