/src/h2o/lib/common/filecache.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2015 DeNA Co., Ltd., Kazuho Oku |
3 | | * |
4 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | | * of this software and associated documentation files (the "Software"), to |
6 | | * deal in the Software without restriction, including without limitation the |
7 | | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
8 | | * sell copies of the Software, and to permit persons to whom the Software is |
9 | | * furnished to do so, subject to the following conditions: |
10 | | * |
11 | | * The above copyright notice and this permission notice shall be included in |
12 | | * all copies or substantial portions of the Software. |
13 | | * |
14 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
17 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
18 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
19 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
20 | | * IN THE SOFTWARE. |
21 | | */ |
22 | | #include <assert.h> |
23 | | #include <errno.h> |
24 | | #include <fcntl.h> |
25 | | #include <stddef.h> |
26 | | #include <unistd.h> |
27 | | #include "khash.h" |
28 | | #include "h2o/memory.h" |
29 | | #include "h2o/filecache.h" |
30 | | |
31 | | KHASH_SET_INIT_STR(opencache_set) |
32 | | |
33 | | struct st_h2o_filecache_t { |
34 | | khash_t(opencache_set) * hash; |
35 | | h2o_linklist_t lru; |
36 | | size_t capacity; |
37 | | }; |
38 | | |
39 | | static inline void release_from_cache(h2o_filecache_t *cache, khiter_t iter) |
40 | 0 | { |
41 | 0 | const char *path = kh_key(cache->hash, iter); |
42 | 0 | h2o_filecache_ref_t *ref = H2O_STRUCT_FROM_MEMBER(h2o_filecache_ref_t, _path, path); |
43 | | |
44 | | /* detach from list */ |
45 | 0 | kh_del(opencache_set, cache->hash, iter); |
46 | 0 | h2o_linklist_unlink(&ref->_lru); |
47 | | |
48 | | /* and close */ |
49 | 0 | h2o_filecache_close_file(ref); |
50 | 0 | } |
51 | | |
52 | | h2o_filecache_t *h2o_filecache_create(size_t capacity) |
53 | 1 | { |
54 | 1 | h2o_filecache_t *cache = h2o_mem_alloc(sizeof(*cache)); |
55 | | |
56 | 1 | cache->hash = kh_init(opencache_set); |
57 | 1 | h2o_linklist_init_anchor(&cache->lru); |
58 | 1 | cache->capacity = capacity; |
59 | | |
60 | 1 | return cache; |
61 | 1 | } |
62 | | |
63 | | void h2o_filecache_destroy(h2o_filecache_t *cache) |
64 | 0 | { |
65 | 0 | h2o_filecache_clear(cache); |
66 | 0 | assert(kh_size(cache->hash) == 0); |
67 | 0 | assert(h2o_linklist_is_empty(&cache->lru)); |
68 | 0 | kh_destroy(opencache_set, cache->hash); |
69 | 0 | free(cache); |
70 | 0 | } |
71 | | |
72 | | void h2o_filecache_clear(h2o_filecache_t *cache) |
73 | 0 | { |
74 | 0 | khiter_t iter; |
75 | 0 | for (iter = kh_begin(cache->hash); iter != kh_end(cache->hash); ++iter) { |
76 | 0 | if (!kh_exist(cache->hash, iter)) |
77 | 0 | continue; |
78 | 0 | release_from_cache(cache, iter); |
79 | 0 | } |
80 | 0 | assert(kh_size(cache->hash) == 0); |
81 | 0 | } |
82 | | |
83 | | h2o_filecache_ref_t *h2o_filecache_open_file(h2o_filecache_t *cache, const char *path, int oflag) |
84 | 1.15k | { |
85 | 1.15k | khiter_t iter = kh_get(opencache_set, cache->hash, path); |
86 | 1.15k | h2o_filecache_ref_t *ref; |
87 | 1.15k | int dummy; |
88 | | |
89 | | /* lookup cache, and return the one if found */ |
90 | 1.15k | if (iter != kh_end(cache->hash)) { |
91 | 0 | ref = H2O_STRUCT_FROM_MEMBER(h2o_filecache_ref_t, _path, kh_key(cache->hash, iter)); |
92 | 0 | ++ref->_refcnt; |
93 | 0 | goto Exit; |
94 | 0 | } |
95 | | |
96 | | /* create a new cache entry */ |
97 | 1.15k | ref = h2o_mem_alloc(offsetof(h2o_filecache_ref_t, _path) + strlen(path) + 1); |
98 | 1.15k | ref->_refcnt = 1; |
99 | 1.15k | ref->_lru = (h2o_linklist_t){NULL}; |
100 | 1.15k | strcpy(ref->_path, path); |
101 | | |
102 | | /* if cache is used, then... */ |
103 | 1.15k | if (cache->capacity != 0) { |
104 | | /* purge one entry from LRU if cache is full */ |
105 | 0 | if (kh_size(cache->hash) == cache->capacity) { |
106 | 0 | h2o_filecache_ref_t *purge_ref = H2O_STRUCT_FROM_MEMBER(h2o_filecache_ref_t, _lru, cache->lru.prev); |
107 | 0 | khiter_t purge_iter = kh_get(opencache_set, cache->hash, purge_ref->_path); |
108 | 0 | assert(purge_iter != kh_end(cache->hash)); |
109 | 0 | release_from_cache(cache, purge_iter); |
110 | 0 | } |
111 | | /* assign the new entry */ |
112 | 0 | ++ref->_refcnt; |
113 | 0 | kh_put(opencache_set, cache->hash, ref->_path, &dummy); |
114 | 0 | h2o_linklist_insert(cache->lru.next, &ref->_lru); |
115 | 0 | } |
116 | | |
117 | | /* open the file, or memoize the error */ |
118 | 1.15k | if ((ref->fd = open(path, oflag)) != -1 && fstat(ref->fd, &ref->st) == 0) { |
119 | 0 | ref->_last_modified.str[0] = '\0'; |
120 | 0 | ref->_etag.len = 0; |
121 | 1.15k | } else { |
122 | 1.15k | ref->open_err = errno; |
123 | 1.15k | if (ref->fd != -1) { |
124 | 0 | close(ref->fd); |
125 | 0 | ref->fd = -1; |
126 | 0 | } |
127 | 1.15k | } |
128 | | |
129 | 1.15k | Exit: |
130 | | /* if the cache entry retains an error, return it instead of the reference */ |
131 | 1.15k | if (ref->fd == -1) { |
132 | 1.15k | errno = ref->open_err; |
133 | 1.15k | h2o_filecache_close_file(ref); |
134 | 1.15k | ref = NULL; |
135 | 1.15k | } |
136 | 1.15k | return ref; |
137 | 1.15k | } |
138 | | |
139 | | void h2o_filecache_close_file(h2o_filecache_ref_t *ref) |
140 | 1.15k | { |
141 | 1.15k | if (--ref->_refcnt != 0) |
142 | 0 | return; |
143 | 1.15k | assert(!h2o_linklist_is_linked(&ref->_lru)); |
144 | 1.15k | if (ref->fd != -1) { |
145 | 0 | close(ref->fd); |
146 | 0 | ref->fd = -1; |
147 | 0 | } |
148 | 1.15k | free(ref); |
149 | 1.15k | } |
150 | | |
151 | | struct tm *h2o_filecache_get_last_modified(h2o_filecache_ref_t *ref, char *outbuf) |
152 | 0 | { |
153 | 0 | assert(ref->fd != -1); |
154 | 0 | if (ref->_last_modified.str[0] == '\0') { |
155 | 0 | gmtime_r(&ref->st.st_mtime, &ref->_last_modified.gm); |
156 | 0 | h2o_time2str_rfc1123(ref->_last_modified.str, &ref->_last_modified.gm); |
157 | 0 | } |
158 | 0 | if (outbuf != NULL) |
159 | 0 | memcpy(outbuf, ref->_last_modified.str, H2O_TIMESTR_RFC1123_LEN + 1); |
160 | 0 | return &ref->_last_modified.gm; |
161 | 0 | } |
162 | | |
163 | | size_t h2o_filecache_get_etag(h2o_filecache_ref_t *ref, char *outbuf) |
164 | 0 | { |
165 | 0 | assert(ref->fd != -1); |
166 | 0 | if (ref->_etag.len == 0) |
167 | 0 | ref->_etag.len = sprintf(ref->_etag.buf, "\"%08x-%zx\"", (unsigned)ref->st.st_mtime, (size_t)ref->st.st_size); |
168 | 0 | memcpy(outbuf, ref->_etag.buf, ref->_etag.len + 1); |
169 | 0 | return ref->_etag.len; |
170 | 0 | } |
171 | | |
172 | | int h2o_filecache_compare_etag_strong(const char *tag1, size_t tag1_len, const char *tag2, size_t tag2_len) |
173 | 0 | { |
174 | 0 | size_t i; |
175 | | |
176 | | /* first check if tag1 a valid strong etag, then just strictly compare tag1 with tag2 */ |
177 | 0 | if (tag1_len < sizeof("\"\"")) /* strong etag should be at least one character quoted */ |
178 | 0 | return 0; |
179 | 0 | if (tag1[0] != '"' || tag1[tag1_len - 1] != '"') /* not a valid etag */ |
180 | 0 | return 0; |
181 | 0 | for (i = 1; i < tag1_len - 1; i++) { |
182 | 0 | if (tag1[i] < 0x21 || tag1[i] == '"') /* VCHAR except double quotes, plus obs-text */ |
183 | 0 | return 0; |
184 | 0 | } |
185 | 0 | return h2o_memis(tag1, tag1_len, tag2, tag2_len); |
186 | 0 | } |