diff options
author | Emil Renner Berthing <esmil@mailme.dk> | 2012-12-16 23:27:27 +0100 |
---|---|---|
committer | Emil Renner Berthing <esmil@mailme.dk> | 2012-12-18 17:35:32 +0100 |
commit | 86ccbbee7d038bcd9dfa2eb56551a383d84b908a (patch) | |
tree | 86c8124fe6af400e5e386186a5146a26cad07348 | |
parent | 089b048e2e0f872a653b7b353f66f300578377aa (diff) | |
download | lem-86ccbbee7d038bcd9dfa2eb56551a383d84b908a.tar.gz lem-86ccbbee7d038bcd9dfa2eb56551a383d84b908a.tar.xz lem-86ccbbee7d038bcd9dfa2eb56551a383d84b908a.zip |
lfs: added
-rw-r--r-- | Makefile.in | 2 | ||||
-rw-r--r-- | lem/lfs.lua | 54 | ||||
-rw-r--r-- | lem/lfs/core.c | 777 | ||||
-rwxr-xr-x | test/lfs.lua | 97 |
4 files changed, 930 insertions, 0 deletions
diff --git a/Makefile.in b/Makefile.in index 77b80aa..a9e7901 100644 --- a/Makefile.in +++ b/Makefile.in @@ -27,12 +27,14 @@ llibs = \ lem/repl.lua \ lem/io.lua \ lem/io/queue.lua \ + lem/lfs.lua \ lem/http.lua \ lem/hathaway.lua clibs = \ lem/utils.so \ lem/io/core.so \ + lem/lfs/core.so \ lem/http/core.so ifdef V diff --git a/lem/lfs.lua b/lem/lfs.lua new file mode 100644 index 0000000..ea4e317 --- /dev/null +++ b/lem/lfs.lua @@ -0,0 +1,54 @@ +-- +-- This file is part of LEM, a Lua Event Machine. +-- Copyright 2011-2012 Emil Renner Berthing +-- +-- LEM is free software: you can redistribute it 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. +-- +-- 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 General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with LEM. If not, see <http://www.gnu.org/licenses/>. +-- + +local lfs = require 'lem.lfs.core' + +function lfs.lock(file, ...) + return file:lock(...) +end + +function lfs.unlock(file, ...) + return file:lock('u', ...) +end + +function lfs.setmode() + return 'binary' +end + +do + local setmetatable, remove, link = setmetatable, lfs.remove, lfs.link + + local Lock = { __index = true, free = true } + Lock.__index = Lock + + function Lock:free() + return remove(self.filename) + end + + function lfs.lock_dir(path) + local filename = path .. '/lockfile.lfs' + local ok, err = link('lock', filename, true) + if not ok then return ok, err end + + return setmetatable({ filename = filename }, Lock) + end +end + +return lfs + +-- vim: ts=2 sw=2 noet: diff --git a/lem/lfs/core.c b/lem/lfs/core.c new file mode 100644 index 0000000..6a95927 --- /dev/null +++ b/lem/lfs/core.c @@ -0,0 +1,777 @@ +/* + * This file is part of LEM, a Lua Event Machine. + * Copyright 2011-2012 Emil Renner Berthing + * + * LEM is free software: you can redistribute it 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. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LEM. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <dirent.h> +#include <utime.h> +#include <assert.h> + +#include <lem.h> + +static int +lfs_closed(lua_State *T) +{ + lua_pushnil(T); + lua_pushliteral(T, "closed"); + return 2; +} + +static int +lfs_busy(lua_State *T) +{ + lua_pushnil(T); + lua_pushliteral(T, "busy"); + return 2; +} + +static int +lfs_strerror(lua_State *T, int err) +{ + lua_pushnil(T); + lua_pushstring(T, strerror(err)); + return 2; +} + +struct lfs_pathop { + struct lem_async a; + union { + const char *path; + int ret; + }; +}; + +static void +lfs_pathop_reap(struct lem_async *a) +{ + struct lfs_pathop *po = (struct lfs_pathop *)a; + lua_State *T = po->a.T; + int ret = po->ret; + + free(po); + if (ret) { + lem_queue(T, lfs_strerror(T, ret)); + return; + } + + lua_pushboolean(T, 1); + lem_queue(T, 1); +} + +/* + * lfs.chdir() + */ +static void +lfs_chdir_work(struct lem_async *a) +{ + struct lfs_pathop *po = (struct lfs_pathop *)a; + + if (chdir(po->path)) + po->ret = errno; + else + po->ret = 0; +} + +static int +lfs_chdir(lua_State *T) +{ + const char *path = luaL_checkstring(T, 1); + struct lfs_pathop *po; + + po = lem_xmalloc(sizeof(struct lfs_pathop)); + po->path = path; + lem_async_do(&po->a, T, lfs_chdir_work, lfs_pathop_reap); + + lua_settop(T, 1); + return lua_yield(T, 1); +} + +/* + * lfs.mkdir() + */ +static void +lfs_mkdir_work(struct lem_async *a) +{ + struct lfs_pathop *po = (struct lfs_pathop *)a; + + if (mkdir(po->path, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | + S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH )) + po->ret = errno; + else + po->ret = 0; +} + +static int +lfs_mkdir(lua_State *T) +{ + const char *path = luaL_checkstring(T, 1); + struct lfs_pathop *po; + + po = lem_xmalloc(sizeof(struct lfs_pathop)); + po->path = path; + lem_async_do(&po->a, T, lfs_mkdir_work, lfs_pathop_reap); + + lua_settop(T, 1); + return lua_yield(T, 1); +} + +/* + * lfs.rmdir() + */ +static void +lfs_rmdir_work(struct lem_async *a) +{ + struct lfs_pathop *po = (struct lfs_pathop *)a; + + if (rmdir(po->path)) + po->ret = errno; + else + po->ret = 0; +} + +static int +lfs_rmdir(lua_State *T) +{ + const char *path = luaL_checkstring(T, 1); + struct lfs_pathop *po; + + po = lem_xmalloc(sizeof(struct lfs_pathop)); + po->path = path; + lem_async_do(&po->a, T, lfs_rmdir_work, lfs_pathop_reap); + + lua_settop(T, 1); + return lua_yield(T, 1); +} + +/* + * lfs.remove() + */ +static void +lfs_remove_work(struct lem_async *a) +{ + struct lfs_pathop *po = (struct lfs_pathop *)a; + + if (unlink(po->path)) + po->ret = errno; + else + po->ret = 0; +} + +static int +lfs_remove(lua_State *T) +{ + const char *path = luaL_checkstring(T, 1); + struct lfs_pathop *po; + + po = lem_xmalloc(sizeof(struct lfs_pathop)); + po->path = path; + lem_async_do(&po->a, T, lfs_remove_work, lfs_pathop_reap); + + lua_settop(T, 1); + return lua_yield(T, 1); +} + +/* + * lfs.link() and lfs.rename() + */ +struct lfs_twoop { + struct lem_async a; + union { + struct { + const char *old; + const char *new; + }; + int ret; + }; +}; + +static void +lfs_twoop_reap(struct lem_async *a) +{ + struct lfs_twoop *to = (struct lfs_twoop *)a; + lua_State *T = to->a.T; + int ret = to->ret; + + free(to); + if (ret) { + lem_queue(T, lfs_strerror(T, ret)); + return; + } + + lua_pushboolean(T, 1); + lem_queue(T, 1); +} + +static void +lfs_link_work(struct lem_async *a) +{ + struct lfs_twoop *to = (struct lfs_twoop *)a; + + if (link(to->old, to->new)) + to->ret = errno; + else + to->ret = 0; +} + +static void +lfs_symlink_work(struct lem_async *a) +{ + struct lfs_twoop *to = (struct lfs_twoop *)a; + + if (symlink(to->old, to->new)) + to->ret = errno; + else + to->ret = 0; +} + +static int +lfs_link(lua_State *T) +{ + const char *old = luaL_checkstring(T, 1); + const char *new = luaL_checkstring(T, 2); + int symlink = lua_toboolean(T, 3); + struct lfs_twoop *to; + + to = lem_xmalloc(sizeof(struct lfs_twoop)); + to->old = old; + to->new = new; + lem_async_do(&to->a, T, symlink ? lfs_symlink_work : lfs_link_work, + lfs_twoop_reap); + + lua_settop(T, 2); + return lua_yield(T, 2); +} + +static void +lfs_rename_work(struct lem_async *a) +{ + struct lfs_twoop *to = (struct lfs_twoop *)a; + + if (rename(to->old, to->new)) + to->ret = errno; + else + to->ret = 0; +} + +static int +lfs_rename(lua_State *T) +{ + const char *old = luaL_checkstring(T, 1); + const char *new = luaL_checkstring(T, 2); + struct lfs_twoop *to; + + to = lem_xmalloc(sizeof(struct lfs_twoop)); + to->old = old; + to->new = new; + lem_async_do(&to->a, T, lfs_rename_work, lfs_twoop_reap); + + lua_settop(T, 2); + return lua_yield(T, 2); +} + +/* + * lfs.attributes() and lfs.symlinkattributes() + */ +struct lfs_attr { + struct lem_async a; + struct stat st; + const char *path; + int op; + int ret; +}; + +static void +lfs_stat_work(struct lem_async *a) +{ + struct lfs_attr *at = (struct lfs_attr *)a; + + if (stat(at->path, &at->st)) + at->ret = errno; + else + at->ret = 0; +} + +static void +lfs_lstat_work(struct lem_async *a) +{ + struct lfs_attr *at = (struct lfs_attr *)a; + + if (lstat(at->path, &at->st)) + at->ret = errno; + else + at->ret = 0; +} + +static const char *const lfs_attrs[] = { + "dev", + "ino", + "mode", + "permissions", + "nlink", + "uid", + "gid", + "rdev", + "size", + "blksize", + "blocks", + "access", + "modification", + "change", + "*", NULL }; + +static void +lfs_attr_pushmode(lua_State *T, mode_t mode) +{ + if (S_ISREG(mode)) + lua_pushliteral(T, "file"); + else if (S_ISDIR(mode)) + lua_pushliteral(T, "directory"); + else if (S_ISLNK(mode)) + lua_pushliteral(T, "link"); + else if (S_ISSOCK(mode)) + lua_pushliteral(T, "socket"); + else if (S_ISFIFO(mode)) + lua_pushliteral(T, "named pipe"); + else if (S_ISCHR(mode)) + lua_pushliteral(T, "char device"); + else if (S_ISBLK(mode)) + lua_pushliteral(T, "block device"); + else + lua_pushliteral(T, "other"); +} + +static void +lfs_attr_pushperm(lua_State *T, mode_t mode) +{ + static const char sign[3] = { 'r', 'w', 'x' }; + char str[9]; + mode_t mask = 0400; + int i; + + for (i = 0; i < 9; i++) { + if (mode & mask) + str[i] = sign[i % 3]; + else + str[i] = '-'; + mask >>= 1; + } + + lua_pushlstring(T, str, 9); +} + +static void +lfs_attr_push(lua_State *T, struct stat *st, int i) +{ + switch (i) { + case 0: lua_pushnumber(T, st->st_dev); break; + case 1: lua_pushnumber(T, st->st_ino); break; + case 2: lfs_attr_pushmode(T, st->st_mode); break; + case 3: lfs_attr_pushperm(T, st->st_mode); break; + case 4: lua_pushnumber(T, st->st_nlink); break; + case 5: lua_pushnumber(T, st->st_uid); break; + case 6: lua_pushnumber(T, st->st_gid); break; + case 7: lua_pushnumber(T, st->st_rdev); break; + case 8: lua_pushnumber(T, st->st_size); break; + case 9: lua_pushnumber(T, st->st_blksize); break; + case 10: lua_pushnumber(T, st->st_blocks); break; + case 11: lua_pushnumber(T, st->st_atime); break; + case 12: lua_pushnumber(T, st->st_mtime); break; + case 13: lua_pushnumber(T, st->st_ctime); break; + } +} + +static void +lfs_attr_reap(struct lem_async *a) +{ + struct lfs_attr *at = (struct lfs_attr *)a; + lua_State *T = at->a.T; + struct stat *st = &at->st; + + if (at->ret) { + lem_queue(T, lfs_strerror(T, at->ret)); + free(at); + return; + } + + if (at->op == 14) { + int i; + + lua_createtable(T, 0, 14); + for (i = 0; i < 14; i++) { + lfs_attr_push(T, st, i); + lua_setfield(T, -2, lfs_attrs[i]); + } + } else + lfs_attr_push(T, st, at->op); + + free(at); + lem_queue(T, 1); +} + +static int +lfs_attr(lua_State *T) +{ + const char *path = luaL_checkstring(T, 1); + int op = luaL_checkoption(T, 2, "*", lfs_attrs); + struct lfs_attr *at; + + at = lem_xmalloc(sizeof(struct lfs_attr)); + at->path = path; + at->op = op; + lem_async_do(&at->a, T, lfs_stat_work, lfs_attr_reap); + + lua_settop(T, 1); + return lua_yield(T, 1); +} + +static int +lfs_symattr(lua_State *T) +{ + const char *path = luaL_checkstring(T, 1); + struct lfs_attr *at; + + at = lem_xmalloc(sizeof(struct lfs_attr)); + at->path = path; + lem_async_do(&at->a, T, lfs_lstat_work, lfs_attr_reap); + + lua_settop(T, 1); + return lua_yield(T, 1); +} + +/* + * lfs.touch() + */ +struct lfs_touch { + struct lem_async a; + union { + struct { + struct utimbuf utb; + struct utimbuf *buf; + const char *path; + }; + int ret; + }; +}; + +static void +lfs_touch_work(struct lem_async *a) +{ + struct lfs_touch *t = (struct lfs_touch *)a; + + if (utime(t->path, t->buf)) + t->ret = errno; + else + t->ret = 0; +} + +static void +lfs_touch_reap(struct lem_async *a) +{ + struct lfs_touch *t = (struct lfs_touch *)a; + lua_State *T = t->a.T; + int ret = t->ret; + + free(t); + if (ret) { + lem_queue(T, lfs_strerror(T, ret)); + return; + } + + lua_pushboolean(T, 1); + lem_queue(T, 1); +} + +static int +lfs_touch(lua_State *T) +{ + const char *path = luaL_checkstring(T, 1); + struct lfs_touch *t; + + t = lem_xmalloc(sizeof(struct lfs_touch)); + t->path = path; + if (lua_gettop(T) == 1) { + t->buf = NULL; + } else { + t->utb.actime = luaL_optnumber(T, 2, 0); + t->utb.modtime = luaL_optnumber(T, 3, t->utb.actime); + t->buf = &t->utb; + } + lem_async_do(&t->a, T, lfs_touch_work, lfs_touch_reap); + + lua_settop(T, 1); + return lua_yield(T, 1); +} + +/* + * lfs.currentdir() + */ +static int +lfs_currentdir(lua_State *T) +{ + char stbuf[128]; + size_t len = 128; + char *buf = NULL; + char *path; + + path = getcwd(stbuf, len); + while (path == NULL && errno == ERANGE) { + free(buf); + len *= 2; + buf = lem_xmalloc(len); + path = getcwd(buf, len); + } + + if (path == NULL) { + free(buf); + return lfs_strerror(T, errno); + } + + lua_pushstring(T, path); + free(buf); + return 1; +} + +/* + * dir + */ +struct lfs_dir { + struct lem_async a; + DIR *handle; + struct dirent *entry; + union { + const char *path; + int ret; + }; +}; + +/* + * dir:__gc() + */ +static int +lfs_dir_gc(lua_State *T) +{ + struct lfs_dir *d = lua_touserdata(T, 1); + + if (d->handle != NULL) + (void)closedir(d->handle); + + return 0; +} + +/* + * dir:close() + */ +static int +lfs_dir_close(lua_State *T) +{ + struct lfs_dir *d; + int ret; + + luaL_checktype(T, 1, LUA_TUSERDATA); + d = lua_touserdata(T, 1); + if (d->handle == NULL) + return lfs_closed(T); + if (d->a.T != NULL) + return lfs_busy(T); + + ret = closedir(d->handle); + d->handle = NULL; + if (ret) + return lfs_strerror(T, errno); + + lua_pushboolean(T, 1); + return 1; +} + +/* + * dir:next() + */ +static void +lfs_dir_next_work(struct lem_async *a) +{ + struct lfs_dir *d = (struct lfs_dir *)a; + + errno = 0; + d->entry = readdir(d->handle); + d->ret = errno; + + if (d->entry == NULL) { + int ret = closedir(d->handle); + d->handle = NULL; + if (ret) + d->ret = errno; + } +} + +static void +lfs_dir_next_reap(struct lem_async *a) +{ + struct lfs_dir *d = (struct lfs_dir *)a; + lua_State *T = d->a.T; + + d->a.T = NULL; + if (d->ret) { + lem_queue(T, lfs_strerror(T, d->ret)); + return; + } + + if (d->entry == NULL) + lua_pushnil(T); + else + lua_pushstring(T, d->entry->d_name); + lem_queue(T, 1); +} + +static int +lfs_dir_next(lua_State *T) +{ + struct lfs_dir *d; + + luaL_checktype(T, 1, LUA_TUSERDATA); + d = lua_touserdata(T, 1); + if (d->handle == NULL) + return lfs_closed(T); + if (d->a.T != NULL) + return lfs_busy(T); + + lem_async_do(&d->a, T, lfs_dir_next_work, lfs_dir_next_reap); + + lua_settop(T, 1); + return lua_yield(T, 1); +} + +/* + * lfs.dir() + */ +static void +lfs_dir_work(struct lem_async *a) +{ + struct lfs_dir *d = (struct lfs_dir *)a; + + d->handle = opendir(d->path); + if (d->handle == NULL) + d->ret = errno; + else + d->ret = 0; +} + +static void +lfs_dir_reap(struct lem_async *a) +{ + struct lfs_dir *d = (struct lfs_dir *)a; + lua_State *T = d->a.T; + + d->a.T = NULL; + if (d->ret) + lem_queue(T, lfs_strerror(T, d->ret)); + else + lem_queue(T, 2); +} + +static int +lfs_dir(lua_State *T) +{ + const char *path = luaL_checkstring(T, 1); + struct lfs_dir *d; + + lua_settop(T, 1); + lua_pushvalue(T, lua_upvalueindex(1)); + + /* create dir object and set metatable */ + d = lua_newuserdata(T, sizeof(struct lfs_dir)); + lua_pushvalue(T, lua_upvalueindex(2)); + lua_setmetatable(T, -2); + + d->handle = NULL; + d->path = path; + lem_async_do(&d->a, T, lfs_dir_work, lfs_dir_reap); + + return lua_yield(T, 3); +} + +int +luaopen_lem_lfs_core(lua_State *L) +{ + /* create module table */ + lua_newtable(L); + + /* push dir:next() method */ + lua_pushcfunction(L, lfs_dir_next); + + /* create dir object metatable */ + lua_newtable(L); + /* mt.__index = mt */ + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + /* mt.__gc = <lfs_dir_gc> */ + lua_pushcfunction(L, lfs_dir_gc); + lua_setfield(L, -2, "__gc"); + /* mt.close = <lfs_dir_close> */ + lua_pushcfunction(L, lfs_dir_close); + lua_setfield(L, -2, "close"); + /* mt.next = <lfs_dir_next> */ + lua_pushvalue(L, -2); /* already on the stack */ + lua_setfield(L, -2, "next"); + + /* insert dir function */ + /* upvalue 1: next function */ + /* upvalue 2: dir metatable */ + lua_pushcclosure(L, lfs_dir, 2); + lua_setfield(L, -2, "dir"); + + /* insert chdir function */ + lua_pushcfunction(L, lfs_chdir); + lua_setfield(L, -2, "chdir"); + /* insert mkdir function */ + lua_pushcfunction(L, lfs_mkdir); + lua_setfield(L, -2, "mkdir"); + /* insert rmdir function */ + lua_pushcfunction(L, lfs_rmdir); + lua_setfield(L, -2, "rmdir"); + /* insert remove function */ + lua_pushcfunction(L, lfs_remove); + lua_setfield(L, -2, "remove"); + + /* insert link function */ + lua_pushcfunction(L, lfs_link); + lua_setfield(L, -2, "link"); + /* insert rename function */ + lua_pushcfunction(L, lfs_rename); + lua_setfield(L, -2, "rename"); + + /* insert attributes function */ + lua_pushcfunction(L, lfs_attr); + lua_setfield(L, -2, "attributes"); + /* insert attributes function */ + lua_pushcfunction(L, lfs_symattr); + lua_setfield(L, -2, "symlinkattributes"); + + /* insert touch function */ + lua_pushcfunction(L, lfs_touch); + lua_setfield(L, -2, "touch"); + + /* insert currentdir function */ + lua_pushcfunction(L, lfs_currentdir); + lua_setfield(L, -2, "currentdir"); + + return 1; +} diff --git a/test/lfs.lua b/test/lfs.lua new file mode 100755 index 0000000..0f084fa --- /dev/null +++ b/test/lfs.lua @@ -0,0 +1,97 @@ +#!bin/lem +-- +-- This file is part of LEM, a Lua Event Machine. +-- Copyright 2011-2012 Emil Renner Berthing +-- +-- LEM is free software: you can redistribute it 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. +-- +-- 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 General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with LEM. If not, see <http://www.gnu.org/licenses/>. +-- + +local utils = require 'lem.utils' +local io = require 'lem.io' +local lfs = require 'lem.lfs' + +local testdir = 'testdir' +local testfile = 'testfile' + +local spawnticker, stopticker +do + local write, yield = io.write, utils.yield + local stop = true + + local function ticker() + stop = false + repeat + write('.') + yield() + until stop + end + + function spawnticker() + utils.spawn(ticker) + end + + function stopticker() + stop = true + end +end + +local sleeper = utils.newsleeper() +local attr + +io.write('Current directory: '.. lfs.currentdir() ..'\n') + +spawnticker() + +for name in lfs.dir('.') do + io.write('\n"' .. name .. '"\n') +end + +io.write('\nCreating testdir\n') +assert(lfs.mkdir(testdir)) +io.write('\nLocking directory\n') +local lock = assert(lfs.lock_dir(testdir)) +io.write('\nGetting attributes\n') +attr = assert(lfs.attributes(testdir)) +io.write('\nAttributes:\n') +for k, v in pairs(attr) do + print(k, v) +end +io.write('\nUnlocking testdir\n') +assert(lock:free()) +io.write('\nRemoving testdir\n') +assert(lfs.rmdir(testdir)) +io.write('\nCreating testfile\n') +local file = assert(io.open(testfile, 'w')) +io.write('\nLocking testfile\n') +assert(lfs.lock(file, 'w')) +io.write('\nGetting change time\n') +attr = assert(lfs.attributes(testfile, 'change')) +io.write('\nChange time: ' .. attr .. '\n') +stopticker() +io.write('\nSleeping two seconds..\n') +sleeper:sleep(2) +spawnticker() +io.write('\nTouching testfile\n') +assert(lfs.touch(testfile)) +io.write('\nGetting change time\n') +attr = assert(lfs.attributes(testfile, 'change')) +io.write('\nChange time: ' .. attr .. '\n') +io.write('\nRenaming testfile\n') +assert(lfs.rename(testfile, testfile..'2')) +io.write('\nRemoving testfile\n') +assert(lfs.remove(testfile..'2')) +io.write('\nDone\n') +stopticker() + +-- vim: syntax=lua ts=2 sw=2 noet: |