Commit 31439957 authored by Michal 'vorner' Vaner's avatar Michal 'vorner' Vaner

lua: File locks

Wrap the lockf call into an object. It locks a file when created and it
is possible to release the lock.

Include some code refactoring, so creating lua modules is easier.
parent 8ecdfba9
......@@ -9,11 +9,13 @@ $(O)/.gen/src/lib/lautoload.embedlist: $(S)/src/lib/gen_embed.sh $(S)/src/lib/em
libupdater_MODULES := \
arguments \
inject \
interpreter \
lautoload.embed \
embed_types \
events \
journal \
locks \
util
libupdater_PKG_CONFIGS := $(LUA_NAME) libevent
......
/*
* Copyright 2016, CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This file is part of the turris updater.
*
* Updater 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.
*
* Updater 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 Updater. If not, see <http://www.gnu.org/licenses/>.
*/
#include "inject.h"
#include "util.h"
void inject_func_n(lua_State *L, const char *module, const struct inject_func *inject, size_t count) {
// Inject the functions
for (size_t i = 0; i < count; i ++) {
DBG("Injecting function %s.%s", module, inject[i].name);
lua_pushcfunction(L, inject[i].func);
lua_setfield(L, -2, inject[i].name);
}
}
void inject_str_const(lua_State *L, const char *module, const char *name, const char *value) {
DBG("Injecting constant %s.%s", module, name);
lua_pushstring(L, value);
lua_setfield(L, -2, name);
}
void inject_module(lua_State *L, const char *module) {
DBG("Injecting module %s", module);
// package.loaded[module] = _M
lua_getglobal(L, "package");
lua_getfield(L, -1, "loaded");
lua_pushvalue(L, -3); // Copy the _M table on top of the stack
lua_setfield(L, -2, module);
// journal = _M
lua_pushvalue(L, -3); // Copy the _M table
lua_setglobal(L, module);
// Drop the _M, package, loaded
lua_pop(L, 3);
}
/*
* Copyright 2016, CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This file is part of the turris updater.
*
* Updater 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.
*
* Updater 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 Updater. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UPDATER_INJECT_H
#define UPDATER_INJECT_H
#include <lua.h>
// Helper functions to inject stuff into lua modules
struct inject_func {
int (*func)(lua_State *L);
const char *name;
};
// Inject n functions into the table on top of the stack.
void inject_func_n(lua_State *L, const char *module, const struct inject_func *injects, size_t count) __attribute__((nonnull));
// Inject a string into the table on top of the stack.
void inject_str_const(lua_State *L, const char *module, const char *name, const char *value) __attribute__((nonnull));
// Make the table on top of the stack a module. Drop the table from the stack.
void inject_module(lua_State *L, const char *module) __attribute__((nonnull));
#endif
......@@ -22,6 +22,7 @@
#include "util.h"
#include "events.h"
#include "journal.h"
#include "locks.h"
#include <lua.h>
#include <lualib.h>
......@@ -581,6 +582,7 @@ struct interpreter *interpreter_create(struct events *events) {
}
// Some binary embedded modules
journal_mod_init(L);
locks_mod_init(L);
return result;
}
......
......@@ -19,6 +19,7 @@
#include "journal.h"
#include "util.h"
#include "inject.h"
#include <lualib.h>
#include <lauxlib.h>
......@@ -311,12 +312,7 @@ static int lua_opened(lua_State *L) {
return 1;
}
struct func {
int (*func)(lua_State *L);
const char *name;
};
static struct func inject[] = {
static const struct inject_func inject[] = {
{ lua_fresh, "fresh" },
{ lua_recover, "recover" },
{ lua_finish, "finish" },
......@@ -329,28 +325,11 @@ void journal_mod_init(lua_State *L) {
// Create _M
lua_newtable(L);
// Some variables
DBG("Injecting variable journal.path");
// journal.path = DEFAULT_JOURNAL_PATH
lua_pushstring(L, DEFAULT_JOURNAL_PATH);
lua_setfield(L, -2, "path");
inject_str_const(L, "journal", "path", DEFAULT_JOURNAL_PATH);
// journal.XXX = int(XXX) - init the constants
#define X(VAL) DBG("Injecting constant journal." #VAL); lua_pushinteger(L, RT_##VAL); lua_setfield(L, -2, #VAL);
RECORD_TYPES
#undef X
// Inject the functions
for (size_t i = 0; i < sizeof inject / sizeof *inject; i ++) {
DBG("Injecting function journal.%s", inject[i].name);
lua_pushcfunction(L, inject[i].func);
lua_setfield(L, -2, inject[i].name);
}
// package.loaded["journal"] = _M
lua_getglobal(L, "package");
lua_getfield(L, -1, "loaded");
lua_pushvalue(L, -3); // Copy the _M table on top of the stack
lua_setfield(L, -2, "journal");
// journal = _M
lua_pushvalue(L, -3); // Copy the _M table
lua_setglobal(L, "journal");
// Drop the _M, package, loaded
lua_pop(L, 3);
inject_func_n(L, "journal", inject, sizeof inject / sizeof *inject);
inject_module(L, "journal");
}
/*
* Copyright 2016, CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This file is part of the turris updater.
*
* Updater 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.
*
* Updater 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 Updater. If not, see <http://www.gnu.org/licenses/>.
*/
#include "locks.h"
#include "inject.h"
#include "util.h"
#include <lauxlib.h>
#include <lualib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#define DEFAULT_LOCKFILE_PATH "/var/lock/opkg.lock"
#define LOCK_META "updater_lock_meta"
struct lock {
char *path;
int fd;
bool locked;
};
static int lua_acquire(lua_State *L) {
const char *path = luaL_checkstring(L, 1);
DBG("Trying to get a lock at %s", path);
struct lock *lock = lua_newuserdata(L, sizeof *lock);
// Mark it as not locked before we do anything
lock->path = strdup(path);
lock->locked = false;
lock->fd = -1;
// Set the corresponding meta table, so we know how to close it when necessary
luaL_getmetatable(L, LOCK_META);
lua_setmetatable(L, -2);
lock->fd = creat(path, S_IRUSR | S_IWUSR);
if (lock->fd == -1)
return luaL_error(L, "Failed to create the lock file %s: %s", path, strerror(errno));
if (lockf(lock->fd, F_TLOCK, 0) == -1)
// Leave closing up on the GC, that is enough
return luaL_error(L, "Failed to lock the lock file %s: %s", path, strerror(errno));
// OK, it is now locked.
lock->locked = true;
// And return it.
return 1;
}
static const struct inject_func funcs[] = {
{ lua_acquire, "acquire" }
};
static int lua_lock_release(lua_State *L) {
struct lock *lock = luaL_checkudata(L, 1, LOCK_META);
if (!lock->locked)
luaL_error(L, "Lock on file %s is not held", lock->path);
ASSERT(lock->fd != -1);
// Unlocking what we have locked shall always succeed
ASSERT(lockf(lock->fd, F_ULOCK, 0) == 0);
lock->locked = false;
ASSERT(close(lock->fd) == 0);
lock->fd = -1;
return 0;
}
static int lua_lock_gc(lua_State *L) {
struct lock *lock = luaL_checkudata(L, 1, LOCK_META);
if (lock->locked) {
WARN("Lock on %s released by garbage collector", lock->path);
lua_lock_release(L);
}
if (lock->fd != -1) {
// Unlocked, but opened might actually happen, if there's an error locking in the constructor
ASSERT(close(lock->fd) == 0);
lock->fd = -1;
}
free(lock->path);
lock->path = NULL;
return 0;
}
static const struct inject_func lock_meta[] = {
{ lua_lock_release, "release" },
{ lua_lock_gc, "__gc" }
};
void locks_mod_init(lua_State *L) {
DBG("Locks module init");
lua_newtable(L);
inject_func_n(L, "locks", funcs, sizeof funcs / sizeof *funcs);
inject_module(L, "locks");
ASSERT(luaL_newmetatable(L, LOCK_META) == 1);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
inject_func_n(L, LOCK_META, lock_meta, sizeof lock_meta / sizeof *lock_meta);
}
/*
* Copyright 2016, CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This file is part of the turris updater.
*
* Updater 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.
*
* Updater 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 Updater. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UPDATER_LOCKS_H
#define UPDATER_LOCKS_H
#include <lua.h>
// Create the locks module and inject it into the lua state
void locks_mod_init(lua_State *lua) __attribute__((nonnull));
#endif
......@@ -122,6 +122,13 @@ sync()::
Writes everything to a permanent storage (equivalent to the shell's
`sync` command).
locks.acquire(path)::
Lock a file with the `lockf` call. Fail if the lock is already held
by other process. Create the file as needed. Return a lock object as
a result. It has release() method, to release the lock. The lock is
also released when the object is garbage collected, but there's no
guarantee how soon it may be.
Journal manipulation
--------------------
......
.PHONY: check-clean test valgrind check
.PHONY: check-clean test valgrind check test-locks valgrind-locks
BINARIES_NOTARGET += tests/locks
locks_MODULES += locks
locks_LOCAL_LIBS += updater
$(O)/tests/check-compiled/compiled:
mkdir -p $(O)/tests/check-compiled
......@@ -64,6 +68,15 @@ endef
$(eval $(foreach TEST,$(C_TESTS),$(call DO_C_TEST,$(TEST))))
$(eval $(foreach TEST,$(LUA_TESTS),$(call DO_LUA_TEST,$(TEST))))
test-locks: $(O)/bin/locks
$(O)/bin/locks
valgrind-locks: $(O)/bin/locks
$(VALGRIND) $(O)/bin/locks
test: test-locks
valgrind: valgrind-locks
check: test valgrind
include $(S)/tests/lunit-launch/Makefile.dir
......
/*
* Copyright 2016, CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This file is part of the turris updater.
*
* Updater 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.
*
* Updater 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 Updater. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../src/lib/interpreter.h"
#include "../src/lib/util.h"
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
/*
* Tests for the lua locks module. They are somewhat odd, so that's why
* it is outside of the usual testing mechanism.
*
* Because we can repeatedly lock the same file from the same process, we
* need to fork to check it works correctly. Because we want to do some
* non-trivial forking/waiting and such, we don't do it from lua code.
*/
int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) {
struct interpreter *interpreter = interpreter_create(NULL);
interpreter_autoload(interpreter);
const char *err = interpreter_call(interpreter, "mkdtemp", NULL, "");
ASSERT_MSG(!err, "%s", err);
const char *dir;
ASSERT(interpreter_collect_results(interpreter, "s", &dir) == -1);
const char *f1 = aprintf("%s/file1", dir);
const char *f2 = aprintf("%s/file2", dir);
err = interpreter_include(interpreter, "function get_lock(name, file) _G[name] = locks.acquire(file) end", 0, "lock-fun");
ASSERT_MSG(!err, "%s", err);
err = interpreter_call(interpreter, "get_lock", NULL, "ss", "l1", f1);
ASSERT_MSG(!err, "%s", err);
pid_t pid = fork();
ASSERT(pid != -1);
if (!pid) {
// This one should fail, since it is already held by other process.
err = interpreter_call(interpreter, "get_lock", NULL, "ss", "extra", f1);
ASSERT(err);
// But we can get a different lock
err = interpreter_call(interpreter, "get_lock", NULL, "ss", "l2", f2);
ASSERT_MSG(!err, "%s", err);
interpreter_destroy(interpreter);
return 0;
}
int status;
ASSERT(pid == wait(&status));
ASSERT(status == 0);
// OK, release the lock and try it acquiring in a child again.
// Do the release through include, as interpreter_call doesn't handle well methods on userdata
err = interpreter_call(interpreter, "l1:release", NULL, "");
ASSERT_MSG(!err, "%s", err);
pid = fork();
ASSERT(pid != -1);
if (!pid) {
err = interpreter_call(interpreter, "get_lock", NULL, "ss", "extra", f1);
ASSERT_MSG(!err, "%s", err);
interpreter_destroy(interpreter);
return 0;
}
ASSERT(wait(&status) == pid);
ASSERT(status == 0);
interpreter_destroy(interpreter);
return 0;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment