ostree/trivial-httpd: Fix --autoexit with --daemonize and --log-file
When --autoexit is used with --daemonize and --log-file, the program never exits when the root directory is deleted. I believe what happens is that g_file_new_for_path triggers the glib worker context to be started to talk to GVfs. Once the program forks, the parent exits and the thread iterating the worker context is gone. The file monitor then never receives any events because the inotify helper also runs from the worker context. Move the fork earlier just after parsing and validating the command line arguments. In order to handle setup errors in the child, a pipe is opened and the parents waits until the child writes a status byte to it. If the byte is 0, the parent considers the child setup successful and exits and the child carries on as a daemon. Notably, the child doesn't reopen stderr to /dev/null until after this so that it can send error messages there. Fixes: #1941
This commit is contained in:
parent
8cc81126a1
commit
fbf5a94e0a
|
|
@ -507,6 +507,7 @@ run (int argc, char **argv, GCancellable *cancellable, GError **error)
|
||||||
const char *dirpath;
|
const char *dirpath;
|
||||||
OtTrivialHttpd appstruct = { 0, };
|
OtTrivialHttpd appstruct = { 0, };
|
||||||
OtTrivialHttpd *app = &appstruct;
|
OtTrivialHttpd *app = &appstruct;
|
||||||
|
int pipefd[2] = { -1, -1 };
|
||||||
glnx_unref_object SoupServer *server = NULL;
|
glnx_unref_object SoupServer *server = NULL;
|
||||||
g_autoptr(GFileMonitor) dirmon = NULL;
|
g_autoptr(GFileMonitor) dirmon = NULL;
|
||||||
|
|
||||||
|
|
@ -540,17 +541,86 @@ run (int argc, char **argv, GCancellable *cancellable, GError **error)
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opt_daemonize && (g_strcmp0 (opt_log, "-") == 0))
|
||||||
|
{
|
||||||
|
ot_util_usage_error (context, "Cannot use --log-file=- and --daemonize at the same time", error);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fork early before glib sets up its worker context and thread since they'll
|
||||||
|
* be gone once the parent exits. The parent waits on a pipe with the child to
|
||||||
|
* handle setup errors. The child writes a 0 when setup is successful and a 1
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
if (opt_daemonize)
|
||||||
|
{
|
||||||
|
if (pipe (pipefd) == -1)
|
||||||
|
{
|
||||||
|
glnx_set_error_from_errno (error);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t pid = fork();
|
||||||
|
if (pid == -1)
|
||||||
|
{
|
||||||
|
glnx_set_error_from_errno (error);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
else if (pid > 0)
|
||||||
|
{
|
||||||
|
/* Parent, read the child status from the pipe */
|
||||||
|
glnx_close_fd (&pipefd[1]);
|
||||||
|
guint8 buf;
|
||||||
|
int res = TEMP_FAILURE_RETRY (read (pipefd[0], &buf, 1));
|
||||||
|
if (res < 0)
|
||||||
|
{
|
||||||
|
glnx_set_error_from_errno (error);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
else if (res == 0)
|
||||||
|
{
|
||||||
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||||
|
"Child process closed pipe without writing status");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_debug ("Read %u from child", buf);
|
||||||
|
if (buf > 0)
|
||||||
|
{
|
||||||
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||||
|
"Child process failed during setup");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
glnx_close_fd (&pipefd[0]);
|
||||||
|
|
||||||
|
ret = TRUE;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Child, continue */
|
||||||
|
glnx_close_fd (&pipefd[0]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Since we're used for testing purposes, let's just do this by
|
||||||
|
* default. This ensures we exit when our parent does.
|
||||||
|
*/
|
||||||
|
if (prctl (PR_SET_PDEATHSIG, SIGTERM) != 0)
|
||||||
|
{
|
||||||
|
if (errno != ENOSYS)
|
||||||
|
{
|
||||||
|
glnx_set_error_from_errno (error);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (opt_log)
|
if (opt_log)
|
||||||
{
|
{
|
||||||
GOutputStream *stream = NULL;
|
GOutputStream *stream = NULL;
|
||||||
|
|
||||||
if (g_strcmp0 (opt_log, "-") == 0)
|
if (g_strcmp0 (opt_log, "-") == 0)
|
||||||
{
|
{
|
||||||
if (opt_daemonize)
|
|
||||||
{
|
|
||||||
ot_util_usage_error (context, "Cannot use --log-file=- and --daemonize at the same time", error);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
stream = G_OUTPUT_STREAM (g_unix_output_stream_new (STDOUT_FILENO, FALSE));
|
stream = G_OUTPUT_STREAM (g_unix_output_stream_new (STDOUT_FILENO, FALSE));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -625,45 +695,39 @@ run (int argc, char **argv, GCancellable *cancellable, GError **error)
|
||||||
#if !SOUP_CHECK_VERSION(2, 48, 0)
|
#if !SOUP_CHECK_VERSION(2, 48, 0)
|
||||||
soup_server_run_async (server);
|
soup_server_run_async (server);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (opt_daemonize)
|
if (opt_daemonize)
|
||||||
{
|
{
|
||||||
pid_t pid = fork();
|
/* Write back a 0 to the pipe to indicate setup was successful. */
|
||||||
if (pid == -1)
|
guint8 buf = 0;
|
||||||
|
g_debug ("Writing %u to parent", buf);
|
||||||
|
if (TEMP_FAILURE_RETRY (write (pipefd[1], &buf, 1)) == -1)
|
||||||
{
|
{
|
||||||
int errsv = errno;
|
glnx_set_error_from_errno (error);
|
||||||
g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
|
|
||||||
g_strerror (errsv));
|
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
else if (pid > 0)
|
glnx_close_fd (&pipefd[1]);
|
||||||
{
|
|
||||||
ret = TRUE;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
/* Child, continue */
|
|
||||||
if (setsid () < 0)
|
if (setsid () < 0)
|
||||||
err (1, "setsid");
|
{
|
||||||
|
glnx_set_prefix_error_from_errno (error, "%s", "setsid: ");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
/* Daemonising: close stdout/stderr so $() et al work on us */
|
/* Daemonising: close stdout/stderr so $() et al work on us */
|
||||||
if (freopen("/dev/null", "r", stdin) == NULL)
|
if (freopen("/dev/null", "r", stdin) == NULL)
|
||||||
err (1, "freopen");
|
|
||||||
if (freopen("/dev/null", "w", stdout) == NULL)
|
|
||||||
err (1, "freopen");
|
|
||||||
if (freopen("/dev/null", "w", stderr) == NULL)
|
|
||||||
err (1, "freopen");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* Since we're used for testing purposes, let's just do this by
|
|
||||||
* default. This ensures we exit when our parent does.
|
|
||||||
*/
|
|
||||||
if (prctl (PR_SET_PDEATHSIG, SIGTERM) != 0)
|
|
||||||
{
|
{
|
||||||
if (errno != ENOSYS)
|
glnx_set_prefix_error_from_errno (error, "%s", "freopen: ");
|
||||||
{
|
goto out;
|
||||||
glnx_set_error_from_errno (error);
|
}
|
||||||
goto out;
|
if (freopen("/dev/null", "w", stdout) == NULL)
|
||||||
}
|
{
|
||||||
|
glnx_set_prefix_error_from_errno (error, "%s", "freopen: ");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (freopen("/dev/null", "w", stderr) == NULL)
|
||||||
|
{
|
||||||
|
glnx_set_prefix_error_from_errno (error, "%s", "freopen: ");
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -699,6 +763,21 @@ run (int argc, char **argv, GCancellable *cancellable, GError **error)
|
||||||
|
|
||||||
ret = TRUE;
|
ret = TRUE;
|
||||||
out:
|
out:
|
||||||
|
if (pipefd[0] >= 0)
|
||||||
|
{
|
||||||
|
/* Read end in the parent. This should only be open on errors. */
|
||||||
|
g_assert_false (ret);
|
||||||
|
glnx_close_fd (&pipefd[0]);
|
||||||
|
}
|
||||||
|
if (pipefd[1] >= 0)
|
||||||
|
{
|
||||||
|
/* Write end in the child. This should only be open on errors. */
|
||||||
|
g_assert_false (ret);
|
||||||
|
guint8 buf = 1;
|
||||||
|
g_debug ("Writing %u to parent", buf);
|
||||||
|
(void) TEMP_FAILURE_RETRY (write (pipefd[1], &buf, 1));
|
||||||
|
glnx_close_fd (&pipefd[1]);
|
||||||
|
}
|
||||||
if (app->root_dfd != -1)
|
if (app->root_dfd != -1)
|
||||||
(void) close (app->root_dfd);
|
(void) close (app->root_dfd);
|
||||||
g_clear_object (&app->log);
|
g_clear_object (&app->log);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue