/*** This file is part of PulseAudio. Copyright 2004-2006 Lennart Poettering Copyright 2006-2007 Pierre Ossman for Cendio AB PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #ifdef HAVE_SYS_UN_H #include #endif #include #include #include #include #include #include #include #include "iochannel.h" struct pa_iochannel { int ifd, ofd; int ifd_type, ofd_type; pa_mainloop_api* mainloop; pa_iochannel_cb_t callback; void*userdata; bool readable:1; bool writable:1; bool hungup:1; bool no_close:1; pa_io_event* input_event, *output_event; }; static void callback(pa_mainloop_api* m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata); static void delete_events(pa_iochannel *io) { pa_assert(io); if (io->input_event) io->mainloop->io_free(io->input_event); if (io->output_event && io->output_event != io->input_event) io->mainloop->io_free(io->output_event); io->input_event = io->output_event = NULL; } static void enable_events(pa_iochannel *io) { pa_assert(io); if (io->hungup) { delete_events(io); return; } if (io->ifd == io->ofd && io->ifd >= 0) { pa_io_event_flags_t f = PA_IO_EVENT_NULL; if (!io->readable) f |= PA_IO_EVENT_INPUT; if (!io->writable) f |= PA_IO_EVENT_OUTPUT; pa_assert(io->input_event == io->output_event); if (f != PA_IO_EVENT_NULL) { if (io->input_event) io->mainloop->io_enable(io->input_event, f); else io->input_event = io->output_event = io->mainloop->io_new(io->mainloop, io->ifd, f, callback, io); } else delete_events(io); } else { if (io->ifd >= 0) { if (!io->readable) { if (io->input_event) io->mainloop->io_enable(io->input_event, PA_IO_EVENT_INPUT); else io->input_event = io->mainloop->io_new(io->mainloop, io->ifd, PA_IO_EVENT_INPUT, callback, io); } else if (io->input_event) { io->mainloop->io_free(io->input_event); io->input_event = NULL; } } if (io->ofd >= 0) { if (!io->writable) { if (io->output_event) io->mainloop->io_enable(io->output_event, PA_IO_EVENT_OUTPUT); else io->output_event = io->mainloop->io_new(io->mainloop, io->ofd, PA_IO_EVENT_OUTPUT, callback, io); } else if (io->output_event) { io->mainloop->io_free(io->output_event); io->output_event = NULL; } } } } static void callback(pa_mainloop_api* m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) { pa_iochannel *io = userdata; bool changed = false; pa_assert(m); pa_assert(e); pa_assert(fd >= 0); pa_assert(userdata); if ((f & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) && !io->hungup) { io->hungup = true; changed = true; } if ((f & PA_IO_EVENT_INPUT) && !io->readable) { io->readable = true; changed = true; pa_assert(e == io->input_event); } if ((f & PA_IO_EVENT_OUTPUT) && !io->writable) { io->writable = true; changed = true; pa_assert(e == io->output_event); } if (changed) { enable_events(io); if (io->callback) io->callback(io, io->userdata); } } pa_iochannel* pa_iochannel_new(pa_mainloop_api*m, int ifd, int ofd) { pa_iochannel *io; pa_assert(m); pa_assert(ifd >= 0 || ofd >= 0); io = pa_xnew0(pa_iochannel, 1); io->ifd = ifd; io->ofd = ofd; io->mainloop = m; if (io->ifd >= 0) pa_make_fd_nonblock(io->ifd); if (io->ofd >= 0 && io->ofd != io->ifd) pa_make_fd_nonblock(io->ofd); enable_events(io); return io; } void pa_iochannel_free(pa_iochannel*io) { pa_assert(io); delete_events(io); if (!io->no_close) { if (io->ifd >= 0) pa_close(io->ifd); if (io->ofd >= 0 && io->ofd != io->ifd) pa_close(io->ofd); } pa_xfree(io); } bool pa_iochannel_is_readable(pa_iochannel*io) { pa_assert(io); return io->readable || io->hungup; } bool pa_iochannel_is_writable(pa_iochannel*io) { pa_assert(io); return io->writable && !io->hungup; } bool pa_iochannel_is_hungup(pa_iochannel*io) { pa_assert(io); return io->hungup; } ssize_t pa_iochannel_write(pa_iochannel*io, const void*data, size_t l) { ssize_t r; pa_assert(io); pa_assert(data); pa_assert(l); pa_assert(io->ofd >= 0); r = pa_write(io->ofd, data, l, &io->ofd_type); if ((size_t) r == l) return r; /* Fast path - we almost always successfully write everything */ if (r < 0) { if (errno == EAGAIN) r = 0; else return r; } /* Partial write - let's get a notification when we can write more */ io->writable = io->hungup = false; enable_events(io); return r; } ssize_t pa_iochannel_read(pa_iochannel*io, void*data, size_t l) { ssize_t r; pa_assert(io); pa_assert(data); pa_assert(io->ifd >= 0); if ((r = pa_read(io->ifd, data, l, &io->ifd_type)) >= 0) { /* We also reset the hangup flag here to ensure that another * IO callback is triggered so that we will again call into * user code */ io->readable = io->hungup = false; enable_events(io); } return r; } #ifdef HAVE_CREDS #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__GNU__) typedef struct cmsgcred pa_ucred_t; #define SCM_CREDENTIALS SCM_CREDS #else typedef struct ucred pa_ucred_t; #endif bool pa_iochannel_creds_supported(pa_iochannel *io) { struct { struct sockaddr sa; #ifdef HAVE_SYS_UN_H struct sockaddr_un un; #endif struct sockaddr_storage storage; } sa; socklen_t l; pa_assert(io); pa_assert(io->ifd >= 0); pa_assert(io->ofd == io->ifd); l = sizeof(sa); if (getsockname(io->ifd, &sa.sa, &l) < 0) return false; return sa.sa.sa_family == AF_UNIX; } int pa_iochannel_creds_enable(pa_iochannel *io) { #if !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && !defined(__GNU__) int t = 1; #endif pa_assert(io); pa_assert(io->ifd >= 0); #if !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && !defined(__GNU__) if (setsockopt(io->ifd, SOL_SOCKET, SO_PASSCRED, &t, sizeof(t)) < 0) { pa_log_error("setsockopt(SOL_SOCKET, SO_PASSCRED): %s", pa_cstrerror(errno)); return -1; } #endif return 0; } ssize_t pa_iochannel_write_with_creds(pa_iochannel*io, const void*data, size_t l, const pa_creds *ucred) { ssize_t r; struct msghdr mh; struct iovec iov; union { struct cmsghdr hdr; uint8_t data[CMSG_SPACE(sizeof(pa_ucred_t))]; } cmsg; pa_ucred_t *u; pa_assert(io); pa_assert(data); pa_assert(l); pa_assert(io->ofd >= 0); pa_zero(iov); iov.iov_base = (void*) data; iov.iov_len = l; pa_zero(cmsg); cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(pa_ucred_t)); cmsg.hdr.cmsg_level = SOL_SOCKET; cmsg.hdr.cmsg_type = SCM_CREDENTIALS; u = (pa_ucred_t*) CMSG_DATA(&cmsg.hdr); #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__GNU__) // the kernel fills everything #else u->pid = getpid(); if (ucred) { u->uid = ucred->uid; u->gid = ucred->gid; } else { u->uid = getuid(); u->gid = getgid(); } #endif pa_zero(mh); mh.msg_iov = &iov; mh.msg_iovlen = 1; mh.msg_control = &cmsg; mh.msg_controllen = sizeof(cmsg); if ((r = sendmsg(io->ofd, &mh, MSG_NOSIGNAL)) >= 0) { io->writable = io->hungup = false; enable_events(io); } return r; } /* For more details on FD passing, check the cmsg(3) manpage * and IETF RFC #2292: "Advanced Sockets API for IPv6" */ ssize_t pa_iochannel_write_with_fds(pa_iochannel*io, const void*data, size_t l, int nfd, const int *fds) { ssize_t r; int *msgdata; struct msghdr mh; struct iovec iov; union { struct cmsghdr hdr; uint8_t data[CMSG_SPACE(sizeof(int) * MAX_ANCIL_DATA_FDS)]; } cmsg; pa_assert(io); pa_assert(data); pa_assert(l); pa_assert(io->ofd >= 0); pa_assert(fds); pa_assert(nfd > 0); pa_assert(nfd <= MAX_ANCIL_DATA_FDS); pa_zero(iov); iov.iov_base = (void*) data; iov.iov_len = l; pa_zero(cmsg); cmsg.hdr.cmsg_level = SOL_SOCKET; cmsg.hdr.cmsg_type = SCM_RIGHTS; msgdata = (int*) CMSG_DATA(&cmsg.hdr); memcpy(msgdata, fds, nfd * sizeof(int)); cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(int) * nfd); pa_zero(mh); mh.msg_iov = &iov; mh.msg_iovlen = 1; mh.msg_control = &cmsg; /* If we followed the example on the cmsg man page, we'd use * sizeof(cmsg.data) here, but if nfd < MAX_ANCIL_DATA_FDS, then the data * buffer is larger than needed, and the kernel doesn't like it if we set * msg_controllen to a larger than necessary value. The commit message for * commit 451d1d6762 contains a longer explanation. */ mh.msg_controllen = CMSG_SPACE(sizeof(int) * nfd); if ((r = sendmsg(io->ofd, &mh, MSG_NOSIGNAL)) >= 0) { io->writable = io->hungup = false; enable_events(io); } return r; } ssize_t pa_iochannel_read_with_ancil_data(pa_iochannel*io, void*data, size_t l, pa_cmsg_ancil_data *ancil_data) { ssize_t r; struct msghdr mh; struct iovec iov; union { struct cmsghdr hdr; uint8_t data[CMSG_SPACE(sizeof(pa_ucred_t)) + CMSG_SPACE(sizeof(int) * MAX_ANCIL_DATA_FDS)]; } cmsg; pa_assert(io); pa_assert(data); pa_assert(l); pa_assert(io->ifd >= 0); pa_assert(ancil_data); if (io->ifd_type > 0) { ancil_data->creds_valid = false; ancil_data->nfd = 0; return pa_iochannel_read(io, data, l); } iov.iov_base = data; iov.iov_len = l; pa_zero(mh); mh.msg_iov = &iov; mh.msg_iovlen = 1; mh.msg_control = &cmsg; mh.msg_controllen = sizeof(cmsg); if ((r = recvmsg(io->ifd, &mh, 0)) >= 0) { struct cmsghdr *cmh; ancil_data->creds_valid = false; ancil_data->nfd = 0; for (cmh = CMSG_FIRSTHDR(&mh); cmh; cmh = CMSG_NXTHDR(&mh, cmh)) { if (cmh->cmsg_level != SOL_SOCKET) continue; if (cmh->cmsg_type == SCM_CREDENTIALS) { pa_ucred_t u; pa_assert(cmh->cmsg_len == CMSG_LEN(sizeof(pa_ucred_t))); memcpy(&u, CMSG_DATA(cmh), sizeof(pa_ucred_t)); #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__GNU__) ancil_data->creds.gid = u.cmcred_gid; ancil_data->creds.uid = u.cmcred_uid; #else ancil_data->creds.gid = u.gid; ancil_data->creds.uid = u.uid; #endif ancil_data->creds_valid = true; } else if (cmh->cmsg_type == SCM_RIGHTS) { int nfd = (cmh->cmsg_len - CMSG_LEN(0)) / sizeof(int); if (nfd > MAX_ANCIL_DATA_FDS) { int i; pa_log("Trying to receive too many file descriptors!"); for (i = 0; i < nfd; i++) pa_close(((int*) CMSG_DATA(cmh))[i]); continue; } memcpy(ancil_data->fds, CMSG_DATA(cmh), nfd * sizeof(int)); ancil_data->nfd = nfd; ancil_data->close_fds_on_cleanup = true; } } io->readable = io->hungup = false; enable_events(io); } if (r == -1 && errno == ENOTSOCK) { io->ifd_type = 1; return pa_iochannel_read_with_ancil_data(io, data, l, ancil_data); } return r; } #endif /* HAVE_CREDS */ void pa_iochannel_set_callback(pa_iochannel*io, pa_iochannel_cb_t _callback, void *userdata) { pa_assert(io); io->callback = _callback; io->userdata = userdata; } void pa_iochannel_set_noclose(pa_iochannel*io, bool b) { pa_assert(io); io->no_close = b; } void pa_iochannel_socket_peer_to_string(pa_iochannel*io, char*s, size_t l) { pa_assert(io); pa_assert(s); pa_assert(l); pa_socket_peer_to_string(io->ifd, s, l); } int pa_iochannel_socket_set_rcvbuf(pa_iochannel *io, size_t l) { pa_assert(io); return pa_socket_set_rcvbuf(io->ifd, l); } int pa_iochannel_socket_set_sndbuf(pa_iochannel *io, size_t l) { pa_assert(io); return pa_socket_set_sndbuf(io->ofd, l); } pa_mainloop_api* pa_iochannel_get_mainloop_api(pa_iochannel *io) { pa_assert(io); return io->mainloop; } int pa_iochannel_get_recv_fd(pa_iochannel *io) { pa_assert(io); return io->ifd; } int pa_iochannel_get_send_fd(pa_iochannel *io) { pa_assert(io); return io->ofd; } bool pa_iochannel_socket_is_local(pa_iochannel *io) { pa_assert(io); if (pa_socket_is_local(io->ifd)) return true; if (io->ifd != io->ofd) if (pa_socket_is_local(io->ofd)) return true; return false; }