File: src\libraries\System.Private.CoreLib\src\System\IO\PersistedFiles.Unix.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
 
namespace System.IO
{
    internal static partial class PersistedFiles
    {
        private static string? s_userProductDirectory;
 
        /// <summary>
        /// Get the location of where to persist information for a particular aspect of a feature of
        /// the framework, such as "x509stores" within "cryptography".
        /// </summary>
        /// <param name="featureName">The directory name for the feature</param>
        /// <param name="subFeatureName">The directory name for the sub-feature</param>
        /// <returns>A path within the user's home directory for persisting data for the sub-feature</returns>
        internal static string GetUserFeatureDirectory(string featureName, string subFeatureName)
        {
            if (s_userProductDirectory == null)
            {
                EnsureUserDirectories();
            }
 
            return Path.Combine(s_userProductDirectory, featureName, subFeatureName);
        }
 
        [MemberNotNull(nameof(s_userProductDirectory))]
        private static void EnsureUserDirectories()
        {
            string? userHomeDirectory = GetHomeDirectory();
 
            if (string.IsNullOrEmpty(userHomeDirectory))
            {
                throw new InvalidOperationException(SR.PersistedFiles_NoHomeDirectory);
            }
 
            s_userProductDirectory = Path.Combine(
                userHomeDirectory,
                TopLevelHiddenDirectory,
                SecondLevelDirectory);
        }
 
        /// <summary>Gets the current user's home directory.</summary>
        /// <returns>The path to the home directory, or null if it could not be determined.</returns>
        internal static string? GetHomeDirectory()
        {
            // First try to get the user's home directory from the HOME environment variable.
            // This should work in most cases.
            string? userHomeDirectory = Environment.GetEnvironmentVariable("HOME");
            if (!string.IsNullOrEmpty(userHomeDirectory))
                return userHomeDirectory;
 
            // In initialization conditions, however, the "HOME" environment variable may
            // not yet be set. For such cases, consult with the password entry.
            unsafe
            {
                // First try with a buffer that should suffice for 99% of cases.
                // Note that, theoretically, userHomeDirectory may be null in the success case
                // if we simply couldn't find a home directory for the current user.
                // In that case, we pass back the null value and let the caller decide
                // what to do.
                const int BufLen = Interop.Sys.Passwd.InitialBufferSize;
                byte* stackBuf = stackalloc byte[BufLen];
                if (TryGetHomeDirectoryFromPasswd(stackBuf, BufLen, out userHomeDirectory))
                    return userHomeDirectory;
 
                // Fallback to heap allocations if necessary, growing the buffer until
                // we succeed.  TryGetHomeDirectory will throw if there's an unexpected error.
                int lastBufLen = BufLen;
                while (true)
                {
                    lastBufLen *= 2;
                    byte[] heapBuf = new byte[lastBufLen];
                    fixed (byte* buf = &heapBuf[0])
                    {
                        if (TryGetHomeDirectoryFromPasswd(buf, heapBuf.Length, out userHomeDirectory))
                            return userHomeDirectory;
                    }
                }
            }
        }
 
        /// <summary>Wrapper for getpwuid_r.</summary>
        /// <param name="buf">The scratch buffer to pass into getpwuid_r.</param>
        /// <param name="bufLen">The length of <paramref name="buf"/>.</param>
        /// <param name="path">The resulting path; null if the user didn't have an entry.</param>
        /// <returns>true if the call was successful (path may still be null); false is a larger buffer is needed.</returns>
        private static unsafe bool TryGetHomeDirectoryFromPasswd(byte* buf, int bufLen, out string? path)
        {
            // Call getpwuid_r to get the passwd struct
            Interop.Sys.Passwd passwd;
            int error = Interop.Sys.GetPwUidR(Interop.Sys.GetEUid(), out passwd, buf, bufLen);
 
            // If the call succeeds, give back the home directory path retrieved
            if (error == 0)
            {
                Debug.Assert(passwd.HomeDirectory != null);
                path = Marshal.PtrToStringUTF8((IntPtr)passwd.HomeDirectory);
                return true;
            }
 
            // If the current user's entry could not be found, give back null
            // path, but still return true as false indicates the buffer was
            // too small.
            if (error == -1)
            {
                path = null;
                return true;
            }
 
            var errorInfo = new Interop.ErrorInfo(error);
 
            // If the call failed because the buffer was too small, return false to
            // indicate the caller should try again with a larger buffer.
            if (errorInfo.Error == Interop.Error.ERANGE)
            {
                path = null;
                return false;
            }
 
            // Otherwise, fail.
            throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
        }
    }
}