Skip to content

Commit

Permalink
add file lock to avoid instance fighting
Browse files Browse the repository at this point in the history
  • Loading branch information
Cryptkeeper committed Aug 31, 2024
1 parent e90f081 commit 7cd65d5
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 9 deletions.
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ Usage: fsautoproc -i <file>
Options:
-c <file> Configuration file (default: `fsautoproc.json`)
-i <file> File index write path
-j Include ignored files in index (default: false)
-l List time spent for each command set (default: false)
-p Pipe subprocess stdout/stderr to files (default: false)
-j Enable including ignored files in index
-l List time spent for each command set
-p Pipe subprocess stdout/stderr to files
-s <dir> Search directory root (default: `.`)
-t <#> Number of worker threads (default: 4)
-r <file> Trace which command sets match the file
-u Skip processing files, only update file index
-v Enable verbose output
-x <file> Exclusive lock file path
```

### Basic Configuration
Expand Down Expand Up @@ -84,3 +86,7 @@ fsautoproc uses a symbol table when logging file changes and program status. Thi
| `[s]` | A directory is being scanned |
| `[x]` | A system command is being invoked |
| `[!]` | An error has occurred |

#### Locking

`fsautoproc` uses a single, exclusive file lock to prevent multiple instances of the program from running simultaneously within the same search ("working") directory. The lock file is created in the working directory by default, but can be specified via the `-x` flag. The lock file is removed when the program exits. Should the program crash or exit unexpectedly, the lock file may remain and must be manually removed.
30 changes: 30 additions & 0 deletions include/fl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef FSAUTOPROC_FL_H
#define FSAUTOPROC_FL_H

struct flock_s {
const char* path; /* file path */
int fd; /* opened file descriptor */
_Bool open; /* file descriptor open flag */
};

/// @brief Initializes a unlocked, ready-to-use file lock structure with the
/// given target lock file path. The file descriptor is set to -1 and is not
/// opened until the file is locked with `fllock()`.
/// @param fp The file path to lock.
/// @return The initialized file lock structure.
#define flinit(fp) ((struct flock_s){.path = (fp), .fd = -1, .open = 0})

/// @brief Locks the file at the given path. If a file descriptor cannot be
/// obtained, or the file cannot be locked, an error code is returned.
/// @note The lock must be initialized with `flinit()` before calling `fllock`.
/// @param fl The file lock structure.
/// @return 0 if successful, otherwise a non-zero error code.
int fllock(struct flock_s* fl);

/// @brief Unlocks the file at the given path. If the file is not open, or the
/// file cannot be unlocked, an error code is returned.
/// @param fl The file lock structure.
/// @return 0 if successful, otherwise a non-zero error code.
int flunlock(struct flock_s* fl);

#endif//FSAUTOPROC_FL_H
39 changes: 39 additions & 0 deletions src/fl.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include "fl.h"

#include <assert.h>
#include <fcntl.h>
#include <stddef.h>
#include <sys/file.h>
#include <unistd.h>

/// @brief Opens the file at the given path and updates the file lock structure
/// with the file descriptor. If the file is already open, the file descriptor
/// is not updated and the function will return successfully. If the file cannot
/// be opened, the error code is returned via the file lock structure.
/// @param fl The file lock structure to update.
/// @return 0 if successful, otherwise a non-zero error code.
static int flopen(struct flock_s* fl) {
assert(fl->path != NULL);
if (fl->open) return 0;// file is already open
fl->fd = open(fl->path, O_RDWR | O_CREAT | O_TRUNC, 0644);
fl->open = fl->fd >= 0;
return fl->open ? 0 : -1;
}

int fllock(struct flock_s* fl) {
assert(fl->path != NULL);
if (flopen(fl)) return -1; // get or open file descriptor
if (flock(fl->fd, LOCK_EX) < 0) return -2;// lock file descriptor
return 0;
}

int flunlock(struct flock_s* fl) {
assert(fl->path != NULL);
if (!fl->open) return -1; // ensure file is open
if (flock(fl->fd, LOCK_UN) < 0) return -2;// release lock
close(fl->fd); // close file descriptor
unlink(fl->path); // remove unlocked file
fl->fd = -1;
fl->open = 0;
return 0;
}
44 changes: 38 additions & 6 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "deng.h"
#include "fd.h"
#include "fl.h"
#include "fs.h"
#include "index.h"
#include "lcmd.h"
Expand All @@ -18,6 +19,7 @@
static struct {
char* configfile;
char* indexfile;
char* lockfile;
char* searchdir;
char* tracefile;
_Bool pipefiles;
Expand All @@ -32,6 +34,7 @@ static struct {
static void freeinitargs(void) {
free(initargs.configfile);
free(initargs.tracefile);
free(initargs.lockfile);
free(initargs.indexfile);
free(initargs.searchdir);
}
Expand All @@ -41,8 +44,16 @@ static struct lcmdset_s** cmdsets;
static struct index_s lastmap; /* stored index from previous run (if any) */
static struct index_s thismap; /* live checked index from this run */

static struct flock_s worklock; /* exclusive lock for work directory */

/// @brief Frees all allocated resources.
static void freeall(void) {
// release work lock, if successfully opened
if (worklock.open && flunlock(&worklock))
log_error("error releasing lock file for local directory: %s (you may need "
"to delete it manually)",
worklock.path);

tpshutdown();
freeinitargs();
lcmdfree_r(cmdsets);
Expand All @@ -61,23 +72,23 @@ static void freeall(void) {

static int parseinitargs(const int argc, char** const argv) {
int c;
while ((c = getopt(argc, argv, ":hc:i:jlps:t:r:uv")) != -1) {
while ((c = getopt(argc, argv, ":hc:i:jlps:t:r:uvx:")) != -1) {
switch (c) {
case 'h':
printf("Usage: %s -i <file>\n"
"\n"
"Options:\n"
" -c <file> Configuration file (default: `fsautoproc.json`)\n"
" -i <file> File index write path\n"
" -j Include ignored files in index (default: false)\n"
" -j Enable including ignored files in index\n"
" -l List time spent for each command set\n"
" -p Pipe subprocess stdout/stderr to files "
"(default: false)\n"
" -p Pipe subprocess stdout/stderr to files\n"
" -s <dir> Search directory root (default: `.`)\n"
" -t <#> Number of worker threads (default: 4)\n"
" -r <file> Trace which command sets match the file\n"
" -u Skip processing files, only update file index\n"
" -v Enable verbose output\n",
" -v Enable verbose output\n"
" -x <file> Exclusive lock file path\n",
argv[0]);
exit(0);
case 'c':
Expand Down Expand Up @@ -110,6 +121,9 @@ static int parseinitargs(const int argc, char** const argv) {
case 'v':
initargs.verbose = true;
break;
case 'x':
strdupoptarg(initargs.lockfile);
break;
case ':':
log_error("option is missing argument: %c", optopt);
return 1;
Expand All @@ -134,6 +148,13 @@ static int parseinitargs(const int argc, char** const argv) {
if ((initargs.indexfile = strdup(fp)) == NULL) return 1;
}

if (initargs.lockfile == NULL) {
// default to using fsautoproc.lock inside search directory
char fp[256];
snprintf(fp, sizeof(fp), "%s/fsautoproc.lock", initargs.searchdir);
if ((initargs.lockfile = strdup(fp)) == NULL) return 1;
}

if (initargs.threads == 0) initargs.threads = 4;

return 0;
Expand Down Expand Up @@ -270,8 +291,19 @@ int main(int argc, char** argv) {
atexit(freeall);
if (parseinitargs(argc, argv)) return 1;

// init worker thread pool
int err;

// establish work lock
worklock = flinit(initargs.lockfile);
if ((err = fllock(&worklock))) {
log_error("error establishing exclusive lock file for local directory "
"`%s`: %d (is another instance already running? did a previous "
"instance crash?)",
worklock.path, err);
return 1;
}

// init worker thread pool
const int tpflags = initargs.pipefiles ? TPOPT_LOGFILES : 0;
if ((err = tpinit(initargs.threads, tpflags))) {
log_error("error initializing thread pool: %d", err);
Expand Down

0 comments on commit 7cd65d5

Please sign in to comment.