+/*****************************************
+ Code for reading script's stdout/stderr
+ based on mod_cgi's code
+ *****************************************/
+
+#if APR_FILES_AS_SOCKETS
+
+static const apr_bucket_type_t bucket_type_suphp;
+
+struct suphp_bucket_data {
+ apr_pollset_t *pollset;
+ request_rec *r;
+};
+
+static apr_bucket *suphp_bucket_create(request_rec *r, apr_file_t *out, apr_file_t *err, apr_bucket_alloc_t *list)
+{
+ apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
+ apr_status_t rv;
+ apr_pollfd_t fd;
+ struct suphp_bucket_data *data = apr_palloc(r->pool, sizeof(*data));
+
+ APR_BUCKET_INIT(b);
+ b->free = apr_bucket_free;
+ b->list = list;
+ b->type = &bucket_type_suphp;
+ b->length = (apr_size_t) (-1);
+ b->start = (-1);
+
+ /* Create the pollset */
+ rv = apr_pollset_create(&data->pollset, 2, r->pool, 0);
+ AP_DEBUG_ASSERT(rv == APR_SUCCESS);
+
+ fd.desc_type = APR_POLL_FILE;
+ fd.reqevents = APR_POLLIN;
+ fd.p = r->pool;
+ fd.desc.f = out; /* script's stdout */
+ fd.client_data = (void *) 1;
+ rv = apr_pollset_add(data->pollset, &fd);
+ AP_DEBUG_ASSERT(rv == APR_SUCCESS);
+
+ fd.desc.f = err; /* script's stderr */
+ fd.client_data = (void *) 2;
+ rv = apr_pollset_add(data->pollset, &fd);
+ AP_DEBUG_ASSERT(rv == APR_SUCCESS);
+
+ data->r = r;
+ b->data = data;
+ return b;
+}
+
+static apr_bucket *suphp_bucket_dup(struct suphp_bucket_data *data, apr_bucket_alloc_t *list)
+{
+ apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
+ APR_BUCKET_INIT(b);
+ b->free = apr_bucket_free;
+ b->list = list;
+ b->type = &bucket_type_suphp;
+ b->length = (apr_size_t) (-1);
+ b->start = (-1);
+ b->data = data;
+ return b;
+}
+
+/* This utility method is needed, because APR's implementation for the
+ pipe bucket cannot handle or special bucket type */
+static apr_status_t suphp_read_fd(apr_bucket *b, apr_file_t *fd, const char **str, apr_size_t *len)
+{
+ char *buf;
+ apr_status_t rv;
+
+ *str = NULL;
+ *len = APR_BUCKET_BUFF_SIZE;
+ buf = apr_bucket_alloc(*len, b->list);
+
+ rv = apr_file_read(fd, buf, len);
+
+ if (*len > 0) {
+ /* Got data */
+ struct suphp_bucket_data *data = b->data;
+ apr_bucket_heap *h;
+
+ /* Change the current bucket to refer to what we read
+ and append the pipe bucket after it */
+ b = apr_bucket_heap_make(b, buf, *len, apr_bucket_free);
+ /* Here, b->data is the new heap bucket data */
+ h = b->data;
+ h->alloc_len = APR_BUCKET_BUFF_SIZE; /* note the real buffer size */
+ *str = buf;
+ APR_BUCKET_INSERT_AFTER(b, suphp_bucket_dup(data, b->list));
+ } else {
+ /* Got no data */
+ apr_bucket_free(buf);
+ b = apr_bucket_immortal_make(b, "", 0);
+ /* Here, b->data is the reference to the empty string */
+ *str = b->data;
+ }
+ return rv;
+}
+
+/* Poll on stdout and stderr to make sure the process does not block
+ because of a full system (stderr) buffer */
+static apr_status_t suphp_bucket_read(apr_bucket *b, const char **str, apr_size_t *len, apr_read_type_e block) {
+ struct suphp_bucket_data *data = b->data;
+ apr_interval_time_t timeout;
+ apr_status_t rv;
+ int gotdata = 0;
+
+ timeout = (block == APR_NONBLOCK_READ) ? 0 : data->r->server->timeout;
+
+ do {
+ const apr_pollfd_t *results;
+ apr_int32_t num;
+
+ rv = apr_pollset_poll(data->pollset, timeout, &num, &results);
+ if (APR_STATUS_IS_TIMEUP(rv)) {
+ return (timeout == 0) ? APR_EAGAIN : rv;
+ } else if (APR_STATUS_IS_EINTR(rv)) {
+ continue;
+ } else if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, data->r, "Poll failed waiting for suPHP child process");
+ return rv;
+ }
+
+ while (num > 0) {
+ if (results[0].client_data == (void *) 1) {
+ /* handle stdout */
+ rv = suphp_read_fd(b, results[0].desc.f, str, len);
+ if (APR_STATUS_IS_EOF(rv)) {
+ rv = APR_SUCCESS;
+ }
+ gotdata = 1;
+ } else {
+ /* handle stderr */
+ apr_status_t rv2 = suphp_log_script_err(data->r, results[0].desc.f);
+ if (APR_STATUS_IS_EOF(rv2)) {
+ apr_pollset_remove(data->pollset, &results[0]);
+ }
+ }
+ num--;
+ results++;
+ }
+ } while (!gotdata);
+
+ return rv;
+}
+
+static const apr_bucket_type_t bucket_type_suphp = {
+ "SUPHP", 5, APR_BUCKET_DATA,
+ apr_bucket_destroy_noop,
+ suphp_bucket_read,
+ apr_bucket_setaside_notimpl,
+ apr_bucket_split_notimpl,
+ apr_bucket_copy_notimpl
+};
+
+#endif
+
+static void suphp_discard_output(apr_bucket_brigade *bb) {
+ apr_bucket *b;
+ const char *buf;
+ apr_size_t len;
+ apr_status_t rv;
+ APR_BRIGADE_FOREACH(b, bb) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ break;
+ }
+ rv = apr_bucket_read(b, &buf, &len, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) {
+ break;
+ }
+ }
+}
+