Загрузка данных
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
public static class SystemFileEditor
{
[DllImport("libc")]
private static extern uint geteuid();
public static void OpenSystemFileInKWrite(string fileName)
{
_ = Task.Run(() =>
{
try
{
if (string.IsNullOrWhiteSpace(fileName))
throw new ArgumentException("Не указан путь к файлу");
string adminFilePath = ToKdeAdminPath(fileName);
if (IsRoot())
{
UserInfo? userInfo = GetRealUserInfo();
if (userInfo == null)
{
throw new InvalidOperationException(
"Приложение запущено от root, но не удалось определить обычного пользователя"
);
}
StartKWriteAsUser(userInfo, adminFilePath);
}
else
{
StartKWrite(adminFilePath);
}
}
catch (Exception ex)
{
Locator.Instance.Resolve<ILogger>()
.Error($"Ошибка при открытии системного файла в KWrite: {ex.Message}");
}
});
}
private static bool IsRoot()
{
return OperatingSystem.IsLinux() && geteuid() == 0;
}
private static string ToKdeAdminPath(string fileName)
{
string fullPath = Path.GetFullPath(fileName);
// /etc/hosts -> admin:///etc/hosts
return "admin://" + fullPath;
}
private static void StartKWrite(string fileName)
{
var psi = new ProcessStartInfo
{
FileName = "kwrite",
UseShellExecute = false,
CreateNoWindow = true
};
psi.ArgumentList.Add(fileName);
Process.Start(psi);
}
private static void StartKWriteAsUser(UserInfo userInfo, string fileName)
{
var psi = new ProcessStartInfo
{
FileName = "runuser",
UseShellExecute = false,
CreateNoWindow = true
};
psi.ArgumentList.Add("-u");
psi.ArgumentList.Add(userInfo.UserName);
psi.ArgumentList.Add("--");
psi.ArgumentList.Add("env");
AddCurrentEnvironmentArgument(psi, "DISPLAY");
AddCurrentEnvironmentArgument(psi, "WAYLAND_DISPLAY");
AddCurrentEnvironmentArgument(psi, "QT_QPA_PLATFORM");
AddCurrentEnvironmentArgument(psi, "XDG_CURRENT_DESKTOP");
AddCurrentEnvironmentArgument(psi, "KDE_FULL_SESSION");
psi.ArgumentList.Add($"HOME={userInfo.HomeDirectory}");
psi.ArgumentList.Add($"USER={userInfo.UserName}");
psi.ArgumentList.Add($"LOGNAME={userInfo.UserName}");
psi.ArgumentList.Add($"XDG_RUNTIME_DIR=/run/user/{userInfo.UserId}");
psi.ArgumentList.Add($"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{userInfo.UserId}/bus");
string xAuthority = Environment.GetEnvironmentVariable("XAUTHORITY")
?? Path.Combine(userInfo.HomeDirectory, ".Xauthority");
if (File.Exists(xAuthority))
psi.ArgumentList.Add($"XAUTHORITY={xAuthority}");
psi.ArgumentList.Add("kwrite");
psi.ArgumentList.Add(fileName);
Process.Start(psi);
}
private static void AddCurrentEnvironmentArgument(ProcessStartInfo psi, string name)
{
string? value = Environment.GetEnvironmentVariable(name);
if (!string.IsNullOrWhiteSpace(value))
psi.ArgumentList.Add($"{name}={value}");
}
private static UserInfo? GetRealUserInfo()
{
string? sudoUser = Environment.GetEnvironmentVariable("SUDO_USER");
string? sudoUid = Environment.GetEnvironmentVariable("SUDO_UID");
if (!string.IsNullOrWhiteSpace(sudoUser) &&
!string.IsNullOrWhiteSpace(sudoUid) &&
sudoUser != "root")
{
string homeDirectory = GetHomeDirectoryByUserName(sudoUser)
?? $"/home/{sudoUser}";
return new UserInfo
{
UserName = sudoUser,
UserId = sudoUid,
HomeDirectory = homeDirectory
};
}
string? pkexecUid = Environment.GetEnvironmentVariable("PKEXEC_UID");
if (!string.IsNullOrWhiteSpace(pkexecUid))
{
var userInfo = GetUserInfoByUid(pkexecUid);
if (userInfo != null)
return userInfo;
}
return null;
}
private static string? GetHomeDirectoryByUserName(string userName)
{
try
{
if (!File.Exists("/etc/passwd"))
return null;
string? line = File.ReadLines("/etc/passwd")
.FirstOrDefault(x => x.StartsWith(userName + ":", StringComparison.Ordinal));
if (line == null)
return null;
string[] parts = line.Split(':');
if (parts.Length < 6)
return null;
return parts[5];
}
catch
{
return null;
}
}
private static UserInfo? GetUserInfoByUid(string uid)
{
try
{
if (!File.Exists("/etc/passwd"))
return null;
foreach (string line in File.ReadLines("/etc/passwd"))
{
string[] parts = line.Split(':');
if (parts.Length < 6)
continue;
string userName = parts[0];
string userId = parts[2];
string homeDirectory = parts[5];
if (userId == uid && userName != "root")
{
return new UserInfo
{
UserName = userName,
UserId = userId,
HomeDirectory = homeDirectory
};
}
}
return null;
}
catch
{
return null;
}
}
private sealed class UserInfo
{
public string UserName { get; set; } = string.Empty;
public string UserId { get; set; } = string.Empty;
public string HomeDirectory { get; set; } = string.Empty;
}
}