ext: handle weird regular files with O_NONBLOCK set
[clogger.git] / ext / clogger_ext / clogger.c
blob834debeba15d93d59454d3c8d8b233195b4b917a
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 if (flags & O_NONBLOCK) {
264 struct stat sb;
266 if (fstat(fd, &sb) < 0)
267 return -1;
269 /* O_NONBLOCK is no-op for regular files: */
270 if (! S_ISREG(sb.st_mode))
271 return -1;
273 return fd;
274 #else /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
275 return -1;
276 #endif /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
279 /* :nodoc: */
280 static VALUE clogger_reentrant(VALUE self)
282 return clogger_get(self)->reentrant == 0 ? Qfalse : Qtrue;
285 /* :nodoc: */
286 static VALUE clogger_wrap_body(VALUE self)
288 return clogger_get(self)->wrap_body == 0 ? Qfalse : Qtrue;
291 static void append_status(struct clogger *c)
293 char buf[sizeof("999")];
294 int nr;
295 VALUE status = c->status;
297 if (TYPE(status) != T_FIXNUM) {
298 status = rb_funcall(status, to_i_id, 0);
299 /* no way it's a valid status code (at least not HTTP/1.1) */
300 if (TYPE(status) != T_FIXNUM) {
301 rb_str_buf_append(c->log_buf, g_dash);
302 return;
306 nr = FIX2INT(status);
307 if (nr >= 100 && nr <= 999) {
308 nr = snprintf(buf, sizeof(buf), "%03d", nr);
309 assert(nr == 3);
310 rb_str_buf_cat(c->log_buf, buf, nr);
311 } else {
312 /* raise?, swap for 500? */
313 rb_str_buf_append(c->log_buf, g_dash);
317 /* this is Rack 1.0.0-compatible, won't try to parse commas in XFF */
318 static void append_ip(struct clogger *c)
320 VALUE env = c->env;
321 VALUE tmp = rb_hash_aref(env, g_HTTP_X_FORWARDED_FOR);
323 if (NIL_P(tmp)) {
324 /* can't be faked on any real server, so no escape */
325 tmp = rb_hash_aref(env, g_REMOTE_ADDR);
326 if (NIL_P(tmp))
327 tmp = g_dash;
328 } else {
329 tmp = byte_xs(tmp);
331 rb_str_buf_append(c->log_buf, tmp);
334 static void append_body_bytes_sent(struct clogger *c)
336 char buf[(sizeof(off_t) * 8) / 3 + 1];
337 const char *fmt = sizeof(off_t) == sizeof(long) ? "%ld" : "%lld";
338 int nr = snprintf(buf, sizeof(buf), fmt, c->body_bytes_sent);
340 assert(nr > 0 && nr < (int)sizeof(buf));
341 rb_str_buf_cat(c->log_buf, buf, nr);
344 static void append_tv(struct clogger *c, const VALUE *op, struct timeval *tv)
346 char buf[sizeof(".000000") + ((sizeof(tv->tv_sec) * 8) / 3)];
347 int nr;
348 char *fmt = RSTRING_PTR(op[1]);
349 int ndiv = NUM2INT(op[2]);
351 nr = snprintf(buf, sizeof(buf), fmt,
352 (int)tv->tv_sec, (int)(tv->tv_usec / ndiv));
353 assert(nr > 0 && nr < (int)sizeof(buf));
354 rb_str_buf_cat(c->log_buf, buf, nr);
357 static void append_request_time_fmt(struct clogger *c, const VALUE *op)
359 struct timeval now, d;
361 gettimeofday(&now, NULL);
362 timersub(&now, &c->tv_start, &d);
363 append_tv(c, op, &d);
366 static void append_time_fmt(struct clogger *c, const VALUE *op)
368 struct timeval now;
370 gettimeofday(&now, NULL);
371 append_tv(c, op, &now);
374 static void append_request_uri(struct clogger *c)
376 VALUE tmp;
378 tmp = rb_hash_aref(c->env, g_REQUEST_URI);
379 if (NIL_P(tmp)) {
380 tmp = rb_hash_aref(c->env, g_PATH_INFO);
381 if (!NIL_P(tmp))
382 rb_str_buf_append(c->log_buf, byte_xs(tmp));
383 tmp = rb_hash_aref(c->env, g_QUERY_STRING);
384 if (!NIL_P(tmp) && RSTRING_LEN(tmp) != 0) {
385 rb_str_buf_append(c->log_buf, g_question_mark);
386 rb_str_buf_append(c->log_buf, byte_xs(tmp));
388 } else {
389 rb_str_buf_append(c->log_buf, byte_xs(tmp));
393 static void append_request(struct clogger *c)
395 VALUE tmp;
397 /* REQUEST_METHOD doesn't need escaping, Rack::Lint governs it */
398 tmp = rb_hash_aref(c->env, g_REQUEST_METHOD);
399 if (!NIL_P(tmp))
400 rb_str_buf_append(c->log_buf, tmp);
402 rb_str_buf_append(c->log_buf, g_space);
404 append_request_uri(c);
406 /* HTTP_VERSION can be injected by malicious clients */
407 tmp = rb_hash_aref(c->env, g_HTTP_VERSION);
408 if (!NIL_P(tmp)) {
409 rb_str_buf_append(c->log_buf, g_space);
410 rb_str_buf_append(c->log_buf, byte_xs(tmp));
414 static void append_request_length(struct clogger *c)
416 VALUE tmp = rb_hash_aref(c->env, g_rack_input);
417 if (NIL_P(tmp)) {
418 rb_str_buf_append(c->log_buf, g_dash);
419 } else {
420 tmp = rb_funcall(tmp, size_id, 0);
421 rb_str_buf_append(c->log_buf, rb_funcall(tmp, to_s_id, 0));
425 static void append_time(struct clogger *c, enum clogger_opcode op, VALUE fmt)
427 /* you'd have to be a moron to use formats this big... */
428 char buf[sizeof("Saturday, November 01, 1970, 00:00:00 PM +0000")];
429 size_t nr;
430 struct tm tmp;
431 time_t t = time(NULL);
433 if (op == CL_OP_TIME_LOCAL)
434 localtime_r(&t, &tmp);
435 else if (op == CL_OP_TIME_UTC)
436 gmtime_r(&t, &tmp);
437 else
438 assert(0 && "unknown op");
440 nr = strftime(buf, sizeof(buf), RSTRING_PTR(fmt), &tmp);
441 if (nr == 0 || nr == sizeof(buf))
442 rb_str_buf_append(c->log_buf, g_dash);
443 else
444 rb_str_buf_cat(c->log_buf, buf, nr);
447 static void append_pid(struct clogger *c)
449 char buf[(sizeof(pid_t) * 8) / 3 + 1];
450 int nr = snprintf(buf, sizeof(buf), "%d", (int)getpid());
452 assert(nr > 0 && nr < (int)sizeof(buf));
453 rb_str_buf_cat(c->log_buf, buf, nr);
456 static void append_eval(struct clogger *c, VALUE str)
458 int state = -1;
459 VALUE rv = rb_eval_string_protect(RSTRING_PTR(str), &state);
461 rv = state == 0 ? rb_obj_as_string(rv) : g_dash;
462 rb_str_buf_append(c->log_buf, rv);
465 static void append_cookie(struct clogger *c, VALUE key)
467 VALUE cookie;
469 if (c->cookies == Qfalse)
470 c->cookies = rb_hash_aref(c->env, g_rack_request_cookie_hash);
472 if (NIL_P(c->cookies)) {
473 cookie = g_dash;
474 } else {
475 cookie = rb_hash_aref(c->cookies, key);
476 if (NIL_P(cookie))
477 cookie = g_dash;
479 rb_str_buf_append(c->log_buf, cookie);
482 static void append_request_env(struct clogger *c, VALUE key)
484 VALUE tmp = rb_hash_aref(c->env, key);
486 tmp = NIL_P(tmp) ? g_dash : byte_xs(tmp);
487 rb_str_buf_append(c->log_buf, tmp);
490 static void append_response(struct clogger *c, VALUE key)
492 VALUE v;
494 assert(rb_obj_is_kind_of(c->headers, cHeaderHash) && "not HeaderHash");
496 v = rb_funcall(c->headers, sq_brace_id, 1, key);
497 v = NIL_P(v) ? g_dash : byte_xs(v);
498 rb_str_buf_append(c->log_buf, v);
501 static void special_var(struct clogger *c, enum clogger_special var)
503 switch (var) {
504 case CL_SP_body_bytes_sent:
505 append_body_bytes_sent(c);
506 break;
507 case CL_SP_status:
508 append_status(c);
509 break;
510 case CL_SP_request:
511 append_request(c);
512 break;
513 case CL_SP_request_length:
514 append_request_length(c);
515 break;
516 case CL_SP_response_length:
517 if (c->body_bytes_sent == 0)
518 rb_str_buf_append(c->log_buf, g_dash);
519 else
520 append_body_bytes_sent(c);
521 break;
522 case CL_SP_ip:
523 append_ip(c);
524 break;
525 case CL_SP_pid:
526 append_pid(c);
527 break;
528 case CL_SP_request_uri:
529 append_request_uri(c);
533 static VALUE cwrite(struct clogger *c)
535 const VALUE ops = c->fmt_ops;
536 const VALUE *ary = RARRAY_PTR(ops);
537 long i = RARRAY_LEN(ops);
538 VALUE dst = c->log_buf;
540 rb_str_set_len(dst, 0);
542 for (; --i >= 0; ary++) {
543 const VALUE *op = RARRAY_PTR(*ary);
544 enum clogger_opcode opcode = FIX2INT(op[0]);
546 switch (opcode) {
547 case CL_OP_LITERAL:
548 rb_str_buf_append(dst, op[1]);
549 break;
550 case CL_OP_REQUEST:
551 append_request_env(c, op[1]);
552 break;
553 case CL_OP_RESPONSE:
554 append_response(c, op[1]);
555 break;
556 case CL_OP_SPECIAL:
557 special_var(c, FIX2INT(op[1]));
558 break;
559 case CL_OP_EVAL:
560 append_eval(c, op[1]);
561 break;
562 case CL_OP_TIME_LOCAL:
563 case CL_OP_TIME_UTC:
564 append_time(c, opcode, op[1]);
565 break;
566 case CL_OP_REQUEST_TIME:
567 append_request_time_fmt(c, op);
568 break;
569 case CL_OP_TIME:
570 append_time_fmt(c, op);
571 break;
572 case CL_OP_COOKIE:
573 append_cookie(c, op[1]);
574 break;
578 if (c->fd >= 0) {
579 write_full(c->fd, RSTRING_PTR(dst), RSTRING_LEN(dst));
580 } else {
581 VALUE logger = c->logger;
583 if (NIL_P(logger))
584 logger = rb_hash_aref(c->env, g_rack_errors);
585 rb_funcall(logger, ltlt_id, 1, dst);
588 return Qnil;
592 * call-seq:
593 * Clogger.new(app, :logger => $stderr, :format => string) => obj
595 * Creates a new Clogger object that wraps +app+. +:logger+ may
596 * be any object that responds to the "<<" method with a string argument.
598 static VALUE clogger_init(int argc, VALUE *argv, VALUE self)
600 struct clogger *c = clogger_get(self);
601 VALUE o = Qnil;
602 VALUE fmt = rb_const_get(mFormat, rb_intern("Common"));
604 rb_scan_args(argc, argv, "11", &c->app, &o);
605 c->fd = -1;
606 c->logger = Qnil;
607 c->reentrant = -1; /* auto-detect */
609 if (TYPE(o) == T_HASH) {
610 VALUE tmp;
612 c->logger = rb_hash_aref(o, ID2SYM(rb_intern("logger")));
613 if (!NIL_P(c->logger)) {
614 rb_rescue(obj_enable_sync, c->logger, NULL, 0);
615 tmp = rb_rescue(obj_fileno, c->logger, NULL, 0);
616 c->fd = raw_fd(tmp);
619 tmp = rb_hash_aref(o, ID2SYM(rb_intern("format")));
620 if (!NIL_P(tmp))
621 fmt = tmp;
623 tmp = rb_hash_aref(o, ID2SYM(rb_intern("reentrant")));
624 switch (TYPE(tmp)) {
625 case T_TRUE:
626 c->reentrant = 1;
627 break;
628 case T_FALSE:
629 c->reentrant = 0;
630 case T_NIL:
631 break;
632 default:
633 rb_raise(rb_eArgError, ":reentrant must be boolean");
637 init_buffers(c);
638 c->fmt_ops = rb_funcall(self, rb_intern("compile_format"), 2, fmt, o);
640 if (Qtrue == rb_funcall(self, rb_intern("need_response_headers?"),
641 1, c->fmt_ops))
642 c->need_resp = 1;
643 if (Qtrue == rb_funcall(self, rb_intern("need_wrap_body?"),
644 1, c->fmt_ops))
645 c->wrap_body = 1;
647 return self;
650 static VALUE body_iter_i(VALUE str, VALUE memop)
652 off_t *len = (off_t *)memop;
654 str = rb_obj_as_string(str);
655 *len += RSTRING_LEN(str);
657 return rb_yield(str);
660 static VALUE body_close(struct clogger *c)
662 if (rb_respond_to(c->body, close_id))
663 return rb_funcall(c->body, close_id, 0);
664 return Qnil;
668 * call-seq:
669 * clogger.each { |part| socket.write(part) }
671 * Delegates the body#each call to the underlying +body+ object
672 * while tracking the number of bytes yielded. This will log
673 * the request.
675 static VALUE clogger_each(VALUE self)
677 struct clogger *c = clogger_get(self);
679 rb_need_block();
680 c->body_bytes_sent = 0;
681 rb_iterate(rb_each, c->body, body_iter_i, (VALUE)&c->body_bytes_sent);
683 return self;
687 * call-seq:
688 * clogger.close
690 * Delegates the body#close call to the underlying +body+ object.
691 * This is only used when Clogger is wrapping the +body+ of a Rack
692 * response and should be automatically called by the web server.
694 static VALUE clogger_close(VALUE self)
696 struct clogger *c = clogger_get(self);
698 return rb_ensure(body_close, (VALUE)c, cwrite, (VALUE)c);
701 /* :nodoc: */
702 static VALUE clogger_fileno(VALUE self)
704 struct clogger *c = clogger_get(self);
706 return c->fd < 0 ? Qnil : INT2NUM(c->fd);
709 static VALUE ccall(struct clogger *c, VALUE env)
711 VALUE rv;
713 gettimeofday(&c->tv_start, NULL);
714 c->env = env;
715 c->cookies = Qfalse;
716 rv = rb_funcall(c->app, call_id, 1, env);
717 if (TYPE(rv) == T_ARRAY && RARRAY_LEN(rv) == 3) {
718 VALUE *tmp = RARRAY_PTR(rv);
720 c->status = tmp[0];
721 c->headers = tmp[1];
722 c->body = tmp[2];
724 rv = rb_ary_new4(3, tmp);
725 if (c->need_resp &&
726 ! rb_obj_is_kind_of(tmp[1], cHeaderHash)) {
727 c->headers = rb_funcall(cHeaderHash, new_id, 1, tmp[1]);
728 rb_ary_store(rv, 1, c->headers);
730 } else {
731 volatile VALUE tmp = rb_inspect(rv);
733 c->status = INT2FIX(500);
734 c->headers = c->body = rb_ary_new();
735 cwrite(c);
736 rb_raise(rb_eTypeError,
737 "app response not a 3 element Array: %s",
738 RSTRING_PTR(tmp));
741 return rv;
745 * call-seq:
746 * clogger.call(env) => [ status, headers, body ]
748 * calls the wrapped Rack application with +env+, returns the
749 * [status, headers, body ] tuplet required by Rack.
751 static VALUE clogger_call(VALUE self, VALUE env)
753 struct clogger *c = clogger_get(self);
754 VALUE rv;
756 env = rb_check_convert_type(env, T_HASH, "Hash", "to_hash");
758 if (c->wrap_body) {
759 if (c->reentrant < 0) {
760 VALUE tmp = rb_hash_aref(env, g_rack_multithread);
761 c->reentrant = Qfalse == tmp ? 0 : 1;
763 if (c->reentrant) {
764 self = rb_obj_dup(self);
765 c = clogger_get(self);
768 rv = ccall(c, env);
769 assert(!OBJ_FROZEN(rv) && "frozen response array");
771 if (rb_respond_to(c->body, to_path_id))
772 self = rb_funcall(cToPath, new_id, 1, self);
773 rb_ary_store(rv, 2, self);
775 return rv;
778 rv = ccall(c, env);
779 cwrite(c);
781 return rv;
784 /* :nodoc */
785 static VALUE clogger_init_copy(VALUE clone, VALUE orig)
787 struct clogger *a = clogger_get(orig);
788 struct clogger *b = clogger_get(clone);
790 memcpy(b, a, sizeof(struct clogger));
791 init_buffers(b);
793 return clone;
796 #define CONST_GLOBAL_STR2(var, val) do { \
797 g_##var = rb_obj_freeze(rb_str_new(val, sizeof(val) - 1)); \
798 rb_global_variable(&g_##var); \
799 } while (0)
801 #define CONST_GLOBAL_STR(val) CONST_GLOBAL_STR2(val, #val)
803 static VALUE to_path(VALUE self)
805 struct clogger *c = clogger_get(RSTRUCT_PTR(self)[0]);
806 VALUE path = rb_funcall(c->body, to_path_id, 0);
807 struct stat sb;
808 int rv;
809 const char *cpath;
811 Check_Type(path, T_STRING);
812 cpath = RSTRING_PTR(path);
814 /* try to avoid an extra path lookup */
815 if (rb_respond_to(c->body, to_io_id))
816 rv = fstat(my_fileno(c->body), &sb);
818 * Rainbows! can use "/dev/fd/%d" in to_path output to avoid
819 * extra open() syscalls, too.
821 else if (sscanf(cpath, "/dev/fd/%d", &rv) == 1)
822 rv = fstat(rv, &sb);
823 else
824 rv = stat(cpath, &sb);
826 c->body_bytes_sent = rv == 0 ? sb.st_size : 0;
827 return path;
830 void Init_clogger_ext(void)
832 VALUE tmp;
834 ltlt_id = rb_intern("<<");
835 call_id = rb_intern("call");
836 each_id = rb_intern("each");
837 close_id = rb_intern("close");
838 to_i_id = rb_intern("to_i");
839 to_s_id = rb_intern("to_s");
840 size_id = rb_intern("size");
841 sq_brace_id = rb_intern("[]");
842 new_id = rb_intern("new");
843 to_path_id = rb_intern("to_path");
844 to_io_id = rb_intern("to_io");
845 cClogger = rb_define_class("Clogger", rb_cObject);
846 mFormat = rb_define_module_under(cClogger, "Format");
847 rb_define_alloc_func(cClogger, clogger_alloc);
848 rb_define_method(cClogger, "initialize", clogger_init, -1);
849 rb_define_method(cClogger, "initialize_copy", clogger_init_copy, 1);
850 rb_define_method(cClogger, "call", clogger_call, 1);
851 rb_define_method(cClogger, "each", clogger_each, 0);
852 rb_define_method(cClogger, "close", clogger_close, 0);
853 rb_define_method(cClogger, "fileno", clogger_fileno, 0);
854 rb_define_method(cClogger, "wrap_body?", clogger_wrap_body, 0);
855 rb_define_method(cClogger, "reentrant?", clogger_reentrant, 0);
856 CONST_GLOBAL_STR(REMOTE_ADDR);
857 CONST_GLOBAL_STR(HTTP_X_FORWARDED_FOR);
858 CONST_GLOBAL_STR(REQUEST_METHOD);
859 CONST_GLOBAL_STR(PATH_INFO);
860 CONST_GLOBAL_STR(QUERY_STRING);
861 CONST_GLOBAL_STR(REQUEST_URI);
862 CONST_GLOBAL_STR(HTTP_VERSION);
863 CONST_GLOBAL_STR2(rack_errors, "rack.errors");
864 CONST_GLOBAL_STR2(rack_input, "rack.input");
865 CONST_GLOBAL_STR2(rack_multithread, "rack.multithread");
866 CONST_GLOBAL_STR2(dash, "-");
867 CONST_GLOBAL_STR2(space, " ");
868 CONST_GLOBAL_STR2(question_mark, "?");
869 CONST_GLOBAL_STR2(rack_request_cookie_hash, "rack.request.cookie_hash");
871 tmp = rb_const_get(rb_cObject, rb_intern("Rack"));
872 tmp = rb_const_get(tmp, rb_intern("Utils"));
873 cHeaderHash = rb_const_get(tmp, rb_intern("HeaderHash"));
874 cToPath = rb_const_get(cClogger, rb_intern("ToPath"));
875 rb_define_method(cToPath, "to_path", to_path, 0);