From cc7c8542a52d11cc9b94882dc9d9dffef6b3fa3e Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Thu, 14 Jun 2012 07:30:08 +0200 Subject: [PATCH] unix, windows: add stat() based file watcher Monitors a file path for changes. Supersedes ev_stat. --- config-mingw.mk | 4 +- config-unix.mk | 4 +- include/uv-private/uv-unix.h | 3 + include/uv-private/uv-win.h | 3 + include/uv.h | 59 ++++++++-- src/fs-poll.c | 221 +++++++++++++++++++++++++++++++++++ src/unix/core.c | 13 ++- src/uv-common.h | 2 + src/win/handle-inl.h | 4 + src/win/handle.c | 6 + src/win/internal.h | 6 + test/test-fs-poll.c | 127 ++++++++++++++++++++ test/test-list.h | 4 + test/test-ref.c | 10 ++ uv.gyp | 2 + 15 files changed, 448 insertions(+), 20 deletions(-) create mode 100644 src/fs-poll.c create mode 100644 test/test-fs-poll.c diff --git a/config-mingw.mk b/config-mingw.mk index 89bf2df3..9e49ec71 100644 --- a/config-mingw.mk +++ b/config-mingw.mk @@ -37,8 +37,8 @@ RUNNER_LINKFLAGS=$(LINKFLAGS) RUNNER_LIBS=-lws2_32 -lpsapi -liphlpapi RUNNER_SRC=test/runner-win.c -uv.a: $(WIN_OBJS) src/cares.o src/uv-common.o $(CARES_OBJS) - $(AR) rcs uv.a $(WIN_OBJS) src/cares.o src/uv-common.o $(CARES_OBJS) +uv.a: $(WIN_OBJS) src/cares.o src/fs-poll.o src/uv-common.o $(CARES_OBJS) + $(AR) rcs uv.a $^ src/%.o: src/%.c include/uv.h include/uv-private/uv-win.h $(CC) $(CFLAGS) -c $< -o $@ diff --git a/config-unix.mk b/config-unix.mk index c1ba2d0a..7220f108 100644 --- a/config-unix.mk +++ b/config-unix.mk @@ -129,8 +129,8 @@ endif RUNNER_LIBS= RUNNER_SRC=test/runner-unix.c -uv.a: $(OBJS) src/cares.o src/uv-common.o src/unix/ev/ev.o src/unix/uv-eio.o src/unix/eio/eio.o $(CARES_OBJS) - $(AR) rcs uv.a $(OBJS) src/cares.o src/uv-common.o src/unix/uv-eio.o src/unix/ev/ev.o src/unix/eio/eio.o $(CARES_OBJS) +uv.a: $(OBJS) src/cares.o src/fs-poll.o src/uv-common.o src/unix/ev/ev.o src/unix/uv-eio.o src/unix/eio/eio.o $(CARES_OBJS) + $(AR) rcs uv.a $^ src/%.o: src/%.c include/uv.h include/uv-private/uv-unix.h $(CC) $(CSTDFLAG) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ diff --git a/include/uv-private/uv-unix.h b/include/uv-private/uv-unix.h index 62bb0aa4..f79deed8 100644 --- a/include/uv-private/uv-unix.h +++ b/include/uv-private/uv-unix.h @@ -249,6 +249,9 @@ struct uv__io_s { struct stat statbuf; \ eio_req* eio; +#define UV_FS_POLL_PRIVATE_FIELDS \ + struct stat statbuf; + #define UV_WORK_PRIVATE_FIELDS \ eio_req* eio; diff --git a/include/uv-private/uv-win.h b/include/uv-private/uv-win.h index 10a8ec0c..d43f66ae 100644 --- a/include/uv-private/uv-win.h +++ b/include/uv-private/uv-win.h @@ -487,6 +487,9 @@ RB_HEAD(uv_timer_tree_s, uv_timer_s); }; \ }; +#define UV_FS_POLL_PRIVATE_FIELDS \ + struct _stati64 statbuf; + #define UV_WORK_PRIVATE_FIELDS \ #define UV_FS_EVENT_PRIVATE_FIELDS \ diff --git a/include/uv.h b/include/uv.h index 8bd387ac..03de4475 100644 --- a/include/uv.h +++ b/include/uv.h @@ -140,6 +140,7 @@ typedef enum { XX(ASYNC, async) \ XX(CHECK, check) \ XX(FS_EVENT, fs_event) \ + XX(FS_POLL, fs_poll) \ XX(IDLE, idle) \ XX(NAMED_PIPE, pipe) \ XX(POLL, poll) \ @@ -209,6 +210,7 @@ typedef struct uv_udp_send_s uv_udp_send_t; typedef struct uv_fs_s uv_fs_t; /* uv_fs_event_t is a subclass of uv_handle_t. */ typedef struct uv_fs_event_s uv_fs_event_t; +typedef struct uv_fs_poll_s uv_fs_poll_t; typedef struct uv_work_s uv_work_t; @@ -295,6 +297,7 @@ typedef void (*uv_async_cb)(uv_async_t* handle, int status); typedef void (*uv_prepare_cb)(uv_prepare_t* handle, int status); typedef void (*uv_check_cb)(uv_check_t* handle, int status); typedef void (*uv_idle_cb)(uv_idle_t* handle, int status); +typedef void (*uv_fs_poll_cb)(uv_fs_poll_t* handle, int status); typedef void (*uv_getaddrinfo_cb)(uv_getaddrinfo_t* handle, int status, struct addrinfo* res); typedef void (*uv_exit_cb)(uv_process_t*, int exit_status, int term_signal); @@ -1510,6 +1513,41 @@ struct uv_fs_event_s { }; +/* + * uv_fs_stat() based polling file watcher. + */ +struct uv_fs_poll_s { + UV_HANDLE_FIELDS + /* Private, don't touch. */ + int busy_polling; /* TODO(bnoordhuis) Fold into flags field. */ + unsigned int interval; + uint64_t start_time; + char* path; + uv_fs_poll_cb poll_cb; + uv_timer_t timer_handle; + uv_fs_t* fs_req; + UV_FS_POLL_PRIVATE_FIELDS +}; + +UV_EXTERN int uv_fs_poll_init(uv_loop_t* loop, uv_fs_poll_t* handle); + +/* + * Check the file at `path` for changes every `interval` milliseconds. + * + * Your callback gets invoked repeatedly with `status == -1` if `path` + * does not exist or is inaccessible. The watcher is *not* stopped. This + * lets you monitor a path until the resource becomes available (again). + * + * For maximum portability, use multi-second intervals. Sub-second intervals + * will not detect all changes on many file systems. + */ +UV_EXTERN int uv_fs_poll_start(uv_fs_poll_t* handle, + uv_fs_poll_cb poll_cb, + const char* path, + unsigned int interval); + +UV_EXTERN int uv_fs_poll_stop(uv_fs_poll_t* handle); + /* * Gets load avg * See: http://en.wikipedia.org/wiki/Load_(computing) @@ -1683,22 +1721,23 @@ union uv_any_req { struct uv_counters_s { + uint64_t async_init; + uint64_t check_init; uint64_t eio_init; - uint64_t req_init; + uint64_t fs_event_init; + uint64_t fs_poll_init; uint64_t handle_init; - uint64_t stream_init; - uint64_t tcp_init; - uint64_t udp_init; + uint64_t idle_init; uint64_t pipe_init; - uint64_t tty_init; uint64_t poll_init; uint64_t prepare_init; - uint64_t check_init; - uint64_t idle_init; - uint64_t async_init; - uint64_t timer_init; uint64_t process_init; - uint64_t fs_event_init; + uint64_t req_init; + uint64_t stream_init; + uint64_t tcp_init; + uint64_t timer_init; + uint64_t tty_init; + uint64_t udp_init; }; diff --git a/src/fs-poll.c b/src/fs-poll.c new file mode 100644 index 00000000..318d1679 --- /dev/null +++ b/src/fs-poll.c @@ -0,0 +1,221 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "uv.h" +#include "uv-common.h" + +#include +#include +#include + +#ifdef _WIN32 +typedef struct _stati64 uv__statbuf_t; +#else +typedef struct stat uv__statbuf_t; +#endif + +static int statbuf_eq(const uv__statbuf_t* a, const uv__statbuf_t* b); +static void timer_cb(uv_timer_t* timer, int status); +static void poll_cb(uv_fs_t* req); + + +int uv_fs_poll_init(uv_loop_t* loop, uv_fs_poll_t* handle) { + /* TODO(bnoordhuis) Mark fs_req internal. */ + uv__handle_init(loop, (uv_handle_t*)handle, UV_FS_POLL); + loop->counters.fs_poll_init++; + + if (uv_timer_init(loop, &handle->timer_handle)) + return -1; + + handle->timer_handle.flags |= UV__HANDLE_INTERNAL; + uv__handle_unref(&handle->timer_handle); + + return 0; +} + + +int uv_fs_poll_start(uv_fs_poll_t* handle, + uv_fs_poll_cb cb, + const char* path, + unsigned int interval) { + uv_fs_t* req; + size_t len; + + if (uv__is_active(handle)) + return 0; + + len = strlen(path) + 1; + req = malloc(sizeof(*req) + len); + + if (req == NULL) + return uv__set_artificial_error(handle->loop, UV_ENOMEM); + + req->data = handle; + handle->path = memcpy(req + 1, path, len); + handle->fs_req = req; + handle->poll_cb = cb; + handle->interval = interval ? interval : 1; + handle->start_time = uv_now(handle->loop); + handle->busy_polling = 0; + + if (uv_fs_stat(handle->loop, handle->fs_req, handle->path, poll_cb)) + abort(); + + uv__handle_start(handle); + + return 0; +} + + +int uv_fs_poll_stop(uv_fs_poll_t* handle) { + if (!uv__is_active(handle)) + return 0; + + /* Don't free the fs req if it's active. Signal poll_cb that it needs to free + * the req by removing the handle backlink. + * + * TODO(bnoordhuis) Have uv-unix postpone the close callback until the req + * finishes so we don't need this pointer / lifecycle hackery. The callback + * always runs on the next tick now. + */ + if (handle->fs_req->data) + handle->fs_req->data = NULL; + else + free(handle->fs_req); + + handle->fs_req = NULL; + handle->path = NULL; + + uv_timer_stop(&handle->timer_handle); + uv__handle_stop(handle); + + return 0; +} + + +void uv__fs_poll_close(uv_fs_poll_t* handle) { + uv_fs_poll_stop(handle); + uv_close((uv_handle_t*)&handle->timer_handle, NULL); +} + + +static void timer_cb(uv_timer_t* timer, int status) { + uv_fs_poll_t* handle; + + handle = container_of(timer, uv_fs_poll_t, timer_handle); + handle->start_time = uv_now(handle->loop); + handle->fs_req->data = handle; + + if (uv_fs_stat(handle->loop, handle->fs_req, handle->path, poll_cb)) + abort(); + + assert(uv__is_active(handle)); +} + + +static void poll_cb(uv_fs_t* req) { + uv__statbuf_t* statbuf; + uv_fs_poll_t* handle; + uint64_t interval; + + handle = req->data; + + if (handle == NULL) /* Handle has been stopped or closed. */ + goto out; + + assert(req == handle->fs_req); + + if (req->result != 0) { + /* TODO(bnoordhuis) Only signal the error the first time? What if the + * error reason changes? + */ + uv__set_artificial_error(handle->loop, req->errorno); + handle->poll_cb(handle, -1); + handle->busy_polling = -1; + goto out; + } + + statbuf = req->ptr; + + if (handle->busy_polling == 0) { + handle->statbuf = *statbuf; + handle->busy_polling = 1; + } + else if (handle->busy_polling == -1) { + handle->statbuf = *statbuf; + handle->busy_polling = 1; + handle->poll_cb(handle, 0); /* Error went away. */ + } + else if (!statbuf_eq(statbuf, &handle->statbuf)) { + handle->statbuf = *statbuf; + handle->poll_cb(handle, 0); + } + +out: + uv_fs_req_cleanup(req); + + if (req->data == NULL) { /* Handle has been stopped or closed. */ + free(req); + return; + } + + req->data = NULL; /* Tell uv_fs_poll_stop() it's safe to free the req. */ + + /* Reschedule timer, subtract the delay from doing the stat(). */ + interval = handle->interval; + interval -= (uv_now(handle->loop) - handle->start_time) % interval; + + if (uv_timer_start(&handle->timer_handle, timer_cb, interval, 0)) + abort(); +} + + +static int statbuf_eq(const uv__statbuf_t* a, const uv__statbuf_t* b) { +#ifdef _WIN32 + return a->st_mtime == b->st_mtime + && a->st_size == b->st_size + && a->st_mode == b->st_mode; +#else + return a->st_ctime == b->st_ctime + && a->st_mtime == b->st_mtime + && a->st_size == b->st_size + && a->st_mode == b->st_mode + && a->st_uid == b->st_uid + && a->st_gid == b->st_gid + && a->st_ino == b->st_ino + && a->st_dev == b->st_dev; +#endif +} + + +#ifdef _WIN32 + +#include "win/internal.h" +#include "win/handle-inl.h" + +void uv__fs_poll_endgame(uv_loop_t* loop, uv_fs_poll_t* handle) { + assert(handle->flags & UV_HANDLE_CLOSING); + assert(!(handle->flags & UV_HANDLE_CLOSED)); + uv__handle_stop(handle); + uv__handle_close(handle); +} + +#endif /* _WIN32 */ diff --git a/src/unix/core.c b/src/unix/core.c index 524ffd0c..318eb718 100644 --- a/src/unix/core.c +++ b/src/unix/core.c @@ -109,6 +109,10 @@ void uv_close(uv_handle_t* handle, uv_close_cb close_cb) { uv__poll_close((uv_poll_t*)handle); break; + case UV_FS_POLL: + uv__fs_poll_close((uv_fs_poll_t*)handle); + break; + default: assert(0); } @@ -133,6 +137,9 @@ static void uv__finish_close(uv_handle_t* handle) { case UV_ASYNC: case UV_TIMER: case UV_PROCESS: + case UV_FS_EVENT: + case UV_FS_POLL: + case UV_POLL: break; case UV_NAMED_PIPE: @@ -148,12 +155,6 @@ static void uv__finish_close(uv_handle_t* handle) { uv__udp_finish_close((uv_udp_t*)handle); break; - case UV_FS_EVENT: - break; - - case UV_POLL: - break; - default: assert(0); break; diff --git a/src/uv-common.h b/src/uv-common.h index 4c316ec1..d159727d 100644 --- a/src/uv-common.h +++ b/src/uv-common.h @@ -86,6 +86,8 @@ int uv__tcp_connect6(uv_connect_t* req, struct sockaddr_in6 address, uv_connect_cb cb); +void uv__fs_poll_close(uv_fs_poll_t* handle); + UNUSED static int uv__has_active_reqs(const uv_loop_t* loop) { return !ngx_queue_empty(&loop->active_reqs); diff --git a/src/win/handle-inl.h b/src/win/handle-inl.h index ea2d9e0b..4064c24c 100644 --- a/src/win/handle-inl.h +++ b/src/win/handle-inl.h @@ -131,6 +131,10 @@ INLINE static void uv_process_endgames(uv_loop_t* loop) { uv_fs_event_endgame(loop, (uv_fs_event_t*) handle); break; + case UV_FS_POLL: + uv__fs_poll_endgame(loop, (uv_fs_poll_t*) handle); + break; + default: assert(0); break; diff --git a/src/win/handle.c b/src/win/handle.c index 9343d256..d4b86de9 100644 --- a/src/win/handle.c +++ b/src/win/handle.c @@ -132,6 +132,12 @@ void uv_close(uv_handle_t* handle, uv_close_cb cb) { uv_fs_event_close(loop, (uv_fs_event_t*) handle); return; + case UV_FS_POLL: + uv__fs_poll_close((uv_fs_poll_t*) handle); + uv__handle_start(handle); + uv_want_endgame(loop, handle); + return; + default: /* Not supported */ abort(); diff --git a/src/win/internal.h b/src/win/internal.h index 30d9e8c5..aaae66e7 100644 --- a/src/win/internal.h +++ b/src/win/internal.h @@ -268,6 +268,12 @@ void uv_fs_event_close(uv_loop_t* loop, uv_fs_event_t* handle); void uv_fs_event_endgame(uv_loop_t* loop, uv_fs_event_t* handle); +/* + * Stat poller. + */ +void uv__fs_poll_endgame(uv_loop_t* loop, uv_fs_poll_t* handle); + + /* * Utilities. */ diff --git a/test/test-fs-poll.c b/test/test-fs-poll.c new file mode 100644 index 00000000..4f5703ea --- /dev/null +++ b/test/test-fs-poll.c @@ -0,0 +1,127 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "uv.h" +#include "task.h" + +#include +#include + +#define FIXTURE "testfile" + +static void poll_cb(uv_fs_poll_t* handle, int status); +static void timer_cb(uv_timer_t* handle, int status); +static void close_cb(uv_handle_t* handle); + +static uv_fs_poll_t poll_handle; +static uv_timer_t timer_handle; +static uv_loop_t* loop; + +static int poll_cb_called; +static int timer_cb_called; +static int close_cb_called; + + +static void touch_file(const char* path) { + static int count; + FILE* fp; + int i; + + ASSERT((fp = fopen(FIXTURE, "w+"))); + + /* Need to change the file size because the poller may not pick up + * sub-second mtime changes. + */ + i = ++count; + + while (i--) + fputc('*', fp); + + fclose(fp); +} + + +static void close_cb(uv_handle_t* handle) { + close_cb_called++; +} + + +static void timer_cb(uv_timer_t* handle, int status) { + touch_file(FIXTURE); + timer_cb_called++; +} + + +static void poll_cb(uv_fs_poll_t* handle, int status) { + ASSERT(handle == &poll_handle); + ASSERT(uv_is_active((uv_handle_t*)handle)); + + switch (poll_cb_called++) { + case 0: + ASSERT(status == -1); + ASSERT(uv_last_error(loop).code == UV_ENOENT); + touch_file(FIXTURE); + break; + + case 1: + ASSERT(status == 0); + ASSERT(0 == uv_timer_start(&timer_handle, timer_cb, 20, 0)); + break; + + case 2: + ASSERT(status == 0); + ASSERT(0 == uv_timer_start(&timer_handle, timer_cb, 200, 0)); + break; + + case 3: + ASSERT(status == 0); + remove(FIXTURE); + break; + + case 4: + ASSERT(status == -1); + ASSERT(uv_last_error(loop).code == UV_ENOENT); + uv_close((uv_handle_t*)handle, close_cb); + break; + + default: + ASSERT(0); + } +} + + +TEST_IMPL(fs_poll) { + loop = uv_default_loop(); + + remove(FIXTURE); + + ASSERT(0 == uv_timer_init(loop, &timer_handle)); + ASSERT(0 == uv_fs_poll_init(loop, &poll_handle)); + ASSERT(0 == uv_fs_poll_start(&poll_handle, poll_cb, FIXTURE, 100)); + ASSERT(0 == uv_run(loop)); + + ASSERT(poll_cb_called == 5); + ASSERT(timer_cb_called == 2); + ASSERT(close_cb_called == 1); + uv_loop_delete(loop); + + return 0; +} diff --git a/test/test-list.h b/test/test-list.h index 9af246dd..cf9a02ed 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -97,6 +97,7 @@ TEST_DECLARE (unref_in_prepare_cb) TEST_DECLARE (timer_ref) TEST_DECLARE (timer_ref2) TEST_DECLARE (fs_event_ref) +TEST_DECLARE (fs_poll_ref) TEST_DECLARE (tcp_ref) TEST_DECLARE (tcp_ref2) TEST_DECLARE (tcp_ref3) @@ -134,6 +135,7 @@ TEST_DECLARE (spawn_and_ping) TEST_DECLARE (spawn_setuid_fails) TEST_DECLARE (spawn_setgid_fails) TEST_DECLARE (spawn_stdout_to_file) +TEST_DECLARE (fs_poll) TEST_DECLARE (kill) TEST_DECLARE (fs_file_noent) TEST_DECLARE (fs_file_nametoolong) @@ -293,6 +295,7 @@ TASK_LIST_START TEST_ENTRY (ref) TEST_ENTRY (idle_ref) + TEST_ENTRY (fs_poll_ref) TEST_ENTRY (async_ref) TEST_ENTRY (prepare_ref) TEST_ENTRY (check_ref) @@ -360,6 +363,7 @@ TASK_LIST_START TEST_ENTRY (spawn_setuid_fails) TEST_ENTRY (spawn_setgid_fails) TEST_ENTRY (spawn_stdout_to_file) + TEST_ENTRY (fs_poll) TEST_ENTRY (kill) #ifdef _WIN32 TEST_ENTRY (spawn_detect_pipe_name_collisions_on_windows) diff --git a/test/test-ref.c b/test/test-ref.c index cc59aa7b..e0c38858 100644 --- a/test/test-ref.c +++ b/test/test-ref.c @@ -168,6 +168,16 @@ TEST_IMPL(fs_event_ref) { } +TEST_IMPL(fs_poll_ref) { + uv_fs_poll_t h; + uv_fs_poll_init(uv_default_loop(), &h); + uv_fs_poll_start(&h, NULL, ".", 999); + uv_unref((uv_handle_t*)&h); + uv_run(uv_default_loop()); + return 0; +} + + TEST_IMPL(tcp_ref) { uv_tcp_t h; uv_tcp_init(uv_default_loop(), &h); diff --git a/uv.gyp b/uv.gyp index 0a519b79..63f3ec0e 100644 --- a/uv.gyp +++ b/uv.gyp @@ -50,6 +50,7 @@ 'include/uv-private/ngx-queue.h', 'include/uv-private/tree.h', 'src/cares.c', + 'src/fs-poll.c', 'src/uv-common.c', 'src/uv-common.h', 'src/ares/ares_cancel.c', @@ -339,6 +340,7 @@ 'test/test-shutdown-close.c', 'test/test-shutdown-eof.c', 'test/test-spawn.c', + 'test/test-fs-poll.c', 'test/test-stdio-over-pipes.c', 'test/test-tcp-bind-error.c', 'test/test-tcp-bind6-error.c',