Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fallocate #5

Merged
merged 11 commits into from
Apr 20, 2021
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

internal static partial class Interop
{
internal static partial class Sys
{
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_FAllocate", SetLastError = false /* this is explicitly called out in the man page */)]
internal static extern int FAllocate(SafeFileHandle fd, long offset, long length);
}
}
2 changes: 2 additions & 0 deletions src/libraries/Native/Unix/Common/pal_config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
#cmakedefine01 HAVE_STRLCAT
#cmakedefine01 HAVE_SHM_OPEN_THAT_WORKS_WELL_ENOUGH_WITH_MMAP
#cmakedefine01 HAVE_POSIX_ADVISE
#cmakedefine01 HAVE_POSIX_FALLOCATE
#cmakedefine01 HAVE_POSIX_FALLOCATE64
#cmakedefine01 PRIORITY_REQUIRES_INT_WHO
#cmakedefine01 KEVENT_REQUIRES_INT_PARAMS
#cmakedefine01 HAVE_IOCTL
Expand Down
1 change: 1 addition & 0 deletions src/libraries/Native/Unix/System.Native/entrypoints.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ static const Entry s_sysNative[] =
DllImportEntry(SystemNative_FTruncate)
DllImportEntry(SystemNative_Poll)
DllImportEntry(SystemNative_PosixFAdvise)
DllImportEntry(SystemNative_FAllocate)
DllImportEntry(SystemNative_Read)
DllImportEntry(SystemNative_ReadLink)
DllImportEntry(SystemNative_Rename)
Expand Down
56 changes: 54 additions & 2 deletions src/libraries/Native/Unix/System.Native/pal_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,7 @@ int32_t SystemNative_FSync(intptr_t fd)
int fileDescriptor = ToFileDescriptor(fd);

