From: David Lamparter Date: Tue, 3 Dec 2019 23:17:50 +0000 (+0100) Subject: lib: allow returning a file descriptor over vtysh X-Git-Tag: pim6-testing-20220430~286^2~5 X-Git-Url: https://git.puffer.fish/?a=commitdiff_plain;h=b2dde56b2c1aa793a476ac6beed2a28dd74e4591;p=matthieu%2Ffrr.git lib: allow returning a file descriptor over vtysh This adds the plumbing necessary to yield back a file descriptor to vtysh. The fd is passed on the command status code bytes through AF_UNIX SCM_RIGHTS. Signed-off-by: David Lamparter --- diff --git a/lib/vty.c b/lib/vty.c index a42b8c92be..ad9cd719e0 100644 --- a/lib/vty.c +++ b/lib/vty.c @@ -1464,6 +1464,11 @@ static void vty_read(struct thread *thread) vty_out(vty, "\n"); buffer_flush_available(vty->obuf, vty->wfd); vty_execute(vty); + + if (vty->pass_fd != -1) { + close(vty->pass_fd); + vty->pass_fd = -1; + } break; case '\t': vty_complete_command(vty); @@ -1561,6 +1566,7 @@ struct vty *vty_new(void) new->obuf = buffer_new(0); /* Use default buffer size. */ new->buf = XCALLOC(MTYPE_VTY, VTY_BUFSIZ); new->max = VTY_BUFSIZ; + new->pass_fd = -1; return new; } @@ -2010,9 +2016,64 @@ static void vtysh_accept(struct thread *thread) vty_event(VTYSH_READ, vty); } +static int vtysh_do_pass_fd(struct vty *vty) +{ + struct iovec iov[1] = { + { + .iov_base = vty->pass_fd_status, + .iov_len = sizeof(vty->pass_fd_status), + }, + }; + union { + uint8_t buf[CMSG_SPACE(sizeof(int))]; + struct cmsghdr align; + } u; + struct msghdr mh = { + .msg_iov = iov, + .msg_iovlen = array_size(iov), + .msg_control = u.buf, + .msg_controllen = sizeof(u.buf), + }; + struct cmsghdr *cmh = CMSG_FIRSTHDR(&mh); + ssize_t ret; + + cmh->cmsg_level = SOL_SOCKET; + cmh->cmsg_type = SCM_RIGHTS; + cmh->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmh), &vty->pass_fd, sizeof(int)); + + ret = sendmsg(vty->wfd, &mh, 0); + if (ret < 0 && ERRNO_IO_RETRY(errno)) + return BUFFER_PENDING; + + close(vty->pass_fd); + vty->pass_fd = -1; + vty->status = VTY_NORMAL; + + if (ret <= 0) + return BUFFER_ERROR; + + /* resume accepting commands (suspended in vtysh_read) */ + vty_event(VTYSH_READ, vty); + + if ((size_t)ret < sizeof(vty->pass_fd_status)) { + size_t remains = sizeof(vty->pass_fd_status) - ret; + + buffer_put(vty->obuf, vty->pass_fd_status + ret, remains); + return BUFFER_PENDING; + } + return BUFFER_EMPTY; +} + static int vtysh_flush(struct vty *vty) { - switch (buffer_flush_available(vty->obuf, vty->wfd)) { + int ret; + + ret = buffer_flush_available(vty->obuf, vty->wfd); + if (ret == BUFFER_EMPTY && vty->status == VTY_PASSFD) + ret = vtysh_do_pass_fd(vty); + + switch (ret) { case BUFFER_PENDING: vty_event(VTYSH_WRITE, vty); break; @@ -2031,6 +2092,14 @@ static int vtysh_flush(struct vty *vty) return 0; } +void vty_pass_fd(struct vty *vty, int fd) +{ + if (vty->pass_fd != -1) + close(vty->pass_fd); + + vty->pass_fd = fd; +} + static void vtysh_read(struct thread *thread) { int ret; @@ -2090,6 +2159,26 @@ static void vtysh_read(struct thread *thread) printf("vtysh node: %d\n", vty->node); #endif /* VTYSH_DEBUG */ + if (vty->pass_fd != -1) { + memset(vty->pass_fd_status, 0, 4); + vty->pass_fd_status[3] = ret; + vty->status = VTY_PASSFD; + + if (!vty->t_write) + vty_event(VTYSH_WRITE, vty); + + /* this introduces a "sequence point" + * command output is written normally, + * read processing is suspended until + * buffer is empty + * then retcode + FD is written + * then normal processing resumes + * + * => skip vty_event(VTYSH_READ, vty)! + */ + return; + } + /* hack for asynchronous "write integrated" * - other commands in "buf" will be ditched * - input during pending config-write is @@ -2161,6 +2250,11 @@ void vty_close(struct vty *vty) THREAD_OFF(vty->t_write); THREAD_OFF(vty->t_timeout); + if (vty->pass_fd != -1) { + close(vty->pass_fd); + vty->pass_fd = -1; + } + /* Flush buffer. */ buffer_flush_all(vty->obuf, vty->wfd); diff --git a/lib/vty.h b/lib/vty.h index 9ffbce3268..92fbb468a9 100644 --- a/lib/vty.h +++ b/lib/vty.h @@ -161,7 +161,19 @@ struct vty { unsigned char escape; /* Current vty status. */ - enum { VTY_NORMAL, VTY_CLOSE, VTY_MORE, VTY_MORELINE } status; + enum { + VTY_NORMAL, + VTY_CLOSE, + VTY_MORE, + VTY_MORELINE, + VTY_PASSFD, + } status; + + /* vtysh socket/fd passing (for terminal monitor) */ + int pass_fd; + + /* CLI command return value (likely CMD_SUCCESS) when pass_fd != -1 */ + uint8_t pass_fd_status[4]; /* IAC handling: was the last character received the IAC (interpret-as-command) escape character (and therefore the next @@ -329,6 +341,11 @@ extern bool vty_set_include(struct vty *vty, const char *regexp); */ extern int vty_json(struct vty *vty, struct json_object *json); +/* post fd to be passed to the vtysh client + * fd is owned by the VTY code after this and will be closed when done + */ +extern void vty_pass_fd(struct vty *vty, int fd); + extern bool vty_read_config(struct nb_config *config, const char *config_file, char *config_default_dir); extern void vty_time_print(struct vty *, int);