
/*
 * Copyright (C) by OpenResty Inc.
 */


#ifndef DDEBUG
#define DDEBUG 0
#endif
#include "ddebug.h"


#include "ngx_http_lua_common.h"
#include "ngx_http_lua_input_filters.h"
#include "ngx_http_lua_util.h"
#include "ngx_http_lua_pipe.h"
#if (NGX_HTTP_LUA_HAVE_SIGNALFD)
#include <sys/signalfd.h>
#endif


#ifdef HAVE_NGX_LUA_PIPE
static ngx_rbtree_node_t *ngx_http_lua_pipe_lookup_pid(ngx_rbtree_key_t key);
#if !(NGX_HTTP_LUA_HAVE_SIGNALFD)
static void ngx_http_lua_pipe_sigchld_handler(int signo, siginfo_t *siginfo,
    void *ucontext);
#endif
static void ngx_http_lua_pipe_sigchld_event_handler(ngx_event_t *ev);
static ssize_t ngx_http_lua_pipe_fd_read(ngx_connection_t *c, u_char *buf,
    size_t size);
static ssize_t ngx_http_lua_pipe_fd_write(ngx_connection_t *c, u_char *buf,
    size_t size);
static void ngx_http_lua_pipe_close_helper(ngx_http_lua_pipe_t *pipe,
    ngx_http_lua_pipe_ctx_t *pipe_ctx, ngx_event_t *ev);
static void ngx_http_lua_pipe_close_stdin(ngx_http_lua_pipe_t *pipe);
static void ngx_http_lua_pipe_close_stdout(ngx_http_lua_pipe_t *pipe);
static void ngx_http_lua_pipe_close_stderr(ngx_http_lua_pipe_t *pipe);
static void ngx_http_lua_pipe_proc_finalize(ngx_http_lua_ffi_pipe_proc_t *proc);
static ngx_int_t ngx_http_lua_pipe_get_lua_ctx(ngx_http_request_t *r,
    ngx_http_lua_ctx_t **ctx, u_char *errbuf, size_t *errbuf_size);
static void ngx_http_lua_pipe_put_error(ngx_http_lua_pipe_ctx_t *pipe_ctx,
    u_char *errbuf, size_t *errbuf_size);
static void ngx_http_lua_pipe_put_data(ngx_http_lua_pipe_t *pipe,
    ngx_http_lua_pipe_ctx_t *pipe_ctx, u_char **buf, size_t *buf_size);
static ngx_int_t ngx_http_lua_pipe_add_input_buffer(ngx_http_lua_pipe_t *pipe,
    ngx_http_lua_pipe_ctx_t *pipe_ctx);
static ngx_int_t ngx_http_lua_pipe_read_all(void *data, ssize_t bytes);
static ngx_int_t ngx_http_lua_pipe_read_bytes(void *data, ssize_t bytes);
static ngx_int_t ngx_http_lua_pipe_read_line(void *data, ssize_t bytes);
static ngx_int_t ngx_http_lua_pipe_read_any(void *data, ssize_t bytes);
static ngx_int_t ngx_http_lua_pipe_read(ngx_http_lua_pipe_t *pipe,
    ngx_http_lua_pipe_ctx_t *pipe_ctx);
static ngx_int_t ngx_http_lua_pipe_init_ctx(
    ngx_http_lua_pipe_ctx_t **pipe_ctx_pt, int fd, ngx_pool_t *pool,
    u_char *errbuf, size_t *errbuf_size);
static ngx_int_t ngx_http_lua_pipe_write(ngx_http_lua_pipe_t *pipe,
    ngx_http_lua_pipe_ctx_t *pipe_ctx);
static int ngx_http_lua_pipe_read_stdout_retval(
    ngx_http_lua_ffi_pipe_proc_t *proc, lua_State *L);
static int ngx_http_lua_pipe_read_stderr_retval(
    ngx_http_lua_ffi_pipe_proc_t *proc, lua_State *L);
static int ngx_http_lua_pipe_read_retval_helper(
    ngx_http_lua_ffi_pipe_proc_t *proc, lua_State *L, int from_stderr);
static int ngx_http_lua_pipe_write_retval(ngx_http_lua_ffi_pipe_proc_t *proc,
    lua_State *L);
static int ngx_http_lua_pipe_wait_retval(ngx_http_lua_ffi_pipe_proc_t *proc,
    lua_State *L);
static void ngx_http_lua_pipe_resume_helper(ngx_event_t *ev,
    ngx_http_lua_co_ctx_t *wait_co_ctx);
static void ngx_http_lua_pipe_resume_read_stdout_handler(ngx_event_t *ev);
static void ngx_http_lua_pipe_resume_read_stderr_handler(ngx_event_t *ev);
static void ngx_http_lua_pipe_resume_write_handler(ngx_event_t *ev);
static void ngx_http_lua_pipe_resume_wait_handler(ngx_event_t *ev);
static ngx_int_t ngx_http_lua_pipe_resume(ngx_http_request_t *r);
static void ngx_http_lua_pipe_dummy_event_handler(ngx_event_t *ev);
static void ngx_http_lua_pipe_clear_event(ngx_event_t *ev);
static void ngx_http_lua_pipe_proc_read_stdout_cleanup(void *data);
static void ngx_http_lua_pipe_proc_read_stderr_cleanup(void *data);
static void ngx_http_lua_pipe_proc_write_cleanup(void *data);
static void ngx_http_lua_pipe_proc_wait_cleanup(void *data);
static void ngx_http_lua_pipe_reap_pids(ngx_event_t *ev);
static void ngx_http_lua_pipe_reap_timer_handler(ngx_event_t *ev);
void ngx_http_lua_ffi_pipe_proc_destroy(
    ngx_http_lua_ffi_pipe_proc_t *proc);


static ngx_rbtree_t       ngx_http_lua_pipe_rbtree;
static ngx_rbtree_node_t  ngx_http_lua_pipe_proc_sentinel;
static ngx_event_t        ngx_reap_pid_event;


#if (NGX_HTTP_LUA_HAVE_SIGNALFD)
static int                                ngx_http_lua_signalfd;
static struct signalfd_siginfo            ngx_http_lua_pipe_notification;

#define ngx_http_lua_read_sigfd           ngx_http_lua_signalfd

#else
static int                                ngx_http_lua_sigchldfd[2];
static u_char                             ngx_http_lua_pipe_notification[1];

#define ngx_http_lua_read_sigfd           ngx_http_lua_sigchldfd[0]
#define ngx_http_lua_write_sigfd          ngx_http_lua_sigchldfd[1]
#endif


static ngx_connection_t                  *ngx_http_lua_sigfd_conn = NULL;


/* The below signals are ignored by Nginx.
 * We need to reset them for the spawned child processes. */
ngx_http_lua_pipe_signal_t ngx_signals[] = {
    { SIGSYS, "SIGSYS" },
    { SIGPIPE, "SIGPIPE" },
    { 0, NULL }
};


enum {
    PIPE_ERR_CLOSED = 1,
    PIPE_ERR_SYSCALL,
    PIPE_ERR_NOMEM,
    PIPE_ERR_TIMEOUT,
    PIPE_ERR_ADD_READ_EV,
    PIPE_ERR_ADD_WRITE_EV,
    PIPE_ERR_ABORTED,
};


enum {
    PIPE_READ_ALL = 0,
    PIPE_READ_BYTES,
    PIPE_READ_LINE,
    PIPE_READ_ANY,
};


#define REASON_EXIT         "exit"
#define REASON_SIGNAL       "signal"
#define REASON_UNKNOWN      "unknown"

#define REASON_RUNNING_CODE  0
#define REASON_EXIT_CODE     1
#define REASON_SIGNAL_CODE   2
#define REASON_UNKNOWN_CODE  3


void
ngx_http_lua_pipe_init(void)
{
    ngx_rbtree_init(&ngx_http_lua_pipe_rbtree,
                    &ngx_http_lua_pipe_proc_sentinel, ngx_rbtree_insert_value);
}


ngx_int_t
ngx_http_lua_pipe_add_signal_handler(ngx_cycle_t *cycle)
{
    ngx_event_t         *rev;
#if (NGX_HTTP_LUA_HAVE_SIGNALFD)
    sigset_t             set;

#else
    int                  rc;
    struct sigaction     sa;
#endif

    ngx_reap_pid_event.handler = ngx_http_lua_pipe_reap_timer_handler;
    ngx_reap_pid_event.log = cycle->log;
    ngx_reap_pid_event.data = cycle;
    ngx_reap_pid_event.cancelable = 1;

    if (!ngx_reap_pid_event.timer_set) {
        ngx_add_timer(&ngx_reap_pid_event, 1000);
    }

#if (NGX_HTTP_LUA_HAVE_SIGNALFD)
    if (sigemptyset(&set) != 0) {
        ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno,
                      "lua pipe init signal set failed");
        return NGX_ERROR;
    }

    if (sigaddset(&set, SIGCHLD) != 0) {
        ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno,
                      "lua pipe add SIGCHLD to signal set failed");
        return NGX_ERROR;
    }

    if (sigprocmask(SIG_BLOCK, &set, NULL) != 0) {
        ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno,
                      "lua pipe block SIGCHLD failed");
        return NGX_ERROR;
    }

    ngx_http_lua_signalfd = signalfd(-1, &set, SFD_NONBLOCK|SFD_CLOEXEC);
    if (ngx_http_lua_signalfd < 0) {
        ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno,
                      "lua pipe create signalfd instance failed");
        return NGX_ERROR;
    }

#else /* !(NGX_HTTP_LUA_HAVE_SIGNALFD) */
#   if (NGX_HTTP_LUA_HAVE_PIPE2)
    rc = pipe2(ngx_http_lua_sigchldfd, O_NONBLOCK|O_CLOEXEC);
#   else
    rc = pipe(ngx_http_lua_sigchldfd);
#   endif

    if (rc == -1) {
        ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno,
                      "lua pipe init SIGCHLD fd failed");
        return NGX_ERROR;
    }

