src/server/safs/cgi.c

changeset 430
83560f32e7d5
parent 415
d938228c382e
child 431
032b0ad35ee3
--- a/src/server/safs/cgi.c	Sat Nov 12 17:34:58 2022 +0100
+++ b/src/server/safs/cgi.c	Sat Nov 12 20:50:45 2022 +0100
@@ -42,6 +42,7 @@
 #include "../util/pblock.h"
 #include "../daemon/netsite.h"
 #include "../util/io.h"
+#include "../daemon/event.h"
 
 #include "cgiutils.h"
 
@@ -80,13 +81,23 @@
     
     const char *args = pblock_findval("query", rq->reqpb);
     char **argv = cgi_create_argv(path, NULL, args);
+    if(!argv) {
+        return REQ_ABORTED;
+    }
     
     char **env = http_hdrs2env(rq->headers);
     env = cgi_common_vars(sn, rq, env);
     env = cgi_specific_vars(sn, rq, args, env, 1);
     
-    CGIProcess cgip;
-    int ret = cgi_start(&cgip, path, argv, env);
+    // event handler object for non-blocking io event handler
+    CGIHandler *handler = pool_malloc(sn->pool, sizeof(CGIHandler));
+    if(!handler) {
+        return REQ_ABORTED;
+    }
+    ZERO(handler, sizeof(CGIHandler));
+    handler->path = path;
+    
+    int ret = cgi_start(&handler->process, path, argv, env);
     if(ret != REQ_PROCEED) {
         util_env_free(env);
         cgi_free_argv(argv);
@@ -95,7 +106,7 @@
     
     util_env_free(env);
     cgi_free_argv(argv);
-    
+      
     char buf[4096]; // I/O buffer
     ssize_t r;
     
@@ -109,101 +120,173 @@
                         LOG_FAILURE,
                         "send-cgi: script: %s: cannot read request body",
                         path);
-                kill(cgip.pid, SIGTERM);
-                cgi_close(&cgip);
+                kill(handler->process.pid, SIGTERM);
+                cgi_close(&handler->process);
                 return REQ_ABORTED;
             }
-            ssize_t w = write(cgip.in[1], buf, r);
+            ssize_t w = write(handler->process.in[1], buf, r);
             if(w <= 0) {
                 // TODO: handle error
                 log_ereport(
                         LOG_FAILURE,
                         "send-cgi: script: %s: cannot send request body to cgi process",
                         path);
-                kill(cgip.pid, SIGKILL);
-                cgi_close(&cgip);
+                kill(handler->process.pid, SIGKILL);
+                cgi_close(&handler->process);
                 return REQ_ABORTED;
             }
             n += r;
         }
     }
-    system_close(cgip.in[1]);
-    cgip.in[1] = -1;
+    system_close(handler->process.in[1]);
+    handler->process.in[1] = -1;
+    
+    handler->parser = cgi_parser_new(sn, rq);
+    
+    Event *readev = pool_malloc(sn->pool, sizeof(Event));
+    ZERO(readev, sizeof(Event));
+    readev->cookie = handler;
+    readev->fn = cgi_stdout_readevent;
+    readev->finish = cgi_event_finish;
+    
+    Event *stderr_readev = pool_malloc(sn->pool, sizeof(Event));
+    ZERO(stderr_readev, sizeof(Event));
+    stderr_readev->cookie = handler;
+    stderr_readev->fn = cgi_stderr_readevent;
+    stderr_readev->finish = NULL;
+    
+    Event *writeev = pool_malloc(sn->pool, sizeof(Event));
+    ZERO(writeev, sizeof(Event));
+    writeev->cookie = handler;
+    // TODO: fn
+    
+    handler->writeev = writeev;
     
