mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-17 02:51:45 -05:00
Create MS.SqlTools.Credentials project (#249)
* Initial credential service files * Clean-up hostloader * Update build scripts to build credentials archive * Move hosting files to new assembly * Add credentials files to MS.SqlTools.Credentials * Remove duplicate files * Update namespace in program.cs * Fix test build breaks * Update extensions visibility. * Remove unused resource strings * Add xproj files to SLN for appveyor builds * Fix appveyor build break in test project * Fix extensibility tests * Fix various typos in latest iteration * Add settings for Integration build * Fix codecoverage.bat to use full pdb for new projects * Fix bug when packing in folder with native images * Fix typos in xproj * Reset XLF to fix build.cmd
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
|
||||
{
|
||||
/// <summary>
|
||||
/// Simplified class to enable writing a set of credentials to/from disk
|
||||
/// </summary>
|
||||
public class CredentialsWrapper
|
||||
{
|
||||
public List<Credential> Credentials { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
|
||||
{
|
||||
|
||||
#if !WINDOWS_ONLY_BUILD
|
||||
|
||||
public class FileTokenStorage
|
||||
{
|
||||
private const int OwnerAccessMode = 384; // Permission 0600 - owner read/write, nobody else has access
|
||||
|
||||
private object lockObject = new object();
|
||||
|
||||
private string fileName;
|
||||
|
||||
public FileTokenStorage(string fileName)
|
||||
{
|
||||
Validate.IsNotNullOrEmptyString("fileName", fileName);
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public void AddEntries(IEnumerable<Credential> newEntries, IEnumerable<Credential> existingEntries)
|
||||
{
|
||||
var allEntries = existingEntries.Concat(newEntries);
|
||||
this.SaveEntries(allEntries);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
this.SaveEntries(new List<Credential>());
|
||||
}
|
||||
|
||||
public IEnumerable<Credential> LoadEntries()
|
||||
{
|
||||
if(!File.Exists(this.fileName))
|
||||
{
|
||||
return Enumerable.Empty<Credential>();
|
||||
}
|
||||
|
||||
string serializedCreds;
|
||||
lock (lockObject)
|
||||
{
|
||||
serializedCreds = File.ReadAllText(this.fileName);
|
||||
}
|
||||
|
||||
CredentialsWrapper creds = JsonConvert.DeserializeObject<CredentialsWrapper>(serializedCreds, Constants.JsonSerializerSettings);
|
||||
if(creds != null)
|
||||
{
|
||||
return creds.Credentials;
|
||||
}
|
||||
return Enumerable.Empty<Credential>();
|
||||
}
|
||||
|
||||
public void SaveEntries(IEnumerable<Credential> entries)
|
||||
{
|
||||
CredentialsWrapper credentials = new CredentialsWrapper() { Credentials = entries.ToList() };
|
||||
string serializedCreds = JsonConvert.SerializeObject(credentials, Constants.JsonSerializerSettings);
|
||||
|
||||
lock(lockObject)
|
||||
{
|
||||
WriteToFile(this.fileName, serializedCreds);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteToFile(string filePath, string fileContents)
|
||||
{
|
||||
string dir = Path.GetDirectoryName(filePath);
|
||||
if(!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
// Overwrite file, then use ChMod to ensure we have
|
||||
File.WriteAllText(filePath, fileContents);
|
||||
// set appropriate permissions so only current user can read/write
|
||||
Interop.Sys.ChMod(filePath, OwnerAccessMode);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
{
|
||||
|
||||
#if !WINDOWS_ONLY_BUILD
|
||||
|
||||
internal static partial class Interop
|
||||
{
|
||||
/// <summary>Common Unix errno error codes.</summary>
|
||||
internal enum Error
|
||||
{
|
||||
// These values were defined in src/Native/System.Native/fxerrno.h
|
||||
//
|
||||
// They compare against values obtained via Interop.Sys.GetLastError() not Marshal.GetLastWin32Error()
|
||||
// which obtains the raw errno that varies between unixes. The strong typing as an enum is meant to
|
||||
// prevent confusing the two. Casting to or from int is suspect. Use GetLastErrorInfo() if you need to
|
||||
// correlate these to the underlying platform values or obtain the corresponding error message.
|
||||
//
|
||||
|
||||
SUCCESS = 0,
|
||||
|
||||
E2BIG = 0x10001, // Argument list too long.
|
||||
EACCES = 0x10002, // Permission denied.
|
||||
EADDRINUSE = 0x10003, // Address in use.
|
||||
EADDRNOTAVAIL = 0x10004, // Address not available.
|
||||
EAFNOSUPPORT = 0x10005, // Address family not supported.
|
||||
EAGAIN = 0x10006, // Resource unavailable, try again (same value as EWOULDBLOCK),
|
||||
EALREADY = 0x10007, // Connection already in progress.
|
||||
EBADF = 0x10008, // Bad file descriptor.
|
||||
EBADMSG = 0x10009, // Bad message.
|
||||
EBUSY = 0x1000A, // Device or resource busy.
|
||||
ECANCELED = 0x1000B, // Operation canceled.
|
||||
ECHILD = 0x1000C, // No child processes.
|
||||
ECONNABORTED = 0x1000D, // Connection aborted.
|
||||
ECONNREFUSED = 0x1000E, // Connection refused.
|
||||
ECONNRESET = 0x1000F, // Connection reset.
|
||||
EDEADLK = 0x10010, // Resource deadlock would occur.
|
||||
EDESTADDRREQ = 0x10011, // Destination address required.
|
||||
EDOM = 0x10012, // Mathematics argument out of domain of function.
|
||||
EDQUOT = 0x10013, // Reserved.
|
||||
EEXIST = 0x10014, // File exists.
|
||||
EFAULT = 0x10015, // Bad address.
|
||||
EFBIG = 0x10016, // File too large.
|
||||
EHOSTUNREACH = 0x10017, // Host is unreachable.
|
||||
EIDRM = 0x10018, // Identifier removed.
|
||||
EILSEQ = 0x10019, // Illegal byte sequence.
|
||||
EINPROGRESS = 0x1001A, // Operation in progress.
|
||||
EINTR = 0x1001B, // Interrupted function.
|
||||
EINVAL = 0x1001C, // Invalid argument.
|
||||
EIO = 0x1001D, // I/O error.
|
||||
EISCONN = 0x1001E, // Socket is connected.
|
||||
EISDIR = 0x1001F, // Is a directory.
|
||||
ELOOP = 0x10020, // Too many levels of symbolic links.
|
||||
EMFILE = 0x10021, // File descriptor value too large.
|
||||
EMLINK = 0x10022, // Too many links.
|
||||
EMSGSIZE = 0x10023, // Message too large.
|
||||
EMULTIHOP = 0x10024, // Reserved.
|
||||
ENAMETOOLONG = 0x10025, // Filename too long.
|
||||
ENETDOWN = 0x10026, // Network is down.
|
||||
ENETRESET = 0x10027, // Connection aborted by network.
|
||||
ENETUNREACH = 0x10028, // Network unreachable.
|
||||
ENFILE = 0x10029, // Too many files open in system.
|
||||
ENOBUFS = 0x1002A, // No buffer space available.
|
||||
ENODEV = 0x1002C, // No such device.
|
||||
ENOENT = 0x1002D, // No such file or directory.
|
||||
ENOEXEC = 0x1002E, // Executable file format error.
|
||||
ENOLCK = 0x1002F, // No locks available.
|
||||
ENOLINK = 0x10030, // Reserved.
|
||||
ENOMEM = 0x10031, // Not enough space.
|
||||
ENOMSG = 0x10032, // No message of the desired type.
|
||||
ENOPROTOOPT = 0x10033, // Protocol not available.
|
||||
ENOSPC = 0x10034, // No space left on device.
|
||||
ENOSYS = 0x10037, // Function not supported.
|
||||
ENOTCONN = 0x10038, // The socket is not connected.
|
||||
ENOTDIR = 0x10039, // Not a directory or a symbolic link to a directory.
|
||||
ENOTEMPTY = 0x1003A, // Directory not empty.
|
||||
ENOTSOCK = 0x1003C, // Not a socket.
|
||||
ENOTSUP = 0x1003D, // Not supported (same value as EOPNOTSUP).
|
||||
ENOTTY = 0x1003E, // Inappropriate I/O control operation.
|
||||
ENXIO = 0x1003F, // No such device or address.
|
||||
EOVERFLOW = 0x10040, // Value too large to be stored in data type.
|
||||
EPERM = 0x10042, // Operation not permitted.
|
||||
EPIPE = 0x10043, // Broken pipe.
|
||||
EPROTO = 0x10044, // Protocol error.
|
||||
EPROTONOSUPPORT = 0x10045, // Protocol not supported.
|
||||
EPROTOTYPE = 0x10046, // Protocol wrong type for socket.
|
||||
ERANGE = 0x10047, // Result too large.
|
||||
EROFS = 0x10048, // Read-only file system.
|
||||
ESPIPE = 0x10049, // Invalid seek.
|
||||
ESRCH = 0x1004A, // No such process.
|
||||
ESTALE = 0x1004B, // Reserved.
|
||||
ETIMEDOUT = 0x1004D, // Connection timed out.
|
||||
ETXTBSY = 0x1004E, // Text file busy.
|
||||
EXDEV = 0x1004F, // Cross-device link.
|
||||
ESOCKTNOSUPPORT = 0x1005E, // Socket type not supported.
|
||||
EPFNOSUPPORT = 0x10060, // Protocol family not supported.
|
||||
ESHUTDOWN = 0x1006C, // Socket shutdown.
|
||||
EHOSTDOWN = 0x10070, // Host is down.
|
||||
ENODATA = 0x10071, // No data available.
|
||||
|
||||
// POSIX permits these to have the same value and we make them always equal so
|
||||
// that CoreFX cannot introduce a dependency on distinguishing between them that
|
||||
// would not work on all platforms.
|
||||
EOPNOTSUPP = ENOTSUP, // Operation not supported on socket.
|
||||
EWOULDBLOCK = EAGAIN, // Operation would block.
|
||||
}
|
||||
|
||||
|
||||
// Represents a platform-agnostic Error and underlying platform-specific errno
|
||||
internal struct ErrorInfo
|
||||
{
|
||||
private Error _error;
|
||||
private int _rawErrno;
|
||||
|
||||
internal ErrorInfo(int errno)
|
||||
{
|
||||
_error = Interop.Sys.ConvertErrorPlatformToPal(errno);
|
||||
_rawErrno = errno;
|
||||
}
|
||||
|
||||
internal ErrorInfo(Error error)
|
||||
{
|
||||
_error = error;
|
||||
_rawErrno = -1;
|
||||
}
|
||||
|
||||
internal Error Error
|
||||
{
|
||||
get { return _error; }
|
||||
}
|
||||
|
||||
internal int RawErrno
|
||||
{
|
||||
get { return _rawErrno == -1 ? (_rawErrno = Interop.Sys.ConvertErrorPalToPlatform(_error)) : _rawErrno; }
|
||||
}
|
||||
|
||||
internal string GetErrorMessage()
|
||||
{
|
||||
return Interop.Sys.StrError(RawErrno);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(
|
||||
"RawErrno: {0} Error: {1} GetErrorMessage: {2}", // No localization required; text is member names used for debugging purposes
|
||||
RawErrno, Error, GetErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
internal partial class Sys
|
||||
{
|
||||
internal static Error GetLastError()
|
||||
{
|
||||
return ConvertErrorPlatformToPal(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
internal static ErrorInfo GetLastErrorInfo()
|
||||
{
|
||||
return new ErrorInfo(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
internal static string StrError(int platformErrno)
|
||||
{
|
||||
int maxBufferLength = 1024; // should be long enough for most any UNIX error
|
||||
IntPtr buffer = Marshal.AllocHGlobal(maxBufferLength);
|
||||
try
|
||||
{
|
||||
IntPtr message = StrErrorR(platformErrno, buffer, maxBufferLength);
|
||||
|
||||
if (message == IntPtr.Zero)
|
||||
{
|
||||
// This means the buffer was not large enough, but still contains
|
||||
// as much of the error message as possible and is guaranteed to
|
||||
// be null-terminated. We're not currently resizing/retrying because
|
||||
// maxBufferLength is large enough in practice, but we could do
|
||||
// so here in the future if necessary.
|
||||
message = buffer;
|
||||
}
|
||||
|
||||
string returnMsg = Marshal.PtrToStringAnsi(message);
|
||||
return returnMsg;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Deallocate the buffer we created
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ConvertErrorPlatformToPal")]
|
||||
internal static extern Error ConvertErrorPlatformToPal(int platformErrno);
|
||||
|
||||
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ConvertErrorPalToPlatform")]
|
||||
internal static extern int ConvertErrorPalToPlatform(Error error);
|
||||
|
||||
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_StrErrorR")]
|
||||
private static extern IntPtr StrErrorR(int platformErrno, IntPtr buffer, int bufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: extension method can't be nested inside Interop class.
|
||||
internal static class InteropErrorExtensions
|
||||
{
|
||||
// Intended usage is e.g. Interop.Error.EFAIL.Info() for brevity
|
||||
// vs. new Interop.ErrorInfo(Interop.Error.EFAIL) for synthesizing
|
||||
// errors. Errors originated from the system should be obtained
|
||||
// via GetLastErrorInfo(), not GetLastError().Info() as that will
|
||||
// convert twice, which is not only inefficient but also lossy if
|
||||
// we ever encounter a raw errno that no equivalent in the Error
|
||||
// enum.
|
||||
public static Interop.ErrorInfo Info(this Interop.Error error)
|
||||
{
|
||||
return new Interop.ErrorInfo(error);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
{
|
||||
|
||||
#if !WINDOWS_ONLY_BUILD
|
||||
|
||||
internal static partial class Interop
|
||||
{
|
||||
internal static partial class Sys
|
||||
{
|
||||
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ChMod", SetLastError = true)]
|
||||
internal static extern int ChMod(string path, int mode);
|
||||
|
||||
internal struct Passwd
|
||||
{
|
||||
internal IntPtr Name; // char*
|
||||
internal IntPtr Password; // char*
|
||||
internal uint UserId;
|
||||
internal uint GroupId;
|
||||
internal IntPtr UserInfo; // char*
|
||||
internal IntPtr HomeDirectory; // char*
|
||||
internal IntPtr Shell; // char*
|
||||
};
|
||||
|
||||
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetPwUidR", SetLastError = false)]
|
||||
internal static extern int GetPwUidR(uint uid, out Passwd pwd, IntPtr buf, int bufLen);
|
||||
|
||||
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetEUid")]
|
||||
internal static extern uint GetEUid();
|
||||
|
||||
private static partial class Libraries
|
||||
{
|
||||
internal const string SystemNative = "System.Native";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
|
||||
{
|
||||
/// <summary>
|
||||
/// Store configuration struct
|
||||
/// </summary>
|
||||
internal struct StoreConfig
|
||||
{
|
||||
public string CredentialFolder { get; set; }
|
||||
public string CredentialFile { get; set; }
|
||||
public bool IsRelativeToUserHomeDir { get; set; }
|
||||
}
|
||||
|
||||
#if !WINDOWS_ONLY_BUILD
|
||||
|
||||
/// <summary>
|
||||
/// Linux implementation of the credential store.
|
||||
///
|
||||
/// <remarks>
|
||||
/// This entire implementation may need to be revised to support encryption of
|
||||
/// passwords and protection of them when loaded into memory.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
internal class LinuxCredentialStore : ICredentialStore
|
||||
{
|
||||
private string credentialFolderPath;
|
||||
private string credentialFileName;
|
||||
private FileTokenStorage storage;
|
||||
|
||||
public LinuxCredentialStore(StoreConfig config)
|
||||
{
|
||||
Validate.IsNotNull("config", config);
|
||||
Validate.IsNotNullOrEmptyString("credentialFolder", config.CredentialFolder);
|
||||
Validate.IsNotNullOrEmptyString("credentialFileName", config.CredentialFile);
|
||||
|
||||
this.credentialFolderPath = config.IsRelativeToUserHomeDir ? GetUserScopedDirectory(config.CredentialFolder) : config.CredentialFolder;
|
||||
this.credentialFileName = config.CredentialFile;
|
||||
|
||||
|
||||
string combinedPath = Path.Combine(this.credentialFolderPath, this.credentialFileName);
|
||||
storage = new FileTokenStorage(combinedPath);
|
||||
}
|
||||
|
||||
public bool DeletePassword(string credentialId)
|
||||
{
|
||||
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
|
||||
IEnumerable<Credential> creds;
|
||||
if (LoadCredentialsAndFilterById(credentialId, out creds))
|
||||
{
|
||||
storage.SaveEntries(creds);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets filtered credentials with a specific ID filtered out
|
||||
/// </summary>
|
||||
/// <returns>True if the credential to filter was removed, false if it was not found</returns>
|
||||
private bool LoadCredentialsAndFilterById(string idToFilter, out IEnumerable<Credential> creds)
|
||||
{
|
||||
bool didRemove = false;
|
||||
creds = storage.LoadEntries().Where(cred =>
|
||||
{
|
||||
if (IsCredentialMatch(idToFilter, cred))
|
||||
{
|
||||
didRemove = true;
|
||||
return false; // filter this out
|
||||
}
|
||||
return true;
|
||||
}).ToList(); // Call ToList ensures Where clause is executed so didRemove can be evaluated
|
||||
|
||||
return didRemove;
|
||||
}
|
||||
|
||||
private static bool IsCredentialMatch(string credentialId, Credential cred)
|
||||
{
|
||||
return string.Equals(credentialId, cred.CredentialId, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public bool TryGetPassword(string credentialId, out string password)
|
||||
{
|
||||
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
|
||||
Credential cred = storage.LoadEntries().FirstOrDefault(c => IsCredentialMatch(credentialId, c));
|
||||
if (cred != null)
|
||||
{
|
||||
password = cred.Password;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Else this was not found in the list
|
||||
password = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Save(Credential credential)
|
||||
{
|
||||
Credential.ValidateForSave(credential);
|
||||
|
||||
// Load the credentials, removing the existing Cred for this
|
||||
IEnumerable<Credential> creds;
|
||||
LoadCredentialsAndFilterById(credential.CredentialId, out creds);
|
||||
storage.SaveEntries(creds.Append(credential));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Internal for testing purposes only
|
||||
/// </summary>
|
||||
internal string CredentialFolderPath
|
||||
{
|
||||
get { return this.credentialFolderPath; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Concatenates a directory to the user home directory's path
|
||||
/// </summary>
|
||||
internal static string GetUserScopedDirectory(string userPath)
|
||||
{
|
||||
string homeDir = GetHomeDirectory() ?? string.Empty;
|
||||
return Path.Combine(homeDir, userPath);
|
||||
}
|
||||
|
||||
|
||||
/// <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.
|
||||
|
||||
// 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.
|
||||
return GetHomeDirectoryFromPw();
|
||||
}
|
||||
|
||||
internal static string GetHomeDirectoryFromPw()
|
||||
{
|
||||
string userHomeDirectory = null;
|
||||
const int BufLen = 1024;
|
||||
if (TryGetHomeDirectoryFromPasswd(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;
|
||||
if (TryGetHomeDirectoryFromPasswd(lastBufLen, out userHomeDirectory))
|
||||
{
|
||||
return userHomeDirectory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Wrapper for getpwuid_r.</summary>
|
||||
/// <param name="bufLen">The length of the buffer to use when storing the password result.</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 bool TryGetHomeDirectoryFromPasswd(int bufLen, out string path)
|
||||
{
|
||||
// Call getpwuid_r to get the passwd struct
|
||||
Interop.Sys.Passwd passwd;
|
||||
IntPtr buffer = Marshal.AllocHGlobal(bufLen);
|
||||
try
|
||||
{
|
||||
int error = Interop.Sys.GetPwUidR(Interop.Sys.GetEUid(), out passwd, buffer, bufLen);
|
||||
|
||||
// If the call succeeds, give back the home directory path retrieved
|
||||
if (error == 0)
|
||||
{
|
||||
Debug.Assert(passwd.HomeDirectory != IntPtr.Zero);
|
||||
path = Marshal.PtrToStringAnsi(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);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Deallocate the buffer we created
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user