Skip to content

Commit eb89bde

Browse files
authored
[release/6.0] SafeFileHandle.Unix: don't DeleteOnClose when lock is not successful. (#60112)
* SafeFileHandle.Unix: don't DeleteOnClose when lock is not succesful. In #55153 DeleteOnClose handling moved from FileStream to SafeFileHandle. This unintentionally caused DeleteOnClose to be applied even when FileShare locking fails. As on Windows, DeleteOnClose should not take effect when sharing prevents the file from being opened. This also swaps the order of unlink and LOCK_UN in Dispose as it was prior to #55153. Either order should be fine. * test: add ConditionalFact IsFileLockingEnabled
1 parent 2cc1b09 commit eb89bde

File tree

2 files changed

+27
-12
lines changed

2 files changed

+27
-12
lines changed

src/libraries/System.IO.FileSystem/tests/FileStream/SafeFileHandle.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,18 @@ public async Task ThrowWhenHandlePositionIsChanged_async()
7878
await ThrowWhenHandlePositionIsChanged(useAsync: true);
7979
}
8080

81+
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsFileLockingEnabled))]
82+
public void DeleteOnClose_FailedShareDoesNotDeleteFile()
83+
{
84+
string fileName = GetTestFilePath();
85+
86+
using SafeFileHandle handle = File.OpenHandle(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
87+
88+
Assert.Throws<IOException>(() => File.OpenHandle(fileName, FileMode.Open, FileAccess.Write, FileShare.None, FileOptions.DeleteOnClose));
89+
90+
Assert.True(File.Exists(fileName));
91+
}
92+
8193
private async Task ThrowWhenHandlePositionIsChanged(bool useAsync)
8294
{
8395
string fileName = GetTestFilePath();

src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -121,17 +121,6 @@ private static bool DirectoryExists(string fullPath)
121121

122122
protected override bool ReleaseHandle()
123123
{
124-
// When the SafeFileHandle was opened, we likely issued an flock on the created descriptor in order to add
125-
// an advisory lock. This lock should be removed via closing the file descriptor, but close can be
126-
// interrupted, and we don't retry closes. As such, we could end up leaving the file locked,
127-
// which could prevent subsequent usage of the file until this process dies. To avoid that, we proactively
128-
// try to release the lock before we close the handle.
129-
if (_isLocked)
130-
{
131-
Interop.Sys.FLock(handle, Interop.Sys.LockOperations.LOCK_UN); // ignore any errors
132-
_isLocked = false;
133-
}
134-
135124
// If DeleteOnClose was requested when constructed, delete the file now.
136125
// (Unix doesn't directly support DeleteOnClose, so we mimic it here.)
137126
if (_deleteOnClose)
@@ -143,6 +132,17 @@ protected override bool ReleaseHandle()
143132
Interop.Sys.Unlink(_path); // ignore errors; it's valid that the path may no longer exist
144133
}
145134

135+
// When the SafeFileHandle was opened, we likely issued an flock on the created descriptor in order to add
136+
// an advisory lock. This lock should be removed via closing the file descriptor, but close can be
137+
// interrupted, and we don't retry closes. As such, we could end up leaving the file locked,
138+
// which could prevent subsequent usage of the file until this process dies. To avoid that, we proactively
139+
// try to release the lock before we close the handle.
140+
if (_isLocked)
141+
{
142+
Interop.Sys.FLock(handle, Interop.Sys.LockOperations.LOCK_UN); // ignore any errors
143+
_isLocked = false;
144+
}
145+
146146
// Close the descriptor. Although close is documented to potentially fail with EINTR, we never want
147147
// to retry, as the descriptor could actually have been closed, been subsequently reassigned, and
148148
// be in use elsewhere in the process. Instead, we simply check whether the call was successful.
@@ -274,7 +274,6 @@ private static Interop.Sys.OpenFlags PreOpenConfigurationFromOptions(FileMode mo
274274
private void Init(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
275275
{
276276
IsAsync = (options & FileOptions.Asynchronous) != 0;
277-
_deleteOnClose = (options & FileOptions.DeleteOnClose) != 0;
278277

279278
// Lock the file if requested via FileShare. This is only advisory locking. FileShare.None implies an exclusive
280279
// lock on the file and all other modes use a shared lock. While this is not as granular as Windows, not mandatory,
@@ -293,6 +292,10 @@ private void Init(string path, FileMode mode, FileAccess access, FileShare share
293292
}
294293
}
295294

295+
// Enable DeleteOnClose when we've succesfully locked the file.
296+
// On Windows, the locking happens atomically as part of opening the file.
297+
_deleteOnClose = (options & FileOptions.DeleteOnClose) != 0;
298+
296299
// These provide hints around how the file will be accessed. Specifying both RandomAccess
297300
// and Sequential together doesn't make sense as they are two competing options on the same spectrum,
298301
// so if both are specified, we prefer RandomAccess (behavior on Windows is unspecified if both are provided).

0 commit comments

Comments
 (0)