-    // read from child
-    CGIResponseParser *parser = cgi_parser_new(sn, rq);
-    WSBool cgiheader = TRUE;
+    // add poll events for cgi stdout/stderr
+    int error = 0;
+    if(ev_pollin(sn->ev, handler->process.err[0], stderr_readev)) {
+        log_ereport(LOG_FAILURE, "send-cgi: stderr ev_pollin failed");
+        error = 1;
+    }
+    if(ev_pollin(sn->ev, handler->process.out[0], readev)) {
+        log_ereport(LOG_FAILURE, "send-cgi: stdout ev_pollin failed");
+        error = 1;
+    }
+    
+    if(error) {
+        log_ereport(LOG_FAILURE, "cgi-send: kill script: %s", path);
+        kill(handler->process.pid, SIGKILL);
+        cgi_parser_free(handler->parser);
+        return REQ_ABORTED;
+    }
+    
+    return REQ_PROCESSING;
+}
+
+int cgi_stdout_readevent(EventHandler *ev, Event *event) {
+    CGIHandler *handler = event->cookie;
+    CGIResponseParser *parser = handler->parser;
+    Session *sn = parser->sn;
+    Request *rq = parser->rq;
+    
+    char buf[4096]; // I/O buffer
+    ssize_t r;
     ssize_t wr = 0;
-    int result = REQ_PROCEED;
-    size_t response_length = 0;
-    while((r = read(cgip.out[0], buf, 4096)) > 0) {
-        if(cgiheader) {
+    
+    handler->result = REQ_PROCEED;
+    while((r = read(handler->process.out[0], buf, 4096)) > 0) {
+        if(parser->cgiheader) {
             size_t pos;
-            ret = cgi_parse_response(parser, buf, r, &pos);
+            int ret = cgi_parse_response(parser, buf, r, &pos);
             if(ret == -1) {
                 log_ereport(
                         LOG_FAILURE,
-                        "broken cgi script response: path: %s", path);
+                        "broken cgi script response: path: %s", handler->path);
                 protocol_status(sn, rq, 500, NULL);
-                result = REQ_ABORTED;
+                handler->result = REQ_ABORTED;
                 break;
             } else if(ret == 1) {
-                cgiheader = FALSE;
+                parser->cgiheader = FALSE;
                 if(parser->status > 0) {
                     protocol_status(sn, rq, parser->status, parser->msg);
                 }
                 http_start_response(sn, rq);
                 if(pos < r) {
-                    response_length += r-pos;
+                    parser->response_length += r-pos;
                     wr = net_write(sn->csd, &buf[pos], r-pos);
                     if(wr <= 0) {
-                        result = REQ_ABORTED;
+                        handler->result = REQ_ABORTED;
                         break;
                     }
                 }
             }
         } else {
-            response_length += r;
+            parser->response_length += r;
             wr = net_write(sn->csd, buf, r);
             if(wr <= 0) {
-                result = REQ_ABORTED;
+                handler->result = REQ_ABORTED;
                 break;
             }
         }
     }
+    if(r < 0 && errno == EWOULDBLOCK) {
+        event->events = EVENT_POLLIN;
+        return 1;
+    }
     
     char *ctlen_header = pblock_findkeyval(pb_key_content_length, rq->srvhdrs);
     if(ctlen_header) {
         int64_t ctlenhdr;
         if(util_strtoint(ctlen_header, &ctlenhdr)) {
-            if(ctlenhdr != response_length) {
+            if(ctlenhdr != parser->response_length) {
                 log_ereport(
                         LOG_FAILURE,
                         "cgi-send: script: %s: content length mismatch",
-                        path);
+                        handler->path);
                 rq->rq_attr.keep_alive = 0;
-                result = REQ_ABORTED;
+                handler->result = REQ_ABORTED;
             }
         }
     }
     
-    if(result == REQ_ABORTED) {
-        log_ereport(LOG_FAILURE, "cgi-send: kill script: %s", path);
-        kill(cgip.pid, SIGKILL);
+    return 0;
+}
+
+int cgi_stderr_readevent(EventHandler *ev, Event *event) {
+    CGIHandler *handler = event->cookie;
+    
+    char  buf[4096];
+    ssize_t r = read(handler->process.err[0], buf, 4096);
+    log_ereport(LOG_INFORM, "cgi pid %d %s stderr: %.*s", (int)handler->process.pid, handler->path, (int)r, buf);
+    
+    return 0;
+}
+
+int cgi_event_finish(EventHandler *ev, Event *event) {
+    CGIHandler *handler = event->cookie;
+    CGIResponseParser *parser = handler->parser;
+    Session *sn = parser->sn;
+    Request *rq = parser->rq;
+    
+    if(handler->result == REQ_ABORTED) {
+        log_ereport(LOG_FAILURE, "cgi-send: kill script: %s", handler->path);
+        kill(handler->process.pid, SIGKILL);
     }
     
-    int exit_code = cgi_close(&cgip);
+    int exit_code = cgi_close(&handler->process);
     if(exit_code != 0) {
-        log_ereport(LOG_FAILURE, "send-cgi: script: %s exited with code %d", path, exit_code);
-        ret = REQ_ABORTED;
+        log_ereport(LOG_FAILURE, "send-cgi: script: %s exited with code %d", handler->path, exit_code);
+        handler->result = REQ_ABORTED;
     }
       
     cgi_parser_free(parser);
-    return result;
+    // return to nsapi loop
+    nsapi_function_return(sn, rq, handler->result);
+    return 0;
 }
 
 int cgi_start(CGIProcess *p, char *path, char *const argv[], char *const envp[]) {
-    if(pipe(p->in) || pipe(p->out)) {
+    if(pipe(p->in) || pipe(p->out) || pipe(p->err)) {
         log_ereport(
                 LOG_FAILURE,
                 "send-cgi: cannot create pipe: %s",
@@ -241,6 +324,10 @@
             perror("cgi_start: dup2");
             exit(EXIT_FAILURE);
         }
+        if(dup2(p->err[1], STDERR_FILENO) == -1) {
+            perror("cgi_start: dup2");
+            exit(EXIT_FAILURE);
+        }
         
         // we need to close this unused pipe
         // otherwise stdin cannot reach EOF
@@ -251,7 +338,9 @@
     } else {
         // parent  
         system_close(p->out[1]);
+        system_close(p->err[1]);
         p->out[1] = -1;
+        p->err[1] = -1;
     }
     
     return REQ_PROCEED;
@@ -273,6 +362,12 @@
     if(p->out[1] != -1) {
         system_close(p->out[1]);
     }
+    if(p->err[0] != -1) {
+        system_close(p->err[0]);
+    }
+    if(p->err[1] != -1) {
+        system_close(p->err[1]);
+    }
     
     return status;
 }
@@ -283,6 +378,8 @@
     parser->rq = rq;
     parser->status = 0;
     parser->msg = NULL;
+    parser->response_length = 0;
+    parser->cgiheader = TRUE;
     cxBufferInit(&parser->tmp, NULL, 64, pool_allocator(sn->pool), CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS);
     return parser;
 }

mercurial