#   if !(NGX_HTTP_LUA_HAVE_PIPE2)
    if (ngx_nonblocking(ngx_http_lua_read_sigfd) == -1) {
        ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno, "lua pipe "
                      ngx_nonblocking_n " SIGCHLD read fd failed");
        goto failed;
    }

    if (ngx_nonblocking(ngx_http_lua_write_sigfd) == -1) {
        ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno, "lua pipe "
                      ngx_nonblocking_n " SIGCHLD write fd failed");
        goto failed;
    }

    /* it's ok not to set the pipe fd with O_CLOEXEC. This requires
     * extra syscall */
#   endif /* !(NGX_HTTP_LUA_HAVE_PIPE2) */
#endif /* NGX_HTTP_LUA_HAVE_SIGNALFD */

    ngx_http_lua_sigfd_conn = ngx_get_connection(ngx_http_lua_read_sigfd,
                                                 cycle->log);
    if (ngx_http_lua_sigfd_conn == NULL) {
        goto failed;
    }

    ngx_http_lua_sigfd_conn->log = cycle->log;
    ngx_http_lua_sigfd_conn->recv = ngx_http_lua_pipe_fd_read;
    rev = ngx_http_lua_sigfd_conn->read;
    rev->log = ngx_http_lua_sigfd_conn->log;
    rev->handler = ngx_http_lua_pipe_sigchld_event_handler;

#ifdef HAVE_SOCKET_CLOEXEC_PATCH
    rev->skip_socket_leak_check = 1;
#endif

    if (ngx_handle_read_event(rev, 0) == NGX_ERROR) {
        goto failed;
    }

#if !(NGX_HTTP_LUA_HAVE_SIGNALFD)
    ngx_memzero(&sa, sizeof(struct sigaction));
    sa.sa_sigaction = ngx_http_lua_pipe_sigchld_handler;
    sa.sa_flags = SA_SIGINFO;

    if (sigemptyset(&sa.sa_mask) != 0) {
        ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno,
                      "lua pipe init signal mask failed");
        goto failed;
    }

    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno,
                      "lua pipe sigaction(SIGCHLD) failed");
        goto failed;
    }
#endif

    return NGX_OK;

failed:

    if (ngx_http_lua_sigfd_conn != NULL) {
        ngx_close_connection(ngx_http_lua_sigfd_conn);
        ngx_http_lua_sigfd_conn = NULL;
    }

    if (close(ngx_http_lua_read_sigfd) == -1) {
        ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
                      "lua pipe close the read sigfd failed");
    }

#if !(NGX_HTTP_LUA_HAVE_SIGNALFD)
    if (close(ngx_http_lua_write_sigfd) == -1) {
        ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
                      "lua pipe close the write sigfd failed");
    }
#endif

    return NGX_ERROR;
}


static ngx_rbtree_node_t *
ngx_http_lua_pipe_lookup_pid(ngx_rbtree_key_t key)
{
    ngx_rbtree_node_t    *node, *sentinel;

    node = ngx_http_lua_pipe_rbtree.root;
    sentinel = ngx_http_lua_pipe_rbtree.sentinel;

    while (node != sentinel) {
        if (key < node->key) {
            node = node->left;
            continue;
        }

        if (key > node->key) {
            node = node->right;
            continue;
        }

        return node;
    }

    return NULL;
}


#if !(NGX_HTTP_LUA_HAVE_SIGNALFD)
static void
ngx_http_lua_pipe_sigchld_handler(int signo, siginfo_t *siginfo,
    void *ucontext)
{
    ngx_err_t                        err, saved_err;
    ngx_int_t                        n;

    saved_err = ngx_errno;

    for ( ;; ) {
        n = write(ngx_http_lua_write_sigfd, ngx_http_lua_pipe_notification,
                  sizeof(ngx_http_lua_pipe_notification));

        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0,
                       "lua pipe SIGCHLD fd write siginfo:%p", siginfo);

        if (n >= 0) {
            break;
        }

        err = ngx_errno;

        if (err != NGX_EINTR) {
            if (err != NGX_EAGAIN) {
                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, err,
                              "lua pipe SIGCHLD fd write failed");
            }

            break;
        }

        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, err,
                       "lua pipe SIGCHLD fd write was interrupted");
    }

    ngx_set_errno(saved_err);
}
#endif


static void
ngx_http_lua_pipe_sigchld_event_handler(ngx_event_t *ev)
{
    int                              n;
    ngx_connection_t                *c = ev->data;

    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0,
                   "lua pipe reaping children");

    for ( ;; ) {
#if (NGX_HTTP_LUA_HAVE_SIGNALFD)
        n = c->recv(c, (u_char *) &ngx_http_lua_pipe_notification,
#else
        n = c->recv(c, ngx_http_lua_pipe_notification,
#endif
                    sizeof(ngx_http_lua_pipe_notification));

        if (n <= 0) {
            if (n == NGX_ERROR) {
                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
                              "lua pipe SIGCHLD fd read failed");
            }

            break;
        }

        ngx_http_lua_pipe_reap_pids(ev);
    }
}


static void
ngx_http_lua_pipe_reap_pids(ngx_event_t *ev)
{
    int                              status;
    ngx_pid_t                        pid;
    ngx_rbtree_node_t               *node;
    ngx_http_lua_pipe_node_t        *pipe_node;

    for ( ;; ) {
        pid = waitpid(-1, &status, WNOHANG);

        if (pid == 0) {
            break;
        }

        if (pid < 0) {
            if (ngx_errno != NGX_ECHILD) {
                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
                              "lua pipe waitpid failed");
            }

            break;
        }

        /* This log is ported from Nginx's signal handler since we override
         * or block it in this implementation. */
        ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
                      "signal %d (SIGCHLD) received from %P",
                      SIGCHLD, pid);

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                       "lua pipe SIGCHLD fd read pid:%P status:%d", pid,
                       status);

        node = ngx_http_lua_pipe_lookup_pid(pid);
        if (node != NULL) {
            pipe_node = (ngx_http_lua_pipe_node_t *) &node->color;
            if (pipe_node->wait_co_ctx != NULL) {
                ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                               "lua pipe resume process:%p waiting for %P",
                               pipe_node->proc, pid);

                /*
                 * We need the extra parentheses around the first argument
                 * of ngx_post_event() just to work around macro issues in
                 * nginx cores older than 1.7.12 (exclusive).
                 */
                ngx_post_event((&pipe_node->wait_co_ctx->sleep),
                               &ngx_posted_events);
            }

            /* TODO: we should proactively close and free up the pipe after
             * the user consume all the data in the pipe.
             */
            pipe_node->proc->pipe->dead = 1;

            if (WIFSIGNALED(status)) {
                pipe_node->status = WTERMSIG(status);
                pipe_node->reason_code = REASON_SIGNAL_CODE;

            } else if (WIFEXITED(status)) {
                pipe_node->status = WEXITSTATUS(status);
                pipe_node->reason_code = REASON_EXIT_CODE;

            } else {
                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
                              "lua pipe unknown exit status %d from "
                              "process %P", status, pid);
                pipe_node->status = status;
                pipe_node->reason_code = REASON_UNKNOWN_CODE;
            }
        }
    }
}


static void
ngx_http_lua_pipe_reap_timer_handler(ngx_event_t *ev)
{
    ngx_http_lua_pipe_reap_pids(ev);

    if (!ngx_exiting) {
        ngx_add_timer(&ngx_reap_pid_event, 1000);
        ngx_reap_pid_event.timedout = 0;
    }
}


static ssize_t
ngx_http_lua_pipe_fd_read(ngx_connection_t *c, u_char *buf, size_t size)
{
    ssize_t       n;
    ngx_err_t     err;
    ngx_event_t  *rev;

    rev = c->read;

    do {
        n = read(c->fd, buf, size);

        err = ngx_errno;

        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
                       "read: fd:%d %z of %uz", c->fd, n, size);

        if (n == 0) {
            rev->ready = 0;
            rev->eof = 1;
            return 0;
        }

        if (n > 0) {
            if ((size_t) n < size
                && !(ngx_event_flags & NGX_USE_GREEDY_EVENT))
            {
                rev->ready = 0;
            }

            return n;
        }

        if (err == NGX_EAGAIN || err == NGX_EINTR) {
            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
                           "read() not ready");
            n = NGX_AGAIN;

        } else {
            n = ngx_connection_error(c, err, "read() failed");
            break;
        }

    } while (err == NGX_EINTR);

    rev->ready = 0;

    if (n == NGX_ERROR) {
        rev->error = 1;
    }

    return n;
}


static ssize_t
ngx_http_lua_pipe_fd_write(ngx_connection_t *c, u_char *buf, size_t size)
{
    ssize_t       n;
    ngx_err_t     err;
    ngx_event_t  *wev;

    wev = c->write;

    do {
        n = write(c->fd, buf, size);

        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
                       "write: fd:%d %z of %uz", c->fd, n, size);

        if (n >= 0) {
            if ((size_t) n != size) {
                wev->ready = 0;
            }

            return n;
        }

        err = ngx_errno;

        if (err == NGX_EAGAIN || err == NGX_EINTR) {
            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
                           "write() not ready");
            n = NGX_AGAIN;

        } else if (err != NGX_EPIPE) {
            n = ngx_connection_error(c, err, "write() failed");
            break;
        }

    } while (err == NGX_EINTR);

    wev->ready = 0;

    if (n == NGX_ERROR) {
        wev->error = 1;
    }

    return n;
}


#if !(NGX_HTTP_LUA_HAVE_EXECVPE)
static int
ngx_http_lua_execvpe(const char *program, char * const argv[],
    char * const envp[])
{
    int    rc;
    char **saved = environ;

    environ = (char **) envp;
    rc = execvp(program, argv);
    environ = saved;
    return rc;
}
#endif


