remove unused function (obj_fileno)
[clogger.git] / ext / clogger_ext / clogger.c
blob9703f6998b46d4b50551bd582c025fb4549e0b39
1 #include <ruby.h>
2 #ifdef HAVE_RUBY_IO_H
3 # include <ruby/io.h>
4 #else
5 # include <rubyio.h>
6 #endif
7 #include <assert.h>
8 #include <unistd.h>
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <sys/time.h>
12 #include <errno.h>
13 #ifdef HAVE_FCNTL_H
14 # include <fcntl.h>
15 #endif
16 #define _POSIX_C_SOURCE 200112L
17 #include <time.h>
18 #include "ruby_1_9_compat.h"
20 static void clock_diff(struct timespec *a, const struct timespec *b)
22 a->tv_sec -= b->tv_sec;
23 a->tv_nsec -= b->tv_nsec;
24 if (a->tv_nsec < 0) {
25 --a->tv_sec;
26 a->tv_nsec += 1000000000;
30 /* give GCC hints for better branch prediction
31 * (we layout branches so that ASCII characters are handled faster) */
32 #if defined(__GNUC__) && (__GNUC__ >= 3)
33 # define likely(x) __builtin_expect (!!(x), 1)
34 # define unlikely(x) __builtin_expect (!!(x), 0)
35 #else
36 # define unlikely(x) (x)
37 # define likely(x) (x)
38 #endif
40 enum clogger_opcode {
41 CL_OP_LITERAL = 0,
42 CL_OP_REQUEST,
43 CL_OP_RESPONSE,
44 CL_OP_SPECIAL,
45 CL_OP_EVAL,
46 CL_OP_TIME_LOCAL,
47 CL_OP_TIME_UTC,
48 CL_OP_REQUEST_TIME,
49 CL_OP_TIME,
50 CL_OP_COOKIE
53 enum clogger_special {
54 CL_SP_body_bytes_sent = 0,
55 CL_SP_status,
56 CL_SP_request,
57 CL_SP_request_length,
58 CL_SP_response_length,
59 CL_SP_ip,
60 CL_SP_pid,
61 CL_SP_request_uri
64 struct clogger {
65 VALUE app;
67 VALUE fmt_ops;
68 VALUE logger;
69 VALUE log_buf;
71 VALUE env;
72 VALUE cookies;
73 VALUE status;
74 VALUE headers;
75 VALUE body;
77 off_t body_bytes_sent;
78 struct timespec ts_start;
80 int fd;
81 int wrap_body;
82 int need_resp;
83 int reentrant; /* tri-state, -1:auto, 1/0 true/false */
86 static ID ltlt_id;
87 static ID call_id;
88 static ID each_id;
89 static ID close_id;
90 static ID to_i_id;
91 static ID to_s_id;
92 static ID size_id;
93 static ID sq_brace_id;
94 static ID new_id;
95 static ID to_path_id;
96 static ID to_io_id;
97 static VALUE cClogger;
98 static VALUE cToPath;
99 static VALUE mFormat;
100 static VALUE cHeaderHash;
102 /* common hash lookup keys */
103 static VALUE g_HTTP_X_FORWARDED_FOR;
104 static VALUE g_REMOTE_ADDR;
105 static VALUE g_REQUEST_METHOD;
106 static VALUE g_PATH_INFO;
107 static VALUE g_REQUEST_URI;
108 static VALUE g_QUERY_STRING;
109 static VALUE g_HTTP_VERSION;
110 static VALUE g_rack_errors;
111 static VALUE g_rack_input;
112 static VALUE g_rack_multithread;
113 static VALUE g_dash;
114 static VALUE g_space;
115 static VALUE g_question_mark;
116 static VALUE g_rack_request_cookie_hash;
118 #define LOG_BUF_INIT_SIZE 128
120 static void init_buffers(struct clogger *c)
122 c->log_buf = rb_str_buf_new(LOG_BUF_INIT_SIZE);
125 static inline int need_escape(unsigned c)
127 assert(c <= 0xff);
128 return !!(c == '\'' || c == '"' || c <= 0x1f);
131 /* we are encoding-agnostic, clients can send us all sorts of junk */
132 static VALUE byte_xs_str(VALUE from)
134 static const char esc[] = "0123456789ABCDEF";
135 unsigned char *new_ptr;
136 unsigned char *ptr = (unsigned char *)RSTRING_PTR(from);
137 long len = RSTRING_LEN(from);
138 long new_len = len;
139 VALUE rv;
141 for (; --len >= 0; ptr++) {
142 unsigned c = *ptr;
144 if (unlikely(need_escape(c)))
145 new_len += 3; /* { '\', 'x', 'X', 'X' } */
148 len = RSTRING_LEN(from);
149 if (new_len == len)
150 return from;
152 rv = rb_str_new(NULL, new_len);
153 new_ptr = (unsigned char *)RSTRING_PTR(rv);
154 ptr = (unsigned char *)RSTRING_PTR(from);
155 for (; --len >= 0; ptr++) {
156 unsigned c = *ptr;
158 if (unlikely(need_escape(c))) {
159 *new_ptr++ = '\\';
160 *new_ptr++ = 'x';
161 *new_ptr++ = esc[c >> 4];
162 *new_ptr++ = esc[c & 0xf];
163 } else {
164 *new_ptr++ = c;
167 assert(RSTRING_PTR(rv)[RSTRING_LEN(rv)] == '\0');
169 return rv;
172 static VALUE byte_xs(VALUE from)
174 return byte_xs_str(rb_obj_as_string(from));
177 static void clogger_mark(void *ptr)
179 struct clogger *c = ptr;
181 rb_gc_mark(c->app);
182 rb_gc_mark(c->fmt_ops);
183 rb_gc_mark(c->logger);
184 rb_gc_mark(c->log_buf);
185 rb_gc_mark(c->env);
186 rb_gc_mark(c->cookies);
187 rb_gc_mark(c->status);
188 rb_gc_mark(c->headers);
189 rb_gc_mark(c->body);
192 static VALUE clogger_alloc(VALUE klass)
194 struct clogger *c;
196 return Data_Make_Struct(klass, struct clogger, clogger_mark, -1, c);
199 static struct clogger *clogger_get(VALUE self)
201 struct clogger *c;
203 Data_Get_Struct(self, struct clogger, c);
204 assert(c);
205 return c;
208 /* only for writing to regular files, not stupid crap like NFS */
209 static void write_full(int fd, const void *buf, size_t count)
211 ssize_t r;
212 unsigned long ubuf = (unsigned long)buf;
214 while (count > 0) {
215 r = write(fd, (void *)ubuf, count);
217 if ((size_t)r == count) { /* overwhelmingly likely */
218 return;
219 } else if (r > 0) {
220 count -= r;
221 ubuf += r;
222 } else {
223 if (errno == EINTR || errno == EAGAIN)
224 continue; /* poor souls on NFS and like: */
225 if (!errno)
226 errno = ENOSPC;
227 rb_sys_fail("write");
233 * allow us to use write_full() iff we detect a blocking file
234 * descriptor that wouldn't play nicely with Ruby threading/fibers
236 static int raw_fd(VALUE my_fd)
238 #if defined(HAVE_FCNTL) && defined(F_GETFL) && defined(O_NONBLOCK)
239 int fd;
240 int flags;
242 if (NIL_P(my_fd))
243 return -1;
244 fd = NUM2INT(my_fd);
246 flags = fcntl(fd, F_GETFL);
247 if (flags < 0)
248 rb_sys_fail("fcntl");
250 if (flags & O_NONBLOCK) {
251 struct stat sb;
253 if (fstat(fd, &sb) < 0)
254 return -1;
256 /* O_NONBLOCK is no-op for regular files: */
257 if (! S_ISREG(sb.st_mode))
258 return -1;
260 return fd;
261 #else /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
262 return -1;
263 #endif /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
266 /* :nodoc: */
267 static VALUE clogger_reentrant(VALUE self)
269 return clogger_get(self)->reentrant == 0 ? Qfalse : Qtrue;
272 /* :nodoc: */
273 static VALUE clogger_wrap_body(VALUE self)
275 return clogger_get(self)->wrap_body == 0 ? Qfalse : Qtrue;
278 static void append_status(struct clogger *c)
280 char buf[sizeof("999")];
281 int nr;
282 VALUE status = c->status;
284 if (TYPE(status) != T_FIXNUM) {
285 status = rb_funcall(status, to_i_id, 0);
286 /* no way it's a valid status code (at least not HTTP/1.1) */
287 if (TYPE(status) != T_FIXNUM) {
288 rb_str_buf_append(c->log_buf, g_dash);
289 return;
293 nr = FIX2INT(status);
294 if (nr >= 100 && nr <= 999) {
295 nr = snprintf(buf, sizeof(buf), "%03d", nr);
296 assert(nr == 3);
297 rb_str_buf_cat(c->log_buf, buf, nr);
298 } else {
299 /* raise?, swap for 500? */
300 rb_str_buf_append(c->log_buf, g_dash);
304 /* this is Rack 1.0.0-compatible, won't try to parse commas in XFF */
305 static void append_ip(struct clogger *c)
307 VALUE env = c->env;
308 VALUE tmp = rb_hash_aref(env, g_HTTP_X_FORWARDED_FOR);
310 if (NIL_P(tmp)) {
311 /* can't be faked on any real server, so no escape */
312 tmp = rb_hash_aref(env, g_REMOTE_ADDR);
313 if (NIL_P(tmp))
314 tmp = g_dash;
315 } else {
316 tmp = byte_xs(tmp);
318 rb_str_buf_append(c->log_buf, tmp);
321 static void append_body_bytes_sent(struct clogger *c)
323 char buf[(sizeof(off_t) * 8) / 3 + 1];
324 const char *fmt = sizeof(off_t) == sizeof(long) ? "%ld" : "%lld";
325 int nr = snprintf(buf, sizeof(buf), fmt, c->body_bytes_sent);
327 assert(nr > 0 && nr < (int)sizeof(buf));
328 rb_str_buf_cat(c->log_buf, buf, nr);
331 static void append_ts(struct clogger *c, const VALUE *op, struct timespec *ts)
333 char buf[sizeof(".000000") + ((sizeof(ts->tv_sec) * 8) / 3)];
334 int nr;
335 char *fmt = RSTRING_PTR(op[1]);
336 int ndiv = NUM2INT(op[2]);
337 int usec = ts->tv_nsec / 1000;
339 nr = snprintf(buf, sizeof(buf), fmt,
340 (int)ts->tv_sec, (int)(usec / ndiv));
341 assert(nr > 0 && nr < (int)sizeof(buf));
342 rb_str_buf_cat(c->log_buf, buf, nr);
345 static void append_request_time_fmt(struct clogger *c, const VALUE *op)
347 struct timespec now;
348 int r = clock_gettime(CLOCK_MONOTONIC, &now);
350 if (unlikely(r != 0))
351 rb_sys_fail("clock_gettime(CLOCK_MONONTONIC)");
353 clock_diff(&now, &c->ts_start);
354 append_ts(c, op, &now);
357 static void append_time_fmt(struct clogger *c, const VALUE *op)
359 struct timespec now;
360 int r = clock_gettime(CLOCK_REALTIME, &now);
362 if (unlikely(r != 0))
363 rb_sys_fail("clock_gettime(CLOCK_REALTIME)");
364 append_ts(c, op, &now);
367 static void append_request_uri(struct clogger *c)
369 VALUE tmp;
371 tmp = rb_hash_aref(c->env, g_REQUEST_URI);
372 if (NIL_P(tmp)) {
373 tmp = rb_hash_aref(c->env, g_PATH_INFO);
374 if (!NIL_P(tmp))
375 rb_str_buf_append(c->log_buf, byte_xs(tmp));
376 tmp = rb_hash_aref(c->env, g_QUERY_STRING);
377 if (!NIL_P(tmp) && RSTRING_LEN(tmp) != 0) {
378 rb_str_buf_append(c->log_buf, g_question_mark);
379 rb_str_buf_append(c->log_buf, byte_xs(tmp));
381 } else {
382 rb_str_buf_append(c->log_buf, byte_xs(tmp));
386 static void append_request(struct clogger *c)
388 VALUE tmp;
390 /* REQUEST_METHOD doesn't need escaping, Rack::Lint governs it */
391 tmp = rb_hash_aref(c->env, g_REQUEST_METHOD);
392 if (!NIL_P(tmp))
393 rb_str_buf_append(c->log_buf, tmp);
395 rb_str_buf_append(c->log_buf, g_space);
397 append_request_uri(c);
399 /* HTTP_VERSION can be injected by malicious clients */
400 tmp = rb_hash_aref(c->env, g_HTTP_VERSION);
401 if (!NIL_P(tmp)) {
402 rb_str_buf_append(c->log_buf, g_space);
403 rb_str_buf_append(c->log_buf, byte_xs(tmp));
407 static void append_request_length(struct clogger *c)
409 VALUE tmp = rb_hash_aref(c->env, g_rack_input);
410 if (NIL_P(tmp)) {
411 rb_str_buf_append(c->log_buf, g_dash);
412 } else {
413 tmp = rb_funcall(tmp, size_id, 0);
414 rb_str_buf_append(c->log_buf, rb_funcall(tmp, to_s_id, 0));
418 static void append_time(struct clogger *c, enum clogger_opcode op, VALUE fmt)
420 /* you'd have to be a moron to use formats this big... */
421 char buf[sizeof("Saturday, November 01, 1970, 00:00:00 PM +0000")];
422 size_t nr;
423 struct tm tmp;
424 time_t t = time(NULL);
426 if (op == CL_OP_TIME_LOCAL)
427 localtime_r(&t, &tmp);
428 else if (op == CL_OP_TIME_UTC)
429 gmtime_r(&t, &tmp);
430 else
431 assert(0 && "unknown op");
433 nr = strftime(buf, sizeof(buf), RSTRING_PTR(fmt), &tmp);
434 if (nr == 0 || nr == sizeof(buf))
435 rb_str_buf_append(c->log_buf, g_dash);
436 else
437 rb_str_buf_cat(c->log_buf, buf, nr);
440 static void append_pid(struct clogger *c)
442 char buf[(sizeof(pid_t) * 8) / 3 + 1];
443 int nr = snprintf(buf, sizeof(buf), "%d", (int)getpid());
445 assert(nr > 0 && nr < (int)sizeof(buf));
446 rb_str_buf_cat(c->log_buf, buf, nr);
449 static void append_eval(struct clogger *c, VALUE str)
451 int state = -1;
452 VALUE rv = rb_eval_string_protect(RSTRING_PTR(str), &state);
454 rv = state == 0 ? rb_obj_as_string(rv) : g_dash;
455 rb_str_buf_append(c->log_buf, rv);
458 static void append_cookie(struct clogger *c, VALUE key)
460 VALUE cookie;
462 if (c->cookies == Qfalse)
463 c->cookies = rb_hash_aref(c->env, g_rack_request_cookie_hash);
465 if (NIL_P(c->cookies)) {
466 cookie = g_dash;
467 } else {
468 cookie = rb_hash_aref(c->cookies, key);
469 if (NIL_P(cookie))
470 cookie = g_dash;
472 rb_str_buf_append(c->log_buf, cookie);
475 static void append_request_env(struct clogger *c, VALUE key)
477 VALUE tmp = rb_hash_aref(c->env, key);
479 tmp = NIL_P(tmp) ? g_dash : byte_xs(tmp);
480 rb_str_buf_append(c->log_buf, tmp);
483 static void append_response(struct clogger *c, VALUE key)
485 VALUE v;
487 assert(rb_obj_is_kind_of(c->headers, cHeaderHash) && "not HeaderHash");
489 v = rb_funcall(c->headers, sq_brace_id, 1, key);
490 v = NIL_P(v) ? g_dash : byte_xs(v);
491 rb_str_buf_append(c->log_buf, v);
494 static void special_var(struct clogger *c, enum clogger_special var)
496 switch (var) {
497 case CL_SP_body_bytes_sent:
498 append_body_bytes_sent(c);
499 break;
500 case CL_SP_status:
501 append_status(c);
502 break;
503 case CL_SP_request:
504 append_request(c);
505 break;
506 case CL_SP_request_length:
507 append_request_length(c);
508 break;
509 case CL_SP_response_length:
510 if (c->body_bytes_sent == 0)
511 rb_str_buf_append(c->log_buf, g_dash);
512 else
513 append_body_bytes_sent(c);
514 break;
515 case CL_SP_ip:
516 append_ip(c);
517 break;
518 case CL_SP_pid:
519 append_pid(c);
520 break;
521 case CL_SP_request_uri:
522 append_request_uri(c);
526 static VALUE cwrite(struct clogger *c)
528 const VALUE ops = c->fmt_ops;
529 const VALUE *ary = RARRAY_PTR(ops);
530 long i = RARRAY_LEN(ops);
531 VALUE dst = c->log_buf;
533 rb_str_set_len(dst, 0);
535 for (; --i >= 0; ary++) {
536 const VALUE *op = RARRAY_PTR(*ary);
537 enum clogger_opcode opcode = FIX2INT(op[0]);
539 switch (opcode) {
540 case CL_OP_LITERAL:
541 rb_str_buf_append(dst, op[1]);
542 break;
543 case CL_OP_REQUEST:
544 append_request_env(c, op[1]);
545 break;
546 case CL_OP_RESPONSE:
547 append_response(c, op[1]);
548 break;
549 case CL_OP_SPECIAL:
550 special_var(c, FIX2INT(op[1]));
551 break;
552 case CL_OP_EVAL:
553 append_eval(c, op[1]);
554 break;
555 case CL_OP_TIME_LOCAL:
556 case CL_OP_TIME_UTC:
557 append_time(c, opcode, op[1]);
558 break;
559 case CL_OP_REQUEST_TIME:
560 append_request_time_fmt(c, op);
561 break;
562 case CL_OP_TIME:
563 append_time_fmt(c, op);
564 break;
565 case CL_OP_COOKIE:
566 append_cookie(c, op[1]);
567 break;
571 if (c->fd >= 0) {
572 write_full(c->fd, RSTRING_PTR(dst), RSTRING_LEN(dst));
573 } else {
574 VALUE logger = c->logger;
576 if (NIL_P(logger))
577 logger = rb_hash_aref(c->env, g_rack_errors);
578 rb_funcall(logger, ltlt_id, 1, dst);
581 return Qnil;
584 static void init_logger(struct clogger *c, VALUE path)
586 ID id;
588 if (!NIL_P(path) && !NIL_P(c->logger))
589 rb_raise(rb_eArgError, ":logger and :path are independent");
590 if (!NIL_P(path)) {
591 VALUE ab = rb_str_new2("ab");
592 id = rb_intern("open");
593 c->logger = rb_funcall(rb_cFile, id, 2, path, ab);
596 id = rb_intern("sync=");
597 if (rb_respond_to(c->logger, id))
598 rb_funcall(c->logger, id, 1, Qtrue);
600 id = rb_intern("fileno");
601 if (rb_respond_to(c->logger, id))
602 c->fd = raw_fd(rb_funcall(c->logger, id, 0));
606 * call-seq:
607 * Clogger.new(app, :logger => $stderr, :format => string) => obj
609 * Creates a new Clogger object that wraps +app+. +:logger+ may
610 * be any object that responds to the "<<" method with a string argument.
611 * If +:logger:+ is a string, it will be treated as a path to a
612 * File that will be opened in append mode.
614 static VALUE clogger_init(int argc, VALUE *argv, VALUE self)
616 struct clogger *c = clogger_get(self);
617 VALUE o = Qnil;
618 VALUE fmt = rb_const_get(mFormat, rb_intern("Common"));
620 rb_scan_args(argc, argv, "11", &c->app, &o);
621 c->fd = -1;
622 c->logger = Qnil;
623 c->reentrant = -1; /* auto-detect */
625 if (TYPE(o) == T_HASH) {
626 VALUE tmp;
628 tmp = rb_hash_aref(o, ID2SYM(rb_intern("path")));
629 c->logger = rb_hash_aref(o, ID2SYM(rb_intern("logger")));
630 init_logger(c, tmp);
632 tmp = rb_hash_aref(o, ID2SYM(rb_intern("format")));
633 if (!NIL_P(tmp))
634 fmt = tmp;
636 tmp = rb_hash_aref(o, ID2SYM(rb_intern("reentrant")));
637 switch (TYPE(tmp)) {
638 case T_TRUE:
639 c->reentrant = 1;
640 break;
641 case T_FALSE:
642 c->reentrant = 0;
643 case T_NIL:
644 break;
645 default:
646 rb_raise(rb_eArgError, ":reentrant must be boolean");
650 init_buffers(c);
651 c->fmt_ops = rb_funcall(self, rb_intern("compile_format"), 2, fmt, o);
653 if (Qtrue == rb_funcall(self, rb_intern("need_response_headers?"),
654 1, c->fmt_ops))
655 c->need_resp = 1;
656 if (Qtrue == rb_funcall(self, rb_intern("need_wrap_body?"),
657 1, c->fmt_ops))
658 c->wrap_body = 1;
660 return self;
663 static VALUE body_iter_i(VALUE str, VALUE memop)
665 off_t *len = (off_t *)memop;
667 str = rb_obj_as_string(str);
668 *len += RSTRING_LEN(str);
670 return rb_yield(str);
673 static VALUE body_close(struct clogger *c)
675 if (rb_respond_to(c->body, close_id))
676 return rb_funcall(c->body, close_id, 0);
677 return Qnil;
681 * call-seq:
682 * clogger.each { |part| socket.write(part) }
684 * Delegates the body#each call to the underlying +body+ object
685 * while tracking the number of bytes yielded. This will log
686 * the request.
688 static VALUE clogger_each(VALUE self)
690 struct clogger *c = clogger_get(self);
692 rb_need_block();
693 c->body_bytes_sent = 0;
694 rb_iterate(rb_each, c->body, body_iter_i, (VALUE)&c->body_bytes_sent);
696 return self;
700 * call-seq:
701 * clogger.close
703 * Delegates the body#close call to the underlying +body+ object.
704 * This is only used when Clogger is wrapping the +body+ of a Rack
705 * response and should be automatically called by the web server.
707 static VALUE clogger_close(VALUE self)
709 struct clogger *c = clogger_get(self);
711 return rb_ensure(body_close, (VALUE)c, cwrite, (VALUE)c);
714 /* :nodoc: */
715 static VALUE clogger_fileno(VALUE self)
717 struct clogger *c = clogger_get(self);
719 return c->fd < 0 ? Qnil : INT2NUM(c->fd);
722 static VALUE ccall(struct clogger *c, VALUE env)
724 VALUE rv;
726 clock_gettime(CLOCK_MONOTONIC, &c->ts_start);
727 c->env = env;
728 c->cookies = Qfalse;
729 rv = rb_funcall(c->app, call_id, 1, env);
730 if (TYPE(rv) == T_ARRAY && RARRAY_LEN(rv) == 3) {
731 VALUE *tmp = RARRAY_PTR(rv);
733 c->status = tmp[0];
734 c->headers = tmp[1];
735 c->body = tmp[2];
737 rv = rb_ary_new4(3, tmp);
738 if (c->need_resp &&
739 ! rb_obj_is_kind_of(tmp[1], cHeaderHash)) {
740 c->headers = rb_funcall(cHeaderHash, new_id, 1, tmp[1]);
741 rb_ary_store(rv, 1, c->headers);
743 } else {
744 volatile VALUE tmp = rb_inspect(rv);
746 c->status = INT2FIX(500);
747 c->headers = c->body = rb_ary_new();
748 cwrite(c);
749 rb_raise(rb_eTypeError,
750 "app response not a 3 element Array: %s",
751 RSTRING_PTR(tmp));
754 return rv;
758 * call-seq:
759 * clogger.call(env) => [ status, headers, body ]
761 * calls the wrapped Rack application with +env+, returns the
762 * [status, headers, body ] tuplet required by Rack.
764 static VALUE clogger_call(VALUE self, VALUE env)
766 struct clogger *c = clogger_get(self);
767 VALUE rv;
769 env = rb_check_convert_type(env, T_HASH, "Hash", "to_hash");
771 if (c->wrap_body) {
772 if (c->reentrant < 0) {
773 VALUE tmp = rb_hash_aref(env, g_rack_multithread);
774 c->reentrant = Qfalse == tmp ? 0 : 1;
776 if (c->reentrant) {
777 self = rb_obj_dup(self);
778 c = clogger_get(self);
781 rv = ccall(c, env);
782 assert(!OBJ_FROZEN(rv) && "frozen response array");
784 if (rb_respond_to(c->body, to_path_id))
785 self = rb_funcall(cToPath, new_id, 1, self);
786 rb_ary_store(rv, 2, self);
788 return rv;
791 rv = ccall(c, env);
792 cwrite(c);
794 return rv;
797 /* :nodoc */
798 static VALUE clogger_init_copy(VALUE clone, VALUE orig)
800 struct clogger *a = clogger_get(orig);
801 struct clogger *b = clogger_get(clone);
803 memcpy(b, a, sizeof(struct clogger));
804 init_buffers(b);
806 return clone;
809 #define CONST_GLOBAL_STR2(var, val) do { \
810 g_##var = rb_obj_freeze(rb_str_new(val, sizeof(val) - 1)); \
811 rb_global_variable(&g_##var); \
812 } while (0)
814 #define CONST_GLOBAL_STR(val) CONST_GLOBAL_STR2(val, #val)
816 #ifdef RSTRUCT_PTR
817 # define ToPath_clogger(tp) RSTRUCT_PTR(tp)[0]
818 #else
819 static ID clogger_id;
820 # define ToPath_clogger(tp) rb_funcall(tp,clogger_id,0)
821 #endif
823 static VALUE to_path(VALUE self)
825 VALUE my_clogger = ToPath_clogger(self);
826 struct clogger *c = clogger_get(my_clogger);
827 VALUE path = rb_funcall(c->body, to_path_id, 0);
828 struct stat sb;
829 int rv;
830 unsigned devfd;
831 const char *cpath;
833 Check_Type(path, T_STRING);
834 cpath = RSTRING_PTR(path);
836 /* try to avoid an extra path lookup */
837 if (rb_respond_to(c->body, to_io_id))
838 rv = fstat(my_fileno(c->body), &sb);
840 * Rainbows! can use "/dev/fd/%u" in to_path output to avoid
841 * extra open() syscalls, too.
843 else if (sscanf(cpath, "/dev/fd/%u", &devfd) == 1)
844 rv = fstat((int)devfd, &sb);
845 else
846 rv = stat(cpath, &sb);
849 * calling this method implies the web server will bypass
850 * the each method where body_bytes_sent is calculated,
851 * so we stat and set that value here.
853 c->body_bytes_sent = rv == 0 ? sb.st_size : 0;
854 return path;
857 void Init_clogger_ext(void)
859 VALUE tmp;
861 ltlt_id = rb_intern("<<");
862 call_id = rb_intern("call");
863 each_id = rb_intern("each");
864 close_id = rb_intern("close");
865 to_i_id = rb_intern("to_i");
866 to_s_id = rb_intern("to_s");
867 size_id = rb_intern("size");
868 sq_brace_id = rb_intern("[]");
869 new_id = rb_intern("new");
870 to_path_id = rb_intern("to_path");
871 to_io_id = rb_intern("to_io");
872 cClogger = rb_define_class("Clogger", rb_cObject);
873 mFormat = rb_define_module_under(cClogger, "Format");
874 rb_define_alloc_func(cClogger, clogger_alloc);
875 rb_define_method(cClogger, "initialize", clogger_init, -1);
876 rb_define_method(cClogger, "initialize_copy", clogger_init_copy, 1);
877 rb_define_method(cClogger, "call", clogger_call, 1);
878 rb_define_method(cClogger, "each", clogger_each, 0);
879 rb_define_method(cClogger, "close", clogger_close, 0);
880 rb_define_method(cClogger, "fileno", clogger_fileno, 0);
881 rb_define_method(cClogger, "wrap_body?", clogger_wrap_body, 0);
882 rb_define_method(cClogger, "reentrant?", clogger_reentrant, 0);
883 CONST_GLOBAL_STR(REMOTE_ADDR);
884 CONST_GLOBAL_STR(HTTP_X_FORWARDED_FOR);
885 CONST_GLOBAL_STR(REQUEST_METHOD);
886 CONST_GLOBAL_STR(PATH_INFO);
887 CONST_GLOBAL_STR(QUERY_STRING);
888 CONST_GLOBAL_STR(REQUEST_URI);
889 CONST_GLOBAL_STR(HTTP_VERSION);
890 CONST_GLOBAL_STR2(rack_errors, "rack.errors");
891 CONST_GLOBAL_STR2(rack_input, "rack.input");
892 CONST_GLOBAL_STR2(rack_multithread, "rack.multithread");
893 CONST_GLOBAL_STR2(dash, "-");
894 CONST_GLOBAL_STR2(space, " ");
895 CONST_GLOBAL_STR2(question_mark, "?");
896 CONST_GLOBAL_STR2(rack_request_cookie_hash, "rack.request.cookie_hash");
898 tmp = rb_const_get(rb_cObject, rb_intern("Rack"));
899 tmp = rb_const_get(tmp, rb_intern("Utils"));
900 cHeaderHash = rb_const_get(tmp, rb_intern("HeaderHash"));
901 cToPath = rb_const_get(cClogger, rb_intern("ToPath"));
902 rb_define_method(cToPath, "to_path", to_path, 0);
903 #ifndef RSTRUCT_PTR
904 clogger_id = rb_intern("clogger");
905 #endif