/* vim: set expandtab ts=4 sw=4: */ /* * You may redistribute this program 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 "util/events/libuv/UvWrapper.h" #include "exception/Er.h" #include "memory/Allocator.h" #include "util/events/Pipe.h" #include "util/events/libuv/Pipe_pvt.h" #include "util/events/libuv/EventBase_pvt.h" #include "util/log/Log.h" #include "util/Identity.h" #include "util/CString.h" #include "wire/Message.h" #include "wire/Error.h" #include "benc/String.h" #include #include #include #include #include struct Pipe_WriteRequest_pvt; struct Pipe_pvt { struct Pipe pub; uv_pipe_t peer; /** Job to close the handles when the allocator is freed */ struct Allocator_OnFreeJob* closeHandlesOnFree; /** Job which blocks the freeing until the callback completes */ struct Allocator_OnFreeJob* blockFreeInsideCallback; // true if we can pass file descriptors through this pipe bool ipc; /** 1 when the pipe becomes active. */ int isActive; int queueLen; /** Used by blockFreeInsideCallback */ int isInCallback; /** only non-null before the connection is setup. */ struct Pipe_WriteRequest_pvt* bufferedRequest; struct Allocator* alloc; struct Log* log; Identity }; struct Pipe_WriteRequest_pvt { uv_write_t uvReq; struct Pipe_pvt* pipe; struct Message* msg; struct Allocator* alloc; Identity }; static void sendMessageCallback(uv_write_t* uvReq, int error) { struct Pipe_WriteRequest_pvt* req = Identity_check((struct Pipe_WriteRequest_pvt*) uvReq); if (error) { Log_info(req->pipe->log, "Failed to write to pipe [%s] [%s]", req->pipe->pub.fullName, uv_strerror(error) ); } req->pipe->queueLen -= Message_getLength(req->msg); Assert_ifParanoid(req->pipe->queueLen >= 0); Allocator_free(req->alloc); } static void sendMessage2(struct Pipe_WriteRequest_pvt* req) { struct Pipe_pvt* pipe = req->pipe; struct Message* m = req->msg; uv_buf_t buffers[] = { { .base = (char*)m->msgbytes, .len = Message_getLength(m) } }; int ret = -1; int fd = Message_getAssociatedFd(m); if (pipe->ipc && fd > -1 && !Defined(win32)) { uv_stream_t* fake_handle = Allocator_calloc(req->alloc, sizeof(uv_stream_t), 1); #ifndef win32 fake_handle->io_watcher.fd = fd; #endif fake_handle->type = UV_TCP; ret = uv_write2( &req->uvReq, (uv_stream_t*) &pipe->peer, buffers, 1, fake_handle, sendMessageCallback); Log_debug(pipe->log, "Sending message with fd [%d]", fd); } else { ret = uv_write(&req->uvReq, (uv_stream_t*) &pipe->peer, buffers, 1, sendMessageCallback); } if (ret) { Log_info(pipe->log, "Failed writing to pipe [%s] [%s]", pipe->pub.fullName, uv_strerror(ret) ); Allocator_free(req->alloc); return; } pipe->queueLen += Message_getLength(m); return; } static Iface_DEFUN sendMessage(struct Message* m, struct Iface* iface) { struct Pipe_pvt* pipe = Identity_check((struct Pipe_pvt*) iface); if (pipe->queueLen > 50000) { return Error(m, "OVERFLOW"); } // This allocator will hold the message allocator in existance after it is freed. struct Allocator* reqAlloc = Allocator_child(pipe->alloc); Allocator_adopt(reqAlloc, Message_getAlloc(m)); struct Pipe_WriteRequest_pvt* req = Allocator_clone(reqAlloc, (&(struct Pipe_WriteRequest_pvt) { .pipe = pipe, .msg = m, .alloc = reqAlloc })); Identity_set(req); if (pipe->isActive) { sendMessage2(req); } else { if (!pipe->bufferedRequest) { Log_debug(pipe->log, "Buffering a message"); pipe->bufferedRequest = req; } else { Log_debug(pipe->log, "Appending to the buffered message"); struct Message* m2 = Message_new(0, Message_getLength(m) + Message_getLength(pipe->bufferedRequest->msg), reqAlloc); Er_assert(Message_epush(m2, m->msgbytes, Message_getLength(m))); Er_assert(Message_epush(m2, pipe->bufferedRequest->msg->msgbytes, Message_getLength(pipe->bufferedRequest->msg))); req->msg = m2; Allocator_free(pipe->bufferedRequest->alloc); pipe->bufferedRequest = req; } } return NULL; } /** Asynchronous allocator freeing. */ static void onClose(uv_handle_t* handle) { struct Pipe_pvt* pipe = Identity_check((struct Pipe_pvt*)handle->data); handle->data = NULL; if (pipe->closeHandlesOnFree && !pipe->peer.data) { Allocator_onFreeComplete((struct Allocator_OnFreeJob*) pipe->closeHandlesOnFree); } } #if Pipe_PADDING_AMOUNT < 8 #error #endif #define ALLOC(buff) (((struct Message**) &(buff[-(8 + (((uintptr_t)buff) % 8))]))[0]) static void incoming(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { struct Pipe_pvt* pipe = Identity_check((struct Pipe_pvt*) stream->data); // Grab out the msg which was placed there by allocate() struct Message* msg = buf->base ? ALLOC(buf->base) : NULL; pipe->isInCallback = 1; Assert_true(!msg || Message_getAlloc(msg)->fileName == pipe->alloc->fileName); if (nread < 0) { if (pipe->pub.onClose) { pipe->pub.onClose(&pipe->pub, 0); } } else if (nread == 0) { // This is common. //Log_debug(pipe->log, "Pipe 0 length read [%s]", pipe->pub.fullName); } else { Assert_true(msg); Er_assert(Message_truncate(msg, nread)); if (pipe->ipc) { #ifndef win32 Message_setAssociatedFd(msg, stream->accepted_fd); #endif } Iface_send(&pipe->pub.iface, msg); } if (msg) { Allocator_free(Message_getAlloc(msg)); } pipe->isInCallback = 0; if (pipe->blockFreeInsideCallback) { Allocator_onFreeComplete((struct Allocator_OnFreeJob*) pipe->blockFreeInsideCallback); } } static void incoming2(uv_pipe_t* stream, ssize_t nread, const uv_buf_t* buf, uv_handle_type _) { incoming((uv_stream_t*)stream, nread, buf); } static void allocate(uv_handle_t* handle, size_t size, uv_buf_t* buf) { struct Pipe_pvt* pipe = Identity_check((struct Pipe_pvt*) handle->data); size = Pipe_BUFFER_CAP; struct Allocator* child = Allocator_child(pipe->alloc); struct Message* msg = Message_new(size, Pipe_PADDING_AMOUNT, child); ALLOC(msg->msgbytes) = msg; buf->base = msg->msgbytes; buf->len = size; } static int startPipe(struct Pipe_pvt* pipe) { if (pipe->ipc) { return uv_read2_start((uv_stream_t*)&pipe->peer, allocate, incoming2); } else { return uv_read_start((uv_stream_t*)&pipe->peer, allocate, incoming); } } static void connected(uv_connect_t* req, int status) { uv_stream_t* link = req->handle; struct Pipe_pvt* pipe = Identity_check((struct Pipe_pvt*) link->data); Log_debug(pipe->log, "Pipe [%s] established connection", pipe->pub.fullName); int ret; if (status) { Log_info(pipe->log, "uv_pipe_connect() failed for pipe [%s] [%s]", pipe->pub.fullName, uv_strerror(status) ); uv_close((uv_handle_t*) &pipe->peer, onClose); } else if ((ret = startPipe(pipe))) { Log_info(pipe->log, "uv_read_start() failed for pipe [%s] [%s]", pipe->pub.fullName, uv_strerror(ret)); uv_close((uv_handle_t*) &pipe->peer, onClose); } else { pipe->isActive = 1; if (pipe->pub.onConnection) { pipe->pub.onConnection(&pipe->pub, status); } // If there's anything buffered then send it. if (pipe->bufferedRequest) { Log_debug(pipe->log, "Sending buffered message"); sendMessage2(pipe->bufferedRequest); pipe->bufferedRequest = NULL; } } } static int blockFreeInsideCallback(struct Allocator_OnFreeJob* job) { struct Pipe_pvt* pipe = Identity_check((struct Pipe_pvt*)job->userData); if (!pipe->isInCallback) { return 0; } pipe->blockFreeInsideCallback = job; return Allocator_ONFREE_ASYNC; } static int closeHandlesOnFree(struct Allocator_OnFreeJob* job) { struct Pipe_pvt* pipe = Identity_check((struct Pipe_pvt*)job->userData); pipe->closeHandlesOnFree = job; if (pipe->peer.data) { uv_close((uv_handle_t*) &pipe->peer, onClose); return Allocator_ONFREE_ASYNC; } return 0; } static Er_DEFUN(struct Pipe_pvt* newPipeAny(struct EventBase* eb, const char* fullPath, bool ipc, struct Log* log, struct Allocator* userAlloc)) { struct EventBase_pvt* ctx = EventBase_privatize(eb); struct Allocator* alloc = Allocator_child(userAlloc); struct Pipe_pvt* out = Allocator_clone(alloc, (&(struct Pipe_pvt) { .pub = { .iface = { .send = sendMessage }, .fullName = (fullPath) ? CString_strdup(fullPath, alloc) : NULL, }, .alloc = alloc, .log = log, .ipc = ipc, })); int ret = uv_pipe_init(ctx->loop, &out->peer, ipc); if (ret) { Er_raise(alloc, "uv_pipe_init() failed [%s]", uv_strerror(ret)); } Allocator_onFree(alloc, closeHandlesOnFree, out); Allocator_onFree(alloc, blockFreeInsideCallback, out); out->peer.data = out; Identity_set(out); Er_ret(out); } Er_DEFUN(struct Pipe* Pipe_forFd(int fd, bool ipc, struct EventBase* eb, struct Log* log, struct Allocator* userAlloc)) { char buff[32] = {0}; snprintf(buff, 31, "forFd(%d)", fd); struct Pipe_pvt* out = Er(newPipeAny(eb, buff, ipc, log, userAlloc)); int ret = uv_pipe_open(&out->peer, fd); if (ret) { Er_raise(out->alloc, "uv_pipe_open(inFd) failed [%s]", uv_strerror(ret)); } uv_connect_t req = { .handle = (uv_stream_t*) &out->peer }; connected(&req, 0); Er_ret(&out->pub); } Er_DEFUN(struct Pipe* Pipe_named(const char* fullPath, struct EventBase* eb, struct Log* log, struct Allocator* userAlloc)) { struct Pipe_pvt* out = Er(newPipeAny(eb, fullPath, true, log, userAlloc)); uv_connect_t* req = Allocator_malloc(out->alloc, sizeof(uv_connect_t)); req->data = out; uv_pipe_connect(req, &out->peer, out->pub.fullName, connected); int err = 0; // We get the error back synchronously but windows doesn't support that // TODO(cjd): Find a better way #ifndef win32 err = (&out->peer)->delayed_error; #endif if (err != 0) { Er_raise(out->alloc, "uv_pipe_connect() failed [%s] for pipe [%s]", uv_strerror(err), out->pub.fullName); } Er_ret(&out->pub); } Er_DEFUN(struct Pipe* Pipe_serverAccept(uv_pipe_t* server, const char* pipeName, struct EventBase* eb, struct Log* log, struct Allocator* userAlloc)) { struct Pipe_pvt* out = Er(newPipeAny(eb, NULL, true, log, userAlloc)); int ret = uv_accept((uv_stream_t*) server, (uv_stream_t*) &out->peer); if (ret) { uv_close((uv_handle_t*) &out->peer, onClose); Er_raise(out->alloc, "uv_accept() failed: pipe [%s] [%s]", pipeName, uv_strerror(ret)); } else { uv_connect_t req = { .handle = (uv_stream_t*) &out->peer }; connected(&req, 0); } Er_ret(&out->pub); } Er_DEFUN(bool Pipe_exists(const char* path, struct Allocator* errAlloc)) { struct stat st; if (stat(path, &st)) { if (errno == ENOENT) { Er_ret(false); } Er_raise(errAlloc, "Failed stat(%s) [%s]", path, strerror(errno)); } else { int flag = 0; #ifdef win32 flag = S_IFIFO; #elif defined(S_IFSOCK) flag = S_IFSOCK; #else #error "missing S_IFSOCK" #endif Er_ret(((int)(st.st_mode & S_IFMT)) == flag); } }