Project

General

Profile

Feature #1615 ยป mod_post_to_disk.c

stepancheg, 2008-03-27 10:04

 
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>

#include "base.h"
#include "log.h"
#include "buffer.h"

#include "plugin.h"

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

/**
* this is a post_to_disk lighttpd plugin
*/

/* plugin config for all request/connections */

typedef struct {
buffer *tmp_dir_buf;
buffer *done_dir_buf;
buffer *failed_dir_buf;
} plugin_config;

typedef struct {
PLUGIN_DATA;

plugin_config **config_storage;

plugin_config conf;
} plugin_data;

typedef struct {
int fd;
char tmp_file[PATH_MAX];
char done_file[PATH_MAX];
char failed_file[PATH_MAX];
} handler_ctx;

static handler_ctx * handler_ctx_init() {
handler_ctx * hctx;

hctx = calloc(1, sizeof(*hctx));
hctx->fd = -1;
return hctx;
}

static void handler_ctx_free(handler_ctx *hctx) {

free(hctx);
}

/* init the plugin data */
INIT_FUNC(mod_post_to_disk_init) {
UNUSED(srv);

plugin_data *p;

p = calloc(1, sizeof(*p));

return p;
}

/* detroy the plugin data */
FREE_FUNC(mod_post_to_disk_free) {
plugin_data *p = p_d;

UNUSED(srv);

if (!p) return HANDLER_GO_ON;

if (p->config_storage) {
size_t i;

for (i = 0; i < srv->config_context->used; i++) {
plugin_config *s = p->config_storage[i];

if (!s) continue;

buffer_free(s->tmp_dir_buf);
buffer_free(s->done_dir_buf);
buffer_free(s->failed_dir_buf);

free(s);
}
free(p->config_storage);
}

//buffer_free(p->tmp_dir_buf);
//buffer_free(p->done_dir_buf);
//buffer_free(p->failed_dir_buf);

free(p);

return HANDLER_GO_ON;
}

/* handle plugin config and check values */

SETDEFAULTS_FUNC(mod_post_to_disk_set_defaults) {
plugin_data *p = p_d;
size_t i = 0;

config_values_t cv[] = {
{ "post_to_disk.tmp-dir", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
{ "post_to_disk.done-dir", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
{ "post_to_disk.failed-dir", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
{ NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
};

if (!p) return HANDLER_ERROR;

p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));

for (i = 0; i < srv->config_context->used; i++) {
plugin_config *s;

s = calloc(1, sizeof(plugin_config));
s->tmp_dir_buf = buffer_init();
s->done_dir_buf = buffer_init();
s->failed_dir_buf = buffer_init();

cv[0].destination = s->tmp_dir_buf;
cv[1].destination = s->done_dir_buf;
cv[2].destination = s->failed_dir_buf;

p->config_storage[i] = s;

if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
return HANDLER_ERROR;
}
}

return HANDLER_GO_ON;
}

static int mod_post_to_disk_patch_connection(server *srv, connection *con, plugin_data *p) {
size_t i, j;
plugin_config *s = p->config_storage[0];

PATCH_OPTION(tmp_dir_buf);
PATCH_OPTION(done_dir_buf);
PATCH_OPTION(failed_dir_buf);

/* skip the first, the global context */
for (i = 1; i < srv->config_context->used; i++) {
data_config *dc = (data_config *)srv->config_context->data[i];
s = p->config_storage[i];

/* condition didn't match */
if (!config_check_cond(srv, con, dc)) continue;

/* merge config */
for (j = 0; j < dc->value->used; j++) {
data_unset *du = dc->value->data[j];

if (buffer_is_equal_string(du->key, CONST_STR_LEN("post_to_disk.tmp-dir"))) {
PATCH_OPTION(tmp_dir_buf);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("post_to_disk.done-dir"))) {
PATCH_OPTION(done_dir_buf);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("post_to_disk.failed-dir"))) {
PATCH_OPTION(failed_dir_buf);
}
}
}
return 0;
}