int
ngx_http_lua_ffi_pipe_spawn(ngx_http_request_t *r,
    ngx_http_lua_ffi_pipe_proc_t *proc,
    const char *file, const char **argv, int merge_stderr, size_t buffer_size,
    const char **environ, u_char *errbuf, size_t *errbuf_size)
{
    int                             rc;
    int                             in[2];
    int                             out[2];
    int                             err[2];
    int                             stdin_fd, stdout_fd, stderr_fd;
    int                             errlog_fd, temp_errlog_fd;
    ngx_pid_t                       pid;
    ssize_t                         pool_size;
    ngx_pool_t                     *pool;
    ngx_uint_t                      i;
    ngx_listening_t                *ls;
    ngx_http_lua_pipe_t            *pp;
    ngx_rbtree_node_t              *node;
    ngx_http_lua_pipe_node_t       *pipe_node;
    struct sigaction                sa;
    ngx_http_lua_pipe_signal_t     *sig;
    ngx_pool_cleanup_t             *cln;
    sigset_t                        set;

    pool_size = ngx_align(NGX_MIN_POOL_SIZE + buffer_size * 2,
                          NGX_POOL_ALIGNMENT);

    pool = ngx_create_pool(pool_size, ngx_cycle->log);
    if (pool == NULL) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "no memory")
                       - errbuf;
        return NGX_ERROR;
    }

    pp = ngx_pcalloc(pool, sizeof(ngx_http_lua_pipe_t)
                     + offsetof(ngx_rbtree_node_t, color)
                     + sizeof(ngx_http_lua_pipe_node_t));
    if (pp == NULL) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "no memory")
                       - errbuf;
        goto free_pool;
    }

    rc = pipe(in);
    if (rc == -1) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "pipe failed: %s",
                                    strerror(errno))
                       - errbuf;
        goto free_pool;
    }

    rc = pipe(out);
    if (rc == -1) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "pipe failed: %s",
                                    strerror(errno))
                       - errbuf;
        goto close_in_fd;
    }

    if (!merge_stderr) {
        rc = pipe(err);
        if (rc == -1) {
            *errbuf_size = ngx_snprintf(errbuf, *errbuf_size,
                                        "pipe failed: %s", strerror(errno))
                           - errbuf;
            goto close_in_out_fd;
        }
    }

    pid = fork();
    if (pid == -1) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "fork failed: %s",
                                    strerror(errno))
                       - errbuf;
        goto close_in_out_err_fd;
    }

    if (pid == 0) {

#if (NGX_HAVE_CPU_AFFINITY)
        /* reset the CPU affinity mask */
        ngx_uint_t     log_level;
        ngx_cpuset_t   child_cpu_affinity;

        if (ngx_process == NGX_PROCESS_WORKER
            && ngx_get_cpu_affinity(ngx_worker) != NULL)
        {
            CPU_ZERO(&child_cpu_affinity);

            for (i = 0; i < (ngx_uint_t) ngx_min(ngx_ncpu, CPU_SETSIZE); i++) {
                CPU_SET(i, &child_cpu_affinity);
            }

            log_level = ngx_cycle->log->log_level;
            ngx_cycle->log->log_level = NGX_LOG_WARN;
            ngx_setaffinity(&child_cpu_affinity, ngx_cycle->log);
            ngx_cycle->log->log_level = log_level;
        }
#endif

        /* reset the handler of ignored signals to the default */
        for (sig = ngx_signals; sig->signo != 0; sig++) {
            ngx_memzero(&sa, sizeof(struct sigaction));
            sa.sa_handler = SIG_DFL;

            if (sigemptyset(&sa.sa_mask) != 0) {
                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
                              "lua pipe child init signal mask failed");
                exit(EXIT_FAILURE);
            }

            if (sigaction(sig->signo, &sa, NULL) == -1) {
                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
                              "lua pipe child reset signal handler for %s "
                              "failed", sig->signame);
                exit(EXIT_FAILURE);
            }
        }

        /* reset signal mask */
        if (sigemptyset(&set) != 0) {
            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
                          "lua pipe child init signal set failed");
            exit(EXIT_FAILURE);
        }

        if (sigprocmask(SIG_SETMASK, &set, NULL) != 0) {
            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
                          "lua pipe child reset signal mask failed");
            exit(EXIT_FAILURE);
        }

        /* close listening socket fd */
        ls = ngx_cycle->listening.elts;
        for (i = 0; i < ngx_cycle->listening.nelts; i++) {
            if (ls[i].fd != (ngx_socket_t) -1 &&
                ngx_close_socket(ls[i].fd) == -1)
            {
                ngx_log_error(NGX_LOG_WARN, ngx_cycle->log, ngx_socket_errno,
                              "lua pipe child " ngx_close_socket_n
                              " %V failed", &ls[i].addr_text);
            }
        }

        /* close and dup pipefd */
        if (close(in[1]) == -1) {
            ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
                          "lua pipe child failed to close the in[1] "
                          "pipe fd");
        }

        if (close(out[0]) == -1) {
            ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
                          "lua pipe child failed to close the out[0] "
                          "pipe fd");
        }

        if (ngx_cycle->log->file && ngx_cycle->log->file->fd == STDERR_FILENO) {
            errlog_fd = ngx_cycle->log->file->fd;
            temp_errlog_fd = dup(errlog_fd);

            if (temp_errlog_fd == -1) {
                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
                              "lua pipe child dup errlog fd failed");
                exit(EXIT_FAILURE);
            }

            if (ngx_cloexec(temp_errlog_fd) == -1) {
                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
                              "lua pipe child new errlog fd " ngx_cloexec_n
                              " failed");
            }

            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                           "lua pipe child dup old errlog fd %d to new fd %d",
                           ngx_cycle->log->file->fd, temp_errlog_fd);

            ngx_cycle->log->file->fd = temp_errlog_fd;
        }

        if (dup2(in[0], STDIN_FILENO) == -1) {
            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
                          "lua pipe child dup2 stdin failed");
            exit(EXIT_FAILURE);
        }

        if (dup2(out[1], STDOUT_FILENO) == -1) {
            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
                          "lua pipe child dup2 stdout failed");
            exit(EXIT_FAILURE);
        }

        if (merge_stderr) {
            if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1) {
                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
                              "lua pipe child dup2 stderr failed");
                exit(EXIT_FAILURE);
            }

        } else {
            if (close(err[0]) == -1) {
                ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
                              "lua pipe child failed to close the err[0] "
                              "pipe fd");
            }

            if (dup2(err[1], STDERR_FILENO) == -1) {
                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
                              "lua pipe child dup2 stderr failed");
                exit(EXIT_FAILURE);
            }
        }

        if (close(in[0]) == -1) {
            ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
                          "lua pipe failed to close the in[0]");
        }

        if (close(out[1]) == -1) {
            ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
                          "lua pipe failed to close the out[1]");
        }

        if (!merge_stderr) {
            if (close(err[1]) == -1) {
                ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
                              "lua pipe failed to close the err[1]");
            }
        }

        if (environ != NULL) {
#if (NGX_HTTP_LUA_HAVE_EXECVPE)
            if (execvpe(file, (char * const *) argv, (char * const *) environ)
#else
            if (ngx_http_lua_execvpe(file, (char * const *) argv,
                                     (char * const *) environ)
#endif
                == -1)
            {
                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
                              "lua pipe child execvpe() failed while "
                              "executing %s", file);
            }

        } else {
            if (execvp(file, (char * const *) argv) == -1) {
                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
                              "lua pipe child execvp() failed while "
                              "executing %s", file);
            }
        }

        exit(EXIT_FAILURE);
    }

    /* parent process */
    if (close(in[0]) == -1) {
        ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
                      "lua pipe: failed to close the in[0] pipe fd");
    }

    stdin_fd = in[1];

    if (ngx_nonblocking(stdin_fd) == -1) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size,
                                    ngx_nonblocking_n " failed: %s",
                                    strerror(errno))
                       - errbuf;
        goto close_in_out_err_fd;
    }

    pp->stdin_fd = stdin_fd;

    if (close(out[1]) == -1) {
        ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
                      "lua pipe: failed to close the out[1] pipe fd");
    }

    stdout_fd = out[0];

    if (ngx_nonblocking(stdout_fd) == -1) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size,
                                    ngx_nonblocking_n " failed: %s",
                                    strerror(errno))
                       - errbuf;
        goto close_in_out_err_fd;
    }

    pp->stdout_fd = stdout_fd;

    if (!merge_stderr) {
        if (close(err[1]) == -1) {
            ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
                          "lua pipe: failed to close the err[1] pipe fd");
        }

        stderr_fd = err[0];

        if (ngx_nonblocking(stderr_fd) == -1) {
            *errbuf_size = ngx_snprintf(errbuf, *errbuf_size,
                                        ngx_nonblocking_n " failed: %s",
                                        strerror(errno))
                           - errbuf;
            goto close_in_out_err_fd;
        }

        pp->stderr_fd = stderr_fd;
    }

    if (pp->cleanup == NULL) {
        cln = ngx_pool_cleanup_add(r->pool, 0);

        if (cln == NULL) {
            *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "no memory")
                           - errbuf;
            goto close_in_out_err_fd;
        }

        cln->handler = (ngx_pool_cleanup_pt) ngx_http_lua_ffi_pipe_proc_destroy;
        cln->data = proc;
        pp->cleanup = &cln->handler;
        pp->r = r;
    }

    node = (ngx_rbtree_node_t *) (pp + 1);
    node->key = pid;
    pipe_node = (ngx_http_lua_pipe_node_t *) &node->color;
    pipe_node->proc = proc;
    ngx_rbtree_insert(&ngx_http_lua_pipe_rbtree, node);

    pp->node = node;
    pp->pool = pool;
    pp->merge_stderr = merge_stderr;
    pp->buffer_size = buffer_size;

    proc->_pid = pid;
    proc->pipe = pp;

    ngx_log_debug4(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                   "lua pipe spawn process:%p pid:%P merge_stderr:%d "
                   "buffer_size:%uz", proc, pid, merge_stderr, buffer_size);
    return NGX_OK;

close_in_out_err_fd:

    if (!merge_stderr) {
        if (close(err[0]) == -1) {
            ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
                          "failed to close the err[0] pipe fd");
        }

        if (close(err[1]) == -1) {
            ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
                          "failed to close the err[1] pipe fd");
        }
    }

