Project

General

Profile

Feature #1615 ยป mod_post_to_disk.c

stepancheg, 2008-03-27 10:04

 
1
#include <ctype.h>
2
#include <stdlib.h>
3
#include <string.h>
4
#include <assert.h>
5
#include <stdio.h>
6
#include <unistd.h>
7
#include <errno.h>
8
#include <sys/types.h>
9
#include <sys/stat.h>
10
#include <fcntl.h>
11
#include <sys/types.h>
12
#include <sys/stat.h>
13
#include <limits.h>
14

    
15
#include "base.h"
16
#include "log.h"
17
#include "buffer.h"
18

    
19
#include "plugin.h"
20

    
21
#ifdef HAVE_CONFIG_H
22
#include "config.h"
23
#endif
24

    
25
/**
26
 * this is a post_to_disk lighttpd plugin
27
 */
28

    
29
/* plugin config for all request/connections */
30

    
31
typedef struct {
32
	buffer *tmp_dir_buf;
33
	buffer *done_dir_buf;
34
	buffer *failed_dir_buf;
35
} plugin_config;
36

    
37
typedef struct {
38
	PLUGIN_DATA;
39

    
40
	plugin_config **config_storage;
41

    
42
	plugin_config conf;
43
	
44
} plugin_data;
45

    
46
typedef struct {
47
	int fd;
48
	char tmp_file[PATH_MAX];
49
	char done_file[PATH_MAX];
50
	char failed_file[PATH_MAX];
51
} handler_ctx;
52

    
53
static handler_ctx * handler_ctx_init() {
54
	handler_ctx * hctx;
55

    
56
	hctx = calloc(1, sizeof(*hctx));
57
	hctx->fd = -1;
58
	
59
	return hctx;
60
}
61

    
62
static void handler_ctx_free(handler_ctx *hctx) {
63

    
64
	free(hctx);
65
}
66

    
67
/* init the plugin data */
68
INIT_FUNC(mod_post_to_disk_init) {
69
	UNUSED(srv);
70

    
71
	plugin_data *p;
72

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

    
75
	return p;
76
}
77

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

    
82
	UNUSED(srv);
83

    
84
	if (!p) return HANDLER_GO_ON;
85

    
86
	if (p->config_storage) {
87
		size_t i;
88

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

    
92
			if (!s) continue;
93

    
94
			buffer_free(s->tmp_dir_buf);
95
			buffer_free(s->done_dir_buf);
96
			buffer_free(s->failed_dir_buf);
97

    
98
			free(s);
99
		}
100
		free(p->config_storage);
101
	}
102

    
103
	//buffer_free(p->tmp_dir_buf);
104
	//buffer_free(p->done_dir_buf);
105
	//buffer_free(p->failed_dir_buf);
106

    
107
	free(p);
108

    
109
	return HANDLER_GO_ON;
110
}
111

    
112
/* handle plugin config and check values */
113

    
114
SETDEFAULTS_FUNC(mod_post_to_disk_set_defaults) {
115
	plugin_data *p = p_d;
116
	size_t i = 0;
117

    
118
	config_values_t cv[] = {
119
		{ "post_to_disk.tmp-dir",             NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },       /* 0 */
120
		{ "post_to_disk.done-dir",             NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },       /* 1 */
121
		{ "post_to_disk.failed-dir",             NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },       /* 2 */
122
		{ NULL,                         NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
123
	};
124

    
125
	if (!p) return HANDLER_ERROR;
126

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

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

    
132
		s = calloc(1, sizeof(plugin_config));
133
		
134
		s->tmp_dir_buf = buffer_init();
135
		s->done_dir_buf = buffer_init();
136
		s->failed_dir_buf = buffer_init();
137

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

    
142
		p->config_storage[i] = s;
143

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

    
149
	return HANDLER_GO_ON;
150
}
151

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

    
156
	PATCH_OPTION(tmp_dir_buf);
157
	PATCH_OPTION(done_dir_buf);
158
	PATCH_OPTION(failed_dir_buf);
159

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

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

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

    
172
			if (buffer_is_equal_string(du->key, CONST_STR_LEN("post_to_disk.tmp-dir"))) {
173
				PATCH_OPTION(tmp_dir_buf);
174
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("post_to_disk.done-dir"))) {
175
			    PATCH_OPTION(done_dir_buf);
176
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("post_to_disk.failed-dir"))) {
177
			    PATCH_OPTION(failed_dir_buf);
178
			}
179
		}
180
	}
181
	
182
	return 0;
183
}
184

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

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

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

    
194
    if (r->http_version == HTTP_VERSION_1_1) {
195
        BUFFER_APPEND_STRING_CONST(b, " HTTP/1.1\r\n");
196
    } else {
197
        BUFFER_APPEND_STRING_CONST(b, " HTTP/1.0\r\n");
198
    }
