[PATCH] xserver: Optional backtrace handler
Simon Farnsworth
simon.farnsworth at onelan.com
Thu Sep 22 03:56:09 PDT 2011
Review is inline:
On Wednesday 21 September 2011, Barry Scott <barry.scott at onelan.co.uk> wrote:
> It is useful to be able to run an external program to analyse
> a crashed server process. The server will run an user surplied
^ ^^^^^^^^
The server will run a user supplied
> program in a subprocess of the server.
>
> The subprocess is created when the server starts up so that all
> resources needed to create the subprocess are available. It is not
> always possible to start up a process from within a crashed server.
> For example if the malloc heap is corrupt fork will not work.
>
> If the server crashes a "crash" message is sent to the handler on
> its stdin. The handler can then respond in whatever way it wishes.
> If the handler wants the server to continue, return from
> xorg_backtrace, it must write a line to its stdout.
"If the handler wants the server to return from xorg_backtrace, it
must write a line to its stdout". I'm not sure that the server can
sensibly continue after xorg_backtrace is called.
>
> When then server exits normally the handler is sent the "exit"
> message just before the server exits.
>
> Signed-off-by: Barry Scott <barry.scott at onelan.co.uk>
> ---
> dix/main.c | 6 +++-
> include/os.h | 3 ++
> man/Xserver.man | 19 ++++++++++
> os/backtrace.c | 101
> +++++++++++++++++++++++++++++++++++++++++++++++++++++-- os/utils.c |
> 8 ++++
> 5 files changed, 133 insertions(+), 4 deletions(-)
>
<snip>
> -
> diff --git a/include/os.h b/include/os.h
> index a553f57..48debe9 100644
> --- a/include/os.h
> +++ b/include/os.h
> @@ -542,5 +542,8 @@ extern _X_EXPORT void Error(const char *str);
> extern _X_EXPORT void LogPrintMarkers(void);
>
> extern _X_EXPORT void xorg_backtrace(void);
> +extern _X_EXPORT void xorg_init_backtrace(void);
> +extern _X_EXPORT void xorg_cleanup_backtrace(void);
I'm not sure _X_EXPORT is appropriate here - given that these
functions are meant to be called by main and main alone, would
_X_HIDDEN be better?
> +extern const char *BacktraceHandlerPath;
>
> #endif /* OS_H */
> diff --git a/man/Xserver.man b/man/Xserver.man
> index f743912..4933375 100644
> --- a/man/Xserver.man
> +++ b/man/Xserver.man
<snip>
> -530,6 +533,22 @@ the following font path:
> /usr/share/fonts/default/ghostscript
> .fi
>
> +.SH BACKTRACE HANDLER
> +
> +The optional backtrace handler program will be run in a subprocess when
> +the server starts. The handler is invoked with argv[1] set to the PID
> +of the server.
> +.PP
> +The handler is expected to read a line from stdin that will tell the
> +handler what has happened to the server.
> +.PP
> +When the server is about to exit normally "exit\\n" is sent to the
> handler. +.PP
> +If the server crashes "crash\n" is sent to the handler and the server will
> +wait for a line to written out on the handlers stdout.
"the server will wait for a line to *be* written out on the *handler's*
stdout".
I would add a bit here about what you expect the handler to do to the
server, and what happens if it replies; my guess would be that the
server would attempt to continue normally unless the handler kills it,
but in fact, when the handler says "go", the server will attempt a
clean exit.
> +.PP
> +
> +
> .SH FILES
> .TP 30
> .I /etc/X\fBn\fP.hosts
> diff --git a/os/backtrace.c b/os/backtrace.c
> index 7ca6dab..a897876 100644
> --- a/os/backtrace.c
> +++ b/os/backtrace.c
<snip>
> @@ -207,7 +212,97 @@ void xorg_backtrace(void) {
> # else
>
> /* Default fallback if we can't find any way to get a backtrace */
> -void xorg_backtrace(void) { return; }
> +void xorg_builtin_backtrace(void) { return; }
>
> # endif
> #endif
> +
> +const char *BacktraceHandlerPath = NULL;
> +
> +static int backtrace_handler_stdin;
> +static int backtrace_handler_stdout;
> +static pid_t child_pid;
> +
> +static const char cmd_crash[] = "crash\n";
> +static const char cmd_exit[] = "exit\n";
> +
> +static void report_init_error(const char *msg) {
> + /* prevent any further use of the handler */
> + BacktraceHandlerPath = NULL;
> + FatalError("%s - errno %d", msg, errno);
> +}
> +
> +void xorg_init_backtrace(void) {
> + char pid_buffer[32];
> + int pipe_fds[2];
> + int child_stdin;
> + int child_stdout;
> +
> + if (!BacktraceHandlerPath)
> + return;
> +
> + if (0!=pipe(pipe_fds))
Unusual coding style for the X server - I'd expect to see:
if (pipe(pipe_fds) != 0)
> + report_init_error("failed to create pipe");
> + backtrace_handler_stdin = pipe_fds[1];
> + child_stdin = pipe_fds[0];
> +
> + if (0!=pipe(pipe_fds))
And again here.
> + report_init_error("failed to create pipe");
> + backtrace_handler_stdout = pipe_fds[0];
> + child_stdout = pipe_fds[1];
> +
> + /* server pid will be passed in argv[1] */
> + snprintf( pid_buffer, sizeof(pid_buffer), "%d", getpid());
> +
> + child_pid = fork();
> + if (child_pid < 0) {
> + report_init_error("failed to fork");
> + } else {
> + if (child_pid == 0) {
> + /* setup stdin, stdout to point to the pipes */
> + dup2(child_stdin, STDIN_FILENO);
> + dup2(child_stdout, STDOUT_FILENO);
> +
> + /* close all other fd to the pipes */
I'd make fd plural here - "close all other fds"
> + close(child_stdin);
> + close(child_stdout);
> + close(backtrace_handler_stdout);
> + close(backtrace_handler_stdout);
> +
> + execl(BacktraceHandlerPath, BacktraceHandlerPath, pid_buffer,
NULL);
> + exit(1);
> + } else {
> + if (0!=close(child_stdin))
Again odd - I'd expect:
if (close(child_stdin) != 0 )
> + report_init_error("failed to close pipe fd");
> + if (0!=close(child_stdout))
And again.
> + report_init_error("failed to close pipe fd");
> + }
> + }
> +}
> +
> +void xorg_backtrace(void) {
> + if (BacktraceHandlerPath) {
> + int status;
> + /* check that the handler process is still running */
> + if (0==waitpid(child_pid, &status, WNOHANG)) {
if (waitpid(child_pid, &status, WNOHANG) == 0) {
> + char buffer[1];
> + /* tell backtrace handler xorg has crashed */
> + write(backtrace_handler_stdin, cmd_crash, sizeof( cmd_crash
)-1);
Spaces in sizeof() are odd - "sizeof(cmd_crash) - 1" is closer to X.org style.
> + /* exit to caller after backtrace handler writes anything to
stdout */
> + read(backtrace_handler_stdout, buffer, sizeof( buffer ));
And again.
> + } else {
> + /* handler process has quit? run builtin handler */
> + xorg_builtin_backtrace();
> + }
> + } else {
> + xorg_builtin_backtrace();
> + }
> +}
> +
> +void xorg_cleanup_backtrace(void) {
> + char buffer[1];
> + if (BacktraceHandlerPath) {
> + /* tell backtrace handler xorg is about to exit cleanly */
> + write(backtrace_handler_stdin, cmd_exit, sizeof( cmd_exit )-1);
Again with the extra spaces in sizeof.
You've asked the backtracer to die - it would be polite to reap the
resulting zombie with waitpid(). Also, once you've cleaned it up, set
BacktraceHandlerPath to NULL, just in case we enter this path during
shutdown.
A general thing in all of the above; read() and write() can return an
error (EPIPE, EINVAL, EINTR all seem plausible). I would expect you to
retry the operation on EINTR, and assume the external backtracer has
died already on any other return value.
One final point - if the X server dies via FatalError, nothing
terminates the external backtracer. It would be polite to tell it to
go away.
The rest looks good - I'll review the next version when it appears.
--
Simon Farnsworth
Software Engineer
ONELAN Limited
http://www.onelan.com/
More information about the xorg-devel
mailing list