From c13118e28d4728533ecaf5d6abe044993c98065a Mon Sep 17 00:00:00 2001 From: Christopher Wellons Date: Thu, 22 Oct 2026 21:08:02 -0400 Subject: [PATCH] Make flock exclude other locks on Windows busybox-w32 locked the file's bytes from 0 to its current size, so on an empty lock file, the usual case, flock locked nothing, and on a non-empty file Windows' mandatory locks kept even the locked command from reading or writing it. Lock one byte far past the end of the file instead, which behaves like flock(2). Also let flock -n fail silently and flock -u succeed, as on Unix. Co-Authored-By: Claude Opus 5.5 --- man/man1/flock.1 | 80 +++++++++++-------------- src/busybox/flock-range.patch | 108 ++++++++++++++++++++++++++++++++++ src/busybox/series | 1 + 3 files changed, 143 insertions(+), 46 deletions(-) create mode 100644 src/busybox/flock-range.patch diff --git a/man/man1/flock.1 b/man/man1/flock.1 index 52e7fb1..b9ae3a7 100644 --- a/man/man1/flock.1 +++ b/man/man1/flock.1 @@ -37,9 +37,7 @@ If .I file does not exist, .B flock -creates it as an empty file; see -.B Lock range -below. +creates it as an empty file. .I file is opened for reading only, and .B flock @@ -106,32 +104,22 @@ shares the standard input, output, and error of .BR flock , which writes nothing itself except error messages on standard error. .SS Lock range +Windows locks ranges of bytes in a file rather than whole files, and its +locks are mandatory: no other process can read or write the bytes that a +lock covers. .B flock -locks the bytes of -.I file -that exist when the lock is taken, from offset 0 up to the size of the -file at that moment. -Bytes appended later are not covered. -A lock on an empty file covers no bytes, and Windows does not let a lock -of zero bytes conflict with any other lock. -A lock on an empty file therefore excludes nothing, and two +therefore locks a single byte at offset 2^62, past the end of any real +file. +That lock conflicts with other .B flock -commands on it run their programs at the same time. -Write at least one byte to a lock file before using it, as in -.BR EXAMPLES . -.PP -Windows locks are mandatory, not advisory. -While -.B flock -holds an exclusive lock, no other process, including +locks on the same file, but does not stop any process, including .IR prog , -can read or write the locked bytes of -.IR file . -While it holds a shared lock, no process can write them. -Such a read or write fails with a permission error. -Use a separate file as the lock, not the data that -.I prog -works on. +from reading or writing +.IR file , +so the lock is advisory, as on Unix. +An empty file works as a lock file. +This is a w64devkit change; see +.BR NOTES . .SH OPTIONS Options are recognized only before .I file @@ -173,13 +161,14 @@ A lock belongs to the process that took it, and a new .B flock process holds none, so .B \-u -always fails with status 1, and +releases nothing. +It succeeds, as on Unix, and then runs .I prog -does not run. +if one is given. .TP .BR \-n ", " \-\-nonblock Do not wait for a conflicting lock to be released. -Exit at once with status 1 and an error message instead. +Exit at once with status 1 instead, with no message. .TP \fB\-c\fR, \fB\-\-command\fR \fIcommand\fR Run @@ -275,9 +264,6 @@ flock: \fIprog\fR: \fIreason\fR .PP The second form, with an empty field before the reason, reports a failure to lock or unlock, such as -.B Permission denied -for a conflicting lock with -.BR \-n , .B Invalid argument for .B \-s @@ -289,12 +275,10 @@ for an .I fd that is not open. .SH EXAMPLES -Create a lock file of one line, then run a build that no other build -using the same lock file can overlap: +Run a build that no other build using the same lock file can overlap: .PP .RS 4 .EX -$ echo lock >build.lock $ flock build.lock make .EE .RE @@ -336,17 +320,6 @@ it. The only use of this form is to wait until no other process holds a conflicting lock. .IP \(bu 2 -.B \-u -always fails, as described under -.BR OPTIONS . -.IP \(bu 2 -Locks are mandatory, and a lock on an empty file excludes nothing; see -.BR "Lock range" . -.IP \(bu 2 -With -.BR \-n , -a conflicting lock gives an error message as well as status 1. -.IP \(bu 2 A directory cannot be opened, so it cannot be locked. .B flock exits with status 1 and @@ -365,6 +338,21 @@ Windows does not let another process delete or rename while .B flock has it open. +.PP +w64devkit changes the lock range, as described under +.BR "Lock range" . +busybox-w32 locks the bytes of the file that exist when the lock is +taken, so on an empty file it locks nothing and excludes no one, and on +a non-empty file it stops other processes, including +.IR prog , +from using those bytes. +w64devkit also makes +.B \-n +fail silently and +.B \-u +succeed, as on Unix, where busybox-w32 prints +.B Permission denied +or fails. .SH SEE ALSO .BR busybox (1), .BR sh (1), diff --git a/src/busybox/flock-range.patch b/src/busybox/flock-range.patch new file mode 100644 index 0000000..4f98bb6 --- /dev/null +++ b/src/busybox/flock-range.patch @@ -0,0 +1,108 @@ +Fix flock on Windows, emulated with LockFileEx, so that it excludes +other flock users the way flock(2) does: + +* It locked the byte range from 0 to the file's current size, so on a + new, empty lock file, the usual case, it locked nothing at all, and + "flock lockfile cmd" excluded no one. + +* Since Windows byte-range locks are mandatory, on a non-empty file it + stopped every other process, including the command run under the + lock, from reading or writing the locked bytes. + +Lock one byte at offset 2^62 instead, past the end of any real file, +which other locks respect and ordinary I/O does not touch. + +Also report a lock held elsewhere as EWOULDBLOCK rather than EACCES, +so that flock -n exits with status 1 silently instead of printing +"Permission denied", and let unlocking a file that holds no lock +succeed, as flock(2) does, rather than failing flock -u. + +--- a/win32/flock.c ++++ b/win32/flock.c +@@ -31,59 +31,45 @@ + /* _get_osfhandle */ + # include + +-/* Determine the current size of a file. Because the other braindead +- * APIs we'll call need lower/upper 32 bit pairs, keep the file size +- * like that too. +- */ +-static BOOL +-file_size (HANDLE h, DWORD * lower, DWORD * upper) +-{ +- *lower = GetFileSize (h, upper); +- return 1; +-} +- + /* LOCKFILE_FAIL_IMMEDIATELY is undefined on some Windows systems. */ + # ifndef LOCKFILE_FAIL_IMMEDIATELY + # define LOCKFILE_FAIL_IMMEDIATELY 1 + # endif + ++/* Lock a single byte far beyond the end of any real file rather than ++ * the file's contents. Locking the contents locked nothing when the ++ * file was empty, as a new lock file is, and since Windows locks are ++ * mandatory, it stopped every other process, including the command ++ * run under the lock, from reading or writing the file. A lock past ++ * the end of the file excludes only other locks, like flock(2). ++ */ ++#define LOCK_OFFSET_HIGH 0x40000000 /* offset 2^62 */ ++ + /* Acquire a lock. */ + static BOOL + do_lock (HANDLE h, int non_blocking, int exclusive) + { +- BOOL res; +- DWORD size_lower, size_upper; + OVERLAPPED ovlp; + int flags = 0; + +- /* We're going to lock the whole file, so get the file size. */ +- res = file_size (h, &size_lower, &size_upper); +- if (!res) +- return 0; +- +- /* Start offset is 0, and also zero the remaining members of this struct. */ + memset (&ovlp, 0, sizeof ovlp); ++ ovlp.OffsetHigh = LOCK_OFFSET_HIGH; + + if (non_blocking) + flags |= LOCKFILE_FAIL_IMMEDIATELY; + if (exclusive) + flags |= LOCKFILE_EXCLUSIVE_LOCK; + +- return LockFileEx (h, flags, 0, size_lower, size_upper, &ovlp); ++ return LockFileEx (h, flags, 0, 1, 0, &ovlp); + } + + /* Unlock reader or exclusive lock. */ + static BOOL + do_unlock (HANDLE h) + { +- int res; +- DWORD size_lower, size_upper; +- +- res = file_size (h, &size_lower, &size_upper); +- if (!res) +- return 0; +- +- return UnlockFile (h, 0, 0, size_lower, size_upper); ++ /* Unlocking a file that holds no lock succeeds, as in flock(2). */ ++ return UnlockFile (h, 0, LOCK_OFFSET_HIGH, 1, 0) || ++ GetLastError () == ERROR_NOT_LOCKED; + } + + /* Now our BSD-like flock operation. */ +@@ -124,7 +110,11 @@ + */ + if (!res) + { +- errno = err_win_to_posix(); ++ /* A lock held elsewhere is EWOULDBLOCK, as flock(2) reports. */ ++ if (GetLastError () == ERROR_LOCK_VIOLATION) ++ errno = EWOULDBLOCK; ++ else ++ errno = err_win_to_posix(); + return -1; + } + diff --git a/src/busybox/series b/src/busybox/series index 9bff76f..1cf7f39 100644 --- a/src/busybox/series +++ b/src/busybox/series @@ -12,3 +12,4 @@ bare-drive.patch rm-bare-drive.patch drop-test2.patch stty-winsize.patch +flock-range.patch