| 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. #if NETFRAMEWORK using System; using System.Diagnostics; #endif using System.IO; #if NETFRAMEWORK using System.Runtime.InteropServices; using NuGet.Common; #endif namespace NuGet.Packaging { internal static class NuGetExtractionFileIO { #if NETFRAMEWORK private static int _unixPermissions = Convert.ToInt32("766", 8); private static Lazy<Func<string, FileStream>> _createFileMethod = new Lazy<Func<string, FileStream>>(CreateFileMethodSelector); #endif internal static FileStream CreateFile(string path) { #if NETFRAMEWORK return _createFileMethod.Value(path); #else // 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 (!System.OperatingSystem.IsWindows()) { // Match creat's write-only descriptor without restricting other processes from opening the file. return new FileStream( path, new FileStreamOptions { Mode = FileMode.Create, Access = FileAccess.Write, Share = FileShare.ReadWrite | FileShare.Delete, UnixCreateMode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.OtherRead | UnixFileMode.OtherWrite }); } return File.Create(path); #endif } #if NETFRAMEWORK 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.IsMono) { // Since the OS only applies the umask on creation, figure out what the // umask is so that we can apply the correct permission bits to chmod. ApplyUMaskToUnixPermissions(); return MonoPosixCreateFile; } // Windows doesn't use POSIX permission bits. return File.Create; } 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 = "chmod")] private static extern int PosixChmod([MarshalAs(UnmanagedType.LPStr)] string pathname, int mode); [DllImport("libc", EntryPoint = "umask")] private static extern int PosixUMask(int mask); #endif } }