close_in_out_fd:

    if (close(out[0]) == -1) {
        ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
                      "failed to close the out[0] pipe fd");
    }

    if (close(out[1]) == -1) {
        ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
                      "failed to close the out[1] pipe fd");
    }

close_in_fd:

    if (close(in[0]) == -1) {
        ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
                      "failed to close the in[0] pipe fd");
    }

    if (close(in[1]) == -1) {
        ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
                      "failed to close the in[1] pipe fd");
    }

free_pool:

    ngx_destroy_pool(pool);
    return NGX_ERROR;
}


static void
ngx_http_lua_pipe_close_helper(ngx_http_lua_pipe_t *pipe,
    ngx_http_lua_pipe_ctx_t *pipe_ctx, ngx_event_t *ev)
{
    if (ev->handler != ngx_http_lua_pipe_dummy_event_handler) {
        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                       "lua pipe abort blocking operation pipe_ctx:%p ev:%p",
                       pipe_ctx, ev);

        if (pipe->dead) {
            pipe_ctx->err_type = PIPE_ERR_CLOSED;

        } else {
            pipe_ctx->err_type = PIPE_ERR_ABORTED;
        }

        ngx_post_event(ev, &ngx_posted_events);
        return;
    }

    ngx_close_connection(pipe_ctx->c);
    pipe_ctx->c = NULL;
}


static void
ngx_http_lua_pipe_close_stdin(ngx_http_lua_pipe_t *pipe)
{
    ngx_event_t                     *wev;

    if (pipe->stdin_ctx == NULL) {
        if (pipe->stdin_fd != -1) {
            if (close(pipe->stdin_fd) == -1) {
                ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
                              "failed to close the stdin pipe fd");
            }

            pipe->stdin_fd = -1;
        }

    } else if (pipe->stdin_ctx->c != NULL) {
        wev = pipe->stdin_ctx->c->write;
        ngx_http_lua_pipe_close_helper(pipe, pipe->stdin_ctx, wev);
    }
}


static void
ngx_http_lua_pipe_close_stdout(ngx_http_lua_pipe_t *pipe)
{
    ngx_event_t                     *rev;

    if (pipe->stdout_ctx == NULL) {
        if (pipe->stdout_fd != -1) {
            if (close(pipe->stdout_fd) == -1) {
                ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
                              "failed to close the stdout pipe fd");
            }

            pipe->stdout_fd = -1;
        }

    } else if (pipe->stdout_ctx->c != NULL) {
        rev = pipe->stdout_ctx->c->read;
        ngx_http_lua_pipe_close_helper(pipe, pipe->stdout_ctx, rev);
    }
}


static void
ngx_http_lua_pipe_close_stderr(ngx_http_lua_pipe_t *pipe)
{
    ngx_event_t                     *rev;

    if (pipe->stderr_ctx == NULL) {
        if (pipe->stderr_fd != -1) {
            if (close(pipe->stderr_fd) == -1) {
                ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
                              "failed to close the stderr pipe fd");
            }

            pipe->stderr_fd = -1;
        }

    } else if (pipe->stderr_ctx->c != NULL) {
        rev = pipe->stderr_ctx->c->read;
        ngx_http_lua_pipe_close_helper(pipe, pipe->stderr_ctx, rev);
    }
}


int
ngx_http_lua_ffi_pipe_proc_shutdown_stdin(ngx_http_lua_ffi_pipe_proc_t *proc,
    u_char *errbuf, size_t *errbuf_size)
{
    ngx_http_lua_pipe_t             *pipe;

    pipe = proc->pipe;
    if (pipe == NULL || pipe->closed) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "closed") - errbuf;
        return NGX_ERROR;
    }

    ngx_http_lua_pipe_close_stdin(pipe);

    return NGX_OK;
}


int
ngx_http_lua_ffi_pipe_proc_shutdown_stdout(ngx_http_lua_ffi_pipe_proc_t *proc,
    u_char *errbuf, size_t *errbuf_size)
{
    ngx_http_lua_pipe_t             *pipe;

    pipe = proc->pipe;
    if (pipe == NULL || pipe->closed) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "closed") - errbuf;
        return NGX_ERROR;
    }

    ngx_http_lua_pipe_close_stdout(pipe);

    return NGX_OK;
}


int
ngx_http_lua_ffi_pipe_proc_shutdown_stderr(ngx_http_lua_ffi_pipe_proc_t *proc,
    u_char *errbuf, size_t *errbuf_size)
{
    ngx_http_lua_pipe_t             *pipe;

    pipe = proc->pipe;
    if (pipe == NULL || pipe->closed) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "closed") - errbuf;
        return NGX_ERROR;
    }

    if (pipe->merge_stderr) {
        /* stdout is used internally as stderr when merge_stderr is true */
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "merged to stdout")
                       - errbuf;
        return NGX_ERROR;
    }

    ngx_http_lua_pipe_close_stderr(pipe);

    return NGX_OK;
}


static void
ngx_http_lua_pipe_proc_finalize(ngx_http_lua_ffi_pipe_proc_t *proc)
{
    ngx_http_lua_pipe_t          *pipe;

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                   "lua pipe finalize process:%p pid:%P",
                   proc, proc->_pid);
    pipe = proc->pipe;

    if (pipe->node) {
        ngx_rbtree_delete(&ngx_http_lua_pipe_rbtree, pipe->node);
        pipe->node = NULL;
    }

    pipe->dead = 1;

    ngx_http_lua_pipe_close_stdin(pipe);
    ngx_http_lua_pipe_close_stdout(pipe);

    if (!pipe->merge_stderr) {
        ngx_http_lua_pipe_close_stderr(pipe);
    }

    pipe->closed = 1;
}


void
ngx_http_lua_ffi_pipe_proc_destroy(ngx_http_lua_ffi_pipe_proc_t *proc)
{
    ngx_http_lua_pipe_t          *pipe;

    pipe = proc->pipe;
    if (pipe == NULL) {
        return;
    }

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                   "lua pipe destroy process:%p pid:%P", proc, proc->_pid);

    if (!pipe->dead) {
        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                       "lua pipe kill process:%p pid:%P", proc, proc->_pid);

        if (kill(proc->_pid, SIGKILL) == -1) {
            if (ngx_errno != ESRCH) {
                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
                              "lua pipe failed to kill process:%p pid:%P",
                              proc, proc->_pid);
            }
        }
    }

    if (pipe->cleanup != NULL) {
        *pipe->cleanup = NULL;
        ngx_http_lua_cleanup_free(pipe->r, pipe->cleanup);
        pipe->cleanup = NULL;
    }

    ngx_http_lua_pipe_proc_finalize(proc);

    /*
     * pipe_proc_finalize -> ngx_http_lua_pipe_close_helper may leave pipe
     * connections open with active timers/posted events when there are
     * pending I/O operations (handler != dummy_handler).  Close them now
     * before destroying the pool.
     *
     * Without this, when pool cleanup LIFO ordering causes pipe_proc_destroy
     * to run before request_cleanup_handler (e.g. QUIC connection close path:
     * ngx_quic_close_streams -> ngx_http_free_request -> ngx_destroy_pool),
     * request_cleanup sees proc->pipe == NULL and returns early, leaving the
     * timer live.  The timer then fires after the request pool is freed,
     * accessing a dangling wait_co_ctx pointer -> SIGSEGV.
     *
     * ngx_close_connection handles everything: timers (ngx_del_timer),
     * posted events (ngx_delete_posted_event), epoll removal, fd close,
     * and connection recycling.
     */

    if (pipe->stdout_ctx && pipe->stdout_ctx->c) {
        ngx_close_connection(pipe->stdout_ctx->c);
        pipe->stdout_ctx->c = NULL;
    }

    if (pipe->stderr_ctx && pipe->stderr_ctx->c) {
        ngx_close_connection(pipe->stderr_ctx->c);
        pipe->stderr_ctx->c = NULL;
    }

    if (pipe->stdin_ctx && pipe->stdin_ctx->c) {
        ngx_close_connection(pipe->stdin_ctx->c);
        pipe->stdin_ctx->c = NULL;
    }

    ngx_destroy_pool(pipe->pool);
    proc->pipe = NULL;
}


static ngx_int_t
ngx_http_lua_pipe_get_lua_ctx(ngx_http_request_t *r,
    ngx_http_lua_ctx_t **ctx, u_char *errbuf, size_t *errbuf_size)
{
    int                                 rc;

    *ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);
    if (*ctx == NULL) {
        return NGX_HTTP_LUA_FFI_NO_REQ_CTX;
    }

    rc = ngx_http_lua_ffi_check_context(*ctx, NGX_HTTP_LUA_CONTEXT_YIELDABLE,
                                        errbuf, errbuf_size);
    if (rc != NGX_OK) {
        return NGX_HTTP_LUA_FFI_BAD_CONTEXT;
    }

    return NGX_OK;
}


static void
ngx_http_lua_pipe_put_error(ngx_http_lua_pipe_ctx_t *pipe_ctx, u_char *errbuf,
    size_t *errbuf_size)
{
    switch (pipe_ctx->err_type) {

    case PIPE_ERR_CLOSED:
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "closed") - errbuf;
        break;

    case PIPE_ERR_SYSCALL:
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "%s",
                                    strerror(pipe_ctx->pipe_errno))
                       - errbuf;
        break;

    case PIPE_ERR_NOMEM:
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "no memory")
                       - errbuf;
        break;

    case PIPE_ERR_TIMEOUT:
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "timeout")
                       - errbuf;
        break;

    case PIPE_ERR_ADD_READ_EV:
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size,
                                    "failed to add read event")
                       - errbuf;
        break;

    case PIPE_ERR_ADD_WRITE_EV:
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size,
                                    "failed to add write event")
                       - errbuf;
        break;

    case PIPE_ERR_ABORTED:
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "aborted") - errbuf;
        break;

    default:
        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
                      "unexpected err type: %d", pipe_ctx->err_type);
        ngx_http_lua_assert(NULL);
    }
}


