/*
* This file is part of LEM, a Lua Event Machine.
* Copyright 2012-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 <http://www.gnu.org/licenses/>.
*/
struct file {
struct lem_async a;
lua_State *T;
int fd;
int ret;
union {
struct {
struct lem_parser *p;
} readp;
struct {
const char *str;
size_t len;
int idx;
} write;
struct {
off_t val;
} size;
struct {
off_t offset;
int whence;
} seek;
struct {
off_t start;
off_t len;
short type;
} lock;
};
struct lem_inputbuf buf;
};
struct file_gc {
struct lem_async a;
int fd;
};
static struct file *
file_new(lua_State *T, int fd, int mt)
{
struct file *f;
/* create userdata and set the metatable */
f = lua_newuserdata(T, sizeof(struct file));
lua_pushvalue(T, mt);
lua_setmetatable(T, -2);
/* initialize userdata */
f->T = NULL;
f->fd = fd;
lem_inputbuf_init(&f->buf);
return f;
}
/*
* file:__gc() metamethod
*/
static void
file_gc_work(struct lem_async *a)
{
struct file_gc *gc = (struct file_gc *)a;
close(gc->fd);
}
static int
file_gc(lua_State *T)
{
struct file *f = lua_touserdata(T, 1);
lem_debug("collecting %p, fd = %d", f, f->fd);
if (f->fd >= 0) {
struct file_gc *gc = lem_xmalloc(sizeof(struct file_gc));
gc->fd = f->fd;
f->fd = -1;
lem_async_do(&gc->a, file_gc_work, NULL);
}
return 0;
}
static int
file_closed(lua_State *T)
{
struct file *f;
luaL_checktype(T, 1, LUA_TUSERDATA);
f = lua_touserdata(T, 1);
lua_pushboolean(T, f->fd < 0);
return 1;
}
/*
* file:close() method
*/
static void
file_close_work(struct lem_async *a)
{
struct file *f = (struct file *)a;
if (close(f->fd))
f->ret = errno;
else
f->ret = 0;
}
static void
file_close_reap(struct lem_async *a)
{
struct file *f = (struct file *)a;
lua_State *T = f->T;
f->T = NULL;
f->fd = -1;
if (f->ret) {
lem_queue(T, io_strerror(T, f->ret));
return;
}
lua_pushboolean(T, 1);
lem_queue(T, 1);
}
static int
file_close(lua_State *T)
{
struct file *f;
luaL_checktype(T, 1, LUA_TUSERDATA);
f = lua_touserdata(T, 1);
if (f->fd < 0)
return io_closed(T);
if (f->T != NULL)
return io_busy(T);
f->T = T;
lem_async_do(&f->a, file_close_work, file_close_reap);
lua_settop(T, 1);
return lua_yield(T, 1);
}
/*
* file:readp() method
*/
static void
file_readp_work(struct lem_async *a)
{
struct file *f = (struct file *)a;
ssize_t bytes = read(f->fd, f->buf.buf + f->buf.end,
LEM_INPUTBUF_SIZE - f->buf.end);
lem_debug("read %ld bytes from %d", bytes, f->fd);
if (bytes > 0) {
f->ret = 0;
f->buf.end += bytes;
} else if (bytes == 0) {
f->ret = -1;
} else {
close(f->fd);
f->fd = -1;
f->ret = errno;
}
}
static void
file_readp_reap(struct lem_async *a)
{
struct file *f = (struct file *)a;
lua_State *T = f->T;
int ret;
if (f->ret) {
enum lem_preason res = f->ret < 0 ? LEM_PCLOSED : LEM_PERROR;
f->T = NULL;
if (f->readp.p->destroy &&
(ret = f->readp.p->destroy(T, &f->buf, res)) > 0) {
lem_queue(T, ret);
return;
}
lua_pushnil(T);
if (res == LEM_PCLOSED)
lua_pushliteral(T, "eof");
else
lua_pushstring(T, strerror(errno));
lem_queue(T, 2);
return;
}
ret = f->readp.p->process(T, &f->buf);
if (ret > 0) {
f->T = NULL;
lem_queue(T, ret);
return;
}
lem_async_run(&f->a);
}
static int
file_readp(lua_State *T)
{
struct file *f;
struct lem_parser *p;
int ret;
luaL_checktype(T, 1, LUA_TUSERDATA);
ret = lua_type(T, 2);
if (ret != LUA_TUSERDATA && ret != LUA_TLIGHTUSERDATA)
return luaL_argerror(T, 2, "expected userdata");
f = lua_touserdata(T, 1);
if (f->fd < 0)
return io_closed(T);
if (f->T != NULL)
return io_busy(T);
p = lua_touserdata(T, 2);
if (p->init)
p->init(T, &f->buf);
ret = p->process(T, &f->buf);
if (ret > 0)
return ret;
f->T = T;
f->readp.p = p;
lem_async_do(&f->a, file_readp_work, file_readp_reap);
return lua_yield(T, lua_gettop(T));
}
/*
* file:write() method
*/
static void
file_write_work(struct lem_async *a)
{
struct file *f = (struct file *)a;
ssize_t bytes = write(f->fd, f->write.str, f->write.len);
if (bytes < 0)
f->ret = errno;
else
f->ret = 0;
}
static void
file_write_reap(struct lem_async *a)
{
struct file *f = (struct file *)a;
lua_State *T = f->T;
int top;
if (f->ret) {
f->T = NULL;
lem_queue(T, io_strerror(T, f->ret));
return;
}
top = lua_gettop(T);
do {
if (f->write.idx == top) {
f->T = NULL;
lua_pushboolean(T, 1);
lem_queue(T, 1);
return;
}
f->write.str = lua_tolstring(T, ++f->write.idx, &f->write.len);
} while (f->write.len == 0);
lem_async_run(&f->a);
}
static int
file_write(lua_State *T)
{
struct file *f;
const char *str;
size_t len;
int idx;
int i;
int top;
luaL_checktype(T, 1, LUA_TUSERDATA);
top = lua_gettop(T);
idx = 1;
do {
str = luaL_checklstring(T, ++idx, &len);
} while (len == 0 && idx < top);
for (i = idx+1; i <= top; i++)
(void)luaL_checkstring(T, i);
f = lua_touserdata(T, 1);
if (f->fd < 0)
return io_closed(T);
if (f->T != NULL)
return io_busy(T);
if (len == 0) {
lua_pushboolean(T, 1);
return 1;
}
f->T = T;
f->write.str = str;
f->write.len = len;
f->write.idx = idx;
lem_async_do(&f->a, file_write_work, file_write_reap);
return lua_yield(T, top);
}
/*
* file:size() method
*/
static void
file_size_work(struct lem_async *a)
{
struct file *f = (struct file *)a;
struct stat st;
if (fstat(f->fd, &st)) {
f->ret = errno;
} else {
f->ret = 0;
f->size.val = st.st_size;
}
}
static void
file_size_reap(struct lem_async *a)
{
struct file *f = (struct file *)a;
lua_State *T = f->T;
f->T = NULL;
if (f->ret) {
lem_queue(T, io_strerror(T, f->ret));
return;
}
lua_pushinteger(T, f->size.val);
lem_queue(T, 1);
}
static int
file_size(lua_State *T)
{
struct file *f;
luaL_checktype(T, 1, LUA_TUSERDATA);
f = lua_touserdata(T, 1);
if (f->fd < 0)
return io_closed(T);
if (f->T != NULL)
return io_busy(T);
f->T = T;
lem_async_do(&f->a, file_size_work, file_size_reap);
lua_settop(T, 1);
return lua_yield(T, 1);
}
/*
* file:seek() method
*/
static void
file_seek_work(struct lem_async *a)
{
struct file *f = (struct file *)a;
off_t bytes = lseek(f->fd, f->seek.offset, f->seek.whence);
if (bytes == (off_t)-1) {
f->ret = errno;
} else {
f->seek.offset = bytes;
f->ret = 0;
}
}
static void
file_seek_reap(struct lem_async *a)
{
struct file *f = (struct file *)a;
lua_State *T = f->T;
f->T = NULL;
if (f->ret) {
lem_queue(T, io_strerror(T, f->ret));
return;
}
lua_pushinteger(T, f->seek.offset);
lem_queue(T, 1);
}
static int
file_seek(lua_State *T)
{
static const int mode[] = { SEEK_SET, SEEK_CUR, SEEK_END };
static const char *const modenames[] = { "set", "cur", "end", NULL };
struct file *f;
int op;
lua_Number offset;
luaL_checktype(T, 1, LUA_TUSERDATA);
op = luaL_checkoption(T, 2, "cur", modenames);
offset = luaL_optnumber(T, 3, 0.);
f = lua_touserdata(T, 1);
f->seek.offset = (off_t)offset;
luaL_argcheck(T, (lua_Number)f->seek.offset == offset, 3,
"not an integer in proper range");
if (f->fd < 0)
return io_closed(T);
if (f->T != NULL)
return io_busy(T);
/* flush input buffer */
lem_inputbuf_init(&f->buf);
f->T = T;
f->seek.whence = mode[op];
lem_async_do(&f->a, file_seek_work, file_seek_reap);
lua_settop(T, 1);
return lua_yield(T, 1);
}
/*
* file:lock() method
*/
static void
file_lock_work(struct lem_async *a)
{
struct file *f = (struct file *)a;
struct flock fl = {
.l_type = f->lock.type,
.l_whence = SEEK_SET,
.l_start = f->lock.start,
.l_len = f->lock.len,
};
if (fcntl(f->fd, F_SETLK, &fl) == -1)
f->ret = errno;
else
f->ret = 0;
}
static void
file_lock_reap(struct lem_async *a)
{
struct file *f = (struct file *)a;
lua_State *T = f->T;
f->T = NULL;
if (f->ret) {
lem_queue(T, io_strerror(T, f->ret));
return;
}
lua_pushboolean(T, 1);
lem_queue(T, 1);
}
static int
file_lock(lua_State *T)
{
static const short mode[] = { F_RDLCK, F_WRLCK, F_UNLCK };
static const char *const modenames[] = { "r", "w", "u", NULL };
struct file *f;
int op;
lua_Number start;
lua_Number len;
luaL_checktype(T, 1, LUA_TUSERDATA);
op = luaL_checkoption(T, 2, NULL, modenames);
start = luaL_optnumber(T, 3, 0);
len = luaL_optnumber(T, 4, 0);
f = lua_touserdata(T, 1);
f->lock.start = (off_t)start;
luaL_argcheck(T, (lua_Number)f->lock.start == start, 3,
"not an integer in proper range");
f->lock.len = (off_t)len;
luaL_argcheck(T, (lua_Number)f->lock.len == len, 4,
"not an integer in proper range");
if (f->fd < 0)
return io_closed(T);
if (f->T != NULL)
return io_busy(T);
f->T = T;
f->lock.type = mode[op];
lem_async_do(&f->a, file_lock_work, file_lock_reap);
lua_settop(T, 1);
return lua_yield(T, 1);
}