From 90b25e7a4b4aa22d73b50f561deb235ec865ec48 Mon Sep 17 00:00:00 2001 From: pancake Date: Tue, 17 Aug 2021 07:21:33 +0200 Subject: [PATCH] os: filesystem level locking api (#11191) --- vlib/os/filelock/filelock_test.v | 27 +++++++++++ vlib/os/filelock/lib.v | 14 ++++++ vlib/os/filelock/lib_nix.c.v | 82 ++++++++++++++++++++++++++++++++ vlib/os/filelock/lib_windows.c.v | 75 +++++++++++++++++++++++++++++ vlib/os/os_c.v | 4 +- 5 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 vlib/os/filelock/filelock_test.v create mode 100644 vlib/os/filelock/lib.v create mode 100644 vlib/os/filelock/lib_nix.c.v create mode 100644 vlib/os/filelock/lib_windows.c.v diff --git a/vlib/os/filelock/filelock_test.v b/vlib/os/filelock/filelock_test.v new file mode 100644 index 000000000..658d3aa71 --- /dev/null +++ b/vlib/os/filelock/filelock_test.v @@ -0,0 +1,27 @@ +import os +import os.filelock + +fn test_flock() { + lockfile := 'test.lock' + mut l := filelock.new(lockfile) + assert !os.exists(lockfile) + l.acquire() or { panic(err) } + assert os.exists(lockfile) + // do stuff + l.release() + assert !os.exists(lockfile) +} + +fn test_flock_try() { + lockfile := 'test-try.lock' + mut l := filelock.new(lockfile) + assert l.try_acquire() + l.release() + assert !os.exists(lockfile) + assert l.try_acquire() + assert os.exists(lockfile) + l.release() + assert l.try_acquire() + l.release() + assert !os.exists(lockfile) +} diff --git a/vlib/os/filelock/lib.v b/vlib/os/filelock/lib.v new file mode 100644 index 000000000..5a89ad8c2 --- /dev/null +++ b/vlib/os/filelock/lib.v @@ -0,0 +1,14 @@ +module filelock + +pub struct FileLock { + name string +mut: + fd int +} + +pub fn new(fileName string) FileLock { + return FileLock{ + name: fileName + fd: -1 + } +} diff --git a/vlib/os/filelock/lib_nix.c.v b/vlib/os/filelock/lib_nix.c.v new file mode 100644 index 000000000..1af99165f --- /dev/null +++ b/vlib/os/filelock/lib_nix.c.v @@ -0,0 +1,82 @@ +module filelock + +import time + +#include + +fn C.unlink(&char) int +fn C.open(&char, int, int) int +fn C.flock(int, int) int + +[unsafe] +pub fn (mut l FileLock) unlink() { + if l.fd != -1 { + C.close(l.fd) + l.fd = -1 + } + C.unlink(&char(l.name.str)) +} + +pub fn (mut l FileLock) acquire() ?bool { + if l.fd != -1 { + // lock already acquired by this instance + return false + } + fd := open_lockfile(l.name) + if fd == -1 { + return error('cannot create lock file $l.name') + } + if C.flock(fd, C.LOCK_EX) == -1 { + C.close(fd) + return error('cannot lock') + } + l.fd = fd + return true +} + +pub fn (mut l FileLock) release() bool { + if l.fd != -1 { + unsafe { + l.unlink() + } + return true + } + return false +} + +pub fn (mut l FileLock) wait_acquire(s int) ?bool { + fin := time.now().add(s) + for time.now() < fin { + if l.try_acquire() { + return true + } + C.usleep(1000) + } + return false +} + +fn open_lockfile(f string) int { + mut fd := C.open(&char(f.str), C.O_CREAT, 0o644) + if fd == -1 { + // if stat is too old delete lockfile + fd = C.open(&char(f.str), C.O_RDONLY, 0) + } + return fd +} + +pub fn (mut l FileLock) try_acquire() bool { + if l.fd != -1 { + return true + } + fd := open_lockfile('$l.name') + if fd != -1 { + err := C.flock(fd, C.LOCK_EX | C.LOCK_NB) + if err == -1 { + C.close(fd) + return false + } + l.fd = fd + return true + } + return false +} diff --git a/vlib/os/filelock/lib_windows.c.v b/vlib/os/filelock/lib_windows.c.v new file mode 100644 index 000000000..56cbacefe --- /dev/null +++ b/vlib/os/filelock/lib_windows.c.v @@ -0,0 +1,75 @@ +module filelock + +import time + +fn C.DeleteFileW(&u16) bool +fn C.CreateFileW(&u16, u32, u32, voidptr, u32, u32, voidptr) voidptr +fn C.CloseHandle(voidptr) bool + +pub fn (mut l FileLock) unlink() { + if l.fd != -1 { + C.CloseHandle(l.fd) + l.fd = -1 + } + t_wide := l.name.to_wide() + C.DeleteFileW(t_wide) +} + +pub fn (mut l FileLock) acquire() ?bool { + if l.fd != -1 { + // lock already acquired by this instance + return false + } + fd := open(l.name) + if fd == -1 { + return error('cannot create lock file $l.name') + } + l.fd = fd + return true +} + +pub fn (mut l FileLock) release() bool { + if l.fd != -1 { + C.CloseHandle(l.fd) + l.fd = -1 + t_wide := l.name.to_wide() + C.DeleteFileW(t_wide) + return true + } + return false +} + +pub fn (mut l FileLock) wait_acquire(s int) ?bool { + fin := time.now().add(s) + for time.now() < fin { + if l.try_acquire() { + return true + } + time.sleep(1 * time.millisecond) + } + return false +} + +fn open(f string) voidptr { + f_wide := f.to_wide() + // locking it + fd := C.CreateFileW(f_wide, C.GENERIC_READ | C.GENERIC_WRITE, 0, 0, C.OPEN_ALWAYS, + C.FILE_ATTRIBUTE_NORMAL, 0) + if fd == C.INVALID_HANDLE_VALUE { + fd == -1 + } + return fd +} + +pub fn (mut l FileLock) try_acquire() bool { + if l.fd != -1 { + // lock already acquired by this instance + return false + } + fd := open(l.name) + if fd == -1 { + return false + } + l.fd = fd + return true +} diff --git a/vlib/os/os_c.v b/vlib/os/os_c.v index 2cc2bdb14..68bccc97b 100644 --- a/vlib/os/os_c.v +++ b/vlib/os/os_c.v @@ -139,7 +139,7 @@ pub fn read_file(path string) ?string { // truncate changes the size of the file located in `path` to `len`. // Note that changing symbolic links on Windows only works as admin. pub fn truncate(path string, len u64) ? { - fp := C.open(&char(path.str), o_wronly | o_trunc) + fp := C.open(&char(path.str), o_wronly | o_trunc, 0) defer { C.close(fp) } @@ -236,7 +236,7 @@ pub fn cp(src string, dst string) ? { return error_with_code('failed to copy $src to $dst', int(result)) } } $else { - fp_from := C.open(&char(src.str), C.O_RDONLY) + fp_from := C.open(&char(src.str), C.O_RDONLY, 0) if fp_from < 0 { // Check if file opened return error_with_code('cp: failed to open $src', int(fp_from)) } -- 2.30.2