static void
ngx_http_lua_pipe_put_data(ngx_http_lua_pipe_t *pipe,
    ngx_http_lua_pipe_ctx_t *pipe_ctx, u_char **buf, size_t *buf_size)
{
    size_t                   size = 0;
    size_t                   chunk_size;
    size_t                   nbufs;
    u_char                  *p;
    ngx_buf_t               *b;
    ngx_chain_t             *cl;
    ngx_chain_t            **ll;

    nbufs = 0;
    ll = NULL;

    for (cl = pipe_ctx->bufs_in; cl; cl = cl->next) {
        b = cl->buf;
        chunk_size = b->last - b->pos;

        if (cl->next) {
            ll = &cl->next;
        }

        size += chunk_size;

        nbufs++;
    }

    if (*buf_size < size) {
        *buf = NULL;
        *buf_size = size;

        return;
    }

    *buf_size = size;

    p = *buf;
    for (cl = pipe_ctx->bufs_in; cl; cl = cl->next) {
        b = cl->buf;
        chunk_size = b->last - b->pos;
        p = ngx_cpymem(p, b->pos, chunk_size);
    }

    if (nbufs > 1 && ll) {
        *ll = pipe->free_bufs;
        pipe->free_bufs = pipe_ctx->bufs_in;
        pipe_ctx->bufs_in = pipe_ctx->buf_in;
    }

    if (pipe_ctx->buffer.pos == pipe_ctx->buffer.last) {
        pipe_ctx->buffer.pos = pipe_ctx->buffer.start;
        pipe_ctx->buffer.last = pipe_ctx->buffer.start;
    }

    if (pipe_ctx->bufs_in) {
        pipe_ctx->buf_in->buf->last = pipe_ctx->buffer.pos;
        pipe_ctx->buf_in->buf->pos = pipe_ctx->buffer.pos;
    }
}


static ngx_int_t
ngx_http_lua_pipe_add_input_buffer(ngx_http_lua_pipe_t *pipe,
    ngx_http_lua_pipe_ctx_t *pipe_ctx)
{
    ngx_chain_t             *cl;

    cl = ngx_http_lua_chain_get_free_buf(ngx_cycle->log, pipe->pool,
                                         &pipe->free_bufs,
                                         pipe->buffer_size);

    if (cl == NULL) {
        pipe_ctx->err_type = PIPE_ERR_NOMEM;
        return NGX_ERROR;
    }

    pipe_ctx->buf_in->next = cl;
    pipe_ctx->buf_in = cl;
    pipe_ctx->buffer = *cl->buf;

    return NGX_OK;
}


static ngx_int_t
ngx_http_lua_pipe_read_all(void *data, ssize_t bytes)
{
    ngx_http_lua_pipe_ctx_t      *pipe_ctx = data;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, "lua pipe read all");
    return ngx_http_lua_read_all(&pipe_ctx->buffer, pipe_ctx->buf_in, bytes,
                                 ngx_cycle->log);
}


static ngx_int_t
ngx_http_lua_pipe_read_bytes(void *data, ssize_t bytes)
{
    ngx_int_t                          rc;
    ngx_http_lua_pipe_ctx_t           *pipe_ctx = data;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                   "lua pipe read bytes %z", bytes);

    rc = ngx_http_lua_read_bytes(&pipe_ctx->buffer, pipe_ctx->buf_in,
                                 &pipe_ctx->rest, bytes, ngx_cycle->log);
    if (rc == NGX_ERROR) {
        pipe_ctx->err_type = PIPE_ERR_CLOSED;
        return NGX_ERROR;
    }

    return rc;
}


static ngx_int_t
ngx_http_lua_pipe_read_line(void *data, ssize_t bytes)
{
    ngx_int_t                          rc;
    ngx_http_lua_pipe_ctx_t           *pipe_ctx = data;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                   "lua pipe read line");
    rc = ngx_http_lua_read_line(&pipe_ctx->buffer, pipe_ctx->buf_in, bytes,
                                ngx_cycle->log);
    if (rc == NGX_ERROR) {
        pipe_ctx->err_type = PIPE_ERR_CLOSED;
        return NGX_ERROR;
    }

    return rc;
}


static ngx_int_t
ngx_http_lua_pipe_read_any(void *data, ssize_t bytes)
{
    ngx_int_t                          rc;
    ngx_http_lua_pipe_ctx_t           *pipe_ctx = data;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, "lua pipe read any");
    rc = ngx_http_lua_read_any(&pipe_ctx->buffer, pipe_ctx->buf_in,
                               &pipe_ctx->rest, bytes, ngx_cycle->log);
    if (rc == NGX_ERROR) {
        pipe_ctx->err_type = PIPE_ERR_CLOSED;
        return NGX_ERROR;
    }

    return rc;
}


static ngx_int_t
ngx_http_lua_pipe_read(ngx_http_lua_pipe_t *pipe,
    ngx_http_lua_pipe_ctx_t *pipe_ctx)
{
    int                                 rc;
    int                                 read;
    size_t                              size;
    ssize_t                             n;
    ngx_buf_t                          *b;
    ngx_event_t                        *rev;
    ngx_connection_t                   *c;

    c = pipe_ctx->c;
    rev = c->read;
    b = &pipe_ctx->buffer;
    read = 0;

    for ( ;; ) {
        size = b->last - b->pos;

        if (size || pipe_ctx->eof) {
            rc = pipe_ctx->input_filter(pipe_ctx->input_filter_ctx, size);
            if (rc == NGX_ERROR) {
                return NGX_ERROR;
            }

            if (rc == NGX_OK) {
                ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                               "lua pipe read done pipe:%p", pipe_ctx);
                return NGX_OK;
            }

            /* rc == NGX_AGAIN */
            continue;
        }

        if (read && !rev->ready) {
            break;
        }

        size = b->end - b->last;

        if (size == 0) {
            rc = ngx_http_lua_pipe_add_input_buffer(pipe, pipe_ctx);
            if (rc == NGX_ERROR) {
                return NGX_ERROR;
            }

            b = &pipe_ctx->buffer;
            size = (size_t) (b->end - b->last);
        }

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                       "lua pipe try to read data %uz pipe:%p",
                       size, pipe_ctx);

        n = c->recv(c, b->last, size);
        read = 1;

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                       "lua pipe read data returned %z pipe:%p", n, pipe_ctx);

        if (n == NGX_AGAIN) {
            break;
        }

        if (n == 0) {
            pipe_ctx->eof = 1;
            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                           "lua pipe closed pipe:%p", pipe_ctx);
            continue;
        }

        if (n == NGX_ERROR) {
            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, ngx_errno,
                           "lua pipe read data error pipe:%p", pipe_ctx);

            pipe_ctx->err_type = PIPE_ERR_SYSCALL;
            pipe_ctx->pipe_errno = ngx_errno;
            return NGX_ERROR;
        }

        b->last += n;
    }

    return NGX_AGAIN;
}


static ngx_int_t
ngx_http_lua_pipe_init_ctx(ngx_http_lua_pipe_ctx_t **pipe_ctx_pt, int fd,
    ngx_pool_t *pool, u_char *errbuf, size_t *errbuf_size)
{
    ngx_connection_t                   *c;

    if (fd == -1) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "closed") - errbuf;
        return NGX_ERROR;
    }

    *pipe_ctx_pt = ngx_pcalloc(pool, sizeof(ngx_http_lua_pipe_ctx_t));
    if (*pipe_ctx_pt == NULL) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "no memory")
                       - errbuf;
        return NGX_ERROR;
    }

    c = ngx_get_connection(fd, ngx_cycle->log);
    if (c == NULL) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "no connection")
                       - errbuf;
        return NGX_ERROR;
    }

    c->log = ngx_cycle->log;
    c->recv = ngx_http_lua_pipe_fd_read;
    c->read->handler = ngx_http_lua_pipe_dummy_event_handler;
    c->read->log = c->log;

#ifdef HAVE_SOCKET_CLOEXEC_PATCH
    c->read->skip_socket_leak_check = 1;
#endif

    c->send = ngx_http_lua_pipe_fd_write;
    c->write->handler = ngx_http_lua_pipe_dummy_event_handler;
    c->write->log = c->log;
    (*pipe_ctx_pt)->c = c;

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                   "lua pipe init pipe ctx:%p fd:*%d", *pipe_ctx_pt, fd);

    return NGX_OK;
}


