>From 0cafbaeecfde40e44bc5b68fa1847f3cf47cb6d2 Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Sat, 14 May 2016 10:30:50 +0200 Subject: [PATCH] lib: Add tfd This is a small daemon that can connect processes with stream sockets and provide unique IDs. Signed-off-by: Volker Lendecke --- lib/tfd/fddrn.c | 92 +++++++ lib/tfd/fdsrc.c | 118 +++++++++ lib/tfd/sockclnt.c | 115 ++++++++ lib/tfd/socksrv.c | 106 ++++++++ lib/tfd/tfd.c | 410 +++++++++++++++++++++++++++++ lib/tfd/tfd.h | 104 ++++++++ lib/tfd/tfdd.c | 710 ++++++++++++++++++++++++++++++++++++++++++++++++++ lib/tfd/wscript_build | 25 ++ wscript_build | 1 + 9 files changed, 1681 insertions(+) create mode 100644 lib/tfd/fddrn.c create mode 100644 lib/tfd/fdsrc.c create mode 100644 lib/tfd/sockclnt.c create mode 100644 lib/tfd/socksrv.c create mode 100644 lib/tfd/tfd.c create mode 100644 lib/tfd/tfd.h create mode 100644 lib/tfd/tfdd.c create mode 100644 lib/tfd/wscript_build diff --git a/lib/tfd/fddrn.c b/lib/tfd/fddrn.c new file mode 100644 index 0000000..e283552 --- /dev/null +++ b/lib/tfd/fddrn.c @@ -0,0 +1,92 @@ +/* + * Unix SMB/CIFS implementation. + * Copyright (C) Volker Lendecke 2016 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "replace.h" +#include "tfd.h" +#include "poll_funcs/poll_funcs_tevent.h" +#include +#include +#include +#include +#include + +static void recv_fd_cb(uint64_t src, int fd, void *private_data); + +int main(int argc, const char *argv[]) +{ + struct tevent_context *ev; + struct poll_funcs *tevent_poll_funcs; + void *poll_funcs_handle; + int ret; + bool done = false; + + if (argc != 3) { + fprintf(stderr, "Usage: %s \n", + argv[0]); + exit(1); + } + + ev = tevent_context_init(NULL); + if (ev == NULL) { + fprintf(stderr, "tevent_context_init failed\n"); + exit(1); + } + + tevent_poll_funcs = poll_funcs_init_tevent(ev); + if (tevent_poll_funcs == NULL) { + fprintf(stderr, "poll_funcs_init_tevent failed\n"); + exit(1); + } + + poll_funcs_handle = poll_funcs_tevent_register( + ev, tevent_poll_funcs, ev); + if (poll_funcs_handle == NULL) { + fprintf(stderr, "poll_funcs_tevent_register failed\n"); + exit(1); + } + + ret = tfd_init(argv[1], argv[2], tevent_poll_funcs, NULL, 0, + recv_fd_cb, &done, NULL); + if (ret != 0) { + fprintf(stderr, "tfd_init failed: %s\n", strerror(ret)); + exit(1); + } + + while (!done) { + ret = tevent_loop_once(ev); + if (ret != 0) { + fprintf(stderr, "tevent_loop_once failed: %s\n", + strerror(errno)); + exit(1); + } + } + + return 0; +} + +static void recv_fd_cb(uint64_t src, int fd, void *private_data) +{ + bool *done = private_data; + + if (fd == -1) { + *done = true; + return; + } + + close(fd); +} diff --git a/lib/tfd/fdsrc.c b/lib/tfd/fdsrc.c new file mode 100644 index 0000000..776751f --- /dev/null +++ b/lib/tfd/fdsrc.c @@ -0,0 +1,118 @@ +/* + * Unix SMB/CIFS implementation. + * Copyright (C) Volker Lendecke 2016 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "replace.h" +#include "tfd.h" +#include "poll_funcs/poll_funcs_tevent.h" +#include +#include +#include +#include + +static void recv_fd_cb(uint64_t src, int fd, void *private_data); + +int main(int argc, const char *argv[]) +{ + struct tevent_context *ev; + struct poll_funcs *tevent_poll_funcs; + void *poll_funcs_handle; + int ret; + uint64_t dst; + bool done = false; + uint64_t unique; + struct timeval tp1; + unsigned count = 0; + + if (argc != 4) { + fprintf(stderr, "Usage: %s dst\n", + argv[0]); + exit(1); + } + + dst = atoi(argv[3]); + + ev = tevent_context_init(NULL); + if (ev == NULL) { + fprintf(stderr, "tevent_context_init failed\n"); + exit(1); + } + + tevent_poll_funcs = poll_funcs_init_tevent(ev); + if (tevent_poll_funcs == NULL) { + fprintf(stderr, "poll_funcs_init_tevent failed\n"); + exit(1); + } + + poll_funcs_handle = poll_funcs_tevent_register( + ev, tevent_poll_funcs, ev); + if (poll_funcs_handle == NULL) { + fprintf(stderr, "poll_funcs_tevent_register failed\n"); + exit(1); + } + + ret = tfd_init(argv[1], argv[2], tevent_poll_funcs, NULL, 0, + recv_fd_cb, &done, &unique); + if (ret != 0) { + fprintf(stderr, "tfd_init failed: %s\n", strerror(ret)); + exit(1); + } + + printf("I'm unique id %"PRIu64"\n", unique); + + gettimeofday(&tp1, NULL); + + while (true) { + struct timeval tp2, diff; + int sock; + + ret = tfd_connect(dst, &sock, &unique); + if (ret != 0) { + fprintf(stderr, "tfd_connect failed: %s\n", + strerror(ret)); + exit(1); + } + close(sock); + + gettimeofday(&tp2, NULL); + diff = tevent_timeval_until(&tp1, &tp2); + if (diff.tv_sec != 0) { + printf("%8u fds/sec\r", count); + fflush(stdout); + tp1 = tp2; + count = 0; + } + + count += 1; + } + + printf("Sent to unique id %"PRIu64"\n", unique); + + return 0; +} + +static void recv_fd_cb(uint64_t src, int fd, void *private_data) +{ + bool *done = private_data; + + if (fd == -1) { + *done = true; + return; + } + + close(fd); +} diff --git a/lib/tfd/sockclnt.c b/lib/tfd/sockclnt.c new file mode 100644 index 0000000..ca0cefe --- /dev/null +++ b/lib/tfd/sockclnt.c @@ -0,0 +1,115 @@ +/* + * Unix SMB/CIFS implementation. + * Copyright (C) Volker Lendecke 2016 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "replace.h" +#include "tfd.h" +#include "poll_funcs/poll_funcs_tevent.h" +#include +#include +#include +#include + +static void recv_fd_cb(uint64_t src, int fd, void *private_data); + +int main(int argc, const char *argv[]) +{ + struct tevent_context *ev; + struct poll_funcs *tevent_poll_funcs; + void *poll_funcs_handle; + int sock; + int ret; + uint64_t dst; + bool done = false; + uint64_t unique; + struct timeval tp1; + + if (argc != 4) { + fprintf(stderr, "Usage: %s dst\n", + argv[0]); + exit(1); + } + + dst = atoi(argv[3]); + + ev = tevent_context_init(NULL); + if (ev == NULL) { + fprintf(stderr, "tevent_context_init failed\n"); + exit(1); + } + + tevent_poll_funcs = poll_funcs_init_tevent(ev); + if (tevent_poll_funcs == NULL) { + fprintf(stderr, "poll_funcs_init_tevent failed\n"); + exit(1); + } + + poll_funcs_handle = poll_funcs_tevent_register( + ev, tevent_poll_funcs, ev); + if (poll_funcs_handle == NULL) { + fprintf(stderr, "poll_funcs_tevent_register failed\n"); + exit(1); + } + + ret = tfd_init(argv[1], argv[2], tevent_poll_funcs, NULL, 0, + recv_fd_cb, &done, &unique); + if (ret != 0) { + fprintf(stderr, "tfd_init failed: %s\n", strerror(ret)); + exit(1); + } + + printf("I'm unique id %"PRIu64"\n", unique); + + gettimeofday(&tp1, NULL); + + ret = tfd_connect(dst, &sock, &unique); + if (ret != 0) { + fprintf(stderr, "tfd_connect failed: %s\n", strerror(ret)); + exit(1); + } + + printf("Sent to unique id %"PRIu64"\n", unique); + + while (true) { + ret = tevent_loop_once(ev); + if (ret != 0) { + fprintf(stderr, "tevent_loop_once failed: %s\n", + strerror(errno)); + exit(1); + } + } + + return 0; +} + +static void recv_fd_cb(uint64_t src, int fd, void *private_data) +{ + bool *done = private_data; + char c; + ssize_t ret; + + if (fd == -1) { + *done = true; + return; + } + + do { + ret = read(fd, &c, 1); + } while ((ret == -1) && (errno == EINTR)); + + exit(0); +} diff --git a/lib/tfd/socksrv.c b/lib/tfd/socksrv.c new file mode 100644 index 0000000..7c225da --- /dev/null +++ b/lib/tfd/socksrv.c @@ -0,0 +1,106 @@ +/* + * Unix SMB/CIFS implementation. + * Copyright (C) Volker Lendecke 2016 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "replace.h" +#include "tfd.h" +#include "poll_funcs/poll_funcs_tevent.h" +#include +#include +#include +#include +#include + +static void recv_fd_cb(uint64_t src, int fd, void *private_data); + +int main(int argc, const char *argv[]) +{ + struct tevent_context *ev; + struct poll_funcs *tevent_poll_funcs; + void *poll_funcs_handle; + int ret; + bool done = false; + int socks[2]; + + if (argc != 3) { + fprintf(stderr, "Usage: %s \n", + argv[0]); + exit(1); + } + + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, socks); + if (ret == -1) { + fprintf(stderr, "socketpair failed: %s\n", strerror(ret)); + exit(1); + } + + ev = tevent_context_init(NULL); + if (ev == NULL) { + fprintf(stderr, "tevent_context_init failed\n"); + exit(1); + } + + tevent_poll_funcs = poll_funcs_init_tevent(ev); + if (tevent_poll_funcs == NULL) { + fprintf(stderr, "poll_funcs_init_tevent failed\n"); + exit(1); + } + + poll_funcs_handle = poll_funcs_tevent_register( + ev, tevent_poll_funcs, ev); + if (poll_funcs_handle == NULL) { + fprintf(stderr, "poll_funcs_tevent_register failed\n"); + exit(1); + } + + ret = tfd_init(argv[1], argv[2], tevent_poll_funcs, NULL, 0, + recv_fd_cb, socks, NULL); + if (ret != 0) { + fprintf(stderr, "tfd_init failed: %s\n", strerror(ret)); + exit(1); + } + + while (!done) { + ret = tevent_loop_once(ev); + if (ret != 0) { + fprintf(stderr, "tevent_loop_once failed: %s\n", + strerror(errno)); + exit(1); + } + } + + return 0; +} + +static void recv_fd_cb(uint64_t src, int fd, void *private_data) +{ + uint64_t unique; + int ret; + + if (fd == -1) { + exit(1); + return; + } + close(fd); + + ret = tfd_connect(src, &fd, &unique); + if (ret != 0) { + fprintf(stderr, "tfd_connect failed: %s\n", strerror(ret)); + exit(1); + } + close(fd); +} diff --git a/lib/tfd/tfd.c b/lib/tfd/tfd.c new file mode 100644 index 0000000..f65efdb --- /dev/null +++ b/lib/tfd/tfd.c @@ -0,0 +1,410 @@ +/* + * Unix SMB/CIFS implementation. + * Copyright (C) Volker Lendecke 2016 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "replace.h" +#include "tfd.h" +#include "lib/poll_funcs/poll_funcs.h" +#include "lib/util/msghdr.h" +#include "lib/util/iov_buf.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct tfd_queue; + +struct tfd_state { + struct sockaddr_un addr; + char tfdd_exec[MAXPATHLEN]; + int sock; + const struct poll_funcs *ev_funcs; + struct poll_watch *read_watch; + void (*recv_callback)(uint64_t src, int fd, void *private_data); + void *private_data; + struct tfd_queue *queue; +}; + +static struct tfd_state global_tfd_state = { .sock = -1 }; + +static void tfd_read_cb(struct poll_watch *w, int fd, + short events, void *private_data); +static int tfd_spawn_tfdd(const char *tfdd_exe, const char *tfdd_sock); + +int tfd_init(const char *tfdd_exec, const char *tfdd_sock, + const struct poll_funcs *ev_funcs, + const uint64_t *tfdids, uint64_t num_tfdids, + void (*recv_callback)(uint64_t src, int fd, void *private_data), + void *private_data, + uint64_t *my_unique) +{ + struct tfd_state *state = &global_tfd_state; + uint64_t pid; + ssize_t to_write, rw_ret; + int ret; + uint64_t unique; + struct iovec iov[3]; + + if (state->sock != -1) { + return EBUSY; + } + + if (num_tfdids > 15) { + return EMSGSIZE; + } + + if (tfdd_exec != NULL) { + if (strlcpy(state->tfdd_exec, tfdd_exec, + sizeof(state->tfdd_exec)) >= + sizeof(state->tfdd_exec)) { + return ENAMETOOLONG; + } + } else { + state->tfdd_exec[0] = '\0'; + } + + state->addr.sun_family = AF_UNIX; + if (strlcpy(state->addr.sun_path, tfdd_sock, + sizeof(state->addr.sun_path)) >= + sizeof(state->addr.sun_path)) { + return ENAMETOOLONG; + } + + state->ev_funcs = ev_funcs; + state->recv_callback = recv_callback; + state->private_data = private_data; + + state->sock = socket(PF_LOCAL, SOCK_STREAM, 0); + if (state->sock == -1) { + return errno; + } + + ret = connect(state->sock, (const struct sockaddr *)&state->addr, + sizeof(state->addr)); + if ((ret == -1) && (state->tfdd_exec[0] != '\0')) { + /* + * Do the access check for a better error message + */ + ret = access(state->tfdd_exec, X_OK); + if (ret == -1) { + ret = errno; + goto fail; + } + ret = tfd_spawn_tfdd(state->tfdd_exec, tfdd_sock); + if (ret != 0) { + goto fail; + } + ret = connect(state->sock, + (const struct sockaddr *)&state->addr, + sizeof(state->addr)); + if (ret == -1) { + ret = errno; + goto fail; + } + } + + do { + rw_ret = read(state->sock, &unique, sizeof(unique)); + } while ((rw_ret == -1) && (errno == EINTR)); + + if (rw_ret == -1) { + ret = errno; + goto fail; + } + if (rw_ret != sizeof(unique)) { + ret = EPIPE; + goto fail; + } + + if (my_unique != NULL) { + *my_unique = unique; + } + + pid = getpid(); + + iov[0].iov_base = &num_tfdids; + iov[0].iov_len = sizeof(num_tfdids); + iov[1].iov_base = &pid; + iov[1].iov_len = sizeof(pid); + + iov[2].iov_base = discard_const_p(uint64_t, tfdids); + iov[2].iov_len = sizeof(uint64_t) * num_tfdids; + + num_tfdids += 1; + + to_write = iov_buflen(iov, ARRAY_SIZE(iov)); + if (to_write == -1) { + ret = EMSGSIZE; + goto fail; + } + + do { + rw_ret = writev(state->sock, iov, ARRAY_SIZE(iov)); + } while ((rw_ret == -1) && (errno == EINTR)); + + if (rw_ret == -1) { + ret = errno; + goto fail; + } + if (rw_ret != to_write) { + ret = EPIPE; + goto fail; + } + + state->read_watch = ev_funcs->watch_new( + ev_funcs, state->sock, POLLIN, tfd_read_cb, NULL); + if (state->read_watch == NULL) { + ret = ENOMEM; + goto fail; + } + return 0; + +fail: + close(state->sock); + state->sock = -1; + return ret; +} + +static bool tfd_read(int sock, uint64_t *src, int *fd) +{ + size_t bufsize = msghdr_prep_recv_fds(NULL, NULL, 0, 1); + uint8_t buf[bufsize]; + ssize_t ret; + + struct iovec iov = { + .iov_base = src, .iov_len = sizeof(*src), + }; + struct msghdr msg = { + .msg_iov = &iov, .msg_iovlen = 1, + }; + + msghdr_prep_recv_fds(&msg, buf, bufsize, 1); + + do { + ret = recvmsg(sock, &msg, MSG_NOSIGNAL); + } while ((ret == -1) && (errno == EINTR)); + + { + size_t num_fds = msghdr_extract_fds(&msg, NULL, 0); + int fds[num_fds]; + size_t i; + + msghdr_extract_fds(&msg, fds, num_fds); + + if (ret == iov.iov_len) { + if (num_fds == 1) { + *fd = fds[0]; + } else { + *fd = -1; + } + return true; + } + + /* + * Close the fds we just received, this is a protocol error + */ + for (i=0; isock, &src, &new_fd); + if (ok) { + state->recv_callback(src, new_fd, state->private_data); + return; + } + + /* + * Protocol error, tell the caller + */ + state->recv_callback(0, -1, state->private_data); +} + +int tfd_connect(uint64_t dst, int *psock, uint64_t *dst_unique) +{ + struct tfd_state *state = &global_tfd_state; + int socks[2] = { -1, -1 }; + struct iovec iov = { .iov_base = &dst, .iov_len = sizeof(dst) }; + struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 }; + size_t msg_len = msghdr_prep_fds(&msg, NULL, 0, &socks[1], 1); + uint8_t msg_buf[msg_len]; + ssize_t rw_ret; + int ret; + + uint8_t result_buf[sizeof(ret) + sizeof(*dst_unique)]; + + if (state->sock == -1) { + return ENOTCONN; + } + + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, socks); + if (ret == -1) { + return errno; + } + + msghdr_prep_fds(&msg, msg_buf, msg_len, &socks[1], 1); + + do { + rw_ret = sendmsg(state->sock, &msg, MSG_NOSIGNAL); + } while ((rw_ret == -1) && (errno == EINTR)); + + if (rw_ret == -1) { + ret = errno; + goto fail; + } + + if (rw_ret != sizeof(dst)) { + ret = EIO; + goto fail; + } + + close(socks[1]); + socks[1] = -1; + + do { + rw_ret = recv(socks[0], result_buf, sizeof(result_buf), + MSG_NOSIGNAL); + } while ((rw_ret == -1) && (errno == EINTR)); + + if (rw_ret == -1) { + ret = errno; + goto fail; + } + if (rw_ret != sizeof(result_buf)) { + ret = EIO; + goto fail; + } + + memcpy(&ret, result_buf, sizeof(ret)); + if (dst_unique != NULL) { + memcpy(dst_unique, result_buf + sizeof(ret), + sizeof(*dst_unique)); + } + + if (ret == 0) { + *psock = socks[0]; + socks[0] = -1; + } + +fail: + if (socks[0] != -1) { + close(socks[0]); + } + if (socks[1] != -1) { + close(socks[1]); + } + + return ret; + +} + +int tfd_fini(void) +{ + struct tfd_state *state = &global_tfd_state; + + if (state->sock == -1) { + return ENOTCONN; + } + state->ev_funcs->watch_free(state->read_watch); + close(state->sock); + state->sock = -1; + return 0; +} + +static void tfd_tfdd_child(const char *tfdd_exec, const char *tfdd_sock) +{ + char *argvec[3]; + char *envvec[1]; + const char *argv0; + + argv0 = strrchr(tfdd_exec, '/'); + if (argv0 != NULL) { + argv0 += 1; + } else { + argv0 = tfdd_exec; + } + + argvec[0] = discard_const_p(char, argv0); + argvec[1] = discard_const_p(char, tfdd_sock); + argvec[2] = NULL; + envvec[0] = NULL; + + execve(tfdd_exec, argvec, envvec); + exit(1); +} + +static int tfd_spawn_tfdd(const char *tfdd_exec, const char *tfdd_sock) +{ + sigset_t mask, omask; + int ret, status; + pid_t pid, waited_pid; + + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + + ret = pthread_sigmask(SIG_BLOCK, &mask, &omask); + if (ret != 0) { + return ret; + } + + pid = fork(); + if (pid == -1) { + ret = errno; + pthread_sigmask(SIG_SETMASK, &omask, NULL); + return ret; + } + if (pid == 0) { + tfd_tfdd_child(tfdd_exec, tfdd_sock); + } + + do { + waited_pid = waitpid(pid, &status, 0); + } while ((waited_pid == -1) && (errno == EINTR)); + + pthread_sigmask(SIG_SETMASK, &omask, NULL); + + if (WIFEXITED(status)) { + status = WEXITSTATUS(status); + if (status == 0) { + return 0; + } + } + + return EBUSY; +} diff --git a/lib/tfd/tfd.h b/lib/tfd/tfd.h new file mode 100644 index 0000000..ee97451 --- /dev/null +++ b/lib/tfd/tfd.h @@ -0,0 +1,104 @@ +/* + * Unix SMB/CIFS implementation. + * Copyright (C) Volker Lendecke 2016 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __TFD_TFD_H__ +#define __TFD_TFD_H__ + +#include "replace.h" + +/** + * @file tfd.h + * + * @brief Routines to connect processes via stream sockets + * + * To support IPC, this subsystem connects participating processes + * with unix domain stream sockets. The function tfd_connect() creates + * such a socket pair and sends one end to another process that registered + * with the subsystem via tfd_init(). + * + * tfd_init() will fork/exec a central tfdd daemon on demand. tfdd + * creates one unix domain stream socket that everybody will connect + * to. + * + * Every participating process has one stream socket open to + * tfdd. This stream socket carries file descriptors from and to other + * participants, forwarded via tfdd. No IPC data between participants + * is transmitted via this stream socket to tfdd, the IPC is done via + * the socketpairs created in tfd_connect(). + * + * When connecting, a participant can register 64-bit tfdids that it + * can receive messages on. In addition to the explicitly listed + * tfdids, tfd_init() will register the current process id. + * + * As a central point of contact, tfdd assigns a unique 64-bit ID to + * every participant. Every time a process calls tfd_connect(), tfdd + * increments the unique id that is assigned. This number space is + * completely orthogonal to the tfdids a process registers. Samba uses + * the unique IDs to protect against recycled PIDs. If a process locks + * a file, it registers itself in for example locking.tdb with its + * pid/unique combination. A lock contender can make sure that the + * lock holder still exists, even if the pid has been recycled by a + * different smbd. + */ + +struct poll_funcs; + +/** + * @brief Connect to the tfdd socket + * + * Connect to and register the process with tfdd. If tfdd is not + * around fork/exec it on demand. + * + * The current pid is always registered with tfdd. + * + * @param[in] tfdd_exec tfdd executable, started on demand + * @param[in] tfdd_sock Path to tfdd socket + * @param[in] ev_funcs poll_funcs structure to register for async fds + * @param[in] tfdids IDs to be registered with tfdd + * @param[in] num_tfids Number of tfdids + * @param[in] recv_callback Callback to receive file descriptors + * @param[in] private_data Pointer passed back to recv_callback + * @param[out] my_unique Unique ID assigned to the process by tfdd + * @return 0 / errno + */ + +int tfd_init(const char *tfdd_exec, const char *tfdd_sock, + const struct poll_funcs *ev_funcs, + const uint64_t *tfdids, uint64_t num_tfdids, + void (*recv_callback)(uint64_t src, int fd, void *private_data), + void *private_data, + uint64_t *my_unique); + +/** + * @brief Disconnect from the tfdd + * + * @return 0 / errno + */ +int tfd_fini(void); + +/** + * @brief Connect to a remote process with a stream socket + * + * @param[in] dst The tfdid of the remote process + * @param[out] psock The local end of the stream socket + * @param[out] dst_unique The unique id of "dst" + * @return 0 / errno + */ +int tfd_connect(uint64_t dst, int *psock, uint64_t *dst_unique); + +#endif diff --git a/lib/tfd/tfdd.c b/lib/tfd/tfdd.c new file mode 100644 index 0000000..3c79d11 --- /dev/null +++ b/lib/tfd/tfdd.c @@ -0,0 +1,710 @@ +/* + * Unix SMB/CIFS implementation. + * Copyright (C) Volker Lendecke 2016 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "replace.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lib/async_req/async_sock.h" +#include "lib/util/tevent_unix.h" +#include "lib/util/dlinklist.h" +#include "lib/util/blocking.h" +#include "lib/util/sys_rw_data.h" +#include "lib/util/msghdr.h" + +struct tfdd_state { + struct tevent_context *ev; + struct tfdd_client_state *clients; + uint64_t next_unique; + int listen_sock; +}; + +struct tfdd_client_state { + struct tfdd_client_state *prev, *next; + struct tfdd_state *tfdd; + int sock; + uint64_t unique; + uint64_t *tfdids; +}; + +static int tfdd_client_state_destructor(struct tfdd_client_state *s); +static void tfdd_client_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *private_data); + +static struct tevent_req *tfdd_client_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tfdd_state *tfdd, + int sock) +{ + struct tevent_req *req; + struct tfdd_client_state *state; + struct tevent_fd *fde; + ssize_t written; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct tfdd_client_state); + if (req == NULL) { + return NULL; + } + state->sock = sock; + state->tfdd = tfdd; + + DLIST_ADD(tfdd->clients, state); + talloc_set_destructor(state, tfdd_client_state_destructor); + + fde = tevent_add_fd(ev, state, state->sock, TEVENT_FD_READ, + tfdd_client_handler, req); + if (tevent_req_nomem(fde, req)) { + return tevent_req_post(req, ev); + } + tevent_fd_set_auto_close(fde); + + ret = set_blocking(sock, false); + if (tevent_req_error(req, ret)) { + return tevent_req_post(req, ev); + } + + do { + written = send(sock, &tfdd->next_unique, + sizeof(tfdd->next_unique), MSG_NOSIGNAL); + } while ((written == -1) && (errno == EINTR)); + + if (written != sizeof(tfdd->next_unique)) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + state->unique = tfdd->next_unique; + + tfdd->next_unique += 1; + + return req; +} + +static int tfdd_client_state_destructor(struct tfdd_client_state *s) +{ + struct tfdd_state *tfdd = s->tfdd; + + DLIST_REMOVE(tfdd->clients, s); + + return 0; +} + +static struct tfdd_client_state *tfdd_find_client(struct tfdd_state *tfdd, + uint64_t tfdid) +{ + struct tfdd_client_state *cl; + + for (cl = tfdd->clients; cl; cl = cl->next) { + uint64_t *tfdids = cl->tfdids; + size_t num_tfdids = talloc_array_length(cl->tfdids); + size_t i; + + for (i = 0; iunique; + + do { + ret = sendmsg(dst_cli->sock, &msg, + MSG_DONTWAIT|MSG_NOSIGNAL); + } while ((ret == -1) && (errno == EINTR)); + + switch (ret) { + case sizeof(src): + err = 0; + break; + case -1: + err = errno; + break; + default: + err = EIO; + break; + } + } + + memcpy(result_buf, &err, sizeof(err)); + memcpy(result_buf + sizeof(err), &dst_unique, sizeof(dst_unique)); + + do { + ret = send(fd, result_buf, sizeof(result_buf), + MSG_DONTWAIT|MSG_NOSIGNAL); + } while ((ret == -1) && (errno == EINTR)); + + /* + * Ignore failure here: This was the status response to the fd + * that we just sent across. The most likely error is EAGAIN, + * but we need to protect ourselves from a DoS. A client could + * send us a full socket, so we would block here or consume + * memory if we queued thing. Normally this is one end of a + * fresh socketpair, so this should never be full at this + * point. + */ +} + +static void tfdd_client_handle_msg(struct tevent_context *ev, + struct tevent_req *req, + struct msghdr *msg, uint64_t dst, + struct tfdd_client_state *state) +{ + size_t num_fds = msghdr_extract_fds(msg, NULL, 0); + int fds[num_fds]; + size_t i; + + msghdr_extract_fds(msg, fds, num_fds); + + if (state->tfdids == NULL) { + ssize_t to_read, nread; + + if (num_fds != 0) { + goto fail; + } + + if (dst == 0) { + goto fail; + } + + /* + * The very first client->tfdd destination is the + * number of ids to be registered. + */ + + state->tfdids = talloc_array(state, uint64_t, dst); + if (state->tfdids == NULL) { + goto fail; + } + + /* + * To avoid an async engine, assume the client sent + * the number together with the tfdids in one call. If + * this turns out to be a problem, we can always add + * some more complexity here. + */ + + to_read = talloc_get_size(state->tfdids); + + nread = read_data(state->sock, state->tfdids, to_read); + if (nread != to_read) { + goto fail; + } + return; + } + + assert(talloc_array_length(state->tfdids) > 0); + + if (num_fds == 1) { + /* + * Pass a fd to the dest + */ + tfdd_send_fd(state->tfdd, state->tfdids[0], dst, fds[0]); + close(fds[0]); + return; + } + + /* + * Protocol error: We only pass at most one fd + */ + +fail: + for (i=0; isock, &msg, MSG_NOSIGNAL); + } while ((ret == -1) && (errno == EINTR)); + + if (ret == 0) { + tevent_req_done(req); + return; + } + + if (ret == -1) { + tevent_req_error(req, errno); + return; + } + + if (ret != sizeof(dst)) { + tevent_req_error(req, EINVAL); + return; + } + + { + size_t num_fds = msghdr_extract_fds(&msg, NULL, 0); + int fds[num_fds]; + + msghdr_extract_fds(&msg, fds, num_fds); + + tfdd_client_handle_msg(ev, req, &msg, dst, state); + } +} + +static int tfdd_client_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_unix(req); +} + +static void tfdd_accepted(struct tevent_req *subreq); +static void tfdd_client_exited(struct tevent_req *subreq); + +static struct tevent_req *tfdd_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int listen_sock) +{ + struct tevent_req *req, *subreq; + struct tfdd_state *state; + + req = tevent_req_create(mem_ctx, &state, struct tfdd_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->listen_sock = listen_sock; + state->next_unique = 1; + + subreq = accept_send(state, state->ev, state->listen_sock); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, tfdd_accepted, req); + return req; +} + +static void tfdd_accepted(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tfdd_state *state = tevent_req_data( + req, struct tfdd_state); + int sock, err; + + sock = accept_recv(subreq, NULL, NULL, &err); + TALLOC_FREE(subreq); + if (sock == -1) { + tevent_req_error(req, err); + return; + } + + subreq = tfdd_client_send(state, state->ev, state, sock); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tfdd_client_exited, req); + + subreq = accept_send(state, state->ev, state->listen_sock); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tfdd_accepted, req); +} + +static void tfdd_client_exited(struct tevent_req *subreq) +{ + tfdd_client_recv(subreq); + TALLOC_FREE(subreq); +} + +static int tfdd_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_unix(req); +} + +static int tfdd_create_lockfile(const char *sockname, int *pfd, bool *locked) +{ + ssize_t len = strlen(sockname); + char name[len+5]; + struct flock lck; + char pidbuf[64]; + ssize_t written; + int ret, fd; + + snprintf(name, sizeof(name), "%s.lck", sockname); + + fd = open(name, O_NONBLOCK|O_CREAT|O_WRONLY, 0644); + if (fd == -1) { + return errno; + } + + *locked = false; + + lck = (struct flock) { .l_type = F_WRLCK, .l_whence = SEEK_SET }; + do { + ret = fcntl(fd, F_SETLK, &lck); + } while ((ret == -1) && (errno == EINTR)); + + if (ret == -1) { + if (errno == EAGAIN) { + *locked = true; + } + goto fail; + } + + do { + ret = ftruncate(fd, 0); + } while ((ret == -1) && (errno == EINTR)); + + if (ret == -1) { + goto fail; + } + + len = snprintf(pidbuf, sizeof(pidbuf), "%ju\n", (uintmax_t)getpid()); + if (len < 0) { + goto fail; + } + + do { + written = pwrite(fd, pidbuf, len, 0); + } while ((written == -1) && (errno == EINTR)); + + if (written == -1) { + goto fail; + } + if (written != len) { + errno = EIO; + goto fail; + } + + *pfd = fd; + return 0; +fail: + ret = errno; + close(fd); + return ret; +} + +static bool tfdd_try_connect(const struct sockaddr_un *addr) +{ + int i, sock; + + sock = socket(PF_LOCAL, SOCK_STREAM, 0); + if (sock == -1) { + return false; + } + + for (i=0; i<5; i++) { + int ret; + ret = connect(sock, (const struct sockaddr *)addr, + sizeof(*addr)); + if (ret == 0) { + close(sock); + return true; + } + poll(NULL, 0, i); + } + + close(sock); + return false; +} + +static void usage(const char *progname) +{ + fprintf(stderr, "Usage: %s [-i] \n", progname); +} + +static int daemon_init(const char *progname, int facility, + const int *used_fds, int num_used_fds) +{ + pid_t pid; + int i, ret; + int max_used = -1; + + for (i=0; i= sizeof(un_addr.sun_path)) { + fprintf(stderr, "Socket name (%s) too long, max %u\n", + argv[0], (unsigned)sizeof(un_addr.sun_path)); + exit(1); + } + + if (daemonize) { + ret = pipe(err_pipe); + if (ret == -1) { + perror("pipe failed"); + exit(1); + } + + ret = daemon_init(progname, LOG_DAEMON, err_pipe, + ARRAY_SIZE(err_pipe)); + if (ret == -1) { + fprintf(stderr, "fork failed: %s\n", strerror(errno)); + exit(1); + } + if (ret != 0) { + char msg[256]; + ssize_t nread; + + /* parent */ + do { + nread = read(err_pipe[0], msg, sizeof(msg)); + } while ((nread == -1) && (errno == EINTR)); + + if (nread < 2) { + fprintf(stderr, "daemon init failed\n"); + exit(1); + } + + if (strncmp(msg, "OK\n", 3) == 0) { + exit(0); + } + + fprintf(stderr, "%s", msg); + exit(1); + } + + /* child */ + close(err_pipe[0]); + } + + ret = tfdd_create_lockfile(un_addr.sun_path, &lockfd, &locked); + if (ret != 0) { + if (locked && tfdd_try_connect(&un_addr)) { + write_err(err_pipe[1], "OK\n"); + exit(0); + } + write_err(err_pipe[1], "Could not create lockfile: %s\n", + strerror(ret)); + exit(1); + } + + sock = socket(PF_LOCAL, SOCK_STREAM, 0); + if (sock == -1) { + write_err(err_pipe[1], "socket failed: %s\n", strerror(errno)); + exit(1); + } + + ret = unlink(un_addr.sun_path); + if ((ret == -1) && (errno != ENOENT)) { + write_err(err_pipe[1], "unlink failed: %s\n", strerror(errno)); + exit(1); + } + + ret = bind(sock, (struct sockaddr *)&un_addr, sizeof(un_addr)); + if (ret == -1) { + write_err(err_pipe[1], "bind failed: %s\n", strerror(errno)); + exit(1); + } + + ret = listen(sock, 5); + if (ret == -1) { + write_err(err_pipe[1], "listen failed: %s\n", strerror(errno)); + exit(1); + } + + chdir("/"); + + ev = tevent_context_init(NULL); + if (ev == NULL) { + write_err(err_pipe[1], "tevent_context_init failed: %s\n", + strerror(errno)); + exit(1); + } + + req = tfdd_send(ev, ev, sock); + if (req == NULL) { + write_err(err_pipe[1], "tfdd_send failed\n"); + exit(1); + } + + if (err_pipe[1] != -1) { + write_err(err_pipe[1], "OK\n"); + close(err_pipe[1]); + } + + if (!tevent_req_poll(req, ev)) { + perror("tevent_req_poll() failed"); + exit(1); + } + + ret = tfdd_recv(req); + TALLOC_FREE(req); + if (ret != 0) { + fprintf(stderr, "tfdd failed: %s\n", strerror(ret)); + exit(1); + } + + TALLOC_FREE(ev); + + return 0; +} diff --git a/lib/tfd/wscript_build b/lib/tfd/wscript_build new file mode 100644 index 0000000..b5aa4ff --- /dev/null +++ b/lib/tfd/wscript_build @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +bld.SAMBA_SUBSYSTEM('tfd', + source='tfd.c', + deps='msghdr replace pthread') +bld.SAMBA_BINARY('tfdd', + source='tfdd.c', + deps='LIBASYNC_REQ msghdr sys_rw', + install_path='${LIBEXECDIR}') +bld.SAMBA_BINARY('fdsrc', + source='fdsrc.c', + deps='replace talloc POLL_FUNCS_TEVENT tfd', + install=False) +bld.SAMBA_BINARY('fddrn', + source='fddrn.c', + deps='replace talloc POLL_FUNCS_TEVENT tfd', + install=False) +bld.SAMBA_BINARY('socksrv', + source='socksrv.c', + deps='replace talloc POLL_FUNCS_TEVENT tfd', + install=False) +bld.SAMBA_BINARY('sockclnt', + source='sockclnt.c', + deps='replace talloc POLL_FUNCS_TEVENT tfd', + install=False) diff --git a/wscript_build b/wscript_build index 3813cff..bf276ed 100644 --- a/wscript_build +++ b/wscript_build @@ -44,6 +44,7 @@ bld.RECURSE('lib/addns') bld.RECURSE('lib/ldb') bld.RECURSE('lib/param') bld.RECURSE('lib/poll_funcs') +bld.RECURSE('lib/tfd') bld.RECURSE('dynconfig') bld.RECURSE('lib/util/charset') bld.RECURSE('python') -- 1.9.1