1 /* -*- c-basic-offset: 2 -*- */
2 /*
3   Copyright(C) 2012-2017 Brazil
4 
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public
7   License version 2.1 as published by the Free Software Foundation.
8 
9   This library is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12   Lesser General Public License for more details.
13 
14   You should have received a copy of the GNU Lesser General Public
15   License along with this library; if not, write to the Free Software
16   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1335  USA
17 */
18 
19 #ifndef WIN32
20 # define NGX_GRN_SUPPORT_STOP_BY_COMMAND
21 #endif
22 
23 #include <ngx_config.h>
24 #include <ngx_core.h>
25 #include <ngx_http.h>
26 
27 #include <groonga.h>
28 #include <groonga/plugin.h>
29 
30 #include <sys/stat.h>
31 
32 #ifdef NGX_GRN_SUPPORT_STOP_BY_COMMAND
33 # include <sys/types.h>
34 # include <unistd.h>
35 #endif
36 
37 #define GRN_NO_FLAGS 0
38 
39 typedef struct {
40   ngx_flag_t enabled;
41   ngx_str_t database_path;
42   char *database_path_cstr;
43   ngx_flag_t database_auto_create;
44   ngx_str_t base_path;
45   ngx_str_t log_path;
46   ngx_open_file_t *log_file;
47   grn_log_level log_level;
48   ngx_str_t query_log_path;
49   ngx_open_file_t *query_log_file;
50   size_t cache_limit;
51   ngx_msec_t default_request_timeout_msec;
52   char *config_file;
53   int config_line;
54   char *name;
55   grn_obj *database;
56   grn_cache *cache;
57   ngx_str_t cache_base_path;
58 } ngx_http_groonga_loc_conf_t;
59 
60 typedef struct {
61   ngx_log_t *log;
62   ngx_pool_t *pool;
63   ngx_int_t rc;
64 } ngx_http_groonga_database_callback_data_t;
65 
66 typedef struct {
67   grn_bool initialized;
68   grn_rc rc;
69   struct {
70     grn_bool processed;
71     grn_bool header_sent;
72     ngx_http_request_t *r;
73     ngx_int_t rc;
74     ngx_chain_t *free_chain;
75     ngx_chain_t *busy_chain;
76   } raw;
77   struct {
78     grn_obj head;
79     grn_obj body;
80     grn_obj foot;
81   } typed;
82 } ngx_http_groonga_handler_data_t;
83 
84 typedef void (*ngx_http_groonga_loc_conf_callback_pt)(ngx_http_groonga_loc_conf_t *conf, void *user_data);
85 
86 ngx_module_t ngx_http_groonga_module;
87 
88 static grn_ctx ngx_http_groonga_context;
89 static grn_ctx *context = &ngx_http_groonga_context;
90 static ngx_http_groonga_loc_conf_t *ngx_http_groonga_current_location_conf = NULL;
91 
92 static char *
ngx_str_null_terminate(ngx_pool_t * pool,const ngx_str_t * string)93 ngx_str_null_terminate(ngx_pool_t *pool, const ngx_str_t *string)
94 {
95   char *null_terminated_c_string;
96 
97   null_terminated_c_string = ngx_pnalloc(pool, string->len + 1);
98   if (!null_terminated_c_string) {
99     return NULL;
100   }
101 
102   memcpy(null_terminated_c_string, string->data, string->len);
103   null_terminated_c_string[string->len] = '\0';
104 
105   return null_terminated_c_string;
106 }
107 
108 static grn_bool
ngx_str_equal_c_string(ngx_str_t * string,const char * c_string)109 ngx_str_equal_c_string(ngx_str_t *string, const char *c_string)
110 {
111   if (string->len != strlen(c_string)) {
112     return GRN_FALSE;
113   }
114 
115   return memcmp(c_string, string->data, string->len) == 0;
116 }
117 
118 static grn_bool
ngx_str_is_custom_path(ngx_str_t * string)119 ngx_str_is_custom_path(ngx_str_t *string)
120 {
121   if (string->len == 0) {
122     return GRN_FALSE;
123   }
124 
125   if (strncmp((const char *)(string->data), "off", string->len) == 0) {
126     return GRN_FALSE;
127   }
128 
129   return GRN_TRUE;
130 }
131 
132 static uint32_t
ngx_http_groonga_get_thread_limit(void * data)133 ngx_http_groonga_get_thread_limit(void *data)
134 {
135   return 1;
136 }
137 
138 static ngx_int_t
ngx_http_groonga_grn_rc_to_http_status(grn_rc rc)139 ngx_http_groonga_grn_rc_to_http_status(grn_rc rc)
140 {
141   switch (rc) {
142   case GRN_SUCCESS :
143     return NGX_HTTP_OK;
144   case GRN_INVALID_ARGUMENT :
145   case GRN_FUNCTION_NOT_IMPLEMENTED :
146   case GRN_SYNTAX_ERROR :
147     return NGX_HTTP_BAD_REQUEST;
148   case GRN_CANCEL :
149     return NGX_HTTP_REQUEST_TIME_OUT;
150   default :
151     return NGX_HTTP_INTERNAL_SERVER_ERROR;
152   }
153 }
154 
155 static void
ngx_http_groonga_write_fd(ngx_fd_t fd,u_char * buffer,size_t buffer_size,const char * message,size_t message_size)156 ngx_http_groonga_write_fd(ngx_fd_t fd,
157                           u_char *buffer, size_t buffer_size,
158                           const char *message, size_t message_size)
159 {
160   size_t rest_message_size = message_size;
161   const char *current_message = message;
162 
163   while (rest_message_size > 0) {
164     size_t current_message_size;
165 
166     if (rest_message_size > NGX_MAX_ERROR_STR) {
167       current_message_size = NGX_MAX_ERROR_STR;
168     } else {
169       current_message_size = rest_message_size;
170     }
171 
172     grn_memcpy(buffer, current_message, current_message_size);
173     ngx_write_fd(fd, buffer, current_message_size);
174     rest_message_size -= current_message_size;
175     current_message += current_message_size;
176   }
177 }
178 
179 static void
ngx_http_groonga_logger_log(grn_ctx * ctx,grn_log_level level,const char * timestamp,const char * title,const char * message,const char * location,void * user_data)180 ngx_http_groonga_logger_log(grn_ctx *ctx, grn_log_level level,
181                             const char *timestamp, const char *title,
182                             const char *message, const char *location,
183                             void *user_data)
184 {
185   ngx_open_file_t *file = user_data;
186   char level_marks[] = " EACewnid-";
187   u_char buffer[NGX_MAX_ERROR_STR];
188 
189   if (!file) {
190     return;
191   }
192 
193   ngx_http_groonga_write_fd(file->fd,
194                             buffer, NGX_MAX_ERROR_STR,
195                             timestamp, strlen(timestamp));
196   ngx_write_fd(file->fd, "|", 1);
197   ngx_write_fd(file->fd, level_marks + level, 1);
198   ngx_write_fd(file->fd, "|", 1);
199   if (location && *location) {
200     ngx_http_groonga_write_fd(file->fd,
201                               buffer, NGX_MAX_ERROR_STR,
202                               location, strlen(location));
203     ngx_write_fd(file->fd, ": ", 2);
204     if (title && *title) {
205       ngx_http_groonga_write_fd(file->fd,
206                                 buffer, NGX_MAX_ERROR_STR,
207                                 title, strlen(title));
208       ngx_write_fd(file->fd, " ", 1);
209     }
210   } else {
211     ngx_http_groonga_write_fd(file->fd,
212                               buffer, NGX_MAX_ERROR_STR,
213                               title, strlen(title));
214     ngx_write_fd(file->fd, " ", 1);
215   }
216   ngx_http_groonga_write_fd(file->fd,
217                             buffer, NGX_MAX_ERROR_STR,
218                             message, strlen(message));
219   ngx_write_fd(file->fd, "\n", 1);
220 }
221 
222 static void
ngx_http_groonga_logger_reopen(grn_ctx * ctx,void * user_data)223 ngx_http_groonga_logger_reopen(grn_ctx *ctx, void *user_data)
224 {
225   GRN_LOG(ctx, GRN_LOG_NOTICE, "log will be closed.");
226   ngx_reopen_files((ngx_cycle_t *)ngx_cycle, -1);
227   GRN_LOG(ctx, GRN_LOG_NOTICE, "log opened.");
228 }
229 
230 static void
ngx_http_groonga_logger_fin(grn_ctx * ctx,void * user_data)231 ngx_http_groonga_logger_fin(grn_ctx *ctx, void *user_data)
232 {
233 }
234 
235 static grn_logger ngx_http_groonga_logger = {
236   GRN_LOG_DEFAULT_LEVEL,
237   GRN_LOG_TIME | GRN_LOG_MESSAGE | GRN_LOG_PID,
238   NULL,
239   ngx_http_groonga_logger_log,
240   ngx_http_groonga_logger_reopen,
241   ngx_http_groonga_logger_fin
242 };
243 
244 static ngx_int_t
ngx_http_groonga_context_init_logger(ngx_http_groonga_loc_conf_t * location_conf,ngx_pool_t * pool,ngx_log_t * log)245 ngx_http_groonga_context_init_logger(ngx_http_groonga_loc_conf_t *location_conf,
246                                      ngx_pool_t *pool,
247                                      ngx_log_t *log)
248 {
249   if (ngx_http_groonga_current_location_conf) {
250     ngx_http_groonga_current_location_conf->log_level =
251       grn_logger_get_max_level(context);
252   }
253 
254   ngx_http_groonga_logger.max_level = location_conf->log_level;
255   ngx_http_groonga_logger.user_data = location_conf->log_file;
256   grn_logger_set(context, &ngx_http_groonga_logger);
257 
258   return NGX_OK;
259 }
260 
261 static void
ngx_http_groonga_query_logger_log(grn_ctx * ctx,unsigned int flag,const char * timestamp,const char * info,const char * message,void * user_data)262 ngx_http_groonga_query_logger_log(grn_ctx *ctx, unsigned int flag,
263                                   const char *timestamp, const char *info,
264                                   const char *message, void *user_data)
265 {
266   ngx_open_file_t *file = user_data;
267   u_char buffer[NGX_MAX_ERROR_STR];
268   u_char *last;
269 
270   if (!file) {
271     return;
272   }
273 
274   last = ngx_slprintf(buffer, buffer + NGX_MAX_ERROR_STR,
275                       "%s|%s%s\n",
276                       timestamp, info, message);
277   ngx_write_fd(file->fd, buffer, last - buffer);
278 }
279 
280 static void
ngx_http_groonga_query_logger_reopen(grn_ctx * ctx,void * user_data)281 ngx_http_groonga_query_logger_reopen(grn_ctx *ctx, void *user_data)
282 {
283   ngx_reopen_files((ngx_cycle_t *)ngx_cycle, -1);
284 }
285 
286 static void
ngx_http_groonga_query_logger_fin(grn_ctx * ctx,void * user_data)287 ngx_http_groonga_query_logger_fin(grn_ctx *ctx, void *user_data)
288 {
289 }
290 
291 static grn_query_logger ngx_http_groonga_query_logger = {
292   GRN_QUERY_LOG_DEFAULT,
293   NULL,
294   ngx_http_groonga_query_logger_log,
295   ngx_http_groonga_query_logger_reopen,
296   ngx_http_groonga_query_logger_fin
297 };
298 
299 static ngx_int_t
ngx_http_groonga_context_init_query_logger(ngx_http_groonga_loc_conf_t * location_conf,ngx_pool_t * pool,ngx_log_t * log)300 ngx_http_groonga_context_init_query_logger(ngx_http_groonga_loc_conf_t *location_conf,
301                                            ngx_pool_t *pool,
302                                            ngx_log_t *log)
303 {
304   ngx_http_groonga_query_logger.user_data = location_conf->query_log_file;
305   grn_query_logger_set(context, &ngx_http_groonga_query_logger);
306 
307   return NGX_OK;
308 }
309 
310 static ngx_int_t
ngx_http_groonga_context_init(ngx_http_groonga_loc_conf_t * location_conf,ngx_pool_t * pool,ngx_log_t * log)311 ngx_http_groonga_context_init(ngx_http_groonga_loc_conf_t *location_conf,
312                               ngx_pool_t *pool,
313                               ngx_log_t *log)
314 {
315   ngx_int_t status;
316 
317   if (location_conf == ngx_http_groonga_current_location_conf) {
318     return NGX_OK;
319   }
320 
321   status = ngx_http_groonga_context_init_logger(location_conf,
322                                                 pool,
323                                                 log);
324   if (status == NGX_ERROR) {
325     return status;
326   }
327 
328   status = ngx_http_groonga_context_init_query_logger(location_conf,
329                                                       pool,
330                                                       log);
331   if (status == NGX_ERROR) {
332     return status;
333   }
334 
335   grn_ctx_use(context, location_conf->database);
336   grn_cache_current_set(context, location_conf->cache);
337 
338   /* TODO: It doesn't work yet. We need to implement request timeout
339    * handler. */
340   if (location_conf->default_request_timeout_msec == NGX_CONF_UNSET_MSEC) {
341     grn_set_default_request_timeout(0.0);
342   } else {
343     double timeout;
344     timeout = location_conf->default_request_timeout_msec / 1000.0;
345     grn_set_default_request_timeout(timeout);
346   }
347 
348   ngx_http_groonga_current_location_conf = location_conf;
349 
350   return status;
351 }
352 
353 static void
ngx_http_groonga_context_log_error(ngx_log_t * log)354 ngx_http_groonga_context_log_error(ngx_log_t *log)
355 {
356   if (context->rc == GRN_SUCCESS) {
357     return;
358   }
359 
360   ngx_log_error(NGX_LOG_ERR, log, 0, "%s", context->errbuf);
361 }
362 
363 static ngx_int_t
ngx_http_groonga_context_check_error(ngx_log_t * log)364 ngx_http_groonga_context_check_error(ngx_log_t *log)
365 {
366   if (context->rc == GRN_SUCCESS) {
367     return NGX_OK;
368   } else {
369     ngx_http_groonga_context_log_error(log);
370     return NGX_HTTP_BAD_REQUEST;
371   }
372 }
373 
374 static ngx_buf_t *
ngx_http_groonga_grn_obj_to_ngx_buf(ngx_pool_t * pool,grn_obj * object)375 ngx_http_groonga_grn_obj_to_ngx_buf(ngx_pool_t *pool, grn_obj *object)
376 {
377   ngx_buf_t *buffer;
378   buffer = ngx_pcalloc(pool, sizeof(ngx_buf_t));
379   if (buffer == NULL) {
380     return NULL;
381   }
382 
383   /* adjust the pointers of the buffer */
384   buffer->pos = (u_char *)GRN_TEXT_VALUE(object);
385   buffer->last = (u_char *)GRN_TEXT_VALUE(object) + GRN_TEXT_LEN(object);
386   buffer->memory = 1;    /* this buffer is in memory */
387   buffer->in_file = 0;
388 
389   return buffer;
390 }
391 
392 static void
ngx_http_groonga_handler_cleanup(void * user_data)393 ngx_http_groonga_handler_cleanup(void *user_data)
394 {
395   ngx_http_groonga_handler_data_t *data = user_data;
396 
397   if (!data->initialized) {
398     return;
399   }
400 
401   GRN_OBJ_FIN(context, &(data->typed.head));
402   GRN_OBJ_FIN(context, &(data->typed.body));
403   GRN_OBJ_FIN(context, &(data->typed.foot));
404 }
405 
406 static void
ngx_http_groonga_handler_set_content_type(ngx_http_request_t * r,const char * content_type)407 ngx_http_groonga_handler_set_content_type(ngx_http_request_t *r,
408                                           const char *content_type)
409 {
410   r->headers_out.content_type.len = strlen(content_type);
411   r->headers_out.content_type.data = (u_char *)content_type;
412   r->headers_out.content_type_len = r->headers_out.content_type.len;
413 }
414 
415 static void
ngx_http_groonga_context_receive_handler_raw(grn_ctx * context,int flags,ngx_http_groonga_handler_data_t * data)416 ngx_http_groonga_context_receive_handler_raw(grn_ctx *context,
417                                              int flags,
418                                              ngx_http_groonga_handler_data_t *data)
419 {
420   char *chunk = NULL;
421   unsigned int chunk_size = 0;
422   int recv_flags;
423   ngx_http_request_t *r;
424   ngx_log_t *log;
425   grn_bool is_last_chunk;
426 
427   grn_ctx_recv(context, &chunk, &chunk_size, &recv_flags);
428   data->raw.processed = GRN_TRUE;
429 
430   if (data->raw.rc != NGX_OK) {
431     return;
432   }
433 
434   r = data->raw.r;
435   log = r->connection->log;
436   is_last_chunk = (flags & GRN_CTX_TAIL);
437 
438   if (!data->raw.header_sent) {
439     ngx_http_groonga_handler_set_content_type(r, grn_ctx_get_mime_type(context));
440     r->headers_out.status = NGX_HTTP_OK;
441     if (is_last_chunk) {
442       r->headers_out.content_length_n = chunk_size;
443       if (chunk_size == 0) {
444         r->header_only = 1;
445       }
446     } else {
447       r->headers_out.content_length_n = -1;
448     }
449     data->raw.rc = ngx_http_send_header(r);
450     data->raw.header_sent = GRN_TRUE;
451 
452     if (data->raw.rc != NGX_OK) {
453       return;
454     }
455   }
456 
457   if (chunk_size > 0 || is_last_chunk) {
458     ngx_chain_t *chain;
459 
460     chain = ngx_chain_get_free_buf(r->pool, &(data->raw.free_chain));
461     if (!chain) {
462       ngx_log_error(NGX_LOG_ERR, log, 0,
463                     "http_groonga: failed to allocate memory for chunked body");
464       data->raw.rc = NGX_ERROR;
465       return;
466     }
467     if (chunk_size == 0) {
468       chain->buf->pos = NULL;
469       chain->buf->last = NULL;
470       chain->buf->memory = 0;
471     } else {
472       chain->buf->pos = (u_char *)chunk;
473       chain->buf->last = (u_char *)chunk + chunk_size;
474       chain->buf->memory = 1;
475     }
476     chain->buf->tag = (ngx_buf_tag_t)&ngx_http_groonga_module;
477     chain->buf->flush = 1;
478     chain->buf->temporary = 0;
479     chain->buf->in_file = 0;
480     if (is_last_chunk) {
481       chain->buf->last_buf = 1;
482     } else {
483       chain->buf->last_buf = 0;
484     }
485     chain->next = NULL;
486 
487     data->raw.rc = ngx_http_output_filter(r, chain);
488     ngx_chain_update_chains(r->pool,
489                             &(data->raw.free_chain),
490                             &(data->raw.busy_chain),
491                             &chain,
492                             (ngx_buf_tag_t)&ngx_http_groonga_module);
493   }
494 }
495 
496 static void
ngx_http_groonga_context_receive_handler_typed(grn_ctx * context,int flags,ngx_http_groonga_handler_data_t * data)497 ngx_http_groonga_context_receive_handler_typed(grn_ctx *context,
498                                                int flags,
499                                                ngx_http_groonga_handler_data_t *data)
500 {
501   char *result = NULL;
502   unsigned int result_size = 0;
503   int recv_flags;
504 
505   if (!(flags & GRN_CTX_TAIL)) {
506     return;
507   }
508 
509   grn_ctx_recv(context, &result, &result_size, &recv_flags);
510 
511 #ifdef NGX_GRN_SUPPORT_STOP_BY_COMMAND
512   if (recv_flags == GRN_CTX_QUIT) {
513     ngx_int_t ngx_rc;
514     ngx_int_t ngx_pid;
515 
516     if (ngx_process == NGX_PROCESS_SINGLE) {
517       ngx_pid = getpid();
518     } else {
519       ngx_pid = getppid();
520     }
521 
522     ngx_rc = ngx_os_signal_process((ngx_cycle_t *)ngx_cycle,
523                                    "quit",
524                                    ngx_pid);
525     if (ngx_rc == NGX_OK) {
526       context->stat &= ~GRN_CTX_QUIT;
527       grn_ctx_recv(context, &result, &result_size, &recv_flags);
528       context->stat |= GRN_CTX_QUIT;
529     } else {
530       context->rc = GRN_OPERATION_NOT_PERMITTED;
531       result = "false";
532       result_size = strlen(result);
533       context->stat &= ~GRN_CTX_QUIT;
534     }
535   }
536 #endif
537 
538   if (result_size > 0 ||
539       GRN_TEXT_LEN(&(data->typed.body)) > 0 ||
540       context->rc != GRN_SUCCESS) {
541     if (result_size > 0) {
542       GRN_TEXT_PUT(context, &(data->typed.body), result, result_size);
543     }
544 
545     grn_output_envelope(context,
546                         context->rc,
547                         &(data->typed.head),
548                         &(data->typed.body),
549                         &(data->typed.foot),
550                         NULL,
551                         0);
552   }
553 }
554 
555 static void
ngx_http_groonga_context_receive_handler(grn_ctx * context,int flags,void * callback_data)556 ngx_http_groonga_context_receive_handler(grn_ctx *context,
557                                          int flags,
558                                          void *callback_data)
559 {
560   ngx_http_groonga_handler_data_t *data = callback_data;
561 
562   switch (grn_ctx_get_output_type(context)) {
563   case GRN_CONTENT_GROONGA_COMMAND_LIST :
564   case GRN_CONTENT_NONE :
565     ngx_http_groonga_context_receive_handler_raw(context, flags, data);
566     break;
567   default :
568     ngx_http_groonga_context_receive_handler_typed(context, flags, data);
569     break;
570   }
571 }
572 
573 static ngx_int_t
ngx_http_groonga_extract_command_path(ngx_http_request_t * r,ngx_str_t * command_path)574 ngx_http_groonga_extract_command_path(ngx_http_request_t *r,
575                                       ngx_str_t *command_path)
576 {
577   size_t base_path_length;
578 
579   ngx_http_core_loc_conf_t *http_location_conf;
580   ngx_http_groonga_loc_conf_t *location_conf;
581 
582   http_location_conf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
583   location_conf = ngx_http_get_module_loc_conf(r, ngx_http_groonga_module);
584 
585   command_path->data = r->unparsed_uri.data;
586   command_path->len = r->unparsed_uri.len;
587   base_path_length = http_location_conf->name.len;
588   if (location_conf->base_path.len > 0) {
589     if (command_path->len < location_conf->base_path.len) {
590       ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
591                     "requested URI is shorter than groonga_base_path: "
592                     "URI: <%V>, groonga_base_path: <%V>",
593                     &(r->unparsed_uri), &(location_conf->base_path));
594     } else if (strncmp((const char *)command_path->data,
595                        (const char *)(location_conf->base_path.data),
596                        location_conf->base_path.len) < 0) {
597       ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
598                     "groonga_base_path doesn't match requested URI: "
599                     "URI: <%V>, groonga_base_path: <%V>",
600                     &(r->unparsed_uri), &(location_conf->base_path));
601     } else {
602       base_path_length = location_conf->base_path.len;
603     }
604   }
605   command_path->data += base_path_length;
606   command_path->len -= base_path_length;
607   if (command_path->len > 0 && command_path->data[0] == '/') {
608     command_path->data += 1;
609     command_path->len -= 1;
610   }
611   if (command_path->len == 0) {
612     return NGX_HTTP_BAD_REQUEST;
613   }
614 
615   return NGX_OK;
616 }
617 
618 static ngx_int_t
ngx_http_groonga_handler_create_data(ngx_http_request_t * r,ngx_http_groonga_handler_data_t ** data_return)619 ngx_http_groonga_handler_create_data(ngx_http_request_t *r,
620                                      ngx_http_groonga_handler_data_t **data_return)
621 {
622   ngx_int_t rc;
623 
624   ngx_http_groonga_loc_conf_t *location_conf;
625 
626   ngx_http_cleanup_t *cleanup;
627   ngx_http_groonga_handler_data_t *data;
628 
629   location_conf = ngx_http_get_module_loc_conf(r, ngx_http_groonga_module);
630 
631   rc = ngx_http_groonga_context_init(location_conf, r->pool, r->connection->log);
632   if (rc != NGX_OK) {
633     return rc;
634   }
635 
636   cleanup = ngx_http_cleanup_add(r, sizeof(ngx_http_groonga_handler_data_t));
637   cleanup->handler = ngx_http_groonga_handler_cleanup;
638   data = cleanup->data;
639   *data_return = data;
640 
641   data->initialized = GRN_TRUE;
642   data->rc = GRN_SUCCESS;
643 
644   data->raw.processed = GRN_FALSE;
645   data->raw.header_sent = GRN_FALSE;
646   data->raw.r = r;
647   data->raw.rc = NGX_OK;
648   data->raw.free_chain = NULL;
649   data->raw.busy_chain = NULL;
650 
651   GRN_TEXT_INIT(&(data->typed.head), GRN_NO_FLAGS);
652   GRN_TEXT_INIT(&(data->typed.body), GRN_NO_FLAGS);
653   GRN_TEXT_INIT(&(data->typed.foot), GRN_NO_FLAGS);
654 
655   grn_ctx_use(context, location_conf->database);
656   rc = ngx_http_groonga_context_check_error(r->connection->log);
657   if (rc != NGX_OK) {
658     return rc;
659   }
660 
661   grn_ctx_recv_handler_set(context,
662                            ngx_http_groonga_context_receive_handler,
663                            data);
664 
665   return NGX_OK;
666 }
667 
668 static void
ngx_http_groonga_handler_process_command_path(ngx_http_request_t * r,ngx_str_t * command_path,ngx_http_groonga_handler_data_t * data,int flags)669 ngx_http_groonga_handler_process_command_path(ngx_http_request_t *r,
670                                               ngx_str_t *command_path,
671                                               ngx_http_groonga_handler_data_t *data,
672                                               int flags)
673 {
674   grn_obj uri;
675 
676   GRN_TEXT_INIT(&uri, 0);
677   GRN_TEXT_PUTS(context, &uri, "/d/");
678   GRN_TEXT_PUT(context, &uri, command_path->data, command_path->len);
679   grn_ctx_send(context, GRN_TEXT_VALUE(&uri), GRN_TEXT_LEN(&uri), flags);
680   data->rc = context->rc;
681   ngx_http_groonga_context_log_error(r->connection->log);
682   GRN_OBJ_FIN(context, &uri);
683 }
684 
685 static grn_bool
ngx_http_groonga_handler_validate_post_command(ngx_http_request_t * r,ngx_str_t * command_path,ngx_http_groonga_handler_data_t * data)686 ngx_http_groonga_handler_validate_post_command(ngx_http_request_t *r,
687                                                ngx_str_t *command_path,
688                                                ngx_http_groonga_handler_data_t *data)
689 {
690   ngx_str_t command;
691 
692   command.data = command_path->data;
693   if (r->args.len == 0) {
694     command.len = command_path->len;
695   } else {
696     command.len = command_path->len - r->args.len - strlen("?");
697   }
698   if (ngx_str_equal_c_string(&command, "load")) {
699     return GRN_TRUE;
700   }
701 
702   data->rc = GRN_INVALID_ARGUMENT;
703   ngx_http_groonga_handler_set_content_type(r, "text/plain");
704   GRN_TEXT_PUTS(context, &(data->typed.body),
705                 "command for POST must be <load>: <");
706   GRN_TEXT_PUT(context, &(data->typed.body), command.data, command.len);
707   GRN_TEXT_PUTS(context, &(data->typed.body), ">");
708 
709   return GRN_FALSE;
710 }
711 
712 static void
ngx_http_groonga_send_body(ngx_http_request_t * r,ngx_http_groonga_handler_data_t * data)713 ngx_http_groonga_send_body(ngx_http_request_t *r,
714                            ngx_http_groonga_handler_data_t *data)
715 {
716   ngx_log_t *log;
717   grn_obj line_buffer;
718   size_t line_start_offset;
719   size_t line_check_start_offset;
720   ngx_chain_t *chain;
721   size_t line_buffer_chunk_size = 4096;
722 
723   log = r->connection->log;
724 
725   GRN_TEXT_INIT(&line_buffer, 0);
726   line_start_offset = 0;
727   line_check_start_offset = 0;
728   for (chain = r->request_body->bufs; chain; chain = chain->next) {
729     ngx_buf_t *buffer;
730     size_t rest_buffer_size;
731     off_t offset;
732 
733     buffer = chain->buf;
734     rest_buffer_size = ngx_buf_size(buffer);
735     offset = 0;
736     while (rest_buffer_size > 0) {
737       size_t current_buffer_size;
738 
739       if (rest_buffer_size > line_buffer_chunk_size) {
740         current_buffer_size = line_buffer_chunk_size;
741       } else {
742         current_buffer_size = rest_buffer_size;
743       }
744 
745       if (ngx_buf_in_memory(buffer)) {
746         GRN_TEXT_PUT(context,
747                      &line_buffer,
748                      buffer->pos + offset,
749                      current_buffer_size);
750       } else {
751         ngx_int_t rc;
752         grn_bulk_reserve(context, &line_buffer, current_buffer_size);
753         rc = ngx_read_file(buffer->file,
754                            (u_char *)GRN_BULK_CURR(&line_buffer),
755                            current_buffer_size,
756                            offset);
757         if (rc < 0) {
758           GRN_PLUGIN_ERROR(context,
759                            GRN_INPUT_OUTPUT_ERROR,
760                            "[nginx][post][body][read] "
761                            "failed to read a request body from file");
762           goto exit;
763         }
764         GRN_BULK_INCR_LEN(&line_buffer, current_buffer_size);
765       }
766       offset += current_buffer_size;
767       rest_buffer_size -= current_buffer_size;
768 
769       {
770         const char *line_start;
771         const char *line_current;
772         const char *line_end;
773 
774         line_start = GRN_TEXT_VALUE(&line_buffer) + line_start_offset;
775         line_end = GRN_TEXT_VALUE(&line_buffer) + GRN_TEXT_LEN(&line_buffer);
776         for (line_current = line_start + line_check_start_offset;
777              line_current < line_end;
778              line_current++) {
779           size_t line_length;
780           int flags = GRN_NO_FLAGS;
781 
782           if (*line_current != '\n') {
783             continue;
784           }
785 
786           line_length = line_current - line_start + 1;
787           if (line_current + 1 == line_end &&
788               !chain->next &&
789               rest_buffer_size == 0) {
790             flags |= GRN_CTX_TAIL;
791           }
792           grn_ctx_send(context, line_start, line_length, flags);
793           line_start_offset += line_length;
794           line_start += line_length;
795           ngx_http_groonga_context_log_error(log);
796           if (context->rc != GRN_SUCCESS && data->rc == GRN_SUCCESS) {
797             data->rc = context->rc;
798           }
799         }
800 
801         if (line_start_offset == 0) {
802           line_buffer_chunk_size *= 2;
803           line_check_start_offset = GRN_TEXT_LEN(&line_buffer);
804         } else if ((size_t)GRN_TEXT_LEN(&line_buffer) == line_start_offset) {
805           GRN_BULK_REWIND(&line_buffer);
806           line_start_offset = 0;
807           line_check_start_offset = 0;
808         } else {
809           size_t rest_line_size;
810           rest_line_size = GRN_TEXT_LEN(&line_buffer) - line_start_offset;
811           grn_memmove(GRN_TEXT_VALUE(&line_buffer),
812                       GRN_TEXT_VALUE(&line_buffer) + line_start_offset,
813                       rest_line_size);
814           grn_bulk_truncate(context, &line_buffer, rest_line_size);
815           line_start_offset = 0;
816           line_check_start_offset = GRN_TEXT_LEN(&line_buffer);
817         }
818       }
819     }
820   }
821 
822   if (GRN_TEXT_LEN(&line_buffer) > 0) {
823     grn_ctx_send(context,
824                  GRN_TEXT_VALUE(&line_buffer),
825                  GRN_TEXT_LEN(&line_buffer),
826                  GRN_CTX_TAIL);
827     ngx_http_groonga_context_log_error(log);
828     if (context->rc != GRN_SUCCESS && data->rc == GRN_SUCCESS) {
829       data->rc = context->rc;
830     }
831   }
832 
833 exit :
834   GRN_OBJ_FIN(context, &line_buffer);
835 }
836 
837 static void
ngx_http_groonga_handler_process_body(ngx_http_request_t * r,ngx_http_groonga_handler_data_t * data)838 ngx_http_groonga_handler_process_body(ngx_http_request_t *r,
839                                       ngx_http_groonga_handler_data_t *data)
840 {
841   ngx_buf_t *body;
842 
843   body = r->request_body->bufs->buf;
844   if (!body) {
845     data->rc = GRN_INVALID_ARGUMENT;
846     ngx_http_groonga_handler_set_content_type(r, "text/plain");
847     GRN_TEXT_PUTS(context, &(data->typed.body), "must send load data as body");
848     return;
849   }
850 
851   ngx_http_groonga_send_body(r, data);
852 }
853 
854 
855 static void
ngx_http_groonga_handler_process_load(ngx_http_request_t * r,ngx_str_t * command_path,ngx_http_groonga_handler_data_t * data)856 ngx_http_groonga_handler_process_load(ngx_http_request_t *r,
857                                       ngx_str_t *command_path,
858                                       ngx_http_groonga_handler_data_t *data)
859 {
860   if (!ngx_http_groonga_handler_validate_post_command(r, command_path, data)) {
861     return;
862   }
863 
864   ngx_http_groonga_handler_process_command_path(r,
865                                                 command_path,
866                                                 data,
867                                                 GRN_NO_FLAGS);
868   if (data->rc != GRN_SUCCESS) {
869     return;
870   }
871 
872   ngx_http_groonga_handler_process_body(r, data);
873 }
874 
875 static ngx_chain_t *
ngx_http_groonga_attach_chain(ngx_chain_t * chain,ngx_chain_t * new_chain)876 ngx_http_groonga_attach_chain(ngx_chain_t *chain, ngx_chain_t *new_chain)
877 {
878   ngx_chain_t *last_chain;
879 
880   if (new_chain->buf->last == new_chain->buf->pos) {
881     return chain;
882   }
883 
884   new_chain->buf->last_buf = 1;
885   new_chain->next = NULL;
886   if (!chain) {
887     return new_chain;
888   }
889 
890   chain->buf->last_buf = 0;
891   last_chain = chain;
892   while (last_chain->next) {
893     last_chain = last_chain->next;
894   }
895   last_chain->next = new_chain;
896   return chain;
897 }
898 
899 static ngx_int_t
ngx_http_groonga_handler_send_response(ngx_http_request_t * r,ngx_http_groonga_handler_data_t * data)900 ngx_http_groonga_handler_send_response(ngx_http_request_t *r,
901                                        ngx_http_groonga_handler_data_t *data)
902 {
903   ngx_int_t rc;
904   const char *content_type;
905   ngx_buf_t *head_buf, *body_buf, *foot_buf;
906   ngx_chain_t head_chain, body_chain, foot_chain;
907   ngx_chain_t *output_chain = NULL;
908 
909   if (data->raw.processed) {
910     return data->raw.rc;
911   }
912 
913   /* set the 'Content-type' header */
914   if (r->headers_out.content_type.len == 0) {
915     grn_obj *foot = &(data->typed.foot);
916     if (grn_ctx_get_output_type(context) == GRN_CONTENT_JSON &&
917         GRN_TEXT_LEN(foot) > 0 &&
918         GRN_TEXT_VALUE(foot)[GRN_TEXT_LEN(foot) - 1] == ';') {
919       content_type = "application/javascript";
920     } else {
921       content_type = grn_ctx_get_mime_type(context);
922     }
923     ngx_http_groonga_handler_set_content_type(r, content_type);
924   }
925 
926   /* allocate buffers for a response body */
927   head_buf = ngx_http_groonga_grn_obj_to_ngx_buf(r->pool, &(data->typed.head));
928   if (!head_buf) {
929     return NGX_HTTP_INTERNAL_SERVER_ERROR;
930   }
931 
932   body_buf = ngx_http_groonga_grn_obj_to_ngx_buf(r->pool, &(data->typed.body));
933   if (!body_buf) {
934     return NGX_HTTP_INTERNAL_SERVER_ERROR;
935   }
936 
937   foot_buf = ngx_http_groonga_grn_obj_to_ngx_buf(r->pool, &(data->typed.foot));
938   if (!foot_buf) {
939     return NGX_HTTP_INTERNAL_SERVER_ERROR;
940   }
941 
942   /* attach buffers to the buffer chain */
943   head_chain.buf = head_buf;
944   output_chain = ngx_http_groonga_attach_chain(output_chain, &head_chain);
945   body_chain.buf = body_buf;
946   output_chain = ngx_http_groonga_attach_chain(output_chain, &body_chain);
947   foot_chain.buf = foot_buf;
948   output_chain = ngx_http_groonga_attach_chain(output_chain, &foot_chain);
949 
950   /* set the status line */
951   r->headers_out.status = ngx_http_groonga_grn_rc_to_http_status(data->rc);
952   r->headers_out.content_length_n = GRN_TEXT_LEN(&(data->typed.head)) +
953                                     GRN_TEXT_LEN(&(data->typed.body)) +
954                                     GRN_TEXT_LEN(&(data->typed.foot));
955   if (r->headers_out.content_length_n == 0) {
956     r->header_only = 1;
957   }
958 
959   /* send the headers of your response */
960   rc = ngx_http_send_header(r);
961 
962   if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
963     return rc;
964   }
965 
966   /* send the buffer chain of your response */
967   rc = ngx_http_output_filter(r, output_chain);
968 
969   return rc;
970 }
971 
972 static ngx_int_t
ngx_http_groonga_handler_get(ngx_http_request_t * r)973 ngx_http_groonga_handler_get(ngx_http_request_t *r)
974 {
975   ngx_int_t rc;
976   ngx_str_t command_path;
977   ngx_http_groonga_handler_data_t *data;
978 
979   rc = ngx_http_groonga_extract_command_path(r, &command_path);
980   if (rc != NGX_OK) {
981     return rc;
982   }
983 
984   rc = ngx_http_groonga_handler_create_data(r, &data);
985   if (rc != NGX_OK) {
986     return rc;
987   }
988 
989   ngx_http_groonga_handler_process_command_path(r,
990                                                 &command_path,
991                                                 data,
992                                                 GRN_CTX_TAIL);
993 
994   /* discard request body, since we don't need it here */
995   rc = ngx_http_discard_request_body(r);
996   if (rc != NGX_OK) {
997     return rc;
998   }
999 
1000   rc = ngx_http_groonga_handler_send_response(r, data);
1001 
1002   return rc;
1003 }
1004 
1005 static void
ngx_http_groonga_handler_post_send_error_response(ngx_http_request_t * r,ngx_int_t rc)1006 ngx_http_groonga_handler_post_send_error_response(ngx_http_request_t *r,
1007                                                   ngx_int_t rc)
1008 {
1009   r->headers_out.status = rc;
1010   r->headers_out.content_length_n = 0;
1011   r->header_only = 1;
1012   rc = ngx_http_send_header(r);
1013   ngx_http_finalize_request(r, rc);
1014 }
1015 
1016 static void
ngx_http_groonga_handler_post(ngx_http_request_t * r)1017 ngx_http_groonga_handler_post(ngx_http_request_t *r)
1018 {
1019   ngx_int_t rc;
1020   ngx_str_t command_path;
1021   ngx_http_groonga_handler_data_t *data = NULL;
1022 
1023   rc = ngx_http_groonga_extract_command_path(r, &command_path);
1024   if (rc != NGX_OK) {
1025     ngx_http_groonga_handler_post_send_error_response(r, rc);
1026     return;
1027   }
1028 
1029   rc = ngx_http_groonga_handler_create_data(r, &data);
1030   if (rc != NGX_OK) {
1031     ngx_http_groonga_handler_post_send_error_response(r, rc);
1032     return;
1033   }
1034 
1035   ngx_http_groonga_handler_process_load(r, &command_path, data);
1036   rc = ngx_http_groonga_handler_send_response(r, data);
1037   ngx_http_finalize_request(r, rc);
1038 }
1039 
1040 static ngx_int_t
ngx_http_groonga_handler(ngx_http_request_t * r)1041 ngx_http_groonga_handler(ngx_http_request_t *r)
1042 {
1043   ngx_int_t rc;
1044 
1045   switch (r->method) {
1046   case NGX_HTTP_GET:
1047   case NGX_HTTP_HEAD:
1048     rc = ngx_http_groonga_handler_get(r);
1049     break;
1050   case NGX_HTTP_POST:
1051     rc = ngx_http_read_client_request_body(r, ngx_http_groonga_handler_post);
1052     if (rc < NGX_HTTP_SPECIAL_RESPONSE) {
1053       rc = NGX_DONE;
1054     }
1055     break;
1056   default:
1057     rc = NGX_HTTP_NOT_ALLOWED;
1058     break;
1059   }
1060 
1061   return rc;
1062 }
1063 
1064 static char *
ngx_http_groonga_conf_set_groonga_slot(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)1065 ngx_http_groonga_conf_set_groonga_slot(ngx_conf_t *cf, ngx_command_t *cmd,
1066                                        void *conf)
1067 {
1068   char *status;
1069   ngx_http_core_loc_conf_t *location_conf;
1070   ngx_http_groonga_loc_conf_t *groonga_location_conf = conf;
1071 
1072   status = ngx_conf_set_flag_slot(cf, cmd, conf);
1073   if (status != NGX_CONF_OK) {
1074     return status;
1075   }
1076 
1077   location_conf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
1078   if (groonga_location_conf->enabled) {
1079     location_conf->handler = ngx_http_groonga_handler;
1080     groonga_location_conf->name =
1081       ngx_str_null_terminate(cf->pool, &(location_conf->name));
1082     groonga_location_conf->config_file =
1083       ngx_str_null_terminate(cf->pool, &(cf->conf_file->file.name));
1084     groonga_location_conf->config_line = cf->conf_file->line;
1085   } else {
1086     location_conf->handler = NULL;
1087   }
1088 
1089   return NGX_CONF_OK;
1090 }
1091 
1092 static char *
ngx_http_groonga_conf_set_log_path_slot(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)1093 ngx_http_groonga_conf_set_log_path_slot(ngx_conf_t *cf, ngx_command_t *cmd,
1094                                         void *conf)
1095 {
1096   char *status;
1097   ngx_http_groonga_loc_conf_t *groonga_location_conf = conf;
1098 
1099   status = ngx_conf_set_str_slot(cf, cmd, conf);
1100   if (status != NGX_CONF_OK) {
1101     return status;
1102   }
1103 
1104   if (!groonga_location_conf->log_path.data) {
1105     return NGX_CONF_OK;
1106   }
1107 
1108   if (!ngx_str_is_custom_path(&(groonga_location_conf->log_path))) {
1109     return NGX_CONF_OK;
1110   }
1111 
1112   groonga_location_conf->log_file =
1113     ngx_conf_open_file(cf->cycle, &(groonga_location_conf->log_path));
1114   if (!groonga_location_conf->log_file) {
1115     ngx_log_error(NGX_LOG_ERR, cf->cycle->log, 0,
1116                   "http_groonga: failed to open groonga log file: <%V>",
1117                   &(groonga_location_conf->log_path));
1118     return NGX_CONF_ERROR;
1119   }
1120 
1121   return NGX_CONF_OK;
1122 }
1123 
1124 static char *
ngx_http_groonga_conf_set_log_level_slot(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)1125 ngx_http_groonga_conf_set_log_level_slot(ngx_conf_t *cf, ngx_command_t *cmd,
1126                                          void *conf)
1127 {
1128   char *status = NGX_CONF_OK;
1129   ngx_http_groonga_loc_conf_t *groonga_location_conf = conf;
1130   char *value;
1131 
1132   value = ngx_str_null_terminate(cf->cycle->pool,
1133                                  ((ngx_str_t *)cf->args->elts) + 1);
1134   if (!grn_log_level_parse(value, &(groonga_location_conf->log_level))) {
1135     status = "must be one of 'none', 'emergency', 'alert', "
1136       "'critical', 'error', 'warning', 'notice', 'info', 'debug' and 'dump'";
1137   }
1138   ngx_pfree(cf->cycle->pool, value);
1139 
1140   return status;
1141 }
1142 
1143 static char *
ngx_http_groonga_conf_set_query_log_path_slot(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)1144 ngx_http_groonga_conf_set_query_log_path_slot(ngx_conf_t *cf,
1145                                               ngx_command_t *cmd,
1146                                               void *conf)
1147 {
1148   char *status;
1149   ngx_http_groonga_loc_conf_t *groonga_location_conf = conf;
1150 
1151   status = ngx_conf_set_str_slot(cf, cmd, conf);
1152   if (status != NGX_CONF_OK) {
1153     return status;
1154   }
1155 
1156   if (!groonga_location_conf->query_log_path.data) {
1157     return NGX_CONF_OK;
1158   }
1159 
1160   if (!ngx_str_is_custom_path(&(groonga_location_conf->query_log_path))) {
1161     return NGX_CONF_OK;
1162   }
1163 
1164   groonga_location_conf->query_log_file =
1165     ngx_conf_open_file(cf->cycle, &(groonga_location_conf->query_log_path));
1166   if (!groonga_location_conf->query_log_file) {
1167     ngx_log_error(NGX_LOG_ERR, cf->cycle->log, 0,
1168                   "http_groonga: failed to open Groonga query log file: <%V>",
1169                   &(groonga_location_conf->query_log_path));
1170     return NGX_CONF_ERROR;
1171   }
1172 
1173   return NGX_CONF_OK;
1174 }
1175 
1176 static void *
ngx_http_groonga_create_loc_conf(ngx_conf_t * cf)1177 ngx_http_groonga_create_loc_conf(ngx_conf_t *cf)
1178 {
1179   ngx_http_groonga_loc_conf_t *conf;
1180   conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_groonga_loc_conf_t));
1181   if (conf == NULL) {
1182     return NGX_CONF_ERROR;
1183   }
1184 
1185   conf->enabled = NGX_CONF_UNSET;
1186   conf->database_path.data = NULL;
1187   conf->database_path.len = 0;
1188   conf->database_path_cstr = NULL;
1189   conf->database_auto_create = NGX_CONF_UNSET;
1190   conf->base_path.data = NULL;
1191   conf->base_path.len = 0;
1192   conf->log_path.data = NULL;
1193   conf->log_path.len = 0;
1194   conf->log_file = NULL;
1195   conf->log_level = GRN_LOG_DEFAULT_LEVEL;
1196   conf->query_log_path.data = NULL;
1197   conf->query_log_path.len = 0;
1198   conf->query_log_file = NULL;
1199   conf->cache_limit = NGX_CONF_UNSET_SIZE;
1200   conf->config_file = NULL;
1201   conf->config_line = 0;
1202   conf->cache = NULL;
1203   conf->cache_base_path.data = NULL;
1204   conf->cache_base_path.len = 0;
1205 
1206   return conf;
1207 }
1208 
1209 static char *
ngx_http_groonga_merge_loc_conf(ngx_conf_t * cf,void * parent,void * child)1210 ngx_http_groonga_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
1211 {
1212   ngx_http_groonga_loc_conf_t *prev = parent;
1213   ngx_http_groonga_loc_conf_t *conf = child;
1214   ngx_flag_t enabled = 0;
1215 
1216   if (conf->enabled != NGX_CONF_UNSET) {
1217     enabled = conf->enabled;
1218   }
1219 
1220   ngx_conf_merge_str_value(conf->database_path, prev->database_path, NULL);
1221   ngx_conf_merge_value(conf->database_auto_create,
1222                        prev->database_auto_create,
1223                        GRN_TRUE);
1224   ngx_conf_merge_size_value(conf->cache_limit, prev->cache_limit,
1225                             GRN_CACHE_DEFAULT_MAX_N_ENTRIES);
1226 
1227 #ifdef NGX_HTTP_GROONGA_LOG_PATH
1228   ngx_conf_merge_str_value(conf->log_path, prev->log_path,
1229                            NGX_HTTP_GROONGA_LOG_PATH);
1230   if (!conf->log_file &&
1231       ngx_str_is_custom_path(&(conf->log_path)) &&
1232       enabled) {
1233     conf->log_file = ngx_conf_open_file(cf->cycle, &(conf->log_path));
1234     if (!conf->log_file) {
1235       ngx_log_error(NGX_LOG_ERR, cf->cycle->log, 0,
1236                     "http_groonga: "
1237                     "failed to open the default Groonga log file: <%V>",
1238                     &(conf->log_path));
1239       return NGX_CONF_ERROR;
1240     }
1241   }
1242 #endif
1243 
1244   ngx_conf_merge_str_value(conf->query_log_path, prev->query_log_path,
1245                            NGX_HTTP_GROONGA_QUERY_LOG_PATH);
1246   if (!conf->query_log_file &&
1247       ngx_str_is_custom_path(&(conf->query_log_path)) &&
1248       enabled) {
1249     conf->query_log_file = ngx_conf_open_file(cf->cycle,
1250                                               &(conf->query_log_path));
1251     if (!conf->query_log_file) {
1252       ngx_log_error(NGX_LOG_ERR, cf->cycle->log, 0,
1253                     "http_groonga: "
1254                     "failed to open the default Groonga query log file: <%V>",
1255                     &(conf->query_log_path));
1256       return NGX_CONF_ERROR;
1257     }
1258   }
1259 
1260   ngx_conf_merge_str_value(conf->cache_base_path,
1261                            prev->cache_base_path,
1262                            NULL);
1263 
1264   return NGX_CONF_OK;
1265 }
1266 
1267 static void
ngx_http_groonga_each_loc_conf_in_tree(ngx_http_location_tree_node_t * node,ngx_http_groonga_loc_conf_callback_pt callback,void * user_data)1268 ngx_http_groonga_each_loc_conf_in_tree(ngx_http_location_tree_node_t *node,
1269                                        ngx_http_groonga_loc_conf_callback_pt callback,
1270                                        void *user_data)
1271 {
1272   if (!node) {
1273     return;
1274   }
1275 
1276   if (node->exact && node->exact->handler == ngx_http_groonga_handler) {
1277     callback(node->exact->loc_conf[ngx_http_groonga_module.ctx_index],
1278              user_data);
1279   }
1280 
1281   if (node->inclusive && node->inclusive->handler == ngx_http_groonga_handler) {
1282     callback(node->inclusive->loc_conf[ngx_http_groonga_module.ctx_index],
1283              user_data);
1284   }
1285 
1286   ngx_http_groonga_each_loc_conf_in_tree(node->left, callback, user_data);
1287   ngx_http_groonga_each_loc_conf_in_tree(node->right, callback, user_data);
1288   ngx_http_groonga_each_loc_conf_in_tree(node->tree, callback, user_data);
1289 }
1290 
1291 static void
ngx_http_groonga_each_loc_conf(ngx_http_conf_ctx_t * http_conf,ngx_http_groonga_loc_conf_callback_pt callback,void * user_data)1292 ngx_http_groonga_each_loc_conf(ngx_http_conf_ctx_t *http_conf,
1293                                ngx_http_groonga_loc_conf_callback_pt callback,
1294                                void *user_data)
1295 {
1296   ngx_http_core_main_conf_t *main_conf;
1297   ngx_http_core_srv_conf_t **server_confs;
1298   ngx_uint_t i;
1299 
1300   if (!http_conf) {
1301     return;
1302   }
1303 
1304   main_conf = http_conf->main_conf[ngx_http_core_module.ctx_index];
1305   server_confs = main_conf->servers.elts;
1306   for (i = 0; i < main_conf->servers.nelts; i++) {
1307     ngx_http_core_srv_conf_t *server_conf;
1308     ngx_http_core_loc_conf_t *location_conf;
1309 
1310     server_conf = server_confs[i];
1311     location_conf = server_conf->ctx->loc_conf[ngx_http_core_module.ctx_index];
1312     ngx_http_groonga_each_loc_conf_in_tree(location_conf->static_locations,
1313                                            callback,
1314                                            user_data);
1315 
1316 #if NGX_PCRE
1317     if (location_conf->regex_locations) {
1318       ngx_uint_t j;
1319       for (j = 0; location_conf->regex_locations[j]; j++) {
1320         ngx_http_core_loc_conf_t *regex_location_conf;
1321 
1322         regex_location_conf = location_conf->regex_locations[j];
1323         if (regex_location_conf->handler == ngx_http_groonga_handler) {
1324           callback(regex_location_conf->loc_conf[ngx_http_groonga_module.ctx_index],
1325                    user_data);
1326         }
1327       }
1328     }
1329 #endif
1330   }
1331 }
1332 
1333 static void
ngx_http_groonga_set_logger_callback(ngx_http_groonga_loc_conf_t * location_conf,void * user_data)1334 ngx_http_groonga_set_logger_callback(ngx_http_groonga_loc_conf_t *location_conf,
1335                                      void *user_data)
1336 {
1337   ngx_http_groonga_database_callback_data_t *data = user_data;
1338 
1339   data->rc = ngx_http_groonga_context_init_logger(location_conf,
1340                                                   data->pool,
1341                                                   data->log);
1342   if (data->rc != NGX_OK) {
1343     return;
1344   }
1345   data->rc = ngx_http_groonga_context_init_query_logger(location_conf,
1346                                                         data->pool,
1347                                                         data->log);
1348   if (data->rc != NGX_OK) {
1349     return;
1350   }
1351 }
1352 
1353 static ngx_int_t
ngx_http_groonga_mkdir_p(ngx_log_t * log,const char * dir_name)1354 ngx_http_groonga_mkdir_p(ngx_log_t *log, const char *dir_name)
1355 {
1356   char sub_path[PATH_MAX];
1357   size_t i, dir_name_length;
1358 
1359   dir_name_length = strlen(dir_name);
1360   sub_path[0] = dir_name[0];
1361   for (i = 1; i < dir_name_length + 1; i++) {
1362     if (dir_name[i] == '/' || dir_name[i] == '\0') {
1363       struct stat stat_buffer;
1364       sub_path[i] = '\0';
1365       if (stat(sub_path, &stat_buffer) == -1) {
1366         if (ngx_create_dir(sub_path, 0700) == -1) {
1367           ngx_log_error(NGX_LOG_EMERG, log, 0,
1368                         "failed to create directory: %s (%s): %s",
1369                         sub_path, dir_name,
1370                         strerror(errno));
1371           return NGX_ERROR;
1372         }
1373       }
1374     }
1375     sub_path[i] = dir_name[i];
1376   }
1377 
1378   return NGX_OK;
1379 }
1380 
1381 static void
ngx_http_groonga_create_database(ngx_http_groonga_loc_conf_t * location_conf,ngx_http_groonga_database_callback_data_t * data)1382 ngx_http_groonga_create_database(ngx_http_groonga_loc_conf_t *location_conf,
1383                                  ngx_http_groonga_database_callback_data_t *data)
1384 {
1385   const char *database_base_name;
1386 
1387   database_base_name = strrchr(location_conf->database_path_cstr, '/');
1388   if (database_base_name) {
1389     char database_dir[PATH_MAX];
1390     database_dir[0] = '\0';
1391     strncat(database_dir,
1392             location_conf->database_path_cstr,
1393             database_base_name - location_conf->database_path_cstr);
1394     data->rc = ngx_http_groonga_mkdir_p(data->log, database_dir);
1395     if (data->rc != NGX_OK) {
1396       return;
1397     }
1398   }
1399 
1400   location_conf->database =
1401     grn_db_create(context, location_conf->database_path_cstr, NULL);
1402   if (context->rc == GRN_SUCCESS) {
1403     return;
1404   }
1405 
1406   ngx_log_error(NGX_LOG_EMERG, data->log, 0,
1407                 "failed to create Groonga database: %s",
1408                 context->errbuf);
1409   data->rc = NGX_ERROR;
1410 }
1411 
1412 static void
ngx_http_groonga_open_database_callback(ngx_http_groonga_loc_conf_t * location_conf,void * user_data)1413 ngx_http_groonga_open_database_callback(ngx_http_groonga_loc_conf_t *location_conf,
1414                                         void *user_data)
1415 {
1416   ngx_http_groonga_database_callback_data_t *data = user_data;
1417 
1418   data->rc = ngx_http_groonga_context_init_logger(location_conf,
1419                                                   data->pool,
1420                                                   data->log);
1421   if (data->rc != NGX_OK) {
1422     return;
1423   }
1424   data->rc = ngx_http_groonga_context_init_query_logger(location_conf,
1425                                                         data->pool,
1426                                                         data->log);
1427   if (data->rc != NGX_OK) {
1428     return;
1429   }
1430 
1431   if (!location_conf->database_path.data) {
1432     ngx_log_error(NGX_LOG_EMERG, data->log, 0,
1433                   "%s: \"groonga_database\" must be specified in block at %s:%d",
1434                   location_conf->name,
1435                   location_conf->config_file,
1436                   location_conf->config_line);
1437     data->rc = NGX_ERROR;
1438     return;
1439   }
1440 
1441   if (!location_conf->database_path_cstr) {
1442     location_conf->database_path_cstr =
1443       ngx_str_null_terminate(data->pool, &(location_conf->database_path));
1444   }
1445 
1446   location_conf->database =
1447     grn_db_open(context, location_conf->database_path_cstr);
1448   if (context->rc != GRN_SUCCESS) {
1449     if (location_conf->database_auto_create) {
1450       ngx_http_groonga_create_database(location_conf, data);
1451     } else {
1452       ngx_log_error(NGX_LOG_EMERG, data->log, 0,
1453                     "failed to open Groonga database: %s",
1454                     context->errbuf);
1455       data->rc = NGX_ERROR;
1456     }
1457     if (data->rc != NGX_OK) {
1458       return;
1459     }
1460   }
1461 
1462   if (location_conf->cache_base_path.data &&
1463       ngx_str_is_custom_path(&(location_conf->cache_base_path))) {
1464     char cache_base_path[PATH_MAX];
1465     grn_memcpy(cache_base_path,
1466                location_conf->cache_base_path.data,
1467                location_conf->cache_base_path.len);
1468     cache_base_path[location_conf->cache_base_path.len] = '\0';
1469     location_conf->cache = grn_persistent_cache_open(context, cache_base_path);
1470   } else {
1471     location_conf->cache = grn_cache_open(context);
1472   }
1473   if (!location_conf->cache) {
1474     ngx_log_error(NGX_LOG_EMERG, data->log, 0,
1475                   "failed to open Groonga cache: %s",
1476                   context->errbuf);
1477     data->rc = NGX_ERROR;
1478     return;
1479   }
1480 
1481   if (location_conf->cache_limit != NGX_CONF_UNSET_SIZE) {
1482     grn_cache_set_max_n_entries(context,
1483                                 location_conf->cache,
1484                                 location_conf->cache_limit);
1485   }
1486 }
1487 
1488 static void
ngx_http_groonga_close_database_callback(ngx_http_groonga_loc_conf_t * location_conf,void * user_data)1489 ngx_http_groonga_close_database_callback(ngx_http_groonga_loc_conf_t *location_conf,
1490                                          void *user_data)
1491 {
1492   ngx_http_groonga_database_callback_data_t *data = user_data;
1493 
1494   ngx_http_groonga_context_init_logger(location_conf,
1495                                        data->pool,
1496                                        data->log);
1497   ngx_http_groonga_context_init_query_logger(location_conf,
1498                                              data->pool,
1499                                              data->log);
1500   grn_cache_current_set(context, location_conf->cache);
1501 
1502   grn_obj_close(context, location_conf->database);
1503   ngx_http_groonga_context_log_error(data->log);
1504 
1505   grn_cache_current_set(context, NULL);
1506   grn_cache_close(context, location_conf->cache);
1507 }
1508 
1509 static ngx_int_t
ngx_http_groonga_init_process(ngx_cycle_t * cycle)1510 ngx_http_groonga_init_process(ngx_cycle_t *cycle)
1511 {
1512   grn_rc rc;
1513   ngx_http_conf_ctx_t *http_conf;
1514   ngx_http_groonga_database_callback_data_t data;
1515 
1516   grn_thread_set_get_limit_func(ngx_http_groonga_get_thread_limit, NULL);
1517 
1518 #ifdef NGX_HTTP_GROONGA_LOG_PATH
1519   grn_default_logger_set_path(NGX_HTTP_GROONGA_LOG_PATH);
1520 #endif
1521 
1522   http_conf =
1523     (ngx_http_conf_ctx_t *)ngx_get_conf(cycle->conf_ctx, ngx_http_module);
1524 
1525   data.log = cycle->log;
1526   data.pool = cycle->pool;
1527   data.rc = NGX_OK;
1528   ngx_http_groonga_each_loc_conf(http_conf,
1529                                  ngx_http_groonga_set_logger_callback,
1530                                  &data);
1531 
1532   if (data.rc != NGX_OK) {
1533     return data.rc;
1534   }
1535 
1536   rc = grn_init();
1537   if (rc != GRN_SUCCESS) {
1538     return NGX_ERROR;
1539   }
1540 
1541   grn_set_segv_handler();
1542 
1543   rc = grn_ctx_init(context, GRN_NO_FLAGS);
1544   if (rc != GRN_SUCCESS) {
1545     return NGX_ERROR;
1546   }
1547 
1548   ngx_http_groonga_each_loc_conf(http_conf,
1549                                  ngx_http_groonga_open_database_callback,
1550                                  &data);
1551 
1552   return data.rc;
1553 }
1554 
1555 static void
ngx_http_groonga_exit_process(ngx_cycle_t * cycle)1556 ngx_http_groonga_exit_process(ngx_cycle_t *cycle)
1557 {
1558   ngx_http_conf_ctx_t *http_conf;
1559   ngx_http_groonga_database_callback_data_t data;
1560 
1561   http_conf =
1562     (ngx_http_conf_ctx_t *)ngx_get_conf(cycle->conf_ctx, ngx_http_module);
1563   data.log = cycle->log;
1564   data.pool = cycle->pool;
1565   ngx_http_groonga_each_loc_conf(http_conf,
1566                                  ngx_http_groonga_close_database_callback,
1567                                  &data);
1568 
1569   grn_ctx_fin(context);
1570 
1571   grn_fin();
1572 
1573   return;
1574 }
1575 
1576 /* entry point */
1577 static ngx_command_t ngx_http_groonga_commands[] = {
1578   { ngx_string("groonga"),
1579     NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1580     ngx_http_groonga_conf_set_groonga_slot,
1581     NGX_HTTP_LOC_CONF_OFFSET,
1582     offsetof(ngx_http_groonga_loc_conf_t, enabled),
1583     NULL },
1584 
1585   { ngx_string("groonga_database"),
1586     NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1587     ngx_conf_set_str_slot,
1588     NGX_HTTP_LOC_CONF_OFFSET,
1589     offsetof(ngx_http_groonga_loc_conf_t, database_path),
1590     NULL },
1591 
1592   { ngx_string("groonga_database_auto_create"),
1593     NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1594     ngx_conf_set_flag_slot,
1595     NGX_HTTP_LOC_CONF_OFFSET,
1596     offsetof(ngx_http_groonga_loc_conf_t, database_auto_create),
1597     NULL },
1598 
1599   { ngx_string("groonga_base_path"),
1600     NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1601     ngx_conf_set_str_slot,
1602     NGX_HTTP_LOC_CONF_OFFSET,
1603     offsetof(ngx_http_groonga_loc_conf_t, base_path),
1604     NULL },
1605 
1606   { ngx_string("groonga_log_path"),
1607     NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1608     ngx_http_groonga_conf_set_log_path_slot,
1609     NGX_HTTP_LOC_CONF_OFFSET,
1610     offsetof(ngx_http_groonga_loc_conf_t, log_path),
1611     NULL },
1612 
1613   { ngx_string("groonga_log_level"),
1614     NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1615     ngx_http_groonga_conf_set_log_level_slot,
1616     NGX_HTTP_LOC_CONF_OFFSET,
1617     0,
1618     NULL },
1619 
1620   { ngx_string("groonga_query_log_path"),
1621     NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1622     ngx_http_groonga_conf_set_query_log_path_slot,
1623     NGX_HTTP_LOC_CONF_OFFSET,
1624     offsetof(ngx_http_groonga_loc_conf_t, query_log_path),
1625     NULL },
1626 
1627   { ngx_string("groonga_cache_limit"),
1628     NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1629     ngx_conf_set_size_slot,
1630     NGX_HTTP_LOC_CONF_OFFSET,
1631     offsetof(ngx_http_groonga_loc_conf_t, cache_limit),
1632     NULL },
1633 
1634   { ngx_string("groonga_default_request_timeout"),
1635     NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1636     ngx_conf_set_msec_slot,
1637     NGX_HTTP_LOC_CONF_OFFSET,
1638     offsetof(ngx_http_groonga_loc_conf_t, default_request_timeout_msec),
1639     NULL },
1640 
1641   { ngx_string("groonga_cache_base_path"),
1642     NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1643     ngx_conf_set_str_slot,
1644     NGX_HTTP_LOC_CONF_OFFSET,
1645     offsetof(ngx_http_groonga_loc_conf_t, cache_base_path),
1646     NULL },
1647 
1648   ngx_null_command
1649 };
1650 
1651 static ngx_http_module_t ngx_http_groonga_module_ctx = {
1652   NULL, /* preconfiguration */
1653   NULL, /* postconfiguration */
1654 
1655   NULL, /* create main configuration */
1656   NULL, /* init main configuration */
1657 
1658   NULL, /* create server configuration */
1659   NULL, /* merge server configuration */
1660 
1661   ngx_http_groonga_create_loc_conf, /* create location configuration */
1662   ngx_http_groonga_merge_loc_conf, /* merge location configuration */
1663 };
1664 
1665 ngx_module_t ngx_http_groonga_module = {
1666   NGX_MODULE_V1,
1667   &ngx_http_groonga_module_ctx, /* module context */
1668   ngx_http_groonga_commands, /* module directives */
1669   NGX_HTTP_MODULE, /* module type */
1670   NULL, /* init master */
1671   NULL, /* init module */
1672   ngx_http_groonga_init_process, /* init process */
1673   NULL, /* init thread */
1674   NULL, /* exit thread */
1675   ngx_http_groonga_exit_process, /* exit process */
1676   NULL, /* exit master */
1677   NGX_MODULE_V1_PADDING
1678 };
1679