Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

gdp / apps / gdp-rest.c @ master

History | View | Annotate | Download (34.5 KB)

1 4e425382 Eric Allman
/* vim: set ai sw=4 sts=4 ts=4 :*/
2 47c6ea64 Eric Allman
/*
3 4e425382 Eric Allman
**        RESTful interface to GDP
4 174e9f6d Eric Allman
**
5 4e425382 Eric Allman
**                Uses SCGI between the web server and this process.        We link
6
**                with Sam Alexander's SCGI C Library, http://www.xamuel.com/scgilib/
7 055d3009 Eric Allman
**
8
**        ----- BEGIN LICENSE BLOCK -----
9
**        Applications for the Global Data Plane
10
**        From the Ubiquitous Swarm Lab, 490 Cory Hall, U.C. Berkeley.
11
**
12 c87dd166 Eric Allman
**        Copyright (c) 2015-2019, Regents of the University of California.
13 6bd5476b Eric Allman
**        All rights reserved.
14 055d3009 Eric Allman
**
15 6bd5476b Eric Allman
**        Permission is hereby granted, without written agreement and without
16
**        license or royalty fees, to use, copy, modify, and distribute this
17
**        software and its documentation for any purpose, provided that the above
18
**        copyright notice and the following two paragraphs appear in all copies
19
**        of this software.
20 055d3009 Eric Allman
**
21 6bd5476b Eric Allman
**        IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
22
**        SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST
23
**        PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION,
24
**        EVEN IF REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 055d3009 Eric Allman
**
26 6bd5476b Eric Allman
**        REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
27 055d3009 Eric Allman
**        LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
28 6bd5476b Eric Allman
**        FOR A PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION,
29
**        IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO
30
**        OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
31
**        OR MODIFICATIONS.
32 055d3009 Eric Allman
**        ----- END LICENSE BLOCK -----
33 174e9f6d Eric Allman
*/
34 47c6ea64 Eric Allman
35
#include <ep/ep.h>
36 9f48568c Eric Allman
#include <ep/ep_app.h>
37
#include <ep/ep_hash.h>
38 47c6ea64 Eric Allman
#include <ep/ep_dbg.h>
39
#include <ep/ep_pcvt.h>
40 a0672b22 Eric Allman
#include <ep/ep_sd.h>
41 9f48568c Eric Allman
#include <ep/ep_stat.h>
42 47c6ea64 Eric Allman
#include <ep/ep_xlate.h>
43 1b267ec1 Eric Allman
#include <gdp/gdp.h>
44 63b801e4 Eric Allman
#include <pthread.h>
45 47c6ea64 Eric Allman
#include <unistd.h>
46
#include <stdio.h>
47
#include <sysexits.h>
48
#include <string.h>
49
#include <ctype.h>
50
#include <errno.h>
51 4e425382 Eric Allman
#include <getopt.h>
52
#include <sys/socket.h>
53 47c6ea64 Eric Allman
#include <scgilib/scgilib.h>
54
#include <event2/event.h>
55 80c5389d Eric Allman
#include <jansson.h>
56 04ad7859 Rick Pratt
#include <sys/types.h>
57
#include <sys/wait.h>
58 47c6ea64 Eric Allman
59 174e9f6d Eric Allman
static EP_DBG        Dbg = EP_DBG_INIT("gdp.rest", "RESTful interface to GDP");
60 47c6ea64 Eric Allman
61 18188fcc Eric Allman
#define DEF_URI_PREFIX        "/gdp/v1"
62 0c663d10 Eric Allman
63 4e425382 Eric Allman
const char                *GclUriPrefix;                        // prefix on all REST calls
64
EP_HASH                        *OpenGclCache;                        // cache of open GCLs
65 47c6ea64 Eric Allman
66 a555e771 Rick Pratt
gdp_open_info_t *shared_gdp_open_info;
67 1b267ec1 Eric Allman
68 eae1d3ec Eric Allman
// most gdp-create parameters are available through gdp-rest
69 0cb06b6b Rick Pratt
#define GCL_C_PARAM_SERV_e                0
70
#define GCL_C_PARAM_SERV_S                1
71 55ebd220 Rick Pratt
#define GCL_C_PARAM_SERV_K                2
72
#define GCL_C_PARAM_SERV_MAX        3
73 0cb06b6b Rick Pratt
#define GCL_C_PARAM_MAX                        (GCL_C_PARAM_SERV_MAX + 6)
74 04ad7859 Rick Pratt
75 eae1d3ec Eric Allman
const char *exec_gdp_create_param[GCL_C_PARAM_MAX] =
76 04ad7859 Rick Pratt
{
77 0cb06b6b Rick Pratt
        /* 3 server controlled options */
78
        "-e", "-S", "-K",
79
        /* 6 client options */
80
        "-C", "-D", "-h", "-k", "-b", "-c"
81
        /* remaining unsupported options: "-G", "-q", "-s", "-w", "-W" */        
82 04ad7859 Rick Pratt
};
83 eae1d3ec Eric Allman
const char *exec_gdp_create = "/usr/bin/gdp-create";
84 04ad7859 Rick Pratt
85 eae1d3ec Eric Allman
// WARNING: gdp-rest parses gdp-create text output (which will be kept stable!)
86 04ad7859 Rick Pratt
#define GCL_C_OUT_GCL_NAME      16
87
#define GCL_C_OUT_GCL_NAME_END  59
88
#define GCL_C_OUT_LOGD_NAME     75
89
#define GCL_C_OUT_BUF_SIZE     256 // likely output * 2, rounded up
90 eae1d3ec Eric Allman
// gdp-create text output expectation (which will be kept stable!)
91
const char *gdp_c_min_out =
92 04ad7859 Rick Pratt
        "Created new GCL 0123456789012345678901234567890123456789012\n" //60
93
        "\ton log server N\n"; // N is a variable length > 0 log server name
94
95 1b267ec1 Eric Allman
/*
96
**  LOG_ERROR --- generic error logging routine
97
*/
98
99
void
100
log_error(const char *fmt, ...)
101 47c6ea64 Eric Allman
{
102 1b267ec1 Eric Allman
        va_list av;
103
104
        va_start(av, fmt);
105
        vfprintf(stderr, fmt, av);
106
        fprintf(stderr, ": %s\n", strerror(errno));
107
}
108 47c6ea64 Eric Allman
109
/*
110 4e425382 Eric Allman
**        SCGI_METHOD_NAME --- return printable name of method
111 174e9f6d Eric Allman
**
112 4e425382 Eric Allman
**                Arguably this should be in SCGILIB.
113 174e9f6d Eric Allman
*/
114 47c6ea64 Eric Allman
115
const char *
116
scgi_method_name(types_of_methods_for_http_protocol meth)
117
{
118 4e425382 Eric Allman
        switch (meth)
119
        {
120
                case SCGI_METHOD_UNSPECIFIED:
121
                        return "unspecified";
122
                case SCGI_METHOD_UNKNOWN:
123
                        return "unknown";
124
                case SCGI_METHOD_GET:
125
                        return "GET";
126
                case SCGI_METHOD_POST:
127
                        return "POST";
128
                case SCGI_METHOD_PUT:
129
                        return "PUT";
130
                case SCGI_METHOD_DELETE:
131
                        return "DELETE";
132
                case SCGI_METHOD_HEAD:
133
                        return "HEAD";
134 2ffaa038 Rick Pratt
                case SCGI_METHOD_OPTIONS:
135
                        return "OPTIONS";
136 4e425382 Eric Allman
        }
137
        return "impossible";
138 47c6ea64 Eric Allman
}
139
140 174e9f6d Eric Allman
141 47c6ea64 Eric Allman
void
142 174e9f6d Eric Allman
write_scgi(scgi_request *req,
143 4e425382 Eric Allman
                char *sbuf)
