/// <inheritdoc /> public Task <bool> CopyFileAsync( string source, string destination, Func <SafeFileHandle, SafeFileHandle, bool> predicate = null, Action <SafeFileHandle, SafeFileHandle> onCompletion = null) { Contract.Requires(!string.IsNullOrEmpty(source)); Contract.Requires(!string.IsNullOrEmpty(destination)); return(ExceptionUtilities.HandleRecoverableIOExceptionAsync( async() => { using (FileStream sourceStream = CreateAsyncFileStream( source, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)) { if (predicate != null) { SafeFileHandle destinationHandle; OpenFileResult predicateQueryOpenResult = s_fileSystem.TryCreateOrOpenFile( destination, FileDesiredAccess.GenericRead, FileShare.Read | FileShare.Delete, FileMode.OpenOrCreate, FileFlagsAndAttributes.None, out destinationHandle); using (destinationHandle) { if (!predicateQueryOpenResult.Succeeded) { throw new BuildXLException( I($"Failed to open a copy destination '{destination}' to check its version"), predicateQueryOpenResult.CreateExceptionForError()); } if (!predicate(sourceStream.SafeFileHandle, predicateQueryOpenResult.OpenedOrTruncatedExistingFile ? destinationHandle : null)) { return false; } } } using (FileStream destinationStream = CreateReplacementFile(destination, FileShare.Delete, openAsync: true)) { await sourceStream.CopyToAsync(destinationStream); onCompletion?.Invoke(sourceStream.SafeFileHandle, destinationStream.SafeFileHandle); } } return true; }, ex => { throw new BuildXLException("File copy failed", ex); })); }
/// <inheritdoc /> public Task <bool> WriteAllBytesAsync( string filePath, byte[] bytes, Func <SafeFileHandle, bool> predicate = null, Action <SafeFileHandle> onCompletion = null) { Contract.Requires(!string.IsNullOrEmpty(filePath)); Contract.Requires(bytes != null); return(ExceptionUtilities.HandleRecoverableIOExceptionAsync( async() => { if (predicate != null) { SafeFileHandle destinationHandle; OpenFileResult predicateQueryOpenResult = m_fileSystem.TryCreateOrOpenFile( filePath, FileDesiredAccess.GenericRead, FileShare.Read | FileShare.Delete, FileMode.OpenOrCreate, FileFlagsAndAttributes.None, out destinationHandle); using (destinationHandle) { if (!predicateQueryOpenResult.Succeeded) { throw new BuildXLException( I($"Failed to open file '{filePath}' to check its version"), predicateQueryOpenResult.CreateExceptionForError()); } if (!predicate(predicateQueryOpenResult.OpenedOrTruncatedExistingFile ? destinationHandle : null)) { return false; } } } using (FileStream stream = CreateReplacementFile(filePath, FileShare.Delete, openAsync: true)) { await stream.WriteAsync(bytes, 0, bytes.Length); } if (onCompletion != null) { using (var file = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Delete)) { onCompletion(file.SafeFileHandle); } } return true; }, ex => { throw new BuildXLException("File write failed", ex); })); }
/// <summary> /// Creates an instance of <see cref="NamedPipeServerStream"/> and immediately connects a client to it. /// </summary> /// <param name="serverDirection">Server direction.</param> /// <param name="serverOptions">Server options.</param> /// <param name="clientOptions">Client options.</param> /// <param name="clientHandle">Output client handle.</param> /// <returns>An instance of <see cref="NamedPipeServerStream"/>.</returns> public static NamedPipeServerStream CreateNamedPipeServerStream( PipeDirection serverDirection, PipeOptions serverOptions, PipeOptions clientOptions, out SafeFileHandle clientHandle) { string pipeName = @"BuildXL-" + Guid.NewGuid().ToString("N"); var pipeServerStream = new NamedPipeServerStream( pipeName, serverDirection, maxNumberOfServerInstances: 1, transmissionMode: PipeTransmissionMode.Byte, options: serverOptions, inBufferSize: PipeBufferSize, outBufferSize: PipeBufferSize); FileDesiredAccess clientDesiredAccess = FileDesiredAccess.None; if ((PipeDirection.In & serverDirection) != 0) { clientDesiredAccess |= FileDesiredAccess.GenericWrite; } if ((PipeDirection.Out & serverDirection) != 0) { clientDesiredAccess |= FileDesiredAccess.GenericRead; } FileFlagsAndAttributes clientFlags = clientOptions == PipeOptions.Asynchronous ? FileFlagsAndAttributes.FileFlagOverlapped : 0; OpenFileResult openClientHandle = FileUtilities.TryCreateOrOpenFile( @"\\.\pipe\" + pipeName, clientDesiredAccess, FileShare.None, FileMode.Open, clientFlags | FileFlagsAndAttributes.SecurityAnonymous, out clientHandle); if (!openClientHandle.Succeeded) { throw openClientHandle.CreateExceptionForError(); } // Client should be made inheritable. SetInheritable(clientHandle); pipeServerStream.WaitForConnection(); return(pipeServerStream); }
/// <summary> /// Creates a pipe in which the specified ends are inheritable by child processes. /// In order for the pipe to be closable by the child, the inherited end should be closed after process creation. /// </summary> /// <remarks> /// This implementation creates a named pipe instance and immediately connects it. This is effectively the same implementation /// as <c>CreatePipe</c> (see %SDXROOT%\minkernel\kernelbase\pipe.c), but we compute a random pipe named rather than using the special /// 'anonymous' naming case of opening \\.\pipe or \Device\NamedPipe\ (without a subsequent path). /// We choose to not use <c>CreatePipe</c> since it opens both sides for synchronous I/O. Instead, for running build tools we want the /// BuildXL side opened for overlapped I/O and the build tool side opened for synchronous I/O (if the build tool side acts as a redirected /// console handle; recall that synchronous read and write attempts will fail on an async handle, which motivates the <c>CreatePipe</c> defaults /// in the first place). This is significant since many build tool console pipes are forever silent, and with sync handles we'd need N * 3 threads /// (stdin, stdout, stderr for N concurrent processes) to pessimistically drain them. /// </remarks> public static void CreateInheritablePipe(PipeInheritance inheritance, PipeFlags flags, out SafeFileHandle readHandle, out SafeFileHandle writeHandle) { string pipeName = @"\\.\pipe\BuildXL-" + Guid.NewGuid().ToString("N"); writeHandle = ProcessUtilities.CreateNamedPipe( pipeName, PipeOpenMode.PipeAccessOutbound | (((flags & PipeFlags.WriteSideAsync) != 0) ? PipeOpenMode.FileFlagOverlapped : 0), PipeMode.PipeTypeByte | PipeMode.PipeRejectRemoteClients, nMaxInstances: 1, nOutBufferSize: PipeBufferSize, nInBufferSize: PipeBufferSize, nDefaultTimeout: 0, lpSecurityAttributes: IntPtr.Zero); if (writeHandle.IsInvalid) { throw new NativeWin32Exception(Marshal.GetLastWin32Error(), "CreateNamedPipeW failed"); } var readSideFlags = FileFlagsAndAttributes.SecurityAnonymous; if ((flags & PipeFlags.ReadSideAsync) != 0) { readSideFlags |= FileFlagsAndAttributes.FileFlagOverlapped; } OpenFileResult openReadSideResult = FileUtilities.TryCreateOrOpenFile( pipeName, FileDesiredAccess.GenericRead, FileShare.None, FileMode.Open, readSideFlags, out readHandle); if (!openReadSideResult.Succeeded) { throw openReadSideResult.CreateExceptionForError(); } if ((inheritance & PipeInheritance.InheritRead) != 0) { SetInheritable(readHandle); } if ((inheritance & PipeInheritance.InheritWrite) != 0) { SetInheritable(writeHandle); } }
/// <inheritdoc /> public Task <bool> CopyFileAsync( string source, string destination, Func <SafeFileHandle, SafeFileHandle, bool> predicate = null, Action <SafeFileHandle, SafeFileHandle> onCompletion = null) { Contract.Requires(!string.IsNullOrEmpty(source)); Contract.Requires(!string.IsNullOrEmpty(destination)); return(ExceptionUtilities.HandleRecoverableIOExceptionAsync( async() => { using (FileStream sourceStream = CreateAsyncFileStream( source, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)) { if (predicate != null) { SafeFileHandle destinationHandle; OpenFileResult predicateQueryOpenResult = m_fileSystem.TryCreateOrOpenFile( destination, FileDesiredAccess.GenericRead, FileShare.Read | FileShare.Delete, FileMode.OpenOrCreate, FileFlagsAndAttributes.None, out destinationHandle); using (destinationHandle) { if (!predicateQueryOpenResult.Succeeded) { throw new BuildXLException( I($"Failed to open a copy destination '{destination}' to check its version"), predicateQueryOpenResult.CreateExceptionForError()); } if (!predicate(sourceStream.SafeFileHandle, predicateQueryOpenResult.OpenedOrTruncatedExistingFile ? destinationHandle : null)) { return false; } } } using (var destinationStream = CreateReplacementFile(destination, FileShare.Delete, openAsync: true)) { await sourceStream.CopyToAsync(destinationStream); var mode = GetFilePermissionsForFilePath(source, followSymlink: false); var result = SetFilePermissionsForFilePath(destination, checked ((FilePermissions)mode), followSymlink: false); if (result < 0) { throw new BuildXLException($"Failed to set permissions for file copy at '{destination}' - error: {Marshal.GetLastWin32Error()}"); } } if (onCompletion != null) { using (var dest = File.Open(destination, FileMode.Open, FileAccess.Read, FileShare.Delete)) { onCompletion(sourceStream.SafeFileHandle, dest.SafeFileHandle); } } } return true; }, ex => { throw new BuildXLException(I($"File copy from '{source}' to '{destination}' failed"), ex); })); }
/// <summary> /// Creates a pipe in which the specified ends are inheritable by child processes. /// In order for the pipe to be closable by the child, the inherited end should be closed after process creation. /// </summary> /// <remarks> /// This implementation creates a named pipe instance and immediately connects it. This is effectively the same implementation /// as <c>CreatePipe</c> (see %SDXROOT%\minkernel\kernelbase\pipe.c), but we compute a random pipe named rather than using the special /// 'anonymous' naming case of opening \\.\pipe or \Device\NamedPipe\ (without a subsequent path). /// We choose to not use <c>CreatePipe</c> since it opens both sides for synchronous I/O. Instead, for running build tools we want the /// BuildXL side opened for overlapped I/O and the build tool side opened for synchronous I/O (if the build tool side acts as a redirected /// console handle; recall that synchronous read and write attempts will fail on an async handle, which motivates the <c>CreatePipe</c> defaults /// in the first place). This is significant since many build tool console pipes are forever silent, and with sync handles we'd need N * 3 threads /// (stdin, stdout, stderr for N concurrent processes) to pessimistically drain them. /// </remarks> public static void CreateInheritablePipe(PipeInheritance inheritance, PipeFlags flags, out SafeFileHandle readHandle, out SafeFileHandle writeHandle) { string pipeName = @"\\.\pipe\BuildXL-" + Guid.NewGuid().ToString("N"); writeHandle = ProcessUtilities.CreateNamedPipe( pipeName, PipeOpenMode.PipeAccessOutbound | (((flags & PipeFlags.WriteSideAsync) != 0) ? PipeOpenMode.FileFlagOverlapped : 0), PipeMode.PipeTypeByte | PipeMode.PipeRejectRemoteClients, nMaxInstances: 1, nOutBufferSize: PipeBufferSize, nInBufferSize: PipeBufferSize, nDefaultTimeout: 0, lpSecurityAttributes: IntPtr.Zero); if (writeHandle.IsInvalid) { throw new NativeWin32Exception(Marshal.GetLastWin32Error(), "CreateNamedPipeW failed"); } var readSideFlags = FileFlagsAndAttributes.SecurityAnonymous; if ((flags & PipeFlags.ReadSideAsync) != 0) { readSideFlags |= FileFlagsAndAttributes.FileFlagOverlapped; } int maxRetry = 3; while (true) { OpenFileResult openReadSideResult = FileUtilities.TryCreateOrOpenFile( pipeName, FileDesiredAccess.GenericRead, FileShare.None, FileMode.Open, readSideFlags, out readHandle); if (openReadSideResult.Succeeded) { break; } if (openReadSideResult.NativeErrorCode != NativeIOConstants.ErrorPipeBusy || maxRetry == 0) { throw openReadSideResult.CreateExceptionForError(); } bool success = false; // Wait for at most 5s. for (int i = 0; i < 10; ++i) { success = ProcessUtilities.WaitNamedPipe(pipeName, 500); if (success) { break; } } if (!success) { // After waiting for 5s, pipe is still not ready. throw new NativeWin32Exception(Marshal.GetLastWin32Error(), "WaitNamedPipe"); } --maxRetry; } if ((inheritance & PipeInheritance.InheritRead) != 0) { SetInheritable(readHandle); } if ((inheritance & PipeInheritance.InheritWrite) != 0) { SetInheritable(writeHandle); } }