summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--ot_accesslist.c46
-rw-r--r--tests/integration/test_dynamic_accesslist.py50
3 files changed, 92 insertions, 6 deletions
diff --git a/Makefile b/Makefile
index 61eb872..dbc6166 100644
--- a/Makefile
+++ b/Makefile
@@ -136,7 +136,7 @@ check-sanitize: $(TESTBIN).sanitize
136PYTHON?=python3 136PYTHON?=python3
137# The client timeout is cut right down so that tests can reach the expiry 137# The client timeout is cut right down so that tests can reach the expiry
138# sweep. tests/integration/test_expiry.py mirrors these two numbers. 138# sweep. tests/integration/test_expiry.py mirrors these two numbers.
139FEATURES_integration=$(FEATURES) -DWANT_IP_FROM_QUERY_STRING -DWANT_ACCESSLIST_BLACK -DWANT_KEEPALIVE -DOT_CLIENT_TIMEOUT=5 -DOT_CLIENT_TIMEOUT_CHECKINTERVAL=1 139FEATURES_integration=$(FEATURES) -DWANT_IP_FROM_QUERY_STRING -DWANT_ACCESSLIST_BLACK -DWANT_DYNAMIC_ACCESSLIST -DWANT_KEEPALIVE -DOT_CLIENT_TIMEOUT=5 -DOT_CLIENT_TIMEOUT_CHECKINTERVAL=1
140CFLAGS_integration=$(CFLAGS) $(OPTS_debug) $(FEATURES_integration) -fsanitize=address,undefined -fno-omit-frame-pointer 140CFLAGS_integration=$(CFLAGS) $(OPTS_debug) $(FEATURES_integration) -fsanitize=address,undefined -fno-omit-frame-pointer
141LDFLAGS_integration=$(LDFLAGS) -fsanitize=address,undefined 141LDFLAGS_integration=$(LDFLAGS) -fsanitize=address,undefined
142 142
diff --git a/ot_accesslist.c b/ot_accesslist.c
index e8e0476..df084b8 100644
--- a/ot_accesslist.c
+++ b/ot_accesslist.c
@@ -273,7 +273,8 @@ static void *accesslist_worker(void *args) {
273} 273}
274 274
275#ifdef WANT_DYNAMIC_ACCESSLIST 275#ifdef WANT_DYNAMIC_ACCESSLIST
276static pthread_t thread_adder_id, thread_deleter_id; 276static pthread_t thread_adder_id, thread_deleter_id;
277static _Atomic int adder_left, deleter_left;
277static void *accesslist_adddel_worker(const char *fifoname, ot_accesslist *_Atomic *adding_to, ot_accesslist *_Atomic *removing_from) { 278static void *accesslist_adddel_worker(const char *fifoname, ot_accesslist *_Atomic *adding_to, ot_accesslist *_Atomic *removing_from) {
278 struct stat st; 279 struct stat st;
279 280
@@ -297,6 +298,9 @@ static void *accesslist_adddel_worker(const char *fifoname, ot_accesslist *_
297 ssize_t linelen; 298 ssize_t linelen;
298 299
299 if (!fifo) { 300 if (!fifo) {
301 /* Woken on purpose: the loop condition above decides what happens next */
302 if (errno == EINTR)
303 continue;
300 fprintf(stderr, "Error when reading dynamic accesslists: Couldn't open FIFO at %s, error: %s\n", fifoname, strerror(errno)); 304 fprintf(stderr, "Error when reading dynamic accesslists: Couldn't open FIFO at %s, error: %s\n", fifoname, strerror(errno));
301 return NULL; 305 return NULL;
302 } 306 }
@@ -324,8 +328,14 @@ static void *accesslist_adddel_worker(const char *fifoname, ot_accesslist *_
324 ptrdiff_t off = exactmatch - (*removing_from)->list; 328 ptrdiff_t off = exactmatch - (*removing_from)->list;
325 ot_accesslist *accesslist_new = accesslist_make(*removing_from, (*removing_from)->size - 1); 329 ot_accesslist *accesslist_new = accesslist_make(*removing_from, (*removing_from)->size - 1);
326 if (accesslist_new) { 330 if (accesslist_new) {
327 memcpy(accesslist_new->list, (*removing_from)->list, sizeof(ot_hash) * off); 331 size_t after = (*removing_from)->size - off - 1;
328 memcpy(accesslist_new->list + off, (*removing_from)->list + off + 1, sizeof(ot_hash) * ((*removing_from)->size - off - 1)); 332
333 /* Removing the only entry leaves a list with no room at all, and
334 memcpy may not be handed that even for nothing */
335 if (off)
336 memcpy(accesslist_new->list, (*removing_from)->list, sizeof(ot_hash) * off);
337 if (after)
338 memcpy(accesslist_new->list + off, (*removing_from)->list + off + 1, sizeof(ot_hash) * after);
329 (*removing_from)->base = time_now_minutes(); 339 (*removing_from)->base = time_now_minutes();
330 *removing_from = accesslist_new; 340 *removing_from = accesslist_new;
331 } 341 }
@@ -360,6 +370,7 @@ static void *accesslist_adddel_worker(const char *fifoname, ot_accesslist *_
360 pthread_mutex_unlock(&g_accesslist_mutex); 370 pthread_mutex_unlock(&g_accesslist_mutex);
361 } 371 }
362 372
373 free(line);
363 fclose(fifo); 374 fclose(fifo);
364 } 375 }
365 return NULL; 376 return NULL;
@@ -367,11 +378,30 @@ static void *accesslist_adddel_worker(const char *fifoname, ot_accesslist *_
367 378
368static void *accesslist_adder_worker(void *args) { 379static void *accesslist_adder_worker(void *args) {
369 (void)args; 380 (void)args;
370 return accesslist_adddel_worker(g_accesslist_pipe_add, &g_accesslist_add, &g_accesslist_delete); 381 accesslist_adddel_worker(g_accesslist_pipe_add, &g_accesslist_add, &g_accesslist_delete);
382 atomic_store(&adder_left, 1);
383 return NULL;
371} 384}
372static void *accesslist_deleter_worker(void *args) { 385static void *accesslist_deleter_worker(void *args) {
373 (void)args; 386 (void)args;
374 return accesslist_adddel_worker(g_accesslist_pipe_delete, &g_accesslist_delete, &g_accesslist_add); 387 accesslist_adddel_worker(g_accesslist_pipe_delete, &g_accesslist_delete, &g_accesslist_add);
388 atomic_store(&deleter_left, 1);
389 return NULL;
390}
391
392/* These sit in a blocking open or read on a FIFO, so they are woken the way
393 the other workers are, and the signal is repeated because it only
394 interrupts a call already under way. The pipe name is what says whether
395 the thread was ever started. */
396static void accesslist_stop_fifo_worker(pthread_t thread, _Atomic int *left, const char *pipe_name) {
397 if (!pipe_name)
398 return;
399
400 while (!atomic_load(left)) {
401 pthread_kill(thread, OT_SIGNAL_WAKE);
402 usleep(1000);
403 }
404 pthread_join(thread, NULL);
375} 405}
376#endif 406#endif
377 407
@@ -392,6 +422,12 @@ void accesslist_deinit(void) {
392 pthread_kill(thread_id, SIGHUP); 422 pthread_kill(thread_id, SIGHUP);
393 pthread_join(thread_id, NULL); 423 pthread_join(thread_id, NULL);
394 424
425#ifdef WANT_DYNAMIC_ACCESSLIST
426 /* Before the lists they are reading are freed below */
427 accesslist_stop_fifo_worker(thread_adder_id, &adder_left, g_accesslist_pipe_add);
428 accesslist_stop_fifo_worker(thread_deleter_id, &deleter_left, g_accesslist_pipe_delete);
429#endif
430
395 pthread_mutex_lock(&g_accesslist_mutex); 431 pthread_mutex_lock(&g_accesslist_mutex);
396 432
397 g_accesslist = accesslist_free(g_accesslist); 433 g_accesslist = accesslist_free(g_accesslist);
diff --git a/tests/integration/test_dynamic_accesslist.py b/tests/integration/test_dynamic_accesslist.py
new file mode 100644
index 0000000..7fa5fde
--- /dev/null
+++ b/tests/integration/test_dynamic_accesslist.py
@@ -0,0 +1,50 @@
1"""Hashes added to and removed from the blacklist while the tracker runs.
2
3The two FIFOs are read by threads of their own, which is also what makes
4this the test that drives those threads through a shutdown.
5"""
6
7import os
8import time
9import unittest
10
11from harness import TrackerTestCase
12
13BLOCKED = bytes(range(1, 21))
14
15
16def as_hex(info_hash):
17 return "".join("%02x" % b for b in info_hash)
18
19
20class DynamicAccesslist(TrackerTestCase):
21 config = ("access.blacklist {dir}/blacklist\n"
22 "access.fifo_add {dir}/add.fifo\n"
23 "access.fifo_delete {dir}/delete.fifo\n")
24 files = {"blacklist": b""}
25
26 def feed(self, name, info_hash):
27 """Writing to the FIFO blocks until the reader has it, so this also
28 tells us the thread is alive."""
29 with open(os.path.join(self.tracker.dir, name), "w") as fifo:
30 fifo.write("%s added by a test\n" % as_hex(info_hash))
31
32 def wait_for(self, info_hash, blocked, patience=5.0):
33 """The reader thread picks the hash up on its own schedule, so this
34 waits for the answer to change rather than assuming it already has."""
35 deadline = time.time() + patience
36 while time.time() < deadline:
37 answer = self.tracker.announce(info_hash)
38 if (b"failure reason" in answer) == blocked:
39 return True
40 time.sleep(0.05)
41 return False
42
43 def test_a_hash_can_be_blocked_and_released_while_running(self):
44 self.assertNotIn(b"failure reason", self.tracker.announce(BLOCKED))
45
46 self.feed("add.fifo", BLOCKED)
47 self.assertTrue(self.wait_for(BLOCKED, blocked=True), "the hash was never blocked")
48
49 self.feed("delete.fifo", BLOCKED)
50 self.assertTrue(self.wait_for(BLOCKED, blocked=False), "the hash was never released")