#include "KinoSearch/Util/ToolSet.h"
#include <errno.h>
#include <stdio.h>
#define KINO_WANT_LOCK_VTABLE
#include "KinoSearch/Store/Lock.r"
#include "KinoSearch/Store/Folder.r"
#include "KinoSearch/Store/OutStream.r"
#include "KinoSearch/Util/YAML.h"
#include <signal.h> /* requires POSIX. TODO: write alternative for MSVC */
#include <unistd.h>
/* Delete a given lock file which meets these conditions:
* - lock name matches.
* - agent id matches.
*
* If delete_mine is false, don't delete a lock file which
* matches this process's pid. If delete_other is false, don't delete lock
* files which don't match this process's pid.
*/
static bool_t
clear_file(Lock *self, const ByteBuf *filename, bool_t delete_mine,
bool_t delete_other);
Lock*
Lock_new(Folder *folder, const ByteBuf *lock_name, const ByteBuf *agent_id,
i32_t timeout)
{
CREATE(self, Lock, LOCK);
/* assign */
self->folder = REFCOUNT_INC(folder);
self->timeout = timeout;
self->lock_name = BB_CLONE(lock_name);
self->agent_id = BB_CLONE(agent_id);
/* derive */
self->filename = BB_CLONE(lock_name);
BB_Cat_Str(self->filename, ".lock", 5);
return self;
}
void
Lock_destroy(Lock *self)
{
REFCOUNT_DEC(self->folder);
REFCOUNT_DEC(self->agent_id);
REFCOUNT_DEC(self->lock_name);
REFCOUNT_DEC(self->filename);
free(self);
}
bool_t
Lock_obtain(Lock *self)
{
float sleep_count = self->timeout / LOCK_POLL_INTERVAL;
bool_t locked = Lock_Do_Obtain(self);
while (!locked && sleep_count-- > 0) {
sleep(1);
locked = Lock_Do_Obtain(self);
}
return locked;
}
bool_t
Lock_do_obtain(Lock *self)
{
OutStream *lock_stream = Folder_Safe_Open_OutStream(self->folder,
self->filename);
Hash *file_data;
ByteBuf *yaml_buf;
ByteBuf *pid_buf;
if (lock_stream == NULL)
return false;
/* write pid, lock name, and agent id to the lock file as YAML */
pid_buf = BB_new_i64( getpid() );
file_data = Hash_new(3);
Hash_Store(file_data, "pid", 3, (Obj*)pid_buf);
Hash_Store(file_data, "agent_id", 8, (Obj*)self->agent_id);
Hash_Store(file_data, "lock_name", 9, (Obj*)self->lock_name);
yaml_buf = YAML_encode_yaml((Obj*)file_data);
OutStream_Write_Bytes(lock_stream, yaml_buf->ptr, yaml_buf->len);
/* clean up */
OutStream_SClose(lock_stream);
REFCOUNT_DEC(lock_stream);
REFCOUNT_DEC(pid_buf);
REFCOUNT_DEC(file_data);
REFCOUNT_DEC(yaml_buf);
return true;
}
void
Lock_release(Lock *self)
{
clear_file(self, self->filename, true, false);
}
bool_t
Lock_is_locked(Lock *self)
{
return Folder_File_Exists(self->folder, self->filename);
}
void
Lock_clear_stale(Lock *self)
{
VArray *files = Folder_List(self->folder);
u32_t i;
/* take a stab at any file that begins with our lock_name */
for (i = 0; i < files->size; i++) {
ByteBuf *filename = (ByteBuf*)VA_Fetch(files, i);
if (BB_Starts_With(filename, self->lock_name)) {
clear_file(self, filename, false, true);
}
}
REFCOUNT_DEC(files);
}
static bool_t
clear_file(Lock *self, const ByteBuf *filename, bool_t delete_mine,
bool_t delete_other)
{
Folder *folder = self->folder;
bool_t success = false;
/* only delete locks that start with our lock_name */
if ( !BB_Starts_With(filename, self->lock_name) )
return false;
/* attempt to delete dead lock file */
if (Folder_File_Exists(folder, filename)) {
ByteBuf *file_contents = Folder_Slurp_File(folder, filename);
Hash *hash = (Hash*)YAML_parse_yaml(file_contents);
if ( hash != NULL && OBJ_IS_A(hash, HASH) ) {
ByteBuf *pid_bb = (ByteBuf*)Hash_Fetch(hash, "pid", 3);
ByteBuf *agent_id = (ByteBuf*)Hash_Fetch(hash, "agent_id", 8);
ByteBuf *lock_name = (ByteBuf*)Hash_Fetch(hash, "lock_name", 9);
/* match agent id and lock name */
if ( agent_id != NULL
&& BB_Equals(agent_id, (Obj*)self->agent_id)
&& lock_name != NULL
&& BB_Equals(lock_name, (Obj*)self->lock_name)
&& pid_bb != NULL
) {
/* verify that pid is either mine or dead */
int pid = BB_To_I64(pid_bb);
/* this process */
if ( ( delete_mine && pid == getpid() )
/* dead pid */
|| ( delete_other && kill(pid, 0) && errno == ESRCH )
) {
Folder_Delete_File(folder, filename);
success = true;
}
}
}
REFCOUNT_DEC(hash);
REFCOUNT_DEC(file_contents);
}
return success;
}
/* Copyright 2006-2007 Marvin Humphrey
*
* This program is free software; you can redistribute it and/or modify
* under the same terms as Perl itself.
*/