199

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

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

    
205
        buffer_append_string_buffer(b, ds->key);
206
        BUFFER_APPEND_STRING_CONST(b, ": ");
207
        buffer_append_string_buffer(b, ds->value);
208
        BUFFER_APPEND_STRING_CONST(b, "\r\n");
209
        //ERROR("YYY: %s: %s\n", ds->key->ptr, ds->value->ptr);
210
    }
211

    
212
    BUFFER_APPEND_STRING_CONST(b, "\r\n");
213
    
214
    // end of copy-paste
215
}
216

    
217
static void check_directory_writable(const char *path) {
218
    int r;
219
    
220
    struct stat s;
221
    memset(&s, 0, sizeof(s));
222
    
223
    r = stat(path, &s);
224
    if (r < 0) {
225
        ERROR("failed to stat %s: %s", path, strerror(errno));
226
        return;
227
    }
228
    
229
    if (!S_ISDIR(s.st_mode)) {
230
        ERROR("%s is not a directory", path);
231
        return;
232
    }
233
    
234
    r = access(path, W_OK|X_OK);
235
    if (r < 0) {
236
        ERROR("failed to check access on %s: %s", path, strerror(errno));
237
        return;
238
    }
239
}
240

    
241
URIHANDLER_FUNC(mod_post_to_disk_uri_handler) {
242
	plugin_data *p = p_d;
243
	handler_ctx *hctx;
244

    
245
	UNUSED(srv);
246

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

    
249
	mod_post_to_disk_patch_connection(srv, con, p);
250
	
251
	if (p->conf.tmp_dir_buf->used == 0 && p->conf.done_dir_buf->used == 0 && p->conf.failed_dir_buf->used == 0) {
252
	    return HANDLER_GO_ON;
253
	}
254
	
255
	if (p->conf.tmp_dir_buf->used == 0) {
256
	    ERROR("tmp-dir param not set", "");
257
	    return HANDLER_ERROR;
258
	}
259
	
260
	if (p->conf.done_dir_buf->used == 0) {
261
	    ERROR("done-dir param not set", "");
262
	    return HANDLER_ERROR;
263
	}
264
	
265
	if (p->conf.failed_dir_buf->used == 0) {
266
	    ERROR("failed-dir param not set", "");
267
	    return HANDLER_ERROR;
268
	}
269
	
270
	con->mode = p->id;
271

    
272
	if (con->plugin_ctx[p->id]) {
273
		hctx = con->plugin_ctx[p->id];
274
	} else {
275
		hctx = handler_ctx_init();
276
		con->plugin_ctx[p->id] = hctx;
277
	}
278

    
279
	assert(hctx->fd == -1);
280
	
281
	time_t t = time(NULL);
282
	
283
	char time_string[20];
284
	strftime(time_string, sizeof(time_string), "%Y%m%d-%H%M%S", localtime(&t));
285
    
286
    char filename[40];
287
    snprintf(filename, 40, "%s.%d.%08lx", time_string, getpid(), random());
288
    
289
    snprintf(hctx->tmp_file, PATH_MAX, "%s/%s", p->conf.tmp_dir_buf->ptr, filename);
290
    snprintf(hctx->done_file, PATH_MAX, "%s/%s", p->conf.done_dir_buf->ptr, filename);
291
    snprintf(hctx->failed_file, PATH_MAX, "%s/%s", p->conf.failed_dir_buf->ptr, filename);
292
    
293
    check_directory_writable(p->conf.tmp_dir_buf->ptr);
294
    check_directory_writable(p->conf.done_dir_buf->ptr);
295
    check_directory_writable(p->conf.failed_dir_buf->ptr);
296
    
297
	int fd = open(hctx->tmp_file, O_CREAT|O_TRUNC|O_WRONLY, S_IRWXU|S_IRWXG|S_IRWXO);
298
	if (fd < 0) {
299
		ERROR("failed to open tmp file", hctx->tmp_file);
300
		return HANDLER_ERROR;
301
	}
302
	
303
	// temp hack agains umask
304
	chmod(hctx->tmp_file, 0666);
305
	
306
	hctx->fd = fd;
307
	
308
    int rv = 0;
309
    buffer *b = buffer_init();
310
    reconstruct_request(&con->request, b);
311
    
312
    size_t written = 0;
313
    while (written < b->used - 1) {
314
        int r = write(fd, b->ptr + written, b->used - 1 - written);
315
        if (r < 0) {
316
            ERROR("failed to write headers", "");
317
            rv = HANDLER_ERROR;
318
            goto finally;
319
        }
320
        
321
        written += r;
322
    }
323
    
324
    /*
325
    iosocket sock;
326
    sock.fd = fd;
327
    
328
    while (cq->bytes_in < cq->bytes_out) {
329
        int r = network_write_chunkqueue_write(srv, con, &sock, cq);
330
        switch (r) {
331
            case NETWORK_STATUS_SUCCESS:
332
                break;
333
            default:
334
                ERROR("failed to write to file", "");
335
                rv = HANDLER_ERROR;
336
                goto finally;
337
        }
338
    }
339
    */
340
    
341
	rv = HANDLER_GO_ON;
342
finally:
343
    buffer_free(b);
344
    return rv;
345
}
346

    
347
SUBREQUEST_FUNC(mod_post_to_disk_read_response_content) {
348
	plugin_data *p = p_d;
349
	
350
	UNUSED(srv);
351
	
352
	if (con->mode != p->id) return HANDLER_GO_ON;
353
	
354
	//if (con->send->is_closed == 0) {
355
    buffer *b;
356
    
357
    b = chunkqueue_get_append_buffer(con->send);
358
    BUFFER_APPEND_STRING_CONST(b, "uploaded successfully");
359
    
360
    con->send->is_closed = 1;
361
	//}
362
	
363
	return HANDLER_FINISHED;
364
}
365

    
366
URIHANDLER_FUNC(mod_post_to_disk_send_request_content) {
367
	plugin_data *p = p_d;
368
	
369
	handler_ctx *hctx = con->plugin_ctx[p->id];
370
	
371
	if (con->mode != p->id) return HANDLER_GO_ON;
372
	
373
	assert(hctx->fd >= 0);
374
	
375
	iosocket sock;
376
	sock.fd = hctx->fd;
377
	
378
	while (con->request.content_length > 0 && con->recv->bytes_in > con->recv->bytes_out) {
379
		int r = network_write_chunkqueue_write(srv, con, &sock, con->recv);
380
		switch (r) {
381
		case NETWORK_STATUS_SUCCESS:
382
			break;
383
		case NETWORK_STATUS_WAIT_FOR_EVENT:
384
		case NETWORK_STATUS_WAIT_FOR_AIO_EVENT:
385
			break; // not sure
386
		default:
387
			ERROR("failed to write", "");
388
			return HANDLER_ERROR;
389
		}
390
		chunkqueue_remove_finished_chunks(con->recv);
391
	}
392
	
393
    /* We have to close file and finish the request */
394
    if ((con->recv->is_closed && con->recv->bytes_in == con->recv->bytes_out) ||
395
            con->request.content_length <= 0)
396
    {
397
        close(hctx->fd);
398
        hctx->fd = -1;
399
        int r = rename(hctx->tmp_file, hctx->done_file);
400
        if (r < 0) {
401
            ERROR("failed to rename %s to %s: %s", hctx->tmp_file, hctx->done_file, strerror(errno));
402
        }
403
    } else {
404
		/* There is more data to write */
405
		return HANDLER_GO_ON;
406
	}
407
	
408
	// strange behaviour of lighttpd: function must be called explicitly here?
409
	return mod_post_to_disk_read_response_content(srv, con, p_d);
410
}
411

    
412
REQUESTDONE_FUNC(mod_post_to_disk_connection_reset) {
413
	plugin_data *p = p_d;
414
	handler_ctx *hctx = con->plugin_ctx[p->id];
415
	
416
	UNUSED(srv);
417
	
418
    if (hctx) {
419
        // cleanup; maybe there is a better place
420
        
421
        if (hctx->fd >= 0) {
422
            close(hctx->fd);
423
            hctx->fd = -1;
424
        }
425
        
426
        rename(hctx->tmp_file, hctx->failed_file);
427
    }
428
        
429
	if (con->plugin_ctx[p->id]) {
430
        handler_ctx_free(con->plugin_ctx[p->id]);
431
        con->plugin_ctx[p->id] = NULL;
432
    }
433
    
434
    return HANDLER_GO_ON;
435
}
436

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

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

    
443
	p->init        = mod_post_to_disk_init;
444
	p->cleanup     = mod_post_to_disk_free;
445
	
446
	p->handle_send_request_content = mod_post_to_disk_send_request_content;
447
	//p->handle_read_response_content = mod_post_to_disk_read_response_content;
448
	
449
	p->connection_reset = mod_post_to_disk_connection_reset;
450
	
451
	p->handle_uri_clean  = mod_post_to_disk_uri_handler;
452
	p->set_defaults  = mod_post_to_disk_set_defaults;
453

    
454
	p->data        = NULL;
455

    
456
	return 0;
457
}
458

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