Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit b3451ec

Browse files
committed
Merge pull request #2889 from stephentoub/home_directory_unix
Fix access to user's home directory on Unix
2 parents ce294a3 + 5b8f55c commit b3451ec

File tree

17 files changed

+291
-29
lines changed

17 files changed

+291
-29
lines changed

src/Common/src/Interop/Unix/libc/Interop.getegid.cs renamed to src/Common/src/Interop/Unix/System.Native/Interop.GetEGid.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
internal static partial class Interop
88
{
9-
internal static partial class libc
9+
internal static partial class Sys
1010
{
11-
[DllImport(Libraries.Libc)]
12-
internal static extern int getegid();
11+
[DllImport(Libraries.SystemNative)]
12+
internal static extern int GetEGid();
1313
}
1414
}

src/Common/src/Interop/Unix/libc/Interop.geteuid.cs renamed to src/Common/src/Interop/Unix/System.Native/Interop.GetEUid.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
internal static partial class Interop
88
{
9-
internal static partial class libc
9+
internal static partial class Sys
1010
{
11-
[DllImport(Libraries.Libc)]
12-
internal static extern int geteuid();
11+
[DllImport(Libraries.SystemNative)]
12+
internal static extern int GetEUid();
1313
}
1414
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
internal static partial class Interop
8+
{
9+
internal static partial class Sys
10+
{
11+
internal unsafe struct Passwd
12+
{
13+
internal byte* Name;
14+
internal byte* Password;
15+
internal int UserId;
16+
internal int GroupId;
17+
internal byte* UserInfo;
18+
internal byte* HomeDirectory;
19+
internal byte* Shell;
20+
};
21+
22+
[DllImport(Libraries.SystemNative, SetLastError = true)]
23+
internal static extern unsafe int GetPwUid(int uid, out Passwd pwd, byte* buf, long bufLen, out IntPtr result);
24+
}
25+
}

src/Common/src/System/IO/PersistedFiles.Unix.cs

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System.Diagnostics;
5+
using System.Runtime.InteropServices;
56

67
namespace System.IO
78
{
@@ -81,8 +82,7 @@ internal static string GetUserFeatureDirectory(params string[] featurePathParts)
8182

8283
private static void EnsureUserDirectories()
8384
{
84-
// TODO (2820): Use getpwuid_r(geteuid(), ...) instead of the HOME environment variable
85-
string userHomeDirectory = Environment.GetEnvironmentVariable("HOME");
85+
string userHomeDirectory = GetHomeDirectory();
8686

8787
if (string.IsNullOrEmpty(userHomeDirectory))
8888
{
@@ -94,5 +94,95 @@ private static void EnsureUserDirectories()
9494
TopLevelHiddenDirectory,
9595
SecondLevelDirectory);
9696
}
97+
98+
/// <summary>Gets the current user's home directory.</summary>
99+
/// <returns>The path to the home directory, or null if it could not be determined.</returns>
100+
internal static string GetHomeDirectory()
101+
{
102+
// First try to get the user's home directory from the HOME environment variable.
103+
// This should work in most cases.
104+
string userHomeDirectory = Environment.GetEnvironmentVariable("HOME");
105+
if (!string.IsNullOrEmpty(userHomeDirectory))
106+
return userHomeDirectory;
107+
108+
// In initialization conditions, however, the "HOME" enviroment variable may
109+
// not yet be set. For such cases, consult with the password entry.
110+
unsafe
111+
{
112+
// First try with a buffer that should suffice for 99% of cases.
113+
// Note that, theoretically, userHomeDirectory may be null in the success case
114+
// if we simply couldn't find a home directory for the current user.
115+
// In that case, we pass back the null value and let the caller decide
116+
// what to do.
117+
const int BufLen = 1024;
118+
byte* stackBuf = stackalloc byte[BufLen];
119+
if (TryGetHomeDirectoryFromPasswd(stackBuf, BufLen, out userHomeDirectory))
120+
return userHomeDirectory;
121+
122+
// Fallback to heap allocations if necessary, growing the buffer until
123+
// we succeed. TryGetHomeDirectory will throw if there's an unexpected error.
124+
int lastBufLen = BufLen;
125+
while (true)
126+
{
127+
lastBufLen *= 2;
128+
byte[] heapBuf = new byte[lastBufLen];
129+
fixed (byte* buf = heapBuf)
130+
{
131+
if (TryGetHomeDirectoryFromPasswd(buf, heapBuf.Length, out userHomeDirectory))
132+
return userHomeDirectory;
133+
}
134+
}
135+
}
136+
}
137+
138+
/// <summary>Wrapper for getpwuid_r.</summary>
139+
/// <param name="buf">The scratch buffer to pass into getpwuid_r.</param>
140+
/// <param name="bufLen">The length of <paramref name="buf"/>.</param>
141+
/// <param name="path">The resulting path; null if the user didn't have an entry.</param>
142+
/// <returns>true if the call was successful (path may still be null); false is a larger buffer is needed.</returns>
143+
private static unsafe bool TryGetHomeDirectoryFromPasswd(byte* buf, int bufLen, out string path)
144+
{
145+
while (true)
146+
{
147+
// Call getpwuid_r to get the passwd struct
148+
Interop.Sys.Passwd passwd;
149+
IntPtr result;
150+
int rv = Interop.Sys.GetPwUid(Interop.Sys.GetEUid(), out passwd, buf, bufLen, out result);
151+
152+
// If the call succeeds, give back the home directory path retrieved
153+
if (rv == 0)
154+
{
155+
if (result == IntPtr.Zero)
156+
{
157+
// Current user's entry could not be found
158+
path = null; // we'll still return true, as false indicates the buffer was too small
159+
}
160+
else
161+
{
162+
Debug.Assert(result == (IntPtr)(&passwd));
163+
Debug.Assert(passwd.HomeDirectory != null);
164+
path = Marshal.PtrToStringAnsi((IntPtr)passwd.HomeDirectory);
165+
}
166+
return true;
167+
}
168+
169+
// If the call failed because it was interrupted, try again.
170+
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
171+
if (errorInfo.Error == Interop.Error.EINTR)
172+
continue;
173+
174+
// If the call failed because the buffer was too small, return false to
175+
// indicate the caller should try again with a larger buffer.
176+
if (errorInfo.Error == Interop.Error.ERANGE)
177+
{
178+
path = null;
179+
return false;
180+
}
181+
182+
// Otherwise, fail.
183+
throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
184+
}
185+
}
186+
97187
}
98188
}