static void reconstruct_request(request *r, buffer *b) {
// copy past from mod_proxy_backend_http

buffer_append_string(b, get_http_method_name(r->http_method));
BUFFER_APPEND_STRING_CONST(b, " ");

/* request uri */
buffer_append_string_buffer(b, r->uri);

if (r->http_version == HTTP_VERSION_1_1) {
BUFFER_APPEND_STRING_CONST(b, " HTTP/1.1\r\n");
} else {
BUFFER_APPEND_STRING_CONST(b, " HTTP/1.0\r\n");
}

for (size_t i = 0; i < r->headers->used; i++) {
data_string *ds;

ds = (data_string *)r->headers->data[i];

buffer_append_string_buffer(b, ds->key);
BUFFER_APPEND_STRING_CONST(b, ": ");
buffer_append_string_buffer(b, ds->value);
BUFFER_APPEND_STRING_CONST(b, "\r\n");
//ERROR("YYY: %s: %s\n", ds->key->ptr, ds->value->ptr);
}

BUFFER_APPEND_STRING_CONST(b, "\r\n");
// end of copy-paste
}

static void check_directory_writable(const char *path) {
int r;
struct stat s;
memset(&s, 0, sizeof(s));
r = stat(path, &s);
if (r < 0) {
ERROR("failed to stat %s: %s", path, strerror(errno));
return;
}
if (!S_ISDIR(s.st_mode)) {
ERROR("%s is not a directory", path);
return;
}
r = access(path, W_OK|X_OK);
if (r < 0) {
ERROR("failed to check access on %s: %s", path, strerror(errno));
return;
}
}

URIHANDLER_FUNC(mod_post_to_disk_uri_handler) {
plugin_data *p = p_d;
handler_ctx *hctx;

UNUSED(srv);

if (con->uri.path->used == 0) return HANDLER_GO_ON;

mod_post_to_disk_patch_connection(srv, con, p);
if (p->conf.tmp_dir_buf->used == 0 && p->conf.done_dir_buf->used == 0 && p->conf.failed_dir_buf->used == 0) {
return HANDLER_GO_ON;
}
if (p->conf.tmp_dir_buf->used == 0) {
ERROR("tmp-dir param not set", "");
return HANDLER_ERROR;
}
if (p->conf.done_dir_buf->used == 0) {
ERROR("done-dir param not set", "");
return HANDLER_ERROR;
}
if (p->conf.failed_dir_buf->used == 0) {
ERROR("failed-dir param not set", "");
return HANDLER_ERROR;
}
con->mode = p->id;

if (con->plugin_ctx[p->id]) {
hctx = con->plugin_ctx[p->id];
} else {
hctx = handler_ctx_init();
con->plugin_ctx[p->id] = hctx;
}

assert(hctx->fd == -1);
time_t t = time(NULL);
char time_string[20];
strftime(time_string, sizeof(time_string), "%Y%m%d-%H%M%S", localtime(&t));
char filename[40];
snprintf(filename, 40, "%s.%d.%08lx", time_string, getpid(), random());
snprintf(hctx->tmp_file, PATH_MAX, "%s/%s", p->conf.tmp_dir_buf->ptr, filename);
snprintf(hctx->done_file, PATH_MAX, "%s/%s", p->conf.done_dir_buf->ptr, filename);
snprintf(hctx->failed_file, PATH_MAX, "%s/%s", p->conf.failed_dir_buf->ptr, filename);
check_directory_writable(p->conf.tmp_dir_buf->ptr);
check_directory_writable(p->conf.done_dir_buf->ptr);
check_directory_writable(p->conf.failed_dir_buf->ptr);
int fd = open(hctx->tmp_file, O_CREAT|O_TRUNC|O_WRONLY, S_IRWXU|S_IRWXG|S_IRWXO);
if (fd < 0) {
ERROR("failed to open tmp file", hctx->tmp_file);
return HANDLER_ERROR;
}
// temp hack agains umask
chmod(hctx->tmp_file, 0666);
hctx->fd = fd;
int rv = 0;
buffer *b = buffer_init();
reconstruct_request(&con->request, b);
size_t written = 0;
while (written < b->used - 1) {
int r = write(fd, b->ptr + written, b->used - 1 - written);
if (r < 0) {
ERROR("failed to write headers", "");
rv = HANDLER_ERROR;
goto finally;
}
written += r;
}
/*
iosocket sock;
sock.fd = fd;
while (cq->bytes_in < cq->bytes_out) {
int r = network_write_chunkqueue_write(srv, con, &sock, cq);
switch (r) {
case NETWORK_STATUS_SUCCESS:
break;
default:
ERROR("failed to write to file", "");
rv = HANDLER_ERROR;
goto finally;
}
}
*/
rv = HANDLER_GO_ON;
finally:
buffer_free(b);
return rv;
}

