From 73502c91a21fbf4f4732cb89a386d8bd7f863c41 Mon Sep 17 00:00:00 2001 From: Nick Guerrera Date: Wed, 26 Aug 2015 11:48:52 -0700 Subject: [PATCH] Shim pipe and fcntl --- .../Linux/libc/Interop.FcntlCommands.cs | 15 ----- .../src/Interop/Linux/libc/Interop.pipe2.cs | 20 ------- .../Unix/System.Native/Interop.Fcntl.cs | 25 +++++++++ .../Unix/System.Native/Interop.Pipe.cs | 30 ++++++++++ .../src/Interop/Unix/libc/Interop.fcntl.cs | 21 ------- .../Unix/libcurl/Interop.SafeCurlHandle.cs | 12 ++-- src/Native/System.Native/pal_io.cpp | 55 +++++++++++++++++++ src/Native/System.Native/pal_io.h | 47 ++++++++++++++++ src/Native/config.h.in | 1 + src/Native/configure.cmake | 4 ++ .../src/System.IO.Pipes.csproj | 24 ++------ .../src/System/IO/Pipes/PipeStream.Linux.cs | 40 -------------- .../src/System/IO/Pipes/PipeStream.OSX.cs | 25 --------- .../src/System/IO/Pipes/PipeStream.Unix.cs | 39 +++++++++++-- .../src/System.Net.Http.csproj | 4 +- 15 files changed, 210 insertions(+), 152 deletions(-) delete mode 100644 src/Common/src/Interop/Linux/libc/Interop.FcntlCommands.cs delete mode 100644 src/Common/src/Interop/Linux/libc/Interop.pipe2.cs create mode 100644 src/Common/src/Interop/Unix/System.Native/Interop.Fcntl.cs create mode 100644 src/Common/src/Interop/Unix/System.Native/Interop.Pipe.cs delete mode 100644 src/Common/src/Interop/Unix/libc/Interop.fcntl.cs delete mode 100644 src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Linux.cs delete mode 100644 src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.OSX.cs diff --git a/src/Common/src/Interop/Linux/libc/Interop.FcntlCommands.cs b/src/Common/src/Interop/Linux/libc/Interop.FcntlCommands.cs deleted file mode 100644 index 9c745178fcff..000000000000 --- a/src/Common/src/Interop/Linux/libc/Interop.FcntlCommands.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -internal static partial class Interop -{ - internal static partial class libc - { - internal enum FcntlCommands - { - F_LINUX_SPECIFIC_BASE = 1024, - F_SETPIPE_SZ = F_LINUX_SPECIFIC_BASE + 7, - F_GETPIPE_SZ = F_LINUX_SPECIFIC_BASE + 8 - } - } -} diff --git a/src/Common/src/Interop/Linux/libc/Interop.pipe2.cs b/src/Common/src/Interop/Linux/libc/Interop.pipe2.cs deleted file mode 100644 index 957c132c8443..000000000000 --- a/src/Common/src/Interop/Linux/libc/Interop.pipe2.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Runtime.InteropServices; - -internal static partial class Interop -{ - internal static partial class libc - { - [Flags] - internal enum Pipe2Flags - { - O_CLOEXEC = 0x80000, - } - - [DllImport(Libraries.Libc, SetLastError = true)] - internal static extern unsafe int pipe2(int* pipefd, Pipe2Flags flags); - } -} diff --git a/src/Common/src/Interop/Unix/System.Native/Interop.Fcntl.cs b/src/Common/src/Interop/Unix/System.Native/Interop.Fcntl.cs new file mode 100644 index 000000000000..7c1782a11cd4 --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Native/Interop.Fcntl.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + internal static class Fcntl + { + internal static readonly bool CanGetSetPipeSz = (FcntlCanGetSetPipeSz() != 0); + + [DllImport(Libraries.SystemNative, EntryPoint="FcntlGetPipeSz", SetLastError=true)] + internal static extern int GetPipeSz(int fd); + + [DllImport(Libraries.SystemNative, EntryPoint="FcntlSetPipeSz", SetLastError=true)] + internal static extern int SetPipeSz(int fd, int size); + + [DllImport(Libraries.SystemNative)] + private static extern int FcntlCanGetSetPipeSz(); + } + } +} diff --git a/src/Common/src/Interop/Unix/System.Native/Interop.Pipe.cs b/src/Common/src/Interop/Unix/System.Native/Interop.Pipe.cs new file mode 100644 index 000000000000..2aabe3736126 --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Native/Interop.Pipe.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + [Flags] + internal enum PipeFlags + { + O_CLOEXEC = 0x0010, + } + + /// + /// The index into the array filled by which represents the read end of the pipe. + /// + internal const int ReadEndOfPipe = 0; + + /// + /// The index into the array filled by which represents the read end of the pipe. + /// + internal const int WriteEndOfPipe = 1; + + [DllImport(Libraries.SystemNative, SetLastError = true)] + internal static extern unsafe int Pipe(int* pipefd, PipeFlags flags = 0); // pipefd is an array of two ints + } +} diff --git a/src/Common/src/Interop/Unix/libc/Interop.fcntl.cs b/src/Common/src/Interop/Unix/libc/Interop.fcntl.cs deleted file mode 100644 index a713a5d89f2d..000000000000 --- a/src/Common/src/Interop/Unix/libc/Interop.fcntl.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Runtime.InteropServices; - -internal static partial class Interop -{ - internal static partial class libc - { - // fcntl takes a variable number of arguments, which is difficult to represent in C#. - // Instead, since we only have a small and fixed number of call sites, we declare - // an overload for each of the specific argument sets we need. - - [DllImport(Libraries.Libc)] - internal static extern unsafe int fcntl(int fd, FcntlCommands cmd); - - [DllImport(Libraries.Libc)] - internal static extern unsafe int fcntl(int fd, FcntlCommands cmd, int arg1); - } -} diff --git a/src/Common/src/Interop/Unix/libcurl/Interop.SafeCurlHandle.cs b/src/Common/src/Interop/Unix/libcurl/Interop.SafeCurlHandle.cs index 367058e88d26..3f93f4c189ab 100644 --- a/src/Common/src/Interop/Unix/libcurl/Interop.SafeCurlHandle.cs +++ b/src/Common/src/Interop/Unix/libcurl/Interop.SafeCurlHandle.cs @@ -77,7 +77,7 @@ public SafeCurlMultiHandle() { fixed(int* fds = _specialFds) { - while (Interop.CheckIo(libc.pipe(fds))); + while (Interop.CheckIo(Interop.Sys.Pipe(fds))); } } } @@ -116,7 +116,7 @@ internal void PollFds(List readyFds) // is ready for a read/write. The special fd is the read end of a pipe // Whenever an fd is added/removed in _fdSet, a write happens to the // write end of the pipe thus causing the poll to return. - pollFds[0].fd = _specialFds[libc.ReadEndOfPipe]; + pollFds[0].fd = _specialFds[Interop.Sys.ReadEndOfPipe]; pollFds[0].events = PollFlags.POLLIN; int i = 1; foreach (int fd in _fdSet) @@ -186,7 +186,7 @@ internal void SignalFdSetChange(int fd, bool isRemove) { // Write to special fd byte* dummyBytes = stackalloc byte[1]; - if ((int)libc.write(_specialFds[libc.WriteEndOfPipe], dummyBytes, (size_t)1) <= 0) + if ((int)libc.write(_specialFds[Interop.Sys.WriteEndOfPipe], dummyBytes, (size_t)1) <= 0) { // TODO: How to handle errors? throw new InvalidOperationException("Cannot write data: " + Marshal.GetLastWin32Error()); @@ -212,8 +212,8 @@ protected override bool ReleaseHandle() Debug.Assert(0 == _requestCount); Debug.Assert(_pollCancelled); - Interop.Sys.Close(_specialFds[libc.ReadEndOfPipe]); - Interop.Sys.Close(_specialFds[libc.WriteEndOfPipe]); + Interop.Sys.Close(_specialFds[Interop.Sys.ReadEndOfPipe]); + Interop.Sys.Close(_specialFds[Interop.Sys.WriteEndOfPipe]); libcurl.curl_multi_cleanup(this.handle); return true; @@ -227,7 +227,7 @@ private int ReadSpecialFd(PollFlags revents) return -1; } Debug.Assert((revents & PollFlags.POLLIN) != 0); - int pipeReadFd = _specialFds[libc.ReadEndOfPipe]; + int pipeReadFd = _specialFds[Interop.Sys.ReadEndOfPipe]; int bytesRead = 0; unsafe { diff --git a/src/Native/System.Native/pal_io.cpp b/src/Native/System.Native/pal_io.cpp index a91224dcb5cf..1707b0cc5d43 100644 --- a/src/Native/System.Native/pal_io.cpp +++ b/src/Native/System.Native/pal_io.cpp @@ -302,3 +302,58 @@ int32_t CloseDir(DIR* dir) { return closedir(dir); } + +extern "C" +int32_t Pipe(int32_t pipeFds[2], int32_t flags) +{ + switch (flags) + { + case 0: + break; + case PAL_O_CLOEXEC: + flags = O_CLOEXEC; + break; + default: + assert(!"Unknown flag."); + errno = EINVAL; + return -1; + } + +#if HAVE_PIPE2 + return pipe2(pipeFds, flags); +#else + return pipe(pipeFds); // CLOEXEC intentionally ignored on platforms without pipe2. +#endif +} + +extern "C" +int32_t FcntlCanGetSetPipeSz() +{ +#if defined(F_GETPIPE_SZ) && defined(F_SETPIPE_SZ) + return true; +#else + return false; +#endif +} + +extern "C" +int32_t FcntlGetPipeSz(int32_t fd) +{ +#ifdef F_GETPIPE_SZ + return fcntl(fd, F_GETPIPE_SZ); +#else + errno = ENOTSUP; + return -1; +#endif +} + +extern "C" +int32_t FcntlSetPipeSz(int32_t fd, int32_t size) +{ +#ifdef F_SETPIPE_SZ + return fcntl(fd, F_SETPIPE_SZ, size); +#else + errno = ENOTSUP; + return -1; +#endif +} diff --git a/src/Native/System.Native/pal_io.h b/src/Native/System.Native/pal_io.h index ad13bf35b075..2929c0e459f6 100644 --- a/src/Native/System.Native/pal_io.h +++ b/src/Native/System.Native/pal_io.h @@ -232,3 +232,50 @@ DIR* OpenDir( extern "C" int32_t CloseDir( DIR* dir); + +/** + * Creates a pipe. Implemented as shim to pipe(2) or pipe2(2) if available. + * Flags are ignored if pipe2 is not available. + * + * Returns 0 for success, -1 for failure. Sets errno on failure. + */ +extern "C" +int32_t Pipe( + int32_t pipefd[2], // [out] pipefds[0] gets read end, pipefd[1] gets write end. + int32_t flags); // 0 for defaults or PAL_O_CLOEXEC for close-on-exec + + +// NOTE: Rather than a general fcntl shim, we opt to export separate functions +// for each command. This allows use to have strongly typed arguments and saves +// complexity around converting command codes. + +/** + * Determines if the current platform supports getting and setting pipe capacity. + * + * Returns true (non-zero) if supported, false (zero) if not. + */ +extern "C" +int32_t FcntlCanGetSetPipeSz(); + +/** + * Gets the capacity of a pipe. + * + * Returns the capacity or -1 with errno set aprropriately on failure. + * + * NOTE: Some platforms do not support this operation and will always fail with errno = ENOTSUP. + */ +extern "C" +int32_t FcntlGetPipeSz( + int32_t fd); + +/** + * Sets the capacity of a pipe. + * + * Returns 0 for success, -1 for failure. Sets errno for failure. + * + * NOTE: Some platforms do not support this operation and will always fail with errno = ENOTSUP. + */ +extern "C" +int32_t FcntlSetPipeSz( + int32_t fd, + int32_t size); \ No newline at end of file diff --git a/src/Native/config.h.in b/src/Native/config.h.in index 9f7e0ac1bf47..ca932191c406 100644 --- a/src/Native/config.h.in +++ b/src/Native/config.h.in @@ -1,6 +1,7 @@ #pragma once #cmakedefine01 HAVE_STAT64 +#cmakedefine01 HAVE_PIPE2 #cmakedefine01 HAVE_STAT_BIRTHTIME #cmakedefine01 HAVE_GNU_STRERROR_R #cmakedefine01 HAVE_DIRENT_NAME_LEN diff --git a/src/Native/configure.cmake b/src/Native/configure.cmake index e8c39dd90b73..a1b228be4732 100644 --- a/src/Native/configure.cmake +++ b/src/Native/configure.cmake @@ -6,6 +6,10 @@ check_function_exists( stat64 HAVE_STAT64) +check_function_exists( + pipe2 + HAVE_PIPE2) + check_struct_has_member( "struct stat" st_birthtime diff --git a/src/System.IO.Pipes/src/System.IO.Pipes.csproj b/src/System.IO.Pipes/src/System.IO.Pipes.csproj index d61fdb582ffa..51d614f6af33 100644 --- a/src/System.IO.Pipes/src/System.IO.Pipes.csproj +++ b/src/System.IO.Pipes/src/System.IO.Pipes.csproj @@ -153,12 +153,12 @@ Common\Interop\Unix\Interop.IOErrors.cs - - Common\Interop\Unix\Interop.pipe2.cs - Common\Interop\Unix\Interop.Close.cs + + Common\Interop\Unix\Interop.Fcntl.cs + Common\Interop\Unix\Interop.gethostname.cs @@ -180,8 +180,8 @@ Common\Interop\Unix\Interop.Permissions.cs - - Common\Interop\Unix\Interop.pipe.cs + + Common\Interop\Unix\Interop.Pipe.cs Common\Interop\Unix\Interop.poll.cs @@ -205,20 +205,6 @@ Common\System\Threading\Tasks\ForceAsyncAwaiter.cs - - - - - Common\Interop\Unix\Interop.fcntl.cs - - - Common\Interop\Linux\Interop.FcntlCommands.cs - - - - - - diff --git a/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Linux.cs b/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Linux.cs deleted file mode 100644 index f0bcc8e4993f..000000000000 --- a/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Linux.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using Microsoft.Win32.SafeHandles; - -namespace System.IO.Pipes -{ - public abstract partial class PipeStream - { - internal void InitializeBufferSize(SafePipeHandle handle, int bufferSize) - { - if (bufferSize > 0) - { - SysCall(handle, (fd, _, size, __) => Interop.libc.fcntl(fd, Interop.libc.FcntlCommands.F_SETPIPE_SZ, size), - IntPtr.Zero, bufferSize); - } - } - - private int InBufferSizeCore { get { return GetPipeBufferSize(); } } - - private int OutBufferSizeCore { get { return GetPipeBufferSize(); } } - - private int GetPipeBufferSize() - { - // If we have a handle, get the capacity of the pipe (there's no distinction between in/out direction). - // If we don't, the pipe has been created but not yet connected (in the case of named pipes), - // so just return the buffer size that was passed to the constructor. - return _handle != null ? - (int)SysCall(_handle, (fd, _, __, ___) => Interop.libc.fcntl(fd, Interop.libc.FcntlCommands.F_GETPIPE_SZ)) : - _outBufferSize; - } - - internal static unsafe void CreateAnonymousPipe(HandleInheritability inheritability, int* fdsptr) - { - var flags = (inheritability & HandleInheritability.Inheritable) == 0 ? - Interop.libc.Pipe2Flags.O_CLOEXEC : 0; - while (Interop.CheckIo(Interop.libc.pipe2(fdsptr, flags))) ; - } - } -} diff --git a/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.OSX.cs b/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.OSX.cs deleted file mode 100644 index fc3234ffbc0a..000000000000 --- a/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.OSX.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using Microsoft.Win32.SafeHandles; - -namespace System.IO.Pipes -{ - public abstract partial class PipeStream - { - internal void InitializeBufferSize(SafePipeHandle handle, int bufferSize) - { - // Nop. We can't configure the capacity, and as it's just advisory, ignore it. - } - - private int InBufferSizeCore { get { throw new PlatformNotSupportedException(); } } - - private int OutBufferSizeCore { get { throw new PlatformNotSupportedException(); } } - - internal static unsafe void CreateAnonymousPipe(HandleInheritability inheritability, int* fdsptr) - { - // Ignore inheritability, as on OSX pipe2 isn't available - while (Interop.CheckIo(Interop.libc.pipe(fdsptr))) ; - } - } -} diff --git a/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Unix.cs b/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Unix.cs index 99609bbbb01c..778314651d6b 100644 --- a/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Unix.cs +++ b/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Unix.cs @@ -154,7 +154,7 @@ public virtual int InBufferSize { throw new NotSupportedException(SR.NotSupported_UnreadableStream); } - return InBufferSizeCore; + return GetPipeBufferSize(); } } @@ -173,7 +173,7 @@ public virtual int OutBufferSize { throw new NotSupportedException(SR.NotSupported_UnwritableStream); } - return OutBufferSizeCore; + return GetPipeBufferSize(); } } @@ -452,8 +452,8 @@ internal static unsafe void CreateAnonymousPipe( CreateAnonymousPipe(inheritability, fds); // Store the file descriptors into our safe handles - reader.SetHandle(fds[Interop.libc.ReadEndOfPipe]); - writer.SetHandle(fds[Interop.libc.WriteEndOfPipe]); + reader.SetHandle(fds[Interop.Sys.ReadEndOfPipe]); + writer.SetHandle(fds[Interop.Sys.WriteEndOfPipe]); } /// @@ -588,5 +588,36 @@ private long SysCall( } } + internal void InitializeBufferSize(SafePipeHandle handle, int bufferSize) + { + // bufferSize is just advisory and ignored if platform does not support setting pipe capacity via fcntl. + if (bufferSize > 0 && Interop.Sys.Fcntl.CanGetSetPipeSz) + { + SysCall(handle, (fd, _, size, __) => Interop.Sys.Fcntl.SetPipeSz(fd, size), + IntPtr.Zero, bufferSize); + } + } + + private int GetPipeBufferSize() + { + if (!Interop.Sys.Fcntl.CanGetSetPipeSz) + { + throw new PlatformNotSupportedException(); + } + + // If we have a handle, get the capacity of the pipe (there's no distinction between in/out direction). + // If we don't, the pipe has been created but not yet connected (in the case of named pipes), + // so just return the buffer size that was passed to the constructor. + return _handle != null ? + (int)SysCall(_handle, (fd, _, __, ___) => Interop.Sys.Fcntl.GetPipeSz(fd)) : + _outBufferSize; + } + + internal static unsafe void CreateAnonymousPipe(HandleInheritability inheritability, int* fdsptr) + { + var flags = (inheritability & HandleInheritability.Inheritable) == 0 ? + Interop.Sys.PipeFlags.O_CLOEXEC : 0; + while (Interop.CheckIo(Interop.Sys.Pipe(fdsptr, flags))) ; + } } } diff --git a/src/System.Net.Http/src/System.Net.Http.csproj b/src/System.Net.Http/src/System.Net.Http.csproj index 226d18da6957..8e14f3b0c560 100644 --- a/src/System.Net.Http/src/System.Net.Http.csproj +++ b/src/System.Net.Http/src/System.Net.Http.csproj @@ -159,8 +159,8 @@ Common\Interop\Unix\System.Native\Interop.OpenFlags.cs - - Common\Interop\Unix\libc\Interop.pipe.cs + + Common\Interop\Unix\System.Native\Interop.Pipe.cs Common\Interop\Unix\libc\Interop.poll.cs