1 /*
2  * Copyright (c) 2009-2012, FRiCKLE <info@frickle.com>
3  * Copyright (c) 2009-2012, Piotr Sikora <piotr.sikora@frickle.com>
4  * Copyrithg (c) 2002-2011, Igor Sysoev <igor@sysoev.ru>
5  * All rights reserved.
6  *
7  * This project was fully funded by c2hosting.com.
8  * Included cache_purge functionality was fully funded by yo.se.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include <ngx_config.h>
33 #include <ngx_core.h>
34 #include <ngx_http.h>
35 #include <nginx.h>
36 
37 #if (NGX_HTTP_CACHE)
38 
39 #define SLOWFS_PROCESS_NAME "slowfs cache process"
40 
41 ngx_int_t   ngx_http_slowfs_init(ngx_conf_t *);
42 ngx_int_t   ngx_http_slowfs_add_variables(ngx_conf_t *);
43 void       *ngx_http_slowfs_create_loc_conf(ngx_conf_t *);
44 char       *ngx_http_slowfs_merge_loc_conf(ngx_conf_t *, void *, void *);
45 
46 char       *ngx_http_slowfs_cache_conf(ngx_conf_t *, ngx_command_t *, void *);
47 char       *ngx_http_slowfs_cache_key_conf(ngx_conf_t *, ngx_command_t *,
48                 void *);
49 char       *ngx_http_slowfs_cache_purge_conf(ngx_conf_t *, ngx_command_t *,
50                 void *);
51 
52 ngx_int_t   ngx_http_slowfs_handler(ngx_http_request_t *);
53 ngx_int_t   ngx_http_slowfs_cache_purge_handler(ngx_http_request_t *r);
54 
55 ngx_int_t   ngx_http_slowfs_cache_send(ngx_http_request_t *);
56 ngx_int_t   ngx_http_slowfs_static_send(ngx_http_request_t *);
57 void        ngx_http_slowfs_cache_update(ngx_http_request_t *,
58                 ngx_open_file_info_t *, ngx_str_t *);
59 ngx_int_t   ngx_http_slowfs_cache_purge(ngx_http_request_t *,
60                 ngx_http_file_cache_t *, ngx_http_complex_value_t *);
61 
62 ngx_int_t   ngx_http_slowfs_cache_status(ngx_http_request_t *,
63                 ngx_http_variable_value_t *, uintptr_t);
64 
65 typedef struct {
66     ngx_flag_t                 enabled;
67     ngx_shm_zone_t            *cache;
68     ngx_http_complex_value_t   cache_key;
69     ngx_uint_t                 cache_min_uses;
70     ngx_array_t               *cache_valid;
71     ngx_path_t                *temp_path;
72     size_t                     big_file_size;
73 } ngx_http_slowfs_loc_conf_t;
74 
75 typedef struct {
76     ngx_uint_t                 cache_status;
77 } ngx_http_slowfs_ctx_t;
78 
79 ngx_module_t  ngx_http_slowfs_module;
80 
81 static ngx_path_init_t  ngx_http_slowfs_temp_path = {
82     ngx_string("/tmp"), { 1, 2, 0 }
83 };
84 
85 static ngx_command_t  ngx_http_slowfs_module_commands[] = {
86 
87     { ngx_string("slowfs_cache"),
88       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
89       ngx_http_slowfs_cache_conf,
90       NGX_HTTP_LOC_CONF_OFFSET,
91       0,
92       NULL },
93 
94     { ngx_string("slowfs_cache_key"),
95       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
96       ngx_http_slowfs_cache_key_conf,
97       NGX_HTTP_LOC_CONF_OFFSET,
98       0,
99       NULL },
100 
101     { ngx_string("slowfs_cache_purge"),
102       NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
103       ngx_http_slowfs_cache_purge_conf,
104       NGX_HTTP_LOC_CONF_OFFSET,
105       0,
106       NULL },
107 
108     { ngx_string("slowfs_cache_path"),
109       NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE,
110       ngx_http_file_cache_set_slot,
111       0,
112       0,
113       &ngx_http_slowfs_module },
114 
115     { ngx_string("slowfs_cache_min_uses"),
116       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
117       ngx_conf_set_num_slot,
118       NGX_HTTP_LOC_CONF_OFFSET,
119       offsetof(ngx_http_slowfs_loc_conf_t, cache_min_uses),
120       NULL },
121 
122     { ngx_string("slowfs_cache_valid"),
123       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
124       ngx_http_file_cache_valid_set_slot,
125       NGX_HTTP_LOC_CONF_OFFSET,
126       offsetof(ngx_http_slowfs_loc_conf_t, cache_valid),
127       NULL },
128 
129     { ngx_string("slowfs_big_file_size"),
130       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
131       ngx_conf_set_size_slot,
132       NGX_HTTP_LOC_CONF_OFFSET,
133       offsetof(ngx_http_slowfs_loc_conf_t, big_file_size),
134       NULL },
135 
136     { ngx_string("slowfs_temp_path"),
137       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
138       ngx_conf_set_path_slot,
139       NGX_HTTP_LOC_CONF_OFFSET,
140       offsetof(ngx_http_slowfs_loc_conf_t, temp_path),
141       NULL },
142 
143       ngx_null_command
144 };
145 
146 static ngx_http_variable_t  ngx_http_slowfs_module_variables[] = {
147 
148     { ngx_string("slowfs_cache_status"), NULL,
149       ngx_http_slowfs_cache_status, 0,
150       NGX_HTTP_VAR_NOHASH|NGX_HTTP_VAR_NOCACHEABLE, 0 },
151 
152     { ngx_null_string, NULL, NULL, 0, 0, 0 }
153 };
154 
155 static ngx_http_module_t  ngx_http_slowfs_module_ctx = {
156     ngx_http_slowfs_add_variables,    /* preconfiguration */
157     ngx_http_slowfs_init,             /* postconfiguration */
158 
159     NULL,                             /* create main configuration */
160     NULL,                             /* init main configuration */
161 
162     NULL,                             /* create server configuration */
163     NULL,                             /* merge server configuration */
164 
165     ngx_http_slowfs_create_loc_conf,  /* create location configuration */
166     ngx_http_slowfs_merge_loc_conf    /* merge location configuration */
167 };
168 
169 ngx_module_t  ngx_http_slowfs_module = {
170     NGX_MODULE_V1,
171     &ngx_http_slowfs_module_ctx,      /* module context */
172     ngx_http_slowfs_module_commands,  /* module directives */
173     NGX_HTTP_MODULE,                  /* module type */
174     NULL,                             /* init master */
175     NULL,                             /* init module */
176     NULL,                             /* init process */
177     NULL,                             /* init thread */
178     NULL,                             /* exit thread */
179     NULL,                             /* exit process */
180     NULL,                             /* exit master */
181     NGX_MODULE_V1_PADDING
182 };
183 
184 /*
185  * source: ngx_http_static_module.c/ngx_http_static_handler
186  * Copyright (C) Igor Sysoev
187  */
188 ngx_int_t
ngx_http_slowfs_static_send(ngx_http_request_t * r)189 ngx_http_slowfs_static_send(ngx_http_request_t *r)
190 {
191     u_char                      *last, *location, *procname;
192     size_t                       root, len;
193     ngx_str_t                    path;
194     ngx_int_t                    rc;
195     ngx_uint_t                   level;
196     ngx_log_t                   *log;
197     ngx_buf_t                   *b;
198     ngx_chain_t                  out;
199     ngx_open_file_info_t         of;
200     ngx_http_core_loc_conf_t    *clcf;
201     /* slowfs */
202     ngx_http_slowfs_loc_conf_t  *slowcf;
203 #if defined(nginx_version) && (nginx_version < 8048)
204     ngx_http_slowfs_ctx_t       *slowctx;
205     ngx_uint_t                   old_status;
206 #endif
207     ngx_http_cache_t            *c;
208     time_t                       valid;
209 
210     log = r->connection->log;
211 
212     /*
213      * ngx_http_map_uri_to_path() allocates memory for terminating '\0'
214      * so we do not need to reserve memory for '/' for possible redirect
215      */
216 
217     last = ngx_http_map_uri_to_path(r, &path, &root, 0);
218     if (last == NULL) {
219         return NGX_HTTP_INTERNAL_SERVER_ERROR;
220     }
221 
222     path.len = last - path.data;
223 
224     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
225                    "http filename: \"%s\"", path.data);
226 
227     clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
228 
229     ngx_memzero(&of, sizeof(ngx_open_file_info_t));
230 
231 #if defined(nginx_version) && (nginx_version >= 8018)
232     of.read_ahead = clcf->read_ahead;
233 #endif
234     of.directio = clcf->directio;
235     of.valid = clcf->open_file_cache_valid;
236     of.min_uses = clcf->open_file_cache_min_uses;
237     of.errors = clcf->open_file_cache_errors;
238     of.events = clcf->open_file_cache_events;
239 
240     if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
241         != NGX_OK)
242     {
243         switch (of.err) {
244 
245         case 0:
246             return NGX_HTTP_INTERNAL_SERVER_ERROR;
247 
248         case NGX_ENOENT:
249         case NGX_ENOTDIR:
250         case NGX_ENAMETOOLONG:
251 
252             level = NGX_LOG_ERR;
253             rc = NGX_HTTP_NOT_FOUND;
254             break;
255 
256         case NGX_EACCES:
257 
258             level = NGX_LOG_ERR;
259             rc = NGX_HTTP_FORBIDDEN;
260             break;
261 
262         default:
263 
264             level = NGX_LOG_CRIT;
265             rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
266             break;
267         }
268 
269         if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {
270             ngx_log_error(level, log, of.err,
271                           "%s \"%s\" failed", of.failed, path.data);
272         }
273 
274         return rc;
275     }
276 
277     r->root_tested = !r->error_page;
278 
279     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http static fd: %d", of.fd);
280 
281     if (of.is_dir) {
282 
283         ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http dir");
284 
285         r->headers_out.location = ngx_palloc(r->pool, sizeof(ngx_table_elt_t));
286         if (r->headers_out.location == NULL) {
287             return NGX_HTTP_INTERNAL_SERVER_ERROR;
288         }
289 
290         len = r->uri.len + 1;
291 
292         if (!clcf->alias && clcf->root_lengths == NULL && r->args.len == 0) {
293             location = path.data + clcf->root.len;
294 
295             *last = '/';
296 
297         } else {
298             if (r->args.len) {
299                 len += r->args.len + 1;
300             }
301 
302             location = ngx_pnalloc(r->pool, len);
303             if (location == NULL) {
304                 return NGX_HTTP_INTERNAL_SERVER_ERROR;
305             }
306 
307             last = ngx_copy(location, r->uri.data, r->uri.len);
308 
309             *last = '/';
310 
311             if (r->args.len) {
312                 *++last = '?';
313                 ngx_memcpy(++last, r->args.data, r->args.len);
314             }
315         }
316 
317         /*
318          * we do not need to set the r->headers_out.location->hash and
319          * r->headers_out.location->key fields
320          */
321 
322         r->headers_out.location->value.len = len;
323         r->headers_out.location->value.data = location;
324 
325         return NGX_HTTP_MOVED_PERMANENTLY;
326     }
327 
328 #if !(NGX_WIN32) /* the not regular files are probably Unix specific */
329 
330     if (!of.is_file) {
331         ngx_log_error(NGX_LOG_CRIT, log, 0,
332                       "\"%s\" is not a regular file", path.data);
333 
334         return NGX_HTTP_NOT_FOUND;
335     }
336 
337 #endif
338 
339     log->action = "sending response to client";
340 
341     r->headers_out.status = NGX_HTTP_OK;
342     r->headers_out.content_length_n = of.size;
343     r->headers_out.last_modified_time = of.mtime;
344 
345     if (ngx_http_set_content_type(r) != NGX_OK) {
346         return NGX_HTTP_INTERNAL_SERVER_ERROR;
347     }
348 
349     if (r != r->main && of.size == 0) {
350         return ngx_http_send_header(r);
351     }
352 
353     r->allow_ranges = 1;
354 
355     /* slowfs */
356     slowcf = ngx_http_get_module_loc_conf(r, ngx_http_slowfs_module);
357     valid = ngx_http_file_cache_valid(slowcf->cache_valid, 200);
358 
359     /* Don't cache content that instantly expires. */
360     if (valid
361 #if defined(nginx_version) && (nginx_version < 8031)
362     /*
363      * Don't cache 0 byte files, because nginx doesn't flush response
364      * while serving them from cache and client timeouts.
365      * This has been fixed in nginx-0.8.31.
366      */
367        && of.size
368 #endif
369     ) {
370         c = r->cache;
371 
372         ngx_shmtx_lock(&c->file_cache->shpool->mutex);
373         if (c->node->uses >= c->min_uses && !c->node->updating) {
374             ngx_shmtx_unlock(&c->file_cache->shpool->mutex);
375 
376             if ((size_t) of.size < slowcf->big_file_size) {
377                 /*
378                  * Small files:
379                  * - copy file to the cache in worker process,
380                  * - send file to current client from the cache.
381                  */
382 #if defined(nginx_version) && (nginx_version < 8048)
383                 slowctx = ngx_http_get_module_ctx(r, ngx_http_slowfs_module);
384                 old_status = slowctx->cache_status;
385 #endif
386 
387 #if defined(nginx_version) && (nginx_version >= 1001012)
388                 ngx_shmtx_lock(&c->file_cache->shpool->mutex);
389                 c->node->count++;
390                 ngx_shmtx_unlock(&c->file_cache->shpool->mutex);
391 #endif
392 
393                 ngx_http_slowfs_cache_update(r, &of, &path);
394                 /* Allow cache_cleanup after cache_update. */
395                 c->updated = 0;
396 
397 #if defined(nginx_version) && (nginx_version < 8048)
398                 if (old_status == NGX_HTTP_CACHE_EXPIRED) {
399                     /*
400                      * Expired cached files don't increment counter,
401                      * because ngx_http_file_cache_exists isn't called.
402                      */
403                     ngx_shmtx_lock(&c->file_cache->shpool->mutex);
404                     c->node->count++;
405                     ngx_shmtx_unlock(&c->file_cache->shpool->mutex);
406                 }
407 #endif
408 
409                 rc = ngx_http_slowfs_cache_send(r);
410 
411 #if defined(nginx_version) && (nginx_version < 8048)
412                 slowctx->cache_status = old_status;
413 #endif
414 
415                 if (rc != NGX_DECLINED) {
416                     return rc;
417                 }
418 
419                 /* continue static processing for NGX_DECLINED... */
420             } else {
421                 /*
422                  * Big files:
423                  * - fork() new process,
424                  * - copy file to the cache in new process,
425                  * - send file to current client from the original source.
426                  */
427                 switch (fork()) {
428                 case -1: /* failed */
429                     ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
430                                   "fork() failed while spawning \"%s\"",
431                                   SLOWFS_PROCESS_NAME);
432                 break;
433                 case 0: /* child */
434                     ngx_pid = ngx_getpid();
435 
436                     len = sizeof(SLOWFS_PROCESS_NAME) - 1 + sizeof(":  to ") - 1
437                           + path.len + c->file.name.len;
438 
439                     procname = ngx_pnalloc(r->pool, len + 1);
440                     if (procname == NULL) {
441                         return NGX_HTTP_INTERNAL_SERVER_ERROR;
442                     }
443 
444                     last = ngx_snprintf(procname, len, SLOWFS_PROCESS_NAME
445                                         ": %V to %V", &path, &c->file.name);
446                     *last = '\0';
447 
448                     ngx_setproctitle((char *) procname);
449 
450                     ngx_http_slowfs_cache_update(r, &of, &path);
451 
452                     exit(0);
453                     break;
454                 default: /* parent */
455                     c->node = NULL;
456                     c->updated = 1;
457                     break;
458                 }
459             }
460         } else {
461             ngx_shmtx_unlock(&c->file_cache->shpool->mutex);
462         }
463     }
464     /* slowfs */
465 
466     /* we need to allocate all before the header would be sent */
467 
468     b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
469     if (b == NULL) {
470         return NGX_HTTP_INTERNAL_SERVER_ERROR;
471     }
472 
473     b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
474     if (b->file == NULL) {
475         return NGX_HTTP_INTERNAL_SERVER_ERROR;
476     }
477 
478     rc = ngx_http_send_header(r);
479 
480     if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
481         return rc;
482     }
483 
484     b->file_pos = 0;
485     b->file_last = of.size;
486 
487     b->in_file = b->file_last ? 1: 0;
488     b->last_buf = (r == r->main) ? 1: 0;
489     b->last_in_chain = 1;
490 
491     b->file->fd = of.fd;
492     b->file->name = path;
493     b->file->log = log;
494     b->file->directio = of.is_directio;
495 
496     out.buf = b;
497     out.next = NULL;
498 
499     return ngx_http_output_filter(r, &out);
500 }
501 
502 ngx_int_t
ngx_http_slowfs_cache_send(ngx_http_request_t * r)503 ngx_http_slowfs_cache_send(ngx_http_request_t *r)
504 {
505     ngx_http_slowfs_loc_conf_t  *slowcf;
506     ngx_http_slowfs_ctx_t       *slowctx;
507     ngx_http_cache_t            *c;
508     ngx_str_t                   *key;
509     ngx_int_t                    rc;
510 
511     slowcf = ngx_http_get_module_loc_conf(r, ngx_http_slowfs_module);
512     slowctx = ngx_http_get_module_ctx(r, ngx_http_slowfs_module);
513 
514     c = r->cache;
515     if (c != NULL) {
516         goto skip_alloc;
517     }
518 
519     c = ngx_pcalloc(r->pool, sizeof(ngx_http_cache_t));
520     if (c == NULL) {
521         return NGX_HTTP_INTERNAL_SERVER_ERROR;
522     }
523 
524     rc = ngx_array_init(&c->keys, r->pool, 1, sizeof(ngx_str_t));
525     if (rc != NGX_OK) {
526         return NGX_HTTP_INTERNAL_SERVER_ERROR;
527     }
528 
529     key = ngx_array_push(&c->keys);
530     if (key == NULL) {
531         return NGX_HTTP_INTERNAL_SERVER_ERROR;
532     }
533 
534     rc = ngx_http_complex_value(r, &slowcf->cache_key, key);
535     if (rc != NGX_OK) {
536         return NGX_HTTP_INTERNAL_SERVER_ERROR;
537     }
538 
539     slowctx = ngx_palloc(r->pool, sizeof(ngx_http_slowfs_ctx_t));
540     if (slowctx == NULL) {
541         return NGX_HTTP_INTERNAL_SERVER_ERROR;
542     }
543 
544     slowctx->cache_status = NGX_HTTP_CACHE_MISS;
545 
546     ngx_http_set_ctx(r, slowctx, ngx_http_slowfs_module);
547 
548     r->cache = c;
549     c->body_start = ngx_pagesize;
550     c->min_uses = slowcf->cache_min_uses;
551     c->file_cache = slowcf->cache->data;
552     c->file.log = r->connection->log;
553 
554     ngx_http_file_cache_create_key(r);
555 
556 skip_alloc:
557     rc = ngx_http_file_cache_open(r);
558     if (rc != NGX_OK) {
559         if (rc == NGX_HTTP_CACHE_STALE) {
560             /*
561              * Revert c->node->updating = 1, we want this to be true only when
562              * ngx_slowfs_cache is in the process of copying given file.
563              */
564             ngx_shmtx_lock(&c->file_cache->shpool->mutex);
565             c->node->updating = 0;
566             c->updating = 0;
567             ngx_shmtx_unlock(&c->file_cache->shpool->mutex);
568 
569             slowctx->cache_status = NGX_HTTP_CACHE_EXPIRED;
570         } else if (rc == NGX_HTTP_CACHE_UPDATING) {
571             slowctx->cache_status = NGX_HTTP_CACHE_EXPIRED;
572         }
573 
574         return NGX_DECLINED;
575     }
576 
577     r->connection->log->action = "sending cached response to client";
578 
579     slowctx->cache_status = NGX_HTTP_CACHE_HIT;
580 
581     r->headers_out.status = NGX_HTTP_OK;
582     r->headers_out.content_length_n = c->length - c->body_start;
583     r->headers_out.last_modified_time = c->last_modified;
584 
585     if (ngx_http_set_content_type(r) != NGX_OK) {
586         return NGX_HTTP_INTERNAL_SERVER_ERROR;
587     }
588 
589     r->allow_ranges = 1;
590 
591     return ngx_http_cache_send(r);
592 }
593 
594 void
ngx_http_slowfs_cache_update(ngx_http_request_t * r,ngx_open_file_info_t * of,ngx_str_t * path)595 ngx_http_slowfs_cache_update(ngx_http_request_t *r, ngx_open_file_info_t *of,
596     ngx_str_t *path)
597 {
598     ngx_http_slowfs_loc_conf_t  *slowcf;
599     ngx_temp_file_t             *tf;
600     ngx_http_cache_t            *c;
601     ngx_int_t                    rc;
602     u_char                      *buf;
603     time_t                       valid, now;
604     off_t                        size;
605     size_t                       len;
606     ssize_t                      n;
607 
608     c = r->cache;
609 
610     ngx_shmtx_lock(&c->file_cache->shpool->mutex);
611 
612     if (c->node->updating) {
613         /* race between concurrent processes, backoff */
614         c->node->count--;
615         ngx_shmtx_unlock(&c->file_cache->shpool->mutex);
616         return;
617     }
618 
619     c->node->updating = 1;
620     c->updating = 1;
621 
622     ngx_shmtx_unlock(&c->file_cache->shpool->mutex);
623 
624     r->connection->log->action = "populating cache";
625 
626     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
627                    "http file cache copy: \"%s\" to \"%s\"",
628                    path->data, c->file.name.data);
629 
630     len = 8 * ngx_pagesize;
631 
632     tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t));
633     if (tf == NULL) {
634         goto failed;
635     }
636 
637     buf = ngx_palloc(r->pool, len);
638     if (buf == NULL) {
639         goto failed;
640     }
641 
642     slowcf = ngx_http_get_module_loc_conf(r, ngx_http_slowfs_module);
643 
644     valid = ngx_http_file_cache_valid(slowcf->cache_valid, 200);
645 
646     now = ngx_time();
647 
648     c->valid_sec = now + valid;
649     c->date = now;
650     c->last_modified = r->headers_out.last_modified_time;
651 
652     /*
653      * We don't save headers, but we add empty line
654      * as a workaround for older nginx versions,
655      * so c->header_start < c->body_start.
656      */
657     c->body_start = c->header_start + 1;
658 
659     ngx_http_file_cache_set_header(r, buf);
660     *(buf + c->header_start) = LF;
661 
662     tf->file.fd = NGX_INVALID_FILE;
663     tf->file.log = r->connection->log;
664     tf->path = slowcf->temp_path;
665     tf->pool = r->pool;
666     tf->persistent = 1;
667 
668     rc = ngx_create_temp_file(&tf->file, tf->path, tf->pool, tf->persistent,
669                               tf->clean, tf->access);
670     if (rc != NGX_OK) {
671         goto failed;
672     }
673 
674     n = ngx_write_fd(tf->file.fd, buf, c->body_start);
675     if ((size_t) n != c->body_start) {
676         goto failed;
677     }
678 
679     size = of->size;
680 
681     /*
682      * source: ngx_file.c/ngx_copy_file
683      * Copyright (C) Igor Sysoev
684      */
685     while (size > 0) {
686 
687         if ((off_t) len > size) {
688             len = (size_t) size;
689         }
690 
691         n = ngx_read_fd(of->fd, buf, len);
692 
693         if (n == NGX_FILE_ERROR) {
694             ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
695                           ngx_read_fd_n " \"%s\" failed", path->data);
696             goto failed;
697         }
698 
699         if ((size_t) n != len) {
700             ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
701                           ngx_read_fd_n " has read only %z of %uz bytes",
702                           n, size);
703             goto failed;
704         }
705 
706         n = ngx_write_fd(tf->file.fd, buf, len);
707 
708         if (n == NGX_FILE_ERROR) {
709             ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
710                           ngx_write_fd_n " \"%s\" failed", tf->file.name.data);
711             goto failed;
712         }
713 
714         if ((size_t) n != len) {
715             ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
716                           ngx_write_fd_n " has written only %z of %uz bytes",
717                           n, size);
718             goto failed;
719         }
720 
721         size -= n;
722     }
723 
724     ngx_http_file_cache_update(r, tf);
725 
726     return;
727 
728 failed:
729     ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
730                   "http file cache copy: \"%s\" failed", path->data);
731 
732     ngx_http_file_cache_free(c, tf);
733 
734     return;
735 }
736 
737 ngx_int_t
ngx_http_slowfs_cache_purge(ngx_http_request_t * r,ngx_http_file_cache_t * cache,ngx_http_complex_value_t * cache_key)738 ngx_http_slowfs_cache_purge(ngx_http_request_t *r, ngx_http_file_cache_t *cache,
739     ngx_http_complex_value_t *cache_key)
740 {
741     ngx_http_cache_t           *c;
742     ngx_str_t                  *key;
743     ngx_int_t                   rc;
744 
745     rc = ngx_http_discard_request_body(r);
746     if (rc != NGX_OK) {
747         return NGX_ERROR;
748     }
749 
750     c = ngx_pcalloc(r->pool, sizeof(ngx_http_cache_t));
751     if (c == NULL) {
752         return NGX_ERROR;
753     }
754 
755     rc = ngx_array_init(&c->keys, r->pool, 1, sizeof(ngx_str_t));
756     if (rc != NGX_OK) {
757         return NGX_ERROR;
758     }
759 
760     key = ngx_array_push(&c->keys);
761     if (key == NULL) {
762         return NGX_ERROR;
763     }
764 
765     rc = ngx_http_complex_value(r, cache_key, key);
766     if (rc != NGX_OK) {
767         return NGX_ERROR;
768     }
769 
770     r->cache = c;
771     c->body_start = ngx_pagesize;
772     c->file_cache = cache;
773     c->file.log = r->connection->log;
774 
775     ngx_http_file_cache_create_key(r);
776 
777     rc = ngx_http_file_cache_open(r);
778 
779     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
780                    "http file cache purge: %i, \"%s\"",
781                    rc, c->file.name.data);
782 
783     if (rc == NGX_HTTP_CACHE_UPDATING || rc == NGX_HTTP_CACHE_STALE) {
784         rc = NGX_OK;
785     }
786 
787     if (rc != NGX_OK) {
788         if (rc == NGX_DECLINED) {
789             return rc;
790         } else {
791             return NGX_ERROR;
792         }
793     }
794 
795     /*
796      * delete file from disk but *keep* in-memory node,
797      * because other requests might still point to it.
798      */
799 
800     ngx_shmtx_lock(&cache->shpool->mutex);
801 
802     if (!c->node->exists) {
803         /* race between concurrent purges, backoff */
804         ngx_shmtx_unlock(&cache->shpool->mutex);
805         return NGX_DECLINED;
806     }
807 
808 #if defined(nginx_version) && (nginx_version >= 1000001)
809     cache->sh->size -= c->node->fs_size;
810     c->node->fs_size = 0;
811 #else
812     cache->sh->size -= (c->node->length + cache->bsize - 1) / cache->bsize;
813     c->node->length = 0;
814 #endif
815 
816     c->node->exists = 0;
817     c->node->updating = 0;
818     c->updating = 0;
819 
820     ngx_shmtx_unlock(&cache->shpool->mutex);
821 
822     if (ngx_delete_file(c->file.name.data) == NGX_FILE_ERROR) {
823         /* entry in error log is enough, don't notice client */
824         ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
825                       ngx_delete_file_n " \"%s\" failed", c->file.name.data);
826     }
827 
828     /* file deleted from cache */
829     return NGX_OK;
830 }
831 
832 ngx_int_t
ngx_http_slowfs_handler(ngx_http_request_t * r)833 ngx_http_slowfs_handler(ngx_http_request_t *r)
834 {
835     ngx_http_slowfs_loc_conf_t  *slowcf;
836     ngx_int_t                    rc;
837 
838     slowcf = ngx_http_get_module_loc_conf(r, ngx_http_slowfs_module);
839     if (!slowcf->enabled) {
840         return NGX_DECLINED;
841     }
842 
843     if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
844         return NGX_HTTP_NOT_ALLOWED;
845     }
846 
847     if (r->uri.data[r->uri.len - 1] == '/') {
848         return NGX_DECLINED;
849     }
850 
851 #if defined(nginx_version) \
852     && ((nginx_version < 7066) \
853         || ((nginx_version >= 8000) && (nginx_version < 8038)))
854     if (r->zero_in_uri) {
855         return NGX_DECLINED;
856     }
857 #endif
858 
859     rc = ngx_http_discard_request_body(r);
860     if (rc != NGX_OK) {
861         return rc;
862     }
863 
864     rc = ngx_http_slowfs_cache_send(r);
865     if (rc == NGX_DECLINED) {
866         rc = ngx_http_slowfs_static_send(r);
867     }
868 
869     return rc;
870 }
871 
872 static char ngx_http_cache_purge_success_page_top[] =
873 "<html>" CRLF
874 "<head><title>Successful purge</title></head>" CRLF
875 "<body bgcolor=\"white\">" CRLF
876 "<center><h1>Successful purge</h1>" CRLF
877 ;
878 
879 static char ngx_http_cache_purge_success_page_tail[] =
880 CRLF "</center>" CRLF
881 "<hr><center>" NGINX_VER "</center>" CRLF
882 "</body>" CRLF
883 "</html>" CRLF
884 ;
885 
886 ngx_int_t
ngx_http_slowfs_cache_purge_handler(ngx_http_request_t * r)887 ngx_http_slowfs_cache_purge_handler(ngx_http_request_t *r)
888 {
889     ngx_http_slowfs_loc_conf_t  *slowcf;
890     ngx_chain_t                  out;
891     ngx_buf_t                   *b;
892     ngx_str_t                   *key;
893     ngx_int_t                    rc;
894     size_t                       len;
895 
896     slowcf = ngx_http_get_module_loc_conf(r, ngx_http_slowfs_module);
897 
898     rc = ngx_http_slowfs_cache_purge(r, slowcf->cache->data,
899                                      &slowcf->cache_key);
900     if (rc == NGX_ERROR) {
901         return NGX_HTTP_INTERNAL_SERVER_ERROR;
902     } else if (rc == NGX_DECLINED) {
903         return NGX_HTTP_NOT_FOUND;
904     }
905 
906     key = r->cache->keys.elts;
907 
908     len = sizeof(ngx_http_cache_purge_success_page_top) - 1
909           + sizeof(ngx_http_cache_purge_success_page_tail) - 1
910           + sizeof("<br>Key : ") - 1 + sizeof(CRLF "<br>Path: ") - 1
911           + key[0].len + r->cache->file.name.len;
912 
913     r->headers_out.content_type.len = sizeof("text/html") - 1;
914     r->headers_out.content_type.data = (u_char *) "text/html";
915     r->headers_out.status = NGX_HTTP_OK;
916     r->headers_out.content_length_n = len;
917 
918     if (r->method == NGX_HTTP_HEAD) {
919         rc = ngx_http_send_header(r);
920         if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
921             return rc;
922         }
923     }
924 
925     b = ngx_create_temp_buf(r->pool, len);
926     if (b == NULL) {
927         return NGX_HTTP_INTERNAL_SERVER_ERROR;
928     }
929 
930     out.buf = b;
931     out.next = NULL;
932 
933     b->last = ngx_cpymem(b->last, ngx_http_cache_purge_success_page_top,
934                          sizeof(ngx_http_cache_purge_success_page_top) - 1);
935     b->last = ngx_cpymem(b->last, "<br>Key : ", sizeof("<br>Key : ") - 1);
936     b->last = ngx_cpymem(b->last, key[0].data, key[0].len);
937     b->last = ngx_cpymem(b->last, CRLF "<br>Path: ",
938                          sizeof(CRLF "<br>Path: ") - 1);
939     b->last = ngx_cpymem(b->last, r->cache->file.name.data,
940                          r->cache->file.name.len);
941     b->last = ngx_cpymem(b->last, ngx_http_cache_purge_success_page_tail,
942                          sizeof(ngx_http_cache_purge_success_page_tail) - 1);
943     b->last_buf = 1;
944 
945     rc = ngx_http_send_header(r);
946     if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
947        return rc;
948     }
949 
950     return ngx_http_output_filter(r, &out);
951 }
952 
953 /*
954  * source: ngx_http_upstream.c/ngx_http_upstream_cache_status
955  * Copyright (C) Igor Sysoev
956  */
957 ngx_int_t
ngx_http_slowfs_cache_status(ngx_http_request_t * r,ngx_http_variable_value_t * v,uintptr_t data)958 ngx_http_slowfs_cache_status(ngx_http_request_t *r,
959     ngx_http_variable_value_t *v, uintptr_t data)
960 {
961     ngx_http_slowfs_ctx_t  *slowctx;
962     ngx_uint_t              n;
963 
964     slowctx = ngx_http_get_module_ctx(r, ngx_http_slowfs_module);
965 
966     if (slowctx == NULL || slowctx->cache_status == 0) {
967         v->not_found = 1;
968         return NGX_OK;
969     }
970 
971     n = slowctx->cache_status - 1;
972 
973     v->valid = 1;
974     v->no_cacheable = 0;
975     v->not_found = 0;
976     v->len = ngx_http_cache_status[n].len;
977     v->data = ngx_http_cache_status[n].data;
978 
979     return NGX_OK;
980 }
981 
982 char *
ngx_http_slowfs_cache_conf(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)983 ngx_http_slowfs_cache_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
984 {
985     ngx_str_t                   *value = cf->args->elts;
986     ngx_http_slowfs_loc_conf_t  *slowcf = conf;
987 
988     if (slowcf->cache != NGX_CONF_UNSET_PTR && slowcf->cache != NULL) {
989         return "is either duplicate or collides with \"slowfs_cache_purge\"";
990     }
991 
992     if (ngx_strcmp(value[1].data, "off") == 0) {
993         slowcf->enabled = 0;
994         slowcf->cache = NULL;
995         return NGX_CONF_OK;
996     }
997 
998     slowcf->cache = ngx_shared_memory_add(cf, &value[1], 0,
999                                           &ngx_http_slowfs_module);
1000     if (slowcf->cache == NULL) {
1001         return NGX_CONF_ERROR;
1002     }
1003 
1004     slowcf->enabled = 1;
1005 
1006     return NGX_CONF_OK;
1007 }
1008 
1009 char *
ngx_http_slowfs_cache_key_conf(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)1010 ngx_http_slowfs_cache_key_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
1011 {
1012     ngx_str_t                         *value = cf->args->elts;
1013     ngx_http_slowfs_loc_conf_t        *slowcf = conf;
1014     ngx_http_compile_complex_value_t   ccv;
1015 
1016     if (slowcf->cache_key.value.len) {
1017         return "is either duplicate or collides with \"slowfs_cache_purge\"";
1018     }
1019 
1020     ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
1021 
1022     ccv.cf = cf;
1023     ccv.value = &value[1];
1024     ccv.complex_value = &slowcf->cache_key;
1025 
1026     if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1027         return NGX_CONF_ERROR;
1028     }
1029 
1030     return NGX_CONF_OK;
1031 }
1032 
1033 char *
ngx_http_slowfs_cache_purge_conf(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)1034 ngx_http_slowfs_cache_purge_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
1035 {
1036     ngx_str_t                         *value = cf->args->elts;
1037     ngx_http_slowfs_loc_conf_t        *slowcf = conf;
1038     ngx_http_core_loc_conf_t          *clcf;
1039     ngx_http_compile_complex_value_t   ccv;
1040 
1041     /* check for duplicates / collisions */
1042     if (slowcf->cache != NGX_CONF_UNSET_PTR && slowcf->cache != NULL) {
1043         return "is either duplicate or collides with \"slowfs_cache\"";
1044     }
1045 
1046     if (slowcf->cache_key.value.len) {
1047         return "is either duplicate or collides with \"slowfs_cache\"";
1048     }
1049 
1050     /* set slowfs_cache part */
1051     slowcf->cache = ngx_shared_memory_add(cf, &value[1], 0,
1052                                           &ngx_http_slowfs_module);
1053     if (slowcf->cache == NULL) {
1054         return NGX_CONF_ERROR;
1055     }
1056 
1057     /* set slowfs_cache_key part */
1058     ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
1059 
1060     ccv.cf = cf;
1061     ccv.value = &value[2];
1062     ccv.complex_value = &slowcf->cache_key;
1063 
1064     if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1065         return NGX_CONF_ERROR;
1066     }
1067 
1068     /* set handler */
1069     clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
1070 
1071     clcf->handler = ngx_http_slowfs_cache_purge_handler;
1072 
1073     return NGX_CONF_OK;
1074 }
1075 
1076 void *
ngx_http_slowfs_create_loc_conf(ngx_conf_t * cf)1077 ngx_http_slowfs_create_loc_conf(ngx_conf_t *cf)
1078 {
1079     ngx_http_slowfs_loc_conf_t  *slowcf;
1080 
1081     slowcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_slowfs_loc_conf_t));
1082     if (slowcf == NULL) {
1083         return NGX_CONF_ERROR;
1084     }
1085 
1086     /*
1087      * via ngx_pcalloc():
1088      * slowcf->cache_key = NULL;
1089      * slowcf->temp_path = NULL;
1090      */
1091 
1092     slowcf->enabled = NGX_CONF_UNSET;
1093     slowcf->cache = NGX_CONF_UNSET_PTR;
1094     slowcf->cache_min_uses = NGX_CONF_UNSET_UINT;
1095     slowcf->cache_valid = NGX_CONF_UNSET_PTR;
1096     slowcf->big_file_size = NGX_CONF_UNSET_SIZE;
1097 
1098     return slowcf;
1099 }
1100 
1101 char *
ngx_http_slowfs_merge_loc_conf(ngx_conf_t * cf,void * parent,void * child)1102 ngx_http_slowfs_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
1103 {
1104     ngx_http_slowfs_loc_conf_t  *prev = parent;
1105     ngx_http_slowfs_loc_conf_t  *slowcf = child;
1106 
1107     ngx_conf_merge_value(slowcf->enabled, prev->enabled, 0);
1108 
1109     if (slowcf->cache_key.value.data == NULL) {
1110         slowcf->cache_key = prev->cache_key;
1111     }
1112 
1113     ngx_conf_merge_ptr_value(slowcf->cache, prev->cache, NULL);
1114     if (slowcf->cache && slowcf->cache->data == NULL) {
1115         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1116                            "\"slowfs_cache\" zone \"%V\" is unknown",
1117                            &slowcf->cache->shm.name);
1118         return NGX_CONF_ERROR;
1119     }
1120 
1121     ngx_conf_merge_uint_value(slowcf->cache_min_uses, prev->cache_min_uses, 1);
1122 
1123     ngx_conf_merge_ptr_value(slowcf->cache_valid, prev->cache_valid, NULL);
1124 
1125     ngx_conf_merge_size_value(slowcf->big_file_size, prev->big_file_size,
1126                               131072);
1127 
1128     if (ngx_conf_merge_path_value(cf, &slowcf->temp_path, prev->temp_path,
1129                                   &ngx_http_slowfs_temp_path) != NGX_OK)
1130     {
1131         return NGX_CONF_ERROR;
1132     }
1133 
1134     return NGX_CONF_OK;
1135 }
1136 
1137 ngx_int_t
ngx_http_slowfs_add_variables(ngx_conf_t * cf)1138 ngx_http_slowfs_add_variables(ngx_conf_t *cf)
1139 {
1140     ngx_http_variable_t  *var, *v;
1141 
1142     v = ngx_http_slowfs_module_variables;
1143 
1144     var = ngx_http_add_variable(cf, &v->name, v->flags);
1145     if (var == NULL) {
1146         return NGX_ERROR;
1147     }
1148 
1149     var->get_handler = v->get_handler;
1150     var->data = v->data;
1151 
1152     return NGX_OK;
1153 }
1154 
1155 ngx_int_t
ngx_http_slowfs_init(ngx_conf_t * cf)1156 ngx_http_slowfs_init(ngx_conf_t *cf)
1157 {
1158     ngx_http_handler_pt        *h;
1159     ngx_http_core_main_conf_t  *cmcf;
1160 
1161     cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
1162 
1163     h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
1164     if (h == NULL) {
1165         return NGX_ERROR;
1166     }
1167 
1168     *h = ngx_http_slowfs_handler;
1169 
1170     return NGX_OK;
1171 }
1172 
1173 #else /* !NGX_HTTP_CACHE */
1174 
1175 static ngx_http_module_t  ngx_http_slowfs_module_ctx = {
1176     NULL,  /* preconfiguration */
1177     NULL,  /* postconfiguration */
1178 
1179     NULL,  /* create main configuration */
1180     NULL,  /* init main configuration */
1181 
1182     NULL,  /* create server configuration */
1183     NULL,  /* merge server configuration */
1184 
1185     NULL,  /* create location configuration */
1186     NULL,  /* merge location configuration */
1187 };
1188 
1189 ngx_module_t  ngx_http_slowfs_module = {
1190     NGX_MODULE_V1,
1191     &ngx_http_slowfs_module_ctx,  /* module context */
1192     NULL,                         /* module directives */
1193     NGX_HTTP_MODULE,              /* module type */
1194     NULL,                         /* init master */
1195     NULL,                         /* init module */
1196     NULL,                         /* init process */
1197     NULL,                         /* init thread */
1198     NULL,                         /* exit thread */
1199     NULL,                         /* exit process */
1200     NULL,                         /* exit master */
1201     NGX_MODULE_V1_PADDING
1202 };
1203 
1204 #endif /* NGX_HTTP_CACHE */
1205