Cross-platform lockFile()/unlockFile() procedures for io2. (#116)

This commit is contained in:
Eugene Kabanov 2022-08-05 19:58:34 +03:00 committed by GitHub
parent 49db5b27b9
commit 0476bcad1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 509 additions and 20 deletions

View File

@ -22,16 +22,34 @@ proc test(args, path: string) =
"error"
# Compilation language is controlled by TEST_LANG
exec "nim " & getEnv("TEST_LANG", "c") & " " & getEnv("NIMFLAGS") & " " &
args & " -r --hints:off --skipParentCfg --styleCheck:usages --styleCheck:" &
styleCheckStyle & " " & path
exec "nim " & getEnv("TEST_LANG", "c") & " " & getEnv("NIMFLAGS") &
" " & args &
" -r --hints:off --skipParentCfg --styleCheck:usages --styleCheck:" &
styleCheckStyle & " " & path
proc buildHelper(args, path: string) =
let styleCheckStyle =
if (NimMajor, NimMinor) < (1, 6):
"hint"
else:
"error"
exec "nim " & getEnv("TEST_LANG", "c") & " " & getEnv("NIMFLAGS") &
" " & args &
" --hints:off --skipParentCfg --styleCheck:usages --styleCheck:" &
styleCheckStyle & " " & path
task test, "Run all tests":
# Building `test_helper.nim`.
buildHelper("", "tests/test_helper")
test "--threads:off", "tests/all_tests"
test "--threads:on -d:nimTypeNames", "tests/all_tests"
test "--threads:on -d:noIntrinsicsBitOpts -d:noIntrinsicsEndians", "tests/all_tests"
test "--threads:on -d:noIntrinsicsBitOpts -d:noIntrinsicsEndians",
"tests/all_tests"
task testvcc, "Run all tests with vcc compiler":
# Building `test_helper.nim`.
buildHelper("--cc:vcc", "tests/test_helper")
test "--cc:vcc --threads:off", "tests/all_tests"
test "--cc:vcc --threads:on -d:nimTypeNames", "tests/all_tests"
test "--cc:vcc --threads:on -d:noIntrinsicsBitOpts -d:noIntrinsicsEndians", "tests/all_tests"
test "--cc:vcc --threads:on -d:noIntrinsicsBitOpts -d:noIntrinsicsEndians",
"tests/all_tests"

View File

@ -33,8 +33,10 @@ when defined(windows):
TRUNCATE_EXISTING = 5'u32
FILE_FLAG_OVERLAPPED = 0x40000000'u32
FILE_SHARE_READ = 0x00000001'u32
FILE_SHARE_WRITE = 0x00000002'u32
FILE_FLAG_NO_BUFFERING = 0x20000000'u32
FILE_SHARE_READ = 1'u32
FILE_ATTRIBUTE_READONLY = 0x00000001'u32
FILE_ATTRIBUTE_DIRECTORY = 0x00000010'u32
@ -100,6 +102,13 @@ when defined(windows):
changeTime: uint64
fileAttributes: uint32
OVERLAPPED* {.pure, inheritable.} = object
internal*: uint
internalHigh*: uint
offset*: uint32
offsetHigh*: uint32
hEvent*: IoHandle
proc getLastError(): uint32 {.
importc: "GetLastError", stdcall, dynlib: "kernel32", sideEffect.}
proc createDirectoryW(pathName: WideCString,
@ -140,35 +149,49 @@ when defined(windows):
arguments: pointer): uint32 {.
importc: "FormatMessageW", stdcall, dynlib: "kernel32".}
proc localFree(p: pointer): uint {.
importc: "LocalFree", stdcall, dynlib: "kernel32".}
importc: "LocalFree", stdcall, dynlib: "kernel32", sideEffect.}
proc getLongPathNameW(lpszShortPath: WideCString, lpszLongPath: WideCString,
cchBuffer: uint32): uint32 {.
importc: "GetLongPathNameW", dynlib: "kernel32.dll", stdcall.}
importc: "GetLongPathNameW", dynlib: "kernel32.dll", stdcall,
sideEffect.}
proc findFirstFileW(lpFileName: WideCString,
lpFindFileData: var WIN32_FIND_DATAW): uint {.
importc: "FindFirstFileW", dynlib: "kernel32", stdcall.}
importc: "FindFirstFileW", dynlib: "kernel32", stdcall, sideEffect.}
proc findClose(hFindFile: uint): int32 {.
importc: "FindClose", dynlib: "kernel32", stdcall.}
importc: "FindClose", dynlib: "kernel32", stdcall, sideEffect.}
proc getFileInformationByHandle(hFile: uint,
info: var BY_HANDLE_FILE_INFORMATION): int32 {.
importc: "GetFileInformationByHandle", dynlib: "kernel32", stdcall.}
importc: "GetFileInformationByHandle", dynlib: "kernel32", stdcall,
sideEffect.}
proc getFileInformationByHandleEx(hFile: uint, information: uint32,
lpFileInformation: pointer,
dwBufferSize: uint32): int32 {.
importc: "GetFileInformationByHandleEx", dynlib: "kernel32", stdcall.}
importc: "GetFileInformationByHandleEx", dynlib: "kernel32", stdcall,
sideEffect.}
proc setFileInformationByHandle(hFile: uint, information: uint32,
lpFileInformation: pointer,
dwBufferSize: uint32): int32 {.
importc: "SetFileInformationByHandle", dynlib: "kernel32", stdcall.}
importc: "SetFileInformationByHandle", dynlib: "kernel32", stdcall,
sideEffect.}
proc getFileSize(hFile: uint, lpFileSizeHigh: var uint32): uint32 {.
importc: "GetFileSize", dynlib: "kernel32", stdcall.}
importc: "GetFileSize", dynlib: "kernel32", stdcall, sideEffect.}
proc setFilePointerEx(hFile: uint, liDistanceToMove: int64,
lpNewFilePointer: ptr int64,
dwMoveMethod: uint32): int32 {.
importc: "SetFilePointerEx", dynlib: "kernel32", stdcall.}
importc: "SetFilePointerEx", dynlib: "kernel32", stdcall, sideEffect.}
proc lockFileEx(hFile: uint, dwFlags, dwReserved: uint32,
nNumberOfBytesToLockLow, nNumberOfBytesToLockHigh: uint32,
lpOverlapped: pointer): uint32 {.
importc: "LockFileEx", dynlib: "kernel32", stdcall, sideEffect.}
proc unlockFileEx(hFile: uint, dwReserved: uint32,
nNumberOfBytesToLockLow, nNumberOfBytesToLockHigh: uint32,
lpOverlapped: pointer): uint32 {.
importc: "UnlockFileEx", dynlib: "kernel32", stdcall, sideEffect.}
const
NO_ERROR = IoErrorCode(0)
LOCKFILE_EXCLUSIVE_LOCK = 0x00000002'u32
LOCKFILE_FAIL_IMMEDIATELY = 0x00000001'u32
proc `==`*(a: IoErrorCode, b: uint32): bool {.inline.} =
(uint32(a) == b)
@ -181,6 +204,11 @@ elif defined(posix):
AltSep* = '/'
BothSeps* = {'/'}
LOCK_SH* = 0x01
LOCK_EX* = 0x02
LOCK_NB* = 0x04
LOCK_UN* = 0x08
type
IoHandle* = distinct cint
IoErrorCode* = distinct cint
@ -209,18 +237,28 @@ elif defined(posix):
O_CLOEXEC = cint(0x1000000)
F_NOCACHE = cint(48)
type
FlockStruct* {.importc: "struct flock", final, pure,
header: "<fcntl.h>".} = object
ltype* {.importc: "l_type".}: cshort
lwhence* {.importc: "l_whence".}: cshort
start* {.importc: "l_start".}: int
length* {.importc: "l_len".}: int
pid* {.importc: "l_pid".}: int32
var errno {.importc, header: "<errno.h>".}: cint
proc write(a1: cint, a2: pointer, a3: csize_t): int {.
importc, header: "<unistd.h>".}
importc, header: "<unistd.h>", sideEffect.}
proc read(a1: cint, a2: pointer, a3: csize_t): int {.
importc, header: "<unistd.h>".}
importc, header: "<unistd.h>", sideEffect.}
proc c_strerror(errnum: cint): cstring {.
importc: "strerror", header: "<string.h>".}
importc: "strerror", header: "<string.h>", sideEffect.}
proc c_free(p: pointer) {.
importc: "free", header: "<stdlib.h>".}
importc: "free", header: "<stdlib.h>", sideEffect.}
proc getcwd(a1: cstring, a2: int): cstring {.
importc, header: "<unistd.h>", sideEffect.}
proc `==`*(a: IoErrorCode, b: cint): bool {.inline.} =
(cint(a) == b)
@ -229,7 +267,7 @@ type
OpenFlags* {.pure.} = enum
Read, Write, Create, Exclusive, Append, Truncate,
Inherit, NonBlock, Direct
Inherit, NonBlock, Direct, ShareRead, ShareWrite
Permission* = enum
UserRead, UserWrite, UserExec,
@ -244,6 +282,14 @@ type
AccessFlags* {.pure.} = enum
Find, Read, Write, Execute
LockType* {.pure.} = enum
Shared, Exclusive
IoLockHandle* = object
handle*: IoHandle
offset*: int64
size*: int64
const
NimErrorCode = 100_000
UnsupportedFileSize* = IoErrorCode(NimErrorCode)
@ -1010,6 +1056,11 @@ proc openFile*(pathName: string, flags: set[OpenFlags],
((dwAccess and (GENERIC_READ or GENERIC_WRITE)) == GENERIC_READ):
dwShareMode = dwShareMode or FILE_SHARE_READ
if OpenFlags.ShareRead in flags:
dwShareMode = dwShareMode or FILE_SHARE_READ
if OpenFlags.ShareWrite in flags:
dwShareMode = dwShareMode or FILE_SHARE_WRITE
if OpenFlags.NonBlock in flags:
dwFlags = dwFlags or FILE_FLAG_OVERLAPPED
if OpenFlags.Direct in flags:
@ -1148,6 +1199,8 @@ proc writeFile*(pathName: string, data: openArray[byte],
when defined(windows):
template makeInt64(a, b: uint32): int64 =
(int64(a and 0x7FFF_FFFF'u32) shl 32) or int64(b and 0xFFFF_FFFF'u32)
template makeUint32(a: uint64): tuple[lowPart: uint32, highPart: uint32] =
(uint32(a and 0xFFFF_FFFF'u64), uint32((a shr 32) and 0xFFFF_FFFF'u64))
proc writeFile*(pathName: string, data: openArray[char],
createMode: int = 0o644,
@ -1315,3 +1368,160 @@ proc readAllChars*(pathName: string): IoResult[string] =
proc readAllFile*(pathName: string): IoResult[seq[byte]] =
## Alias for ``readAllBytes()``.
readAllBytes(pathName)
proc lockFile*(handle: IoHandle, kind: LockType, offset,
size: int64): IoResult[void] =
## Apply shared or exclusive file segment lock for file handle ``handle`` and
## range specified by ``offset`` and ``size`` parameters.
##
## ``kind`` - type of lock (shared or exclusive). Please note that only
## exclusive locks have cross-platform compatible behavior. Hovewer, exclusive
## locks require ``handle`` to be opened for writing.
##
## ``offset`` - starting byte offset in the file where the lock should
## begin. ``offset`` should be always bigger or equal to ``0``.
##
## ``size`` - length of the byte range to be locked. ``size`` should be always
## bigger or equal to ``0``.
##
## If ``offset`` and ``size`` are both equal to ``0`` the entire file is locked.
doAssert(offset >= 0)
doAssert(size >= 0)
when defined(posix):
let ltype =
case kind
of LockType.Shared:
cshort(posix.F_RDLCK)
of LockType.Exclusive:
cshort(posix.F_WRLCK)
var flockObj =
when sizeof(int) == 8:
# There is no need to perform overflow check, so we just cast.
FlockStruct(ltype: ltype, lwhence: cshort(posix.SEEK_SET),
start: cast[int](offset), length: cast[int](size))
else:
# Currently we do not support `__USE_FILE_OFFSET64` or
# `__USE_LARGEFILE64` because its Linux specific #defines, and is not
# present on BSD systems. Therefore, on 32bit systems we do not support
# range locks which exceed `int32` value size.
if offset > int64(high(int)):
return err(IoErrorCode(EFBIG))
if size > int64(high(int)):
return err(IoErrorCode(EFBIG))
# We already made overflow check, so we just cast.
FlockStruct(ltype: ltype, lwhence: cshort(posix.SEEK_SET),
start: cast[int](offset), length: cast[int](size))
while true:
let res = posix.fcntl(cint(handle), posix.F_SETLK, addr flockObj)
if res == -1:
let errCode = ioLastError()
if errCode == EINTR:
continue
else:
return err(errCode)
else:
return ok()
elif defined(windows):
let (lowOffsetPart, highOffsetPart, lowSizePart, highSizePart) =
if offset == 0'i64 and size == 0'i64:
# We try to keep cross-platform behavior on Windows. And we can do it
# because: Locking a region that goes beyond the current end-of-file
# position is not an error.
(0'u32, 0'u32, 0xFFFF_FFFF'u32, 0xFFFF_FFFF'u32)
else:
let offsetTuple = makeUint32(uint64(offset))
let sizeTuple = makeUint32(uint64(size))
(offsetTuple[0], offsetTuple[1], sizeTuple[0], sizeTuple[1])
var ovl = OVERLAPPED(offset: lowOffsetPart, offsetHigh: highOffsetPart)
let
flags =
case kind
of LockType.Shared:
LOCKFILE_FAIL_IMMEDIATELY
of LockType.Exclusive:
LOCKFILE_FAIL_IMMEDIATELY or LOCKFILE_EXCLUSIVE_LOCK
res = lockFileEx(uint(handle), flags, 0'u32, lowSizePart,
highSizePart, addr ovl)
if res == 0:
err(ioLastError())
else:
ok()
proc unlockFile*(handle: IoHandle, offset, size: int64): IoResult[void] =
## Clear shared or exclusive file segment lock for file handle ``handle`` and
## range specified by ``offset`` and ``size`` parameters.
##
## ``offset`` - starting byte offset in the file where the lock placed.
## ``offset`` should be always bigger or equal to ``0``.
##
## ``size`` - length of the byte range to be unlocked. ``size`` should be
## always bigger or equal to ``0``.
doAssert(offset >= 0)
doAssert(size >= 0)
when defined(posix):
let ltype = cshort(posix.F_UNLCK)
var flockObj =
when sizeof(int) == 8:
# There is no need to perform overflow check, so we just cast.
FlockStruct(ltype: ltype, lwhence: cshort(posix.SEEK_SET),
start: cast[int](offset), length: cast[int](size))
else:
# Currently we do not support `__USE_FILE_OFFSET64` because its
# Linux specific #define, and it not present in BSD systems. So
# on 32bit systems we do not support range locks which exceed `int32`
# value size.
if offset > int64(high(int)):
return err(IoErrorCode(EFBIG))
if size > int64(high(int)):
return err(IoErrorCode(EFBIG))
# We already made overflow check, so we just cast.
FlockStruct(ltype: ltype, lwhence: cshort(posix.SEEK_SET),
start: cast[int](offset), length: cast[int](size))
while true:
let res = posix.fcntl(cint(handle), F_SETLK, addr flockObj)
if res == -1:
let errCode = ioLastError()
if errCode == EINTR:
continue
else:
return err(errCode)
else:
return ok()
elif defined(windows):
let (lowOffsetPart, highOffsetPart, lowSizePart, highSizePart) =
if offset == 0'i64 and size == 0'i64:
# We try to keep cross-platform behavior on Windows. And we can do it
# because: Locking a region that goes beyond the current end-of-file
# position is not an error.
(0'u32, 0'u32, 0xFFFF_FFFF'u32, 0xFFFF_FFFF'u32)
else:
let offsetTuple = makeUint32(uint64(offset))
let sizeTuple = makeUint32(uint64(size))
(offsetTuple[0], offsetTuple[1], sizeTuple[0], sizeTuple[1])
var ovl = OVERLAPPED(offset: lowOffsetPart, offsetHigh: highOffsetPart)
let res = unlockFileEx(uint(handle), 0'u32, lowSizePart,
highSizePart, addr ovl)
if res == 0:
err(ioLastError())
else:
ok()
proc lockFile*(handle: IoHandle, kind: LockType): IoResult[IoLockHandle] =
## Apply exclusive or shared lock to whole file specified by file handle
## ``handle``.
##
## ``kind`` - type of lock (shared or exclusive). Please note that only
## exclusive locks have cross-platform compatible behavior. Hovewer, exclusive
## locks require ``handle`` to be opened for writing.
##
## On success returns ``IoLockHandle`` object which could be used for unlock.
? lockFile(handle, kind, 0'i64, 0'i64)
ok(IoLockHandle(handle: handle, offset: 0'i64, size: 0'i64))
proc unlockFile*(lock: IoLockHandle): IoResult[void] =
## Clear shared or exclusive lock ``lock``.
let res = unlockFile(lock.handle, lock.offset, lock.size)
if res.isErr():
err(res.error())
else:
ok()

47
tests/test_helper.nim Normal file
View File

@ -0,0 +1,47 @@
# Copyright (c) 2020-2022 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license: http://opensource.org/licenses/MIT
# * Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import std/[os, strutils]
import ../stew/io2, ../stew/results
proc lockFileFlags(path: string, flags: set[OpenFlags],
lockType: LockType): IoResult[void] =
let handle = ? openFile(path, flags)
let info = ? lockFile(handle, lockType)
? unlockFile(info)
? closeFile(handle)
ok()
when isMainModule:
if paramCount() != 1:
echo "Not enough parameters"
else:
const TestFlags = [
({OpenFlags.Read}, LockType.Shared),
({OpenFlags.Write}, LockType.Exclusive),
({OpenFlags.Read, OpenFlags.Write}, LockType.Shared),
({OpenFlags.Read, OpenFlags.Write}, LockType.Exclusive),
({OpenFlags.Read, OpenFlags.ShareRead}, LockType.Shared),
({OpenFlags.Write, OpenFlags.ShareWrite}, LockType.Exclusive),
({OpenFlags.Read, OpenFlags.Write,
OpenFlags.ShareRead, OpenFlags.ShareWrite}, LockType.Shared),
({OpenFlags.Read, OpenFlags.Write,
OpenFlags.ShareRead, OpenFlags.ShareWrite}, LockType.Exclusive),
]
let pathName = paramStr(1)
let response =
block:
var res: seq[string]
for test in TestFlags:
let
lres = lockFileFlags(pathName, test[0], test[1])
data = if lres.isOk(): "OK" else: "E" & $int(lres.error())
res.add(data)
res.join(":")
echo response

View File

@ -7,8 +7,12 @@
{.used.}
import unittest2
import std/[osproc, strutils]
import ../stew/io2
when defined(posix):
from std/posix import EAGAIN
suite "OS Input/Output procedures test suite":
test "getCurrentDir() test":
let res = getCurrentDir()
@ -521,3 +525,213 @@ suite "OS Input/Output procedures test suite":
positions[2] == 0'i64
positions[3] == 10'i64
positions[4] == 20'i64
test "lockFile(handle)/unlockFile(handle) test":
type
TestResult = object
output: string
status: int
proc createLockFile(path: string): IoResult[void] =
io2.writeFile(path, "LOCKFILEDATA")
proc removeLockFile(path: string): IoResult[void] =
io2.removeFile(path)
proc lockTest(path: string, flags: set[OpenFlags],
lockType: LockType): IoResult[array[3, TestResult]] =
const HelperPath =
when defined(windows):
"test_helper "
else:
"tests/test_helper "
let
handle = ? openFile(path, flags)
lock = ? lockFile(handle, lockType)
let res1 =
try:
execCmdEx(HelperPath & path)
except CatchableError as exc:
echo "Exception happens [", $exc.name, "]: ", $exc.msg
("", -1)
? unlockFile(lock)
let res2 =
try:
execCmdEx(HelperPath & path)
except CatchableError as exc:
echo "Exception happens [", $exc.name, "]: ", $exc.msg
("", -1)
? closeFile(handle)
let res3 =
try:
execCmdEx(HelperPath & path)
except CatchableError as exc:
echo "Exception happens [", $exc.name, "]: ", $exc.msg
("", -1)
ok([
TestResult(output: strip(res1.output), status: res1.exitCode),
TestResult(output: strip(res2.output), status: res2.exitCode),
TestResult(output: strip(res3.output), status: res3.exitCode),
])
proc performTest(): IoResult[void] =
let path1 = "testfile.lock"
when defined(windows):
const
ERROR_LOCK_VIOLATION = 33
ERROR_SHARING_VIOLATION = 32
let
LockTests = [
(
{OpenFlags.Read},
LockType.Shared,
"OK:E$1:E$1:E$1:OK:E$1:E$1:E$1" %
[$ERROR_SHARING_VIOLATION],
"OK:E$1:E$1:E$1:OK:E$1:E$1:E$1" %
[$ERROR_SHARING_VIOLATION],
"OK:OK:OK:OK:OK:OK:OK:OK"
),
(
{OpenFlags.Write},
LockType.Exclusive,
"E$1:E$1:E$1:E$1:E$1:E$1:E$1:E$1" %
[$ERROR_SHARING_VIOLATION],
"E$1:E$1:E$1:E$1:E$1:E$1:E$1:E$1" %
[$ERROR_SHARING_VIOLATION],
"OK:OK:OK:OK:OK:OK:OK:OK"
),
(
{OpenFlags.Read, OpenFlags.Write},
LockType.Shared,
"E$1:E$1:E$1:E$1:E$1:E$1:E$1:E$1" %
[$ERROR_SHARING_VIOLATION],
"E$1:E$1:E$1:E$1:E$1:E$1:E$1:E$1" %
[$ERROR_SHARING_VIOLATION],
"OK:OK:OK:OK:OK:OK:OK:OK"
),
(
{OpenFlags.Read, OpenFlags.Write},
LockType.Exclusive,
"E$1:E$1:E$1:E$1:E$1:E$1:E$1:E$1" %
[$ERROR_SHARING_VIOLATION],
"E$1:E$1:E$1:E$1:E$1:E$1:E$1:E$1" %
[$ERROR_SHARING_VIOLATION],
"OK:OK:OK:OK:OK:OK:OK:OK"
),
(
{OpenFlags.Read, OpenFlags.ShareRead},
LockType.Shared,
"OK:E$1:E$1:E$1:OK:E$1:E$1:E$1" %
[$ERROR_SHARING_VIOLATION],
"OK:E$1:E$1:E$1:OK:E$1:E$1:E$1" %
[$ERROR_SHARING_VIOLATION],
"OK:OK:OK:OK:OK:OK:OK:OK"
),
(
{OpenFlags.Write, OpenFlags.ShareWrite},
LockType.Exclusive,
"E$1:E$1:E$1:E$1:E$1:E$2:E$1:E$1" %
[$ERROR_SHARING_VIOLATION, $ERROR_LOCK_VIOLATION],
"E$1:E$1:E$1:E$1:E$1:OK:E$1:E$1" %
[$ERROR_SHARING_VIOLATION],
"OK:OK:OK:OK:OK:OK:OK:OK"
),
(
{OpenFlags.Read, OpenFlags.Write, OpenFlags.ShareRead,
OpenFlags.ShareWrite},
LockType.Shared,
"E$1:E$1:E$1:E$1:E$1:E$1:OK:E$2" %
[$ERROR_SHARING_VIOLATION, $ERROR_LOCK_VIOLATION],
"E$1:E$1:E$1:E$1:E$1:E$1:OK:OK" %
[$ERROR_SHARING_VIOLATION],
"OK:OK:OK:OK:OK:OK:OK:OK"
),
(
{OpenFlags.Read, OpenFlags.Write, OpenFlags.ShareRead,
OpenFlags.ShareWrite},
LockType.Exclusive,
"E$1:E$1:E$1:E$1:E$1:E$1:E$2:E$2" %
[$ERROR_SHARING_VIOLATION, $ERROR_LOCK_VIOLATION],
"E$1:E$1:E$1:E$1:E$1:E$1:OK:OK" %
[$ERROR_SHARING_VIOLATION],
"OK:OK:OK:OK:OK:OK:OK:OK"
),
]
else:
let
LockTests = [
(
{OpenFlags.Read},
LockType.Shared,
"OK:E$1:OK:E$1:OK:E$1:OK:E$1" % [$EAGAIN],
"OK:OK:OK:OK:OK:OK:OK:OK",
"OK:OK:OK:OK:OK:OK:OK:OK"
),
(
{OpenFlags.Write},
LockType.Exclusive,
"E$1:E$1:E$1:E$1:E$1:E$1:E$1:E$1" % [$EAGAIN],
"OK:OK:OK:OK:OK:OK:OK:OK",
"OK:OK:OK:OK:OK:OK:OK:OK"
),
(
{OpenFlags.Read, OpenFlags.Write},
LockType.Shared,
"OK:E$1:OK:E$1:OK:E$1:OK:E$1" % [$EAGAIN],
"OK:OK:OK:OK:OK:OK:OK:OK",
"OK:OK:OK:OK:OK:OK:OK:OK"
),
(
{OpenFlags.Read, OpenFlags.Write},
LockType.Exclusive,
"E$1:E$1:E$1:E$1:E$1:E$1:E$1:E$1" % [$EAGAIN],
"OK:OK:OK:OK:OK:OK:OK:OK",
"OK:OK:OK:OK:OK:OK:OK:OK"
),
(
{OpenFlags.Read, OpenFlags.ShareRead},
LockType.Shared,
"OK:E$1:OK:E$1:OK:E$1:OK:E$1" % [$EAGAIN],
"OK:OK:OK:OK:OK:OK:OK:OK",
"OK:OK:OK:OK:OK:OK:OK:OK"
),
(
{OpenFlags.Write, OpenFlags.ShareWrite},
LockType.Exclusive,
"E$1:E$1:E$1:E$1:E$1:E$1:E$1:E$1" % [$EAGAIN],
"OK:OK:OK:OK:OK:OK:OK:OK",
"OK:OK:OK:OK:OK:OK:OK:OK"
),
(
{OpenFlags.Read, OpenFlags.Write, OpenFlags.ShareRead,
OpenFlags.ShareWrite},
LockType.Shared,
"OK:E$1:OK:E$1:OK:E$1:OK:E$1" % [$EAGAIN],
"OK:OK:OK:OK:OK:OK:OK:OK",
"OK:OK:OK:OK:OK:OK:OK:OK",
),
(
{OpenFlags.Read, OpenFlags.Write, OpenFlags.ShareRead,
OpenFlags.ShareWrite},
LockType.Exclusive,
"E$1:E$1:E$1:E$1:E$1:E$1:E$1:E$1" % [$EAGAIN],
"OK:OK:OK:OK:OK:OK:OK:OK",
"OK:OK:OK:OK:OK:OK:OK:OK"
),
]
? createLockFile(path1)
for item in LockTests:
let res = ? lockTest(path1, item[0], item[1])
check:
res[0].status == 0
res[1].status == 0
res[2].status == 0
res[0].output == item[2]
res[1].output == item[3]
res[2].output == item[4]
? removeLockFile(path1)
ok()
check performTest().isOk()