From: Hanna Reitz <hreitz@redhat.com>
To: qemu-block@nongnu.org
Cc: Kevin Wolf <kwolf@redhat.com>, Hanna Reitz <hreitz@redhat.com>,
qemu-devel@nongnu.org
Subject: [PATCH 2/3] qsd: Add --daemonize
Date: Wed, 22 Dec 2021 12:41:52 +0100 [thread overview]
Message-ID: <20211222114153.67721-3-hreitz@redhat.com> (raw)
In-Reply-To: <20211222114153.67721-1-hreitz@redhat.com>
This option does basically the same as --fork does for qemu-nbd:
- We fork off a child process
- The child process is daemonized (closing its stdin and stdout)
- stderr of the child is routed through the parent, so the parent can
see errors and adjust its exit code accordingly
- Once the child closes its end of this stderr pipe (done right after
creating the PID file), the parent exits
It is not named --fork, because --fork was probably a name that few
programs but qemu-nbd ever used. qemu (the system emulator) itself uses
-daemonize, too. (Besides, QSD's interface is not compatible to
qemu-nbd anyway; compare --pidfile vs. --pid-file.)
Signed-off-by: Hanna Reitz <hreitz@redhat.com>
---
docs/tools/qemu-storage-daemon.rst | 7 ++
storage-daemon/qemu-storage-daemon.c | 151 +++++++++++++++++++++++++++
2 files changed, 158 insertions(+)
diff --git a/docs/tools/qemu-storage-daemon.rst b/docs/tools/qemu-storage-daemon.rst
index 3e5a9dc032..83905ad526 100644
--- a/docs/tools/qemu-storage-daemon.rst
+++ b/docs/tools/qemu-storage-daemon.rst
@@ -149,6 +149,13 @@ Standard options:
created but before accepting connections. The daemon has started successfully
when the pid file is written and clients may begin connecting.
+.. option:: --daemonize
+
+ Daemonize the process. The parent process will exit once startup is complete
+ (i.e., after the pid file has been or would have been written) or failure
+ occurs. Its exit code reflects whether the child has started up successfully
+ or failed to do so.
+
Examples
--------
Launch the daemon with QMP monitor socket ``qmp.sock`` so clients can execute
diff --git a/storage-daemon/qemu-storage-daemon.c b/storage-daemon/qemu-storage-daemon.c
index 42a52d3b1c..cc94240545 100644
--- a/storage-daemon/qemu-storage-daemon.c
+++ b/storage-daemon/qemu-storage-daemon.c
@@ -60,6 +60,7 @@
#include "trace/control.h"
static const char *pid_file;
+static bool daemonize_opt;
static volatile bool exit_requested = false;
void qemu_system_killed(int signal, pid_t pid)
@@ -124,6 +125,9 @@ static void help(void)
"\n"
" --pidfile <path> write process ID to a file after startup\n"
"\n"
+" --daemonize daemonize the process, and have the parent exit\n"
+" once startup is complete\n"
+"\n"
QEMU_HELP_BOTTOM "\n",
error_get_progname());
}
@@ -131,6 +135,7 @@ QEMU_HELP_BOTTOM "\n",
enum {
OPTION_BLOCKDEV = 256,
OPTION_CHARDEV,
+ OPTION_DAEMONIZE,
OPTION_EXPORT,
OPTION_MONITOR,
OPTION_NBD_SERVER,
@@ -187,6 +192,7 @@ static void process_options(int argc, char *argv[], bool pre_init_pass)
static const struct option long_options[] = {
{"blockdev", required_argument, NULL, OPTION_BLOCKDEV},
{"chardev", required_argument, NULL, OPTION_CHARDEV},
+ {"daemonize", no_argument, NULL, OPTION_DAEMONIZE},
{"export", required_argument, NULL, OPTION_EXPORT},
{"help", no_argument, NULL, 'h'},
{"monitor", required_argument, NULL, OPTION_MONITOR},
@@ -212,6 +218,7 @@ static void process_options(int argc, char *argv[], bool pre_init_pass)
c == '?' ||
c == 'h' ||
c == 'V' ||
+ c == OPTION_DAEMONIZE ||
c == OPTION_PIDFILE;
/* Process every option only in its respective pass */
@@ -264,6 +271,9 @@ static void process_options(int argc, char *argv[], bool pre_init_pass)
qemu_opts_del(opts);
break;
}
+ case OPTION_DAEMONIZE:
+ daemonize_opt = true;
+ break;
case OPTION_EXPORT:
{
Visitor *v;
@@ -342,8 +352,137 @@ static void pid_file_init(void)
atexit(pid_file_cleanup);
}
+/**
+ * Handle daemonizing.
+ *
+ * Return false on error, and true if and only if daemonizing was
+ * successful and we are in the child process. (The parent process will
+ * never return true, but instead rather exit() if there was no error.)
+ *
+ * When returning true, *old_stderr is set to an FD representing the
+ * original stderr. Once the child is set up (after creating the PID
+ * file, and before entering the main loop), it should invoke
+ * `daemon_detach(old_stderr)` to have the parent process exit and
+ * restore the original stderr.
+ */
+static bool daemonize(int *old_stderr, Error **errp)
+{
+ int stderr_fd[2];
+ pid_t pid;
+ int ret;
+
+ if (qemu_pipe(stderr_fd) < 0) {
+ error_setg_errno(errp, errno, "Error setting up communication pipe");
+ return false;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ error_setg_errno(errp, errno, "Failed to fork");
+ return false;
+ }
+
+ if (pid == 0) { /* Child process */
+ close(stderr_fd[0]); /* Close pipe's read end */
+
+ /* Keep the old stderr so we can reuse it after the parent has quit */
+ *old_stderr = dup(STDERR_FILENO);
+ if (*old_stderr < 0) {
+ /*
+ * Cannot return an error without having our stderr point to the
+ * pipe: Otherwise, the parent process would not see the error
+ * message and so not exit with EXIT_FAILURE.
+ */
+ error_setg_errno(errp, errno, "Failed to duplicate stderr FD");
+ dup2(stderr_fd[1], STDERR_FILENO);
+ close(stderr_fd[1]);
+ return false;
+ }
+
+ /*
+ * Daemonize, redirecting all std streams to /dev/null; then
+ * (even on error) redirect stderr to the pipe's write end
+ */
+ ret = qemu_daemon(1, 0);
+
+ /*
+ * Unconditionally redirect stderr to the pipe's write end (and
+ * close the then-unused write end FD, because now stderr points
+ * to it)
+ */
+ dup2(stderr_fd[1], STDERR_FILENO);
+ close(stderr_fd[1]);
+
+ if (ret < 0) {
+ error_setg_errno(errp, errno, "Failed to daemonize");
+ close(*old_stderr);
+ *old_stderr = -1;
+ return false;
+ }
+
+ return true;
+ } else { /* Parent process */
+ bool errors = false;
+ g_autofree char *buf = g_malloc(1024);
+
+ close(stderr_fd[1]); /* Close pipe's write end */
+
+ /* Print error messages from the child until it closes the pipe */
+ while ((ret = read(stderr_fd[0], buf, 1024)) > 0) {
+ errors = true;
+ ret = qemu_write_full(STDERR_FILENO, buf, ret);
+ if (ret < 0) {
+ error_setg_errno(errp, -ret,
+ "Failed to print error received from the "
+ "daemonized child to stderr");
+ close(stderr_fd[0]);
+ return false;
+ }
+ }
+
+ close(stderr_fd[0]); /* Close read end, it is unused now */
+
+ if (ret < 0) {
+ error_setg_errno(errp, -ret, "Cannot read from daemon");
+ return false;
+ }
+
+ /*
+ * Child is either detached and running (in which case it should
+ * not have printed any errors, and @errors should be false), or
+ * has encountered an error (which it should have printed, so
+ * @errors should be true) and has exited.
+ *
+ * Exit with the appropriate exit code.
+ */
+ exit(errors ? EXIT_FAILURE : EXIT_SUCCESS);
+ }
+}
+
+/**
+ * After daemonize(): Let the parent process exit by closing the pipe
+ * connected to it. The original stderr is restored from *old_stderr.
+ *
+ * This function should be called after creating the PID file and before
+ * entering the main loop.
+ */
+static void daemon_detach(int *old_stderr)
+{
+ /*
+ * Ignore errors; closing the old stderr should not fail, and if
+ * dup-ing fails, then we cannot print anything to stderr anyway
+ */
+ dup2(*old_stderr, STDERR_FILENO);
+
+ close(*old_stderr);
+ *old_stderr = -1;
+}
+
int main(int argc, char *argv[])
{
+ Error *local_err = NULL;
+ int old_stderr = -1;
+
#ifdef CONFIG_POSIX
signal(SIGPIPE, SIG_IGN);
#endif
@@ -354,6 +493,14 @@ int main(int argc, char *argv[])
process_options(argc, argv, true);
+ if (daemonize_opt) {
+ if (!daemonize(&old_stderr, &local_err)) {
+ error_report_err(local_err);
+ return EXIT_FAILURE;
+ }
+ assert(old_stderr >= 0);
+ }
+
module_call_init(MODULE_INIT_QOM);
module_call_init(MODULE_INIT_TRACE);
qemu_add_opts(&qemu_trace_opts);
@@ -377,6 +524,10 @@ int main(int argc, char *argv[])
*/
pid_file_init();
+ if (daemonize_opt) {
+ daemon_detach(&old_stderr);
+ }
+
while (!exit_requested) {
main_loop_wait(false);
}
--
2.33.1
next prev parent reply other threads:[~2021-12-22 11:49 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-12-22 11:41 [PATCH 0/3] qsd: Add --daemonize; and add job quit tests Hanna Reitz
2021-12-22 11:41 ` [PATCH 1/3] qsd: Add pre-init argument parsing pass Hanna Reitz
2021-12-30 16:00 ` Vladimir Sementsov-Ogievskiy
2022-01-03 16:14 ` Hanna Reitz
2022-01-19 12:58 ` Markus Armbruster
2022-01-19 13:44 ` Hanna Reitz
2022-01-19 17:21 ` Kevin Wolf
2022-01-20 16:00 ` Markus Armbruster
2022-01-20 16:31 ` Hanna Reitz
2022-01-21 6:10 ` Markus Armbruster
2022-01-21 8:43 ` Hanna Reitz
2022-01-21 10:27 ` Markus Armbruster
2022-01-21 11:16 ` Hanna Reitz
2022-01-21 14:26 ` Markus Armbruster
2022-01-24 8:20 ` Hanna Reitz
2022-01-24 9:23 ` Markus Armbruster
2022-01-24 9:34 ` Hanna Reitz
2021-12-22 11:41 ` Hanna Reitz [this message]
2021-12-30 16:12 ` [PATCH 2/3] qsd: Add --daemonize Vladimir Sementsov-Ogievskiy
2022-01-03 17:15 ` Hanna Reitz
2021-12-22 11:41 ` [PATCH 3/3] iotests/185: Add post-READY quit tests Hanna Reitz
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20211222114153.67721-3-hreitz@redhat.com \
--to=hreitz@redhat.com \
--cc=kwolf@redhat.com \
--cc=qemu-block@nongnu.org \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).