add support for Rubinius
[clogger.git] / ext / clogger_ext / clogger.c
blob4c3d93aa2e9ca13688221411dd8455a8a53c83c3
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 static VALUE obj_fileno(VALUE obj)
210 return rb_funcall(obj, rb_intern("fileno"), 0);
213 /* only for writing to regular files, not stupid crap like NFS */
214 static void write_full(int fd, const void *buf, size_t count)
216 ssize_t r;
217 unsigned long ubuf = (unsigned long)buf;
219 while (count > 0) {
220 r = write(fd, (void *)ubuf, count);
222 if ((size_t)r == count) { /* overwhelmingly likely */
223 return;
224 } else if (r > 0) {
225 count -= r;
226 ubuf += r;
227 } else {
228 if (errno == EINTR || errno == EAGAIN)
229 continue; /* poor souls on NFS and like: */
230 if (!errno)
231 errno = ENOSPC;
232 rb_sys_fail("write");
238 * allow us to use write_full() iff we detect a blocking file
239 * descriptor that wouldn't play nicely with Ruby threading/fibers
241 static int raw_fd(VALUE my_fd)
243 #if defined(HAVE_FCNTL) && defined(F_GETFL) && defined(O_NONBLOCK)
244 int fd;
245 int flags;
247 if (NIL_P(my_fd))
248 return -1;
249 fd = NUM2INT(my_fd);
251 flags = fcntl(fd, F_GETFL);
252 if (flags < 0)
253 rb_sys_fail("fcntl");
255 if (flags & O_NONBLOCK) {
256 struct stat sb;
258 if (fstat(fd, &sb) < 0)
259 return -1;
261 /* O_NONBLOCK is no-op for regular files: */
262 if (! S_ISREG(sb.st_mode))
263 return -1;
265 return fd;
266 #else /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
267 return -1;
268 #endif /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
271 /* :nodoc: */
272 static VALUE clogger_reentrant(VALUE self)
274 return clogger_get(self)->reentrant == 0 ? Qfalse : Qtrue;
277 /* :nodoc: */
278 static VALUE clogger_wrap_body(VALUE self)
280 return clogger_get(self)->wrap_body == 0 ? Qfalse : Qtrue;
283 static void append_status(struct clogger *c)
285 char buf[sizeof("999")];
286 int nr;
287 VALUE status = c->status;
289 if (TYPE(status) != T_FIXNUM) {
290 status = rb_funcall(status, to_i_id, 0);
291 /* no way it's a valid status code (at least not HTTP/1.1) */
292 if (TYPE(status) != T_FIXNUM) {
293 rb_str_buf_append(c->log_buf, g_dash);
294 return;
298 nr = FIX2INT(status);
299 if (nr >= 100 && nr <= 999) {
300 nr = snprintf(buf, sizeof(buf), "%03d", nr);
301 assert(nr == 3);
302 rb_str_buf_cat(c->log_buf, buf, nr);
303 } else {
304 /* raise?, swap for 500? */
305 rb_str_buf_append(c->log_buf, g_dash);
309 /* this is Rack 1.0.0-compatible, won't try to parse commas in XFF */
310 static void append_ip(struct clogger *c)
312 VALUE env = c->env;
313 VALUE tmp = rb_hash_aref(env, g_HTTP_X_FORWARDED_FOR);
315 if (NIL_P(tmp)) {
316 /* can't be faked on any real server, so no escape */
317 tmp = rb_hash_aref(env, g_REMOTE_ADDR);
318 if (NIL_P(tmp))
319 tmp = g_dash;
320 } else {
321 tmp = byte_xs(tmp);
323 rb_str_buf_append(c->log_buf, tmp);
326 static void append_body_bytes_sent(struct clogger *c)
328 char buf[(sizeof(off_t) * 8) / 3 + 1];
329 const char *fmt = sizeof(off_t) == sizeof(long) ? "%ld" : "%lld";
330 int nr = snprintf(buf, sizeof(buf), fmt, c->body_bytes_sent);
332 assert(nr > 0 && nr < (int)sizeof(buf));
333 rb_str_buf_cat(c->log_buf, buf, nr);
336 static void append_ts(struct clogger *c, const VALUE *op, struct timespec *ts)
338 char buf[sizeof(".000000") + ((sizeof(ts->tv_sec) * 8) / 3)];
339 int nr;
340 char *fmt = RSTRING_PTR(op[1]);
341 int ndiv = NUM2INT(op[2]);
342 int usec = ts->tv_nsec / 1000;
344 nr = snprintf(buf, sizeof(buf), fmt,
345 (int)ts->tv_sec, (int)(usec / ndiv));
346 assert(nr > 0 && nr < (int)sizeof(buf));
347 rb_str_buf_cat(c->log_buf, buf, nr);
350 static void append_request_time_fmt(struct clogger *c, const VALUE *op)
352 struct timespec now;
353 int r = clock_gettime(CLOCK_MONOTONIC, &now);
355 if (unlikely(r != 0))
356 rb_sys_fail("clock_gettime(CLOCK_MONONTONIC)");
358 clock_diff(&now, &c->ts_start);
359 append_ts(c, op, &now);
362 static void append_time_fmt(struct clogger *c, const VALUE *op)
364 struct timespec now;
365 int r = clock_gettime(CLOCK_REALTIME, &now);
367 if (unlikely(r != 0))
368 rb_sys_fail("clock_gettime(CLOCK_REALTIME)");
369 append_ts(c, op, &now);
372 static void append_request_uri(struct clogger *c)
374 VALUE tmp;
376 tmp = rb_hash_aref(c->env, g_REQUEST_URI);
377 if (NIL_P(tmp)) {
378 tmp = rb_hash_aref(c->env, g_PATH_INFO);
379 if (!NIL_P(tmp))
380 rb_str_buf_append(c->log_buf, byte_xs(tmp));
381 tmp = rb_hash_aref(c->env, g_QUERY_STRING);
382 if (!NIL_P(tmp) && RSTRING_LEN(tmp) != 0) {
383 rb_str_buf_append(c->log_buf, g_question_mark);
384 rb_str_buf_append(c->log_buf, byte_xs(tmp));
386 } else {
387 rb_str_buf_append(c->log_buf, byte_xs(tmp));
391 static void append_request(struct clogger *c)
393 VALUE tmp;
395 /* REQUEST_METHOD doesn't need escaping, Rack::Lint governs it */
396 tmp = rb_hash_aref(c->env, g_REQUEST_METHOD);
397 if (!NIL_P(tmp))
398 rb_str_buf_append(c->log_buf, tmp);
400 rb_str_buf_append(c->log_buf, g_space);
402 append_request_uri(c);
404 /* HTTP_VERSION can be injected by malicious clients */
405 tmp = rb_hash_aref(c->env, g_HTTP_VERSION);
406 if (!NIL_P(tmp)) {
407 rb_str_buf_append(c->log_buf, g_space);
408 rb_str_buf_append(c->log_buf, byte_xs(tmp));
412 static void append_request_length(struct clogger *c)
414 VALUE tmp = rb_hash_aref(c->env, g_rack_input);
415 if (NIL_P(tmp)) {
416 rb_str_buf_append(c->log_buf, g_dash);
417 } else {
418 tmp = rb_funcall(tmp, size_id, 0);
419 rb_str_buf_append(c->log_buf, rb_funcall(tmp, to_s_id, 0));
423 static void append_time(struct clogger *c, enum clogger_opcode op, VALUE fmt)
425 /* you'd have to be a moron to use formats this big... */
426 char buf[sizeof("Saturday, November 01, 1970, 00:00:00 PM +0000")];
427 size_t nr;
428 struct tm tmp;
429 time_t t = time(NULL);
431 if (op == CL_OP_TIME_LOCAL)
432 localtime_r(&t, &tmp);
433 else if (op == CL_OP_TIME_UTC)
434 gmtime_r(&t, &tmp);
435 else
436 assert(0 && "unknown op");
438 nr = strftime(buf, sizeof(buf), RSTRING_PTR(fmt), &tmp);
439 if (nr == 0 || nr == sizeof(buf))
440 rb_str_buf_append(c->log_buf, g_dash);
441 else
442 rb_str_buf_cat(c->log_buf, buf, nr);
445 static void append_pid(struct clogger *c)
447 char buf[(sizeof(pid_t) * 8) / 3 + 1];
448 int nr = snprintf(buf, sizeof(buf), "%d", (int)getpid());
450 assert(nr > 0 && nr < (int)sizeof(buf));
451 rb_str_buf_cat(c->log_buf, buf, nr);
454 static void append_eval(struct clogger *c, VALUE str)
456 int state = -1;
457 VALUE rv = rb_eval_string_protect(RSTRING_PTR(str), &state);
459 rv = state == 0 ? rb_obj_as_string(rv) : g_dash;
460 rb_str_buf_append(c->log_buf, rv);
463 static void append_cookie(struct clogger *c, VALUE key)
465 VALUE cookie;
467 if (c->cookies == Qfalse)
468 c->cookies = rb_hash_aref(c->env, g_rack_request_cookie_hash);
470 if (NIL_P(c->cookies)) {
471 cookie = g_dash;
472 } else {
473 cookie = rb_hash_aref(c->cookies, key);
474 if (NIL_P(cookie))
475 cookie = g_dash;
477 rb_str_buf_append(c->log_buf, cookie);
480 static void append_request_env(struct clogger *c, VALUE key)
482 VALUE tmp = rb_hash_aref(c->env, key);
484 tmp = NIL_P(tmp) ? g_dash : byte_xs(tmp);
485 rb_str_buf_append(c->log_buf, tmp);
488 static void append_response(struct clogger *c, VALUE key)
490 VALUE v;
492 assert(rb_obj_is_kind_of(c->headers, cHeaderHash) && "not HeaderHash");
494 v = rb_funcall(c->headers, sq_brace_id, 1, key);
495 v = NIL_P(v) ? g_dash : byte_xs(v);
496 rb_str_buf_append(c->log_buf, v);
499 static void special_var(struct clogger *c, enum clogger_special var)
501 switch (var) {
502 case CL_SP_body_bytes_sent:
503 append_body_bytes_sent(c);
504 break;
505 case CL_SP_status:
506 append_status(c);
507 break;
508 case CL_SP_request:
509 append_request(c);
510 break;
511 case CL_SP_request_length:
512 append_request_length(c);
513 break;
514 case CL_SP_response_length:
515 if (c->body_bytes_sent == 0)
516 rb_str_buf_append(c->log_buf, g_dash);
517 else
518 append_body_bytes_sent(c);
519 break;
520 case CL_SP_ip:
521 append_ip(c);
522 break;
523 case CL_SP_pid:
524 append_pid(c);
525 break;
526 case CL_SP_request_uri:
527 append_request_uri(c);
531 static VALUE cwrite(struct clogger *c)
533 const VALUE ops = c->fmt_ops;
534 const VALUE *ary = RARRAY_PTR(ops);
535 long i = RARRAY_LEN(ops);
536 VALUE dst = c->log_buf;
538 rb_str_set_len(dst, 0);
540 for (; --i >= 0; ary++) {
541 const VALUE *op = RARRAY_PTR(*ary);
542 enum clogger_opcode opcode = FIX2INT(op[0]);
544 switch (opcode) {
545 case CL_OP_LITERAL:
546 rb_str_buf_append(dst, op[1]);
547 break;
548 case CL_OP_REQUEST:
549 append_request_env(c, op[1]);
550 break;
551 case CL_OP_RESPONSE:
552 append_response(c, op[1]);
553 break;
554 case CL_OP_SPECIAL:
555 special_var(c, FIX2INT(op[1]));
556 break;
557 case CL_OP_EVAL:
558 append_eval(c, op[1]);
559 break;
560 case CL_OP_TIME_LOCAL:
561 case CL_OP_TIME_UTC:
562 append_time(c, opcode, op[1]);
563 break;
564 case CL_OP_REQUEST_TIME:
565 append_request_time_fmt(c, op);
566 break;
567 case CL_OP_TIME:
568 append_time_fmt(c, op);
569 break;
570 case CL_OP_COOKIE:
571 append_cookie(c, op[1]);
572 break;
576 if (c->fd >= 0) {
577 write_full(c->fd, RSTRING_PTR(dst), RSTRING_LEN(dst));
578 } else {
579 VALUE logger = c->logger;
581 if (NIL_P(logger))
582 logger = rb_hash_aref(c->env, g_rack_errors);
583 rb_funcall(logger, ltlt_id, 1, dst);
586 return Qnil;
589 static void init_logger(struct clogger *c, VALUE path)
591 ID id;
593 if (!NIL_P(path) && !NIL_P(c->logger))
594 rb_raise(rb_eArgError, ":logger and :path are independent");
595 if (!NIL_P(path)) {
596 VALUE ab = rb_str_new2("ab");
597 id = rb_intern("open");
598 c->logger = rb_funcall(rb_cFile, id, 2, path, ab);
601 id = rb_intern("sync=");
602 if (rb_respond_to(c->logger, id))
603 rb_funcall(c->logger, id, 1, Qtrue);
605 id = rb_intern("fileno");
606 if (rb_respond_to(c->logger, id))
607 c->fd = raw_fd(rb_funcall(c->logger, id, 0));
611 * call-seq:
612 * Clogger.new(app, :logger => $stderr, :format => string) => obj
614 * Creates a new Clogger object that wraps +app+. +:logger+ may
615 * be any object that responds to the "<<" method with a string argument.
616 * If +:logger:+ is a string, it will be treated as a path to a
617 * File that will be opened in append mode.
619 static VALUE clogger_init(int argc, VALUE *argv, VALUE self)
621 struct clogger *c = clogger_get(self);
622 VALUE o = Qnil;
623 VALUE fmt = rb_const_get(mFormat, rb_intern("Common"));
625 rb_scan_args(argc, argv, "11", &c->app, &o);
626 c->fd = -1;
627 c->logger = Qnil;
628 c->reentrant = -1; /* auto-detect */
630 if (TYPE(o) == T_HASH) {
631 VALUE tmp;
633 tmp = rb_hash_aref(o, ID2SYM(rb_intern("path")));
634 c->logger = rb_hash_aref(o, ID2SYM(rb_intern("logger")));
635 init_logger(c, tmp);
637 tmp = rb_hash_aref(o, ID2SYM(rb_intern("format")));
638 if (!NIL_P(tmp))
639 fmt = tmp;
641 tmp = rb_hash_aref(o, ID2SYM(rb_intern("reentrant")));
642 switch (TYPE(tmp)) {
643 case T_TRUE:
644 c->reentrant = 1;
645 break;
646 case T_FALSE:
647 c->reentrant = 0;
648 case T_NIL:
649 break;
650 default:
651 rb_raise(rb_eArgError, ":reentrant must be boolean");
655 init_buffers(c);
656 c->fmt_ops = rb_funcall(self, rb_intern("compile_format"), 2, fmt, o);
658 if (Qtrue == rb_funcall(self, rb_intern("need_response_headers?"),
659 1, c->fmt_ops))
660 c->need_resp = 1;
661 if (Qtrue == rb_funcall(self, rb_intern("need_wrap_body?"),
662 1, c->fmt_ops))
663 c->wrap_body = 1;
665 return self;
668 static VALUE body_iter_i(VALUE str, VALUE memop)
670 off_t *len = (off_t *)memop;
672 str = rb_obj_as_string(str);
673 *len += RSTRING_LEN(str);
675 return rb_yield(str);
678 static VALUE body_close(struct clogger *c)
680 if (rb_respond_to(c->body, close_id))
681 return rb_funcall(c->body, close_id, 0);
682 return Qnil;
686 * call-seq:
687 * clogger.each { |part| socket.write(part) }
689 * Delegates the body#each call to the underlying +body+ object
690 * while tracking the number of bytes yielded. This will log
691 * the request.
693 static VALUE clogger_each(VALUE self)
695 struct clogger *c = clogger_get(self);
697 rb_need_block();
698 c->body_bytes_sent = 0;
699 rb_iterate(rb_each, c->body, body_iter_i, (VALUE)&c->body_bytes_sent);
701 return self;
705 * call-seq:
706 * clogger.close
708 * Delegates the body#close call to the underlying +body+ object.
709 * This is only used when Clogger is wrapping the +body+ of a Rack
710 * response and should be automatically called by the web server.
712 static VALUE clogger_close(VALUE self)
714 struct clogger *c = clogger_get(self);
716 return rb_ensure(body_close, (VALUE)c, cwrite, (VALUE)c);
719 /* :nodoc: */
720 static VALUE clogger_fileno(VALUE self)
722 struct clogger *c = clogger_get(self);
724 return c->fd < 0 ? Qnil : INT2NUM(c->fd);
727 static VALUE ccall(struct clogger *c, VALUE env)
729 VALUE rv;
731 clock_gettime(CLOCK_MONOTONIC, &c->ts_start);
732 c->env = env;
733 c->cookies = Qfalse;
734 rv = rb_funcall(c->app, call_id, 1, env);
735 if (TYPE(rv) == T_ARRAY && RARRAY_LEN(rv) == 3) {
736 VALUE *tmp = RARRAY_PTR(rv);
738 c->status = tmp[0];
739 c->headers = tmp[1];
740 c->body = tmp[2];
742 rv = rb_ary_new4(3, tmp);
743 if (c->need_resp &&
744 ! rb_obj_is_kind_of(tmp[1], cHeaderHash)) {
745 c->headers = rb_funcall(cHeaderHash, new_id, 1, tmp[1]);
746 rb_ary_store(rv, 1, c->headers);
748 } else {
749 volatile VALUE tmp = rb_inspect(rv);
751 c->status = INT2FIX(500);
752 c->headers = c->body = rb_ary_new();
753 cwrite(c);
754 rb_raise(rb_eTypeError,
755 "app response not a 3 element Array: %s",
756 RSTRING_PTR(tmp));
759 return rv;
763 * call-seq:
764 * clogger.call(env) => [ status, headers, body ]
766 * calls the wrapped Rack application with +env+, returns the
767 * [status, headers, body ] tuplet required by Rack.
769 static VALUE clogger_call(VALUE self, VALUE env)
771 struct clogger *c = clogger_get(self);
772 VALUE rv;
774 env = rb_check_convert_type(env, T_HASH, "Hash", "to_hash");
776 if (c->wrap_body) {
777 if (c->reentrant < 0) {
778 VALUE tmp = rb_hash_aref(env, g_rack_multithread);
779 c->reentrant = Qfalse == tmp ? 0 : 1;
781 if (c->reentrant) {
782 self = rb_obj_dup(self);
783 c = clogger_get(self);
786 rv = ccall(c, env);
787 assert(!OBJ_FROZEN(rv) && "frozen response array");
789 if (rb_respond_to(c->body, to_path_id))
790 self = rb_funcall(cToPath, new_id, 1, self);
791 rb_ary_store(rv, 2, self);
793 return rv;
796 rv = ccall(c, env);
797 cwrite(c);
799 return rv;
802 /* :nodoc */
803 static VALUE clogger_init_copy(VALUE clone, VALUE orig)
805 struct clogger *a = clogger_get(orig);
806 struct clogger *b = clogger_get(clone);
808 memcpy(b, a, sizeof(struct clogger));
809 init_buffers(b);
811 return clone;
814 #define CONST_GLOBAL_STR2(var, val) do { \
815 g_##var = rb_obj_freeze(rb_str_new(val, sizeof(val) - 1)); \
816 rb_global_variable(&g_##var); \
817 } while (0)
819 #define CONST_GLOBAL_STR(val) CONST_GLOBAL_STR2(val, #val)
821 #ifdef RSTRUCT_PTR
822 # define ToPath_clogger(tp) RSTRUCT_PTR(tp)[0]
823 #else
824 static ID clogger_id;
825 # define ToPath_clogger(tp) rb_funcall(tp,clogger_id,0)
826 #endif
828 static VALUE to_path(VALUE self)
830 VALUE my_clogger = ToPath_clogger(self);
831 struct clogger *c = clogger_get(my_clogger);
832 VALUE path = rb_funcall(c->body, to_path_id, 0);
833 struct stat sb;
834 int rv;
835 unsigned devfd;
836 const char *cpath;
838 Check_Type(path, T_STRING);
839 cpath = RSTRING_PTR(path);
841 /* try to avoid an extra path lookup */
842 if (rb_respond_to(c->body, to_io_id))
843 rv = fstat(my_fileno(c->body), &sb);
845 * Rainbows! can use "/dev/fd/%u" in to_path output to avoid
846 * extra open() syscalls, too.
848 else if (sscanf(cpath, "/dev/fd/%u", &devfd) == 1)
849 rv = fstat((int)devfd, &sb);
850 else
851 rv = stat(cpath, &sb);
854 * calling this method implies the web server will bypass
855 * the each method where body_bytes_sent is calculated,
856 * so we stat and set that value here.
858 c->body_bytes_sent = rv == 0 ? sb.st_size : 0;
859 return path;
862 void Init_clogger_ext(void)
864 VALUE tmp;
866 ltlt_id = rb_intern("<<");
867 call_id = rb_intern("call");
868 each_id = rb_intern("each");
869 close_id = rb_intern("close");
870 to_i_id = rb_intern("to_i");
871 to_s_id = rb_intern("to_s");
872 size_id = rb_intern("size");
873 sq_brace_id = rb_intern("[]");
874 new_id = rb_intern("new");
875 to_path_id = rb_intern("to_path");
876 to_io_id = rb_intern("to_io");
877 cClogger = rb_define_class("Clogger", rb_cObject);
878 mFormat = rb_define_module_under(cClogger, "Format");
879 rb_define_alloc_func(cClogger, clogger_alloc);
880 rb_define_method(cClogger, "initialize", clogger_init, -1);
881 rb_define_method(cClogger, "initialize_copy", clogger_init_copy, 1);
882 rb_define_method(cClogger, "call", clogger_call, 1);
883 rb_define_method(cClogger, "each", clogger_each, 0);
884 rb_define_method(cClogger, "close", clogger_close, 0);
885 rb_define_method(cClogger, "fileno", clogger_fileno, 0);
886 rb_define_method(cClogger, "wrap_body?", clogger_wrap_body, 0);
887 rb_define_method(cClogger, "reentrant?", clogger_reentrant, 0);
888 CONST_GLOBAL_STR(REMOTE_ADDR);
889 CONST_GLOBAL_STR(HTTP_X_FORWARDED_FOR);
890 CONST_GLOBAL_STR(REQUEST_METHOD);
891 CONST_GLOBAL_STR(PATH_INFO);
892 CONST_GLOBAL_STR(QUERY_STRING);
893 CONST_GLOBAL_STR(REQUEST_URI);
894 CONST_GLOBAL_STR(HTTP_VERSION);
895 CONST_GLOBAL_STR2(rack_errors, "rack.errors");
896 CONST_GLOBAL_STR2(rack_input, "rack.input");
897 CONST_GLOBAL_STR2(rack_multithread, "rack.multithread");
898 CONST_GLOBAL_STR2(dash, "-");
899 CONST_GLOBAL_STR2(space, " ");
900 CONST_GLOBAL_STR2(question_mark, "?");
901 CONST_GLOBAL_STR2(rack_request_cookie_hash, "rack.request.cookie_hash");
903 tmp = rb_const_get(rb_cObject, rb_intern("Rack"));
904 tmp = rb_const_get(tmp, rb_intern("Utils"));
905 cHeaderHash = rb_const_get(tmp, rb_intern("HeaderHash"));
906 cToPath = rb_const_get(cClogger, rb_intern("ToPath"));
907 rb_define_method(cToPath, "to_path", to_path, 0);
908 #ifndef RSTRUCT_PTR
909 clogger_id = rb_intern("clogger");
910 #endif