int
ngx_http_lua_ffi_pipe_proc_read(ngx_http_request_t *r,
    ngx_http_lua_ffi_pipe_proc_t *proc, int from_stderr, int reader_type,
    size_t length, u_char **buf, size_t *buf_size, u_char *errbuf,
    size_t *errbuf_size)
{
    int                                 rc;
    ngx_msec_t                          timeout;
    ngx_event_t                        *rev;
    ngx_connection_t                   *c;
    ngx_http_lua_ctx_t                 *ctx;
    ngx_http_lua_pipe_t                *pipe;
    ngx_http_lua_co_ctx_t              *wait_co_ctx;
    ngx_http_lua_pipe_ctx_t            *pipe_ctx;

    rc = ngx_http_lua_pipe_get_lua_ctx(r, &ctx, errbuf, errbuf_size);
    if (rc != NGX_OK) {
        return rc;
    }

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "lua pipe read process:%p pid:%P", proc, proc->_pid);

    pipe = proc->pipe;
    if (pipe == NULL || pipe->closed) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "closed") - errbuf;
        return NGX_ERROR;
    }

    if (pipe->merge_stderr && from_stderr) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "merged to stdout")
                       - errbuf;
        return NGX_ERROR;
    }

    if (from_stderr) {
        if (pipe->stderr_ctx == NULL) {
            if (ngx_http_lua_pipe_init_ctx(&pipe->stderr_ctx, pipe->stderr_fd,
                                           pipe->pool, errbuf,
                                           errbuf_size)
                != NGX_OK)
            {
                return NGX_ERROR;
            }

        } else {
            pipe->stderr_ctx->err_type = 0;
        }

        pipe_ctx = pipe->stderr_ctx;

    } else {
        if (pipe->stdout_ctx == NULL) {
            if (ngx_http_lua_pipe_init_ctx(&pipe->stdout_ctx, pipe->stdout_fd,
                                           pipe->pool, errbuf,
                                           errbuf_size)
                != NGX_OK)
            {
                return NGX_ERROR;
            }

        } else {
            pipe->stdout_ctx->err_type = 0;
        }

        pipe_ctx = pipe->stdout_ctx;
    }

    c = pipe_ctx->c;
    if (c == NULL) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "closed") - errbuf;
        return NGX_ERROR;
    }

    rev = c->read;
    if (rev->handler != ngx_http_lua_pipe_dummy_event_handler) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "pipe busy reading")
                       - errbuf;
        return NGX_ERROR;
    }

    pipe_ctx->input_filter_ctx = pipe_ctx;

    switch (reader_type) {

    case PIPE_READ_ALL:
        pipe_ctx->input_filter = ngx_http_lua_pipe_read_all;
        break;

    case PIPE_READ_BYTES:
        pipe_ctx->input_filter = ngx_http_lua_pipe_read_bytes;
        break;

    case PIPE_READ_LINE:
        pipe_ctx->input_filter = ngx_http_lua_pipe_read_line;
        break;

    case PIPE_READ_ANY:
        pipe_ctx->input_filter = ngx_http_lua_pipe_read_any;
        break;

    default:
        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
                      "unexpected reader_type: %d", reader_type);
        ngx_http_lua_assert(NULL);
    }

    pipe_ctx->rest = length;

    if (pipe_ctx->bufs_in == NULL) {
        pipe_ctx->bufs_in =
            ngx_http_lua_chain_get_free_buf(ngx_cycle->log, pipe->pool,
                                            &pipe->free_bufs,
                                            pipe->buffer_size);

        if (pipe_ctx->bufs_in == NULL) {
            pipe_ctx->err_type = PIPE_ERR_NOMEM;
            goto error;
        }

        pipe_ctx->buf_in = pipe_ctx->bufs_in;
        pipe_ctx->buffer = *pipe_ctx->buf_in->buf;
    }

    rc = ngx_http_lua_pipe_read(pipe, pipe_ctx);
    if (rc == NGX_ERROR) {
        goto error;
    }

    if (rc == NGX_OK) {
        ngx_http_lua_pipe_put_data(pipe, pipe_ctx, buf, buf_size);
        return NGX_OK;
    }

    /* rc == NGX_AGAIN */
    wait_co_ctx = ctx->cur_co_ctx;

    c->data = wait_co_ctx;
    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
        pipe_ctx->err_type = PIPE_ERR_ADD_READ_EV;
        goto error;
    }

    wait_co_ctx->data = proc;

    if (from_stderr) {
        rev->handler = ngx_http_lua_pipe_resume_read_stderr_handler;
        wait_co_ctx->cleanup = ngx_http_lua_pipe_proc_read_stderr_cleanup;
        timeout = proc->stderr_read_timeout;

    } else {
        rev->handler = ngx_http_lua_pipe_resume_read_stdout_handler;
        wait_co_ctx->cleanup = ngx_http_lua_pipe_proc_read_stdout_cleanup;
        timeout = proc->stdout_read_timeout;
    }

    if (timeout > 0) {
        ngx_add_timer(rev, timeout);
        ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "lua pipe add timer for reading: %d(ms) process:%p "
                       "pid:%P pipe:%p ev:%p", timeout, proc, proc->_pid, pipe,
                       rev);
    }

    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "lua pipe read yielding process:%p pid:%P pipe:%p", proc,
                   proc->_pid, pipe);

    return NGX_AGAIN;

error:

    if (pipe_ctx->bufs_in) {
        ngx_http_lua_pipe_put_data(pipe, pipe_ctx, buf, buf_size);
        ngx_http_lua_pipe_put_error(pipe_ctx, errbuf, errbuf_size);
        return NGX_DECLINED;
    }

    ngx_http_lua_pipe_put_error(pipe_ctx, errbuf, errbuf_size);

    return NGX_ERROR;
}


/*
 * ngx_http_lua_ffi_pipe_get_read_result should only be called just after
 * ngx_http_lua_ffi_pipe_proc_read, so we omit most of the sanity check already
 * done in ngx_http_lua_ffi_pipe_proc_read.
 */
int
ngx_http_lua_ffi_pipe_get_read_result(ngx_http_request_t *r,
    ngx_http_lua_ffi_pipe_proc_t *proc, int from_stderr, u_char **buf,
    size_t *buf_size, u_char *errbuf, size_t *errbuf_size)
{
    ngx_http_lua_pipe_t                *pipe;
    ngx_http_lua_pipe_ctx_t            *pipe_ctx;

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "lua pipe get read result process:%p pid:%P", proc,
                   proc->_pid);

    pipe = proc->pipe;
    pipe_ctx = from_stderr ? pipe->stderr_ctx : pipe->stdout_ctx;

    if (!pipe_ctx->err_type) {
        ngx_http_lua_pipe_put_data(pipe, pipe_ctx, buf, buf_size);
        return NGX_OK;
    }

    if (pipe_ctx->bufs_in) {
        ngx_http_lua_pipe_put_data(pipe, pipe_ctx, buf, buf_size);
        ngx_http_lua_pipe_put_error(pipe_ctx, errbuf, errbuf_size);
        return NGX_DECLINED;
    }

    ngx_http_lua_pipe_put_error(pipe_ctx, errbuf, errbuf_size);

    return NGX_ERROR;
}


static ngx_int_t
ngx_http_lua_pipe_write(ngx_http_lua_pipe_t *pipe,
    ngx_http_lua_pipe_ctx_t *pipe_ctx)
{
    size_t                       size;
    ngx_int_t                    n;
    ngx_buf_t                   *b;
    ngx_connection_t            *c;

    c = pipe_ctx->c;
    b = pipe_ctx->buf_in->buf;

    for ( ;; ) {
        size = b->last - b->pos;
        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                       "lua pipe try to write data %uz pipe:%p", size,
                       pipe_ctx);

        n = c->send(c, b->pos, size);
        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                       "lua pipe write returned %i pipe:%p", n, pipe_ctx);

        if (n >= 0) {
            b->pos += n;

            if (b->pos == b->last) {
                b->pos = b->start;
                b->last = b->start;

                if (!pipe->free_bufs) {
                    pipe->free_bufs = pipe_ctx->buf_in;

                } else {
                    pipe->free_bufs->next = pipe_ctx->buf_in;
                }

                ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                               "lua pipe write done pipe:%p", pipe_ctx);
                return NGX_OK;
            }

            continue;
        }

        /* NGX_ERROR || NGX_AGAIN */
        break;
    }

    if (n == NGX_ERROR) {
        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, ngx_errno,
                       "lua pipe write data error pipe:%p", pipe_ctx);

        if (ngx_errno == NGX_EPIPE) {
            pipe_ctx->err_type = PIPE_ERR_CLOSED;

        } else {
            pipe_ctx->err_type = PIPE_ERR_SYSCALL;
            pipe_ctx->pipe_errno = ngx_errno;
        }

        return NGX_ERROR;
    }

    return NGX_AGAIN;
}


ssize_t
ngx_http_lua_ffi_pipe_proc_write(ngx_http_request_t *r,
    ngx_http_lua_ffi_pipe_proc_t *proc, const u_char *data, size_t len,
    u_char *errbuf, size_t *errbuf_size)
{
    int                                 rc;
    ngx_buf_t                          *b;
    ngx_msec_t                          timeout;
    ngx_chain_t                        *cl;
    ngx_event_t                        *wev;
    ngx_http_lua_ctx_t                 *ctx;
    ngx_http_lua_pipe_t                *pipe;
    ngx_http_lua_co_ctx_t              *wait_co_ctx;
    ngx_http_lua_pipe_ctx_t            *pipe_ctx;

    rc = ngx_http_lua_pipe_get_lua_ctx(r, &ctx, errbuf, errbuf_size);
    if (rc != NGX_OK) {
        return rc;
    }

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "lua pipe write process:%p pid:%P", proc, proc->_pid);

    pipe = proc->pipe;
    if (pipe == NULL || pipe->closed) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "closed") - errbuf;
        return NGX_ERROR;
    }

    if (pipe->stdin_ctx == NULL) {
        if (ngx_http_lua_pipe_init_ctx(&pipe->stdin_ctx, pipe->stdin_fd,
                                       pipe->pool, errbuf,
                                       errbuf_size)
            != NGX_OK)
        {
            return NGX_ERROR;
        }

    } else {
        pipe->stdin_ctx->err_type = 0;
    }

    pipe_ctx = pipe->stdin_ctx;
    if (pipe_ctx->c == NULL) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "closed") - errbuf;
        return NGX_ERROR;
    }

    wev = pipe_ctx->c->write;
    if (wev->handler != ngx_http_lua_pipe_dummy_event_handler) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "pipe busy writing")
                       - errbuf;
        return NGX_ERROR;
    }

    pipe_ctx->rest = len;

    cl = ngx_http_lua_chain_get_free_buf(ngx_cycle->log, pipe->pool,
                                         &pipe->free_bufs, len);
    if (cl == NULL) {
        pipe_ctx->err_type = PIPE_ERR_NOMEM;
        goto error;
    }

    pipe_ctx->buf_in = cl;
    b = pipe_ctx->buf_in->buf;
    b->last = ngx_copy(b->last, data, len);

    rc = ngx_http_lua_pipe_write(pipe, pipe_ctx);
    if (rc == NGX_ERROR) {
        goto error;
    }

    if (rc == NGX_OK) {
        return len;
    }

    /* rc == NGX_AGAIN */
    wait_co_ctx = ctx->cur_co_ctx;
    pipe_ctx->c->data = wait_co_ctx;

    wev->handler = ngx_http_lua_pipe_resume_write_handler;
    if (ngx_handle_write_event(wev, 0) != NGX_OK) {
        pipe_ctx->err_type = PIPE_ERR_ADD_WRITE_EV;
        goto error;
    }

    wait_co_ctx->data = proc;
    wait_co_ctx->cleanup = ngx_http_lua_pipe_proc_write_cleanup;
    timeout = proc->write_timeout;

    if (timeout > 0) {
        ngx_add_timer(wev, timeout);
        ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "lua pipe add timer for writing: %d(ms) process:%p "
                       "pid:%P pipe:%p ev:%p", timeout, proc, proc->_pid, pipe,
                       wev);
    }

    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "lua pipe write yielding process:%p pid:%P pipe:%p", proc,
                   proc->_pid, pipe);

    return NGX_AGAIN;

