diff --git a/src/util/fdlist.c b/src/util/fdlist.c index 5b80ce9d..83381025 100644 --- a/src/util/fdlist.c +++ b/src/util/fdlist.c @@ -64,6 +64,47 @@ int fdlist_new_consume_fds(FDList **listp, const int *fds, size_t n_fds) { return r; } +/** + * fdlist_new_dup_fds() - create fdlist and duplicate FDs + * @listp: output for new fdlist + * @fds: FD array to duplicate + * @n_fds: array size of @fds + * + * This is the same as fdlist_new_with_fds() but duplicates the FDs. That is, + * the caller retains the FDs, and an independent copy of each FD will be owned + * by the new object. + * + * Return: 0 on success, negative error code on failure. + */ +int fdlist_new_dup_fds(FDList **listp, const int *fds, size_t n_fds) { + _c_cleanup_(fdlist_freep) FDList *list = NULL; + size_t i; + int r, *p; + + r = fdlist_new_with_fds(&list, fds, n_fds); + if (r) + return error_trace(r); + + p = fdlist_data(list); + for (i = 0; i < n_fds; ++i) { + p[i] = fcntl(p[i], F_DUPFD_CLOEXEC, 3); + if (p[i] < 0) { + r = -errno; + while (i > 0) { + --i; + p[i] = c_close(p[i]); + } + return error_origin(r); + } + } + + list->consumed = true; + + *listp = list; + list = NULL; + return 0; +} + /** * fdlist_free() - free fdlist * @list: fdlist to operate on, or NULL diff --git a/src/util/fdlist.h b/src/util/fdlist.h index 233ffc08..e42cdc2c 100644 --- a/src/util/fdlist.h +++ b/src/util/fdlist.h @@ -17,6 +17,7 @@ struct FDList { int fdlist_new_with_fds(FDList **listp, const int *fds, size_t n_fds); int fdlist_new_consume_fds(FDList **listp, const int *fds, size_t n_fds); +int fdlist_new_dup_fds(FDList **listp, const int *fds, size_t n_fds); FDList *fdlist_free(FDList *list); void fdlist_truncate(FDList *list, size_t n_fds); int fdlist_steal(FDList *list, size_t index); diff --git a/src/util/test-fdlist.c b/src/util/test-fdlist.c index 5e71df93..9785a550 100644 --- a/src/util/test-fdlist.c +++ b/src/util/test-fdlist.c @@ -85,9 +85,56 @@ static void test_consumer(void) { l = fdlist_free(l); } +static void test_dup(void) { + int r, prev, p[2], expected[2]; + FDList *l; + + /* + * Verify that the `dup' feature of FDList objects actually duplicates + * the passed FDs and closes them on destruction. We use epoll-fds as + * examples here, though any FD would work. + * + * We use the fact that FD spaces are sparse to verify that a given FD + * was actually properly closed. + */ + + prev = epoll_create1(EPOLL_CLOEXEC); + c_assert(prev >= 0); + + p[0] = epoll_create1(EPOLL_CLOEXEC); + c_assert(p[0] >= 0); + c_assert(p[0] == prev + 1); + + p[1] = epoll_create1(EPOLL_CLOEXEC); + c_assert(p[1] >= 0); + c_assert(p[1] == prev + 2); + + r = fdlist_new_dup_fds(&l, p, C_ARRAY_SIZE(p)); + c_assert(!r); + + expected[0] = prev + 3; + expected[1] = prev + 4; + + c_assert(fdlist_count(l) == C_ARRAY_SIZE(p)); + c_assert(!memcmp(fdlist_data(l), expected, sizeof(expected))); + + fdlist_truncate(l, 0); + + r = epoll_create1(EPOLL_CLOEXEC); + c_assert(r == prev + 3); + close(r); + + close(p[1]); + close(p[0]); + close(prev); + + l = fdlist_free(l); +} + int main(int argc, char **argv) { test_setup(); test_dummy(); test_consumer(); + test_dup(); return 0; }