SUBREQUEST_FUNC(mod_post_to_disk_read_response_content) {
plugin_data *p = p_d;
UNUSED(srv);
if (con->mode != p->id) return HANDLER_GO_ON;
//if (con->send->is_closed == 0) {
buffer *b;
b = chunkqueue_get_append_buffer(con->send);
BUFFER_APPEND_STRING_CONST(b, "uploaded successfully");
con->send->is_closed = 1;
//}
return HANDLER_FINISHED;
}

URIHANDLER_FUNC(mod_post_to_disk_send_request_content) {
plugin_data *p = p_d;
handler_ctx *hctx = con->plugin_ctx[p->id];
if (con->mode != p->id) return HANDLER_GO_ON;
assert(hctx->fd >= 0);
iosocket sock;
sock.fd = hctx->fd;
while (con->request.content_length > 0 && con->recv->bytes_in > con->recv->bytes_out) {
int r = network_write_chunkqueue_write(srv, con, &sock, con->recv);
switch (r) {
case NETWORK_STATUS_SUCCESS:
break;
case NETWORK_STATUS_WAIT_FOR_EVENT:
case NETWORK_STATUS_WAIT_FOR_AIO_EVENT:
break; // not sure
default:
ERROR("failed to write", "");
return HANDLER_ERROR;
}
chunkqueue_remove_finished_chunks(con->recv);
}
/* We have to close file and finish the request */
if ((con->recv->is_closed && con->recv->bytes_in == con->recv->bytes_out) ||
con->request.content_length <= 0)
{
close(hctx->fd);
hctx->fd = -1;
int r = rename(hctx->tmp_file, hctx->done_file);
if (r < 0) {
ERROR("failed to rename %s to %s: %s", hctx->tmp_file, hctx->done_file, strerror(errno));
}
} else {
/* There is more data to write */
return HANDLER_GO_ON;
}
// strange behaviour of lighttpd: function must be called explicitly here?
return mod_post_to_disk_read_response_content(srv, con, p_d);
}

REQUESTDONE_FUNC(mod_post_to_disk_connection_reset) {
plugin_data *p = p_d;
handler_ctx *hctx = con->plugin_ctx[p->id];
UNUSED(srv);
if (hctx) {
// cleanup; maybe there is a better place
if (hctx->fd >= 0) {
close(hctx->fd);
hctx->fd = -1;
}
rename(hctx->tmp_file, hctx->failed_file);
}
if (con->plugin_ctx[p->id]) {
handler_ctx_free(con->plugin_ctx[p->id]);
con->plugin_ctx[p->id] = NULL;
}
return HANDLER_GO_ON;
}

/* this function is called at dlopen() time and inits the callbacks */

int mod_post_to_disk_plugin_init(plugin *p) {
p->version = LIGHTTPD_VERSION_ID;
p->name = buffer_init_string("post_to_disk");

p->init = mod_post_to_disk_init;
p->cleanup = mod_post_to_disk_free;
p->handle_send_request_content = mod_post_to_disk_send_request_content;
//p->handle_read_response_content = mod_post_to_disk_read_response_content;
p->connection_reset = mod_post_to_disk_connection_reset;
p->handle_uri_clean = mod_post_to_disk_uri_handler;
p->set_defaults = mod_post_to_disk_set_defaults;

p->data = NULL;

return 0;
}

// vim: syntax=off ts=4 sw=4 et:
    (1-1/1)