error:

    ngx_http_lua_pipe_put_error(pipe_ctx, errbuf, errbuf_size);
    return NGX_ERROR;
}


/*
 * ngx_http_lua_ffi_pipe_get_write_result should only be called just after
 * ngx_http_lua_ffi_pipe_proc_write, so we omit most of the sanity check
 * already done in ngx_http_lua_ffi_pipe_proc_write.
 */
ssize_t
ngx_http_lua_ffi_pipe_get_write_result(ngx_http_request_t *r,
    ngx_http_lua_ffi_pipe_proc_t *proc, u_char *errbuf, size_t *errbuf_size)
{
    ngx_http_lua_pipe_t                *pipe;
    ngx_http_lua_pipe_ctx_t            *pipe_ctx;

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "lua pipe get write result process:%p pid:%P", proc,
                   proc->_pid);

    pipe = proc->pipe;
    pipe_ctx = pipe->stdin_ctx;

    if (pipe_ctx->err_type) {
        ngx_http_lua_pipe_put_error(pipe_ctx, errbuf, errbuf_size);
        return NGX_ERROR;
    }

    return pipe_ctx->rest;
}


int
ngx_http_lua_ffi_pipe_proc_wait(ngx_http_request_t *r,
    ngx_http_lua_ffi_pipe_proc_t *proc, char **reason, int *status,
    u_char *errbuf, size_t *errbuf_size)
{
    int                                 rc;
    ngx_rbtree_node_t                  *node;
    ngx_http_lua_ctx_t                 *ctx;
    ngx_http_lua_pipe_t                *pipe;
    ngx_http_lua_co_ctx_t              *wait_co_ctx;
    ngx_http_lua_pipe_node_t           *pipe_node;

    rc = ngx_http_lua_pipe_get_lua_ctx(r, &ctx, errbuf, errbuf_size);
    if (rc != NGX_OK) {
        return rc;
    }

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "lua pipe wait process:%p pid:%P", proc, proc->_pid);

    pipe = proc->pipe;
    if (pipe == NULL || pipe->closed) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "exited") - errbuf;
        return NGX_ERROR;
    }

    node = pipe->node;
    pipe_node = (ngx_http_lua_pipe_node_t *) &node->color;
    if (pipe_node->wait_co_ctx) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "pipe busy waiting")
                       - errbuf;
        return NGX_ERROR;
    }

    if (pipe_node->reason_code == REASON_RUNNING_CODE) {
        wait_co_ctx = ctx->cur_co_ctx;
        wait_co_ctx->data = proc;
        ngx_memzero(&wait_co_ctx->sleep, sizeof(ngx_event_t));
        wait_co_ctx->sleep.handler = ngx_http_lua_pipe_resume_wait_handler;
        wait_co_ctx->sleep.data = wait_co_ctx;
        wait_co_ctx->sleep.log = r->connection->log;
        wait_co_ctx->cleanup = ngx_http_lua_pipe_proc_wait_cleanup;

        pipe_node->wait_co_ctx = wait_co_ctx;

        if (proc->wait_timeout > 0) {
            ngx_add_timer(&wait_co_ctx->sleep, proc->wait_timeout);
            ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "lua pipe add timer for waiting: %d(ms) process:%p "
                           "pid:%P ev:%p", proc->wait_timeout, proc,
                           proc->_pid, &wait_co_ctx->sleep);
        }

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "lua pipe wait yielding process:%p pid:%P", proc,
                       proc->_pid);

        return NGX_AGAIN;
    }

    *status = pipe_node->status;

    switch (pipe_node->reason_code) {

    case REASON_EXIT_CODE:
        *reason = REASON_EXIT;
        break;

    case REASON_SIGNAL_CODE:
        *reason = REASON_SIGNAL;
        break;

    default:
        *reason = REASON_UNKNOWN;
    }

    ngx_http_lua_pipe_proc_finalize(proc);

    if (*status == 0) {
        return NGX_OK;
    }

    return NGX_DECLINED;
}


int
ngx_http_lua_ffi_pipe_proc_kill(ngx_http_lua_ffi_pipe_proc_t *proc, int signal,
    u_char *errbuf, size_t *errbuf_size)
{
    ngx_pid_t                           pid;
    ngx_http_lua_pipe_t                *pipe;

    pipe = proc->pipe;

    if (pipe == NULL || pipe->dead) {
        *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "exited") - errbuf;
        return NGX_ERROR;
    }

    pid = proc->_pid;

    if (kill(pid, signal) == -1) {
        switch (ngx_errno) {
        case EINVAL:
            *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "invalid signal")
                           - errbuf;
            break;

        case ESRCH:
            *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "exited")
                           - errbuf;
            break;

        default:
            *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "%s",
                                        strerror(ngx_errno))
                           - errbuf;
        }

        return NGX_ERROR;
    }

    return NGX_OK;
}


static int
ngx_http_lua_pipe_read_stdout_retval(ngx_http_lua_ffi_pipe_proc_t *proc,
    lua_State *L)
{
    return ngx_http_lua_pipe_read_retval_helper(proc, L, 0);
}


static int
ngx_http_lua_pipe_read_stderr_retval(ngx_http_lua_ffi_pipe_proc_t *proc,
    lua_State *L)
{
    return ngx_http_lua_pipe_read_retval_helper(proc, L, 1);
}


static int
ngx_http_lua_pipe_read_retval_helper(ngx_http_lua_ffi_pipe_proc_t *proc,
    lua_State *L, int from_stderr)
{
    int                              rc;
    ngx_msec_t                       timeout;
    ngx_event_t                     *rev;
    ngx_http_lua_pipe_t             *pipe;
    ngx_http_lua_pipe_ctx_t         *pipe_ctx;

    pipe = proc->pipe;
    if (from_stderr) {
        pipe_ctx = pipe->stderr_ctx;

    } else {
        pipe_ctx = pipe->stdout_ctx;
    }

    if (pipe->timeout) {
        pipe->timeout = 0;
        pipe_ctx->err_type = PIPE_ERR_TIMEOUT;
        return 0;
    }

    if (pipe_ctx->err_type == PIPE_ERR_ABORTED) {
        ngx_close_connection(pipe_ctx->c);
        pipe_ctx->c = NULL;
        return 0;
    }

    rc = ngx_http_lua_pipe_read(pipe, pipe_ctx);
    if (rc != NGX_AGAIN) {
        return 0;
    }

    rev = pipe_ctx->c->read;

    if (from_stderr) {
        rev->handler = ngx_http_lua_pipe_resume_read_stderr_handler;
        timeout = proc->stderr_read_timeout;

    } else {
        rev->handler = ngx_http_lua_pipe_resume_read_stdout_handler;
        timeout = proc->stdout_read_timeout;
    }

    if (timeout > 0) {
        ngx_add_timer(rev, timeout);
        ngx_log_debug5(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                       "lua pipe add timer for reading: %d(ms) proc:%p "
                       "pid:%P pipe:%p ev:%p", timeout, proc, proc->_pid, pipe,
                       rev);
    }

    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                   "lua pipe read yielding process:%p pid:%P pipe:%p", proc,
                   proc->_pid, pipe);

    return NGX_AGAIN;
}


static int
ngx_http_lua_pipe_write_retval(ngx_http_lua_ffi_pipe_proc_t *proc,
    lua_State *L)
{
    int                              rc;
    ngx_msec_t                       timeout;
    ngx_event_t                     *wev;
    ngx_http_lua_pipe_t             *pipe;
    ngx_http_lua_pipe_ctx_t         *pipe_ctx;

    pipe = proc->pipe;
    pipe_ctx = pipe->stdin_ctx;

    if (pipe->timeout) {
        pipe->timeout = 0;
        pipe_ctx->err_type = PIPE_ERR_TIMEOUT;
        return 0;
    }

    if (pipe_ctx->err_type == PIPE_ERR_ABORTED) {
        ngx_close_connection(pipe_ctx->c);
        pipe_ctx->c = NULL;
        return 0;
    }

    rc = ngx_http_lua_pipe_write(pipe, pipe_ctx);
    if (rc != NGX_AGAIN) {
        return 0;
    }

    wev = pipe_ctx->c->write;
    wev->handler = ngx_http_lua_pipe_resume_write_handler;
    timeout = proc->write_timeout;

    if (timeout > 0) {
        ngx_add_timer(wev, timeout);
        ngx_log_debug5(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                       "lua pipe add timer for writing: %d(ms) proc:%p "
                       "pid:%P pipe:%p ev:%p", timeout, proc, proc->_pid, pipe,
                       wev);
    }

    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                   "lua pipe write yielding process:%p pid:%P pipe:%p", proc,
                   proc->_pid, pipe);

    return NGX_AGAIN;
}


