From a45f83d4abf27c033a95d2e611f638f4b82ed9f9 Mon Sep 17 00:00:00 2001 From: Masashi Umezawa Date: Thu, 13 Nov 2025 22:34:48 +0900 Subject: [PATCH 1/7] Added Windows file lock support --- .../WindowsFileLockTest.class.st | 226 +++++++++++++++++ src/Soil-File/BinaryFileStream.extension.st | 101 +++++--- src/Soil-File/WinOverlapped.class.st | 103 ++++++++ src/Soil-File/WinPlatform.extension.st | 6 + src/Soil-File/WindowsFileLock.class.st | 234 ++++++++++++++++++ 5 files changed, 638 insertions(+), 32 deletions(-) create mode 100644 src/Soil-File-Tests/WindowsFileLockTest.class.st create mode 100644 src/Soil-File/WinOverlapped.class.st create mode 100644 src/Soil-File/WinPlatform.extension.st create mode 100644 src/Soil-File/WindowsFileLock.class.st diff --git a/src/Soil-File-Tests/WindowsFileLockTest.class.st b/src/Soil-File-Tests/WindowsFileLockTest.class.st new file mode 100644 index 00000000..48348538 --- /dev/null +++ b/src/Soil-File-Tests/WindowsFileLockTest.class.st @@ -0,0 +1,226 @@ +Class { + #name : 'WindowsFileLockTest', + #superclass : 'TestCase', + #instVars : [ + 'testFile', + 'stream' + ], + #category : 'Soil-File-Tests', + #package : 'Soil-File-Tests' +} + +{ #category : 'running' } +WindowsFileLockTest >> setUp [ + super setUp. + testFile := 'soil-winlock-test.tmp' asFileReference. + testFile ensureDelete +] + +{ #category : 'running' } +WindowsFileLockTest >> tearDown [ + stream ifNotNil: [ + [ stream close ] on: Error do: [ :ex | "ignore close errors" ] ]. + testFile ifNotNil: [ + [ testFile ensureDelete ] on: Error do: [ :ex | "ignore delete errors" ] ]. + super tearDown +] + +{ #category : 'tests' } +WindowsFileLockTest >> testErrorDescriptions [ + "Test error description helper method" + + OSPlatform current isWindows ifFalse: [ ^ self skip ]. + + "Test known error codes" + self assert: (WindowsFileLock errorDescription: 5) equals: 'Access denied'. + self assert: (WindowsFileLock errorDescription: 6) equals: 'Invalid file handle'. + self assert: (WindowsFileLock errorDescription: 33) equals: 'Lock violation (region already locked)'. + self assert: (WindowsFileLock errorDescription: 158) equals: 'Region not locked'. + + "Test unknown error code" + self assert: ((WindowsFileLock errorDescription: 9999) includesSubstring: 'Unknown error') +] + +{ #category : 'tests' } +WindowsFileLockTest >> testFdatasyncEntireFile [ + "Test fdatasync on Windows (should call same as fsync)" + + OSPlatform current isWindows ifFalse: [ ^ self skip ]. + + stream := testFile binaryWriteStream. + stream nextPutAll: 'Testing fdatasync on Windows!'. + stream flush. + + "Call fdatasync - should not raise an error" + self shouldnt: [ stream fileStream fdatasync ] raise: Error. + + stream close +] + +{ #category : 'tests' } +WindowsFileLockTest >> testFlushFileBuffersDirect [ + "Test FlushFileBuffers FFI call directly" + + | result | + OSPlatform current isWindows ifFalse: [ ^ self skip ]. + + stream := testFile binaryWriteStream. + stream nextPutAll: 'Testing direct FlushFileBuffers call'. + stream flush. + + "Call FlushFileBuffers directly" + result := WindowsFileLock flushFileBuffers: stream fileStream fileHandle. + + "Should return true (non-zero) on success" + self assert: result +] + +{ #category : 'tests' } +WindowsFileLockTest >> testFsyncEntireFile [ + "Test fsync on Windows using FlushFileBuffers" + + OSPlatform current isWindows ifFalse: [ ^ self skip ]. + + stream := testFile binaryWriteStream. + stream nextPutAll: 'Testing fsync on Windows!'. + stream flush. + + "Call fsync - should not raise an error" + self shouldnt: [ stream fileStream fsync ] raise: Error. + + stream close +] + +{ #category : 'tests' } +WindowsFileLockTest >> testLockAndUnlockEntireFile [ + "Test locking and unlocking the entire file (length = 0)" + + OSPlatform current isWindows ifFalse: [ ^ self skip ]. + + stream := testFile binaryWriteStream. + stream nextPutAll: 'Hello, Windows file locking!'. + stream flush. + + "Lock entire file" + self assert: (WindowsFileLock lock: stream fileStream fileHandle from: 0 length: 0). + + "Unlock entire file" + self assert: (WindowsFileLock unlock: stream fileStream fileHandle from: 0 length: 0) +] + +{ #category : 'tests' } +WindowsFileLockTest >> testLockAndUnlockRange [ + "Test locking and unlocking a specific byte range" + + OSPlatform current isWindows ifFalse: [ ^ self skip ]. + + stream := testFile binaryWriteStream. + stream nextPutAll: 'Hello, Windows file locking!'. + stream flush. + + "Lock bytes 7 to 14 (the word 'Windows')" + self assert: (WindowsFileLock lock: stream fileStream fileHandle from: 7 length: 7). + + "Unlock the same range" + self assert: (WindowsFileLock unlock: stream fileStream fileHandle from: 7 length: 7) +] + +{ #category : 'tests' } +WindowsFileLockTest >> testLockExclusive [ + "Test exclusive lock" + + OSPlatform current isWindows ifFalse: [ ^ self skip ]. + + stream := testFile binaryWriteStream. + stream nextPutAll: 'Test exclusive lock'. + stream flush. + + "Lock with exclusive flag" + self assert: (WindowsFileLock lock: stream fileStream fileHandle from: 0 length: 100 exclusive: true). + + "Unlock" + self assert: (WindowsFileLock unlock: stream fileStream fileHandle from: 0 length: 100) +] + +{ #category : 'tests' } +WindowsFileLockTest >> testLockOrErrorSuccess [ + "Test lockOrError with successful lock" + + OSPlatform current isWindows ifFalse: [ ^ self skip ]. + + stream := testFile binaryWriteStream. + stream nextPutAll: 'Test lockOrError success'. + stream flush. + + "Should not raise an error" + self shouldnt: [ + WindowsFileLock lockOrError: stream fileStream fileHandle from: 0 length: 100 exclusive: true ] + raise: Error. + + "Clean up" + WindowsFileLock unlock: stream fileStream fileHandle from: 0 length: 100 +] + +{ #category : 'tests' } +WindowsFileLockTest >> testLockShared [ + "Test shared lock" + + OSPlatform current isWindows ifFalse: [ ^ self skip ]. + + stream := testFile binaryWriteStream. + stream nextPutAll: 'Test shared lock'. + stream flush. + + "Lock with shared flag" + self assert: (WindowsFileLock lock: stream fileStream fileHandle from: 0 length: 100 exclusive: false). + + "Unlock" + self assert: (WindowsFileLock unlock: stream fileStream fileHandle from: 0 length: 100) +] + +{ #category : 'tests' } +WindowsFileLockTest >> testMultipleLocks [ + "Test locking multiple non-overlapping ranges" + + OSPlatform current isWindows ifFalse: [ ^ self skip ]. + + stream := testFile binaryWriteStream. + stream nextPutAll: '0123456789ABCDEFGHIJ'. + stream flush. + + "Lock range 0-9" + self assert: (WindowsFileLock lock: stream fileStream fileHandle from: 0 length: 10). + + "Lock range 10-19 (non-overlapping)" + self assert: (WindowsFileLock lock: stream fileStream fileHandle from: 10 length: 10). + + "Unlock both ranges" + self assert: (WindowsFileLock unlock: stream fileStream fileHandle from: 0 length: 10). + self assert: (WindowsFileLock unlock: stream fileStream fileHandle from: 10 length: 10) +] + +{ #category : 'tests' } +WindowsFileLockTest >> testWinOverlappedStructure [ + "Test WinOverlapped structure initialization and field access" + + | overlapped | + OSPlatform current isWindows ifFalse: [ ^ self skip ]. + + overlapped := WinOverlapped new. + + "Test field setters and getters" + overlapped offset: 16r12345678. + self assert: overlapped offset equals: 16r12345678. + + overlapped offsetHigh: 16rABCDEF00. + self assert: overlapped offsetHigh equals: 16rABCDEF00. + + overlapped internal: 0. + self assert: overlapped internal equals: 0. + + overlapped internalHigh: 0. + self assert: overlapped internalHigh equals: 0. + + overlapped hEvent: ExternalAddress null. + self assert: overlapped hEvent getHandle equals: ExternalAddress null +] diff --git a/src/Soil-File/BinaryFileStream.extension.st b/src/Soil-File/BinaryFileStream.extension.st index a274dda0..2a4957db 100644 --- a/src/Soil-File/BinaryFileStream.extension.st +++ b/src/Soil-File/BinaryFileStream.extension.st @@ -1,39 +1,50 @@ -Extension { #name : #BinaryFileStream } +Extension { #name : 'BinaryFileStream' } -{ #category : #'*Soil-File' } -BinaryFileStream >> fdatasync [ - | fd err | - fd := self fileno. - err := self fdatasync: self fileno. - (err == 0) ifFalse: [ - Error signal: 'fdatasync(', fd printString, ') failed with error code: ', err printString ] +{ #category : '*Soil-File' } +BinaryFileStream >> fdatasync [ + "Flush data (but not necessarily metadata) to disk. Platform-independent implementation." + + OSPlatform current isWindows + ifTrue: [ self fsyncWindows ] + ifFalse: [ self fdatasyncUnix ] ] -{ #category : #'*Soil-File' } +{ #category : '*Soil-File' } BinaryFileStream >> fdatasync: fd [ ^ self ffiCall: #(int fdatasync(int fd)) module: LibC ] -{ #category : #'*Soil-File' } +{ #category : '*Soil-File' } +BinaryFileStream >> fdatasyncUnix [ + "Unix/Linux/MacOS implementation of fdatasync using POSIX API" + + | fd err | + fd := self fileno. + err := self fdatasync: fd. + (err = 0) ifFalse: [ + Error signal: 'fdatasync(', fd printString, ') failed with error code: ', err printString ] +] + +{ #category : '*Soil-File' } BinaryFileStream >> fileHandle [ ^ handle pointerAt: 9 ] -{ #category : #'*Soil-File' } +{ #category : '*Soil-File' } BinaryFileStream >> fileno [ | fd | fd := self fileno: self fileHandle. - (fd == -1) ifTrue: [ + (fd = -1) ifTrue: [ Error signal: 'cannot get file descriptor for ', self name, ': error = ', ((ExternalAddress loadSymbol: #errno) signedLongAt: 1) printString]. ^ fd ] -{ #category : #'*Soil-File' } +{ #category : '*Soil-File' } BinaryFileStream >> fileno: stream [ ^ self @@ -41,28 +52,54 @@ BinaryFileStream >> fileno: stream [ module: LibC ] -{ #category : #'*Soil-File' } +{ #category : '*Soil-File' } BinaryFileStream >> flockClass [ ^ OSPlatform current flockClass ] -{ #category : #'*Soil-File' } -BinaryFileStream >> fsync [ - | fd err | - fd := self fileno. - err := self fsync: self fileno. - (err == 0) ifFalse: [ - Error signal: 'fsync(', fd printString, ') failed with error code: ', err printString ] +{ #category : '*Soil-File' } +BinaryFileStream >> fsync [ + "Flush all buffered data to disk. Platform-independent implementation." + + OSPlatform current isWindows + ifTrue: [ self fsyncWindows ] + ifFalse: [ self fsyncUnix ] ] -{ #category : #'*Soil-File' } +{ #category : '*Soil-File' } BinaryFileStream >> fsync: fd [ ^ self ffiCall: #(int fsync(int fd)) module: LibC ] -{ #category : #'*Soil-File' } +{ #category : '*Soil-File' } +BinaryFileStream >> fsyncUnix [ + "Unix/Linux/MacOS implementation of fsync using POSIX API" + + | fd err | + fd := self fileno. + err := self fsync: fd. + err = 0 ifFalse: [ + Error signal: + 'fsync(' , fd printString , ') failed with error code: ' + , err printString ] +] + +{ #category : '*Soil-File' } +BinaryFileStream >> fsyncWindows [ + "Windows implementation of fsync using FlushFileBuffers API" + + | result errorCode | + result := WindowsFileLock flushFileBuffers: self fileHandle. + result ifFalse: [ + errorCode := WindowsFileLock getLastError. + Error signal: + 'FlushFileBuffers failed with error code: ' + , errorCode printString ] +] + +{ #category : '*Soil-File' } BinaryFileStream >> lockAt: position length: length [ ^ self flockClass lock: self fileHandle @@ -70,7 +107,7 @@ BinaryFileStream >> lockAt: position length: length [ length: length ] -{ #category : #'*Soil-File' } +{ #category : '*Soil-File' } BinaryFileStream >> lockEntireFile [ ^ self flockClass lock: self fileHandle @@ -78,7 +115,7 @@ BinaryFileStream >> lockEntireFile [ length: self size ] -{ #category : #'*Soil-File' } +{ #category : '*Soil-File' } BinaryFileStream >> lockEntireFileAndBeyond [ "add a lock for appending to a file. We use the whole file starting at 0 to have reliable semantics. Using the end of the file might be more @@ -90,7 +127,7 @@ BinaryFileStream >> lockEntireFileAndBeyond [ length: 0 ] -{ #category : #'*Soil-File' } +{ #category : '*Soil-File' } BinaryFileStream >> lockForAppendStartingAt: offset [ "add append lock by taking an offset. Having an append lock and matching unlock is not possible from within this stream so the offset needs to be @@ -101,20 +138,20 @@ BinaryFileStream >> lockForAppendStartingAt: offset [ length: 0 ] -{ #category : #'*Soil-File' } +{ #category : '*Soil-File' } BinaryFileStream >> sync [ ^ self sync: (self fileno: self fileHandle). ] -{ #category : #'*Soil-File' } +{ #category : '*Soil-File' } BinaryFileStream >> sync: fd [ ^ self ffiCall: #(int fsync(int fd)) module: LibC ] -{ #category : #'*Soil-File' } +{ #category : '*Soil-File' } BinaryFileStream >> unlockAt: position length: length [ ^ self flockClass @@ -123,7 +160,7 @@ BinaryFileStream >> unlockAt: position length: length [ length: length ] -{ #category : #'*Soil-File' } +{ #category : '*Soil-File' } BinaryFileStream >> unlockEntireFile [ ^ self flockClass unlock: self fileHandle @@ -131,7 +168,7 @@ BinaryFileStream >> unlockEntireFile [ length: self size ] -{ #category : #'*Soil-File' } +{ #category : '*Soil-File' } BinaryFileStream >> unlockEntireFileAndBeyond [ "release a lock for appending to a file. We use the whole file starting at 0 to have reliable semantics. The end of the file might have changed @@ -143,7 +180,7 @@ BinaryFileStream >> unlockEntireFileAndBeyond [ length: 0 ] -{ #category : #'*Soil-File' } +{ #category : '*Soil-File' } BinaryFileStream >> unlockForAppendStartingAt: offset [ "release append lock by taking an offset. Having an append lock and matching unlock is not possible from within this stream so the offset needs to be diff --git a/src/Soil-File/WinOverlapped.class.st b/src/Soil-File/WinOverlapped.class.st new file mode 100644 index 00000000..66fc5e90 --- /dev/null +++ b/src/Soil-File/WinOverlapped.class.st @@ -0,0 +1,103 @@ +Class { + #name : 'WinOverlapped', + #superclass : 'FFIStructure', + #classVars : [ + 'OFFSET_HEVENT', + 'OFFSET_INTERNAL', + 'OFFSET_INTERNALHIGH', + 'OFFSET_OFFSET', + 'OFFSET_OFFSETHIGH' + ], + #category : 'Soil-File', + #package : 'Soil-File' +} + +{ #category : 'field definition' } +WinOverlapped class >> fieldsDesc [ + "self rebuildFieldAccessors" + + ^ #( + uint64 internal; + uint64 internalHigh; + uint offset; + uint offsetHigh; + void* hEvent; + ) +] + +{ #category : 'accessing - structure variables' } +WinOverlapped >> hEvent [ + "This method was automatically generated" + ^ExternalData fromHandle: (handle pointerAt: OFFSET_HEVENT) type: ExternalType void asPointerType +] + +{ #category : 'accessing - structure variables' } +WinOverlapped >> hEvent: anObject [ + "This method was automatically generated" + handle pointerAt: OFFSET_HEVENT put: anObject getHandle. +] + +{ #category : 'initialization' } +WinOverlapped >> initialize [ + super initialize +] + +{ #category : 'accessing - structure variables' } +WinOverlapped >> internal [ + "This method was automatically generated" + ^handle unsignedLongLongAt: OFFSET_INTERNAL +] + +{ #category : 'accessing - structure variables' } +WinOverlapped >> internal: anObject [ + "This method was automatically generated" + handle unsignedLongLongAt: OFFSET_INTERNAL put: anObject +] + +{ #category : 'accessing - structure variables' } +WinOverlapped >> internalHigh [ + "This method was automatically generated" + ^handle unsignedLongLongAt: OFFSET_INTERNALHIGH +] + +{ #category : 'accessing - structure variables' } +WinOverlapped >> internalHigh: anObject [ + "This method was automatically generated" + handle unsignedLongLongAt: OFFSET_INTERNALHIGH put: anObject +] + +{ #category : 'accessing - structure variables' } +WinOverlapped >> offset [ + "This method was automatically generated" + ^handle unsignedLongAt: OFFSET_OFFSET +] + +{ #category : 'accessing - structure variables' } +WinOverlapped >> offset: anObject [ + "This method was automatically generated" + handle unsignedLongAt: OFFSET_OFFSET put: anObject +] + +{ #category : 'accessing - structure variables' } +WinOverlapped >> offsetHigh [ + "This method was automatically generated" + ^handle unsignedLongAt: OFFSET_OFFSETHIGH +] + +{ #category : 'accessing - structure variables' } +WinOverlapped >> offsetHigh: anObject [ + "This method was automatically generated" + handle unsignedLongAt: OFFSET_OFFSETHIGH put: anObject +] + +{ #category : 'printing' } +WinOverlapped >> printOn: aStream [ + "Append to the argument, aStream, the names and values of all the record's variables." + + aStream nextPutAll: self class name; nextPutAll: ' ( '; cr. + self class fieldSpec fieldNames do: [ :field | + aStream nextPutAll: field; nextPut: $:; space; tab. + (self perform: field ) printOn: aStream. + ] separatedBy: [ aStream cr ]. + aStream cr; nextPut: $) +] diff --git a/src/Soil-File/WinPlatform.extension.st b/src/Soil-File/WinPlatform.extension.st new file mode 100644 index 00000000..d8d6acbb --- /dev/null +++ b/src/Soil-File/WinPlatform.extension.st @@ -0,0 +1,6 @@ +Extension { #name : 'WinPlatform' } + +{ #category : '*Soil-File' } +WinPlatform >> flockClass [ + ^ WindowsFileLock +] diff --git a/src/Soil-File/WindowsFileLock.class.st b/src/Soil-File/WindowsFileLock.class.st new file mode 100644 index 00000000..b4984235 --- /dev/null +++ b/src/Soil-File/WindowsFileLock.class.st @@ -0,0 +1,234 @@ +Class { + #name : 'WindowsFileLock', + #superclass : 'Object', + #classVars : [ + 'ERROR_ACCESS_DENIED', + 'ERROR_INVALID_HANDLE', + 'ERROR_IO_PENDING', + 'ERROR_LOCK_VIOLATION', + 'ERROR_NOT_LOCKED', + 'LOCKFILE_EXCLUSIVE_LOCK', + 'LOCKFILE_FAIL_IMMEDIATELY', + 'LOCKFILE_SHARED_LOCK' + ], + #category : 'Soil-File', + #package : 'Soil-File' +} + +{ #category : 'testing' } +WindowsFileLock class >> canLock: fileHandle from: start length: length exclusive: exclusive [ + "Windows doesn't have a direct 'test lock' API like F_GETLK. + We attempt to lock and immediately unlock if successful." + + | success | + success := self lock: fileHandle from: start length: length exclusive: exclusive. + success ifTrue: [ + self unlock: fileHandle from: start length: length ]. + ^ success +] + +{ #category : 'private - error handling' } +WindowsFileLock class >> errorDescription: errorCode [ + "Return a human-readable description for Windows error codes" + + errorCode = ERROR_ACCESS_DENIED ifTrue: [ ^ 'Access denied' ]. + errorCode = ERROR_INVALID_HANDLE ifTrue: [ ^ 'Invalid file handle' ]. + errorCode = ERROR_LOCK_VIOLATION ifTrue: [ ^ 'Lock violation (region already locked)' ]. + errorCode = ERROR_NOT_LOCKED ifTrue: [ ^ 'Region not locked' ]. + errorCode = ERROR_IO_PENDING ifTrue: [ ^ 'I/O operation pending' ]. + + ^ 'Unknown error (code: ', errorCode printString, ')' +] + +{ #category : 'private - ffi' } +WindowsFileLock class >> flushFileBuffers: hFile [ + "Flush all file buffers to disk using Windows API" + + ^ self ffiCall: #(bool FlushFileBuffers(void* hFile)) module: #kernel32 +] + +{ #category : 'private - ffi' } +WindowsFileLock class >> getCurrentProcessId [ + "Get the current process ID using Windows API" + + ^ self ffiCall: #(uint GetCurrentProcessId()) module: #kernel32 +] + +{ #category : 'private - ffi' } +WindowsFileLock class >> getFileHandle: stream [ + "Extract Windows HANDLE from a BinaryFileStream. + For buffered streams (ZnBufferedWriteStream), get the wrapped file stream first." + + | fileStream | + fileStream := stream fileStream. + ^ fileStream fileHandle +] + +{ #category : 'private - ffi' } +WindowsFileLock class >> getLastError [ + "Get Windows error code" + + ^ self ffiCall: #(uint GetLastError()) module: #kernel32 +] + +{ #category : 'class initialization' } +WindowsFileLock class >> initialize [ + "Windows API constants" + + LOCKFILE_EXCLUSIVE_LOCK := 16r00000002. + LOCKFILE_FAIL_IMMEDIATELY := 16r00000001. + LOCKFILE_SHARED_LOCK := 16r00000000. + + "Error codes - from winerror.h" + ERROR_ACCESS_DENIED := 5. + ERROR_INVALID_HANDLE := 6. + ERROR_LOCK_VIOLATION := 33. + ERROR_NOT_LOCKED := 158. + ERROR_IO_PENDING := 997 +] + +{ #category : 'accessing locking' } +WindowsFileLock class >> lock: fileHandle from: start length: length [ + + ^ self + lock: fileHandle + from: start + length: length + exclusive: true +] + +{ #category : 'accessing locking' } +WindowsFileLock class >> lock: fileHandle from: start length: length exclusive: exclusive [ + "Lock file region, returning true on success, false on failure. + For detailed error information, use lockOrError:from:length:exclusive:" + + | overlapped flags result lockLow lockHigh | + + "fileHandle is already a Windows HANDLE (ExternalAddress)" + + "Create and initialize OVERLAPPED structure" + overlapped := WinOverlapped new. + overlapped offset: (start bitAnd: 16rFFFFFFFF). + overlapped offsetHigh: (start bitShift: -32). + overlapped hEvent: ExternalAddress null. + + "Set flags for exclusive or shared lock" + flags := exclusive + ifTrue: [ LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY ] + ifFalse: [ LOCKFILE_SHARED_LOCK | LOCKFILE_FAIL_IMMEDIATELY ]. + + "Calculate lock length in low/high parts" + lockLow := length = 0 + ifTrue: [ 16rFFFFFFFF ] "0 means lock to EOF" + ifFalse: [ length bitAnd: 16rFFFFFFFF ]. + lockHigh := length = 0 + ifTrue: [ 16rFFFFFFFF ] + ifFalse: [ length bitShift: -32 ]. + + "Call LockFileEx" + result := self + lockFileEx: fileHandle + flags: flags + bytesLow: lockLow + bytesHigh: lockHigh + overlapped: overlapped. + + "Return success status" + ^ result ~= 0 +] + +{ #category : 'private - ffi' } +WindowsFileLock class >> lockFileEx: hFile flags: dwFlags bytesLow: nNumberOfBytesToLockLow bytesHigh: nNumberOfBytesToLockHigh overlapped: lpOverlapped [ + + ^ self + ffiCall: #(bool LockFileEx( + void* hFile, + uint dwFlags, + uint 0, + uint nNumberOfBytesToLockLow, + uint nNumberOfBytesToLockHigh, + WinOverlapped *lpOverlapped)) + module: #kernel32 +] + +{ #category : 'accessing locking' } +WindowsFileLock class >> lockOrError: fileHandle from: start length: length exclusive: exclusive [ + "Lock file region, raising an error with detailed information on failure" + + | success errorCode errorDesc lockType | + success := self lock: fileHandle from: start length: length exclusive: exclusive. + + success ifFalse: [ + errorCode := self getLastError. + errorDesc := self errorDescription: errorCode. + lockType := exclusive ifTrue: [ 'exclusive' ] ifFalse: [ 'shared' ]. + + Error signal: 'LockFileEx failed: ', lockType, ' lock at offset ', start printString, + ' length ', length printString, ' - ', errorDesc ]. + + ^ success +] + +{ #category : 'accessing locking' } +WindowsFileLock class >> unlock: fileHandle from: start length: length [ + "Unlock file region, returning true on success, false on failure. + For detailed error information, use unlockOrError:from:length:" + + | overlapped result unlockLow unlockHigh | + + "fileHandle is already a Windows HANDLE (ExternalAddress)" + + "Create and initialize OVERLAPPED structure" + overlapped := WinOverlapped new. + overlapped offset: (start bitAnd: 16rFFFFFFFF). + overlapped offsetHigh: (start bitShift: -32). + overlapped hEvent: ExternalAddress null. + + "Calculate unlock length in low/high parts" + unlockLow := length = 0 + ifTrue: [ 16rFFFFFFFF ] + ifFalse: [ length bitAnd: 16rFFFFFFFF ]. + unlockHigh := length = 0 + ifTrue: [ 16rFFFFFFFF ] + ifFalse: [ length bitShift: -32 ]. + + "Call UnlockFileEx" + result := self + unlockFileEx: fileHandle + bytesLow: unlockLow + bytesHigh: unlockHigh + overlapped: overlapped. + + "Return success status" + ^ result ~= 0 +] + +{ #category : 'private - ffi' } +WindowsFileLock class >> unlockFileEx: hFile bytesLow: nNumberOfBytesToUnlockLow bytesHigh: nNumberOfBytesToUnlockHigh overlapped: lpOverlapped [ + + ^ self + ffiCall: #(bool UnlockFileEx( + void* hFile, + uint 0, + uint nNumberOfBytesToUnlockLow, + uint nNumberOfBytesToUnlockHigh, + WinOverlapped *lpOverlapped)) + module: #kernel32 +] + +{ #category : 'accessing locking' } +WindowsFileLock class >> unlockOrError: fileHandle from: start length: length [ + "Unlock file region, raising an error with detailed information on failure" + + | success errorCode errorDesc | + success := self unlock: fileHandle from: start length: length. + + success ifFalse: [ + errorCode := self getLastError. + errorDesc := self errorDescription: errorCode. + + Error signal: 'UnlockFileEx failed: unlock at offset ', start printString, + ' length ', length printString, ' - ', errorDesc ]. + + ^ success +] From ebd8fb0dcc97cc8a4c7248d3a1c48889c4a820b5 Mon Sep 17 00:00:00 2001 From: Masashi Umezawa Date: Fri, 14 Nov 2025 23:36:51 +0900 Subject: [PATCH 2/7] - Renamed WindowsFileLock to SoilWindowsFileLock - Renamed WinOverlapped to SoilWinOverlappedStruct - Renamed WindowsFileLockTest to SoilWindowsFileLockTest - Removed unused fdatasync methods from BinaryFileStream extension --- ...ss.st => SoilWindowsFileLockTest.class.st} | 84 ++++++++----------- src/Soil-File/BinaryFileStream.extension.st | 31 +------ ...ss.st => SoilWinOverlappedStruct.class.st} | 28 +++---- ....class.st => SoilWindowsFileLock.class.st} | 38 ++++----- src/Soil-File/WinPlatform.extension.st | 2 +- 5 files changed, 70 insertions(+), 113 deletions(-) rename src/Soil-File-Tests/{WindowsFileLockTest.class.st => SoilWindowsFileLockTest.class.st} (57%) rename src/Soil-File/{WinOverlapped.class.st => SoilWinOverlappedStruct.class.st} (79%) rename src/Soil-File/{WindowsFileLock.class.st => SoilWindowsFileLock.class.st} (81%) diff --git a/src/Soil-File-Tests/WindowsFileLockTest.class.st b/src/Soil-File-Tests/SoilWindowsFileLockTest.class.st similarity index 57% rename from src/Soil-File-Tests/WindowsFileLockTest.class.st rename to src/Soil-File-Tests/SoilWindowsFileLockTest.class.st index 48348538..06beaf7a 100644 --- a/src/Soil-File-Tests/WindowsFileLockTest.class.st +++ b/src/Soil-File-Tests/SoilWindowsFileLockTest.class.st @@ -1,5 +1,5 @@ Class { - #name : 'WindowsFileLockTest', + #name : 'SoilWindowsFileLockTest', #superclass : 'TestCase', #instVars : [ 'testFile', @@ -10,14 +10,14 @@ Class { } { #category : 'running' } -WindowsFileLockTest >> setUp [ +SoilWindowsFileLockTest >> setUp [ super setUp. testFile := 'soil-winlock-test.tmp' asFileReference. testFile ensureDelete ] { #category : 'running' } -WindowsFileLockTest >> tearDown [ +SoilWindowsFileLockTest >> tearDown [ stream ifNotNil: [ [ stream close ] on: Error do: [ :ex | "ignore close errors" ] ]. testFile ifNotNil: [ @@ -26,39 +26,23 @@ WindowsFileLockTest >> tearDown [ ] { #category : 'tests' } -WindowsFileLockTest >> testErrorDescriptions [ +SoilWindowsFileLockTest >> testErrorDescriptions [ "Test error description helper method" OSPlatform current isWindows ifFalse: [ ^ self skip ]. "Test known error codes" - self assert: (WindowsFileLock errorDescription: 5) equals: 'Access denied'. - self assert: (WindowsFileLock errorDescription: 6) equals: 'Invalid file handle'. - self assert: (WindowsFileLock errorDescription: 33) equals: 'Lock violation (region already locked)'. - self assert: (WindowsFileLock errorDescription: 158) equals: 'Region not locked'. + self assert: (SoilWindowsFileLock errorDescription: 5) equals: 'Access denied'. + self assert: (SoilWindowsFileLock errorDescription: 6) equals: 'Invalid file handle'. + self assert: (SoilWindowsFileLock errorDescription: 33) equals: 'Lock violation (region already locked)'. + self assert: (SoilWindowsFileLock errorDescription: 158) equals: 'Region not locked'. "Test unknown error code" - self assert: ((WindowsFileLock errorDescription: 9999) includesSubstring: 'Unknown error') + self assert: ((SoilWindowsFileLock errorDescription: 9999) includesSubstring: 'Unknown error') ] { #category : 'tests' } -WindowsFileLockTest >> testFdatasyncEntireFile [ - "Test fdatasync on Windows (should call same as fsync)" - - OSPlatform current isWindows ifFalse: [ ^ self skip ]. - - stream := testFile binaryWriteStream. - stream nextPutAll: 'Testing fdatasync on Windows!'. - stream flush. - - "Call fdatasync - should not raise an error" - self shouldnt: [ stream fileStream fdatasync ] raise: Error. - - stream close -] - -{ #category : 'tests' } -WindowsFileLockTest >> testFlushFileBuffersDirect [ +SoilWindowsFileLockTest >> testFlushFileBuffersDirect [ "Test FlushFileBuffers FFI call directly" | result | @@ -69,14 +53,14 @@ WindowsFileLockTest >> testFlushFileBuffersDirect [ stream flush. "Call FlushFileBuffers directly" - result := WindowsFileLock flushFileBuffers: stream fileStream fileHandle. + result := SoilWindowsFileLock flushFileBuffers: stream fileStream fileHandle. "Should return true (non-zero) on success" self assert: result ] { #category : 'tests' } -WindowsFileLockTest >> testFsyncEntireFile [ +SoilWindowsFileLockTest >> testFsyncEntireFile [ "Test fsync on Windows using FlushFileBuffers" OSPlatform current isWindows ifFalse: [ ^ self skip ]. @@ -92,7 +76,7 @@ WindowsFileLockTest >> testFsyncEntireFile [ ] { #category : 'tests' } -WindowsFileLockTest >> testLockAndUnlockEntireFile [ +SoilWindowsFileLockTest >> testLockAndUnlockEntireFile [ "Test locking and unlocking the entire file (length = 0)" OSPlatform current isWindows ifFalse: [ ^ self skip ]. @@ -102,14 +86,14 @@ WindowsFileLockTest >> testLockAndUnlockEntireFile [ stream flush. "Lock entire file" - self assert: (WindowsFileLock lock: stream fileStream fileHandle from: 0 length: 0). + self assert: (SoilWindowsFileLock lock: stream fileStream fileHandle from: 0 length: 0). "Unlock entire file" - self assert: (WindowsFileLock unlock: stream fileStream fileHandle from: 0 length: 0) + self assert: (SoilWindowsFileLock unlock: stream fileStream fileHandle from: 0 length: 0) ] { #category : 'tests' } -WindowsFileLockTest >> testLockAndUnlockRange [ +SoilWindowsFileLockTest >> testLockAndUnlockRange [ "Test locking and unlocking a specific byte range" OSPlatform current isWindows ifFalse: [ ^ self skip ]. @@ -119,14 +103,14 @@ WindowsFileLockTest >> testLockAndUnlockRange [ stream flush. "Lock bytes 7 to 14 (the word 'Windows')" - self assert: (WindowsFileLock lock: stream fileStream fileHandle from: 7 length: 7). + self assert: (SoilWindowsFileLock lock: stream fileStream fileHandle from: 7 length: 7). "Unlock the same range" - self assert: (WindowsFileLock unlock: stream fileStream fileHandle from: 7 length: 7) + self assert: (SoilWindowsFileLock unlock: stream fileStream fileHandle from: 7 length: 7) ] { #category : 'tests' } -WindowsFileLockTest >> testLockExclusive [ +SoilWindowsFileLockTest >> testLockExclusive [ "Test exclusive lock" OSPlatform current isWindows ifFalse: [ ^ self skip ]. @@ -136,14 +120,14 @@ WindowsFileLockTest >> testLockExclusive [ stream flush. "Lock with exclusive flag" - self assert: (WindowsFileLock lock: stream fileStream fileHandle from: 0 length: 100 exclusive: true). + self assert: (SoilWindowsFileLock lock: stream fileStream fileHandle from: 0 length: 100 exclusive: true). "Unlock" - self assert: (WindowsFileLock unlock: stream fileStream fileHandle from: 0 length: 100) + self assert: (SoilWindowsFileLock unlock: stream fileStream fileHandle from: 0 length: 100) ] { #category : 'tests' } -WindowsFileLockTest >> testLockOrErrorSuccess [ +SoilWindowsFileLockTest >> testLockOrErrorSuccess [ "Test lockOrError with successful lock" OSPlatform current isWindows ifFalse: [ ^ self skip ]. @@ -154,15 +138,15 @@ WindowsFileLockTest >> testLockOrErrorSuccess [ "Should not raise an error" self shouldnt: [ - WindowsFileLock lockOrError: stream fileStream fileHandle from: 0 length: 100 exclusive: true ] + SoilWindowsFileLock lockOrError: stream fileStream fileHandle from: 0 length: 100 exclusive: true ] raise: Error. "Clean up" - WindowsFileLock unlock: stream fileStream fileHandle from: 0 length: 100 + SoilWindowsFileLock unlock: stream fileStream fileHandle from: 0 length: 100 ] { #category : 'tests' } -WindowsFileLockTest >> testLockShared [ +SoilWindowsFileLockTest >> testLockShared [ "Test shared lock" OSPlatform current isWindows ifFalse: [ ^ self skip ]. @@ -172,14 +156,14 @@ WindowsFileLockTest >> testLockShared [ stream flush. "Lock with shared flag" - self assert: (WindowsFileLock lock: stream fileStream fileHandle from: 0 length: 100 exclusive: false). + self assert: (SoilWindowsFileLock lock: stream fileStream fileHandle from: 0 length: 100 exclusive: false). "Unlock" - self assert: (WindowsFileLock unlock: stream fileStream fileHandle from: 0 length: 100) + self assert: (SoilWindowsFileLock unlock: stream fileStream fileHandle from: 0 length: 100) ] { #category : 'tests' } -WindowsFileLockTest >> testMultipleLocks [ +SoilWindowsFileLockTest >> testMultipleLocks [ "Test locking multiple non-overlapping ranges" OSPlatform current isWindows ifFalse: [ ^ self skip ]. @@ -189,24 +173,24 @@ WindowsFileLockTest >> testMultipleLocks [ stream flush. "Lock range 0-9" - self assert: (WindowsFileLock lock: stream fileStream fileHandle from: 0 length: 10). + self assert: (SoilWindowsFileLock lock: stream fileStream fileHandle from: 0 length: 10). "Lock range 10-19 (non-overlapping)" - self assert: (WindowsFileLock lock: stream fileStream fileHandle from: 10 length: 10). + self assert: (SoilWindowsFileLock lock: stream fileStream fileHandle from: 10 length: 10). "Unlock both ranges" - self assert: (WindowsFileLock unlock: stream fileStream fileHandle from: 0 length: 10). - self assert: (WindowsFileLock unlock: stream fileStream fileHandle from: 10 length: 10) + self assert: (SoilWindowsFileLock unlock: stream fileStream fileHandle from: 0 length: 10). + self assert: (SoilWindowsFileLock unlock: stream fileStream fileHandle from: 10 length: 10) ] { #category : 'tests' } -WindowsFileLockTest >> testWinOverlappedStructure [ +SoilWindowsFileLockTest >> testWinOverlappedStructure [ "Test WinOverlapped structure initialization and field access" | overlapped | OSPlatform current isWindows ifFalse: [ ^ self skip ]. - overlapped := WinOverlapped new. + overlapped := SoilWinOverlappedStruct new. "Test field setters and getters" overlapped offset: 16r12345678. diff --git a/src/Soil-File/BinaryFileStream.extension.st b/src/Soil-File/BinaryFileStream.extension.st index 2a4957db..d7dc8280 100644 --- a/src/Soil-File/BinaryFileStream.extension.st +++ b/src/Soil-File/BinaryFileStream.extension.st @@ -1,32 +1,5 @@ Extension { #name : 'BinaryFileStream' } -{ #category : '*Soil-File' } -BinaryFileStream >> fdatasync [ - "Flush data (but not necessarily metadata) to disk. Platform-independent implementation." - - OSPlatform current isWindows - ifTrue: [ self fsyncWindows ] - ifFalse: [ self fdatasyncUnix ] -] - -{ #category : '*Soil-File' } -BinaryFileStream >> fdatasync: fd [ - ^ self - ffiCall: #(int fdatasync(int fd)) - module: LibC -] - -{ #category : '*Soil-File' } -BinaryFileStream >> fdatasyncUnix [ - "Unix/Linux/MacOS implementation of fdatasync using POSIX API" - - | fd err | - fd := self fileno. - err := self fdatasync: fd. - (err = 0) ifFalse: [ - Error signal: 'fdatasync(', fd printString, ') failed with error code: ', err printString ] -] - { #category : '*Soil-File' } BinaryFileStream >> fileHandle [ @@ -91,9 +64,9 @@ BinaryFileStream >> fsyncWindows [ "Windows implementation of fsync using FlushFileBuffers API" | result errorCode | - result := WindowsFileLock flushFileBuffers: self fileHandle. + result := SoilWindowsFileLock flushFileBuffers: self fileHandle. result ifFalse: [ - errorCode := WindowsFileLock getLastError. + errorCode := SoilWindowsFileLock getLastError. Error signal: 'FlushFileBuffers failed with error code: ' , errorCode printString ] diff --git a/src/Soil-File/WinOverlapped.class.st b/src/Soil-File/SoilWinOverlappedStruct.class.st similarity index 79% rename from src/Soil-File/WinOverlapped.class.st rename to src/Soil-File/SoilWinOverlappedStruct.class.st index 66fc5e90..24bbe267 100644 --- a/src/Soil-File/WinOverlapped.class.st +++ b/src/Soil-File/SoilWinOverlappedStruct.class.st @@ -1,5 +1,5 @@ Class { - #name : 'WinOverlapped', + #name : 'SoilWinOverlappedStruct', #superclass : 'FFIStructure', #classVars : [ 'OFFSET_HEVENT', @@ -13,7 +13,7 @@ Class { } { #category : 'field definition' } -WinOverlapped class >> fieldsDesc [ +SoilWinOverlappedStruct class >> fieldsDesc [ "self rebuildFieldAccessors" ^ #( @@ -26,72 +26,72 @@ WinOverlapped class >> fieldsDesc [ ] { #category : 'accessing - structure variables' } -WinOverlapped >> hEvent [ +SoilWinOverlappedStruct >> hEvent [ "This method was automatically generated" ^ExternalData fromHandle: (handle pointerAt: OFFSET_HEVENT) type: ExternalType void asPointerType ] { #category : 'accessing - structure variables' } -WinOverlapped >> hEvent: anObject [ +SoilWinOverlappedStruct >> hEvent: anObject [ "This method was automatically generated" handle pointerAt: OFFSET_HEVENT put: anObject getHandle. ] { #category : 'initialization' } -WinOverlapped >> initialize [ +SoilWinOverlappedStruct >> initialize [ super initialize ] { #category : 'accessing - structure variables' } -WinOverlapped >> internal [ +SoilWinOverlappedStruct >> internal [ "This method was automatically generated" ^handle unsignedLongLongAt: OFFSET_INTERNAL ] { #category : 'accessing - structure variables' } -WinOverlapped >> internal: anObject [ +SoilWinOverlappedStruct >> internal: anObject [ "This method was automatically generated" handle unsignedLongLongAt: OFFSET_INTERNAL put: anObject ] { #category : 'accessing - structure variables' } -WinOverlapped >> internalHigh [ +SoilWinOverlappedStruct >> internalHigh [ "This method was automatically generated" ^handle unsignedLongLongAt: OFFSET_INTERNALHIGH ] { #category : 'accessing - structure variables' } -WinOverlapped >> internalHigh: anObject [ +SoilWinOverlappedStruct >> internalHigh: anObject [ "This method was automatically generated" handle unsignedLongLongAt: OFFSET_INTERNALHIGH put: anObject ] { #category : 'accessing - structure variables' } -WinOverlapped >> offset [ +SoilWinOverlappedStruct >> offset [ "This method was automatically generated" ^handle unsignedLongAt: OFFSET_OFFSET ] { #category : 'accessing - structure variables' } -WinOverlapped >> offset: anObject [ +SoilWinOverlappedStruct >> offset: anObject [ "This method was automatically generated" handle unsignedLongAt: OFFSET_OFFSET put: anObject ] { #category : 'accessing - structure variables' } -WinOverlapped >> offsetHigh [ +SoilWinOverlappedStruct >> offsetHigh [ "This method was automatically generated" ^handle unsignedLongAt: OFFSET_OFFSETHIGH ] { #category : 'accessing - structure variables' } -WinOverlapped >> offsetHigh: anObject [ +SoilWinOverlappedStruct >> offsetHigh: anObject [ "This method was automatically generated" handle unsignedLongAt: OFFSET_OFFSETHIGH put: anObject ] { #category : 'printing' } -WinOverlapped >> printOn: aStream [ +SoilWinOverlappedStruct >> printOn: aStream [ "Append to the argument, aStream, the names and values of all the record's variables." aStream nextPutAll: self class name; nextPutAll: ' ( '; cr. diff --git a/src/Soil-File/WindowsFileLock.class.st b/src/Soil-File/SoilWindowsFileLock.class.st similarity index 81% rename from src/Soil-File/WindowsFileLock.class.st rename to src/Soil-File/SoilWindowsFileLock.class.st index b4984235..943baac8 100644 --- a/src/Soil-File/WindowsFileLock.class.st +++ b/src/Soil-File/SoilWindowsFileLock.class.st @@ -1,5 +1,5 @@ Class { - #name : 'WindowsFileLock', + #name : 'SoilWindowsFileLock', #superclass : 'Object', #classVars : [ 'ERROR_ACCESS_DENIED', @@ -16,7 +16,7 @@ Class { } { #category : 'testing' } -WindowsFileLock class >> canLock: fileHandle from: start length: length exclusive: exclusive [ +SoilWindowsFileLock class >> canLock: fileHandle from: start length: length exclusive: exclusive [ "Windows doesn't have a direct 'test lock' API like F_GETLK. We attempt to lock and immediately unlock if successful." @@ -28,7 +28,7 @@ WindowsFileLock class >> canLock: fileHandle from: start length: length exclusiv ] { #category : 'private - error handling' } -WindowsFileLock class >> errorDescription: errorCode [ +SoilWindowsFileLock class >> errorDescription: errorCode [ "Return a human-readable description for Windows error codes" errorCode = ERROR_ACCESS_DENIED ifTrue: [ ^ 'Access denied' ]. @@ -41,21 +41,21 @@ WindowsFileLock class >> errorDescription: errorCode [ ] { #category : 'private - ffi' } -WindowsFileLock class >> flushFileBuffers: hFile [ +SoilWindowsFileLock class >> flushFileBuffers: hFile [ "Flush all file buffers to disk using Windows API" ^ self ffiCall: #(bool FlushFileBuffers(void* hFile)) module: #kernel32 ] { #category : 'private - ffi' } -WindowsFileLock class >> getCurrentProcessId [ +SoilWindowsFileLock class >> getCurrentProcessId [ "Get the current process ID using Windows API" ^ self ffiCall: #(uint GetCurrentProcessId()) module: #kernel32 ] { #category : 'private - ffi' } -WindowsFileLock class >> getFileHandle: stream [ +SoilWindowsFileLock class >> getFileHandle: stream [ "Extract Windows HANDLE from a BinaryFileStream. For buffered streams (ZnBufferedWriteStream), get the wrapped file stream first." @@ -65,14 +65,14 @@ WindowsFileLock class >> getFileHandle: stream [ ] { #category : 'private - ffi' } -WindowsFileLock class >> getLastError [ +SoilWindowsFileLock class >> getLastError [ "Get Windows error code" ^ self ffiCall: #(uint GetLastError()) module: #kernel32 ] { #category : 'class initialization' } -WindowsFileLock class >> initialize [ +SoilWindowsFileLock class >> initialize [ "Windows API constants" LOCKFILE_EXCLUSIVE_LOCK := 16r00000002. @@ -88,7 +88,7 @@ WindowsFileLock class >> initialize [ ] { #category : 'accessing locking' } -WindowsFileLock class >> lock: fileHandle from: start length: length [ +SoilWindowsFileLock class >> lock: fileHandle from: start length: length [ ^ self lock: fileHandle @@ -98,7 +98,7 @@ WindowsFileLock class >> lock: fileHandle from: start length: length [ ] { #category : 'accessing locking' } -WindowsFileLock class >> lock: fileHandle from: start length: length exclusive: exclusive [ +SoilWindowsFileLock class >> lock: fileHandle from: start length: length exclusive: exclusive [ "Lock file region, returning true on success, false on failure. For detailed error information, use lockOrError:from:length:exclusive:" @@ -107,7 +107,7 @@ WindowsFileLock class >> lock: fileHandle from: start length: length exclusive: "fileHandle is already a Windows HANDLE (ExternalAddress)" "Create and initialize OVERLAPPED structure" - overlapped := WinOverlapped new. + overlapped := SoilWinOverlappedStruct new. overlapped offset: (start bitAnd: 16rFFFFFFFF). overlapped offsetHigh: (start bitShift: -32). overlapped hEvent: ExternalAddress null. @@ -138,7 +138,7 @@ WindowsFileLock class >> lock: fileHandle from: start length: length exclusive: ] { #category : 'private - ffi' } -WindowsFileLock class >> lockFileEx: hFile flags: dwFlags bytesLow: nNumberOfBytesToLockLow bytesHigh: nNumberOfBytesToLockHigh overlapped: lpOverlapped [ +SoilWindowsFileLock class >> lockFileEx: hFile flags: dwFlags bytesLow: nNumberOfBytesToLockLow bytesHigh: nNumberOfBytesToLockHigh overlapped: lpOverlapped [ ^ self ffiCall: #(bool LockFileEx( @@ -147,12 +147,12 @@ WindowsFileLock class >> lockFileEx: hFile flags: dwFlags bytesLow: nNumberOfByt uint 0, uint nNumberOfBytesToLockLow, uint nNumberOfBytesToLockHigh, - WinOverlapped *lpOverlapped)) + SoilWinOverlappedStruct *lpOverlapped)) module: #kernel32 ] { #category : 'accessing locking' } -WindowsFileLock class >> lockOrError: fileHandle from: start length: length exclusive: exclusive [ +SoilWindowsFileLock class >> lockOrError: fileHandle from: start length: length exclusive: exclusive [ "Lock file region, raising an error with detailed information on failure" | success errorCode errorDesc lockType | @@ -170,7 +170,7 @@ WindowsFileLock class >> lockOrError: fileHandle from: start length: length excl ] { #category : 'accessing locking' } -WindowsFileLock class >> unlock: fileHandle from: start length: length [ +SoilWindowsFileLock class >> unlock: fileHandle from: start length: length [ "Unlock file region, returning true on success, false on failure. For detailed error information, use unlockOrError:from:length:" @@ -179,7 +179,7 @@ WindowsFileLock class >> unlock: fileHandle from: start length: length [ "fileHandle is already a Windows HANDLE (ExternalAddress)" "Create and initialize OVERLAPPED structure" - overlapped := WinOverlapped new. + overlapped := SoilWinOverlappedStruct new. overlapped offset: (start bitAnd: 16rFFFFFFFF). overlapped offsetHigh: (start bitShift: -32). overlapped hEvent: ExternalAddress null. @@ -204,7 +204,7 @@ WindowsFileLock class >> unlock: fileHandle from: start length: length [ ] { #category : 'private - ffi' } -WindowsFileLock class >> unlockFileEx: hFile bytesLow: nNumberOfBytesToUnlockLow bytesHigh: nNumberOfBytesToUnlockHigh overlapped: lpOverlapped [ +SoilWindowsFileLock class >> unlockFileEx: hFile bytesLow: nNumberOfBytesToUnlockLow bytesHigh: nNumberOfBytesToUnlockHigh overlapped: lpOverlapped [ ^ self ffiCall: #(bool UnlockFileEx( @@ -212,12 +212,12 @@ WindowsFileLock class >> unlockFileEx: hFile bytesLow: nNumberOfBytesToUnlockLow uint 0, uint nNumberOfBytesToUnlockLow, uint nNumberOfBytesToUnlockHigh, - WinOverlapped *lpOverlapped)) + SoilWinOverlappedStruct *lpOverlapped)) module: #kernel32 ] { #category : 'accessing locking' } -WindowsFileLock class >> unlockOrError: fileHandle from: start length: length [ +SoilWindowsFileLock class >> unlockOrError: fileHandle from: start length: length [ "Unlock file region, raising an error with detailed information on failure" | success errorCode errorDesc | diff --git a/src/Soil-File/WinPlatform.extension.st b/src/Soil-File/WinPlatform.extension.st index d8d6acbb..a2fc4ac8 100644 --- a/src/Soil-File/WinPlatform.extension.st +++ b/src/Soil-File/WinPlatform.extension.st @@ -2,5 +2,5 @@ Extension { #name : 'WinPlatform' } { #category : '*Soil-File' } WinPlatform >> flockClass [ - ^ WindowsFileLock + ^ SoilWindowsFileLock ] From 3e5d8212b5678da8c06f0f8ffa984b4ba2ff015b Mon Sep 17 00:00:00 2001 From: Norbert Hartl Date: Mon, 17 Nov 2025 10:18:12 +0100 Subject: [PATCH 3/7] add windows build to github actions --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 200c86dc..2a9dba54 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,6 +18,7 @@ jobs: strategy: fail-fast: false matrix: + os: [ ubuntu-latest, windows-latest ] pharoversion: [ Pharo64-alpha, Pharo64-13, Pharo64-12, Pharo64-11 ] name: ${{ matrix.pharoversion }} runs-on: ubuntu-latest From 44a644ca63a0c3ce7cb5112565533cf8de59dc13 Mon Sep 17 00:00:00 2001 From: Norbert Hartl Date: Mon, 17 Nov 2025 10:20:40 +0100 Subject: [PATCH 4/7] added runs-on properly --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2a9dba54..746ba02d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: os: [ ubuntu-latest, windows-latest ] pharoversion: [ Pharo64-alpha, Pharo64-13, Pharo64-12, Pharo64-11 ] name: ${{ matrix.pharoversion }} - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - uses: hpi-swa/setup-smalltalkCI@v1 From 066b7a09675848f55d0b9a069233c00f329568a2 Mon Sep 17 00:00:00 2001 From: Norbert Hartl Date: Mon, 17 Nov 2025 10:36:18 +0100 Subject: [PATCH 5/7] Rename the other file lock classes to be consistent --- src/Soil-Core-Tests/SoilTest.class.st | 2 +- src/Soil-File/BinaryFileStream.extension.st | 38 ++++++------ src/Soil-File/MacOSPlatform.extension.st | 2 +- ...ck.class.st => SoilMacOSFileLock.class.st} | 58 +++++++++---------- ...ock.class.st => SoilUnixFileLock.class.st} | 56 +++++++++--------- .../SoilWinOverlappedStruct.class.st | 33 +++++------ src/Soil-File/SoilWindowsFileLock.class.st | 35 ++++++----- src/Soil-File/UnixPlatform.extension.st | 2 +- src/Soil-File/WinPlatform.extension.st | 4 +- 9 files changed, 114 insertions(+), 116 deletions(-) rename src/Soil-File/{MacOSFileLock.class.st => SoilMacOSFileLock.class.st} (76%) rename src/Soil-File/{UnixFileLock.class.st => SoilUnixFileLock.class.st} (76%) diff --git a/src/Soil-Core-Tests/SoilTest.class.st b/src/Soil-Core-Tests/SoilTest.class.st index 01b8d7ad..27e9d9ab 100644 --- a/src/Soil-Core-Tests/SoilTest.class.st +++ b/src/Soil-Core-Tests/SoilTest.class.st @@ -10,7 +10,7 @@ Class { { #category : #accessing } SoilTest class >> classNamesNotUnderTest [ "we for now ignore flock as this is platform specific" - ^ #(#MacOSFileLock #UnixFileLock) + ^ #(#SoilMacOSFileLock #SoilUnixFileLock) ] { #category : #accessing } diff --git a/src/Soil-File/BinaryFileStream.extension.st b/src/Soil-File/BinaryFileStream.extension.st index d7dc8280..f54aa0c2 100644 --- a/src/Soil-File/BinaryFileStream.extension.st +++ b/src/Soil-File/BinaryFileStream.extension.st @@ -1,12 +1,12 @@ -Extension { #name : 'BinaryFileStream' } +Extension { #name : #BinaryFileStream } -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } BinaryFileStream >> fileHandle [ ^ handle pointerAt: 9 ] -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } BinaryFileStream >> fileno [ | fd | @@ -17,7 +17,7 @@ BinaryFileStream >> fileno [ ] -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } BinaryFileStream >> fileno: stream [ ^ self @@ -25,12 +25,12 @@ BinaryFileStream >> fileno: stream [ module: LibC ] -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } BinaryFileStream >> flockClass [ ^ OSPlatform current flockClass ] -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } BinaryFileStream >> fsync [ "Flush all buffered data to disk. Platform-independent implementation." @@ -39,14 +39,14 @@ BinaryFileStream >> fsync [ ifFalse: [ self fsyncUnix ] ] -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } BinaryFileStream >> fsync: fd [ ^ self ffiCall: #(int fsync(int fd)) module: LibC ] -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } BinaryFileStream >> fsyncUnix [ "Unix/Linux/MacOS implementation of fsync using POSIX API" @@ -59,7 +59,7 @@ BinaryFileStream >> fsyncUnix [ , err printString ] ] -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } BinaryFileStream >> fsyncWindows [ "Windows implementation of fsync using FlushFileBuffers API" @@ -72,7 +72,7 @@ BinaryFileStream >> fsyncWindows [ , errorCode printString ] ] -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } BinaryFileStream >> lockAt: position length: length [ ^ self flockClass lock: self fileHandle @@ -80,7 +80,7 @@ BinaryFileStream >> lockAt: position length: length [ length: length ] -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } BinaryFileStream >> lockEntireFile [ ^ self flockClass lock: self fileHandle @@ -88,7 +88,7 @@ BinaryFileStream >> lockEntireFile [ length: self size ] -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } BinaryFileStream >> lockEntireFileAndBeyond [ "add a lock for appending to a file. We use the whole file starting at 0 to have reliable semantics. Using the end of the file might be more @@ -100,7 +100,7 @@ BinaryFileStream >> lockEntireFileAndBeyond [ length: 0 ] -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } BinaryFileStream >> lockForAppendStartingAt: offset [ "add append lock by taking an offset. Having an append lock and matching unlock is not possible from within this stream so the offset needs to be @@ -111,20 +111,20 @@ BinaryFileStream >> lockForAppendStartingAt: offset [ length: 0 ] -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } BinaryFileStream >> sync [ ^ self sync: (self fileno: self fileHandle). ] -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } BinaryFileStream >> sync: fd [ ^ self ffiCall: #(int fsync(int fd)) module: LibC ] -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } BinaryFileStream >> unlockAt: position length: length [ ^ self flockClass @@ -133,7 +133,7 @@ BinaryFileStream >> unlockAt: position length: length [ length: length ] -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } BinaryFileStream >> unlockEntireFile [ ^ self flockClass unlock: self fileHandle @@ -141,7 +141,7 @@ BinaryFileStream >> unlockEntireFile [ length: self size ] -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } BinaryFileStream >> unlockEntireFileAndBeyond [ "release a lock for appending to a file. We use the whole file starting at 0 to have reliable semantics. The end of the file might have changed @@ -153,7 +153,7 @@ BinaryFileStream >> unlockEntireFileAndBeyond [ length: 0 ] -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } BinaryFileStream >> unlockForAppendStartingAt: offset [ "release append lock by taking an offset. Having an append lock and matching unlock is not possible from within this stream so the offset needs to be diff --git a/src/Soil-File/MacOSPlatform.extension.st b/src/Soil-File/MacOSPlatform.extension.st index 8b9487cf..ff4bd022 100644 --- a/src/Soil-File/MacOSPlatform.extension.st +++ b/src/Soil-File/MacOSPlatform.extension.st @@ -2,5 +2,5 @@ Extension { #name : #MacOSPlatform } { #category : #'*Soil-File' } MacOSPlatform >> flockClass [ - ^ MacOSFileLock + ^ SoilMacOSFileLock ] diff --git a/src/Soil-File/MacOSFileLock.class.st b/src/Soil-File/SoilMacOSFileLock.class.st similarity index 76% rename from src/Soil-File/MacOSFileLock.class.st rename to src/Soil-File/SoilMacOSFileLock.class.st index 4c41c1f0..6a5b115a 100644 --- a/src/Soil-File/MacOSFileLock.class.st +++ b/src/Soil-File/SoilMacOSFileLock.class.st @@ -1,5 +1,5 @@ Class { - #name : #MacOSFileLock, + #name : #SoilMacOSFileLock, #superclass : #FFIStructure, #classVars : [ 'F_GETFL', @@ -26,7 +26,7 @@ Class { } { #category : #testing } -MacOSFileLock class >> canLock: fileHandle from: start to: length exclusive: exclusive [ +SoilMacOSFileLock class >> canLock: fileHandle from: start to: length exclusive: exclusive [ | lock result | lock := self newLockExclusive: exclusive start: start length: length. @@ -40,15 +40,15 @@ MacOSFileLock class >> canLock: fileHandle from: start to: length exclusive: exc ] { #category : #private } -MacOSFileLock class >> fcntl: fd command: cmd struct: struct [ +SoilMacOSFileLock class >> fcntl: fd command: cmd struct: struct [ ^ self - ffiCall: #(int fcntl(int fd, int cmd, MacOSFileLock *struct)) + ffiCall: #(int fcntl(int fd, int cmd, #SoilMacOSFileLock *struct)) library: LibC fixedArgumentCount: 2 ] { #category : #private } -MacOSFileLock class >> fcntl: fd command: cmd value: value [ +SoilMacOSFileLock class >> fcntl: fd command: cmd value: value [ ^ self ffiCall: #(int fcntl(int fd, int cmd, ulong value)) @@ -57,7 +57,7 @@ MacOSFileLock class >> fcntl: fd command: cmd value: value [ ] { #category : #'field definition' } -MacOSFileLock class >> fieldsDesc [ +SoilMacOSFileLock class >> fieldsDesc [ "self rebuildFieldAccessors" ^ #( @@ -70,7 +70,7 @@ MacOSFileLock class >> fieldsDesc [ ] { #category : #private } -MacOSFileLock class >> fileno: stream [ +SoilMacOSFileLock class >> fileno: stream [ ^ self ffiCall: #(int fileno("FILE *"void *stream)) @@ -78,7 +78,7 @@ MacOSFileLock class >> fileno: stream [ ] { #category : #private } -MacOSFileLock class >> flock: fd operation: operation [ +SoilMacOSFileLock class >> flock: fd operation: operation [ ^ self ffiCall: #(int flock(int fd, int operation)) @@ -86,13 +86,13 @@ MacOSFileLock class >> flock: fd operation: operation [ ] { #category : #private } -MacOSFileLock class >> getpid [ +SoilMacOSFileLock class >> getpid [ ^ self ffiCall: #(__pid_t getpid()) module: LibC ] { #category : #'class initialization' } -MacOSFileLock class >> initialize [ +SoilMacOSFileLock class >> initialize [ __off64_t := FFIUInt64. __pid_t := FFIUInt32. @@ -116,7 +116,7 @@ MacOSFileLock class >> initialize [ ] { #category : #'accessing locking' } -MacOSFileLock class >> lock: fileHandle from: start length: length [ +SoilMacOSFileLock class >> lock: fileHandle from: start length: length [ ^ self lock: fileHandle @@ -126,7 +126,7 @@ MacOSFileLock class >> lock: fileHandle from: start length: length [ ] { #category : #'accessing locking' } -MacOSFileLock class >> lock: fileHandle from: start length: length exclusive: exclusive [ +SoilMacOSFileLock class >> lock: fileHandle from: start length: length exclusive: exclusive [ | lock result | lock := self newLockExclusive: exclusive start: start length: length. @@ -139,7 +139,7 @@ MacOSFileLock class >> lock: fileHandle from: start length: length exclusive: ex ] { #category : #'accessing locking' } -MacOSFileLock class >> lock: fileHandle from: start to: length exclusive: exclusive [ +SoilMacOSFileLock class >> lock: fileHandle from: start to: length exclusive: exclusive [ | lock result | lock := self newLockExclusive: exclusive start: start length: length. @@ -152,7 +152,7 @@ MacOSFileLock class >> lock: fileHandle from: start to: length exclusive: exclus ] { #category : #'instance creation' } -MacOSFileLock class >> newLockExclusive: exclusive start: start length: length [ +SoilMacOSFileLock class >> newLockExclusive: exclusive start: start length: length [ ^ self newType: (exclusive ifTrue: [ F_WRLCK ] ifFalse: [ F_RDLCK ]) @@ -161,7 +161,7 @@ MacOSFileLock class >> newLockExclusive: exclusive start: start length: length [ ] { #category : #'instance creation' } -MacOSFileLock class >> newType: type start: start length: length [ +SoilMacOSFileLock class >> newType: type start: start length: length [ ^ self new l_type: type; @@ -173,7 +173,7 @@ MacOSFileLock class >> newType: type start: start length: length [ ] { #category : #'instance creation' } -MacOSFileLock class >> newUnlockStart: start length: length [ +SoilMacOSFileLock class >> newUnlockStart: start length: length [ ^ self newType: F_UNLCK @@ -182,7 +182,7 @@ MacOSFileLock class >> newUnlockStart: start length: length [ ] { #category : #accessing } -MacOSFileLock class >> setNonBlock: fileHandle [ +SoilMacOSFileLock class >> setNonBlock: fileHandle [ | fd flags | fd := self fileno: fileHandle. @@ -192,7 +192,7 @@ MacOSFileLock class >> setNonBlock: fileHandle [ ] { #category : #'accessing locking' } -MacOSFileLock class >> unlock: fileHandle from: start length: length [ +SoilMacOSFileLock class >> unlock: fileHandle from: start length: length [ | lock result | lock := self newUnlockStart: start length: length. @@ -205,67 +205,67 @@ MacOSFileLock class >> unlock: fileHandle from: start length: length [ ] { #category : #'accessing - structure variables' } -MacOSFileLock >> l_len [ +SoilMacOSFileLock >> l_len [ "This method was automatically generated" ^handle unsignedLongLongAt: OFFSET_L_LEN ] { #category : #'accessing - structure variables' } -MacOSFileLock >> l_len: anObject [ +SoilMacOSFileLock >> l_len: anObject [ "This method was automatically generated" handle unsignedLongLongAt: OFFSET_L_LEN put: anObject ] { #category : #'accessing - structure variables' } -MacOSFileLock >> l_pid [ +SoilMacOSFileLock >> l_pid [ "This method was automatically generated" ^handle unsignedLongAt: OFFSET_L_PID ] { #category : #'accessing - structure variables' } -MacOSFileLock >> l_pid: anObject [ +SoilMacOSFileLock >> l_pid: anObject [ "This method was automatically generated" handle unsignedLongAt: OFFSET_L_PID put: anObject ] { #category : #'accessing - structure variables' } -MacOSFileLock >> l_start [ +SoilMacOSFileLock >> l_start [ "This method was automatically generated" ^handle unsignedLongLongAt: OFFSET_L_START ] { #category : #'accessing - structure variables' } -MacOSFileLock >> l_start: anObject [ +SoilMacOSFileLock >> l_start: anObject [ "This method was automatically generated" handle unsignedLongLongAt: OFFSET_L_START put: anObject ] { #category : #'accessing - structure variables' } -MacOSFileLock >> l_type [ +SoilMacOSFileLock >> l_type [ "This method was automatically generated" ^handle signedLongAt: OFFSET_L_TYPE ] { #category : #'accessing - structure variables' } -MacOSFileLock >> l_type: anObject [ +SoilMacOSFileLock >> l_type: anObject [ "This method was automatically generated" handle signedLongAt: OFFSET_L_TYPE put: anObject ] { #category : #'accessing - structure variables' } -MacOSFileLock >> l_whence [ +SoilMacOSFileLock >> l_whence [ "This method was automatically generated" ^handle signedLongAt: OFFSET_L_WHENCE ] { #category : #'accessing - structure variables' } -MacOSFileLock >> l_whence: anObject [ +SoilMacOSFileLock >> l_whence: anObject [ "This method was automatically generated" handle signedLongAt: OFFSET_L_WHENCE put: anObject ] { #category : #printing } -MacOSFileLock >> printOn: aStream [ +SoilMacOSFileLock >> printOn: aStream [ "Append to the argument, aStream, the names and values of all the record's variables." aStream nextPutAll: self class name; nextPutAll: ' ( '; cr. diff --git a/src/Soil-File/UnixFileLock.class.st b/src/Soil-File/SoilUnixFileLock.class.st similarity index 76% rename from src/Soil-File/UnixFileLock.class.st rename to src/Soil-File/SoilUnixFileLock.class.st index 3a068b99..59203d40 100644 --- a/src/Soil-File/UnixFileLock.class.st +++ b/src/Soil-File/SoilUnixFileLock.class.st @@ -1,5 +1,5 @@ Class { - #name : #UnixFileLock, + #name : #SoilUnixFileLock, #superclass : #FFIStructure, #classVars : [ 'F_GETFL', @@ -26,7 +26,7 @@ Class { } { #category : #testing } -UnixFileLock class >> canLock: fileHandle from: start length: length exclusive: exclusive [ +SoilUnixFileLock class >> canLock: fileHandle from: start length: length exclusive: exclusive [ | lock result | lock := self newLockExclusive: exclusive start: start length: length. @@ -40,16 +40,16 @@ UnixFileLock class >> canLock: fileHandle from: start length: length exclusive: ] { #category : #private } -UnixFileLock class >> fcntl: fd command: cmd struct: struct [ +SoilUnixFileLock class >> fcntl: fd command: cmd struct: struct [ ^ self - ffiCall: #(int fcntl(int fd, int cmd, UnixFileLock *struct)) + ffiCall: #(int fcntl(int fd, int cmd, #SoilUnixFileLock *struct)) library: LibC fixedArgumentCount: 2 ] { #category : #private } -UnixFileLock class >> fcntl: fd command: cmd value: value [ +SoilUnixFileLock class >> fcntl: fd command: cmd value: value [ ^ self ffiCall: #(int fcntl(int fd, int cmd, int value)) @@ -58,7 +58,7 @@ UnixFileLock class >> fcntl: fd command: cmd value: value [ ] { #category : #'field definition' } -UnixFileLock class >> fieldsDesc [ +SoilUnixFileLock class >> fieldsDesc [ "self rebuildFieldAccessors" ^ #( @@ -71,7 +71,7 @@ UnixFileLock class >> fieldsDesc [ ] { #category : #private } -UnixFileLock class >> fileno: stream [ +SoilUnixFileLock class >> fileno: stream [ ^ self ffiCall: #(int fileno("FILE *"void *stream)) @@ -79,7 +79,7 @@ UnixFileLock class >> fileno: stream [ ] { #category : #private } -UnixFileLock class >> flock: fd operation: operation [ +SoilUnixFileLock class >> flock: fd operation: operation [ ^ self ffiCall: #(int flock(int fd, int operation)) @@ -87,13 +87,13 @@ UnixFileLock class >> flock: fd operation: operation [ ] { #category : #private } -UnixFileLock class >> getpid [ +SoilUnixFileLock class >> getpid [ ^ self ffiCall: #(__pid_t getpid()) module: LibC ] { #category : #'class initialization' } -UnixFileLock class >> initialize [ +SoilUnixFileLock class >> initialize [ __off64_t := FFIUInt64. __pid_t := FFIUInt32. @@ -117,7 +117,7 @@ UnixFileLock class >> initialize [ ] { #category : #'accessing locking' } -UnixFileLock class >> lock: fileHandle from: start length: length [ +SoilUnixFileLock class >> lock: fileHandle from: start length: length [ ^ self lock: fileHandle @@ -127,7 +127,7 @@ UnixFileLock class >> lock: fileHandle from: start length: length [ ] { #category : #'accessing locking' } -UnixFileLock class >> lock: fileHandle from: start length: length exclusive: exclusive [ +SoilUnixFileLock class >> lock: fileHandle from: start length: length exclusive: exclusive [ | lock result | lock := self newLockExclusive: exclusive start: start length: length. @@ -140,7 +140,7 @@ UnixFileLock class >> lock: fileHandle from: start length: length exclusive: exc ] { #category : #'instance creation' } -UnixFileLock class >> newLockExclusive: exclusive start: start length: length [ +SoilUnixFileLock class >> newLockExclusive: exclusive start: start length: length [ ^ self newType: (exclusive ifTrue: [ F_WRLCK ] ifFalse: [ F_RDLCK ]) @@ -149,7 +149,7 @@ UnixFileLock class >> newLockExclusive: exclusive start: start length: length [ ] { #category : #'instance creation' } -UnixFileLock class >> newType: type start: start length: length [ +SoilUnixFileLock class >> newType: type start: start length: length [ ^ self new l_type: type; @@ -161,7 +161,7 @@ UnixFileLock class >> newType: type start: start length: length [ ] { #category : #'instance creation' } -UnixFileLock class >> newUnlockStart: start length: length [ +SoilUnixFileLock class >> newUnlockStart: start length: length [ ^ self newType: F_UNLCK @@ -170,7 +170,7 @@ UnixFileLock class >> newUnlockStart: start length: length [ ] { #category : #accessing } -UnixFileLock class >> setNonBlock: fileHandle [ +SoilUnixFileLock class >> setNonBlock: fileHandle [ | fd flags | fd := self fileno: fileHandle. @@ -180,7 +180,7 @@ UnixFileLock class >> setNonBlock: fileHandle [ ] { #category : #'accessing locking' } -UnixFileLock class >> unlock: fileHandle from: start length: length [ +SoilUnixFileLock class >> unlock: fileHandle from: start length: length [ | lock result | lock := self newUnlockStart: start length: length. @@ -193,67 +193,67 @@ UnixFileLock class >> unlock: fileHandle from: start length: length [ ] { #category : #'accessing structure variables' } -UnixFileLock >> l_len [ +SoilUnixFileLock >> l_len [ "This method was automatically generated" ^handle unsignedLongLongAt: OFFSET_L_LEN ] { #category : #'accessing structure variables' } -UnixFileLock >> l_len: anObject [ +SoilUnixFileLock >> l_len: anObject [ "This method was automatically generated" handle unsignedLongLongAt: OFFSET_L_LEN put: anObject ] { #category : #'accessing structure variables' } -UnixFileLock >> l_pid [ +SoilUnixFileLock >> l_pid [ "This method was automatically generated" ^handle unsignedLongAt: OFFSET_L_PID ] { #category : #'accessing structure variables' } -UnixFileLock >> l_pid: anObject [ +SoilUnixFileLock >> l_pid: anObject [ "This method was automatically generated" handle unsignedLongAt: OFFSET_L_PID put: anObject ] { #category : #'accessing structure variables' } -UnixFileLock >> l_start [ +SoilUnixFileLock >> l_start [ "This method was automatically generated" ^handle unsignedLongLongAt: OFFSET_L_START ] { #category : #'accessing structure variables' } -UnixFileLock >> l_start: anObject [ +SoilUnixFileLock >> l_start: anObject [ "This method was automatically generated" handle unsignedLongLongAt: OFFSET_L_START put: anObject ] { #category : #'accessing structure variables' } -UnixFileLock >> l_type [ +SoilUnixFileLock >> l_type [ "This method was automatically generated" ^handle signedShortAt: OFFSET_L_TYPE ] { #category : #'accessing structure variables' } -UnixFileLock >> l_type: anObject [ +SoilUnixFileLock >> l_type: anObject [ "This method was automatically generated" handle signedShortAt: OFFSET_L_TYPE put: anObject ] { #category : #'accessing structure variables' } -UnixFileLock >> l_whence [ +SoilUnixFileLock >> l_whence [ "This method was automatically generated" ^handle signedShortAt: OFFSET_L_WHENCE ] { #category : #'accessing structure variables' } -UnixFileLock >> l_whence: anObject [ +SoilUnixFileLock >> l_whence: anObject [ "This method was automatically generated" handle signedShortAt: OFFSET_L_WHENCE put: anObject ] { #category : #printing } -UnixFileLock >> printOn: aStream [ +SoilUnixFileLock >> printOn: aStream [ "Append to the argument, aStream, the names and values of all the record's variables." aStream nextPutAll: self class name; nextPutAll: ' ( '; cr. diff --git a/src/Soil-File/SoilWinOverlappedStruct.class.st b/src/Soil-File/SoilWinOverlappedStruct.class.st index 24bbe267..087da165 100644 --- a/src/Soil-File/SoilWinOverlappedStruct.class.st +++ b/src/Soil-File/SoilWinOverlappedStruct.class.st @@ -1,6 +1,6 @@ Class { - #name : 'SoilWinOverlappedStruct', - #superclass : 'FFIStructure', + #name : #SoilWinOverlappedStruct, + #superclass : #FFIStructure, #classVars : [ 'OFFSET_HEVENT', 'OFFSET_INTERNAL', @@ -8,11 +8,10 @@ Class { 'OFFSET_OFFSET', 'OFFSET_OFFSETHIGH' ], - #category : 'Soil-File', - #package : 'Soil-File' + #category : #'Soil-File' } -{ #category : 'field definition' } +{ #category : #'field definition' } SoilWinOverlappedStruct class >> fieldsDesc [ "self rebuildFieldAccessors" @@ -25,72 +24,72 @@ SoilWinOverlappedStruct class >> fieldsDesc [ ) ] -{ #category : 'accessing - structure variables' } +{ #category : #'accessing - structure variables' } SoilWinOverlappedStruct >> hEvent [ "This method was automatically generated" ^ExternalData fromHandle: (handle pointerAt: OFFSET_HEVENT) type: ExternalType void asPointerType ] -{ #category : 'accessing - structure variables' } +{ #category : #'accessing - structure variables' } SoilWinOverlappedStruct >> hEvent: anObject [ "This method was automatically generated" handle pointerAt: OFFSET_HEVENT put: anObject getHandle. ] -{ #category : 'initialization' } +{ #category : #initialization } SoilWinOverlappedStruct >> initialize [ super initialize ] -{ #category : 'accessing - structure variables' } +{ #category : #'accessing - structure variables' } SoilWinOverlappedStruct >> internal [ "This method was automatically generated" ^handle unsignedLongLongAt: OFFSET_INTERNAL ] -{ #category : 'accessing - structure variables' } +{ #category : #'accessing - structure variables' } SoilWinOverlappedStruct >> internal: anObject [ "This method was automatically generated" handle unsignedLongLongAt: OFFSET_INTERNAL put: anObject ] -{ #category : 'accessing - structure variables' } +{ #category : #'accessing - structure variables' } SoilWinOverlappedStruct >> internalHigh [ "This method was automatically generated" ^handle unsignedLongLongAt: OFFSET_INTERNALHIGH ] -{ #category : 'accessing - structure variables' } +{ #category : #'accessing - structure variables' } SoilWinOverlappedStruct >> internalHigh: anObject [ "This method was automatically generated" handle unsignedLongLongAt: OFFSET_INTERNALHIGH put: anObject ] -{ #category : 'accessing - structure variables' } +{ #category : #'accessing - structure variables' } SoilWinOverlappedStruct >> offset [ "This method was automatically generated" ^handle unsignedLongAt: OFFSET_OFFSET ] -{ #category : 'accessing - structure variables' } +{ #category : #'accessing - structure variables' } SoilWinOverlappedStruct >> offset: anObject [ "This method was automatically generated" handle unsignedLongAt: OFFSET_OFFSET put: anObject ] -{ #category : 'accessing - structure variables' } +{ #category : #'accessing - structure variables' } SoilWinOverlappedStruct >> offsetHigh [ "This method was automatically generated" ^handle unsignedLongAt: OFFSET_OFFSETHIGH ] -{ #category : 'accessing - structure variables' } +{ #category : #'accessing - structure variables' } SoilWinOverlappedStruct >> offsetHigh: anObject [ "This method was automatically generated" handle unsignedLongAt: OFFSET_OFFSETHIGH put: anObject ] -{ #category : 'printing' } +{ #category : #printing } SoilWinOverlappedStruct >> printOn: aStream [ "Append to the argument, aStream, the names and values of all the record's variables." diff --git a/src/Soil-File/SoilWindowsFileLock.class.st b/src/Soil-File/SoilWindowsFileLock.class.st index 943baac8..2610e23d 100644 --- a/src/Soil-File/SoilWindowsFileLock.class.st +++ b/src/Soil-File/SoilWindowsFileLock.class.st @@ -1,6 +1,6 @@ Class { - #name : 'SoilWindowsFileLock', - #superclass : 'Object', + #name : #SoilWindowsFileLock, + #superclass : #Object, #classVars : [ 'ERROR_ACCESS_DENIED', 'ERROR_INVALID_HANDLE', @@ -11,11 +11,10 @@ Class { 'LOCKFILE_FAIL_IMMEDIATELY', 'LOCKFILE_SHARED_LOCK' ], - #category : 'Soil-File', - #package : 'Soil-File' + #category : #'Soil-File' } -{ #category : 'testing' } +{ #category : #testing } SoilWindowsFileLock class >> canLock: fileHandle from: start length: length exclusive: exclusive [ "Windows doesn't have a direct 'test lock' API like F_GETLK. We attempt to lock and immediately unlock if successful." @@ -27,7 +26,7 @@ SoilWindowsFileLock class >> canLock: fileHandle from: start length: length excl ^ success ] -{ #category : 'private - error handling' } +{ #category : #'private - error handling' } SoilWindowsFileLock class >> errorDescription: errorCode [ "Return a human-readable description for Windows error codes" @@ -40,21 +39,21 @@ SoilWindowsFileLock class >> errorDescription: errorCode [ ^ 'Unknown error (code: ', errorCode printString, ')' ] -{ #category : 'private - ffi' } +{ #category : #'private - ffi' } SoilWindowsFileLock class >> flushFileBuffers: hFile [ "Flush all file buffers to disk using Windows API" ^ self ffiCall: #(bool FlushFileBuffers(void* hFile)) module: #kernel32 ] -{ #category : 'private - ffi' } +{ #category : #'private - ffi' } SoilWindowsFileLock class >> getCurrentProcessId [ "Get the current process ID using Windows API" ^ self ffiCall: #(uint GetCurrentProcessId()) module: #kernel32 ] -{ #category : 'private - ffi' } +{ #category : #'private - ffi' } SoilWindowsFileLock class >> getFileHandle: stream [ "Extract Windows HANDLE from a BinaryFileStream. For buffered streams (ZnBufferedWriteStream), get the wrapped file stream first." @@ -64,14 +63,14 @@ SoilWindowsFileLock class >> getFileHandle: stream [ ^ fileStream fileHandle ] -{ #category : 'private - ffi' } +{ #category : #'private - ffi' } SoilWindowsFileLock class >> getLastError [ "Get Windows error code" ^ self ffiCall: #(uint GetLastError()) module: #kernel32 ] -{ #category : 'class initialization' } +{ #category : #'class initialization' } SoilWindowsFileLock class >> initialize [ "Windows API constants" @@ -87,7 +86,7 @@ SoilWindowsFileLock class >> initialize [ ERROR_IO_PENDING := 997 ] -{ #category : 'accessing locking' } +{ #category : #'accessing locking' } SoilWindowsFileLock class >> lock: fileHandle from: start length: length [ ^ self @@ -97,7 +96,7 @@ SoilWindowsFileLock class >> lock: fileHandle from: start length: length [ exclusive: true ] -{ #category : 'accessing locking' } +{ #category : #'accessing locking' } SoilWindowsFileLock class >> lock: fileHandle from: start length: length exclusive: exclusive [ "Lock file region, returning true on success, false on failure. For detailed error information, use lockOrError:from:length:exclusive:" @@ -137,7 +136,7 @@ SoilWindowsFileLock class >> lock: fileHandle from: start length: length exclusi ^ result ~= 0 ] -{ #category : 'private - ffi' } +{ #category : #'private - ffi' } SoilWindowsFileLock class >> lockFileEx: hFile flags: dwFlags bytesLow: nNumberOfBytesToLockLow bytesHigh: nNumberOfBytesToLockHigh overlapped: lpOverlapped [ ^ self @@ -151,7 +150,7 @@ SoilWindowsFileLock class >> lockFileEx: hFile flags: dwFlags bytesLow: nNumberO module: #kernel32 ] -{ #category : 'accessing locking' } +{ #category : #'accessing locking' } SoilWindowsFileLock class >> lockOrError: fileHandle from: start length: length exclusive: exclusive [ "Lock file region, raising an error with detailed information on failure" @@ -169,7 +168,7 @@ SoilWindowsFileLock class >> lockOrError: fileHandle from: start length: length ^ success ] -{ #category : 'accessing locking' } +{ #category : #'accessing locking' } SoilWindowsFileLock class >> unlock: fileHandle from: start length: length [ "Unlock file region, returning true on success, false on failure. For detailed error information, use unlockOrError:from:length:" @@ -203,7 +202,7 @@ SoilWindowsFileLock class >> unlock: fileHandle from: start length: length [ ^ result ~= 0 ] -{ #category : 'private - ffi' } +{ #category : #'private - ffi' } SoilWindowsFileLock class >> unlockFileEx: hFile bytesLow: nNumberOfBytesToUnlockLow bytesHigh: nNumberOfBytesToUnlockHigh overlapped: lpOverlapped [ ^ self @@ -216,7 +215,7 @@ SoilWindowsFileLock class >> unlockFileEx: hFile bytesLow: nNumberOfBytesToUnloc module: #kernel32 ] -{ #category : 'accessing locking' } +{ #category : #'accessing locking' } SoilWindowsFileLock class >> unlockOrError: fileHandle from: start length: length [ "Unlock file region, raising an error with detailed information on failure" diff --git a/src/Soil-File/UnixPlatform.extension.st b/src/Soil-File/UnixPlatform.extension.st index c4b53171..62b8fe9f 100644 --- a/src/Soil-File/UnixPlatform.extension.st +++ b/src/Soil-File/UnixPlatform.extension.st @@ -2,5 +2,5 @@ Extension { #name : #UnixPlatform } { #category : #'*Soil-File' } UnixPlatform >> flockClass [ - ^ UnixFileLock + ^ SoilUnixFileLock ] diff --git a/src/Soil-File/WinPlatform.extension.st b/src/Soil-File/WinPlatform.extension.st index a2fc4ac8..ab5a70b2 100644 --- a/src/Soil-File/WinPlatform.extension.st +++ b/src/Soil-File/WinPlatform.extension.st @@ -1,6 +1,6 @@ -Extension { #name : 'WinPlatform' } +Extension { #name : #WinPlatform } -{ #category : '*Soil-File' } +{ #category : #'*Soil-File' } WinPlatform >> flockClass [ ^ SoilWindowsFileLock ] From 5d8681b57fb5145c6671ef9a81cc124d469cfc8f Mon Sep 17 00:00:00 2001 From: Norbert Hartl Date: Mon, 17 Nov 2025 11:04:13 +0100 Subject: [PATCH 6/7] Moved sync code to OSPlatform subclass. This way we don't need to use isXXX testing methods and we can move more platform dependent code (ffi calls) to the corresponding platform class --- src/Soil-File/BinaryFileStream.extension.st | 41 +++------------------ src/Soil-File/MacOSXPlatform.extension.st | 23 ++++++++++++ src/Soil-File/UnixPlatform.extension.st | 22 +++++++++++ src/Soil-File/WinPlatform.extension.st | 10 +++++ 4 files changed, 60 insertions(+), 36 deletions(-) create mode 100644 src/Soil-File/MacOSXPlatform.extension.st diff --git a/src/Soil-File/BinaryFileStream.extension.st b/src/Soil-File/BinaryFileStream.extension.st index f54aa0c2..91e072ba 100644 --- a/src/Soil-File/BinaryFileStream.extension.st +++ b/src/Soil-File/BinaryFileStream.extension.st @@ -34,42 +34,11 @@ BinaryFileStream >> flockClass [ BinaryFileStream >> fsync [ "Flush all buffered data to disk. Platform-independent implementation." - OSPlatform current isWindows - ifTrue: [ self fsyncWindows ] - ifFalse: [ self fsyncUnix ] -] - -{ #category : #'*Soil-File' } -BinaryFileStream >> fsync: fd [ - ^ self - ffiCall: #(int fsync(int fd)) - module: LibC -] - -{ #category : #'*Soil-File' } -BinaryFileStream >> fsyncUnix [ - "Unix/Linux/MacOS implementation of fsync using POSIX API" - - | fd err | - fd := self fileno. - err := self fsync: fd. - err = 0 ifFalse: [ - Error signal: - 'fsync(' , fd printString , ') failed with error code: ' - , err printString ] -] - -{ #category : #'*Soil-File' } -BinaryFileStream >> fsyncWindows [ - "Windows implementation of fsync using FlushFileBuffers API" - - | result errorCode | - result := SoilWindowsFileLock flushFileBuffers: self fileHandle. - result ifFalse: [ - errorCode := SoilWindowsFileLock getLastError. - Error signal: - 'FlushFileBuffers failed with error code: ' - , errorCode printString ] + | errorCode | + errorCode := OSPlatform current syncFile: self fileHandle. + (errorCode == 0) ifFalse: [ + Error signal: 'syncing file failed with error code: ' , errorCode printString ] + ] { #category : #'*Soil-File' } diff --git a/src/Soil-File/MacOSXPlatform.extension.st b/src/Soil-File/MacOSXPlatform.extension.st new file mode 100644 index 00000000..a854123a --- /dev/null +++ b/src/Soil-File/MacOSXPlatform.extension.st @@ -0,0 +1,23 @@ +Extension { #name : #MacOSXPlatform } + +{ #category : #'*Soil-File' } +MacOSXPlatform >> fileno: stream [ + + ^ self + ffiCall: #(int fileno("FILE *"void *stream)) + module: LibC +] + +{ #category : #'*Soil-File' } +MacOSXPlatform >> fsync: fd [ + ^ self + ffiCall: #(int fsync(int fd)) + module: LibC +] + +{ #category : #'*Soil-File' } +MacOSXPlatform >> syncFile: fileHandle [ + "Unix/Linux/MacOS implementation of fsync using POSIX API" + + ^ self fsync: (self fileno: fileHandle) +] diff --git a/src/Soil-File/UnixPlatform.extension.st b/src/Soil-File/UnixPlatform.extension.st index 62b8fe9f..d9ee6a67 100644 --- a/src/Soil-File/UnixPlatform.extension.st +++ b/src/Soil-File/UnixPlatform.extension.st @@ -1,6 +1,28 @@ Extension { #name : #UnixPlatform } +{ #category : #'*Soil-File' } +UnixPlatform >> fileno: stream [ + + ^ self + ffiCall: #(int fileno("FILE *"void *stream)) + module: LibC +] + { #category : #'*Soil-File' } UnixPlatform >> flockClass [ ^ SoilUnixFileLock ] + +{ #category : #'*Soil-File' } +UnixPlatform >> fsync: fd [ + ^ self + ffiCall: #(int fsync(int fd)) + module: LibC +] + +{ #category : #'*Soil-File' } +UnixPlatform >> syncFile: fileHandle [ + "Unix/Linux/MacOS implementation of fsync using POSIX API" + + ^ self fsync: (self fileno: fileHandle) +] diff --git a/src/Soil-File/WinPlatform.extension.st b/src/Soil-File/WinPlatform.extension.st index ab5a70b2..a7cbe19b 100644 --- a/src/Soil-File/WinPlatform.extension.st +++ b/src/Soil-File/WinPlatform.extension.st @@ -4,3 +4,13 @@ Extension { #name : #WinPlatform } WinPlatform >> flockClass [ ^ SoilWindowsFileLock ] + +{ #category : #'*Soil-File' } +WinPlatform >> syncFile: fileHandle [ + "Windows implementation of fsync using FlushFileBuffers API" + + ^ (SoilWindowsFileLock flushFileBuffers: fileHandle) + ifTrue: [ 0 ] + ifFalse: [ + SoilWindowsFileLock getLastError ] +] From e5c934e6dd428dd50cf46762d4c20396909f6dd2 Mon Sep 17 00:00:00 2001 From: Norbert Hartl Date: Mon, 17 Nov 2025 13:45:40 +0100 Subject: [PATCH 7/7] add os to job name --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 746ba02d..a2f3a474 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,7 @@ jobs: matrix: os: [ ubuntu-latest, windows-latest ] pharoversion: [ Pharo64-alpha, Pharo64-13, Pharo64-12, Pharo64-11 ] - name: ${{ matrix.pharoversion }} + name: ${{ matrix.pharoversion }}-${{ matrix.os }} runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2