adamc@1268: #include "config.h" adamc@1268: adamc@859: #include adamc@859: #include adamc@859: #include adamc@859: #include adamc@859: #include adamc@859: #include adamc@859: #include adamc@859: #include adamc@859: #include adamc@859: #include adamc@1094: #include adamc@859: adamc@859: #include adamc@859: adamc@859: #include "urweb.h" adamc@859: #include "request.h" adamc@859: #include "queue.h" adamc@859: adamc@859: #include "fastcgi.h" adamc@859: adamc@1094: extern uw_app uw_application; adamc@1094: adamc@859: typedef struct { adamc@859: unsigned char version; adamc@859: unsigned char type; adamc@859: unsigned char requestIdB1; adamc@859: unsigned char requestIdB0; adamc@859: unsigned char contentLengthB1; adamc@859: unsigned char contentLengthB0; adamc@859: unsigned char paddingLength; adamc@859: unsigned char reserved; adamc@859: unsigned char contentData[65535]; adamc@859: } FCGI_Record; adamc@859: adamc@859: typedef struct { adamc@859: FCGI_Record r; adamc@859: int sock; adamc@859: } FCGI_Output; adamc@859: adamc@859: typedef struct { adamc@859: char buf[sizeof(FCGI_Record) + 255]; adamc@859: int available, used, sock; adamc@859: } FCGI_Input; adamc@859: adamc@859: static FCGI_Output *fastcgi_output() { adamc@859: FCGI_Output *o = malloc(sizeof(FCGI_Output)); adamc@859: adamc@859: o->r.version = FCGI_VERSION_1; adamc@859: o->r.paddingLength = 0; adamc@859: o->r.reserved = 0; adamc@859: adamc@859: return o; adamc@859: } adamc@859: adamc@859: static FCGI_Input *fastcgi_input() { adamc@859: FCGI_Input *i = malloc(sizeof(FCGI_Input)); adamc@859: adamc@859: i->available = i->used = 0; adamc@859: adamc@859: return i; adamc@859: } adamc@859: adamc@859: static void fastcgi_input_reset(FCGI_Input *i) { adamc@859: i->available = i->used = 0; adamc@859: } adamc@859: adamc@859: static int fastcgi_send(FCGI_Output *o, adamc@859: unsigned char type, adamc@859: unsigned short contentLength) { adamc@859: o->r.type = type; adamc@860: o->r.requestIdB1 = o->r.requestIdB0 = 0; adamc@859: o->r.contentLengthB1 = contentLength >> 8; adamc@859: o->r.contentLengthB0 = contentLength & 255; adamc@861: return uw_really_send(o->sock, &o->r, sizeof(o->r) - 65535 + contentLength); adamc@859: } adamc@859: adamc@859: #define LATEST(i) ((FCGI_Record *)(i->buf + i->used)) adamc@859: adamc@859: static FCGI_Record *fastcgi_recv(FCGI_Input *i) { adamc@859: while (1) { adamc@859: ssize_t n; adamc@859: adamc@859: if (i->available >= i->used + sizeof(FCGI_Record) - 65535 adamc@859: && i->available >= i->used + sizeof(FCGI_Record) - 65535 adamc@860: + ((LATEST(i)->contentLengthB1 << 8) | LATEST(i)->contentLengthB0) adamc@859: + LATEST(i)->paddingLength) { adamc@859: FCGI_Record *r = LATEST(i); adamc@859: adamc@859: i->used += sizeof(FCGI_Record) - 65535 adamc@860: + ((LATEST(i)->contentLengthB1 << 8) | LATEST(i)->contentLengthB0) adamc@859: + LATEST(i)->paddingLength; adamc@859: adamc@859: return r; adamc@859: } adamc@859: adamc@859: if (i->used > 0) { adamc@859: memmove(i->buf, i->buf + i->used, i->available - i->used); adamc@859: i->available -= i->used; adamc@859: i->used = 0; adamc@859: } adamc@859: adamc@859: n = recv(i->sock, i->buf + i->available, sizeof(i->buf) - i->available, 0); adamc@859: adamc@859: if (n <= 0) adamc@859: return NULL; adamc@859: adamc@859: i->available += n; adamc@859: } adamc@859: } adamc@859: adamc@859: static void on_success(uw_context ctx) { } adamc@859: adamc@859: static void on_failure(uw_context ctx) { adamc@859: uw_write_header(ctx, "Status: 500 Internal Server Error\r\n"); adamc@859: } adamc@859: adamc@863: static int write_stdout(void *data, const char *buf, size_t len) { adamc@860: FCGI_Output *o = (FCGI_Output *)data; adamc@860: while (len > 0) { adamc@860: size_t len2 = len; adamc@860: if (len2 > 65535) adamc@860: len2 = 65535; adamc@860: memcpy(o->r.contentData, buf, len2); adamc@860: if (fastcgi_send(o, FCGI_STDOUT, len2)) { adamc@860: fprintf(stderr, "fastcgi_send() failed in write_stdout().\n"); adamc@860: return -1; adamc@860: } adamc@860: buf += len2; adamc@860: len -= len2; adamc@860: } adamc@860: adamc@860: return 0; adamc@860: } adamc@860: adam@1411: #include adam@1411: adamc@859: static void write_stderr(FCGI_Output *o, const char *fmt, ...) { adamc@859: int len; adamc@859: va_list ap; adamc@859: va_start(ap, fmt); adamc@859: adamc@1134: len = vsnprintf((char *)o->r.contentData, 65535, fmt, ap); adamc@859: if (len < 0) adamc@860: fprintf(stderr, "vsnprintf() failed in write_stderr().\n"); adamc@860: else if (fastcgi_send(o, FCGI_STDERR, len)) adamc@860: fprintf(stderr, "fastcgi_send() failed in write_stderr().\n"); adamc@860: } adamc@860: adamc@860: static void close_stream(FCGI_Output *o, unsigned char type) { adamc@860: if (fastcgi_send(o, type, 0)) adamc@860: fprintf(stderr, "fastcgi_send() failed in close_stream().\n"); adamc@859: } adamc@859: adamc@859: static void log_error(void *data, const char *fmt, ...) { adamc@859: FCGI_Output *o = (FCGI_Output *)data; adamc@859: va_list ap; adamc@859: va_start(ap, fmt); adamc@859: adamc@859: if (o) { adamc@1134: int len = vsnprintf((char *)o->r.contentData, 65535, fmt, ap); adamc@859: if (len < 0) adamc@859: fprintf(stderr, "vsnprintf() failed in log_error().\n"); adamc@860: else if (fastcgi_send(o, FCGI_STDERR, len)) adamc@859: fprintf(stderr, "fastcgi_send() failed in log_error().\n"); adamc@859: } else adamc@859: vfprintf(stderr, fmt, ap); adamc@859: } adamc@859: adamc@859: static void log_debug(void *data, const char *fmt, ...) { adam@1411: FCGI_Output *o = (FCGI_Output *)data; adam@1411: va_list ap; adam@1411: va_start(ap, fmt); adam@1411: adam@1411: if (o) { adam@1411: strcpy((char *)o->r.contentData, "DEBUG: "); adam@1411: int len = vsnprintf((char *)o->r.contentData + 7, 65535 - 7, fmt, ap); adam@1411: if (len < 0) adam@1411: fprintf(stderr, "vsnprintf() failed in log_debug().\n"); adam@1411: else if (fastcgi_send(o, FCGI_STDERR, len + 7)) { adam@1411: len += 7; adam@1411: if (len >= 65535) len = 65534; adam@1411: o->r.contentData[len] = 0; adam@1411: fputs((char *)o->r.contentData, stderr); adam@1411: fflush(stderr); adam@1411: } adam@1411: } else adam@1411: vfprintf(stderr, fmt, ap); adamc@859: } adamc@859: adamc@860: typedef struct { adamc@860: char *name, *value; adamc@860: unsigned name_len, value_len; adamc@860: } nvp; adamc@860: adamc@860: static char *search_nvps(nvp *nvps, const char *h) { adamc@862: for (; nvps->name[0]; ++nvps) adamc@860: if (!strcmp(h, nvps->name)) adamc@860: return nvps->value; adamc@860: adamc@860: return NULL; adamc@860: } adamc@860: adamc@860: typedef struct { adamc@860: nvp *nvps; adamc@860: char *uppercased; adamc@860: int n_nvps, uppercased_len; adamc@860: } headers; adamc@860: adamc@860: static char *get_header(void *data, const char *h) { adamc@860: headers *hs = (headers *)data; adamc@860: size_t len = strlen(h); adamc@1134: char *s; adamc@860: const char *saved_h = h; adamc@860: adamc@860: if (len > hs->uppercased_len) { adamc@860: hs->uppercased_len = len; adamc@860: hs->uppercased = realloc(hs->uppercased, len + 6); adamc@860: } adamc@860: adamc@860: strcpy(hs->uppercased, "HTTP_"); adamc@860: for (s = hs->uppercased+5; *h; ++h) adam@1435: *s++ = *h == '-' ? '_' : toupper((int)*h); adamc@860: *s = 0; adamc@860: adamc@860: if (!strcasecmp(saved_h, "Content-length") adamc@864: || !strcasecmp(saved_h, "Content-type")) { adamc@1134: if ((s = search_nvps(hs->nvps, hs->uppercased + 5))) adamc@864: return s; adamc@864: } adamc@864: adamc@864: return search_nvps(hs->nvps, hs->uppercased); adamc@860: } adamc@860: adamc@864: static int read_funny_len(unsigned char **buf, int *len) { adamc@861: if (*len <= 0) adamc@861: return -1; adamc@861: adamc@861: if ((*buf)[0] >> 7 == 0) { adamc@861: int r = (*buf)[0]; adamc@861: ++*buf; adamc@861: --*len; adamc@861: return r; adamc@861: } adamc@861: else if (*len < 4) adamc@861: return -1; adamc@861: else { adamc@1049: int r = (((*buf)[0] & 0x7f) << 24) + ((*buf)[1] << 16) + ((*buf)[2] << 8) + (*buf)[3]; adamc@861: *buf += 4; adamc@861: *len -= 4; adamc@861: return r; adamc@861: } adamc@861: } adamc@861: adamc@864: static int read_nvp(unsigned char **buf, int len, nvp *nv) { adamc@860: int nameLength, valueLength; adamc@860: adamc@864: if ((nameLength = read_funny_len(buf, &len)) < 0) adamc@860: return -1; adamc@864: if ((valueLength = read_funny_len(buf, &len)) < 0) adamc@1049: return -2; adamc@860: if (len < nameLength + valueLength) adamc@1049: return -3; adamc@860: adamc@862: if (nameLength+1 > nv->name_len) { adamc@860: nv->name_len = nameLength+1; adamc@862: nv->name = realloc(nv->name, nv->name_len); adamc@860: } adamc@862: if (valueLength+1 > nv->value_len) { adamc@860: nv->value_len = valueLength+1; adamc@862: nv->value = realloc(nv->value, nv->value_len); adamc@860: } adamc@860: adamc@864: memcpy(nv->name, *buf, nameLength); adamc@860: nv->name[nameLength] = 0; adamc@860: adamc@864: memcpy(nv->value, *buf + nameLength, valueLength); adamc@860: nv->value[valueLength] = 0; adamc@860: adamc@864: *buf += nameLength + valueLength; adamc@864: adamc@860: return 0; adamc@860: } adamc@860: adamc@863: static int fastcgi_close_with(FCGI_Output *out, request_result rr) { adamc@863: FCGI_EndRequestBody *erb = (FCGI_EndRequestBody *)out->r.contentData; adamc@863: adamc@863: close_stream(out, FCGI_STDOUT); adamc@863: close_stream(out, FCGI_STDERR); adamc@863: adamc@863: if (rr == SERVED) adamc@863: erb->appStatusB3 = erb->appStatusB2 = erb->appStatusB1 = erb->appStatusB0 = 0; adamc@863: else adamc@863: erb->appStatusB3 = erb->appStatusB2 = erb->appStatusB1 = erb->appStatusB0 = 0xFF; adamc@863: adamc@863: erb->protocolStatus = FCGI_REQUEST_COMPLETE; adamc@863: fastcgi_send(out, FCGI_END_REQUEST, sizeof(FCGI_EndRequestBody)); adamc@863: return close(out->sock); adamc@863: } adamc@863: adamc@863: static int fastcgi_close(int sock) { adamc@863: FCGI_Output out; adamc@863: out.sock = sock; adamc@863: out.r.version = FCGI_VERSION_1; adamc@863: out.r.paddingLength = 0; adamc@863: out.r.reserved = 0; adamc@863: adamc@863: return fastcgi_close_with(&out, SERVED); adamc@863: } adamc@863: adamc@863: int fastcgi_send_normal(int sock, const void *buf, ssize_t len) { adamc@863: FCGI_Output out; adamc@863: out.sock = sock; adamc@863: out.r.version = FCGI_VERSION_1; adamc@863: out.r.paddingLength = 0; adamc@863: out.r.reserved = 0; adamc@863: adamc@863: return write_stdout(&out, buf, len); adamc@863: } adamc@863: adamc@859: static void *worker(void *data) { adamc@859: FCGI_Input *in = fastcgi_input(); adamc@859: FCGI_Output *out = fastcgi_output(); adamc@1094: uw_context ctx = uw_request_new_context(&uw_application, out, log_error, log_debug); adamc@859: uw_request_context rc = uw_new_request_context(); adamc@860: headers hs; adamc@860: size_t body_size = 0; adamc@860: char *body = malloc(0); adamc@860: size_t path_size = 0; adamc@860: char *path_buf = malloc(0); adamc@860: adamc@860: hs.uppercased = malloc(0); adamc@860: hs.uppercased_len = 0; adamc@860: hs.nvps = malloc(sizeof(nvp)); adamc@860: hs.n_nvps = 1; adamc@862: hs.nvps[0].name = malloc(1); adamc@862: hs.nvps[0].name_len = 1; adamc@862: hs.nvps[0].value = malloc(0); adamc@862: hs.nvps[0].value_len = 0; adamc@859: adamc@859: while (1) { adamc@859: FCGI_Record *r; adamc@860: size_t used_nvps = 0; adamc@860: int body_len, body_read; adamc@860: char *s; adamc@860: char *method, *path, *path_info, *query_string; adamc@859: adamc@859: in->sock = out->sock = uw_dequeue(); adamc@859: adamc@859: if (!(r = fastcgi_recv(in))) { adamc@859: fprintf(stderr, "Error receiving initial message\n"); adamc@860: goto done; adamc@859: } adamc@861: adamc@859: if (r->type != FCGI_BEGIN_REQUEST) { adamc@859: write_stderr(out, "First message is not BEGIN_REQUEST\n"); adamc@859: goto done; adamc@1139: } else if (r->contentData[1] != FCGI_RESPONDER) { adamc@1139: write_stderr(out, "Request is for a role besides RESPONDER\n"); adamc@859: goto done; adamc@859: } adamc@859: adamc@860: while (1) { adamc@864: unsigned char *buf; adamc@864: int len; adamc@860: adamc@860: if (!(r = fastcgi_recv(in))) { adamc@860: write_stderr(out, "Error receiving environment variables\n"); adamc@860: goto done; adamc@860: } adamc@860: adamc@860: if (r->type != FCGI_PARAMS) { adamc@860: write_stderr(out, "Expected FCGI_PARAMS but got %d\n", r->type); adamc@860: goto done; adamc@860: } adamc@860: adamc@860: if (r->contentLengthB1 == 0 && r->contentLengthB0 == 0) adamc@860: break; adamc@860: adamc@864: len = (r->contentLengthB1 << 8) | r->contentLengthB0; adamc@864: adamc@864: for (buf = r->contentData; buf < r->contentData + len; ) { adamc@864: if (used_nvps == hs.n_nvps-1) { adamc@864: ++hs.n_nvps; adamc@864: hs.nvps = realloc(hs.nvps, hs.n_nvps * sizeof(nvp)); adamc@864: hs.nvps[hs.n_nvps-1].name = malloc(1); adamc@864: hs.nvps[hs.n_nvps-1].value = malloc(0); adamc@864: hs.nvps[hs.n_nvps-1].name_len = 1; adamc@864: hs.nvps[hs.n_nvps-1].value_len = 0; adamc@864: } adamc@864: adamc@864: if (read_nvp(&buf, len - (buf - r->contentData), &hs.nvps[used_nvps]) < 0) { adamc@864: write_stderr(out, "Error reading FCGI_PARAMS name-value pair\n"); adamc@864: goto done; adamc@864: } adamc@864: adamc@1049: //write_stderr(out, "PARAM: %s -> %s\n", hs.nvps[used_nvps].name, hs.nvps[used_nvps].value); adamc@864: adamc@864: ++used_nvps; adamc@860: } adamc@859: } adamc@860: adamc@862: hs.nvps[used_nvps].name[0] = 0; adamc@860: adamc@1134: if ((s = get_header(&hs, "Content-Length"))) { adamc@860: body_len = atoi(s); adamc@860: if (body_len < 0) { adamc@860: write_stderr(out, "Invalid Content-Length\n"); adamc@860: goto done; adamc@860: } adamc@860: } else adamc@860: body_len = 0; adamc@860: adamc@861: if (body_len+1 > body_size) { adamc@861: body_size = body_len+1; adamc@860: body = realloc(body, body_size); adamc@860: } adamc@860: adamc@860: for (body_read = 0; body_read < body_len; ) { adamc@860: int this_len; adamc@860: adamc@860: if (!(r = fastcgi_recv(in))) { adamc@860: write_stderr(out, "Error receiving STDIN\n"); adamc@860: goto done; adamc@860: } adamc@860: adamc@860: if (r->type != FCGI_STDIN) { adamc@860: write_stderr(out, "Expected FCGI_STDIN but got %d\n", r->type); adamc@860: goto done; adamc@860: } adamc@860: adamc@860: if (r->contentLengthB1 == 0 && r->contentLengthB0 == 0) { adamc@860: write_stderr(out, "End of STDIN\n"); adamc@860: break; adamc@860: } adamc@860: adamc@860: this_len = (r->contentLengthB1 << 8) | r->contentLengthB0; adamc@860: adamc@860: if (body_read + this_len > body_len) { adamc@860: write_stderr(out, "Too much STDIN\n"); adamc@860: goto done; adamc@860: } adamc@860: adamc@860: memcpy(&body[body_read], r->contentData, this_len); adamc@860: body_read += this_len; adamc@860: } adamc@860: adamc@861: body[body_read] = 0; adamc@861: adamc@860: if (!(method = search_nvps(hs.nvps, "REQUEST_METHOD"))) { adamc@860: write_stderr(out, "REQUEST_METHOD not set\n"); adamc@860: goto done; adamc@860: } adamc@860: adamc@860: if (!(path = search_nvps(hs.nvps, "SCRIPT_NAME"))) { adamc@860: write_stderr(out, "SCRIPT_NAME not set\n"); adamc@860: goto done; adamc@860: } adamc@861: adamc@1134: if ((path_info = search_nvps(hs.nvps, "PATH_INFO"))) { adamc@860: int len1 = strlen(path), len2 = strlen(path_info); adamc@860: int len = len1 + len2 + 1; adamc@860: adamc@860: if (len > path_size) { adamc@860: path_size = len; adamc@860: path_buf = realloc(path_buf, path_size); adamc@860: } adamc@860: adamc@860: sprintf(path_buf, "%s%s", path, path_info); adamc@860: path = path_buf; adamc@860: } adamc@860: adamc@860: if (!(query_string = search_nvps(hs.nvps, "QUERY_STRING"))) adamc@860: query_string = ""; adamc@860: adamc@860: uw_set_headers(ctx, get_header, &hs); adamc@861: adamc@860: { adamc@860: request_result rr; adamc@860: adamc@860: rr = uw_request(rc, ctx, method, path, query_string, body, body_read, adamc@860: on_success, on_failure, adamc@860: out, log_error, log_debug, adamc@863: in->sock, fastcgi_send_normal, fastcgi_close); adamc@860: adamc@860: if (rr == KEEP_OPEN) adamc@860: goto done2; adamc@860: adamc@860: uw_output(ctx, write_stdout, out); adamc@863: fastcgi_close_with(out, rr); adamc@863: goto done2; adamc@860: } adamc@859: adamc@859: done: adamc@859: close(in->sock); adamc@860: done2: adamc@859: fastcgi_input_reset(in); adamc@859: uw_reset(ctx); adamc@859: } adamc@1138: adamc@1138: return NULL; adamc@859: } adamc@859: adamc@859: static void help(char *cmd) { adamc@859: printf("Usage: %s [-t ]\n", cmd); adamc@859: } adamc@859: adamc@859: static void sigint(int signum) { adamc@859: printf("Exiting....\n"); adamc@859: exit(0); adamc@859: } adamc@859: adamc@1094: static loggers ls = {&uw_application, NULL, log_error, log_debug}; adamc@859: adamc@859: int main(int argc, char *argv[]) { adamc@859: // The skeleton for this function comes from Beej's sockets tutorial. adamc@859: struct sockaddr_in their_addr; // connector's address information adamc@1134: socklen_t sin_size; adamc@859: int nthreads = 1, i, *names, opt; adamc@859: char *fwsa = getenv("FCGI_WEB_SERVER_ADDRS"), *nthreads_s = getenv("URWEB_NUM_THREADS"); adamc@859: adamc@859: if (nthreads_s) { adamc@859: nthreads = atoi(nthreads_s); adamc@859: if (nthreads <= 0) { adamc@859: fprintf(stderr, "Bad URWEB_NUM_THREADS value\n"); adamc@859: return 1; adamc@859: } adamc@859: } adamc@859: adamc@859: signal(SIGINT, sigint); adamc@859: signal(SIGPIPE, SIG_IGN); adamc@859: signal(SIGUSR1, sigint); adamc@859: signal(SIGTERM, sigint); adamc@859: adamc@859: while ((opt = getopt(argc, argv, "ht:")) != -1) { adamc@859: switch (opt) { adamc@859: case '?': adamc@859: fprintf(stderr, "Unknown command-line option"); adamc@859: help(argv[0]); adamc@859: return 1; adamc@859: adamc@859: case 'h': adamc@859: help(argv[0]); adamc@859: return 0; adamc@859: adamc@859: case 't': adamc@859: nthreads = atoi(optarg); adamc@859: if (nthreads <= 0) { adamc@859: fprintf(stderr, "Invalid thread count\n"); adamc@859: help(argv[0]); adamc@859: return 1; adamc@859: } adamc@859: break; adamc@859: adamc@859: default: adamc@859: fprintf(stderr, "Unexpected getopt() behavior\n"); adamc@859: return 1; adamc@859: } adamc@859: } adamc@859: adamc@860: uw_set_on_success(""); adamc@1094: uw_request_init(&uw_application, NULL, log_error, log_debug); adamc@859: adamc@859: names = calloc(nthreads, sizeof(int)); adamc@859: adamc@859: sin_size = sizeof their_addr; adamc@859: adamc@859: { adamc@859: pthread_t thread; adamc@859: adamc@859: if (pthread_create(&thread, NULL, client_pruner, &ls)) { adamc@859: fprintf(stderr, "Error creating pruner thread\n"); adamc@859: return 1; adamc@859: } adamc@859: } adamc@859: adamc@859: for (i = 0; i < nthreads; ++i) { adamc@859: pthread_t thread; adamc@859: names[i] = i; adamc@859: if (pthread_create(&thread, NULL, worker, &names[i])) { adamc@859: fprintf(stderr, "Error creating worker thread #%d\n", i); adamc@859: return 1; adamc@859: } adamc@859: } adamc@859: adamc@859: while (1) { adamc@859: int new_fd = accept(FCGI_LISTENSOCK_FILENO, (struct sockaddr *)&their_addr, &sin_size); adamc@859: adamc@859: if (new_fd < 0) { adamc@859: fprintf(stderr, "Socket accept failed\n"); adamc@859: return 1; adamc@859: } adamc@859: adamc@859: if (fwsa) { adamc@859: char host[100], matched = 0; adamc@859: char *ips, *sep; adamc@859: adamc@859: if (getnameinfo((struct sockaddr *)&their_addr, sin_size, host, sizeof host, NULL, 0, NI_NUMERICHOST)) { adamc@859: fprintf(stderr, "Remote IP determination failed\n"); adamc@859: return 1; adamc@859: } adamc@859: adamc@1134: for (ips = fwsa; (sep = strchr(ips, ',')); ips = sep+1) { adamc@859: if (!strncmp(ips, host, sep - ips)) { adamc@859: matched = 1; adamc@859: break; adamc@859: } adamc@859: } adamc@859: adamc@859: if (!matched && strcmp(ips, host)) { adamc@859: fprintf(stderr, "Remote address is not in FCGI_WEB_SERVER_ADDRS"); adamc@859: return 1; adamc@859: } adamc@859: } adamc@859: adamc@859: uw_enqueue(new_fd); adamc@859: } adamc@859: } adamc@1121: adamc@1121: void *uw_init_client_data() { adamc@1121: return NULL; adamc@1121: } adamc@1121: adamc@1121: void uw_free_client_data(void *data) { adamc@1121: } adamc@1121: adamc@1121: void uw_copy_client_data(void *dst, void *src) { adamc@1121: } adamc@1121: adamc@1121: void uw_do_expunge(uw_context ctx, uw_Basis_client cli, void *data) { adamc@1121: if (uw_get_app(ctx)->db_begin(ctx)) adamc@1121: uw_error(ctx, FATAL, "Error running SQL BEGIN"); adamc@1121: uw_get_app(ctx)->expunger(ctx, cli); adamc@1121: if (uw_get_app(ctx)->db_commit(ctx)) adamc@1121: uw_error(ctx, FATAL, "Error running SQL COMMIT"); adamc@1121: } adamc@1121: adamc@1121: void uw_post_expunge(uw_context ctx, void *data) { adamc@1121: } adam@1320: adam@1320: int uw_supports_direct_status = 0;