static int
ngx_http_lua_pipe_wait_retval(ngx_http_lua_ffi_pipe_proc_t *proc, lua_State *L)
{
    int                              nret;
    ngx_rbtree_node_t               *node;
    ngx_http_lua_pipe_t             *pipe;
    ngx_http_lua_pipe_node_t        *pipe_node;

    pipe = proc->pipe;
    node = pipe->node;
    pipe_node = (ngx_http_lua_pipe_node_t *) &node->color;
    pipe_node->wait_co_ctx = NULL;

    if (pipe->timeout) {
        pipe->timeout = 0;
        lua_pushnil(L);
        lua_pushliteral(L, "timeout");
        return 2;
    }

    ngx_http_lua_pipe_proc_finalize(pipe_node->proc);

    if (pipe_node->status == 0) {
        lua_pushboolean(L, 1);
        lua_pushliteral(L, REASON_EXIT);
        lua_pushinteger(L, pipe_node->status);
        nret = 3;

    } else {
        lua_pushboolean(L, 0);

        switch (pipe_node->reason_code) {

        case REASON_EXIT_CODE:
            lua_pushliteral(L, REASON_EXIT);
            break;

        case REASON_SIGNAL_CODE:
            lua_pushliteral(L, REASON_SIGNAL);
            break;

        default:
            lua_pushliteral(L, REASON_UNKNOWN);
        }

        lua_pushinteger(L, pipe_node->status);
        nret = 3;
    }

    return nret;
}


static void
ngx_http_lua_pipe_resume_helper(ngx_event_t *ev,
    ngx_http_lua_co_ctx_t *wait_co_ctx)
{
    ngx_connection_t                *c;
    ngx_http_request_t              *r;
    ngx_http_lua_ctx_t              *ctx;
    ngx_http_lua_pipe_t             *pipe;
    ngx_http_lua_ffi_pipe_proc_t    *proc;

    if (ev->timedout) {
        proc = wait_co_ctx->data;
        pipe = proc->pipe;
        pipe->timeout = 1;
        ev->timedout = 0;
    }

    ngx_http_lua_pipe_clear_event(ev);

    r = ngx_http_lua_get_req(wait_co_ctx->co);
    c = r->connection;

    ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);
    ngx_http_lua_assert(ctx != NULL);

    ctx->cur_co_ctx = wait_co_ctx;

    if (ctx->entered_content_phase) {
        (void) ngx_http_lua_pipe_resume(r);

    } else {
        ctx->resume_handler = ngx_http_lua_pipe_resume;
        ngx_http_core_run_phases(r);
    }

    ngx_http_run_posted_requests(c);
}


static void
ngx_http_lua_pipe_resume_read_stdout_handler(ngx_event_t *ev)
{
    ngx_connection_t                *c = ev->data;
    ngx_http_lua_co_ctx_t           *wait_co_ctx;
    ngx_http_lua_pipe_t             *pipe;
    ngx_http_lua_ffi_pipe_proc_t    *proc;

    wait_co_ctx = c->data;
    proc = wait_co_ctx->data;
    pipe = proc->pipe;
    pipe->retval_handler = ngx_http_lua_pipe_read_stdout_retval;
    ngx_http_lua_pipe_resume_helper(ev, wait_co_ctx);
}


static void
ngx_http_lua_pipe_resume_read_stderr_handler(ngx_event_t *ev)
{
    ngx_connection_t                *c = ev->data;
    ngx_http_lua_co_ctx_t           *wait_co_ctx;
    ngx_http_lua_pipe_t             *pipe;
    ngx_http_lua_ffi_pipe_proc_t    *proc;

    wait_co_ctx = c->data;
    proc = wait_co_ctx->data;
    pipe = proc->pipe;
    pipe->retval_handler = ngx_http_lua_pipe_read_stderr_retval;
    ngx_http_lua_pipe_resume_helper(ev, wait_co_ctx);
}


static void
ngx_http_lua_pipe_resume_write_handler(ngx_event_t *ev)
{
    ngx_connection_t                *c = ev->data;
    ngx_http_lua_co_ctx_t           *wait_co_ctx;
    ngx_http_lua_pipe_t             *pipe;
    ngx_http_lua_ffi_pipe_proc_t    *proc;

    wait_co_ctx = c->data;
    proc = wait_co_ctx->data;
    pipe = proc->pipe;
    pipe->retval_handler = ngx_http_lua_pipe_write_retval;
    ngx_http_lua_pipe_resume_helper(ev, wait_co_ctx);
}


static void
ngx_http_lua_pipe_resume_wait_handler(ngx_event_t *ev)
{
    ngx_http_lua_co_ctx_t           *wait_co_ctx = ev->data;
    ngx_http_lua_pipe_t             *pipe;
    ngx_http_lua_ffi_pipe_proc_t    *proc;

    proc = wait_co_ctx->data;
    pipe = proc->pipe;
    pipe->retval_handler = ngx_http_lua_pipe_wait_retval;
    ngx_http_lua_pipe_resume_helper(ev, wait_co_ctx);
}


static ngx_int_t
ngx_http_lua_pipe_resume(ngx_http_request_t *r)
{
    int                              nret;
    lua_State                       *vm;
    ngx_int_t                        rc;
    ngx_uint_t                       nreqs;
    ngx_connection_t                *c;
    ngx_http_lua_ctx_t              *ctx;
    ngx_http_lua_pipe_t             *pipe;
    ngx_http_lua_ffi_pipe_proc_t    *proc;

    ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);
    if (ctx == NULL) {
        return NGX_ERROR;
    }

    ctx->resume_handler = ngx_http_lua_wev_handler;
    ctx->cur_co_ctx->cleanup = NULL;

    proc = ctx->cur_co_ctx->data;
    pipe = proc->pipe;
    nret = pipe->retval_handler(proc, ctx->cur_co_ctx->co);
    if (nret == NGX_AGAIN) {
        return NGX_DONE;
    }

    c = r->connection;
    vm = ngx_http_lua_get_lua_vm(r, ctx);
    nreqs = c->requests;

    rc = ngx_http_lua_run_thread(vm, r, ctx, nret);

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "lua run thread returned %d", rc);

    if (rc == NGX_AGAIN) {
        return ngx_http_lua_run_posted_threads(c, vm, r, ctx, nreqs);
    }

    if (rc == NGX_DONE) {
        ngx_http_lua_finalize_request(r, NGX_DONE);
        return ngx_http_lua_run_posted_threads(c, vm, r, ctx, nreqs);
    }

    /* rc == NGX_ERROR || rc >= NGX_OK */

    if (ctx->entered_content_phase) {
        ngx_http_lua_finalize_request(r, rc);
        return NGX_DONE;
    }

    return rc;
}


static void
ngx_http_lua_pipe_dummy_event_handler(ngx_event_t *ev)
{
    /* do nothing */
}


static void
ngx_http_lua_pipe_clear_event(ngx_event_t *ev)
{
    ev->handler = ngx_http_lua_pipe_dummy_event_handler;

    if (ev->timer_set) {
        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ev->log, 0,
                       "lua pipe del timer for ev:%p", ev);
        ngx_del_timer(ev);
    }

    if (ev->posted) {
        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ev->log, 0,
                       "lua pipe del posted event for ev:%p", ev);
        ngx_delete_posted_event(ev);
    }
}


static void
ngx_http_lua_pipe_proc_read_stdout_cleanup(void *data)
{
    ngx_event_t                    *rev;
    ngx_connection_t               *c;
    ngx_http_lua_co_ctx_t          *wait_co_ctx = data;
    ngx_http_lua_ffi_pipe_proc_t   *proc;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                   "lua pipe proc read stdout cleanup");

    proc = wait_co_ctx->data;

    wait_co_ctx->cleanup = NULL;

    if (proc->pipe == NULL) {
        /* pipe_proc_destroy already ran (LIFO pool cleanup) and cancelled
         * timers/connections; nothing left to clean up here. */
        return;
    }

    c = proc->pipe->stdout_ctx->c;
    if (c) {
        rev = c->read;
        ngx_http_lua_pipe_clear_event(rev);
    }
}


static void
ngx_http_lua_pipe_proc_read_stderr_cleanup(void *data)
{
    ngx_event_t                    *rev;
    ngx_connection_t               *c;
    ngx_http_lua_co_ctx_t          *wait_co_ctx = data;
    ngx_http_lua_ffi_pipe_proc_t   *proc;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                   "lua pipe proc read stderr cleanup");

    proc = wait_co_ctx->data;

    wait_co_ctx->cleanup = NULL;

    if (proc->pipe == NULL) {
        return;
    }

    c = proc->pipe->stderr_ctx->c;
    if (c) {
        rev = c->read;
        ngx_http_lua_pipe_clear_event(rev);
    }
}


static void
ngx_http_lua_pipe_proc_write_cleanup(void *data)
{
    ngx_event_t                    *wev;
    ngx_connection_t               *c;
    ngx_http_lua_co_ctx_t          *wait_co_ctx = data;
    ngx_http_lua_ffi_pipe_proc_t   *proc;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                   "lua pipe proc write cleanup");

    proc = wait_co_ctx->data;

    wait_co_ctx->cleanup = NULL;

    if (proc->pipe == NULL) {
        return;
    }

    c = proc->pipe->stdin_ctx->c;
    if (c) {
        wev = c->write;
        ngx_http_lua_pipe_clear_event(wev);
    }
}


static void
ngx_http_lua_pipe_proc_wait_cleanup(void *data)
{
    ngx_rbtree_node_t              *node;
    ngx_http_lua_co_ctx_t          *wait_co_ctx = data;
    ngx_http_lua_pipe_node_t       *pipe_node;
    ngx_http_lua_ffi_pipe_proc_t   *proc;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                   "lua pipe proc wait cleanup");

    proc = wait_co_ctx->data;

    wait_co_ctx->cleanup = NULL;

    if (proc->pipe == NULL) {
        /* pipe_proc_destroy already ran (LIFO pool cleanup) and closed
         * connections, but wait uses wait_co_ctx->sleep (a standalone event,
         * not tied to any connection), so ngx_close_connection in
         * pipe_proc_destroy cannot cancel it.  Clear it here. */
        ngx_http_lua_pipe_clear_event(&wait_co_ctx->sleep);
        return;
    }

    node = proc->pipe->node;
    pipe_node = (ngx_http_lua_pipe_node_t *) &node->color;
    pipe_node->wait_co_ctx = NULL;

    ngx_http_lua_pipe_clear_event(&wait_co_ctx->sleep);
}


#endif /* HAVE_NGX_LUA_PIPE */

/* vi:set ft=c ts=4 sw=4 et fdm=marker: */
