Skip to content

Commit

Permalink
initial Unix implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
adamsitnik authored Apr 20, 2021
1 parent 07cfc75 commit 945c917
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 7 deletions.
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

0 comments on commit 945c917

Please sign in to comment.