144 47c6ea64 Eric Allman
{
145 4e425382 Eric Allman
        int dead = 0;
146
        int i;
147 80c5389d Eric Allman
        char xbuf[1024];
148
        char *xbase = xbuf;
149
        char *xp;
150
        char *sp;
151
152
        // first translate any lone newlines into crlfs (pity jansson won't do this)
153
        for (i = 0, sp = sbuf; (sp = strchr(sp, '\n')) != NULL; sp++)
154
                if (sp == sbuf || sp[-1] != '\r')
155
                        i++;
156
157
        // i is now the count of newlines without carriage returns
158
        sp = sbuf;
159
        if (i > 0)
160
        {
161
                bool cr = false;
162
163
                // find the total number of bytes we need, using malloc if necessary
164
                i += strlen(sbuf) + 1;
165
                if (i > sizeof xbuf)
166
                        xbase = ep_mem_malloc(i);
167
                xp = xbase;
168
169
                // xp now points to a large enough buffer, possibly malloced
170
                while (*sp != '\0')
171
                {
172
                        if (*sp == '\n' && !cr)
173
                                *xp++ = '\r';
174
                        cr = *sp == '\r';
175
                        *xp++ = *sp++;
176
                }
177
                *xp = '\0';
178
179
                sp = xbase;
180
        }
181
182 4e425382 Eric Allman
183
        // I don't quite understand what "dead" is all about.  It's copied
184
        // from the "helloworld" example.  Something about memory management,
185
        // but his example only seems to use it to print messages.
186
        req->dead = &dead;
187 80c5389d Eric Allman
        i = scgi_write(req, sp);
188
189
        // free buffer memory if necessary
190
        if (xbase != xbuf)
191
                ep_mem_free(xbase);
192 4e425382 Eric Allman
193
        ep_dbg_cprintf(Dbg, 10, "scgi_write => %d, dead = %d\n", i, dead);
194
        if (i == 0)
195
        {
196
                char obuf[40];
197 5bc1cdfb Eric Allman
                char nbuf[40];
198 4e425382 Eric Allman
199 bfecf573 Eric Allman
                (void) (0 == strerror_r(errno, nbuf, sizeof nbuf));
200 4e425382 Eric Allman
                ep_app_error("scgi_write (%s) failed: %s",
201 5bc1cdfb Eric Allman
                                ep_pcvt_str(obuf, sizeof obuf, sbuf), nbuf);
202 4e425382 Eric Allman
        }
203
        else if (dead)
204
        {
205
                ep_dbg_cprintf(Dbg, 1, "dead is set\n");
206
        }
207
        req->dead = NULL;
208 efad251c Siqi Lin
}
209 47c6ea64 Eric Allman
210 174e9f6d Eric Allman
211 47c6ea64 Eric Allman
EP_STAT
212
gdp_failure(scgi_request *req, char *code, char *msg, char *fmt, ...)
213
{
214 80c5389d Eric Allman
        char buf[SCGI_MAX_OUTBUF_SIZE];
215 4e425382 Eric Allman
        va_list av;
216
        char c;
217 80c5389d Eric Allman
        json_t *j;
218
        char *jbuf;
219 4e425382 Eric Allman
220 80c5389d Eric Allman
        // set up the JSON object
221
        j = json_object();
222 de9d8389 Rick Pratt
        // set_new steals new json_string (or auto-decrefs if !j || !key)
223
        json_object_set_new_nocheck(j, "error", json_string(msg));
224
        json_object_set_new_nocheck(j, "code", json_string(code));
225
        json_object_set_new_nocheck(j, "uri", json_string(req->request_uri));
226
        json_object_set_new_nocheck(j, "method",
227 4f2d2174 Eric Allman
                        json_string(scgi_method_name(req->request_method)));
228 4e425382 Eric Allman
229
        va_start(av, fmt);
230
        while ((c = *fmt++) != '\0')
231 47c6ea64 Eric Allman
        {
232 80c5389d Eric Allman
                char *key = va_arg(av, char *);
233 4e425382 Eric Allman
234
                switch (c)
235
                {
236
                case 's':
237 de9d8389 Rick Pratt
                        // set_new steals new json_string (or auto-decrefs if !j || !key)
238
                        json_object_set_new(j, key, json_string(va_arg(av, char *)));
239 4e425382 Eric Allman
                        break;
240
241
                case 'd':
242 de9d8389 Rick Pratt
                        // set_new steals new json_integer (or auto-decrefs if !j || !key)
243
                        json_object_set_new(j, key, json_integer((long) va_arg(av, int)));
244 4e425382 Eric Allman
                        break;
245
246
                default:
247 80c5389d Eric Allman
                        {
248
                                char pbuf[40];
249
250
                                snprintf(pbuf, sizeof pbuf, "Unknown format `%c'", c);
251 de9d8389 Rick Pratt
                                // set_new steals new json_string (auto-decrefs if !j || !key)
252
                                json_object_set_new(j, key, json_string(pbuf));
253 80c5389d Eric Allman
                        }
254 4e425382 Eric Allman
                        break;
255
                }
256 47c6ea64 Eric Allman
        }
257 4e425382 Eric Allman
        va_end(av);
258 80c5389d Eric Allman
259 de9d8389 Rick Pratt
        // get it (malloc'ed) in string format (will return NULL if !j)
260 80c5389d Eric Allman
        jbuf = json_dumps(j, JSON_INDENT(4));
261
262
        // create the entire SCGI return message
263
        snprintf(buf, sizeof buf,
264 de9d8389 Rick Pratt
                         "HTTP/1.1 %s %s\r\n"
265
                         "Content-Type: application/json\r\n"
266
                         "\r\n"
267
                         "%s\r\n",
268
                         code, msg, jbuf ? jbuf : "");
269 4e425382 Eric Allman
        write_scgi(req, buf);
270
271 80c5389d Eric Allman
        // clean up
272
        json_decref(j);
273
        free(jbuf);
274
275 4e425382 Eric Allman
        // should chose something more appropriate here
276
        return EP_STAT_ERROR;
277 47c6ea64 Eric Allman
}
278
279 174e9f6d Eric Allman
280 4e425382 Eric Allman
/*
281 1b267ec1 Eric Allman
**  GDP_SCGI_RECV --- guarded version of scgi_recv().
282
**
283
**                The SCGI library isn't reentrant, so we have to avoid conflict
284
**                here.
285
*/
286
287
EP_THR_MUTEX        ScgiRecvMutex        EP_THR_MUTEX_INITIALIZER;
288
289
scgi_request *
290
gdp_scgi_recv(void)
291
{
292
        scgi_request *req;
293
294
        ep_thr_mutex_lock(&ScgiRecvMutex);
295
        req = scgi_recv();
296
        ep_thr_mutex_unlock(&ScgiRecvMutex);
297
        return req;
298
}
299
300
301 18188fcc Eric Allman
bool
302
is_integer_string(const char *s)
303
{
304
        do
305
        {
306
                if (!isdigit(*s))
307
                        return false;
308
        } while (*++s != '\0');
309
        return true;
310
}
311
312
313
/*
314
**  PARSE_QUERY --- break up the query into key/value pairs
315
*/
316
317
struct qkvpair
318
{
319
        char        *key;
320
        char        *val;
321
};
322
323
int
324
parse_query(char *qtext, struct qkvpair *qkvs, int nqkvs)
325
{
326
        int n = 0;
327
328
        for (n = 0; qtext != NULL && n < nqkvs; n++)
329
        {
330
                char *p;
331
332
                if (*qtext == '\0')
333
                        break;
334
335
                // skip to the next kv pair separator
336
                qkvs[n].key = strsep(&qtext, "&;");
337
338
                // separate the key and the value (if any)
339
                p = strchr(qkvs[n].key, '=');
340
                if (p != NULL && *p != '\0')
341
                        *p++ = '\0';
342
                qkvs[n].val = p;
343
        }
344
345
        return n;
346
}
347
348
349
/*
350
**  FIND_QUERY_KV --- find a key-value pair in query
351
*/
352
353
char *
354
find_query_kv(const char *key, struct qkvpair *qkvs)
355
{
356
        while (qkvs->key != NULL)
357
        {
358
                if (strcasecmp(key, qkvs->key) == 0)
359
                        return qkvs->val;
360
                qkvs++;
361
        }
362
        return NULL;
363
}
364
365 2ffaa038 Rick Pratt
/*
366
**  CORS200 --- issue an OPTIONS response supportive of javascript CORS needs
367
*/
368
EP_STAT
369
cors200(scgi_request *req)
370
{
371
        char buf[SCGI_MAX_OUTBUF_SIZE];
372
        snprintf(buf, sizeof buf,
373
                         "HTTP/1.1 200 OK\r\n"
374
                         "Content-Type: text/plain\r\n"
375
                         "\r\n"
376
                          "\r\n");
377
        write_scgi(req, buf);
378
        return EP_STAT_OK;
379
}
380 18188fcc Eric Allman
381
/*
382 04ad7859 Rick Pratt
**  ERROR400 --- issue a 400 "Bad Request" error
383
*/
384
385
EP_STAT
386
error400(scgi_request *req, const char *detail)
387
{
388
        return gdp_failure(req, "400", "Bad Request", "s", "detail", detail);
389
}
390
391
/*
392 18188fcc Eric Allman
**  ERROR404 --- issue a 404 "Not Found" error
393
*/
394
395
EP_STAT
396
error404(scgi_request *req, const char *detail)
397
{
398 4f2d2174 Eric Allman
        return gdp_failure(req, "404", "Not Found", "s",
399 18188fcc Eric Allman
                        "detail", detail);
400
}
401
402
403 1b267ec1 Eric Allman
/*
404 18188fcc Eric Allman
**  ERROR405 --- issue a 405 "Method Not Allowed" error
405
*/
406
407
EP_STAT
408
error405(scgi_request *req, const char *detail)
409
{
410 4f2d2174 Eric Allman
        return gdp_failure(req, "405", "Method Not Allowed", "s",
411 18188fcc Eric Allman
                        "detail", detail);
412
}
413
414 04ad7859 Rick Pratt
/*
415
**  ERROR409 --- issue a 409 "Conflict" error
416
*/
417
418
EP_STAT
419
error409(scgi_request *req, const char *detail)
420
{
421
        return gdp_failure(req, "409", "Conflict", "s",        "detail", detail);
422
}
423 18188fcc Eric Allman
424
/*
425
**  ERROR500 --- issue a 500 "Internal Server Error" error
426
*/
427
428
EP_STAT
429
error500(scgi_request *req, const char *detail, int eno)
430
{
431
        char nbuf[40];
432
433 bfecf573 Eric Allman
        (void) (0 == strerror_r(eno, nbuf, sizeof nbuf));
434 4f2d2174 Eric Allman
        (void) gdp_failure(req, "500", "Internal Server Error", "ss",
435 18188fcc Eric Allman
                        "errno", nbuf,
436
                        "detail", detail);
437
        return ep_stat_from_errno(eno);
438
}
439
440
441
/*
442
**  ERROR501 --- issue a 501 "Not Implemented" error
443
*/
444
445
EP_STAT
446
error501(scgi_request *req, const char *detail)
447
{
448 4f2d2174 Eric Allman
        return gdp_failure(req, "501", "Not Implemented", "s",
449 18188fcc Eric Allman
                        "detail", detail);
450
}
451
452
453
/*
454 a555e771 Rick Pratt
**  PROCESS_GDP_CREATE_REQ --- create new log via gdp-create, parse stdout msg
455 18188fcc Eric Allman
*/
456
457
EP_STAT
458 eae1d3ec Eric Allman
process_gdp_create_req(scgi_request *req, const char *gclxname,
459 04ad7859 Rick Pratt
                                           json_t *j, json_t *j_meta, size_t options_max)
