-
Notifications
You must be signed in to change notification settings - Fork 4.8k
/
PersistedFiles.Unix.cs
162 lines (141 loc) · 6.92 KB
/
PersistedFiles.Unix.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// 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.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 the framework,
/// such as "cryptography".
/// </summary>
/// <param name="featureName">The directory name for the feature</param>
/// <returns>A path within the user's home directory for persisting data for the feature</returns>
internal static string GetUserFeatureDirectory(string featureName)
{
if (s_userProductDirectory == null)
{
EnsureUserDirectories();
}
return Path.Combine(s_userProductDirectory!, featureName);
}
/// <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);
}
/// <summary>
/// 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"]
/// </summary>
/// <param name="featurePathParts">A non-empty set of directories to use for the storage hierarchy</param>
/// <returns>A path within the user's home directory for persisting data for the feature</returns>
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);
}
/// <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.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);
}
}
}