int32_t result;
while ((result =
while ((result =
#if defined(TARGET_OSX) && HAVE_F_FULLFSYNC
fcntl(fileDescriptor, F_FULLFSYNC)
#else
Expand Down Expand Up @@ -991,6 +991,58 @@ int32_t SystemNative_PosixFAdvise(intptr_t fd, int64_t offset, int64_t length, i
#endif
}

int32_t SystemNative_FAllocate(intptr_t fd, int64_t offset, int64_t length)
{
int fileDescriptor = ToFileDescriptor(fd);
int32_t result;
#if HAVE_POSIX_FALLOCATE64 // 64-bit Linux
while ((result = posix_fallocate64(fileDescriptor, (off64_t)offset, (off64_t)length)) == EINTR);
#elif HAVE_POSIX_FALLOCATE // 32-bit Linux
while ((result = posix_fallocate(fileDescriptor, (off_t)offset, (off_t)length)) == EINTR);
#elif defined(F_PREALLOCATE) // macOS
fstore_t fstore;
fstore.fst_flags = F_ALLOCATECONTIG; // ensure contiguous space
fstore.fst_posmode = F_PEOFPOSMODE; // allocate from the physical end of file, as offset MUST NOT be 0 for F_VOLPOSMODE
fstore.fst_offset = (off_t)offset;
fstore.fst_length = (off_t)length;
fstore.fst_bytesalloc = 0; // output size, can be > length

while ((result = fcntl(fileDescriptor, F_PREALLOCATE, &fstore)) == -1 && errno == EINTR) ;

if (result == -1)
{
// we have failed to allocate contiguous space, let's try non-contiguous
fstore.fst_flags = F_ALLOCATEALL; // all or nothing
while ((result = fcntl(fileDescriptor, F_PREALLOCATE, &fstore)) == -1 && errno == EINTR) ;
}
#elif defined(F_ALLOCSP) || defined(F_ALLOCSP64) // FreeBSD
#if HAVE_FLOCK64
struct flock64 lockArgs;
int command = F_ALLOCSP64;
#else
struct flock lockArgs;
int command = F_ALLOCSP;
#endif

lockArgs.l_whence = SEEK_SET;
lockArgs.l_start = (off_t)offset;
lockArgs.l_len = (off_t)length;

while ((result = fcntl(fileDescriptor, command, &lockArgs)) == -1 && errno == EINTR) ;
#endif

#if defined(F_PREALLOCATE) || defined(F_ALLOCSP) || defined(F_ALLOCSP64)
// most of the Unixes implement posix_fallocate which does NOT set the last error
// fctnl does, but to mimic the posix_fallocate behaviour we just return error
if (result == -1)
{
result = errno;
}
#endif

return result;
}

int32_t SystemNative_Read(intptr_t fd, void* buffer, int32_t bufferSize)
{
return Common_Read(fd, buffer, bufferSize);
Expand Down Expand Up @@ -1184,7 +1236,7 @@ int32_t SystemNative_CopyFile(intptr_t sourceFd, intptr_t destinationFd)
#endif
}
// If we copied to a filesystem (eg EXFAT) that does not preserve POSIX ownership, all files appear
// to be owned by root. If we aren't running as root, then we won't be an owner of our new file, and
// to be owned by root. If we aren't running as root, then we won't be an owner of our new file, and
// attempting to copy metadata to it will fail with EPERM. We have copied successfully, we just can't
// copy metadata. The best thing we can do is skip copying the metadata.
if (ret != 0 && errno != EPERM)
Expand Down
7 changes: 7 additions & 0 deletions src/libraries/Native/Unix/System.Native/pal_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,13 @@ PALEXPORT int32_t SystemNative_Poll(PollEvent* pollEvents, uint32_t eventCount,
*/
PALEXPORT int32_t SystemNative_PosixFAdvise(intptr_t fd, int64_t offset, int64_t length, int32_t advice);

/**
* Ensures that disk space is allocated.
*
* Returns 0 on success; otherwise, the error code is returned and errno is NOT set.
*/
PALEXPORT int32_t SystemNative_FAllocate(intptr_t fd, int64_t offset, int64_t length);

/**
* Reads the number of bytes specified into the provided buffer from the specified, opened file descriptor.
*
Expand Down
10 changes: 10 additions & 0 deletions src/libraries/Native/Unix/configure.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,16 @@ check_symbol_exists(
fcntl.h
HAVE_POSIX_ADVISE)

check_symbol_exists(
posix_fallocate
fcntl.h
HAVE_POSIX_FALLOCATE)

check_symbol_exists(
posix_fallocate64
fcntl.h
HAVE_POSIX_FALLOCATE64)

check_symbol_exists(
ioctl
sys/ioctl.h
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public async Task FileOffsetIsPreservedWhenFileStreamIsCreatedFromSafeFileHandle

using FileStream createdFromHandle = new FileStream(stream.SafeFileHandle, FileAccess.Write);

Assert.Equal(buffer.Length, stream.Position);
Assert.Equal(buffer.Length, stream.Position);
Assert.Equal(stream.Position, createdFromHandle.Position);
}

Expand Down Expand Up @@ -187,17 +187,22 @@ public async Task WriteByteFlushesTheBufferWhenItBecomesFull()
byte[] allBytes = File.ReadAllBytes(filePath);
Assert.Equal(writtenBytes.ToArray(), allBytes);
}

[Fact]
public void WhenFileStreamFailsToPreallocateDiskSpaceTheErrorMessageContainsAllTheDetails()
{
const long tooMuch = 1024L * 1024L * 1024L * 1024L; // 1 TB

string filePath = GetTestFilePath();
IOException ex = Assert.Throws<IOException>(() => new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, BufferSize, Options, tooMuch));

Assert.False(File.Exists(filePath));

IOException ex = Assert.Throws<IOException>(() => new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, BufferSize, Options, tooMuch));
Assert.Contains("disk was full", ex.Message);
Assert.Contains(filePath, ex.Message);
Assert.Contains(AllocationSize.ToString(), ex.Message);

Assert.False(File.Exists(filePath));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project>
<Project>
<PropertyGroup>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>c5ed3c1d-b572-46f1-8f96-522a85ce1179</SharedGUID>
Expand Down Expand Up @@ -1821,6 +1821,9 @@
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.PosixFAdvise.cs">
<Link>Common\Interop\Unix\System.Native\Interop.PosixFAdvise.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.FAllocate.cs">
<Link>Common\Interop\Unix\System.Native\Interop.FAllocate.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Read.cs">
<Link>Common\Interop\Unix\System.Native\Interop.Read.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
using Internal.IO;

namespace System.IO.Strategies
{
// this type defines a set of stateless FileStream/FileStreamStrategy helper methods
internal static partial class FileStreamHelpers
{
private const int ENOSPC_Linux = 28;

// in the future we are most probably going to introduce more strategies (io_uring etc)
private static FileStreamStrategy ChooseStrategyCore(SafeFileHandle handle, FileAccess access, FileShare share, int bufferSize, bool isAsync)
=> new Net5CompatFileStreamStrategy(handle, access, bufferSize, isAsync);
Expand All @@ -35,7 +38,22 @@ internal static SafeFileHandle OpenHandle(string path, FileMode mode, FileAccess
Interop.Sys.Permissions.S_IROTH | Interop.Sys.Permissions.S_IWOTH;

// Open the file and store the safe handle.
return SafeFileHandle.Open(path!, openFlags, (int)OpenPermissions);
SafeFileHandle handle = SafeFileHandle.Open(path!, openFlags, (int)OpenPermissions);
// If allocationSize has been provided for a creatable and writeable file
if (allocationSize > 0 && (access & FileAccess.Write) != 0 && mode != FileMode.Open && mode != FileMode.Append)
{
int allocationResult = Interop.Sys.FAllocate(handle, 0, allocationSize);
if (allocationResult == (int)Interop.Error.ENOSPC || allocationResult == ENOSPC_Linux)
{
handle.Dispose();
Interop.Sys.Unlink(path); // remove the file to mimic Windows behaviour (atomic operation)

throw new IOException(SR.Format(SR.IO_DiskFull_Path_AllocationSize, path, allocationSize));
}
// ignore not supported and other failures (pipe etc)
}

return handle;
}

internal static bool GetDefaultIsAsync(SafeFileHandle handle, bool defaultIsAsync) => handle.IsAsync ?? defaultIsAsync;
Expand Down