PersistedFiles.Unix.cs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. #nullable enable
  5. using System.Diagnostics;
  6. using System.Runtime.InteropServices;
  7. namespace System.IO
  8. {
  9. internal static partial class PersistedFiles
  10. {
  11. private static string? s_userProductDirectory;
  12. /// <summary>
  13. /// Get the location of where to persist information for a particular aspect of the framework,
  14. /// such as "cryptography".
  15. /// </summary>
  16. /// <param name="featureName">The directory name for the feature</param>
  17. /// <returns>A path within the user's home directory for persisting data for the feature</returns>
  18. internal static string GetUserFeatureDirectory(string featureName)
  19. {
  20. if (s_userProductDirectory == null)
  21. {
  22. EnsureUserDirectories();
  23. }
  24. return Path.Combine(s_userProductDirectory!, featureName);
  25. }
  26. /// <summary>
  27. /// Get the location of where to persist information for a particular aspect of a feature of
  28. /// the framework, such as "x509stores" within "cryptography".
  29. /// </summary>
  30. /// <param name="featureName">The directory name for the feature</param>
  31. /// <param name="subFeatureName">The directory name for the sub-feature</param>
  32. /// <returns>A path within the user's home directory for persisting data for the sub-feature</returns>
  33. internal static string GetUserFeatureDirectory(string featureName, string subFeatureName)
  34. {
  35. if (s_userProductDirectory == null)
  36. {
  37. EnsureUserDirectories();
  38. }
  39. return Path.Combine(s_userProductDirectory!, featureName, subFeatureName);
  40. }
  41. /// <summary>
  42. /// Get the location of where to persist information for a particular aspect of the framework,
  43. /// with a lot of hierarchy, such as ["cryptography", "x509stores", "my"]
  44. /// </summary>
  45. /// <param name="featurePathParts">A non-empty set of directories to use for the storage hierarchy</param>
  46. /// <returns>A path within the user's home directory for persisting data for the feature</returns>
  47. internal static string GetUserFeatureDirectory(params string[] featurePathParts)
  48. {
  49. Debug.Assert(featurePathParts != null);
  50. Debug.Assert(featurePathParts.Length > 0);
  51. if (s_userProductDirectory == null)
  52. {
  53. EnsureUserDirectories();
  54. }
  55. return Path.Combine(s_userProductDirectory!, Path.Combine(featurePathParts));
  56. }
  57. private static void EnsureUserDirectories()
  58. {
  59. string? userHomeDirectory = GetHomeDirectory();
  60. if (string.IsNullOrEmpty(userHomeDirectory))
  61. {
  62. throw new InvalidOperationException(SR.PersistedFiles_NoHomeDirectory);
  63. }
  64. s_userProductDirectory = Path.Combine(
  65. userHomeDirectory,
  66. TopLevelHiddenDirectory,
  67. SecondLevelDirectory);
  68. }
  69. /// <summary>Gets the current user's home directory.</summary>
  70. /// <returns>The path to the home directory, or null if it could not be determined.</returns>
  71. internal static string? GetHomeDirectory()
  72. {
  73. // First try to get the user's home directory from the HOME environment variable.
  74. // This should work in most cases.
  75. string? userHomeDirectory = Environment.GetEnvironmentVariable("HOME");
  76. if (!string.IsNullOrEmpty(userHomeDirectory))
  77. return userHomeDirectory;
  78. // In initialization conditions, however, the "HOME" environment variable may
  79. // not yet be set. For such cases, consult with the password entry.
  80. unsafe
  81. {
  82. // First try with a buffer that should suffice for 99% of cases.
  83. // Note that, theoretically, userHomeDirectory may be null in the success case
  84. // if we simply couldn't find a home directory for the current user.
  85. // In that case, we pass back the null value and let the caller decide
  86. // what to do.
  87. const int BufLen = Interop.Sys.Passwd.InitialBufferSize;
  88. byte* stackBuf = stackalloc byte[BufLen];
  89. if (TryGetHomeDirectoryFromPasswd(stackBuf, BufLen, out userHomeDirectory))
  90. return userHomeDirectory;
  91. // Fallback to heap allocations if necessary, growing the buffer until
  92. // we succeed. TryGetHomeDirectory will throw if there's an unexpected error.
  93. int lastBufLen = BufLen;
  94. while (true)
  95. {
  96. lastBufLen *= 2;
  97. byte[] heapBuf = new byte[lastBufLen];
  98. fixed (byte* buf = &heapBuf[0])
  99. {
  100. if (TryGetHomeDirectoryFromPasswd(buf, heapBuf.Length, out userHomeDirectory))
  101. return userHomeDirectory;
  102. }
  103. }
  104. }
  105. }
  106. /// <summary>Wrapper for getpwuid_r.</summary>
  107. /// <param name="buf">The scratch buffer to pass into getpwuid_r.</param>
  108. /// <param name="bufLen">The length of <paramref name="buf"/>.</param>
  109. /// <param name="path">The resulting path; null if the user didn't have an entry.</param>
  110. /// <returns>true if the call was successful (path may still be null); false is a larger buffer is needed.</returns>
  111. private static unsafe bool TryGetHomeDirectoryFromPasswd(byte* buf, int bufLen, out string? path)
  112. {
  113. // Call getpwuid_r to get the passwd struct
  114. Interop.Sys.Passwd passwd;
  115. int error = Interop.Sys.GetPwUidR(Interop.Sys.GetEUid(), out passwd, buf, bufLen);
  116. // If the call succeeds, give back the home directory path retrieved
  117. if (error == 0)
  118. {
  119. Debug.Assert(passwd.HomeDirectory != null);
  120. path = Marshal.PtrToStringAnsi((IntPtr)passwd.HomeDirectory);
  121. return true;
  122. }
  123. // If the current user's entry could not be found, give back null
  124. // path, but still return true as false indicates the buffer was
  125. // too small.
  126. if (error == -1)
  127. {
  128. path = null;
  129. return true;
  130. }
  131. var errorInfo = new Interop.ErrorInfo(error);
  132. // If the call failed because the buffer was too small, return false to
  133. // indicate the caller should try again with a larger buffer.
  134. if (errorInfo.Error == Interop.Error.ERANGE)
  135. {
  136. path = null;
  137. return false;
  138. }
  139. // Otherwise, fail.
  140. throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
  141. }
  142. }
  143. }