Line data Source code
1 : /* Copyright (c) 2017-2021, The Tor Project, Inc. */
2 : /* See LICENSE for licensing information */
3 :
4 : #include "core/or/or.h"
5 : #include "app/config/config.h"
6 : #include "feature/dircache/conscache.h"
7 : #include "lib/encoding/confline.h"
8 : #include "test/test.h"
9 :
10 : #ifdef HAVE_UTIME_H
11 : #include <utime.h>
12 : #endif
13 :
14 : static void
15 1 : test_conscache_open_failure(void *arg)
16 : {
17 1 : (void) arg;
18 : /* Try opening a directory that doesn't exist and which we shouldn't be
19 : * able to create. */
20 1 : consensus_cache_t *cache = consensus_cache_open("a/b/c/d/e/f/g", 128);
21 1 : tt_ptr_op(cache, OP_EQ, NULL);
22 :
23 1 : done:
24 1 : ;
25 1 : }
26 :
27 : static void
28 1 : test_conscache_simple_usage(void *arg)
29 : {
30 1 : (void)arg;
31 1 : consensus_cache_entry_t *ent = NULL, *ent2 = NULL;
32 :
33 : /* Make a temporary datadir for these tests */
34 1 : char *ddir_fname = tor_strdup(get_fname_rnd("datadir_cache"));
35 1 : tor_free(get_options_mutable()->CacheDirectory);
36 1 : get_options_mutable()->CacheDirectory = tor_strdup(ddir_fname);
37 1 : check_private_dir(ddir_fname, CPD_CREATE, NULL);
38 1 : consensus_cache_t *cache = consensus_cache_open("cons", 128);
39 :
40 1 : tt_assert(cache);
41 :
42 : /* Create object; make sure it exists. */
43 1 : config_line_t *labels = NULL;
44 1 : config_line_append(&labels, "Hello", "world");
45 1 : config_line_append(&labels, "Adios", "planetas");
46 1 : ent = consensus_cache_add(cache,
47 : labels, (const uint8_t *)"A\0B\0C", 5);
48 1 : config_free_lines(labels);
49 1 : labels = NULL;
50 1 : tt_assert(ent);
51 :
52 : /* Make a second object */
53 1 : config_line_append(&labels, "Hello", "mundo");
54 1 : config_line_append(&labels, "Adios", "planets");
55 1 : ent2 = consensus_cache_add(cache,
56 : labels, (const uint8_t *)"xyzzy", 5);
57 1 : config_free_lines(labels);
58 1 : labels = NULL;
59 1 : tt_assert(ent2);
60 1 : tt_assert(! consensus_cache_entry_is_mapped(ent2));
61 1 : consensus_cache_entry_decref(ent2);
62 1 : ent2 = NULL;
63 :
64 : /* Check get_value */
65 1 : tt_ptr_op(NULL, OP_EQ, consensus_cache_entry_get_value(ent, "hebbo"));
66 1 : tt_str_op("world", OP_EQ, consensus_cache_entry_get_value(ent, "Hello"));
67 :
68 : /* Check find_first */
69 1 : ent2 = consensus_cache_find_first(cache, "Hello", "world!");
70 1 : tt_ptr_op(ent2, OP_EQ, NULL);
71 1 : ent2 = consensus_cache_find_first(cache, "Hello", "world");
72 1 : tt_ptr_op(ent2, OP_EQ, ent);
73 1 : ent2 = consensus_cache_find_first(cache, "Hello", "mundo");
74 1 : tt_ptr_op(ent2, OP_NE, ent);
75 :
76 1 : tt_assert(! consensus_cache_entry_is_mapped(ent));
77 :
78 : /* Check get_body */
79 1 : const uint8_t *bp = NULL;
80 1 : size_t sz = 0;
81 1 : int r = consensus_cache_entry_get_body(ent, &bp, &sz);
82 1 : tt_int_op(r, OP_EQ, 0);
83 1 : tt_u64_op(sz, OP_EQ, 5);
84 1 : tt_mem_op(bp, OP_EQ, "A\0B\0C", 5);
85 1 : tt_assert(consensus_cache_entry_is_mapped(ent));
86 :
87 : /* Free and re-create the cache, to rescan the directory. */
88 1 : consensus_cache_free(cache);
89 1 : consensus_cache_entry_decref(ent);
90 1 : cache = consensus_cache_open("cons", 128);
91 :
92 : /* Make sure the entry is still there */
93 1 : ent = consensus_cache_find_first(cache, "Hello", "mundo");
94 1 : tt_assert(ent);
95 1 : ent2 = consensus_cache_find_first(cache, "Adios", "planets");
96 1 : tt_ptr_op(ent, OP_EQ, ent2);
97 1 : consensus_cache_entry_incref(ent);
98 1 : tt_assert(! consensus_cache_entry_is_mapped(ent));
99 1 : r = consensus_cache_entry_get_body(ent, &bp, &sz);
100 1 : tt_int_op(r, OP_EQ, 0);
101 1 : tt_u64_op(sz, OP_EQ, 5);
102 1 : tt_mem_op(bp, OP_EQ, "xyzzy", 5);
103 1 : tt_assert(consensus_cache_entry_is_mapped(ent));
104 :
105 : /* There should be two entries total. */
106 1 : smartlist_t *entries = smartlist_new();
107 1 : consensus_cache_find_all(entries, cache, NULL, NULL);
108 1 : int n = smartlist_len(entries);
109 1 : smartlist_free(entries);
110 1 : tt_int_op(n, OP_EQ, 2);
111 :
112 1 : done:
113 1 : consensus_cache_entry_decref(ent);
114 1 : tor_free(ddir_fname);
115 1 : consensus_cache_free(cache);
116 1 : }
117 :
118 : static void
119 1 : test_conscache_cleanup(void *arg)
120 : {
121 1 : (void)arg;
122 1 : const int N = 20;
123 2 : consensus_cache_entry_t **ents =
124 1 : tor_calloc(N, sizeof(consensus_cache_entry_t*));
125 :
126 : /* Make a temporary datadir for these tests */
127 1 : char *ddir_fname = tor_strdup(get_fname_rnd("datadir_cache"));
128 1 : tor_free(get_options_mutable()->CacheDirectory);
129 1 : get_options_mutable()->CacheDirectory = tor_strdup(ddir_fname);
130 1 : check_private_dir(ddir_fname, CPD_CREATE, NULL);
131 1 : consensus_cache_t *cache = consensus_cache_open("cons", 128);
132 :
133 1 : tt_assert(cache);
134 :
135 : /* Create a bunch of entries. */
136 : int i;
137 21 : for (i = 0; i < N; ++i) {
138 20 : config_line_t *labels = NULL;
139 20 : char num[8];
140 20 : tor_snprintf(num, sizeof(num), "%d", i);
141 20 : config_line_append(&labels, "test-id", "cleanup");
142 20 : config_line_append(&labels, "index", num);
143 20 : size_t bodylen = i * 3;
144 20 : uint8_t *body = tor_malloc(bodylen);
145 20 : memset(body, i, bodylen);
146 20 : ents[i] = consensus_cache_add(cache, labels, body, bodylen);
147 20 : tor_free(body);
148 20 : config_free_lines(labels);
149 20 : tt_assert(ents[i]);
150 : /* We're still holding a reference to each entry at this point. */
151 : }
152 :
153 : /* Page all of the entries into RAM */
154 21 : for (i = 0; i < N; ++i) {
155 20 : const uint8_t *bp;
156 20 : size_t sz;
157 20 : tt_assert(! consensus_cache_entry_is_mapped(ents[i]));
158 20 : consensus_cache_entry_get_body(ents[i], &bp, &sz);
159 20 : tt_assert(consensus_cache_entry_is_mapped(ents[i]));
160 : }
161 :
162 : /* Mark some of the entries as deletable. */
163 3 : for (i = 7; i < N; i += 7) {
164 2 : consensus_cache_entry_mark_for_removal(ents[i]);
165 2 : tt_assert(consensus_cache_entry_is_mapped(ents[i]));
166 : }
167 :
168 : /* Mark some of the entries as aggressively unpaged. */
169 7 : for (i = 3; i < N; i += 3) {
170 6 : consensus_cache_entry_mark_for_aggressive_release(ents[i]);
171 6 : tt_assert(consensus_cache_entry_is_mapped(ents[i]));
172 : }
173 :
174 : /* Incref some of the entries again */
175 11 : for (i = 0; i < N; i += 2) {
176 10 : consensus_cache_entry_incref(ents[i]);
177 : }
178 :
179 : /* Now we're going to decref everything. We do so at a specific time. I'm
180 : * picking the moment when I was writing this test, at 2017-04-05 12:16:48
181 : * UTC. */
182 1 : const time_t example_time = 1491394608;
183 1 : update_approx_time(example_time);
184 22 : for (i = 0; i < N; ++i) {
185 20 : consensus_cache_entry_decref(ents[i]);
186 20 : if (i % 2) {
187 10 : ents[i] = NULL; /* We're no longer holding any reference here. */
188 : }
189 : }
190 :
191 : /* At this point, the aggressively-released items with refcount 1 should
192 : * be unmapped. Nothing should be deleted. */
193 1 : consensus_cache_entry_t *e_tmp;
194 1 : e_tmp = consensus_cache_find_first(cache, "index", "3");
195 1 : tt_assert(e_tmp);
196 1 : tt_assert(! consensus_cache_entry_is_mapped(e_tmp));
197 1 : e_tmp = consensus_cache_find_first(cache, "index", "5");
198 1 : tt_assert(e_tmp);
199 1 : tt_assert(consensus_cache_entry_is_mapped(e_tmp));
200 1 : e_tmp = consensus_cache_find_first(cache, "index", "6");
201 1 : tt_assert(e_tmp);
202 1 : tt_assert(consensus_cache_entry_is_mapped(e_tmp));
203 1 : e_tmp = consensus_cache_find_first(cache, "index", "7");
204 1 : tt_ptr_op(e_tmp, OP_EQ, NULL); // not found because pending deletion.
205 :
206 : /* Delete the pending-deletion items. */
207 1 : consensus_cache_delete_pending(cache, 0);
208 : {
209 1 : smartlist_t *entries = smartlist_new();
210 1 : consensus_cache_find_all(entries, cache, NULL, NULL);
211 1 : int n = smartlist_len(entries);
212 1 : smartlist_free(entries);
213 1 : tt_int_op(n, OP_EQ, 20 - 2); /* 1 entry was deleted; 1 is not-found. */
214 : }
215 1 : e_tmp = consensus_cache_find_first(cache, "index", "7"); // refcnt == 1...
216 1 : tt_ptr_op(e_tmp, OP_EQ, NULL); // so deleted.
217 1 : e_tmp = consensus_cache_find_first(cache, "index", "14"); // refcnt == 2
218 1 : tt_ptr_op(e_tmp, OP_EQ, NULL); // not deleted; but not found.
219 :
220 : /* Now do lazy unmapping. */
221 : // should do nothing.
222 1 : consensus_cache_unmap_lazy(cache, example_time - 10);
223 1 : e_tmp = consensus_cache_find_first(cache, "index", "11");
224 1 : tt_assert(e_tmp);
225 1 : tt_assert(consensus_cache_entry_is_mapped(e_tmp));
226 : // should actually unmap
227 1 : consensus_cache_unmap_lazy(cache, example_time + 10);
228 1 : e_tmp = consensus_cache_find_first(cache, "index", "11");
229 1 : tt_assert(e_tmp);
230 1 : tt_assert(! consensus_cache_entry_is_mapped(e_tmp));
231 : // This one will still be mapped, since it has a reference.
232 1 : e_tmp = consensus_cache_find_first(cache, "index", "16");
233 1 : tt_assert(e_tmp);
234 1 : tt_assert(consensus_cache_entry_is_mapped(e_tmp));
235 :
236 21 : for (i = 0; i < N; ++i) {
237 20 : consensus_cache_entry_decref(ents[i]);
238 20 : ents[i] = NULL;
239 : }
240 :
241 : /* Free and re-create the cache, to rescan the directory. Make sure the
242 : * deleted thing is still deleted, along with the other deleted thing. */
243 1 : consensus_cache_free(cache);
244 1 : cache = consensus_cache_open("cons", 128);
245 : {
246 1 : smartlist_t *entries = smartlist_new();
247 1 : consensus_cache_find_all(entries, cache, NULL, NULL);
248 1 : int n = smartlist_len(entries);
249 1 : smartlist_free(entries);
250 1 : tt_int_op(n, OP_EQ, 18);
251 : }
252 :
253 1 : done:
254 21 : for (i = 0; i < N; ++i) {
255 20 : consensus_cache_entry_decref(ents[i]);
256 : }
257 1 : tor_free(ents);
258 1 : tor_free(ddir_fname);
259 1 : consensus_cache_free(cache);
260 1 : }
261 :
262 : static void
263 1 : test_conscache_filter(void *arg)
264 : {
265 1 : (void)arg;
266 1 : const int N = 30;
267 1 : smartlist_t *lst = NULL;
268 :
269 : /* Make a temporary datadir for these tests */
270 1 : char *ddir_fname = tor_strdup(get_fname_rnd("datadir_cache"));
271 1 : tor_free(get_options_mutable()->CacheDirectory);
272 1 : get_options_mutable()->CacheDirectory = tor_strdup(ddir_fname);
273 1 : check_private_dir(ddir_fname, CPD_CREATE, NULL);
274 1 : consensus_cache_t *cache = consensus_cache_open("cons", 128);
275 :
276 1 : tt_assert(cache);
277 :
278 : /* Create a bunch of entries with different labels */
279 : int i;
280 31 : for (i = 0; i < N; ++i) {
281 30 : config_line_t *labels = NULL;
282 30 : char num[8];
283 30 : tor_snprintf(num, sizeof(num), "%d", i);
284 30 : config_line_append(&labels, "test-id", "filter");
285 30 : config_line_append(&labels, "index", num);
286 30 : tor_snprintf(num, sizeof(num), "%d", i % 3);
287 30 : config_line_append(&labels, "mod3", num);
288 30 : tor_snprintf(num, sizeof(num), "%d", i % 5);
289 30 : config_line_append(&labels, "mod5", num);
290 :
291 30 : size_t bodylen = i * 3;
292 30 : uint8_t *body = tor_malloc(bodylen);
293 30 : memset(body, i, bodylen);
294 30 : consensus_cache_entry_t *ent =
295 30 : consensus_cache_add(cache, labels, body, bodylen);
296 30 : tor_free(body);
297 30 : config_free_lines(labels);
298 30 : tt_assert(ent);
299 30 : consensus_cache_entry_decref(ent);
300 : }
301 :
302 1 : lst = smartlist_new();
303 : /* Find nothing. */
304 1 : consensus_cache_find_all(lst, cache, "mod5", "5");
305 1 : tt_int_op(smartlist_len(lst), OP_EQ, 0);
306 : /* Find everything. */
307 1 : consensus_cache_find_all(lst, cache, "test-id", "filter");
308 1 : tt_int_op(smartlist_len(lst), OP_EQ, N);
309 :
310 : /* Now filter to find the entries that have i%3 == 1 */
311 1 : consensus_cache_filter_list(lst, "mod3", "1");
312 1 : tt_int_op(smartlist_len(lst), OP_EQ, 10);
313 : /* Now filter to find the entries that also have i%5 == 3 */
314 1 : consensus_cache_filter_list(lst, "mod5", "3");
315 1 : tt_int_op(smartlist_len(lst), OP_EQ, 2);
316 : /* So now we have those entries for which i%15 == 13. */
317 :
318 1 : consensus_cache_entry_t *ent1 = smartlist_get(lst, 0);
319 1 : consensus_cache_entry_t *ent2 = smartlist_get(lst, 1);
320 1 : const char *idx1 = consensus_cache_entry_get_value(ent1, "index");
321 1 : const char *idx2 = consensus_cache_entry_get_value(ent2, "index");
322 1 : tt_assert( (!strcmp(idx1, "28") && !strcmp(idx2, "13")) ||
323 : (!strcmp(idx1, "13") && !strcmp(idx2, "28")) );
324 :
325 1 : done:
326 1 : tor_free(ddir_fname);
327 1 : consensus_cache_free(cache);
328 1 : smartlist_free(lst);
329 1 : }
330 :
331 : #define ENT(name) \
332 : { #name, test_conscache_ ## name, TT_FORK, NULL, NULL }
333 :
334 : struct testcase_t conscache_tests[] = {
335 : ENT(open_failure),
336 : ENT(simple_usage),
337 : ENT(cleanup),
338 : ENT(filter),
339 : END_OF_TESTCASES
340 : };
|