src/Native/System.Native/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ set(NATIVE_SOURCES
44
pal_errno.cpp
55
pal_exec.cpp
66
pal_stat.cpp
7+
pal_uid.cpp
78
)
89

910
add_library(System.Native

src/Native/System.Native/pal_uid.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
#include "../config.h"
5+
#include "pal_uid.h"
6+
7+
#include <assert.h>
8+
#include <stdlib.h>
9+
#include <unistd.h>
10+
#include <sys/types.h>
11+
#include <pwd.h>
12+
13+
/**
14+
* Gets a password structure for the given uid.
15+
* Implemented as shim to getpwuid_r(3).
16+
*
17+
* Returns 0 for success, -1 for failure. Sets errno on failure.
18+
*/
19+
extern "C"
20+
int32_t GetPwUid(
21+
int32_t uid,
22+
Passwd* pwd,
23+
char* buf,
24+
int64_t buflen,
25+
Passwd** result)
26+
{
27+
assert(pwd != nullptr);
28+
assert(buf != nullptr);
29+
assert(buflen >= 0);
30+
assert(result != nullptr);
31+
32+
struct passwd nativePwd;
33+
struct passwd* nativePwdResultPtr;
34+
int rv = getpwuid_r(uid, &nativePwd, buf, buflen, &nativePwdResultPtr);
35+
36+
// If successful, the result will be null if the user couldn't be found,
37+
// or it'll contain the address of the provided pwd structure, into
38+
// which the results are stored.
39+
if (rv == 0 && nativePwdResultPtr != nullptr)
40+
{
41+
assert(nativePwdResultPtr == &nativePwd);
42+
43+
pwd->Name = nativePwd.pw_name;
44+
pwd->Password = nativePwd.pw_passwd;
45+
pwd->UserId = nativePwd.pw_uid;
46+
pwd->GroupId = nativePwd.pw_gid;
47+
pwd->UserInfo = nativePwd.pw_gecos;
48+
pwd->HomeDirectory = nativePwd.pw_dir;
49+
pwd->Shell = nativePwd.pw_shell;
50+
*result = pwd;
51+
}
52+
else
53+
{
54+
// Make sure we zero out the results fields, as the managed
55+
// caller could be using out variables and expect them to be initialized
56+
// after the call.
57+
*pwd = { };
58+
*result = nullptr;
59+
}
60+
61+
return rv;
62+
}
63+
64+
extern "C"
65+
int32_t GetEUid()
66+
{
67+
return geteuid();
68+
}
69+
70+
extern "C"
71+
int32_t GetEGid()
72+
{
73+
return getegid();
74+
}

src/Native/System.Native/pal_uid.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
#pragma once
5+
6+
#include <stdint.h>
7+
8+
/**
9+
* Passwd struct
10+
*/
11+
struct Passwd {
12+
char* Name;
13+
char* Password;
14+
int32_t UserId;
15+
int32_t GroupId;
16+
char* UserInfo;
17+
char* HomeDirectory;
18+
char* Shell;
19+
};
20+
21+
/**
22+
* Gets a password structure for the given uid.
23+
* Implemented as shim to getpwuid_r(3).
24+
*
25+
* Returns 0 for success, -1 for failure. Sets errno on failure.
26+
*/
27+
extern "C"
28+
int32_t GetPwUid(
29+
int32_t uid,
30+
Passwd* pwd,
31+
char* buf,
32+
int64_t buflen,
33+
Passwd** result);
34+
35+
/**
36+
* Gets and returns the effective user's identity.
37+
* Implemented as shim to geteuid(2).
38+
*
39+
* Always succeeds.
40+
*/
41+
extern "C"
42+
int32_t GetEUid();
43+
44+
/**
45+
* Gets and returns the effective group's identity.
46+
* Implemented as shim to getegid(2).
47+
*
48+
* Always succeeds.
49+
*/
50+
extern "C"
51+
int32_t GetEGid();

src/System.Console/src/Resources/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,4 +198,7 @@
198198
<data name="PlatformNotSupported_GettingColor" xml:space="preserve">
199199
<value>This platform does not support getting the current color.</value>
200200
</data>
201+
<data name="PersistedFiles_NoHomeDirectory" xml:space="preserve">
202+
<value>The home directory of the current user could not be determined.</value>
203+
</data>
201204
</root>

src/System.Console/src/System.Console.csproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@
9696
<Compile Include="$(CommonPath)\Microsoft\Win32\SafeHandles\SafeFileHandle.Unix.cs">
9797
<Link>Common\Microsoft\Win32\SafeHandles\SafeFileHandle.Unix.cs</Link>
9898
</Compile>
99+
<Compile Include="$(CommonPath)\System\IO\PersistedFiles.Unix.cs">
100+
<Link>Common\System\IO\PersistedFiles.Unix.cs</Link>
101+
</Compile>
102+
<Compile Include="$(CommonPath)\System\IO\PersistedFiles.Names.Unix.cs">
103+
<Link>Common\System\IO\PersistedFiles.Names.Unix.cs</Link>
104+
</Compile>
99105
<Compile Include="$(CommonPath)\System\IO\StringBuilderCache.cs">
100106
<Link>Common\System\IO\StringBuilderCache.cs</Link>
101107
</Compile>
@@ -132,6 +138,12 @@
132138
<Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.snprintf.cs">
133139
<Link>Common\Interop\Unix\Interop.snprintf.cs</Link>
134140
</Compile>
141+
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.GetEUid.cs">
142+
<Link>Common\Interop\Unix\Interop.GetEUid.cs</Link>
143+
</Compile>
144+
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.GetPwUid.cs">
145+
<Link>Common\Interop\Unix\Interop.GetPwUid.cs</Link>
146+
</Compile>
135147
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.Stat.cs">
136148
<Link>Common\Interop\Unix\Interop.Stat.cs</Link>
137149
</Compile>

src/System.Console/src/System/ConsolePal.Unix.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ private static Database ReadDatabase(string term)
490490
}
491491

492492
// Then try in the user's home directory.
493-
string home = Environment.GetEnvironmentVariable("HOME");
493+
string home = PersistedFiles.GetHomeDirectory();
494494
if (!string.IsNullOrWhiteSpace(home) && (db = ReadDatabase(term, home + "/.terminfo")) != null)
495495
{
496496
return db;

0 commit comments

Comments
 (0)