/* * This file is part of LEM, a Lua Event Machine. * Copyright 2011-2013 Emil Renner Berthing * * LEM 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 3 of * the License, or (at your option) any later version. * * LEM 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 LEM. If not, see . */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__FreeBSD__) || defined(__APPLE__) #include #include #include extern char **environ; #else #include #include #endif #ifndef UNIX_PATH_MAX #define UNIX_PATH_MAX (sizeof ((struct sockaddr_un *)0)->sun_path) #endif #include static int io_closed(lua_State *T) { lua_pushnil(T); lua_pushliteral(T, "closed"); return 2; } static int io_busy(lua_State *T) { lua_pushnil(T); lua_pushliteral(T, "busy"); return 2; } static int io_strerror(lua_State *T, int err) { lua_pushnil(T); lua_pushstring(T, strerror(err)); return 2; } static int io_optperm(lua_State *T, int idx) { lua_Number n = luaL_optnumber(T, idx, -1); int mode = n; int octal; int i; if ((lua_Number)mode != n) goto error; if (mode == -1) return -1; if (mode < 0) goto error; octal = 0; for (i = 1; i <= 64; i *= 8) { int digit = mode % 10; if (digit > 7) goto error; octal += digit * i; mode /= 10; } if (mode != 0) goto error; return octal; error: return luaL_argerror(T, idx, "invalid permissions"); } #include "file.c" #include "stream.c" #include "server.c" #include "tcp.c" #include "unix.c" /* * io.open() */ struct open { struct lem_async a; lua_State *T; const char *path; int fd; int flags; }; static void io_open_work(struct lem_async *a) { struct open *o = (struct open *)a; int fd; struct stat st; fd = open(o->path, o->flags #ifdef O_CLOEXEC | O_CLOEXEC #endif , o->fd >= 0 ? o->fd : S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (fd < 0) { o->flags = -errno; return; } if ( #ifndef O_CLOXEC fcntl(fd, F_SETFD, FD_CLOEXEC) == -1 || #endif fstat(fd, &st)) { o->flags = -errno; close(fd); return; } o->fd = fd; lem_debug("st.st_mode & S_IFMT = %o", st.st_mode & S_IFMT); switch (st.st_mode & S_IFMT) { case S_IFREG: case S_IFBLK: o->flags = 0; break; case S_IFCHR: case S_IFIFO: o->flags = 1; if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { o->flags = -errno; close(fd); } break; default: o->flags = -EINVAL; break; } } static void io_open_reap(struct lem_async *a) { struct open *o = (struct open *)a; lua_State *T = o->T; int fd = o->fd; int ret = o->flags; lem_debug("ret = %d", ret); free(o); switch (ret) { case 0: file_new(T, fd, 2); break; case 1: stream_new(T, fd, 3); break; default: lem_queue(T, io_strerror(T, -ret)); return; } lem_queue(T, 1); } static int io_mode_to_flags(const char *mode) { int omode; int oflags; switch (*mode++) { case 'r': omode = O_RDONLY; oflags = 0; break; case 'w': omode = O_WRONLY; oflags = O_CREAT | O_TRUNC; break; case 'a': omode = O_WRONLY; oflags = O_CREAT | O_APPEND; break; default: return -1; } next: switch (*mode++) { case '\0': break; case '+': omode = O_RDWR; goto next; case 'b': /* this does nothing on *nix, but * don't treat it as an error */ goto next; case 'x': oflags |= O_EXCL; goto next; default: return -1; } return omode | oflags; } static int io_open(lua_State *T) { const char *path = luaL_checkstring(T, 1); int flags = io_mode_to_flags(luaL_optstring(T, 2, "r")); int perm = io_optperm(T, 3); struct open *o; if (flags < 0) return luaL_error(T, "invalid mode string"); o = lem_xmalloc(sizeof(struct open)); o->T = T; o->path = path; o->fd = perm; o->flags = flags; lem_async_do(&o->a, io_open_work, io_open_reap); lua_settop(T, 1); lua_pushvalue(T, lua_upvalueindex(1)); lua_pushvalue(T, lua_upvalueindex(2)); return lua_yield(T, 3); } /* * io.fromfd() */ struct fromfd { struct lem_async a; lua_State *T; int fd; int ret; }; static int io_socket_listening(int fd) { int val; socklen_t len = sizeof(int); if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &val, &len) == 0 && val) return 1; return 0; } static void io_fromfd_work(struct lem_async *a) { struct fromfd *ff = (struct fromfd *)a; struct stat st; if (fstat(ff->fd, &st)) { ff->ret = -errno; return; } lem_debug("st.st_mode & S_IFMT = %o", st.st_mode & S_IFMT); switch (st.st_mode & S_IFMT) { case S_IFREG: case S_IFBLK: ff->ret = 0; break; case S_IFSOCK: if (io_socket_listening(ff->fd)) { ff->ret = 2; goto nonblock; } /* fallthrough */ case S_IFCHR: case S_IFIFO: ff->ret = 1; goto nonblock; default: ff->ret = -EINVAL; break; } return; nonblock: if (fcntl(ff->fd, F_SETFL, O_NONBLOCK) == -1) ff->ret = -errno; } static void io_fromfd_reap(struct lem_async *a) { struct fromfd *ff = (struct fromfd *)a; lua_State *T = ff->T; int fd = ff->fd; int ret = ff->ret; lem_debug("ret = %d", ret); free(ff); switch (ret) { case 0: file_new(T, fd, 1); break; case 1: stream_new(T, fd, 2); break; case 2: server_new(T, fd, 3); break; default: lem_queue(T, io_strerror(T, -ret)); return; } lem_queue(T, 1); } static int io_fromfd(lua_State *T) { int fd = luaL_checkint(T, 1); struct fromfd *ff; if (fd < 0) return luaL_argerror(T, 1, "invalid fd"); ff = lem_xmalloc(sizeof(struct fromfd)); ff->T = T; ff->fd = fd; lem_async_do(&ff->a, io_fromfd_work, io_fromfd_reap); lua_settop(T, 0); lua_pushvalue(T, lua_upvalueindex(1)); lua_pushvalue(T, lua_upvalueindex(2)); lua_pushvalue(T, lua_upvalueindex(3)); return lua_yield(T, 3); } /* * io.popen() */ static const char *const io_popen_modes[] = { "r", "w", "rw", NULL }; static int io_popen(lua_State *T) { const char *cmd = luaL_checkstring(T, 1); int mode = luaL_checkoption(T, 2, "r", io_popen_modes); char *const argv[4] = { "/bin/sh", "-c", (char *)cmd, NULL }; posix_spawn_file_actions_t fa; int fd[2]; pid_t pid; int err; switch (mode) { case 0: /* "r" */ if (pipe(fd)) return io_strerror(T, errno); posix_spawn_file_actions_init(&fa); posix_spawn_file_actions_adddup2(&fa, fd[1], 1); posix_spawn_file_actions_addclose(&fa, fd[1]); break; case 1: /* "w" */ if (pipe(fd)) return io_strerror(T, errno); err = fd[0]; fd[0] = fd[1]; fd[1] = err; posix_spawn_file_actions_init(&fa); posix_spawn_file_actions_adddup2(&fa, fd[1], 0); posix_spawn_file_actions_addclose(&fa, fd[1]); break; case 2: /* "rw" */ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) return io_strerror(T, errno); posix_spawn_file_actions_init(&fa); posix_spawn_file_actions_adddup2(&fa, fd[1], 0); posix_spawn_file_actions_adddup2(&fa, fd[1], 1); posix_spawn_file_actions_addclose(&fa, fd[1]); break; } /* set our socket flags */ if (fcntl(fd[0], F_SETFD, FD_CLOEXEC) == -1 || fcntl(fd[0], F_SETFL, O_NONBLOCK) == -1) { err = errno; goto error; } err = posix_spawn(&pid, argv[0], &fa, NULL, argv, environ); lem_debug("err = %d, %s", err, strerror(err)); if (err) goto error; posix_spawn_file_actions_destroy(&fa); close(fd[1]); stream_new(T, fd[0], lua_upvalueindex(1)); lua_pushnumber(T, pid); return 2; error: posix_spawn_file_actions_destroy(&fa); close(fd[0]); close(fd[1]); return io_strerror(T, err); } /* * io.streamfile() */ struct streamfile { struct lem_async a; lua_State *T; const char *filename; int pipe[2]; int file; }; static void io_streamfile_worker(struct lem_async *a) { struct streamfile *s = (struct streamfile *)a; int file = s->file; int pipe = s->pipe[1]; #ifdef __FreeBSD__ sendfile(file, pipe, 0, 0, NULL, NULL, SF_SYNC); #else #ifdef __APPLE__ off_t len = 0; sendfile(file, pipe, 0, &len, NULL, 0); #else while (sendfile(pipe, file, NULL, 2147483647) > 0); #endif #endif close(file); close(pipe); } static void io_streamfile_open(struct lem_async *a) { struct streamfile *s = (struct streamfile *)a; int file = open(s->filename, #ifdef O_CLOEXEC O_CLOEXEC | #endif O_RDONLY); if (file < 0) { s->file = -errno; return; } #ifndef O_CLOEXEC if (fcntl(file, F_SETFD, FD_CLOEXEC) == -1) { s->file = -errno; goto err1; } #endif if (socketpair(AF_UNIX, #ifdef SOCK_CLOEXEC SOCK_CLOEXEC | #endif SOCK_STREAM, 0, s->pipe)) { s->file = -errno; goto err1; } if ( #ifndef SOCK_CLOEXEC fcntl(s->pipe[1], F_SETFD, FD_CLOEXEC) == -1 || fcntl(s->pipe[0], F_SETFD, FD_CLOEXEC) == -1 || #endif shutdown(s->pipe[0], SHUT_WR)) { s->file = -errno; goto err2; } if (fcntl(s->pipe[0], F_SETFL, O_NONBLOCK) == -1) { s->file = -errno; goto err2; } s->file = file; return; err2: close(s->pipe[0]); close(s->pipe[1]); err1: close(file); } static void io_streamfile_reap(struct lem_async *a) { struct streamfile *s = (struct streamfile *)a; lua_State *T = s->T; int ret = s->file; if (ret < 0) { free(s); lem_queue(T, io_strerror(T, -ret)); return; } lem_debug("s->file = %d, s->pipe[0] = %d, s->pipe[1] = %d", ret, s->pipe[0], s->pipe[1]); lem_async_do(&s->a, io_streamfile_worker, NULL); stream_new(T, s->pipe[0], 2); lem_queue(T, 1); } static int io_streamfile(lua_State *T) { const char *filename = lua_tostring(T, 1); struct streamfile *s = lem_xmalloc(sizeof(struct streamfile)); s->T = T; s->filename = filename; lem_async_do(&s->a, io_streamfile_open, io_streamfile_reap); lua_settop(T, 1); lua_pushvalue(T, lua_upvalueindex(1)); return lua_yield(T, 2); } static void push_stdstream(lua_State *L, int fd) { struct stream *s; /* make the socket non-blocking */ fcntl(fd, F_SETFL, O_NONBLOCK); s = stream_new(L, fd, -2); /* don't close this in __gc(), but make it blocking again */ s->open = 2; } int luaopen_lem_io_core(lua_State *L) { /* create module table */ lua_newtable(L); /* create File metatable */ lua_newtable(L); /* mt.__index = mt */ lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); /* mt.__gc = */ lua_pushcfunction(L, file_gc); lua_setfield(L, -2, "__gc"); /* mt.closed = */ lua_pushcfunction(L, file_closed); lua_setfield(L, -2, "closed"); /* mt.close = */ lua_pushcfunction(L, file_close); lua_setfield(L, -2, "close"); /* mt.readp = */ lua_pushcfunction(L, file_readp); lua_setfield(L, -2, "readp"); /* mt.write = */ lua_pushcfunction(L, file_write); lua_setfield(L, -2, "write"); /* mt.size = */ lua_pushcfunction(L, file_size); lua_setfield(L, -2, "size"); /* mt.seek = */ lua_pushcfunction(L, file_seek); lua_setfield(L, -2, "seek"); /* mt.lock = */ lua_pushcfunction(L, file_lock); lua_setfield(L, -2, "lock"); /* insert table */ lua_setfield(L, -2, "File"); /* create Stream metatable */ lua_newtable(L); /* mt.__index = mt */ lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); /* mt.__gc = */ lua_pushcfunction(L, stream_gc); lua_setfield(L, -2, "__gc"); /* mt.closed = */ lua_pushcfunction(L, stream_closed); lua_setfield(L, -2, "closed"); /* mt.close = */ lua_pushcfunction(L, stream_close); lua_setfield(L, -2, "close"); /* mt.readp = */ lua_pushcfunction(L, stream_readp); lua_setfield(L, -2, "readp"); /* mt.write = */ lua_pushcfunction(L, stream_write); lua_setfield(L, -2, "write"); #ifdef TCP_CORK /* mt.cork = */ lua_pushcfunction(L, stream_cork); lua_setfield(L, -2, "cork"); /* mt.uncork = */ lua_pushcfunction(L, stream_uncork); lua_setfield(L, -2, "uncork"); #endif /* mt.getpeer = */ lua_pushcfunction(L, stream_getpeer); lua_setfield(L, -2, "getpeer"); /* mt.sendfile = */ lua_pushcfunction(L, stream_sendfile); lua_setfield(L, -2, "sendfile"); /* insert io.stdin stream */ push_stdstream(L, STDIN_FILENO); lua_setfield(L, -3, "stdin"); /* insert io.stdout stream */ push_stdstream(L, STDOUT_FILENO); lua_setfield(L, -3, "stdout"); /* insert io.stderr stream */ push_stdstream(L, STDERR_FILENO); lua_setfield(L, -3, "stderr"); /* insert table */ lua_setfield(L, -2, "Stream"); /* create Server metatable */ lua_newtable(L); /* mt.__index = mt */ lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); /* mt.__gc = */ lua_pushcfunction(L, server_close); lua_setfield(L, -2, "__gc"); /* mt.closed = */ lua_pushcfunction(L, server_closed); lua_setfield(L, -2, "closed"); /* mt.busy = */ lua_pushcfunction(L, server_busy); lua_setfield(L, -2, "busy"); /* mt.close = */ lua_pushcfunction(L, server_close); lua_setfield(L, -2, "close"); /* mt.interrupt = */ lua_pushcfunction(L, server_interrupt); lua_setfield(L, -2, "interrupt"); /* mt.accept = */ lua_getfield(L, -2, "Stream"); /* upvalue 1 = Stream */ lua_pushcclosure(L, server_accept, 1); lua_setfield(L, -2, "accept"); /* mt.autospawn = */ lua_getfield(L, -2, "Stream"); /* upvalue 1 = Stream */ lua_pushcclosure(L, server_autospawn, 1); lua_setfield(L, -2, "autospawn"); /* insert table */ lua_setfield(L, -2, "Server"); /* insert open function */ lua_getfield(L, -1, "File"); /* upvalue 1 = File */ lua_getfield(L, -2, "Stream"); /* upvalue 2 = Stream */ lua_pushcclosure(L, io_open, 2); lua_setfield(L, -2, "open"); /* insert the fromfd function */ lua_getfield(L, -1, "File"); /* upvalue 1 = File */ lua_getfield(L, -2, "Stream"); /* upvalue 2 = Stream */ lua_getfield(L, -3, "Server"); /* upvalue 3 = Server */ lua_pushcclosure(L, io_fromfd, 3); lua_setfield(L, -2, "fromfd"); /* insert popen function */ lua_getfield(L, -1, "Stream"); /* upvalue 1 = Stream */ lua_pushcclosure(L, io_popen, 1); lua_setfield(L, -2, "popen"); /* insert streamfile function */ lua_getfield(L, -1, "Stream"); /* upvalue 1 = Stream */ lua_pushcclosure(L, io_streamfile, 1); lua_setfield(L, -2, "streamfile"); /* create tcp table */ lua_createtable(L, 0, 0); /* insert the connect function */ lua_getfield(L, -2, "Stream"); /* upvalue 1 = Stream */ lua_pushcclosure(L, tcp_connect, 1); lua_setfield(L, -2, "connect"); /* insert the listen4 function */ lua_getfield(L, -2, "Server"); /* upvalue 1 = Server */ lua_pushcclosure(L, tcp_listen4, 1); lua_setfield(L, -2, "listen4"); /* insert the listen6 function */ lua_getfield(L, -2, "Server"); /* upvalue 1 = Server */ lua_pushcclosure(L, tcp_listen6, 1); lua_setfield(L, -2, "listen6"); /* insert the tcp table */ lua_setfield(L, -2, "tcp"); /* create unix table */ lua_createtable(L, 0, 0); /* insert the connect function */ lua_getfield(L, -2, "Stream"); /* upvalue 1 = Stream */ lua_pushcclosure(L, unix_connect, 1); lua_setfield(L, -2, "connect"); /* insert the listen function */ lua_getfield(L, -2, "Server"); /* upvalue 1 = Server */ lua_pushcclosure(L, unix_listen, 1); lua_setfield(L, -2, "listen"); /* insert the unix table */ lua_setfield(L, -2, "unix"); return 1; }