| File: NuGetExtractionFileIO.cs | Web Access |
| Project: src\nuget-client\src\NuGet.Core\NuGet.Packaging\NuGet.Packaging.csproj (NuGet.Packaging) |
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; using NuGet.Common; namespace NuGet.Packaging { internal static class NuGetExtractionFileIO { private static int _unixPermissions = Convert.ToInt32("766", 8); private static Lazy<Func<string, FileStream>> _createFileMethod = new Lazy<Func<string, FileStream>>(CreateFileMethodSelector); internal static FileStream CreateFile(string path) { return _createFileMethod.Value(path); } private static Func<string, FileStream> CreateFileMethodSelector() { // Entry permissions are not restored to maintain backwards compatibility with .NET Core 1.x. // (https://github.com/NuGet/Home/issues/4424) // On .NET Core 1.x, all extracted files had default permissions of 766. // The default on .NET Core 2.x has changed to 666. // To avoid breaking executable files in existing packages (which don't have the x-bit set) // we force the .NET Core 1.x default permissions. if (RuntimeEnvironmentHelper.IsWindows) { // Windows doesn't use POSIX permission bits. return File.Create; } else if (RuntimeEnvironmentHelper.IsMono) { // Mono doesn't work with the DotnetCoreCreateFile method below, so we'll chmod each file. // But since the OS only applies the umask on creation, we'll need to figure out what the // umask is so that we can apply the correct permissions bits to chmod. ApplyUMaskToUnixPermissions(); return MonoPosixCreateFile; } else { return DotnetCoreCreateFile; } } private static FileStream DotnetCoreCreateFile(string path) { // .NET APIs don't expose UNIX file permissions, so P/Invoke the POSIX create file API with our // preferred permissions, and wrap the file handle/descriptor in a SafeFileHandle. int fd; try { fd = PosixCreate(path, _unixPermissions); } catch (Exception exception) { throw new Exception($"Error trying to create file {path}: {exception.Message}", exception); } if (fd == -1) { using (File.Create(path)) { // File.Create() should have thrown an exception with an appropriate error message } File.Delete(path); throw new InvalidOperationException("libc creat failed, but File.Create did not"); } var sfh = new SafeFileHandle((IntPtr)fd, ownsHandle: true); try { return new FileStream(sfh, FileAccess.ReadWrite); } catch { sfh.Dispose(); throw; } } private static FileStream MonoPosixCreateFile(string path) { var fileStream = File.Create(path); _ = PosixChmod(path, _unixPermissions); return fileStream; } private static void ApplyUMaskToUnixPermissions() { if (!ApplyUMaskToUnixPermissionsFromProcess()) { ApplyUMaskToUnixPermissionsFromLibc(); } } private static bool ApplyUMaskToUnixPermissionsFromProcess() { try { string output; using (var process = new Process()) { // Unfortunately typing "umask" in a shell doesn't run a program, instead "umask" is a built-in // function in the shell. The shell named "sh" is almost always available, since many scripts // expect it to be there, so it's a fairly safe assumption. // We're intentionally not using the full path to "sh" because the POSIX spec says this: // http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html#tag_20_117_16 // Applications should note that the standard PATH to the shell cannot be assumed to be either // /bin/sh or /usr/bin/sh, and should be determined by interrogation of the PATH process.StartInfo.FileName = "sh"; process.StartInfo.Arguments = "-c umask"; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.Start(); if (!process.WaitForExit(1000) || process.ExitCode != 0) { return false; } output = process.StandardOutput.ReadToEnd(); } var mask = Convert.ToInt32(output.Substring(0, 4), 8); _unixPermissions = _unixPermissions & ~mask; return true; } catch { return false; } } private static void ApplyUMaskToUnixPermissionsFromLibc() { // POSIX umask API doesn't have a get-only version. So, we must change the mask to get the current value, // then change it back again. There's a potential timing issue if another thread creates a file or // directory after our first call to umask and before the second, so we'll set it to a safe, restrictive // permission, since that's better than accidentally writing files that are too permissive. // Ideally this method would be called before the program creates any threads or async tasks. However, // this class is in a class library, meaning we can't control is the calling assembly has already started // threading or not. We could create a public initialization method, but to enforce it being called we // would have to break backwards compatability. Since this method is only called when the umask couldn't be // read from running "sh -c umask", we're extremely unlikely to ever get here, so best-effort is good enough. var mask = Convert.ToInt32("700", 8); mask = PosixUMask(mask); _ = PosixUMask(mask); _unixPermissions = _unixPermissions & ~mask; } [DllImport("libc", EntryPoint = "creat")] private static extern int PosixCreate([MarshalAs(UnmanagedType.LPStr)] string pathname, int mode); [DllImport("libc", EntryPoint = "chmod")] private static extern int PosixChmod([MarshalAs(UnmanagedType.LPStr)] string pathname, int mode); [DllImport("libc", EntryPoint = "umask")] private static extern int PosixUMask(int mask); } }