460 18188fcc Eric Allman
{
461 04ad7859 Rick Pratt
        EP_STAT estat = EP_STAT_OK;
462
        int opt;
463
        const char *options[options_max];
464
        size_t j_unprocessed;
465
        size_t j_meta_unprocessed;
466
        int o_pipe[2];
467
        pid_t pid;
468
        int status;
469
        int exit_code;
470
        json_t *j_temp;
471
        json_t *j_resp;
472
        char *jbuf;
473
        char sbuf[SCGI_MAX_OUTBUF_SIZE];
474 d56dfbff Christopher Brooks
        int p;
475 d0a04e62 Eric Allman
476 04ad7859 Rick Pratt
        // mandatory gclxname json obj already processed by caller to arrive here
477
        j_unprocessed = json_object_size(j) - 1;
478
479
        // process name in slot zero
480
        opt = 0;
481 eae1d3ec Eric Allman
        options[opt++] = exec_gdp_create;
482 d0a04e62 Eric Allman
483 04ad7859 Rick Pratt
        // then add server controlled parameters
484 eae1d3ec Eric Allman
        options[opt++] = exec_gdp_create_param[GCL_C_PARAM_SERV_e];
485 04ad7859 Rick Pratt
        options[opt++] = "none";
486 0cb06b6b Rick Pratt
        options[opt++] = exec_gdp_create_param[GCL_C_PARAM_SERV_S];
487 eae1d3ec Eric Allman
        options[opt++] = exec_gdp_create_param[GCL_C_PARAM_SERV_K];
488 04ad7859 Rick Pratt
        options[opt++] = "/etc/gdp/keys";
489
490
        // then add client requested options
491 d56dfbff Christopher Brooks
        for (p = GCL_C_PARAM_SERV_MAX; p < GCL_C_PARAM_MAX; p++)
492 04ad7859 Rick Pratt
        {
493
                // borrowed
494 eae1d3ec Eric Allman
                if ((j_temp = json_object_get(j, exec_gdp_create_param[p])) != NULL)
495 04ad7859 Rick Pratt
                {
496 eae1d3ec Eric Allman
                        options[opt++] = exec_gdp_create_param[p];
497 04ad7859 Rick Pratt
                        // borrowed
498
                        options[opt++] = json_string_value(j_temp);
499
                        if (options[opt - 1] != NULL)
500
                                j_unprocessed--;
501
                }
502
        }
503 18188fcc Eric Allman
504 de9d8389 Rick Pratt
        // client optional "META" array elements (size is 0 if !j_meta or !array)
505 04ad7859 Rick Pratt
        j_meta_unprocessed = json_array_size(j_meta);
506
        if (j_meta_unprocessed > 0)
507
        {
508 d0a04e62 Eric Allman
509 04ad7859 Rick Pratt
                size_t j_meta_i;
510
511
                json_array_foreach(j_meta, j_meta_i, j_temp)
512
                {
513
                        if (json_is_string(j_temp))
514
                        {
515
                                // borrowed
516
                                options[opt] = json_string_value(j_temp);
517
                                if (options[opt] != NULL && strchr(options[opt], '=') != NULL)
518
                                {
519
                                        opt++;
520
                                        j_meta_unprocessed--;
521
                                }
522
                        }
523
                }
524
                // json key "META" has been processed
525
                j_unprocessed--;
526
        }
527
528
        // then add the external-name if present (implies HTTP PUT)
529
        if (gclxname != NULL)
530
                options[opt++] = gclxname;
531 d0a04e62 Eric Allman
532 04ad7859 Rick Pratt
        // and finally terminate the argv-style options pointer array
533
        options[opt++] = NULL;
534 18188fcc Eric Allman
535 04ad7859 Rick Pratt
        // unprocessed client content is treated as an error
536
        if (j_unprocessed > 0 || j_meta_unprocessed > 0)
537 18188fcc Eric Allman
        {
538 04ad7859 Rick Pratt
                estat = error400(req, "request contains unrecognized json objects");
539
                return estat;
540
        }
541 18188fcc Eric Allman
542 04ad7859 Rick Pratt
        // prep to capture child's stdout
543
        if ((pipe(o_pipe)) == -1)
544
        {
545
                estat = error500(req, "gdp-rest pipe", errno);
546
                return estat;
547 18188fcc Eric Allman
        }
548 04ad7859 Rick Pratt
549
        if ((pid = fork()) == -1)
550 18188fcc Eric Allman
        {
551 04ad7859 Rick Pratt
                estat = error500(req, "gdp-rest fork", errno);
552
                close(o_pipe[STDOUT_FILENO]);
553
                close(o_pipe[STDIN_FILENO]);
554
                return estat;
555 18188fcc Eric Allman
        }
556 04ad7859 Rick Pratt
557
        if (pid == 0)
558 18188fcc Eric Allman
        {
559 04ad7859 Rick Pratt
                // child moves child side of pipe to own stdout
560
                dup2(o_pipe[STDOUT_FILENO], STDOUT_FILENO);
561
                close(o_pipe[STDOUT_FILENO]);
562
                // child closes parent side of pipe
563
                close(o_pipe[STDIN_FILENO]);
564 eae1d3ec Eric Allman
                // child execv gdp-create
565
                execv(exec_gdp_create, (char * const*)options);
566 04ad7859 Rick Pratt
                // child execv launch failed
567
                perror("gdp-rest execv failure");
568
                exit(EXIT_FAILURE);
569
        }
570 18188fcc Eric Allman
571 04ad7859 Rick Pratt
        // parent closes child side of pipe
572
        close(o_pipe[STDOUT_FILENO]);
573 80c5389d Eric Allman
574 04ad7859 Rick Pratt
        // wait for child
575
        if (waitpid(pid, &status, 0) != pid)
576
        {
577
                estat = error500(req, "gdp-rest waitpid", errno);
578
                close(o_pipe[STDIN_FILENO]);
579
                return estat;
580
        }
581
582
        // check child status for unexpected exits
583
        if (! WIFEXITED(status) ||
584
                (exit_code = WEXITSTATUS(status)) == EXIT_FAILURE)
585
        {
586
                estat = gdp_failure(req, "500", "Internal Server Error", "sds",
587 eae1d3ec Eric Allman
                                                        "detail", "gdp-rest execv gdp-create failure",
588 04ad7859 Rick Pratt
                                                        "status", status,
589
                                                        "request", req->body);
590
                close(o_pipe[STDIN_FILENO]);
591
                return estat;
592
        }
593 eae1d3ec Eric Allman
        ep_dbg_cprintf(Dbg, 5, "gdp-rest exec gdp-create exited(%d)\n", exit_code);
594 d0a04e62 Eric Allman
595 04ad7859 Rick Pratt
        if (exit_code == EX_OK)
596
        {
597
                ssize_t bytes;
598 a555e771 Rick Pratt
                char *created;
599 04ad7859 Rick Pratt
                char obuf[GCL_C_OUT_BUF_SIZE];
600
601
                bytes = read(o_pipe[STDIN_FILENO], obuf, sizeof obuf - 1);
602
                if (bytes < 0)
603
                {
604
                        estat = error500(req, "gdp-rest pipe read", errno);
605
                        close(o_pipe[STDIN_FILENO]);
606
                        return estat;
607
                }
608
                obuf[bytes] = '\0';
609
                ep_dbg_cprintf(Dbg, 5, "pipe read %ld bytes = {\n%s}\n", bytes, obuf);
610 eae1d3ec Eric Allman
                if (bytes < sizeof gdp_c_min_out)
611 04ad7859 Rick Pratt
                {
612
                        estat = gdp_failure(req, "500", "Internal Server Error", "ss",
613
                                                                "detail", "gdp-rest pipe read bytes",
614
                                                                "read", obuf);
615
                        close(o_pipe[STDIN_FILENO]);
616
                        return estat;
617
                }
618 80c5389d Eric Allman
619 04ad7859 Rick Pratt
                // new
620
                if ((j_resp = json_object()) == NULL)
621
                {
622
                        estat = error500(req, "gdp-rest response", ENOMEM);
623
                        close(o_pipe[STDIN_FILENO]);
624
                        return estat;
625
                }
626
627 a555e771 Rick Pratt
                obuf[bytes - 1] = '\0';        // terminate obuf
628
629
                // validate input (i.e. find "Created new GCL ...", which is not first)
630
                created = strstr((const char *)obuf, "Created");
631
                if (created == NULL)
632
                {
633
                        estat = gdp_failure(req, "500", "Internal Server Error", "ss",
634
                                                                "detail", "gdp-rest pipe read created",
635
                                                                "read", obuf);
636 de9d8389 Rick Pratt
                        json_decref(j_resp);
637 a555e771 Rick Pratt
                        close(o_pipe[STDIN_FILENO]);
638
                        return estat;
639
                }
640 d0a04e62 Eric Allman
641 eae1d3ec Eric Allman
                // gdp-create EX_OK output is kept stable to permit string extraction
642 a555e771 Rick Pratt
                created[GCL_C_OUT_GCL_NAME_END] = '\0'; // terminate a newline of line 1
643 04ad7859 Rick Pratt
644
                // new
645 a555e771 Rick Pratt
                j_temp = json_string(&created[GCL_C_OUT_GCL_NAME]);
646 04ad7859 Rick Pratt
                if (j_temp == NULL)
647
                {
648 a555e771 Rick Pratt
                        estat = error500(req, "gdp-rest response gcl_name", EIO);
649 04ad7859 Rick Pratt
                        json_decref(j_resp);
650
                        close(o_pipe[STDIN_FILENO]);
651
                        return estat;
652
                }
653 de9d8389 Rick Pratt
                // set_new steals new json_string (or auto-decrefs if !j || !key)
654 a555e771 Rick Pratt
                if ((json_object_set_new_nocheck(j_resp, "gcl_name", j_temp)) == -1)
655 04ad7859 Rick Pratt
                {
656
                        estat = gdp_failure(req, "500", "Internal Server Error", "ss",
657
                                                                "detail", "gdp-rest response",
658 a555e771 Rick Pratt
                                                                "gcl_name", &created[GCL_C_OUT_GCL_NAME]);
659 04ad7859 Rick Pratt
                        json_decref(j_resp);
660
                        close(o_pipe[STDIN_FILENO]);
661
                        return estat;
662
                }
663 0cb06b6b Rick Pratt
664 04ad7859 Rick Pratt
                // malloc
665
                if ((jbuf = json_dumps(j_resp, JSON_INDENT(4))) == NULL)
666
                {
667
                        estat = error500(req, "gdp-rest response reformat", EIO);
668
                        json_decref(j_resp);
669
                        close(o_pipe[STDIN_FILENO]);
670
                        return estat;
671
                }
672 18188fcc Eric Allman
                snprintf(sbuf, sizeof sbuf,
673 04ad7859 Rick Pratt
                                 "HTTP/1.1 201 GCL created\r\n"
674
                                 "Content-Type: application/json\r\n"
675
                                 "\r\n"
676
                                 "%s\r\n",
677
                                 jbuf);
678 18188fcc Eric Allman
                write_scgi(req, sbuf);
679 04ad7859 Rick Pratt
                free(jbuf);
680
                json_decref(j_resp);
681
        }
682
        else if ((exit_code == EX_CANTCREAT) &&
683
                         (req->request_method == SCGI_METHOD_PUT))
684
                estat = error409(req, "external-name already exists on gdplogd server");
685
        else if (exit_code == EX_CANTCREAT)
686
                estat = error409(req, "generated-name conflict on gdplogd server");
687
        else if (exit_code == EX_NOHOST)
688
                estat = error400(req, "log server host not found");
689
        else if (exit_code == EX_UNAVAILABLE)
690
                estat = error400(req, "key length selection insecure, denied");
691
        else
692
                estat = gdp_failure(req, "500", "Internal Server Error", "sd",
693 eae1d3ec Eric Allman
                                                        "detail", "gdp-create unexpected error",
694 04ad7859 Rick Pratt
                                                        "exit_code", exit_code);
695 80c5389d Eric Allman
696 04ad7859 Rick Pratt
        close(o_pipe[STDIN_FILENO]);
697
        return estat;
698
}
699
700
701
/*
702 a555e771 Rick Pratt
**  A_NEW_GOB --- create new GDP Object
703 04ad7859 Rick Pratt
*/
704
705
EP_STAT
706 eae1d3ec Eric Allman
a_new_gob(scgi_request *req)
707 04ad7859 Rick Pratt
{
708
        EP_STAT estat = EP_STAT_OK;
709
        json_t *j;
710 eae1d3ec Eric Allman
        const char *gobxname;
711 04ad7859 Rick Pratt
        size_t options_max;
712
        json_t *j_temp;
713
        json_t *j_meta;
714 d0a04e62 Eric Allman
715 04ad7859 Rick Pratt
        ep_dbg_cprintf(Dbg, 5, "=== Create new GCL (%s)\n", req->body);
716
717
        // new
718
        if ((j = json_loads(req->body, JSON_REJECT_DUPLICATES, NULL)) == NULL)
719
        {
720
                estat = error400(req, "request body not recognized json format");
721
                return estat;
722
        }
723 d0a04e62 Eric Allman
724 04ad7859 Rick Pratt
        // mandatory external-name obj, to prevent URL crawl-driven log creation
725
        // borrowed
726
        if ((j_temp = json_object_get(j, "external-name")) == NULL)
727
        {
728
                estat = error400(req, "mandatory external-name not found");
729 80c5389d Eric Allman
                json_decref(j);
730 04ad7859 Rick Pratt
                return estat;
731 18188fcc Eric Allman
        }
732 de9d8389 Rick Pratt
        json_incref(j_temp);
733 04ad7859 Rick Pratt
        // borrowed
734 eae1d3ec Eric Allman
        gobxname = json_string_value(j_temp);
735 04ad7859 Rick Pratt
736
        // external-name obj value must be NULL for POST, non-NULL for PUT
737 eae1d3ec Eric Allman
        if ((req->request_method == SCGI_METHOD_POST && gobxname != NULL) ||
738
                (req->request_method == SCGI_METHOD_PUT && gobxname == NULL))
739 4f2d2174 Eric Allman
        {
740 eae1d3ec Eric Allman
                if (gobxname != NULL)
741 04ad7859 Rick Pratt
                        estat = error400(req, "POST external-name must have null value");
742
                else
743
                        estat = error400(req, "PUT external-name must have non-null value");
744 de9d8389 Rick Pratt
                json_decref(j_temp);
745 04ad7859 Rick Pratt
                json_decref(j);
746
                return estat;
747 4f2d2174 Eric Allman
        }
748 04ad7859 Rick Pratt
749
        // procname + 2 * (server params + client params) + glxname + terminator
750
        options_max = 1 + 2 * (GCL_C_PARAM_SERV_MAX + json_object_size(j)) + 1 + 1;
751
752
        // peek at META array now, to size options array, then set aside for later
753
        // borrowed
754 de9d8389 Rick Pratt
        j_meta = json_object_get(j, "META");
755
        json_incref(j_meta);
756
757
        // size is 0 if !j_meta or !array
758
        options_max += json_array_size(j_meta);
759 18188fcc Eric Allman
760 a555e771 Rick Pratt
        estat = process_gdp_create_req(req, gobxname, j, j_meta, options_max);
761 3e05a791 Eric Allman
762 de9d8389 Rick Pratt
        // calls handle NULL
763
        json_decref(j_meta);
764
        json_decref(j_temp);
765 04ad7859 Rick Pratt
        json_decref(j);
766
        return estat;
767
}
768 18188fcc Eric Allman
769
/*
770 a555e771 Rick Pratt
**  A_SHOW_GOB --- show information about a GDP Object
771 18188fcc Eric Allman
*/
772
773
EP_STAT
774 eae1d3ec Eric Allman
a_show_gob(scgi_request *req, gdp_name_t gobiname)
775 18188fcc Eric Allman
{
776
        return error501(req, "GCL status not implemented");
777
}
778
779
780
/*
781 05e1cb19 Eric Allman
**  A_APPEND --- append datum to GCL
782 18188fcc Eric Allman
*/
783
784
EP_STAT
785 eae1d3ec Eric Allman
a_append(scgi_request *req, gdp_name_t gobiname, gdp_datum_t *datum)
786 18188fcc Eric Allman
{
787
        EP_STAT estat = EP_STAT_OK;
788 eae1d3ec Eric Allman
        gdp_gin_t *gin = NULL;
789 de9d8389 Rick Pratt
        char rbuf[SCGI_MAX_OUTBUF_SIZE];
790
        json_t *j;
791
        char *jbuf;
792
        EP_TIME_SPEC ts;
793 18188fcc Eric Allman
794 05e1cb19 Eric Allman
        ep_dbg_cprintf(Dbg, 5, "=== Append value to GCL\n");
795 18188fcc Eric Allman
796 a555e771 Rick Pratt
        estat = gdp_gin_open(gobiname, GDP_MODE_AO, shared_gdp_open_info, &gin);
797 e64cc272 Rick Pratt
        EP_STAT_CHECK(estat, goto fail_open);
798 18188fcc Eric Allman
799 a555e771 Rick Pratt
        estat = gdp_gin_append(gin, datum, NULL);
800 e64cc272 Rick Pratt
        EP_STAT_CHECK(estat, goto fail_append);
801 d0a04e62 Eric Allman
802 de9d8389 Rick Pratt
        // success: send a response
803
        j = json_object();
804 18188fcc Eric Allman
805 de9d8389 Rick Pratt
        // set_new steals new json_integer (or auto-decrefs if !j || !key)
806
        json_object_set_new_nocheck(j, "recno",
807 e64cc272 Rick Pratt
                                                                json_integer(gdp_datum_getrecno(datum)));
808 de9d8389 Rick Pratt
        gdp_datum_getts(datum, &ts);
809
        if (EP_TIME_IS_VALID(&ts))
810
        {
811
                char tbuf[100];
812 80c5389d Eric Allman
813 de9d8389 Rick Pratt
                ep_time_format(&ts, tbuf, sizeof tbuf, EP_TIME_FMT_DEFAULT);
814
                // set_new steals new json_string (or auto-decrefs if !j || !key)
815
                json_object_set_new_nocheck(j, "timestamp", json_string(tbuf));
816
        }
817 80c5389d Eric Allman
818 de9d8389 Rick Pratt
        // get it (malloc'ed) in string format (will return NULL if !j)
819
        jbuf = json_dumps(j, JSON_INDENT(4));
820 80c5389d Eric Allman
821 de9d8389 Rick Pratt
        // create the entire SCGI return message
822
        snprintf(rbuf, sizeof rbuf,
823
                         "HTTP/1.1 200 Successfully appended\r\n"
824
                         "Content-Type: application/json\r\n"
825
                         "\r\n"
826
                         "%s\r\n",
827
                         jbuf ? jbuf : "");
828
        write_scgi(req, rbuf);
829
830
        // clean up
831
        json_decref(j);
832
        free(jbuf);
833 e64cc272 Rick Pratt
834
        // caller frees datum
835 d0a04e62 Eric Allman
836 de9d8389 Rick Pratt
fail_append:
837 eae1d3ec Eric Allman
        gdp_gin_close(gin);
838 de9d8389 Rick Pratt
fail_open:
839
        if (!EP_STAT_ISOK(estat))
840 e64cc272 Rick Pratt
        {
841
                char ebuf[200];
842 eae1d3ec Eric Allman
                gdp_pname_t gobpname;
843 e64cc272 Rick Pratt
844 eae1d3ec Eric Allman
                gdp_printable_name(gobiname, gobpname);
845 e64cc272 Rick Pratt
                gdp_failure(req, "420", "Cannot append to GCL", "ss",
846 eae1d3ec Eric Allman
                                "GCL", gobpname,
847 e64cc272 Rick Pratt
                                "error", ep_stat_tostr(estat, ebuf, sizeof ebuf));
848
        }
849 de9d8389 Rick Pratt
850 18188fcc Eric Allman
        return estat;
851
}
852
853
854
/*
855
**        A_READ_DATUM --- read and return a datum from a GCL
856 4e425382 Eric Allman
**
857
**                XXX Currently doesn't use the GCL cache.  To make that work
858
**                        long term we would have to have to implement LRU in that
859
**                        cache (which we probably need to do anyway).
860
*/
861
862 47c6ea64 Eric Allman
EP_STAT
863 eae1d3ec Eric Allman
a_read_datum(scgi_request *req, gdp_name_t gobiname, gdp_recno_t recno)
864 47c6ea64 Eric Allman
{
865 4e425382 Eric Allman
        EP_STAT estat;
866 eae1d3ec Eric Allman
        gdp_gin_t *gin = NULL;
867 9509f13b Eric Allman
        gdp_datum_t *datum = gdp_datum_new();
868 0c663d10 Eric Allman
869 a555e771 Rick Pratt
        estat = gdp_gin_open(gobiname, GDP_MODE_RO, shared_gdp_open_info, &gin);
870 e64cc272 Rick Pratt
        EP_STAT_CHECK(estat, goto fail_open);
871 47c6ea64 Eric Allman
872 a555e771 Rick Pratt
        estat = gdp_gin_read_by_recno(gin, recno, datum);
873 4e425382 Eric Allman
        if (!EP_STAT_ISOK(estat))
874 e64cc272 Rick Pratt
                goto fail_read;
875 efad251c Siqi Lin
876 4e425382 Eric Allman
        // package up the results and send them back
877 174e9f6d Eric Allman
        {
878 4e425382 Eric Allman
                char rbuf[1024];
879 35509bdc Eric Allman
880 4e425382 Eric Allman
                // figure out the response header
881
                {
882
                        FILE *fp;
883 eae1d3ec Eric Allman
                        gdp_pname_t gobpname;
884 c5699a87 Eric Allman
                        EP_TIME_SPEC ts;
885 4e425382 Eric Allman
886 a5a912e2 Eric Allman
                        fp = ep_fopen_smem(rbuf, sizeof rbuf, "w");
887 4e425382 Eric Allman
                        if (fp == NULL)
888
                        {
889 5bc1cdfb Eric Allman
                                char nbuf[40];
890
891 bfecf573 Eric Allman
                                (void) (0 == strerror_r(errno, nbuf, sizeof nbuf));
892 eae1d3ec Eric Allman
                                ep_app_abort("Cannot open memory for GDP read response: %s",
893 5bc1cdfb Eric Allman
                                                nbuf);
894 4e425382 Eric Allman
                        }
895 eae1d3ec Eric Allman
                        gdp_printable_name(gobiname, gobpname);
896 4e425382 Eric Allman
                        fprintf(fp, "HTTP/1.1 200 GCL Message\r\n"
897 f63dbddd Rick Pratt
                                        "Content-Type: application/json\r\n"
898
                                        "GDP-GOB-Name: %s\r\n"
899
                                        "GDP-Record-Number: %" PRIgdp_recno "\r\n",
900
                                        gobpname,
901
                                        gdp_datum_getrecno(datum));
902 c5699a87 Eric Allman
                        gdp_datum_getts(datum, &ts);
903
                        if (EP_TIME_IS_VALID(&ts))
904 4e425382 Eric Allman
                        {
905
                                fprintf(fp, "GDP-Commit-Timestamp: ");
906 c5699a87 Eric Allman
                                ep_time_print(&ts, fp, EP_TIME_FMT_DEFAULT);
907 4e425382 Eric Allman
                                fprintf(fp, "\r\n");
908
                        }
909
                        fprintf(fp, "\r\n");                                // end of header
910
                        fputc('\0', fp);
911
                        fclose(fp);
912
                }
913
914
                // finish up sending the data out --- the extra copy is annoying
915
                {
916
                        size_t rlen = strlen(rbuf);
917 c5699a87 Eric Allman
                        size_t dlen = evbuffer_get_length(gdp_datum_getbuf(datum));
918 4e425382 Eric Allman
                        char obuf[1024];
919
                        char *obp = obuf;
920
921
                        if (rlen + dlen > sizeof obuf)
922
                                obp = ep_mem_malloc(rlen + dlen);
923
924
                        if (obp == NULL)
925 5bc1cdfb Eric Allman
                        {
926
                                char nbuf[40];
927
928 bfecf573 Eric Allman
                                (void) (0 == strerror_r(errno, nbuf, sizeof nbuf));
929 4e425382 Eric Allman
                                ep_app_abort("Cannot allocate memory for GCL read response: %s",
930 5bc1cdfb Eric Allman
                                                nbuf);
931
                        }
932 4e425382 Eric Allman
933
                        memcpy(obp, rbuf, rlen);
934 c5699a87 Eric Allman
                        gdp_buf_read(gdp_datum_getbuf(datum), obp + rlen, dlen);
935 4e425382 Eric Allman
                        scgi_send(req, obp, rlen + dlen);
936
                        if (obp != obuf)
937
                                ep_mem_free(obp);
938
                }
939 47c6ea64 Eric Allman
        }
940 174e9f6d Eric Allman
941 4e425382 Eric Allman
        // finished
942 9509f13b Eric Allman
        gdp_datum_free(datum);
943 eae1d3ec Eric Allman
        gdp_gin_close(gin);
944 4e425382 Eric Allman
        return estat;
945 174e9f6d Eric Allman
946 e64cc272 Rick Pratt
 fail_read:
947 eae1d3ec Eric Allman
        gdp_gin_close(gin);
948 e64cc272 Rick Pratt
 fail_open:
949
        gdp_datum_free(datum);
950 4e425382 Eric Allman
        {
951
                char ebuf[200];
952 eae1d3ec Eric Allman
                gdp_pname_t gobpname;
953 4e425382 Eric Allman
954 eae1d3ec Eric Allman
                gdp_printable_name(gobiname, gobpname);
955 a1a8f4f7 Eric Allman
                gdp_failure(req, "404", "Cannot read GCL", "ss",
956 eae1d3ec Eric Allman
                                "GOB", gobpname,
957 4e425382 Eric Allman
                                "reason", ep_stat_tostr(estat, ebuf, sizeof ebuf));
958
        }
959
        return estat;
960 47c6ea64 Eric Allman
}
961
962 174e9f6d Eric Allman
963 18188fcc Eric Allman
/*
964 a555e771 Rick Pratt
**  GOB_DO_GET --- helper routine for GET method on a GDP Object
965 18188fcc Eric Allman
**
966
**                Have to look at query to figure out the semantics.
967
**                The query's the thing / wherein I'll catch the
968
**                conscience of the king.
969
*/
970
971
EP_STAT
972 a555e771 Rick Pratt
gob_do_get(scgi_request *req, gdp_name_t gobiname, struct qkvpair *qkvs)
973 47c6ea64 Eric Allman
{
974 18188fcc Eric Allman
        EP_STAT estat;
975
        char *qrecno = find_query_kv("recno", qkvs);
976
        char *qnrecs = find_query_kv("nrecs", qkvs);
977
        char *qtimeout = find_query_kv("timeout", qkvs);
978
979
        if (qnrecs != NULL)
980 4e425382 Eric Allman
        {
981 18188fcc Eric Allman
                // not yet implemented
982
                estat = error501(req, "nrecs query not supported");
983
        }
984
        else if (qtimeout != NULL)
985
        {
986
                // not yet implemented
987
                estat = error501(req, "timeout query not supported");
988
        }
989
        else if (qrecno != NULL)
990
        {
991 591ae6e9 Eric Allman
                gdp_recno_t recno;
992
993
                if (strcmp(qrecno, "last") == 0)
994
                        recno = -1;
995
                else
996
                        recno = atol(qrecno);
997 eae1d3ec Eric Allman
                estat = a_read_datum(req, gobiname, recno);
998 18188fcc Eric Allman
        }
999
        else
1000
        {
1001 a555e771 Rick Pratt
                estat = a_show_gob(req, gobiname);
1002 18188fcc Eric Allman
        }
1003
1004
        return estat;
1005
}
1006
1007
1008
/*
1009
**  PFX_GCL --- process SCGI requests starting with /gdp/v1/gcl
1010
*/
1011
1012
#define NQUERY_KVS                10                // max number of key-value pairs in query part
1013
1014
EP_STAT
1015
pfx_gcl(scgi_request *req, char *uri)
1016
{
1017
        EP_STAT estat;
1018
        struct qkvpair qkvs[NQUERY_KVS + 1];
1019
1020
        if (*uri == '/')
1021
                uri++;
1022
1023
        ep_dbg_cprintf(Dbg, 3, "    gcl=%s\n    query=%s\n", uri, req->query_string);
1024
1025
        // parse the query, if it exists
1026
        {
1027
                int i = parse_query(req->query_string, qkvs, NQUERY_KVS);
1028
                qkvs[i].key = qkvs[i].val = NULL;
1029
        }
1030
1031
        if (*uri == '\0')
1032
        {
1033 04ad7859 Rick Pratt
                // URI does not include a GCL name, implies GDP scoped operations
1034 18188fcc Eric Allman
                switch (req->request_method)
1035
                {
1036
                case SCGI_METHOD_POST:
1037 04ad7859 Rick Pratt
                        // fall-through
1038
                case SCGI_METHOD_PUT:
1039 a555e771 Rick Pratt
                        // create a new GDP Object
1040
                        estat = a_new_gob(req);
1041 18188fcc Eric Allman
                        break;
1042
1043
                case SCGI_METHOD_GET:
1044
                        // XXX if no GCL name, should we print all GCLs?
1045 0b88875d Eric Allman
                        estat = error404(req, "listing GCLs not implemented (yet)");
1046
                        break;
1047
1048 2ffaa038 Rick Pratt
                case SCGI_METHOD_OPTIONS:
1049
                        estat = cors200(req);
1050
                        break;
1051
1052 18188fcc Eric Allman
                default:
1053
                        // unknown URI/method
1054 04ad7859 Rick Pratt
                        estat = error405(req,
1055
                                                         "only GET, POST, or PUT supported in GDP scope");
1056 18188fcc Eric Allman
                        break;
1057
                }
1058
        }
1059
        else
1060
        {
1061 549b367d Eric Allman
                gdp_name_t gcliname;
1062 18188fcc Eric Allman
1063
                // next component is the GCL id (name) in external format
1064 549b367d Eric Allman
                gdp_parse_name(uri, gcliname);
1065 18188fcc Eric Allman
1066 04ad7859 Rick Pratt
                // URI includes a GCL name, implies GCL scoped operations
1067 18188fcc Eric Allman
                switch (req->request_method)
1068
                {
1069
                case SCGI_METHOD_GET:
1070 a555e771 Rick Pratt
                        estat = gob_do_get(req, gcliname, qkvs);
1071 18188fcc Eric Allman
                        break;
1072
1073
                case SCGI_METHOD_POST:
1074
                        // append value to GCL
1075 80d9fdd5 Eric Allman
                        {
1076
                                gdp_datum_t *datum = gdp_datum_new();
1077
1078 04ad7859 Rick Pratt
                                gdp_buf_write(gdp_datum_getbuf(datum), req->body,
1079
                                                          req->scgi_content_length);
1080 05e1cb19 Eric Allman
                                estat = a_append(req, gcliname, datum);
1081 80d9fdd5 Eric Allman
                                gdp_datum_free(datum);
1082
                        }
1083 18188fcc Eric Allman
                        break;
1084
1085 2ffaa038 Rick Pratt
                case SCGI_METHOD_OPTIONS:
1086
                        estat = cors200(req);
1087
                        break;
1088
1089 18188fcc Eric Allman
                default:
1090
                        // unknown URI/method
1091 04ad7859 Rick Pratt
                        estat = error405(req, "only GET or POST supported in GCL scope");
1092 18188fcc Eric Allman
                }
1093
        }
1094
1095
        return estat;
1096 47c6ea64 Eric Allman
}
1097
1098 174e9f6d Eric Allman
1099 18188fcc Eric Allman
/*
1100
**  PFX_POST --- process SCGI requests starting with /gdp/v1/post
1101
*/
1102
1103
EP_STAT
1104
pfx_post(scgi_request *req, char *uri)
1105
{
1106
        if (*uri == '/')
1107
                uri++;
1108
1109
        return error501(req, "/post/... URIs not yet supported");
1110
}
1111
1112 174e9f6d Eric Allman
1113 939e1aee Eric Allman
1114
/**********************************************************************
1115
**
1116
**  KEY-VALUE STORE
1117
**
1118
**                This implementation is very sloppy right now.
1119
**
1120
**********************************************************************/
1121
1122
1123 80d9fdd5 Eric Allman
json_t                        *KeyValStore = NULL;
1124 a555e771 Rick Pratt
gdp_gin_t                *KeyValGcl = NULL;
1125 80d9fdd5 Eric Allman
const char                *KeyValStoreName;
1126 549b367d Eric Allman
gdp_name_t                KeyValInternalName;
1127 939e1aee Eric Allman
1128
1129 80d9fdd5 Eric Allman
EP_STAT
1130 939e1aee Eric Allman
insert_datum(gdp_datum_t *datum)
1131
{
1132
        if (datum == NULL)
1133 80d9fdd5 Eric Allman
                return GDP_STAT_INTERNAL_ERROR;
1134 939e1aee Eric Allman
1135
        gdp_buf_t *buf = gdp_datum_getbuf(datum);
1136
        size_t len = gdp_buf_getlength(buf);
1137
        unsigned char *p = gdp_buf_getptr(buf, len);
1138
1139 0f868b09 Eric Allman
        json_t *j = json_loadb((char *) p, len, 0, NULL);
1140 939e1aee Eric Allman
        if (!json_is_object(j))
1141 80d9fdd5 Eric Allman
                return GDP_STAT_MSGFMT;
1142 939e1aee Eric Allman
1143
        json_object_update(KeyValStore, j);
1144
        json_decref(j);
1145 80d9fdd5 Eric Allman
        return EP_STAT_OK;
1146 939e1aee Eric Allman
}
1147
1148
1149
EP_STAT
1150
kv_initialize(void)
1151
{
1152
        EP_STAT estat;
1153
        gdp_event_t *gev;
1154
1155 80d9fdd5 Eric Allman
        if (KeyValStore != NULL)
1156
                return EP_STAT_OK;
1157
1158 939e1aee Eric Allman
        // get space for our internal database
1159
        KeyValStore = json_object();
1160
1161 a29668f9 Eric Allman
        KeyValStoreName = ep_adm_getstrparam("swarm.rest.kvstore.gclname",
1162
                                                        "swarm.rest.kvstore.gclname");
1163 80d9fdd5 Eric Allman
1164 939e1aee Eric Allman
        // open the "KeyVal" GCL
1165 549b367d Eric Allman
        gdp_parse_name(KeyValStoreName, KeyValInternalName);
1166 a555e771 Rick Pratt
        estat = gdp_gin_open(KeyValInternalName, GDP_MODE_AO, NULL, &KeyValGcl);
1167 939e1aee Eric Allman
        EP_STAT_CHECK(estat, goto fail0);
1168
1169
        // read all the data
1170 a555e771 Rick Pratt
        estat = gdp_gin_read_by_recno_async(KeyValGcl, 1, 0, NULL, NULL);
1171 939e1aee Eric Allman
        EP_STAT_CHECK(estat, goto fail1);
1172 55b8c1ad Eric Allman
        while ((gev = gdp_event_next(KeyValGcl, 0)) != NULL)
1173 939e1aee Eric Allman
        {
1174 5ced6d34 Eric Allman
                if (gdp_event_gettype(gev) == GDP_EVENT_DONE)
1175 939e1aee Eric Allman
                {
1176
                        // end of multiread --- we have it all
1177
                        gdp_event_free(gev);
1178
                        break;
1179
                }
1180
                else if (gdp_event_gettype(gev) == GDP_EVENT_DATA)
1181
                {
1182
                        // update the current stat of the key-value store
1183 80d9fdd5 Eric Allman
                        estat = insert_datum(gdp_event_getdatum(gev));
1184 939e1aee Eric Allman
                }
1185
                gdp_event_free(gev);
1186
        }
1187
1188
        // now start up the subscription (will be read in main loop)
1189 a555e771 Rick Pratt
        estat = gdp_gin_subscribe_by_recno(KeyValGcl, 0, 0, NULL, NULL, NULL);
1190 939e1aee Eric Allman
        goto done;
1191
1192
fail0:
1193
        // couldn't open; try create?
1194 3e05a791 Eric Allman
        //estat = gdp_gcl_create(KeyValInternalName, NULL, &KeyValGcl);
1195 939e1aee Eric Allman
1196
fail1:
1197
        // couldn't read GCL
1198
1199
done:
1200
        return estat;
1201
}
1202
1203
1204
EP_STAT
1205
pfx_kv(scgi_request *req, char *uri)
1206
{
1207
        EP_STAT estat;
1208
1209
        if (*uri == '/')
1210
                uri++;
1211
1212
        if (KeyValStore == NULL)
1213
        {
1214
                estat = kv_initialize();
1215
                EP_STAT_CHECK(estat, goto fail0);
1216
        }
1217
1218
        if (req->request_method == SCGI_METHOD_POST)
1219
        {
1220 80d9fdd5 Eric Allman
                // get the datum out of the SCGI request
1221 939e1aee Eric Allman
                gdp_datum_t *datum = gdp_datum_new();
1222 c5699a87 Eric Allman
                gdp_buf_write(gdp_datum_getbuf(datum), req->body, req->scgi_content_length);
1223 80d9fdd5 Eric Allman
1224
                // try to merge it into the in-memory representation
1225
                estat = insert_datum(datum);
1226
1227
                // if that succeeded, append the record to the GCL
1228
                if (EP_STAT_ISOK(estat))
1229 05e1cb19 Eric Allman
                        a_append(req, KeyValInternalName, datum);
1230 80d9fdd5 Eric Allman
1231
                // don't forget to mop up!
1232 939e1aee Eric Allman
                gdp_datum_free(datum);
1233
        }
1234
        else if (req->request_method == SCGI_METHOD_GET)
1235
        {
1236
                FILE *fp;
1237
                char rbuf[2000];
1238
1239 a5a912e2 Eric Allman
                fp = ep_fopen_smem(rbuf, sizeof rbuf, "w");
1240 939e1aee Eric Allman
                if (fp == NULL)
1241
                {
1242
                        char nbuf[40];
1243
1244 bfecf573 Eric Allman
                        (void) (0 == strerror_r(errno, nbuf, sizeof nbuf));
1245 939e1aee Eric Allman
                        ep_app_abort("Cannot open memory for GCL read response: %s",
1246
                                        nbuf);
1247
                }
1248
1249
                json_t *j0 = json_object_get(KeyValStore, uri);
1250
                if (j0 == NULL)
1251
                        return error404(req, "No such key");
1252
1253
                json_t *j1 = json_object();
1254
                json_object_set(j1, uri, j0);
1255
                fprintf(fp, "HTTP/1.1 200 Data\r\n"
1256
                                        "Content-Type: application/json\r\n"
1257
                                        "\r\n"
1258
                                        "%s\r\n",
1259
                                        json_dumps(j1, 0));
1260
                fputc('\0', fp);
1261
                fclose(fp);
1262
                json_decref(j1);
1263
1264
                scgi_send(req, rbuf, strlen(rbuf));
1265
        }
1266
        else
1267
        {
1268
                return error405(req, "unknown method");
1269
        }
1270
1271
        return EP_STAT_OK;
1272
1273
fail0:
1274
        return error500(req, "Couldn't initialize Key-Value store", errno);
1275
}
1276
1277
1278
void
1279
process_event(gdp_event_t *gev)
1280
{
1281
        if (gdp_event_gettype(gev) != GDP_EVENT_DATA)
1282
                return;
1283
1284
        if (KeyValStore == NULL)
1285
                return;
1286
1287
        // for the time being, assume it comes from the correct GCL
1288
        insert_datum(gdp_event_getdatum(gev));
1289
}
1290
1291
1292
/**********************************************************************/
1293
1294 47c6ea64 Eric Allman
/*
1295 4e425382 Eric Allman
**        PROCESS_SCGI_REQ --- process an already-read SCGI request
1296 174e9f6d Eric Allman
**
1297 4e425382 Eric Allman
**                This is generally called as an event
1298 174e9f6d Eric Allman
*/
1299 47c6ea64 Eric Allman
1300
EP_STAT
1301 1b267ec1 Eric Allman
process_scgi_req(scgi_request *req)
1302 47c6ea64 Eric Allman
{
1303 4e425382 Eric Allman
        char *uri;                                // the URI of the request
1304
        EP_STAT estat = EP_STAT_OK;
1305
1306
        if (ep_dbg_test(Dbg, 3))
1307 47c6ea64 Eric Allman
        {
1308 4e425382 Eric Allman
                ep_dbg_printf("Got connection on port %d from %s:\n",
1309
                                req->descriptor->port->port, req->remote_addr);
1310
                ep_dbg_printf("           %s %s\n", scgi_method_name(req->request_method),
1311
                                req->request_uri);
1312 47c6ea64 Eric Allman
        }
1313 4e425382 Eric Allman
1314 18188fcc Eric Allman
        // strip query string off of URI (I'm surprised it's not already done)
1315
        uri = strchr(req->request_uri, '?');
1316
        if (uri != NULL)
1317
                *uri = '\0';
1318
1319
        // strip off leading "/gdp/v1/" prefix (error if not there)
1320 4e425382 Eric Allman
        if (GclUriPrefix == NULL)
1321 126f8146 Eric Allman
                GclUriPrefix = ep_adm_getstrparam("swarm.rest.prefix", DEF_URI_PREFIX);
1322 4e425382 Eric Allman
        uri = req->request_uri;
1323
        if (strncmp(uri, GclUriPrefix, strlen(GclUriPrefix)) != 0)
1324 18188fcc Eric Allman
        {
1325
                estat = error404(req, "improper URI prefix");
1326
                goto finis;
1327
        }
1328 4e425382 Eric Allman
        uri += strlen(GclUriPrefix);
1329
        if (*uri == '/')
1330
                uri++;
1331
1332 18188fcc Eric Allman
        // next component is "gcl" for RESTful or "post" for RESTish
1333
        if (strncmp(uri, "gcl", 3) == 0 && (uri[3] == '/' || uri[3] == '\0'))
1334 47c6ea64 Eric Allman
        {
1335 18188fcc Eric Allman
                // looking at "/gdp/v1/gcl/" prefix; next component is the GCL name
1336
                estat = pfx_gcl(req, uri + 3);
1337 47c6ea64 Eric Allman
        }
1338 18188fcc Eric Allman
        else if (strncmp(uri, "post", 4) == 0 && (uri[4] == '/' || uri[4] == '\0'))
1339 4e425382 Eric Allman
        {
1340 18188fcc Eric Allman
                // looking at "/gdp/v1/post" prefix
1341
                estat = pfx_post(req, uri + 4);
1342 4e425382 Eric Allman
        }
1343 939e1aee Eric Allman
        else if (strncmp(uri, "kv", 2) == 0 && (uri[2] == '/' || uri[4] == '\0'))
1344
        {
1345
                // looking at "/gdp/v1/kv" prefix
1346
                estat = pfx_kv(req, uri + 2);
1347
        }
1348 4e425382 Eric Allman
        else
1349
        {
1350 18188fcc Eric Allman
                // looking at "/gdp/v1/<unknown>" prefix
1351
                estat = error404(req, "unknown resource");
1352 4e425382 Eric Allman
        }
1353 174e9f6d Eric Allman
1354 18188fcc Eric Allman
finis:
1355 4e425382 Eric Allman
        return estat;
1356 47c6ea64 Eric Allman
}
1357
1358
1359
int
1360
main(int argc, char **argv, char **env)
1361
{
1362 4e425382 Eric Allman
        int opt;
1363
        int listenport = -1;
1364 1b267ec1 Eric Allman
        int64_t poll_delay;
1365 5cd53c1a Eric Allman
        char *gdpd_addr = NULL;
1366 591ae6e9 Eric Allman
        bool show_usage = false;
1367 4e425382 Eric Allman
        extern void run_scgi_protocol(void);
1368
1369 04ad7859 Rick Pratt
        while ((opt = getopt(argc, argv, "D:G:p:u:C:")) > 0)
1370 47c6ea64 Eric Allman
        {
1371 4e425382 Eric Allman
                switch (opt)
1372
                {
1373
                case 'D':                                                // turn on debugging
1374
                        ep_dbg_set(optarg);
1375
                        break;
1376 47c6ea64 Eric Allman
1377 5cd53c1a Eric Allman
                case 'G':                                                // gdp daemon host:port
1378
                        gdpd_addr = optarg;
1379
                        break;
1380
1381 4e425382 Eric Allman
                case 'p':                                                // select listen port
1382
                        listenport = atoi(optarg);
1383
                        break;
1384 47c6ea64 Eric Allman
1385 4e425382 Eric Allman
                case 'u':                                                // URI prefix
1386
                        GclUriPrefix = optarg;
1387
                        break;
1388 591ae6e9 Eric Allman
1389 04ad7859 Rick Pratt
                case 'C':                                                // (development) gcl-create alternative
1390 a555e771 Rick Pratt
                        exec_gdp_create = optarg;
1391 04ad7859 Rick Pratt
                        break;
1392
1393 591ae6e9 Eric Allman
                default:
1394
                        show_usage = true;
1395
                        break;
1396 4e425382 Eric Allman
                }
1397 47c6ea64 Eric Allman
        }
1398 4e425382 Eric Allman
        argc -= optind;
1399
        argv += optind;
1400 47c6ea64 Eric Allman
1401 591ae6e9 Eric Allman
        if (show_usage || argc > 0)
1402
        {
1403
                fprintf(stderr,
1404 04ad7859 Rick Pratt
                                "Usage: %s [-D dbgspec] [-G host:port] [-p port] [-u prefix] "
1405
                                "[-C gcl-create]\n", ep_app_getprogname());
1406 591ae6e9 Eric Allman
                exit(EX_USAGE);
1407
        }
1408
1409 4e425382 Eric Allman
        if (listenport < 0)
1410 cc7db91c Eric Allman
                listenport = ep_adm_getintparam("swarm.rest.scgi.port", 8001);
1411 47c6ea64 Eric Allman
1412 4e425382 Eric Allman
        // Initialize the GDP library
1413 1b267ec1 Eric Allman
        //                Also initializes the EVENT library and starts the I/O thread
1414 f7d9ac86 Rick Pratt
        // Initialize shared gcl open info with caching enabled
1415 4e425382 Eric Allman
        {
1416 5cd53c1a Eric Allman
                EP_STAT estat = gdp_init(gdpd_addr);
1417 4e425382 Eric Allman
                char ebuf[100];
1418 47c6ea64 Eric Allman
1419 4e425382 Eric Allman
                if (!EP_STAT_ISOK(estat))
1420
                {
1421 8404f1db Rick Pratt
                        // DNS lookup failure is usually an external network
1422
                        // issue, so exit with an errorcode and let the system
1423
                        // control restart attempts
1424
                        if (EP_STAT_IS_SAME(estat, EP_STAT_DNS_FAILURE))
1425
                        {
1426
                                char nbuf[40];
1427
1428 bfecf573 Eric Allman
                                (void) (0 == strerror_r(errno, nbuf, sizeof nbuf));
1429 8404f1db Rick Pratt
                                ep_app_error("Cannot initialize gdp library: %s", nbuf);
1430
                                return EX_TEMPFAIL;
1431
                        }
1432
1433 4e425382 Eric Allman
                        ep_app_abort("Cannot initialize gdp library: %s",
1434
                                        ep_stat_tostr(estat, ebuf, sizeof ebuf));
1435
                }
1436
1437 a555e771 Rick Pratt
                shared_gdp_open_info = gdp_open_info_new();
1438
                estat = gdp_open_info_set_caching(shared_gdp_open_info, true);
1439 f7d9ac86 Rick Pratt
1440
                if (!EP_STAT_ISOK(estat))
1441
                {
1442
                        ep_app_abort("Cannot initialize gdp gcl cache preference: %s",
1443
                                        ep_stat_tostr(estat, ebuf, sizeof ebuf));
1444
                }
1445
        }
1446 d0a04e62 Eric Allman
1447 0b88875d Eric Allman
        ep_dbg_cprintf(Dbg, 9, "GDP initialized\n");
1448
1449 4e425382 Eric Allman
        // Initialize SCGI library
1450 1d79b58a Eric Allman
        scgi_debug = ep_dbg_level(Dbg) / 10;
1451 4e425382 Eric Allman
        if (scgi_initialize(listenport))
1452 efad251c Siqi Lin
        {
1453 1d79b58a Eric Allman
                ep_dbg_cprintf(Dbg, 1,
1454
                                "%s: listening for SCGI on port %d, scgi_debug %d\n",
1455
                                ep_app_getprogname(), listenport, scgi_debug);
1456 4e425382 Eric Allman
        }
1457
        else
1458
        {
1459 5bc1cdfb Eric Allman
                char nbuf[40];
1460
1461 bfecf573 Eric Allman
                (void) (0 == strerror_r(errno, nbuf, sizeof nbuf));
1462 4e425382 Eric Allman
                ep_app_error("could not initialize SCGI port %d: %s",
1463 5bc1cdfb Eric Allman
                                listenport, nbuf);
1464 4e425382 Eric Allman
                return EX_OSERR;
1465 47c6ea64 Eric Allman
        }
1466 63b801e4 Eric Allman
1467 4e425382 Eric Allman
        // start looking for SCGI connections
1468
        //        XXX This should really be done through the event library
1469
        //                rather than by polling.         To do this right there should
1470
        //                be a pool of worker threads that would have the SCGI
1471
        //                connection handed off to them.
1472
        //        XXX May be able to cheat by changing the select() in
1473
        //                scgi_update_connections_port to wait.  It's OK if this
1474
        //                thread hangs since the other work happens in a different
1475
        //                thread.
1476 f34c3e4e Rick Pratt
        //
1477
        // originally 100000 x 1000LL gave > 200ms response time for EMG demo tests
1478
        // reducing the poll to 1000 x 1000LL gave 110.83 ms average responses
1479
        // reducing the poll to 10 x 1000LL improve minimum but widen standard dev.
1480
        // dialed back the poll to 100 x 1000LL for the 2017 EC Annual Review
1481
        // Ali/Andy sent a video showing EMG control of the waffle robot with
1482
        // this setting -- too cool! -- commit the new setting to the repo!
1483
        poll_delay = ep_adm_getlongparam("swarm.rest.scgi.pollinterval", 100);
1484 a0672b22 Eric Allman
        ep_sd_notifyf("READY=1\n");
1485 4e425382 Eric Allman
        for (;;)
1486 63b801e4 Eric Allman
        {
1487 939e1aee Eric Allman
                gdp_event_t *gev;
1488 1d79b58a Eric Allman
                EP_TIME_SPEC to = { 0, 0, 0.0 };
1489 939e1aee Eric Allman
1490 1d79b58a Eric Allman
                while ((gev = gdp_event_next(NULL, &to)) != NULL)
1491 939e1aee Eric Allman
                {
1492
                        process_event(gev);
1493
                        gdp_event_free(gev);
1494
                }
1495
1496 1b267ec1 Eric Allman
                scgi_request *req = gdp_scgi_recv();
1497 4e425382 Eric Allman
                int dead = 0;
1498
1499
                if (req == NULL)
1500
                {
1501
                        (void) ep_time_nanosleep(poll_delay * 1000LL);
1502
                        continue;
1503
                }
1504
                req->dead = &dead;
1505 1b267ec1 Eric Allman
                process_scgi_req(req);
1506 63b801e4 Eric Allman
        }
1507 47c6ea64 Eric Allman
}