PersistedFiles.Unix.cs 7.0 KB

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