Загрузка данных


using System;
using System.Diagnostics;
using System.IO;
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())
                {
                    string? userName = GetRealUserName();

                    if (string.IsNullOrWhiteSpace(userName))
                        throw new InvalidOperationException(
                            "Приложение запущено от root, но не удалось определить обычного пользователя для запуска KWrite"
                        );

                    StartKWriteAsUser(userName, adminFilePath);
                }
                else
                {
                    StartKWrite(adminFilePath);
                }
            }
            catch (Exception ex)
            {
                Locator.Instance.Resolve<ILogger>()
                    .Error($"Ошибка при открытии системного файла в KWrite: {ex.Message}");
            }
        }).ConfigureAwait(false);
    }

    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(string userName, string fileName)
    {
        string userId = GetUserId(userName);

        var psi = new ProcessStartInfo
        {
            FileName = "runuser",
            UseShellExecute = false,
            CreateNoWindow = true
        };

        psi.ArgumentList.Add("-u");
        psi.ArgumentList.Add(userName);
        psi.ArgumentList.Add("--");

        psi.ArgumentList.Add("env");

        AddEnvironmentArgument(psi, "DISPLAY");
        AddEnvironmentArgument(psi, "WAYLAND_DISPLAY");
        AddEnvironmentArgument(psi, "XAUTHORITY");
        AddEnvironmentArgument(psi, "QT_QPA_PLATFORM");
        AddEnvironmentArgument(psi, "XDG_CURRENT_DESKTOP");
        AddEnvironmentArgument(psi, "KDE_FULL_SESSION");

        psi.ArgumentList.Add($"XDG_RUNTIME_DIR=/run/user/{userId}");
        psi.ArgumentList.Add($"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{userId}/bus");

        psi.ArgumentList.Add("kwrite");
        psi.ArgumentList.Add(fileName);

        Process.Start(psi);
    }

    private static void AddEnvironmentArgument(ProcessStartInfo psi, string name)
    {
        string? value = Environment.GetEnvironmentVariable(name);

        if (!string.IsNullOrWhiteSpace(value))
            psi.ArgumentList.Add($"{name}={value}");
    }

    private static string? GetRealUserName()
    {
        string? sudoUser = Environment.GetEnvironmentVariable("SUDO_USER");

        if (!string.IsNullOrWhiteSpace(sudoUser) && sudoUser != "root")
            return sudoUser;

        string? pkexecUid = Environment.GetEnvironmentVariable("PKEXEC_UID");

        if (!string.IsNullOrWhiteSpace(pkexecUid))
            return RunCommandAndGetOutput("id", "-nu", pkexecUid);

        return null;
    }

    private static string GetUserId(string userName)
    {
        string? result = RunCommandAndGetOutput("id", "-u", userName);

        if (string.IsNullOrWhiteSpace(result))
            throw new InvalidOperationException($"Не удалось определить UID пользователя {userName}");

        return result;
    }

    private static string? RunCommandAndGetOutput(string fileName, params string[] arguments)
    {
        var psi = new ProcessStartInfo
        {
            FileName = fileName,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            CreateNoWindow = true
        };

        foreach (string argument in arguments)
            psi.ArgumentList.Add(argument);

        using var process = Process.Start(psi);

        if (process == null)
            return null;

        string output = process.StandardOutput.ReadToEnd().Trim();

        process.WaitForExit();

        return process.ExitCode == 0 ? output : null;
    }
}