static void test_buffered_write_size(abts_case *tc, void *data) { const apr_size_t data_len = strlen(NEWFILEDATA); apr_file_t *thefile; apr_finfo_t finfo; apr_status_t rv; apr_size_t bytes; rv = apr_file_open(&thefile, NEWFILENAME, APR_READ | APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BUFFERED | APR_DELONCLOSE, APR_OS_DEFAULT, p); APR_ASSERT_SUCCESS(tc, "open file", rv); /* A funny thing happened to me the other day: I wrote something * into a buffered file, then asked for its size using * apr_file_info_get; and guess what? The size was 0! That's not a * nice way to behave. */ bytes = data_len; rv = apr_file_write(thefile, NEWFILEDATA, &bytes); APR_ASSERT_SUCCESS(tc, "write file contents", rv); ABTS_TRUE(tc, data_len == bytes); rv = apr_file_info_get(&finfo, APR_FINFO_SIZE, thefile); APR_ASSERT_SUCCESS(tc, "get file size", rv); ABTS_TRUE(tc, bytes == (apr_size_t) finfo.size); apr_file_close(thefile); }
static void test_strtok(abts_case *tc, void *data) { struct { char *input; char *sep; } cases[] = { { "", "Z" }, { " asdf jkl; 77889909 \r\n\1\2\3Z", " \r\n\3\2\1" }, { NULL, /* but who cares if apr_strtok() segfaults? */ " \t" }, #if 0 /* don't do this... you deserve to segfault */ { "a b c ", NULL }, #endif { " a b c ", "" }, { "a b c ", " " } }; int curtc; for (curtc = 0; curtc < sizeof cases / sizeof cases[0]; curtc++) { char *retval1, *retval2; char *str1, *str2; char *state; str1 = apr_pstrdup(p, cases[curtc].input); str2 = apr_pstrdup(p, cases[curtc].input); do { retval1 = apr_strtok(str1, cases[curtc].sep, &state); retval2 = strtok(str2, cases[curtc].sep); if (!retval1) { ABTS_TRUE(tc, retval2 == NULL); } else { ABTS_TRUE(tc, retval2 != NULL); ABTS_STR_EQUAL(tc, retval2, retval1); } str1 = str2 = NULL; /* make sure we pass NULL on subsequent calls */ } while (retval1); } }
static void test_singleton_subnets(abts_case *tc, void *data) { const char *v4addrs[] = { "127.0.0.1", "129.42.18.99", "63.161.155.20", "207.46.230.229", "64.208.42.36", "198.144.203.195", "192.18.97.241", "198.137.240.91", "62.156.179.119", "204.177.92.181" }; apr_ipsubnet_t *ipsub; apr_sockaddr_t *sa; apr_status_t rv; int i, j, rc; for (i = 0; i < sizeof v4addrs / sizeof v4addrs[0]; i++) { rv = apr_ipsubnet_create(&ipsub, v4addrs[i], NULL, p); ABTS_TRUE(tc, rv == APR_SUCCESS); for (j = 0; j < sizeof v4addrs / sizeof v4addrs[0]; j++) { rv = apr_sockaddr_info_get(&sa, v4addrs[j], APR_INET, 0, 0, p); ABTS_TRUE(tc, rv == APR_SUCCESS); rc = apr_ipsubnet_test(ipsub, sa); if (!strcmp(v4addrs[i], v4addrs[j])) { ABTS_TRUE(tc, rc != 0); } else { ABTS_TRUE(tc, rc == 0); } } } /* same for v6? */ }
static void string_cpystrn(abts_case *tc, void *data) { char buf[6], *ret; buf[5] = 'Z'; ret = apr_cpystrn(buf, "123456", 5); ABTS_STR_EQUAL(tc, "1234", buf); ABTS_PTR_EQUAL(tc, buf + 4, ret); ABTS_TRUE(tc, *ret == '\0'); ABTS_TRUE(tc, ret[1] == 'Z'); }
static void test_print_addr(abts_case *tc, void *data) { apr_sockaddr_t *sa; apr_status_t rv; char *s; rv = apr_sockaddr_info_get(&sa, "0.0.0.0", APR_INET, 80, 0, p); APR_ASSERT_SUCCESS(tc, "Problem generating sockaddr", rv); s = apr_psprintf(p, "foo %pI bar", sa); ABTS_STR_EQUAL(tc, "foo 0.0.0.0:80 bar", s); #if APR_HAVE_IPV6 rv = apr_sockaddr_info_get(&sa, "::ffff:0.0.0.0", APR_INET6, 80, 0, p); APR_ASSERT_SUCCESS(tc, "Problem generating sockaddr", rv); if (rv == APR_SUCCESS) ABTS_TRUE(tc, sa != NULL); if (rv == APR_SUCCESS && sa) { /* sa should now be a v4-mapped IPv6 address. */ char buf[128]; int rc; rc = apr_sockaddr_is_wildcard(sa); ABTS_INT_NEQUAL(tc, 0, rc); memset(buf, 'z', sizeof buf); APR_ASSERT_SUCCESS(tc, "could not get IP address", apr_sockaddr_ip_getbuf(buf, 22, sa)); ABTS_STR_EQUAL(tc, "0.0.0.0", buf); } #endif }
static void test_timeout(abts_case *tc, apr_reslist_t *rl) { apr_status_t rv; my_resource_t *resources[RESLIST_HMAX]; my_resource_t *res; void *vp; int i; apr_reslist_timeout_set(rl, 1000); /* deplete all possible resources from the resource list * so that the next call will block until timeout is reached * (since there are no other threads to make a resource * available) */ for (i = 0; i < RESLIST_HMAX; i++) { rv = apr_reslist_acquire(rl, (void**)&resources[i]); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); } /* next call will block until timeout is reached */ rv = apr_reslist_acquire(rl, &vp); ABTS_TRUE(tc, APR_STATUS_IS_TIMEUP(rv)); res = vp; /* release the resources; otherwise the destroy operation * will blow */ for (i = 0; i < RESLIST_HMAX; i++) { rv = apr_reslist_release(rl, resources[i]); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); } }
static void sendto_receivefrom6(abts_case *tc, void *data) { int failed; sendto_receivefrom_helper(tc, "::1", APR_INET6); failed = tc->failed; tc->failed = 0; ABTS_TRUE(tc, !failed); }
static void test_ldap_connection(abts_case *tc, LDAP *ldap) { int version = LDAP_VERSION3; int failures, result; /* always default to LDAP V3 */ ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &version); for (failures=0; failures<10; failures++) { result = ldap_simple_bind_s(ldap, (char *)NULL, (char *)NULL); if (LDAP_SERVER_DOWN != result) break; } ABTS_TRUE(tc, result == LDAP_SUCCESS); if (result != LDAP_SUCCESS) { abts_log_message("%s\n", ldap_err2string(result)); } ldap_unbind_s(ldap); return; }
static void pong(toolbox_t *box) { apr_status_t rv; abts_case *tc = box->tc; rv = apr_thread_mutex_lock(box->mutex); ABTS_SUCCESS(rv); if (state == TOSS) state = PONG; do { rv = apr_thread_cond_signal(box->cond); ABTS_SUCCESS(rv); state = PING; rv = apr_thread_cond_wait(box->cond, box->mutex); ABTS_SUCCESS(rv); ABTS_TRUE(tc, state == PONG || state == OVER); } while (state != OVER); rv = apr_thread_mutex_unlock(box->mutex); ABTS_SUCCESS(rv); rv = apr_thread_cond_broadcast(box->cond); ABTS_SUCCESS(rv); }
static void sleep_one(abts_case *tc, void *data) { time_t pretime = time(NULL); time_t posttime; time_t timediff; apr_sleep(apr_time_from_sec(SLEEP_INTERVAL)); posttime = time(NULL); /* normalize the timediff. We should have slept for SLEEP_INTERVAL, so * we should just subtract that out. */ timediff = posttime - pretime - SLEEP_INTERVAL; ABTS_TRUE(tc, timediff >= 0); ABTS_TRUE(tc, timediff <= 1); }
static void test_mtime_set(abts_case *tc, void *data) { apr_file_t *thefile; apr_finfo_t finfo; apr_time_t epoch = 0; apr_status_t rv; /* This test sort of depends on the system clock being at least * marginally ccorrect; We'll be setting the modification time to * the epoch. */ rv = apr_file_open(&thefile, NEWFILENAME, APR_READ | APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BUFFERED | APR_DELONCLOSE, APR_OS_DEFAULT, p); APR_ASSERT_SUCCESS(tc, "open file", rv); /* Check that the current mtime is not the epoch */ rv = apr_stat(&finfo, NEWFILENAME, APR_FINFO_MTIME, p); if (rv == APR_INCOMPLETE) { char *str; int i; str = apr_pstrdup(p, "APR_INCOMPLETE: Missing "); for (i = 0; vfi[i].bits; ++i) { if (vfi[i].bits & ~finfo.valid) { str = apr_pstrcat(p, str, vfi[i].description, " ", NULL); } } ABTS_FAIL(tc, str); } APR_ASSERT_SUCCESS(tc, "get initial mtime", rv); ABTS_TRUE(tc, finfo.mtime != epoch); /* Reset the mtime to the epoch and verify the result. * Note: we blindly assume that if the first apr_stat succeeded, * the second one will, too. */ rv = apr_file_mtime_set(NEWFILENAME, epoch, p); APR_ASSERT_SUCCESS(tc, "set mtime", rv); rv = apr_stat(&finfo, NEWFILENAME, APR_FINFO_MTIME, p); APR_ASSERT_SUCCESS(tc, "get modified mtime", rv); ABTS_TRUE(tc, finfo.mtime == epoch); apr_file_close(thefile); }
static void create_filename(abts_case *tc, void *data) { char *oldfileptr; apr_filepath_get(&file1, 0, p); #ifndef NETWARE #ifdef WIN32 ABTS_TRUE(tc, file1[1] == ':'); #else ABTS_TRUE(tc, file1[0] == '/'); #endif #endif ABTS_TRUE(tc, file1[strlen(file1) - 1] != '/'); oldfileptr = file1; file1 = apr_pstrcat(p, file1,"/data/mmap_datafile.txt" ,NULL); ABTS_TRUE(tc, oldfileptr != file1); }
static void test_open_excl(abts_case *tc, void *data) { apr_status_t rv; apr_file_t *thefile = NULL; rv = apr_file_open(&thefile, FILENAME, APR_CREATE | APR_EXCL | APR_WRITE, APR_UREAD | APR_UWRITE | APR_GREAD, p); ABTS_TRUE(tc, rv != APR_SUCCESS); ABTS_INT_EQUAL(tc, 1, APR_STATUS_IS_EEXIST(rv)); ABTS_PTR_EQUAL(tc, NULL, thefile); }
static void test_open_noreadwrite(abts_case *tc, void *data) { apr_status_t rv; apr_file_t *thefile = NULL; rv = apr_file_open(&thefile, FILENAME, APR_FOPEN_CREATE | APR_FOPEN_EXCL, APR_FPROT_UREAD | APR_FPROT_UWRITE | APR_FPROT_GREAD, p); ABTS_TRUE(tc, rv != APR_SUCCESS); ABTS_INT_EQUAL(tc, 1, APR_STATUS_IS_EACCES(rv)); ABTS_PTR_EQUAL(tc, NULL, thefile); }
static void test_interesting_subnets(abts_case *tc, void *data) { struct { const char *ipstr, *mask; int family; char *in_subnet, *not_in_subnet; } testcases[] = { {"9.67", NULL, APR_INET, "9.67.113.15", "10.1.2.3"} ,{"9.67.0.0", "16", APR_INET, "9.67.113.15", "10.1.2.3"} ,{"9.67.0.0", "255.255.0.0", APR_INET, "9.67.113.15", "10.1.2.3"} ,{"9.67.113.99", "16", APR_INET, "9.67.113.15", "10.1.2.3"} ,{"9.67.113.99", "255.255.255.0", APR_INET, "9.67.113.15", "10.1.2.3"} #if APR_HAVE_IPV6 ,{"fe80::", "8", APR_INET6, "fe80::1", "ff01::1"} ,{"ff01::", "8", APR_INET6, "ff01::1", "fe80::1"} ,{"3FFE:8160::", "28", APR_INET6, "3ffE:816e:abcd:1234::1", "3ffe:8170::1"} ,{"127.0.0.1", NULL, APR_INET6, "::ffff:127.0.0.1", "fe80::1"} ,{"127.0.0.1", "8", APR_INET6, "::ffff:127.0.0.1", "fe80::1"} #endif }; apr_ipsubnet_t *ipsub; apr_sockaddr_t *sa; apr_status_t rv; int i, rc; for (i = 0; i < sizeof testcases / sizeof testcases[0]; i++) { rv = apr_ipsubnet_create(&ipsub, testcases[i].ipstr, testcases[i].mask, p); ABTS_TRUE(tc, rv == APR_SUCCESS); rv = apr_sockaddr_info_get(&sa, testcases[i].in_subnet, testcases[i].family, 0, 0, p); ABTS_TRUE(tc, rv == APR_SUCCESS); rc = apr_ipsubnet_test(ipsub, sa); ABTS_TRUE(tc, rc != 0); rv = apr_sockaddr_info_get(&sa, testcases[i].not_in_subnet, testcases[i].family, 0, 0, p); ABTS_TRUE(tc, rv == APR_SUCCESS); rc = apr_ipsubnet_test(ipsub, sa); ABTS_TRUE(tc, rc == 0); } }
static int add_ldap_certs(abts_case *tc) { apr_status_t status; apr_dir_t *thedir; apr_finfo_t dirent; apr_ldap_err_t *result = NULL; if ((status = apr_dir_open(&thedir, DIRNAME, p)) == APR_SUCCESS) { apr_ldap_opt_tls_cert_t *cert = (apr_ldap_opt_tls_cert_t *)apr_pcalloc(p, sizeof(apr_ldap_opt_tls_cert_t)); do { status = apr_dir_read(&dirent, APR_FINFO_MIN | APR_FINFO_NAME, thedir); if (APR_STATUS_IS_INCOMPLETE(status)) { continue; /* ignore un-stat()able files */ } else if (status != APR_SUCCESS) { break; } if (strstr(dirent.name, ".der")) { cert->type = APR_LDAP_CA_TYPE_DER; cert->path = apr_pstrcat (p, DIRNAME, "/", dirent.name, NULL); apr_ldap_set_option(p, NULL, APR_LDAP_OPT_TLS_CERT, (void *)cert, &result); ABTS_TRUE(tc, result->rc == LDAP_SUCCESS); } if (strstr(dirent.name, ".b64")) { cert->type = APR_LDAP_CA_TYPE_BASE64; cert->path = apr_pstrcat (p, DIRNAME, "/", dirent.name, NULL); apr_ldap_set_option(p, NULL, APR_LDAP_OPT_TLS_CERT, (void *)cert, &result); ABTS_TRUE(tc, result->rc == LDAP_SUCCESS); } } while (1); apr_dir_close(thedir); } return 0; }
static void test_ldap_tls(abts_case *tc, void *data) { apr_pool_t *pool = p; LDAP *ldap; apr_ldap_err_t *result = NULL; apr_ldap_init(pool, &ldap, ldap_host, LDAP_PORT, APR_LDAP_STARTTLS, &(result)); ABTS_TRUE(tc, ldap != NULL); ABTS_PTR_NOTNULL(tc, result); if (result->rc == LDAP_SUCCESS) { add_ldap_certs(tc); test_ldap_connection(tc, ldap); } }
static void string_error(abts_case *tc, void *data) { char buf[128], *rv; apr_status_t n; buf[0] = '\0'; rv = apr_strerror(APR_ENOENT, buf, sizeof buf); ABTS_PTR_EQUAL(tc, buf, rv); ABTS_TRUE(tc, strlen(buf) > 0); rv = apr_strerror(APR_TIMEUP, buf, sizeof buf); ABTS_PTR_EQUAL(tc, buf, rv); ABTS_STR_EQUAL(tc, "The timeout specified has expired", buf); /* throw some randomish numbers at it to check for robustness */ for (n = 1; n < 1000000; n *= 2) { apr_strerror(n, buf, sizeof buf); } }
static void snprintf_overflow(abts_case *tc, void *data) { char buf[4]; int rv; buf[2] = '4'; buf[3] = '2'; rv = apr_snprintf(buf, 2, "%s", "a"); ABTS_INT_EQUAL(tc, 1, rv); rv = apr_snprintf(buf, 2, "%s", "abcd"); ABTS_INT_EQUAL(tc, 1, rv); ABTS_STR_EQUAL(tc, "a", buf); /* Check the buffer really hasn't been overflowed. */ ABTS_TRUE(tc, buf[2] == '4' && buf[3] == '2'); }
static void test_ldap(abts_case *tc, void *data) { apr_pool_t *pool = p; LDAP *ldap; apr_ldap_err_t *result = NULL; ABTS_ASSERT(tc, "failed to get host", ldap_host[0] != '\0'); apr_ldap_init(pool, &ldap, ldap_host, LDAP_PORT, APR_LDAP_NONE, &(result)); ABTS_TRUE(tc, ldap != NULL); ABTS_PTR_NOTNULL(tc, result); if (result->rc == LDAP_SUCCESS) { test_ldap_connection(tc, ldap); } }
static void pipe_consumer(toolbox_t *box) { char ch; apr_status_t rv; apr_size_t nbytes; abts_case *tc = box->tc; apr_file_t *out = box->data; apr_uint32_t consumed = 0; do { rv = apr_thread_mutex_lock(box->mutex); ABTS_SUCCESS(rv); while (!pipe_count && !exiting) { rv = apr_thread_cond_wait(box->cond, box->mutex); ABTS_SUCCESS(rv); } if (!pipe_count && exiting) { rv = apr_thread_mutex_unlock(box->mutex); ABTS_SUCCESS(rv); break; } pipe_count--; consumed++; rv = apr_thread_mutex_unlock(box->mutex); ABTS_SUCCESS(rv); rv = apr_file_read_full(out, &ch, 1, &nbytes); ABTS_SUCCESS(rv); ABTS_SIZE_EQUAL(tc, 1, nbytes); ABTS_TRUE(tc, ch == '.'); } while (1); /* naive fairness test - it would be good to introduce or solidify * a solid test to ensure one thread is not starved. * ABTS_INT_EQUAL(tc, 1, !!consumed); */ }
static void test_named_remove(abts_case *tc, void *data) { apr_status_t rv; apr_shm_t *shm, *shm2; apr_shm_remove(SHARED_FILENAME, p); rv = apr_shm_create(&shm, SHARED_SIZE, SHARED_FILENAME, p); APR_ASSERT_SUCCESS(tc, "Error allocating shared memory block", rv); if (rv != APR_SUCCESS) { return; } ABTS_PTR_NOTNULL(tc, shm); rv = apr_shm_remove(SHARED_FILENAME, p); /* On platforms which acknowledge the removal of the shared resource, * ensure another of the same name may be created after removal; */ if (rv == APR_SUCCESS) { rv = apr_shm_create(&shm2, SHARED_SIZE, SHARED_FILENAME, p); APR_ASSERT_SUCCESS(tc, "Error allocating shared memory block", rv); if (rv != APR_SUCCESS) { return; } ABTS_PTR_NOTNULL(tc, shm2); rv = apr_shm_destroy(shm2); APR_ASSERT_SUCCESS(tc, "Error destroying shared memory block", rv); } rv = apr_shm_destroy(shm); APR_ASSERT_SUCCESS(tc, "Error destroying shared memory block", rv); /* Now ensure no named resource remains which we may attach to */ rv = apr_shm_attach(&shm, SHARED_FILENAME, p); ABTS_TRUE(tc, rv != 0); }
static void test_rmm(abts_case *tc, void *data) { apr_status_t rv; apr_pool_t *pool; apr_shm_t *shm; apr_rmm_t *rmm; apr_size_t size, fragsize; apr_rmm_off_t *off, off2; int i; void *entity; rv = apr_pool_create(&pool, p); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); /* We're going to want 10 blocks of data from our target rmm. */ size = SHARED_SIZE + apr_rmm_overhead_get(FRAG_COUNT + 1); rv = apr_shm_create(&shm, size, NULL, pool); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); if (rv != APR_SUCCESS) return; rv = apr_rmm_init(&rmm, NULL, apr_shm_baseaddr_get(shm), size, pool); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); if (rv != APR_SUCCESS) return; /* Creating each fragment of size fragsize */ fragsize = SHARED_SIZE / FRAG_COUNT; off = apr_palloc(pool, FRAG_COUNT * sizeof(apr_rmm_off_t)); for (i = 0; i < FRAG_COUNT; i++) { off[i] = apr_rmm_malloc(rmm, fragsize); } /* Checking for out of memory allocation */ off2 = apr_rmm_malloc(rmm, FRAG_SIZE * FRAG_COUNT); ABTS_TRUE(tc, !off2); /* Checking each fragment for address alignment */ for (i = 0; i < FRAG_COUNT; i++) { char *c = apr_rmm_addr_get(rmm, off[i]); apr_size_t sc = (apr_size_t)c; ABTS_TRUE(tc, !!off[i]); ABTS_TRUE(tc, !(sc & 7)); } /* Setting each fragment to a unique value */ for (i = 0; i < FRAG_COUNT; i++) { int j; char **c = apr_rmm_addr_get(rmm, off[i]); for (j = 0; j < FRAG_SIZE; j++, c++) { *c = apr_itoa(pool, i + j); } } /* Checking each fragment for its unique value */ for (i = 0; i < FRAG_COUNT; i++) { int j; char **c = apr_rmm_addr_get(rmm, off[i]); for (j = 0; j < FRAG_SIZE; j++, c++) { char *d = apr_itoa(pool, i + j); ABTS_STR_EQUAL(tc, d, *c); } } /* Freeing each fragment */ for (i = 0; i < FRAG_COUNT; i++) { rv = apr_rmm_free(rmm, off[i]); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); } /* Creating one large segment */ off[0] = apr_rmm_calloc(rmm, SHARED_SIZE); /* Setting large segment */ for (i = 0; i < FRAG_COUNT * FRAG_SIZE; i++) { char **c = apr_rmm_addr_get(rmm, off[0]); c[i] = apr_itoa(pool, i); } /* Freeing large segment */ rv = apr_rmm_free(rmm, off[0]); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); /* Creating each fragment of size fragsize */ for (i = 0; i < FRAG_COUNT; i++) { off[i] = apr_rmm_malloc(rmm, fragsize); } /* Freeing each fragment backwards */ for (i = FRAG_COUNT - 1; i >= 0; i--) { rv = apr_rmm_free(rmm, off[i]); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); } /* Creating one large segment (again) */ off[0] = apr_rmm_calloc(rmm, SHARED_SIZE); /* Freeing large segment */ rv = apr_rmm_free(rmm, off[0]); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); /* Checking realloc */ off[0] = apr_rmm_calloc(rmm, SHARED_SIZE - 100); off[1] = apr_rmm_calloc(rmm, 100); ABTS_TRUE(tc, !!off[0]); ABTS_TRUE(tc, !!off[1]); entity = apr_rmm_addr_get(rmm, off[1]); rv = apr_rmm_free(rmm, off[0]); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); { unsigned char *c = entity; /* Fill in the region; the first half with zereos, which will * likely catch the apr_rmm_realloc offset calculation bug by * making it think the old region was zero length. */ for (i = 0; i < 100; i++) { c[i] = (i < 50) ? 0 : i; } } /* now we can realloc off[1] and get many more bytes */ off[0] = apr_rmm_realloc(rmm, entity, SHARED_SIZE - 100); ABTS_TRUE(tc, !!off[0]); { unsigned char *c = apr_rmm_addr_get(rmm, off[0]); /* fill in the region */ for (i = 0; i < 100; i++) { ABTS_TRUE(tc, c[i] == (i < 50 ? 0 : i)); } } rv = apr_rmm_destroy(rmm); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); rv = apr_shm_destroy(shm); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); apr_pool_destroy(pool); }