namespace Sms.Environment.Linux; public sealed class LinuxEnvironmentVariableStore : IEnvironmentVariableStore { private const string ManagedFileName = "99-sms-task-two.conf"; private readonly string _managedFilePath; private readonly string _environmentDirectory; public LinuxEnvironmentVariableStore() : this( Path.Combine( System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile), ".config", "environment.d"), ManagedFileName) { } internal LinuxEnvironmentVariableStore(string environmentDirectory, string managedFileName) { _environmentDirectory = environmentDirectory; _managedFilePath = Path.Combine(environmentDirectory, managedFileName); } public string? Get(string name) { var fromFiles = LoadMergedFromDirectory(); if (fromFiles.TryGetValue(name, out var fileValue)) { return fileValue; } return System.Environment.GetEnvironmentVariable(name); } public string? GetUserPersistedValue(string name) { if (LoadMergedFromDirectory().TryGetValue(name, out var value)) { return value; } return null; } public void Set(string name, string value) { try { Directory.CreateDirectory(_environmentDirectory); var managed = LoadManagedFile(); managed[name] = value; WriteManagedFileAtomic(managed); } catch (Exception ex) when (ex is UnauthorizedAccessException or IOException) { throw new EnvironmentVariableStoreException( $"Failed to write environment variable '{name}' to '{_managedFilePath}'.", ex); } } public bool Exists(string name) => Get(name) is not null; public IReadOnlyDictionary GetAll(IEnumerable names) { var merged = LoadMergedFromDirectory(); var result = new Dictionary(StringComparer.Ordinal); foreach (var name in names) { if (merged.TryGetValue(name, out var value)) { result[name] = value; } else { var processValue = System.Environment.GetEnvironmentVariable(name); if (processValue is not null) { result[name] = processValue; } } } return result; } public IReadOnlyDictionary GetProcessEnvironment() => ToDictionary(System.Environment.GetEnvironmentVariables()); public IReadOnlyDictionary GetUserPersistedEnvironment() => LoadMergedFromDirectory(); public bool IsPersistedInUserStore(string name) => LoadMergedFromDirectory().ContainsKey(name); public void RemoveFromUserStore(string name) { try { if (!Directory.Exists(_environmentDirectory)) { return; } foreach (var file in Directory.EnumerateFiles(_environmentDirectory, "*.conf")) { var variables = SystemdEnvironmentFileParser.Parse(File.ReadAllText(file)); if (!variables.Remove(name)) { continue; } WriteConfFileAtomic(file, variables); } } catch (Exception ex) when (ex is UnauthorizedAccessException or IOException) { throw new EnvironmentVariableStoreException( $"Failed to remove environment variable '{name}' from '{_environmentDirectory}'.", ex); } } private Dictionary LoadManagedFile() { if (!File.Exists(_managedFilePath)) { return new Dictionary(StringComparer.Ordinal); } return SystemdEnvironmentFileParser.Parse(File.ReadAllText(_managedFilePath)); } private Dictionary LoadMergedFromDirectory() { var merged = new Dictionary(StringComparer.Ordinal); if (!Directory.Exists(_environmentDirectory)) { return merged; } foreach (var file in Directory.EnumerateFiles(_environmentDirectory, "*.conf").Order(StringComparer.Ordinal)) { var parsed = SystemdEnvironmentFileParser.Parse(File.ReadAllText(file)); foreach (var pair in parsed) { merged[pair.Key] = pair.Value; } } return merged; } private void WriteManagedFileAtomic(Dictionary variables) => WriteConfFileAtomic(_managedFilePath, variables); private static void WriteConfFileAtomic(string path, Dictionary variables) { var content = SystemdEnvironmentFileParser.Serialize(variables); var tempPath = path + ".tmp"; File.WriteAllText(tempPath, content); File.Move(tempPath, path, overwrite: true); } private static Dictionary ToDictionary(System.Collections.IDictionary source) { var result = new Dictionary(StringComparer.Ordinal); foreach (string key in source.Keys) { result[key] = source[key]?.ToString() ?? string.Empty; } return result; } }