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