ext: cleanup -Wshadow warning
[clogger.git] / ext / clogger_ext / clogger.c
blob838f37ce4ddb6d6829a79d43086a36a02e3bb1ef
1 #define _BSD_SOURCE
2 #include <ruby.h>
3 #ifdef HAVE_RUBY_IO_H
4 # include <ruby/io.h>
5 #else
6 # include <rubyio.h>
7 #endif
8 #include <assert.h>
9 #include <unistd.h>
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <sys/time.h>
13 #include <time.h>
14 #include <errno.h>
15 #ifdef HAVE_FCNTL_H
16 # include <fcntl.h>
17 #endif
18 #include "ruby_1_9_compat.h"
20 /* in case _BSD_SOURCE doesn't give us this macro */
21 #ifndef timersub
22 # define timersub(a, b, result) \
23 do { \
24 (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
25 (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
26 if ((result)->tv_usec < 0) { \
27 --(result)->tv_sec; \
28 (result)->tv_usec += 1000000; \
29 } \
30 } while (0)
31 #endif
33 /* give GCC hints for better branch prediction
34 * (we layout branches so that ASCII characters are handled faster) */
35 #if defined(__GNUC__) && (__GNUC__ >= 3)
36 # define likely(x) __builtin_expect (!!(x), 1)
37 # define unlikely(x) __builtin_expect (!!(x), 0)
38 #else
39 # define unlikely(x) (x)
40 # define likely(x) (x)
41 #endif
43 enum clogger_opcode {
44 CL_OP_LITERAL = 0,
45 CL_OP_REQUEST,
46 CL_OP_RESPONSE,
47 CL_OP_SPECIAL,
48 CL_OP_EVAL,
49 CL_OP_TIME_LOCAL,
50 CL_OP_TIME_UTC,
51 CL_OP_REQUEST_TIME,
52 CL_OP_TIME,
53 CL_OP_COOKIE
56 enum clogger_special {
57 CL_SP_body_bytes_sent = 0,
58 CL_SP_status,
59 CL_SP_request,
60 CL_SP_request_length,
61 CL_SP_response_length,
62 CL_SP_ip,
63 CL_SP_pid,
64 CL_SP_request_uri
67 struct clogger {
68 VALUE app;
70 VALUE fmt_ops;
71 VALUE logger;
72 VALUE log_buf;
74 VALUE env;
75 VALUE cookies;
76 VALUE status;
77 VALUE headers;
78 VALUE body;
80 off_t body_bytes_sent;
81 struct timeval tv_start;
83 int fd;
84 int wrap_body;
85 int need_resp;
86 int reentrant; /* tri-state, -1:auto, 1/0 true/false */
89 static ID ltlt_id;
90 static ID call_id;
91 static ID each_id;
92 static ID close_id;
93 static ID to_i_id;
94 static ID to_s_id;
95 static ID size_id;
96 static ID sq_brace_id;
97 static ID new_id;
98 static ID to_path_id;
99 static ID to_io_id;
100 static VALUE cClogger;
101 static VALUE cToPath;
102 static VALUE mFormat;
103 static VALUE cHeaderHash;
105 /* common hash lookup keys */
106 static VALUE g_HTTP_X_FORWARDED_FOR;
107 static VALUE g_REMOTE_ADDR;
108 static VALUE g_REQUEST_METHOD;
109 static VALUE g_PATH_INFO;
110 static VALUE g_REQUEST_URI;
111 static VALUE g_QUERY_STRING;
112 static VALUE g_HTTP_VERSION;
113 static VALUE g_rack_errors;
114 static VALUE g_rack_input;
115 static VALUE g_rack_multithread;
116 static VALUE g_dash;
117 static VALUE g_space;
118 static VALUE g_question_mark;
119 static VALUE g_rack_request_cookie_hash;
121 #define LOG_BUF_INIT_SIZE 128
123 static void init_buffers(struct clogger *c)
125 c->log_buf = rb_str_buf_new(LOG_BUF_INIT_SIZE);
128 static inline int need_escape(unsigned c)
130 assert(c <= 0xff);
131 return !!(c == '\'' || c == '"' || c <= 0x1f);
134 /* we are encoding-agnostic, clients can send us all sorts of junk */
135 static VALUE byte_xs_str(VALUE from)
137 static const char esc[] = "0123456789ABCDEF";
138 unsigned char *new_ptr;
139 unsigned char *ptr = (unsigned char *)RSTRING_PTR(from);
140 long len = RSTRING_LEN(from);
141 long new_len = len;
142 VALUE rv;
144 for (; --len >= 0; ptr++) {
145 unsigned c = *ptr;
147 if (unlikely(need_escape(c)))
148 new_len += 3; /* { '\', 'x', 'X', 'X' } */
151 len = RSTRING_LEN(from);
152 if (new_len == len)
153 return from;
155 rv = rb_str_new(NULL, new_len);
156 new_ptr = (unsigned char *)RSTRING_PTR(rv);
157 ptr = (unsigned char *)RSTRING_PTR(from);
158 for (; --len >= 0; ptr++) {
159 unsigned c = *ptr;
161 if (unlikely(need_escape(c))) {
162 *new_ptr++ = '\\';
163 *new_ptr++ = 'x';
164 *new_ptr++ = esc[c >> 4];
165 *new_ptr++ = esc[c & 0xf];
166 } else {
167 *new_ptr++ = c;
170 assert(RSTRING_PTR(rv)[RSTRING_LEN(rv)] == '\0');
172 return rv;
175 static VALUE byte_xs(VALUE from)
177 return byte_xs_str(rb_obj_as_string(from));
180 static void clogger_mark(void *ptr)
182 struct clogger *c = ptr;
184 rb_gc_mark(c->app);
185 rb_gc_mark(c->fmt_ops);
186 rb_gc_mark(c->logger);
187 rb_gc_mark(c->log_buf);
188 rb_gc_mark(c->env);
189 rb_gc_mark(c->cookies);
190 rb_gc_mark(c->status);
191 rb_gc_mark(c->headers);
192 rb_gc_mark(c->body);
195 static VALUE clogger_alloc(VALUE klass)
197 struct clogger *c;
199 return Data_Make_Struct(klass, struct clogger, clogger_mark, -1, c);
202 static struct clogger *clogger_get(VALUE self)
204 struct clogger *c;
206 Data_Get_Struct(self, struct clogger, c);
207 assert(c);
208 return c;
211 static VALUE obj_fileno(VALUE obj)
213 return rb_funcall(obj, rb_intern("fileno"), 0);
216 static VALUE obj_enable_sync(VALUE obj)
218 return rb_funcall(obj, rb_intern("sync="), 1, Qtrue);
221 /* only for writing to regular files, not stupid crap like NFS */
222 static void write_full(int fd, const void *buf, size_t count)
224 ssize_t r;
225 unsigned long ubuf = (unsigned long)buf;
227 while (count > 0) {
228 r = write(fd, (void *)ubuf, count);
230 if ((size_t)r == count) { /* overwhelmingly likely */
231 return;
232 } else if (r > 0) {
233 count -= r;
234 ubuf += r;
235 } else {
236 if (errno == EINTR || errno == EAGAIN)
237 continue; /* poor souls on NFS and like: */
238 if (!errno)
239 errno = ENOSPC;
240 rb_sys_fail("write");
246 * allow us to use write_full() iff we detect a blocking file
247 * descriptor that wouldn't play nicely with Ruby threading/fibers
249 static int raw_fd(VALUE my_fd)
251 #if defined(HAVE_FCNTL) && defined(F_GETFL) && defined(O_NONBLOCK)
252 int fd;
253 int flags;
255 if (NIL_P(my_fd))
256 return -1;
257 fd = NUM2INT(my_fd);
259 flags = fcntl(fd, F_GETFL);
260 if (flags < 0)
261 rb_sys_fail("fcntl");
263 return (flags & O_NONBLOCK) ? -1 : fd;
264 #else /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
265 return -1;
266 #endif /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
269 /* :nodoc: */
270 static VALUE clogger_reentrant(VALUE self)
272 return clogger_get(self)->reentrant == 0 ? Qfalse : Qtrue;
275 /* :nodoc: */
276 static VALUE clogger_wrap_body(VALUE self)
278 return clogger_get(self)->wrap_body == 0 ? Qfalse : Qtrue;
281 static void append_status(struct clogger *c)
283 char buf[sizeof("999")];
284 int nr;
285 VALUE status = c->status;
287 if (TYPE(status) != T_FIXNUM) {
288 status = rb_funcall(status, to_i_id, 0);
289 /* no way it's a valid status code (at least not HTTP/1.1) */
290 if (TYPE(status) != T_FIXNUM) {
291 rb_str_buf_append(c->log_buf, g_dash);
292 return;
296 nr = FIX2INT(status);
297 if (nr >= 100 && nr <= 999) {
298 nr = snprintf(buf, sizeof(buf), "%03d", nr);
299 assert(nr == 3);
300 rb_str_buf_cat(c->log_buf, buf, nr);
301 } else {
302 /* raise?, swap for 500? */
303 rb_str_buf_append(c->log_buf, g_dash);
307 /* this is Rack 1.0.0-compatible, won't try to parse commas in XFF */
308 static void append_ip(struct clogger *c)
310 VALUE env = c->env;
311 VALUE tmp = rb_hash_aref(env, g_HTTP_X_FORWARDED_FOR);
313 if (NIL_P(tmp)) {
314 /* can't be faked on any real server, so no escape */
315 tmp = rb_hash_aref(env, g_REMOTE_ADDR);
316 if (NIL_P(tmp))
317 tmp = g_dash;
318 } else {
319 tmp = byte_xs(tmp);
321 rb_str_buf_append(c->log_buf, tmp);
324 static void append_body_bytes_sent(struct clogger *c)
326 char buf[(sizeof(off_t) * 8) / 3 + 1];
327 const char *fmt = sizeof(off_t) == sizeof(long) ? "%ld" : "%lld";
328 int nr = snprintf(buf, sizeof(buf), fmt, c->body_bytes_sent);
330 assert(nr > 0 && nr < (int)sizeof(buf));
331 rb_str_buf_cat(c->log_buf, buf, nr);
334 static void append_tv(struct clogger *c, const VALUE *op, struct timeval *tv)
336 char buf[sizeof(".000000") + ((sizeof(tv->tv_sec) * 8) / 3)];
337 int nr;
338 char *fmt = RSTRING_PTR(op[1]);
339 int ndiv = NUM2INT(op[2]);
341 nr = snprintf(buf, sizeof(buf), fmt,
342 (int)tv->tv_sec, (int)(tv->tv_usec / ndiv));
343 assert(nr > 0 && nr < (int)sizeof(buf));
344 rb_str_buf_cat(c->log_buf, buf, nr);
347 static void append_request_time_fmt(struct clogger *c, const VALUE *op)
349 struct timeval now, d;
351 gettimeofday(&now, NULL);
352 timersub(&now, &c->tv_start, &d);
353 append_tv(c, op, &d);
356 static void append_time_fmt(struct clogger *c, const VALUE *op)
358 struct timeval now;
360 gettimeofday(&now, NULL);
361 append_tv(c, op, &now);
364 static void append_request_uri(struct clogger *c)
366 VALUE tmp;
368 tmp = rb_hash_aref(c->env, g_REQUEST_URI);
369 if (NIL_P(tmp)) {
370 tmp = rb_hash_aref(c->env, g_PATH_INFO);
371 if (!NIL_P(tmp))
372 rb_str_buf_append(c->log_buf, byte_xs(tmp));
373 tmp = rb_hash_aref(c->env, g_QUERY_STRING);
374 if (!NIL_P(tmp) && RSTRING_LEN(tmp) != 0) {
375 rb_str_buf_append(c->log_buf, g_question_mark);
376 rb_str_buf_append(c->log_buf, byte_xs(tmp));
378 } else {
379 rb_str_buf_append(c->log_buf, byte_xs(tmp));
383 static void append_request(struct clogger *c)
385 VALUE tmp;
387 /* REQUEST_METHOD doesn't need escaping, Rack::Lint governs it */
388 tmp = rb_hash_aref(c->env, g_REQUEST_METHOD);
389 if (!NIL_P(tmp))
390 rb_str_buf_append(c->log_buf, tmp);
392 rb_str_buf_append(c->log_buf, g_space);
394 append_request_uri(c);
396 /* HTTP_VERSION can be injected by malicious clients */
397 tmp = rb_hash_aref(c->env, g_HTTP_VERSION);
398 if (!NIL_P(tmp)) {
399 rb_str_buf_append(c->log_buf, g_space);
400 rb_str_buf_append(c->log_buf, byte_xs(tmp));
404 static void append_request_length(struct clogger *c)
406 VALUE tmp = rb_hash_aref(c->env, g_rack_input);
407 if (NIL_P(tmp)) {
408 rb_str_buf_append(c->log_buf, g_dash);
409 } else {
410 tmp = rb_funcall(tmp, size_id, 0);
411 rb_str_buf_append(c->log_buf, rb_funcall(tmp, to_s_id, 0));
415 static void append_time(struct clogger *c, enum clogger_opcode op, VALUE fmt)
417 /* you'd have to be a moron to use formats this big... */
418 char buf[sizeof("Saturday, November 01, 1970, 00:00:00 PM +0000")];
419 size_t nr;
420 struct tm tmp;
421 time_t t = time(NULL);
423 if (op == CL_OP_TIME_LOCAL)
424 localtime_r(&t, &tmp);
425 else if (op == CL_OP_TIME_UTC)
426 gmtime_r(&t, &tmp);
427 else
428 assert(0 && "unknown op");
430 nr = strftime(buf, sizeof(buf), RSTRING_PTR(fmt), &tmp);
431 if (nr == 0 || nr == sizeof(buf))
432 rb_str_buf_append(c->log_buf, g_dash);
433 else
434 rb_str_buf_cat(c->log_buf, buf, nr);
437 static void append_pid(struct clogger *c)
439 char buf[(sizeof(pid_t) * 8) / 3 + 1];
440 int nr = snprintf(buf, sizeof(buf), "%d", (int)getpid());
442 assert(nr > 0 && nr < (int)sizeof(buf));
443 rb_str_buf_cat(c->log_buf, buf, nr);
446 static void append_eval(struct clogger *c, VALUE str)
448 int state = -1;
449 VALUE rv = rb_eval_string_protect(RSTRING_PTR(str), &state);
451 rv = state == 0 ? rb_obj_as_string(rv) : g_dash;
452 rb_str_buf_append(c->log_buf, rv);
455 static void append_cookie(struct clogger *c, VALUE key)
457 VALUE cookie;
459 if (c->cookies == Qfalse)
460 c->cookies = rb_hash_aref(c->env, g_rack_request_cookie_hash);
462 if (NIL_P(c->cookies)) {
463 cookie = g_dash;
464 } else {
465 cookie = rb_hash_aref(c->cookies, key);
466 if (NIL_P(cookie))
467 cookie = g_dash;
469 rb_str_buf_append(c->log_buf, cookie);
472 static void append_request_env(struct clogger *c, VALUE key)
474 VALUE tmp = rb_hash_aref(c->env, key);
476 tmp = NIL_P(tmp) ? g_dash : byte_xs(tmp);
477 rb_str_buf_append(c->log_buf, tmp);
480 static void append_response(struct clogger *c, VALUE key)
482 VALUE v;
484 assert(rb_obj_is_kind_of(c->headers, cHeaderHash) && "not HeaderHash");
486 v = rb_funcall(c->headers, sq_brace_id, 1, key);
487 v = NIL_P(v) ? g_dash : byte_xs(v);
488 rb_str_buf_append(c->log_buf, v);
491 static void special_var(struct clogger *c, enum clogger_special var)
493 switch (var) {
494 case CL_SP_body_bytes_sent:
495 append_body_bytes_sent(c);
496 break;
497 case CL_SP_status:
498 append_status(c);
499 break;
500 case CL_SP_request:
501 append_request(c);
502 break;
503 case CL_SP_request_length:
504 append_request_length(c);
505 break;
506 case CL_SP_response_length:
507 if (c->body_bytes_sent == 0)
508 rb_str_buf_append(c->log_buf, g_dash);
509 else
510 append_body_bytes_sent(c);
511 break;
512 case CL_SP_ip:
513 append_ip(c);
514 break;
515 case CL_SP_pid:
516 append_pid(c);
517 break;
518 case CL_SP_request_uri:
519 append_request_uri(c);
523 static VALUE cwrite(struct clogger *c)
525 const VALUE ops = c->fmt_ops;
526 const VALUE *ary = RARRAY_PTR(ops);
527 long i = RARRAY_LEN(ops);
528 VALUE dst = c->log_buf;
530 rb_str_set_len(dst, 0);
532 for (; --i >= 0; ary++) {
533 const VALUE *op = RARRAY_PTR(*ary);
534 enum clogger_opcode opcode = FIX2INT(op[0]);
536 switch (opcode) {
537 case CL_OP_LITERAL:
538 rb_str_buf_append(dst, op[1]);
539 break;
540 case CL_OP_REQUEST:
541 append_request_env(c, op[1]);
542 break;
543 case CL_OP_RESPONSE:
544 append_response(c, op[1]);
545 break;
546 case CL_OP_SPECIAL:
547 special_var(c, FIX2INT(op[1]));
548 break;
549 case CL_OP_EVAL:
550 append_eval(c, op[1]);
551 break;
552 case CL_OP_TIME_LOCAL:
553 case CL_OP_TIME_UTC:
554 append_time(c, opcode, op[1]);
555 break;
556 case CL_OP_REQUEST_TIME:
557 append_request_time_fmt(c, op);
558 break;
559 case CL_OP_TIME:
560 append_time_fmt(c, op);
561 break;
562 case CL_OP_COOKIE:
563 append_cookie(c, op[1]);
564 break;
568 if (c->fd >= 0) {
569 write_full(c->fd, RSTRING_PTR(dst), RSTRING_LEN(dst));
570 } else {
571 VALUE logger = c->logger;
573 if (NIL_P(logger))
574 logger = rb_hash_aref(c->env, g_rack_errors);
575 rb_funcall(logger, ltlt_id, 1, dst);
578 return Qnil;
582 * call-seq:
583 * Clogger.new(app, :logger => $stderr, :format => string) => obj
585 * Creates a new Clogger object that wraps +app+. +:logger+ may
586 * be any object that responds to the "<<" method with a string argument.
588 static VALUE clogger_init(int argc, VALUE *argv, VALUE self)
590 struct clogger *c = clogger_get(self);
591 VALUE o = Qnil;
592 VALUE fmt = rb_const_get(mFormat, rb_intern("Common"));
594 rb_scan_args(argc, argv, "11", &c->app, &o);
595 c->fd = -1;
596 c->logger = Qnil;
597 c->reentrant = -1; /* auto-detect */
599 if (TYPE(o) == T_HASH) {
600 VALUE tmp;
602 c->logger = rb_hash_aref(o, ID2SYM(rb_intern("logger")));
603 if (!NIL_P(c->logger)) {
604 rb_rescue(obj_enable_sync, c->logger, NULL, 0);
605 tmp = rb_rescue(obj_fileno, c->logger, NULL, 0);
606 c->fd = raw_fd(tmp);
609 tmp = rb_hash_aref(o, ID2SYM(rb_intern("format")));
610 if (!NIL_P(tmp))
611 fmt = tmp;
613 tmp = rb_hash_aref(o, ID2SYM(rb_intern("reentrant")));
614 switch (TYPE(tmp)) {
615 case T_TRUE:
616 c->reentrant = 1;
617 break;
618 case T_FALSE:
619 c->reentrant = 0;
620 case T_NIL:
621 break;
622 default:
623 rb_raise(rb_eArgError, ":reentrant must be boolean");
627 init_buffers(c);
628 c->fmt_ops = rb_funcall(self, rb_intern("compile_format"), 2, fmt, o);
630 if (Qtrue == rb_funcall(self, rb_intern("need_response_headers?"),
631 1, c->fmt_ops))
632 c->need_resp = 1;
633 if (Qtrue == rb_funcall(self, rb_intern("need_wrap_body?"),
634 1, c->fmt_ops))
635 c->wrap_body = 1;
637 return self;
640 static VALUE body_iter_i(VALUE str, VALUE memop)
642 off_t *len = (off_t *)memop;
644 str = rb_obj_as_string(str);
645 *len += RSTRING_LEN(str);
647 return rb_yield(str);
650 static VALUE body_close(struct clogger *c)
652 if (rb_respond_to(c->body, close_id))
653 return rb_funcall(c->body, close_id, 0);
654 return Qnil;
658 * call-seq:
659 * clogger.each { |part| socket.write(part) }
661 * Delegates the body#each call to the underlying +body+ object
662 * while tracking the number of bytes yielded. This will log
663 * the request.
665 static VALUE clogger_each(VALUE self)
667 struct clogger *c = clogger_get(self);
669 rb_need_block();
670 c->body_bytes_sent = 0;
671 rb_iterate(rb_each, c->body, body_iter_i, (VALUE)&c->body_bytes_sent);
673 return self;
677 * call-seq:
678 * clogger.close
680 * Delegates the body#close call to the underlying +body+ object.
681 * This is only used when Clogger is wrapping the +body+ of a Rack
682 * response and should be automatically called by the web server.
684 static VALUE clogger_close(VALUE self)
686 struct clogger *c = clogger_get(self);
688 return rb_ensure(body_close, (VALUE)c, cwrite, (VALUE)c);
691 /* :nodoc: */
692 static VALUE clogger_fileno(VALUE self)
694 struct clogger *c = clogger_get(self);
696 return c->fd < 0 ? Qnil : INT2NUM(c->fd);
699 static VALUE ccall(struct clogger *c, VALUE env)
701 VALUE rv;
703 gettimeofday(&c->tv_start, NULL);
704 c->env = env;
705 c->cookies = Qfalse;
706 rv = rb_funcall(c->app, call_id, 1, env);
707 if (TYPE(rv) == T_ARRAY && RARRAY_LEN(rv) == 3) {
708 VALUE *tmp = RARRAY_PTR(rv);
710 c->status = tmp[0];
711 c->headers = tmp[1];
712 c->body = tmp[2];
714 rv = rb_ary_new4(3, tmp);
715 if (c->need_resp &&
716 ! rb_obj_is_kind_of(tmp[1], cHeaderHash)) {
717 c->headers = rb_funcall(cHeaderHash, new_id, 1, tmp[1]);
718 rb_ary_store(rv, 1, c->headers);
720 } else {
721 volatile VALUE tmp = rb_inspect(rv);
723 c->status = INT2FIX(500);
724 c->headers = c->body = rb_ary_new();
725 cwrite(c);
726 rb_raise(rb_eTypeError,
727 "app response not a 3 element Array: %s",
728 RSTRING_PTR(tmp));
731 return rv;
735 * call-seq:
736 * clogger.call(env) => [ status, headers, body ]
738 * calls the wrapped Rack application with +env+, returns the
739 * [status, headers, body ] tuplet required by Rack.
741 static VALUE clogger_call(VALUE self, VALUE env)
743 struct clogger *c = clogger_get(self);
744 VALUE rv;
746 env = rb_check_convert_type(env, T_HASH, "Hash", "to_hash");
748 if (c->wrap_body) {
749 if (c->reentrant < 0) {
750 VALUE tmp = rb_hash_aref(env, g_rack_multithread);
751 c->reentrant = Qfalse == tmp ? 0 : 1;
753 if (c->reentrant) {
754 self = rb_obj_dup(self);
755 c = clogger_get(self);
758 rv = ccall(c, env);
759 assert(!OBJ_FROZEN(rv) && "frozen response array");
761 if (rb_respond_to(c->body, to_path_id))
762 self = rb_funcall(cToPath, new_id, 1, self);
763 rb_ary_store(rv, 2, self);
765 return rv;
768 rv = ccall(c, env);
769 cwrite(c);
771 return rv;
774 /* :nodoc */
775 static VALUE clogger_init_copy(VALUE clone, VALUE orig)
777 struct clogger *a = clogger_get(orig);
778 struct clogger *b = clogger_get(clone);
780 memcpy(b, a, sizeof(struct clogger));
781 init_buffers(b);
783 return clone;
786 #define CONST_GLOBAL_STR2(var, val) do { \
787 g_##var = rb_obj_freeze(rb_str_new(val, sizeof(val) - 1)); \
788 rb_global_variable(&g_##var); \
789 } while (0)
791 #define CONST_GLOBAL_STR(val) CONST_GLOBAL_STR2(val, #val)
793 static VALUE to_path(VALUE self)
795 struct clogger *c = clogger_get(RSTRUCT_PTR(self)[0]);
796 VALUE path = rb_funcall(c->body, to_path_id, 0);
797 struct stat sb;
798 int rv;
799 const char *cpath;
801 Check_Type(path, T_STRING);
802 cpath = RSTRING_PTR(path);
804 /* try to avoid an extra path lookup */
805 if (rb_respond_to(c->body, to_io_id))
806 rv = fstat(my_fileno(c->body), &sb);
808 * Rainbows! can use "/dev/fd/%d" in to_path output to avoid
809 * extra open() syscalls, too.
811 else if (sscanf(cpath, "/dev/fd/%d", &rv) == 1)
812 rv = fstat(rv, &sb);
813 else
814 rv = stat(cpath, &sb);
816 c->body_bytes_sent = rv == 0 ? sb.st_size : 0;
817 return path;
820 void Init_clogger_ext(void)
822 VALUE tmp;
824 ltlt_id = rb_intern("<<");
825 call_id = rb_intern("call");
826 each_id = rb_intern("each");
827 close_id = rb_intern("close");
828 to_i_id = rb_intern("to_i");
829 to_s_id = rb_intern("to_s");
830 size_id = rb_intern("size");
831 sq_brace_id = rb_intern("[]");
832 new_id = rb_intern("new");
833 to_path_id = rb_intern("to_path");
834 to_io_id = rb_intern("to_io");
835 cClogger = rb_define_class("Clogger", rb_cObject);
836 mFormat = rb_define_module_under(cClogger, "Format");
837 rb_define_alloc_func(cClogger, clogger_alloc);
838 rb_define_method(cClogger, "initialize", clogger_init, -1);
839 rb_define_method(cClogger, "initialize_copy", clogger_init_copy, 1);
840 rb_define_method(cClogger, "call", clogger_call, 1);
841 rb_define_method(cClogger, "each", clogger_each, 0);
842 rb_define_method(cClogger, "close", clogger_close, 0);
843 rb_define_method(cClogger, "fileno", clogger_fileno, 0);
844 rb_define_method(cClogger, "wrap_body?", clogger_wrap_body, 0);
845 rb_define_method(cClogger, "reentrant?", clogger_reentrant, 0);
846 CONST_GLOBAL_STR(REMOTE_ADDR);
847 CONST_GLOBAL_STR(HTTP_X_FORWARDED_FOR);
848 CONST_GLOBAL_STR(REQUEST_METHOD);
849 CONST_GLOBAL_STR(PATH_INFO);
850 CONST_GLOBAL_STR(QUERY_STRING);
851 CONST_GLOBAL_STR(REQUEST_URI);
852 CONST_GLOBAL_STR(HTTP_VERSION);
853 CONST_GLOBAL_STR2(rack_errors, "rack.errors");
854 CONST_GLOBAL_STR2(rack_input, "rack.input");
855 CONST_GLOBAL_STR2(rack_multithread, "rack.multithread");
856 CONST_GLOBAL_STR2(dash, "-");
857 CONST_GLOBAL_STR2(space, " ");
858 CONST_GLOBAL_STR2(question_mark, "?");
859 CONST_GLOBAL_STR2(rack_request_cookie_hash, "rack.request.cookie_hash");
861 tmp = rb_const_get(rb_cObject, rb_intern("Rack"));
862 tmp = rb_const_get(tmp, rb_intern("Utils"));
863 cHeaderHash = rb_const_get(tmp, rb_intern("HeaderHash"));
864 cToPath = rb_const_get(cClogger, rb_intern("ToPath"));
865 rb_define_method(cToPath, "to_path", to_path, 0);