filelock/0000755000176200001440000000000014535511242012044 5ustar liggesusersfilelock/NAMESPACE0000644000176200001440000000024114141557640013265 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method(print,filelock_lock) export(lock) export(unlock) useDynLib(filelock, .registration = TRUE, .fixes = "c_") filelock/LICENSE0000644000176200001440000000005614521174551013055 0ustar liggesusersYEAR: 2023 COPYRIGHT HOLDER: filelock authors filelock/README.md0000644000176200001440000001547014535451265013342 0ustar liggesusers # filelock > Portable File Locking [![R build status](https://github.com/r-lib/filelock/workflows/R-CMD-check/badge.svg)](https://github.com/r-lib/filelock/actions) [![](https://www.r-pkg.org/badges/version/filelock)](https://www.r-pkg.org/pkg/filelock) [![CRAN RStudio mirror downloads](https://cranlogs.r-pkg.org/badges/filelock)](https://www.r-pkg.org/pkg/filelock) [![Coverage Status](https://img.shields.io/codecov/c/github/r-lib/filelock/main.svg)](https://app.codecov.io/github/r-lib/filelock?branch=main) [![Codecov test coverage](https://codecov.io/gh/r-lib/filelock/branch/main/graph/badge.svg)](https://app.codecov.io/gh/r-lib/filelock?branch=main) [![R-CMD-check](https://github.com/r-lib/filelock/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/filelock/actions/workflows/R-CMD-check.yaml) Place an exclusive or shared lock on a file. It uses `LockFile` on Windows and `fcntl` locks on Unix-like systems. ## Installation Install the package from CRAN as usual: ``` r install.packages("filelock") ``` Install the development version from GitHub: ``` r pak::pak("r-lib/filelock") ``` ## Usage ``` r library(filelock) ``` This is R process 1, it gets an exclusive lock. If you want to lock file `myfile`, always create a separate lock file instead of placing the lock on this file directly! ``` r R1> lck <- lock("/tmp/myfile.lck") ``` This is R process 2, it fails to acquire a lock. ``` r R2> lock("/tmp/myfile.lck", timeout = 0) ``` Specifying a timeout interval, before giving up: ``` r R2> lock("/tmp/myfile.lck", timeout = 5000) ``` Wait indefinetely: ``` r R2> lock("/tmp/myfile.lck", timeout = Inf) ``` Once R process 1 released the lock (or terminated), R process 2 can acquire the lock: ``` r R1> unlock(lck) ``` ``` r R2> lock("/tmp/myfile.lck") ``` #> Lock on ‘/tmp/myfile.lck’ ## Documentation ### Warning Always use special files for locking. I.e. if you want to restict access to a certain file, do *not* place the lock on this file. Create a special file, e.g. by appending `.lock` to the original file name and place the lock on that. (The `lock()` function creates the file for you, actually, if it does not exist.) Reading from or writing to a locked file has undefined behavior! (See more about this below at the Internals Section.) It is hard to determine whether and when it is safe to remove these special files, so our current recommendation is just to leave them around. It is best to leave the special lock file empty, simply because on some OSes you cannot write to it (or read from it), once the lock is in place. ### Advisory Locks: All locks set by this package might be advisory. A process that does not respect this locking machanism may be able to read and write the locked file, or even remove it (assuming it has capabilities to do so). ### Unlock on Termination: If a process terminates (with a normal exit, a crash or on a signal), the lock(s) it is holding are automatically released. If the R object that represents the lock (the return value of `lock`) goes out of scope, then the lock will be released automatically as soon as the object is garbage collected. This is more of a safety mechanism, and the user should still `unlock()` locks manually, maybe using `base::on.exit()`, so that the lock is released in case of errors as well, as soon as possible. ### Special File Systems: File locking needs support from the file system, and some *non-standard* file systems do not support it. For example on network file systems like NFS or CIFS, user mode file systems like `sshfs` or `ftpfs`, etc., support might vary. Recent Linux versions and recent NFS versions (from version 3) do support file locking, if enabled. In theory it is possible to simply test for lock support, using two child processes and a timeout, but `filelock` does not do this currently. ### Locking Part of a File: While this is possible in general, `filelock` does not suport it currently. The main purpose of `filelock` is to lock using special lock files, and locking part of these is not really useful. ### Internals on Unix: On Unix (i.e. Linux, macOS, etc.), we use `fcntl` to acquire and release the locks. You can read more about it here: Some important points: - The lock is put on a file descriptor, which is kept open, until the lock is released. - A process can only have one kind of lock set for a given file. - When any file descriptor for that file is closed by the process, all of the locks that process holds on that file are released, even if the locks were made using other descriptors that remain open. Note that in R, using a one-shot function call to modify the file opens and closes a file descriptor to it, so the lock will be released. (This is one of the main reasons for using special lock files, instead of putting the lock on the actual file.) - Locks are not inherited by child processes created using fork. - For lock requests with finite timeout intervals, we set an alarm, and temporarily install a signal handler for it. R is single threaded, so no other code can be running, while the process is waiting to acquire the lock. The signal handler is restored to its original value immediately after the lock is acquired or the timeout expires. (It is actually restored from the signal handler, so there should be no race conditions here. However, if multiple `SIGALRM` signals are delivered via a single call to the signal handler, then alarms might get lost. Currently base R does not use the `SIGALRM` signal for anything, but other packages might.) ### Internals on Windows: On Windows, `LockFileEx` is used to create the lock on the file. If a finite timeout is specified for the lock request, asynchronous (overlapped) I/O is used to wait for the locking event with a timeout. See more about `LockFileEx` on the first hit here: Some important points: - `LockFileEx` locks are mandatory (as opposed to advisory), so indeed no other processes have access to the locked file. Actually, even the locking process has no access to it through a different file handle, than the one used for locking. In general, R cannot read from the locked file, and cannot write to it. (Although, the current R version does not fail, it just does nothing, which is quite puzzling.) Remember, always use a special lock file, instead of putting the lock on the main file, so that you are not affected by these problems. - Inherited handles do not provide access to the child process. ## Code of Conduct Please note that the fs project is released with a [Contributor Code of Conduct](https://r-lib.github.io/filelock/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. ## License MIT © RStudio filelock/man/0000755000176200001440000000000014521174551012622 5ustar liggesusersfilelock/man/filelock-package.Rd0000644000176200001440000000134714535451241016276 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/filelock-package.R \docType{package} \name{filelock-package} \alias{filelock} \alias{filelock-package} \title{filelock: Portable File Locking} \description{ Place an exclusive or shared lock on a file. It uses 'LockFile' on Windows and 'fcntl' locks on Unix-like systems. } \seealso{ Useful links: \itemize{ \item \url{https://r-lib.github.io/filelock/} \item \url{https://github.com/r-lib/filelock} \item Report bugs at \url{https://github.com/r-lib/filelock/issues} } } \author{ \strong{Maintainer}: Gábor Csárdi \email{csardi.gabor@gmail.com} Other contributors: \itemize{ \item Posit Software, PBC [copyright holder, funder] } } \keyword{internal} filelock/man/lock.Rd0000644000176200001440000001613614521174551014050 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/package.R \name{lock} \alias{lock} \alias{unlock} \title{Advisory File Locking and Unlocking} \usage{ lock(path, exclusive = TRUE, timeout = Inf) unlock(lock) } \arguments{ \item{path}{Path to the file to lock. If the file does not exist, it will be created, but the directory of the file must exist. \emph{Do not place the lock on a file that you want to read from or write to!} *Always use a special lock file. See details below.} \item{exclusive}{Whether to acquire an exclusive lock. An exclusive lock gives the process exclusive access to the file, no other processes can place any kind of lock on it. A non-exclusive lock is a shared lock. Multiple processes can hold a shared lock on the same file. A process that writes to a file typically requests an exclusive lock, and a process that reads from it typically requests a shared lock.} \item{timeout}{Timeout to acquire the lock in milliseconds. If \code{Inf}, then the process will wait indefinitely to acquire the lock. If zero, then the function it returns immediately, with or without acquiring the lock} \item{lock}{The lock object to unlock. It is not an error to try to unlock an already unlocked lock. It is not possible to lock an unlocked lock again, a new lock has to be requested.} } \value{ \code{lock} returns a \code{filelock_lock} object if the lock was successfully acquired, and \code{NULL} if a timeout happened. \code{unlock} returns \code{TRUE}, always. } \description{ There are two kinds of locks, \emph{exclusive} and \emph{shared}, see the \code{exclusive} argument and other details below. } \section{Warning}{ Always use special files for locking. I.e. if you want to restrict access to a certain file, do \emph{not} place the lock on this file. Create a special file, e.g. by appending \code{.lock} to the original file name and place the lock on that. (The \code{lock()} function creates the file for you, actually, if it does not exist.) Reading from or writing to a locked file has undefined behavior! (See more about this below at the Internals Section.) It is hard to determine whether and when it is safe to remove these special files, so our current recommendation is just to leave them around. It is best to leave the special lock file empty, simply because on some OSes you cannot write to it (or read from it), once the lock is in place. } \section{Advisory Locks}{ All locks set by this package might be advisory. A process that does not respect this locking mechanism may be able to read and write the locked file, or even remove it (assuming it has capabilities to do so). } \section{Unlock on Termination}{ If a process terminates (with a normal exit, a crash or on a signal), the lock(s) it is holding are automatically released. If the R object that represents the lock (the return value of \code{lock}) goes out of scope, then the lock will be released automatically as soon as the object is garbage collected. This is more of a safety mechanism, and the user should still \code{unlock()} locks manually, maybe using \code{\link[base:on.exit]{base::on.exit()}}, so that the lock is released in case of errors as well, as soon as possible. } \section{Special File Systems}{ File locking needs support from the file system, and some \emph{non-standard} file systems do not support it. For example on network file systems like NFS or CIFS, user mode file systems like \code{sshfs} or \code{ftpfs}, etc., support might vary. Recent Linux versions and recent NFS versions (from version 3) do support file locking, if enabled. In theory it is possible to simply test for lock support, using two child processes and a timeout, but \code{filelock} does not do this currently. } \section{Locking Part of a File}{ While this is possible in general, \code{filelock} does not support it currently. The main purpose of \code{filelock} is to lock using special lock files, and locking part of these is not really useful. } \section{Internals on Unix}{ On Unix (i.e. Linux, macOS, etc.), we use \code{fcntl} to acquire and release the locks. You can read more about it here: \url{https://www.gnu.org/software/libc/manual/html_node/File-Locks.html} Some important points: \itemize{ \item The lock is put on a file descriptor, which is kept open, until the lock is released. \item A process can only have one kind of lock set for a given file. \item When any file descriptor for that file is closed by the process, all of the locks that process holds on that file are released, even if the locks were made using other descriptors that remain open. Note that in R, using a one-shot function call to modify the file opens and closes a file descriptor to it, so the lock will be released. (This is one of the main reasons for using special lock files, instead of putting the lock on the actual file.) \item Locks are not inherited by child processes created using fork. \item For lock requests with finite timeout intervals, we set an alarm, and temporarily install a signal handler for it. R is single threaded, so no other code can be running, while the process is waiting to acquire the lock. The signal handler is restored to its original value immediately after the lock is acquired or the timeout expires. (It is actually restored from the signal handler, so there should be no race conditions here. However, if multiple \code{SIGALRM} signals are delivered via a single call to the signal handler, then alarms might get lost. Currently base R does not use the \code{SIGALRM} signal for anything, but other packages might.) } } \section{Internals on Windows}{ On Windows, \code{LockFileEx} is used to create the lock on the file. If a finite timeout is specified for the lock request, asynchronous (overlapped) I/O is used to wait for the locking event with a timeout. See more about \code{LockFileEx} on the first hit here: \url{https://www.google.com/search?q=LockFileEx} Some important points: \itemize{ \item \code{LockFileEx} locks are mandatory (as opposed to advisory), so indeed no other processes have access to the locked file. Actually, even the locking process has no access to it through a different file handle, than the one used for locking. In general, R cannot read from the locked file, and cannot write to it. (Although, the current R version does not fail, it just does nothing, which is quite puzzling.) Remember, always use a special lock file, instead of putting the lock on the main file, so that you are not affected by these problems. \item Inherited handles do not provide access to the child process. } } \section{Examples}{ \if{html}{\out{
}}\preformatted{## ------------------------------------------------------------- ## R process 1 gets an exclusive lock ## Warning: if you want to lock file 'myfile', always create a ## separate lock file instead of placing the lock on this file directly! lck <- lock(mylockfile) ## ------------------------------------------------------------- ## R process 2 fails to acquire a lock lock(mylockfile, timeout = 0) ## Let's wait for 5 seconds, before giving up lock(mylockfile, timeout = 5000) ## Wait indefinetely lock(mylockfile, timeout = Inf) }\if{html}{\out{
}} } filelock/DESCRIPTION0000644000176200001440000000166314535511242013560 0ustar liggesusersPackage: filelock Title: Portable File Locking Version: 1.0.3 Authors@R: c( person("Gábor", "Csárdi", , "csardi.gabor@gmail.com", role = c("aut", "cre")), person("Posit Software, PBC", role = c("cph", "fnd")) ) Description: Place an exclusive or shared lock on a file. It uses 'LockFile' on Windows and 'fcntl' locks on Unix-like systems. License: MIT + file LICENSE URL: https://r-lib.github.io/filelock/, https://github.com/r-lib/filelock BugReports: https://github.com/r-lib/filelock/issues Depends: R (>= 3.4) Suggests: callr (>= 2.0.0), covr, testthat (>= 3.0.0) Config/Needs/website: tidyverse/tidytemplate Config/testthat/edition: 3 Encoding: UTF-8 RoxygenNote: 7.2.3 NeedsCompilation: yes Packaged: 2023-12-11 01:12:05 UTC; gaborcsardi Author: Gábor Csárdi [aut, cre], Posit Software, PBC [cph, fnd] Maintainer: Gábor Csárdi Repository: CRAN Date/Publication: 2023-12-11 04:40:02 UTC filelock/tests/0000755000176200001440000000000014141557640013213 5ustar liggesusersfilelock/tests/testthat/0000755000176200001440000000000014535511242015046 5ustar liggesusersfilelock/tests/testthat/test.R0000644000176200001440000001613314521174551016157 0ustar liggesusers test_that("can create a shared lock", { tmp <- tempfile() expect_silent({ lck <- lock(tmp, exclusive = FALSE) unlock(lck) }) }) test_that("can create an exclusive lock", { tmp <- tempfile() expect_silent({ lck <- lock(tmp, exclusive = TRUE) unlock(lck) }) }) test_that("an exclusive lock really locks", { tmp <- tempfile() lck <- lock(tmp, exclusive = TRUE) res <- callr::r_safe( function(path) filelock::lock(path, timeout = 0), list(path = tmp), timeout = 3, spinner = FALSE ) expect_null(res) unlock(lck) }) test_that("can release a lock", { tmp <- tempfile() lck <- lock(tmp, exclusive = TRUE) res <- callr::r_safe( function(path) filelock::lock(path, timeout = 0), list(path = tmp), timeout = 3, spinner = FALSE ) expect_null(res) unlock(lck) res <- callr::r_safe( function(path) filelock::lock(path, timeout = 0), list(path = tmp), timeout = 3, spinner = FALSE ) ## By the time it gets here, it will be unlocked, because it is ## an external pointer, so we cannot save it to file, and the child ## process finishes, anyway. expect_equal(class(res), "filelock_lock") }) test_that("printing the lock", { tmp <- tempfile() lck <- lock(tmp, exclusive = TRUE) expect_output(print(lck), "Lock on") expect_output(print(lck), basename(normalizePath(tmp)), fixed = TRUE) unlock(lck) expect_output(print(lck), "Unlocked lock on") expect_output(print(lck), basename(normalizePath(tmp)), fixed = TRUE) }) test_that("finalizer works", { tmp <- tempfile() lck <- lock(tmp, exclusive = TRUE) rm(lck) gc() res <- callr::r_safe( function(path) filelock::lock(path, timeout = 0), list(path = tmp), timeout = 3, spinner = FALSE ) expect_equal(class(res), "filelock_lock") }) test_that("timeout", { tmp <- tempfile() lck <- lock(tmp, exclusive = TRUE) tic <- Sys.time() res <- callr::r_safe( function(path) filelock::lock(path, timeout = 1000), list(path = tmp), timeout = 3, spinner = FALSE ) tac <- Sys.time() expect_null(res) expect_true(tac - tic >= as.difftime(1, units = "secs")) }) test_that("timeout 2", { ## They don't like tests with timings on CRAN skip_on_cran() tmp <- tempfile() px1_opts <- callr::r_process_options( func = function(path) { lck <- filelock::lock(path) Sys.sleep(1) filelock::unlock(lck) }, args = list(path = tmp) ) px1 <- callr::r_process$new(px1_opts) px2_opts <- callr::r_process_options( func = function(path) filelock::lock(path, timeout = 2000), args = list(path = tmp) ) px2 <- callr::r_process$new(px2_opts) px2$wait(timeout = 5000) if (!px2$is_alive()) { res <- px2$get_result() expect_equal(class(res), "filelock_lock") } else { px1$kill() px2$kill() stop("Process did not finish, something is wrong") } }) test_that("wait forever", { ## Thy don't like tests with timings on CRAN skip_on_cran() tmp <- tempfile() px1_opts <- callr::r_process_options( func = function(path) { lck <- filelock::lock(path) Sys.sleep(10) }, args = list(path = tmp) ) px1 <- callr::r_process$new(px1_opts) px2_opts <- callr::r_process_options( func = function(path) filelock::lock(path, timeout = Inf), args = list(path = tmp) ) px2 <- callr::r_process$new(px2_opts) px1$kill() px2$wait(timeout = 2000) if (!px2$is_alive()) { expect_true(px2$get_exit_status() == 0) } else { px2$kill() stop("psx2 still running, something is wrong") } }) test_that("wait forever, lock released", { tmp <- tempfile() ## This process just finishes normally, and that releases the lock px1_opts <- callr::r_process_options( func = function(path) { lck <- filelock::lock(path) Sys.sleep(1) }, args = list(path = tmp) ) px1 <- callr::r_process$new(px1_opts) px2_opts <- callr::r_process_options( func = function(path) filelock::lock(path, timeout = Inf), args = list(path = tmp) ) px2 <- callr::r_process$new(px2_opts) px2$wait(timeout = 3000) if (!px2$is_alive()) { res <- px2$get_result() expect_equal(class(res), "filelock_lock") } else { px1$kill() px2$kill() stop("Process did not finish, something is wrong") } }) test_that("locking the same file twice", { tmp <- tempfile() expect_silent({ lck <- lock(tmp, exclusive = TRUE) }) expect_silent({ lck2 <- lock(tmp, exclusive = TRUE) }) expect_identical(lck, lck2) unlock(lck) unlock(lck2) expect_identical(lck, lck2) }) test_that("lock reference counting", { tmp <- tempfile() ## Two locks of the same kind expect_silent({ lck <- lock(tmp, exclusive = TRUE) lck2 <- lock(tmp, exclusive = TRUE) unlock(lck) }) ## File is still locked res <- callr::r_safe( function(path) filelock::lock(path, timeout = 0), list(path = tmp), timeout = 3, spinner = FALSE ) expect_null(res) ## Now it is unlocked unlock(lck2) res <- callr::r_safe( function(path) filelock::lock(path, timeout = 0), list(path = tmp), timeout = 3, spinner = FALSE ) expect_equal(class(res), "filelock_lock") ## Relock expect_silent({ lck3 <- lock(tmp, exclusive = TRUE) }) ## Now it is locked again res <- callr::r_safe( function(path) filelock::lock(path, timeout = 0), list(path = tmp), timeout = 3, spinner = FALSE ) expect_null(res) unlock(lck3) }) test_that("Multiple locks", { tmp <- tempfile() lck <- lock(tmp, exclusive = TRUE) lck2 <- lock(tmp, exclusive = TRUE) unlock(lck) expect_output(print(lck), "Unlocked lock") expect_output(print(lck2), "^Lock") }) test_that("Relocking does not affect unlocked locks", { tmp <- tempfile() lck <- lock(tmp, exclusive = TRUE) lck2 <- lock(tmp, exclusive = TRUE) unlock(lck) ## Relock lck3 <- lock(tmp, exclusive = TRUE) expect_output(print(lck), "Unlocked lock") expect_output(print(lck2), "^Lock") expect_output(print(lck3), "^Lock") unlock(lck2) unlock(lck3) }) test_that("Multiple, incompatible lock types", { tmp <- tempfile() lck <- lock(tmp, exclusive = TRUE) expect_error(lock(tmp, exclusive = FALSE)) unlock(lck) lck <- lock(tmp, exclusive = FALSE) expect_error(lock(tmp, exclusive = TRUE)) unlock(lck) }) test_that("UTF-8 filenames", { tmp <- paste(tempfile(), "-\u00fc.lock") ## We need to test it the file system supports UTF-8/Unicode file names good <- tryCatch( { cat("hello\n", file = tmp) if (readLines(tmp) != "hello") stop("Not good") unlink(tmp) TRUE }, error = function(e) FALSE ) if (identical(good, FALSE)) skip("FS does not support Unicode file names") expect_silent(l <- lock(tmp)) expect_equal(Encoding(l[[2]]), "UTF-8") expect_silent(unlock(l)) }) ## This used to fail on Windows test_that("non-exclusive lock with timeout", { lockfile <- tempfile() l <- lock(lockfile, exclusive = FALSE, timeout = 1000) expect_s3_class(l, "filelock_lock") expect_true(unlock(l)) }) test_that("unlock() needs lock object", { expect_error(unlock(1), "needs a lock object") }) filelock/tests/testthat.R0000644000176200001440000000020714141557640015175 0ustar liggesusers if (Sys.getenv("NOT_CRAN", "") != "") { Sys.setenv(R_TESTS = "") library(testthat) library(filelock) test_check("filelock") } filelock/src/0000755000176200001440000000000014535460744012645 5ustar liggesusersfilelock/src/locklist.c0000644000176200001440000000342014141557640014627 0ustar liggesusers #include #include #include "filelock.h" filelock__list_t lock_list_head = { 0, 0 }; filelock__list_t *lock_list = &lock_list_head; SEXP filelock__make_lock_handle(filelock__list_t *node) { SEXP ptr, result, path; ptr = PROTECT(R_MakeExternalPtr(node, R_NilValue, R_NilValue)); R_RegisterCFinalizerEx(ptr, filelock__finalizer, 0); path = PROTECT(allocVector(STRSXP, 1)); SET_STRING_ELT(path, 0, mkCharCE(node->path, CE_UTF8)); result = PROTECT(allocVector(VECSXP, 2)); SET_VECTOR_ELT(result, 0, ptr); SET_VECTOR_ELT(result, 1, path); UNPROTECT(3); node->refcount += 1; return result; } #ifdef _WIN32 SEXP filelock__list_add(const char *path, HANDLE file, int exclusive) { #else SEXP filelock__list_add(const char *path, int file, int exclusive) { #endif filelock__list_t *node; node = calloc(1, sizeof(filelock__list_t)); if (!node) error("Out of memory"); node->path = strdup(path); node->file = file; node->exclusive = exclusive; node->refcount = 0; if (!node->path) { free(node); error("Out of memory"); } node->next = lock_list->next; lock_list->next = node; return filelock__make_lock_handle(node); } void filelock__list_remove(const char *path) { filelock__list_t *prev = lock_list, *ptr = lock_list->next; while (ptr) { if (!strcmp(ptr->path, path)) { prev->next = ptr->next; free(ptr->path); free(ptr); return; } prev = ptr; ptr = ptr->next; } } filelock__list_t *filelock__list_find(const char *path) { filelock__list_t *ptr = lock_list->next; while (ptr) { if (!strcmp(ptr->path, path)) { /* TODO: check that the lock is valid, because it might have been unlocked through another lock object. */ return ptr; } ptr = ptr->next; } return 0; } filelock/src/filelock-unix.c0000644000176200001440000001071414407554352015562 0ustar liggesusers #include #include #include #include #include #include #include #include #include #include "filelock.h" #define FILELOCK_INTERRUPT_INTERVAL 200 struct sigaction filelock_old_sa; void filelock__finalizer(SEXP x) { filelock__list_t *ptr = (filelock__list_t*) R_ExternalPtrAddr(x); if (!ptr) return; ptr->refcount -= 1; if (!ptr->refcount) { close(ptr->file); filelock__list_remove(ptr->path); } R_ClearExternalPtr(x); } void filelock__alarm_callback (int signum) { /* Restore signal handler */ sigaction(SIGALRM, &filelock_old_sa, 0); } int filelock__interruptible(int filedes, struct flock *lck, const char *c_path, int c_exclusive, int c_timeout) { struct itimerval timer; struct sigaction sa; int timeleft = c_timeout; int ret = 1; while (c_timeout < 0 || timeleft > 0) { /* If block forever, then always use the interrupt interval, If timeout and just a little time left, use that. */ int waitnow = FILELOCK_INTERRUPT_INTERVAL; if (c_timeout >= 0 && timeleft < FILELOCK_INTERRUPT_INTERVAL) { waitnow = timeleft; } timer.it_value.tv_sec = waitnow / 1000; timer.it_value.tv_usec = (waitnow % 1000) * 1000; timer.it_interval.tv_sec = 0; timer.it_interval.tv_usec = 0; memset(&sa, 0, sizeof (sa)); sa.sa_handler = &filelock__alarm_callback; sigaction(SIGALRM, &sa, &filelock_old_sa); setitimer(ITIMER_REAL, &timer, 0); ret = fcntl(filedes, F_SETLKW, lck); /* We need to remove the timer here, to avoid getting a signal later. A later signal may kill the R process. */ timer.it_value.tv_sec = 0; timer.it_value.tv_usec = 0; timer.it_interval.tv_sec = 0; timer.it_interval.tv_usec = 0; setitimer(ITIMER_REAL, &timer, 0); /* If ret != -1, then we have the lock, return. If -1, but not EINTR, then a real error happened. */ if (ret != -1) { ret = 0; break; } if (ret == -1 && errno != EINTR) { error("Cannot lock file: '%s': %s", c_path, strerror(errno)); } /* Otherwise, need to wait, check for interrupts, and start over */ R_CheckUserInterrupt(); timeleft -= FILELOCK_INTERRUPT_INTERVAL; } return ret; } SEXP filelock_lock(SEXP path, SEXP exclusive, SEXP timeout) { struct flock lck; const char *c_path = CHAR(STRING_ELT(path, 0)); int c_exclusive = LOGICAL(exclusive)[0]; int c_timeout = INTEGER(timeout)[0]; int filedes, ret; /* Check if this file was already locked. */ filelock__list_t *node = filelock__list_find(c_path); if (node) { if ((c_exclusive && node->exclusive) || (!c_exclusive && !node->exclusive)) { return filelock__make_lock_handle(node); } else if (c_exclusive) { error("File already has a shared lock"); } else { error("File already has an exclusive lock"); } } lck.l_type = c_exclusive ? F_WRLCK : F_RDLCK; lck.l_whence = SEEK_SET; lck.l_start = 0; lck.l_len = 0; filedes = open(c_path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); if (filedes == -1) error("Cannot open lock file: %s", strerror(errno)); /* One shot only? Do not block if cannot lock */ if (c_timeout == 0) { ret = fcntl(filedes, F_SETLK, &lck); if (ret == -1) { if (errno == EAGAIN || errno == EACCES) { return R_NilValue; } error("Cannot lock file: '%s': %s", c_path, strerror(errno)); } } else { ret = filelock__interruptible(filedes, &lck, c_path, c_exclusive, c_timeout); } /* Failed to acquire the lock? */ if (ret) { return R_NilValue; } else { return filelock__list_add(c_path, filedes, c_exclusive); } } SEXP filelock_unlock(SEXP lock) { void *ptr = R_ExternalPtrAddr(VECTOR_ELT(lock, 0)); const char *c_path; filelock__list_t *node; if (!ptr) return ScalarLogical(1); c_path = CHAR(STRING_ELT(VECTOR_ELT(lock, 1), 0)); node = filelock__list_find(c_path); /* It has to be there.... */ if (node) { node->refcount -= 1; if (!node->refcount) { close(node->file); filelock__list_remove(c_path); } } R_ClearExternalPtr(VECTOR_ELT(lock, 0)); return ScalarLogical(1); } SEXP filelock_is_unlocked(SEXP lock) { void *ptr = R_ExternalPtrAddr(VECTOR_ELT(lock, 0)); if (ptr) { const char *c_path = CHAR(STRING_ELT(VECTOR_ELT(lock, 1), 0)); int inlist = filelock__list_find(c_path) != 0; return ScalarLogical(! inlist); } else { return ScalarLogical(1); } } filelock/src/init.c0000644000176200001440000000071214521143620013736 0ustar liggesusers #include "filelock.h" static const R_CallMethodDef callMethods[] = { { "filelock_lock", (DL_FUNC) &filelock_lock, 3 }, { "filelock_unlock", (DL_FUNC) &filelock_unlock, 1 }, { "filelock_is_unlocked", (DL_FUNC) &filelock_is_unlocked, 1 }, { NULL, NULL, 0 } }; void R_init_filelock(DllInfo *dll) { R_registerRoutines(dll, NULL, callMethods, NULL, NULL); R_useDynamicSymbols(dll, FALSE); R_forceSymbols(dll, TRUE); } filelock/src/filelock-windows.c0000644000176200001440000001536614535450464016302 0ustar liggesusers #include #include #include #include "filelock.h" #define FILELOCK_INTERRUPT_INTERVAL 200 int filelock__utf8_to_utf16_alloc(const char* s, WCHAR** ws_ptr); void filelock__check_interrupt_fn(void *dummy) { R_CheckUserInterrupt(); } int filelock__is_interrupt_pending(void) { return !(R_ToplevelExec(filelock__check_interrupt_fn, NULL)); } void filelock__error(const char *str, DWORD errorcode) { LPVOID lpMsgBuf; char *msg; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL ); msg = R_alloc(1, strlen(lpMsgBuf) + 1); strcpy(msg, lpMsgBuf); LocalFree(lpMsgBuf); error("Filelock error (%d), %s %s", (int) errorcode, str, msg); } void filelock__finalizer(SEXP x) { filelock__list_t *ptr = (filelock__list_t*) R_ExternalPtrAddr(x); if (!ptr) return; ptr->refcount -= 1; if (!ptr->refcount) { HANDLE *file = &ptr->file; OVERLAPPED ov = { 0 }; UnlockFileEx(*file, 0, 1, 0, &ov); /* ignore errors */ CloseHandle(*file); /* ignore errors */ filelock__list_remove(ptr->path); } R_ClearExternalPtr(x); } int filelock__lock_now(HANDLE file, int exclusive, int *locked) { DWORD dwFlags = LOCKFILE_FAIL_IMMEDIATELY; OVERLAPPED ov = { 0 }; if (exclusive) dwFlags |= LOCKFILE_EXCLUSIVE_LOCK; if (! LockFileEx(file, dwFlags, 0, 1, 0, &ov)) { DWORD error = GetLastError(); *locked = 0; if (error == ERROR_LOCK_VIOLATION) { return 0; } else { return error; } } else { *locked = 1; return 0; } } int filelock__lock_wait(HANDLE file, int exclusive) { DWORD dwFlags = exclusive ? LOCKFILE_EXCLUSIVE_LOCK : 0; BOOL res; OVERLAPPED ov = { 0 }; ov.hEvent = CreateEvent(NULL, 0, 0, NULL); res = LockFileEx(file, dwFlags, 0, 1, 0, &ov); if (!res) { DWORD err = GetLastError(); while (1) { DWORD wres; if (err != ERROR_IO_PENDING) filelock__error("Locking file: ", err); wres = WaitForSingleObject(ov.hEvent, FILELOCK_INTERRUPT_INTERVAL); if (wres == WAIT_TIMEOUT) { /* we'll try again */ } else if (wres == WAIT_OBJECT_0) { CloseHandle(ov.hEvent); return 0; } else if (wres == WAIT_FAILED) { CancelIo(file); CloseHandle(ov.hEvent); filelock__error("Locking file (timeout): ", GetLastError()); } /* Check for interrupt and try again */ if (filelock__is_interrupt_pending()) { CancelIo(file); CloseHandle(ov.hEvent); UnlockFileEx(file, 0, 1, 0, &ov); /* ignore errors */ CloseHandle(file); /* ignore errors */ error("Locking interrupted"); } } } CloseHandle(ov.hEvent); return 0; } int filelock__lock_timeout(HANDLE file, int exclusive, int timeout, int *locked) { DWORD dwFlags = exclusive ? LOCKFILE_EXCLUSIVE_LOCK : 0; BOOL res; int timeleft = timeout; OVERLAPPED ov = { 0 }; /* This is the default, a timeout */ *locked = 0; ov.hEvent = CreateEvent(NULL, 0, 0, NULL); res = LockFileEx(file, dwFlags, 0, 1, 0, &ov); if (!res) { DWORD err = GetLastError(); while (timeleft > 0) { DWORD wres; int waitnow; if (err != ERROR_IO_PENDING) filelock__error("Locking file: ", err); waitnow = timeleft < FILELOCK_INTERRUPT_INTERVAL ? timeleft : FILELOCK_INTERRUPT_INTERVAL; wres = WaitForSingleObject(ov.hEvent, waitnow); if (wres == WAIT_TIMEOUT) { /* we'll try again */ } else if (wres == WAIT_OBJECT_0) { *locked = 1; break; } else { CancelIo(file); CloseHandle(ov.hEvent); filelock__error("Locking file (timeout): ", GetLastError()); } /* Check for interrupt and try again */ if (filelock__is_interrupt_pending()) { CancelIo(file); CloseHandle(ov.hEvent); UnlockFileEx(file, 0, 1, 0, &ov); /* ignore errors */ CloseHandle(file); /* ignore errors */ error("Locking interrupted"); } timeleft -= FILELOCK_INTERRUPT_INTERVAL; } } else { *locked = 1; } CancelIo(file); CloseHandle(ov.hEvent); return 0; } SEXP filelock_lock(SEXP path, SEXP exclusive, SEXP timeout) { const char *c_path = CHAR(STRING_ELT(path, 0)); int c_exclusive = LOGICAL(exclusive)[0]; int c_timeout = INTEGER(timeout)[0]; int ret, locked = 1; /* assume the best :) */ HANDLE file; WCHAR *wpath; /* Check if this file was already locked. */ filelock__list_t *node = filelock__list_find(c_path); if (node) { if ((c_exclusive && node->exclusive) || (!c_exclusive && !node->exclusive)) { return filelock__make_lock_handle(node); } else if (c_exclusive) { error("File already has a shared lock"); } else { error("File already has an exclusive lock"); } } filelock__utf8_to_utf16_alloc(c_path, &wpath); file = CreateFileW( /* lpFilename = */ wpath, /* dwDesiredAccess = */ GENERIC_READ | GENERIC_WRITE, /* dwShareMode = */ FILE_SHARE_READ | FILE_SHARE_WRITE, /* lpSecurityAttributes = */ NULL, /* dwCreationDisposition = */ OPEN_ALWAYS, /* dwFlagsAndAttributes = */ FILE_FLAG_OVERLAPPED, /* hTemplateFile = */ NULL); if (file == INVALID_HANDLE_VALUE) { filelock__error("Opening file: ", GetLastError()); } /* Give it a try, fail immediately */ if (c_timeout == 0) { ret = filelock__lock_now(file, c_exclusive, &locked); /* Wait indefintely */ } else if (c_timeout == -1) { ret = filelock__lock_wait(file, c_exclusive); /* Finite timeout */ } else { ret = filelock__lock_timeout(file, c_exclusive, c_timeout, &locked); } if (ret) { filelock__error("Lock file: ", ret); } if (!locked) { return R_NilValue; } else { return filelock__list_add(c_path, file, c_exclusive); } } SEXP filelock_unlock(SEXP lock) { void *ptr = R_ExternalPtrAddr(VECTOR_ELT(lock, 0)); const char *c_path; filelock__list_t *node; if (!ptr) return ScalarLogical(1); c_path = CHAR(STRING_ELT(VECTOR_ELT(lock, 1), 0)); node = filelock__list_find(c_path); /* It has to be there.... */ if (node) { node->refcount -= 1; if (!node->refcount) { const char *c_path = CHAR(STRING_ELT(VECTOR_ELT(lock, 1), 0)); OVERLAPPED ov = { 0 }; UnlockFileEx(node->file, 0, 1, 0, &ov); /* ignore errors */ CloseHandle(node->file); /* ignore errors */ filelock__list_remove(c_path); } } R_ClearExternalPtr(VECTOR_ELT(lock, 0)); return ScalarLogical(1); } SEXP filelock_is_unlocked(SEXP lock) { void *ptr = R_ExternalPtrAddr(VECTOR_ELT(lock, 0)); if (ptr) { const char *c_path = CHAR(STRING_ELT(VECTOR_ELT(lock, 1), 0)); int inlist = filelock__list_find(c_path) != 0; return ScalarLogical(! inlist); } else { return ScalarLogical(1); } } filelock/src/Makevars0000644000176200001440000000013414426447534014340 0ustar liggesusers OBJECTS = init.o filelock-unix.o locklist.o all: clean clean: rm -f $(SHLIB) $(OBJECTS) filelock/src/Makevars.win0000644000176200001440000000014614141557640015131 0ustar liggesusers OBJECTS = init.o filelock-windows.o locklist.o utf8.o all: clean clean: rm -f $(SHLIB) $(OBJECTS) filelock/src/utf8.c0000644000176200001440000000166114141557640013676 0ustar liggesusers #include #include #include "filelock.h" void filelock__error(const char *str, DWORD errorcode); int filelock__utf8_to_utf16_alloc(const char* s, WCHAR** ws_ptr) { int ws_len, r; WCHAR* ws; ws_len = MultiByteToWideChar( /* CodePage = */ CP_UTF8, /* dwFlags = */ 0, /* lpMultiByteStr = */ s, /* cbMultiByte = */ -1, /* lpWideCharStr = */ NULL, /* cchWideChar = */ 0); if (ws_len <= 0) { filelock__error("Cannot convert UTF8 file name to wide chararacter", GetLastError()); } ws = (WCHAR*) R_alloc(ws_len, sizeof(WCHAR)); r = MultiByteToWideChar( /* CodePage = */ CP_UTF8, /* dwFlags = */ 0, /* lpMultiByteStr = */ s, /* cbMultiBytes = */ -1, /* lpWideCharStr = */ ws, /* cchWideChar = */ ws_len); if (r != ws_len) { error("filelock error interpreting UTF8 filename"); } *ws_ptr = ws; return 0; } filelock/src/filelock.h0000644000176200001440000000156614141557640014611 0ustar liggesusers #include #include #include #ifdef _WIN32 #include #endif SEXP filelock_lock(SEXP path, SEXP exclusive, SEXP timeout); SEXP filelock_unlock(SEXP path); SEXP filelock_is_unlocked(SEXP lock); /* ------------------------------------------------------------------ */ /* INTERNALS */ typedef struct filelock__list_s { char *path; int refcount; int exclusive; #ifdef _WIN32 HANDLE file; #else int file; #endif struct filelock__list_s *next; } filelock__list_t; #ifdef _WIN32 SEXP filelock__list_add(const char *path, HANDLE file, int exclusive); #else SEXP filelock__list_add(const char *path, int file, int exclusive); #endif SEXP filelock__make_lock_handle(filelock__list_t *node); void filelock__list_remove(const char *path); filelock__list_t *filelock__list_find(const char *path); void filelock__finalizer(SEXP x); filelock/R/0000755000176200001440000000000014521174551012250 5ustar liggesusersfilelock/R/assertions.R0000644000176200001440000000036214141557640014570 0ustar liggesusers is_string <- function(x) { is.character(x) && length(x) == 1 && !is.na(x) } is_flag <- function(x) { is.logical(x) && length(x) == 1 && !is.na(x) } is_timeout <- function(x) { is.numeric(x) && length(x) == 1 && !is.na(x) && x >= 0 } filelock/R/filelock-package.R0000644000176200001440000000013514521174551015553 0ustar liggesusers#' @keywords internal "_PACKAGE" ## usethis namespace: start ## usethis namespace: end NULL filelock/R/package.R0000644000176200001440000002006214521174551013766 0ustar liggesusers #' Advisory File Locking and Unlocking #' #' There are two kinds of locks, *exclusive* and *shared*, see the #' `exclusive` argument and other details below. #' #' @section Warning: #' Always use special files for locking. I.e. if you want to restrict access #' to a certain file, do *not* place the lock on this file. Create a special #' file, e.g. by appending `.lock` to the original file name and place the #' lock on that. (The `lock()` function creates the file for you, actually, #' if it does not exist.) Reading from or writing to a locked file has #' undefined behavior! (See more about this below at the Internals Section.) #' #' It is hard to determine whether and when it is safe to remove these #' special files, so our current recommendation is just to leave them #' around. #' #' It is best to leave the special lock file empty, simply because on some #' OSes you cannot write to it (or read from it), once the lock is in place. #' #' @section Advisory Locks: #' All locks set by this package might be advisory. A process that does not #' respect this locking mechanism may be able to read and write the locked #' file, or even remove it (assuming it has capabilities to do so). #' #' @section Unlock on Termination: #' If a process terminates (with a normal exit, a crash or on a signal), the #' lock(s) it is holding are automatically released. #' #' If the R object that represents the lock (the return value of `lock`) #' goes out of scope, then the lock will be released automatically as #' soon as the object is garbage collected. This is more of a safety #' mechanism, and the user should still `unlock()` locks manually, maybe #' using [base::on.exit()], so that the lock is released in case of errors #' as well, as soon as possible. #' #' @section Special File Systems: #' File locking needs support from the file system, and some *non-standard* #' file systems do not support it. For example on network file systems #' like NFS or CIFS, user mode file systems like `sshfs` or `ftpfs`, etc., #' support might vary. Recent Linux versions and recent NFS versions (from #' version 3) do support file locking, if enabled. #' #' In theory it is possible to simply test for lock support, using two #' child processes and a timeout, but `filelock` does not do this #' currently. #' #' @section Locking Part of a File: #' While this is possible in general, `filelock` does not support it #' currently. The main purpose of `filelock` is to lock using special #' lock files, and locking part of these is not really useful. #' #' @section Internals on Unix: #' On Unix (i.e. Linux, macOS, etc.), we use `fcntl` to acquire and #' release the locks. You can read more about it here: #' #' #' Some important points: #' * The lock is put on a file descriptor, which is kept open, until the #' lock is released. #' * A process can only have one kind of lock set for a given file. #' * When any file descriptor for that file is closed by the process, all #' of the locks that process holds on that file are released, even if #' the locks were made using other descriptors that remain open. #' Note that in R, using a one-shot function call to modify the file #' opens and closes a file descriptor to it, so the lock will be #' released. (This is one of the main reasons for using special lock #' files, instead of putting the lock on the actual file.) #' * Locks are not inherited by child processes created using fork. #' * For lock requests with finite timeout intervals, we set an alarm, and #' temporarily install a signal handler for it. R is single threaded, #' so no other code can be running, while the process is waiting to #' acquire the lock. The signal handler is restored to its original value #' immediately after the lock is acquired or the timeout expires. #' (It is actually restored from the signal handler, so there should be #' no race conditions here. However, if multiple `SIGALRM` signals are #' delivered via a single call to the signal handler, then alarms might #' get lost. Currently base R does not use the `SIGALRM` signal for #' anything, but other packages might.) #' #' @section Internals on Windows: #' On Windows, `LockFileEx` is used to create the lock on the file. #' If a finite timeout is specified for the lock request, asynchronous #' (overlapped) I/O is used to wait for the locking event with a timeout. #' See more about `LockFileEx` on the first hit here: #' #' #' Some important points: #' * `LockFileEx` locks are mandatory (as opposed to advisory), so indeed #' no other processes have access to the locked file. Actually, even the #' locking process has no access to it through a different file handle, #' than the one used for locking. In general, R cannot read from the #' locked file, and cannot write to it. (Although, the current R version #' does not fail, it just does nothing, which is quite puzzling.) #' Remember, always use a special lock file, instead of putting the lock #' on the main file, so that you are not affected by these problems. #' * Inherited handles do not provide access to the child process. #' #' @param path Path to the file to lock. If the file does not exist, it #' will be created, but the directory of the file must exist. #' *Do not place the lock on a file that you want to #' read from or write to!* *Always use a special lock file. See details #' below. #' @param exclusive Whether to acquire an exclusive lock. An exclusive #' lock gives the process exclusive access to the file, no other #' processes can place any kind of lock on it. A non-exclusive lock is a #' shared lock. Multiple processes can hold a shared lock on the same #' file. A process that writes to a file typically requests an #' exclusive lock, and a process that reads from it typically requests a #' shared lock. #' @param timeout Timeout to acquire the lock in milliseconds. If `Inf`, #' then the process will wait indefinitely to acquire the lock. If zero, #' then the function it returns immediately, with or without acquiring #' the lock #' @param lock The lock object to unlock. It is not an error to try to #' unlock an already unlocked lock. It is not possible to lock an #' unlocked lock again, a new lock has to be requested. #' #' @return `lock` returns a `filelock_lock` object if the lock was #' successfully acquired, and `NULL` if a timeout happened. #' #' `unlock` returns `TRUE`, always. #' #' @section Examples: #' ``` #' ## ------------------------------------------------------------- #' ## R process 1 gets an exclusive lock #' ## Warning: if you want to lock file 'myfile', always create a #' ## separate lock file instead of placing the lock on this file directly! #' lck <- lock(mylockfile) #' #' ## ------------------------------------------------------------- #' ## R process 2 fails to acquire a lock #' lock(mylockfile, timeout = 0) #' #' ## Let's wait for 5 seconds, before giving up #' lock(mylockfile, timeout = 5000) #' #' ## Wait indefinetely #' lock(mylockfile, timeout = Inf) #' ``` #' #' @export #' @useDynLib filelock, .registration = TRUE, .fixes = "c_" lock <- function(path, exclusive = TRUE, timeout = Inf) { stopifnot(is_string(path)) stopifnot(is_flag(exclusive)) stopifnot(is_timeout(timeout)) ## Inf if encoded as -1 in our C code if (timeout == Inf) timeout <- -1L dn <- dirname(path) ndn <- normalizePath(dn) if (!file.exists(ndn)) { stop("Directory of lock file does not exist: '", dn, "'") } path <- file.path(ndn, basename(path)) res <- .Call(c_filelock_lock, enc2utf8(path), exclusive, as.integer(timeout)) if (is.null(res)) res else structure(res, class = "filelock_lock") } #' @export #' @rdname lock unlock <- function(lock) { if (!inherits(lock, "filelock_lock")) { stop("`unlock()` needs a lock object, not a file name") } .Call(c_filelock_unlock, lock) } #' @export print.filelock_lock <- function(x, ...) { unlocked <- .Call(c_filelock_is_unlocked, x) cat( if (unlocked) "Unlocked lock on " else "Lock on ", sQuote(x[[2]]), "\n", sep = "" ) invisible(x) } filelock/NEWS.md0000644000176200001440000000042314535460662013152 0ustar liggesusers# filelock 1.0.3 * No user visible changes. # filelock 1.0.2 * `lock()` now removes the timer on Unix, to avoid undefined behavior in non-interactive R sessions, when a SIGALRM is delivered after the process acquired the lock. # filelock 1.0.1 First public release. filelock/MD50000644000176200001440000000200014535511242012344 0ustar liggesusersa053300cd78092639e2abd2a4b398c34 *DESCRIPTION 018874858c4cf334dfd9f645d2c9a434 *LICENSE 448e778dd04fb3b77a8b8bbe709d0c8c *NAMESPACE 4f18909dbbfb1fb1955eba5110d6526b *NEWS.md 6b18dd66ef279f9455eb3d152e70f766 *R/assertions.R df6cc46bc7fae1a55b713f3d5065b35a *R/filelock-package.R 07e3631c672eb9a3fb9718a5750f7b9a *R/package.R cf601558bd60be8fbf428ca9d67dcafc *README.md 03bc8ed0b75a978b3546efd86dcc1ac1 *inst/WORDLIST c19fbd34970a5802afa391feaa541238 *man/filelock-package.Rd 3da4c8f0d328ccdf547e5821871f01d9 *man/lock.Rd a1e833fd7620bbcf555d3759a6c0a7a3 *src/Makevars 741f22b6afea94257aeb4cd52fc3ebb2 *src/Makevars.win 1d1ab2fdb58ee240245f9033c6e71040 *src/filelock-unix.c db02b75d5d0d379cd5d11c11cf88646b *src/filelock-windows.c da692f2a7f1b23ce14d0c720fa3256e6 *src/filelock.h 545e07b5aab8486675a2a37aa85a01ac *src/init.c 8373b4528254816d94c2993c99d4eb1d *src/locklist.c d37ed8d6bcb194d9fa3a192318888ef3 *src/utf8.c beb85aa4656398076e1d58dd2598fb18 *tests/testthat.R 17cf082f2f8beefdcd2f431f4d822e92 *tests/testthat/test.R filelock/inst/0000755000176200001440000000000014141557640013026 5ustar liggesusersfilelock/inst/WORDLIST0000644000176200001440000000003614141557640014217 0ustar liggesusersCIFS fcntl LockFile macOS NFS