// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace System.IO
{
internal static partial class PersistedFiles
{
private static string s_userProductDirectory;
///
/// Get the location of where to persist information for a particular aspect of the framework,
/// such as "cryptography".
///
/// The directory name for the feature
/// A path within the user's home directory for persisting data for the feature
internal static string GetUserFeatureDirectory(string featureName)
{
if (s_userProductDirectory == null)
{
EnsureUserDirectories();
}
return Path.Combine(s_userProductDirectory, featureName);
}
///
/// Get the location of where to persist information for a particular aspect of a feature of
/// the framework, such as "x509stores" within "cryptography".
///
/// The directory name for the feature
/// The directory name for the sub-feature
/// A path within the user's home directory for persisting data for the sub-feature
internal static string GetUserFeatureDirectory(string featureName, string subFeatureName)
{
if (s_userProductDirectory == null)
{
EnsureUserDirectories();
}
return Path.Combine(s_userProductDirectory, featureName, subFeatureName);
}
///
/// Get the location of where to persist information for a particular aspect of the framework,
/// with a lot of hierarchy, such as ["cryptography", "x509stores", "my"]
///
/// A non-empty set of directories to use for the storage hierarchy
/// A path within the user's home directory for persisting data for the feature
internal static string GetUserFeatureDirectory(params string[] featurePathParts)
{
Debug.Assert(featurePathParts != null);
Debug.Assert(featurePathParts.Length > 0);
if (s_userProductDirectory == null)
{
EnsureUserDirectories();
}
return Path.Combine(s_userProductDirectory, Path.Combine(featurePathParts));
}
private static void EnsureUserDirectories()
{
string userHomeDirectory = GetHomeDirectory();
if (string.IsNullOrEmpty(userHomeDirectory))
{
throw new InvalidOperationException(SR.PersistedFiles_NoHomeDirectory);
}
s_userProductDirectory = Path.Combine(
userHomeDirectory,
TopLevelHiddenDirectory,
SecondLevelDirectory);
}
/// Gets the current user's home directory.
/// The path to the home directory, or null if it could not be determined.
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;
}
}
}
}
/// Wrapper for getpwuid_r.
/// The scratch buffer to pass into getpwuid_r.
/// The length of .
/// The resulting path; null if the user didn't have an entry.
/// true if the call was successful (path may still be null); false is a larger buffer is needed.
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.PtrToStringAnsi((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);
}
}
}