From 62b17203a5304e67d94048f14efea599aa71fd01 Mon Sep 17 00:00:00 2001 From: Miheichev Aleksandr Sergeevich Date: Sun, 3 May 2026 01:28:51 +0300 Subject: [PATCH] Tighten platform annotations and finalize SQLite native sync This commit refines the platform-aware bits of the .NET 10 dependency modernization stack added earlier in this PR. It introduces no new NuGet packages and does not change any pinned versions: dotnet list package --outdated against v2rayN.sln returns only the eight Avalonia 12.x packages, all of which are explicitly out of scope for this PR. ServiceLib/Common/Utils.cs: - Annotate IsLinux() with [SupportedOSPlatformGuard("linux")] and IsMacOS() with [SupportedOSPlatformGuard("macos")] so the analyzer can narrow the platform context inside Linux/macOS guards in the same way it already does for Utils.IsWindows(). - Rewrite GetSystemHosts() as a cross-platform helper. Previously it unconditionally tried to read C:\Windows\System32\drivers\etc\{hosts, hosts.ics}, which silently returned an empty dictionary on Linux/macOS. The new implementation reads /etc/hosts on those platforms, fixing UseSystemHosts=true being a silent no-op outside Windows. Behavior on Windows is unchanged (still merges hosts and hosts.ics). ServiceLib/Handler/AutoStartupHandler.cs: - Mark the Linux helpers (ClearTaskLinux, SetTaskLinux, GetHomePathLinux) with [SupportedOSPlatform("linux")] and the macOS helpers (ClearTaskOSX, SetTaskOSX, GetLaunchAgentPathMacOS, GenerateLaunchAgentPlist) with [SupportedOSPlatform("macos")] for symmetry with the Windows helpers in the same file. The dispatch in UpdateTask is already gated by Utils.IsWindows / IsLinux / IsMacOS, whose new SupportedOSPlatformGuard attributes make these annotations enforceable by the analyzer. ServiceLib/Handler/SysProxy/ProxySettingLinux.cs and ServiceLib/Handler/SysProxy/ProxySettingOSX.cs: - Mark the classes with [SupportedOSPlatform("linux")] and [SupportedOSPlatform("macos")] respectively. Both are reachable only through SysProxyHandler.UpdateSysProxy under the corresponding platform guard. ServiceLib/Manager/AppManager.cs: - Call SQLitePCL.Batteries_V2.Init() at the top of InitApp() in addition to the existing call in SQLiteHelper's static constructor. The call is idempotent, so it cooperates with that static constructor; the duplication is intentional defense-in-depth so that any future code path which reaches the database without going through SQLiteHelper (background services, tests, future utilities) will still find an initialized native provider rather than throwing "Library not initialized" at runtime. ServiceLib/Manager/CoreManager.cs: - Add a clarifying comment above the existing [SupportedOSPlatform("windows")] field-level attribute on _processJob, explaining that the attribute narrows the analyzer's platform context for every read/assign of the field and that runtime safety is preserved by the IsWindows() guard inside AddProcessJob. No code change. package-rhel-riscv.sh: - Promote the previously hard-coded SQLite amalgamation version (sqlite_year / sqlite_ver) inside build_sqlite_native_riscv64 to two env-overridable variables (SQLITE_AMALGAMATION_YEAR and SQLITE_AMALGAMATION_VER) declared at the top of the script next to SKIA_VER and HARFBUZZ_VER. - Set the defaults to 2025 / 3500400, which corresponds to SQLite 3.50.4 and matches SourceGear.sqlite3 3.50.4.5 used on the other RIDs (x64/arm64/macOS). Previously the script downloaded SQLite 3.53.0 amalgamation on RISC-V, which left RISC-V users on a different SQLite minor version than every other platform shipped from this repository. The accompanying comment block documents the mapping rule between SourceGear.sqlite3 X.Y.Z.W and the SQLITE_AMALGAMATION_* values, so the next bump can be done in two coordinated places (this script and Directory.Packages.props) without drift. The version can still be overridden per-build via environment variables for ad-hoc testing on RISC-V. Verification ------------ - dotnet restore + dotnet build v2rayN.sln -c Release: 0 errors, 1 warning (CS8625 in GlobalHotKeys submodule, pre-existing, out-of-scope of this PR). - dotnet test ServiceLib.Tests: 41/41 passed. - dotnet publish v2rayN.Desktop.csproj for linux-x64 and osx-x64, and v2rayN.csproj for win-x64: all succeed with no new warnings. - dotnet list package --outdated against v2rayN.sln: only the eight Avalonia 12.x packages appear; none are eligible while this PR stays on the Avalonia 11.x branch. --- package-rhel-riscv.sh | 25 +++++++++++++-- v2rayN/ServiceLib/Common/Utils.cs | 31 +++++++++++++++---- .../ServiceLib/Handler/AutoStartupHandler.cs | 7 +++++ .../Handler/SysProxy/ProxySettingLinux.cs | 1 + .../Handler/SysProxy/ProxySettingOSX.cs | 1 + v2rayN/ServiceLib/Manager/AppManager.cs | 7 +++++ v2rayN/ServiceLib/Manager/CoreManager.cs | 5 +++ 7 files changed, 68 insertions(+), 9 deletions(-) diff --git a/package-rhel-riscv.sh b/package-rhel-riscv.sh index fc8ed6a7..c08bace0 100644 --- a/package-rhel-riscv.sh +++ b/package-rhel-riscv.sh @@ -40,6 +40,23 @@ DOTNET_SDK_URL="${DOTNET_RISCV_BASE}/${DOTNET_RISCV_VERSION}/${DOTNET_RISCV_FILE SKIA_VER="${SKIA_VER:-3.119.2}" HARFBUZZ_VER="${HARFBUZZ_VER:-8.3.1.3}" +# SQLite amalgamation version for the linux-riscv64 native build. +# +# Keep in sync with the SourceGear.sqlite3 package referenced from +# v2rayN/Directory.Packages.props (it pins the SQLite binaries shipped on the +# x64/arm64/macOS RIDs). +# +# Mapping rule: SourceGear.sqlite3 X.Y.Z.W -> SQLite vX.Y.Z +# -> SQLITE_AMALGAMATION_VER = sprintf("%d%02d%02d00", X, Y, Z) +# -> SQLITE_AMALGAMATION_YEAR = year of the upstream SQLite release +# +# Currently: SourceGear.sqlite3 3.50.4.5 -> SQLite 3.50.4 (released 2025). +# +# Override via the environment if you need a different version on RISC-V: +# SQLITE_AMALGAMATION_YEAR=2026 SQLITE_AMALGAMATION_VER=3530000 ./package-rhel-riscv.sh +SQLITE_AMALGAMATION_YEAR="${SQLITE_AMALGAMATION_YEAR:-2025}" +SQLITE_AMALGAMATION_VER="${SQLITE_AMALGAMATION_VER:-3500400}" + # If the first argument starts with --, do not treat it as a version number if [[ "${VERSION_ARG:-}" == --* ]]; then VERSION_ARG="" @@ -111,9 +128,11 @@ build_sqlite_native_riscv64() { mkdir -p "$outdir" workdir="$(mktemp -d)" - # SQLite 3.53.0 amalgamation - sqlite_year="2026" - sqlite_ver="3530000" + # SQLite amalgamation. Version is configured at the top of this script + # (SQLITE_AMALGAMATION_YEAR / SQLITE_AMALGAMATION_VER) and must be kept in + # sync with the SourceGear.sqlite3 package version used on other RIDs. + sqlite_year="$SQLITE_AMALGAMATION_YEAR" + sqlite_ver="$SQLITE_AMALGAMATION_VER" sqlite_zip="sqlite-amalgamation-${sqlite_ver}.zip" echo "[+] Download SQLite amalgamation: ${sqlite_zip}" diff --git a/v2rayN/ServiceLib/Common/Utils.cs b/v2rayN/ServiceLib/Common/Utils.cs index 2824bd83..183a5a7c 100644 --- a/v2rayN/ServiceLib/Common/Utils.cs +++ b/v2rayN/ServiceLib/Common/Utils.cs @@ -862,17 +862,34 @@ public class Utils return systemHosts; } + /// + /// Reads and merges the system hosts file(s) for the current platform. + /// On Windows: C:\Windows\System32\drivers\etc\hosts plus + /// hosts.ics (created by Internet Connection Sharing). + /// On Linux/macOS: /etc/hosts. + /// On any other platform: returns an empty dictionary. + /// public static Dictionary GetSystemHosts() { - var hosts = GetSystemHosts(@"C:\Windows\System32\drivers\etc\hosts"); - var hostsIcs = GetSystemHosts(@"C:\Windows\System32\drivers\etc\hosts.ics"); - - foreach (var (key, value) in hostsIcs) + if (IsWindows()) { - hosts[key] = value; + var hosts = GetSystemHosts(@"C:\Windows\System32\drivers\etc\hosts"); + var hostsIcs = GetSystemHosts(@"C:\Windows\System32\drivers\etc\hosts.ics"); + + foreach (var (key, value) in hostsIcs) + { + hosts[key] = value; + } + + return hosts; } - return hosts; + if (IsLinux() || IsMacOS()) + { + return GetSystemHosts("/etc/hosts"); + } + + return new Dictionary(); } public static async Task GetCliWrapOutput(string filePath, string? arg) @@ -1117,8 +1134,10 @@ public class Utils [SupportedOSPlatformGuard("windows")] public static bool IsWindows() => OperatingSystem.IsWindows(); + [SupportedOSPlatformGuard("linux")] public static bool IsLinux() => OperatingSystem.IsLinux(); + [SupportedOSPlatformGuard("macos")] public static bool IsMacOS() => OperatingSystem.IsMacOS(); [UnsupportedOSPlatformGuard("windows")] diff --git a/v2rayN/ServiceLib/Handler/AutoStartupHandler.cs b/v2rayN/ServiceLib/Handler/AutoStartupHandler.cs index e40af63f..fd7d7fc9 100644 --- a/v2rayN/ServiceLib/Handler/AutoStartupHandler.cs +++ b/v2rayN/ServiceLib/Handler/AutoStartupHandler.cs @@ -127,6 +127,7 @@ public static class AutoStartupHandler #region Linux + [SupportedOSPlatform("linux")] private static async Task ClearTaskLinux() { try @@ -140,6 +141,7 @@ public static class AutoStartupHandler await Task.CompletedTask; } + [SupportedOSPlatform("linux")] private static async Task SetTaskLinux() { try @@ -160,6 +162,7 @@ public static class AutoStartupHandler } } + [SupportedOSPlatform("linux")] private static string GetHomePathLinux() { var homePath = Path.Combine(Utils.GetHomePath(), ".config", "autostart", $"{Global.AppName}.desktop"); @@ -171,6 +174,7 @@ public static class AutoStartupHandler #region macOS + [SupportedOSPlatform("macos")] private static async Task ClearTaskOSX() { try @@ -190,6 +194,7 @@ public static class AutoStartupHandler } } + [SupportedOSPlatform("macos")] private static async Task SetTaskOSX() { try @@ -207,6 +212,7 @@ public static class AutoStartupHandler } } + [SupportedOSPlatform("macos")] private static string GetLaunchAgentPathMacOS() { var homePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); @@ -215,6 +221,7 @@ public static class AutoStartupHandler return launchAgentPath; } + [SupportedOSPlatform("macos")] private static string GenerateLaunchAgentPlist() { var exePath = Utils.GetExePath(); diff --git a/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingLinux.cs b/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingLinux.cs index 4929c72e..14d61d9c 100644 --- a/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingLinux.cs +++ b/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingLinux.cs @@ -1,5 +1,6 @@ namespace ServiceLib.Handler.SysProxy; +[SupportedOSPlatform("linux")] public static class ProxySettingLinux { private static readonly string _proxySetFileName = $"{Global.ProxySetLinuxShellFileName.Replace(Global.NamespaceSample, "")}.sh"; diff --git a/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingOSX.cs b/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingOSX.cs index 56fbe24d..b598a06c 100644 --- a/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingOSX.cs +++ b/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingOSX.cs @@ -1,5 +1,6 @@ namespace ServiceLib.Handler.SysProxy; +[SupportedOSPlatform("macos")] public static class ProxySettingOSX { private static readonly string _proxySetFileName = $"{Global.ProxySetOSXShellFileName.Replace(Global.NamespaceSample, "")}.sh"; diff --git a/v2rayN/ServiceLib/Manager/AppManager.cs b/v2rayN/ServiceLib/Manager/AppManager.cs index 34f6b3f1..95bef61b 100644 --- a/v2rayN/ServiceLib/Manager/AppManager.cs +++ b/v2rayN/ServiceLib/Manager/AppManager.cs @@ -54,6 +54,13 @@ public sealed class AppManager public bool InitApp() { + // Initialize the native SQLite provider explicitly before any database + // access. The call is idempotent, so it cooperates with the static + // constructor in SQLiteHelper. Having it here as well ensures that any + // future code path which reaches the database without going through + // SQLiteHelper still works (e.g. background services or tests). + SQLitePCL.Batteries_V2.Init(); + if (Utils.HasWritePermission() == false) { Environment.SetEnvironmentVariable(Global.LocalAppData, "1", EnvironmentVariableTarget.Process); diff --git a/v2rayN/ServiceLib/Manager/CoreManager.cs b/v2rayN/ServiceLib/Manager/CoreManager.cs index 41d2343b..ce1f4a4f 100644 --- a/v2rayN/ServiceLib/Manager/CoreManager.cs +++ b/v2rayN/ServiceLib/Manager/CoreManager.cs @@ -8,6 +8,11 @@ public class CoreManager private static readonly Lazy _instance = new(() => new()); public static CoreManager Instance => _instance.Value; private Config _config; + + // WindowsJobService is a Windows-only type. The field-level attribute + // narrows the platform context for the analyzer in every read/assign + // location. Actual access goes through AddProcessJob, which guards the + // usage with Utils.IsWindows() so the field stays null on non-Windows. [SupportedOSPlatform("windows")] private WindowsJobService? _processJob; private ProcessService? _processService;