From 36b33e7a59ec0f269a6e56e120c9dbcd5b1b9974 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 19 Feb 2024 18:01:23 +0100 Subject: [PATCH 1/3] [xaprepare] Make 7zip work with "dangerous" symlinks in ZIPs From time to time, `7zip` invoked by `xaprepare` with an error similar to: ERROR: Dangerous symbolic link path was ignored : android-ndk-r26c/toolchains/llvm/prebuilt/linux-x86_64/lib/python3.10/site-packages/lldb/lldb-argdumper : ../../../../bin/lldb-argdumper ERROR: Dangerous symbolic link path was ignored : android-ndk-r26c/toolchains/llvm/prebuilt/linux-x86_64/lib/python3.10/site-packages/lldb/_lldb.cpython-310-x86_64-linux-gnu.so : ../../../liblldb.so The problem is that this symlink is **not** a dangerous one, as it doesn't point outside the archived directory tree. This happened on mac, Windows and Linux alike. Try to work around the issue by using an undocumented `-snld` 7zip option when extracting ZIP archives. --- .../xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build-tools/xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs b/build-tools/xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs index 4e025f17239..68d038826da 100644 --- a/build-tools/xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs +++ b/build-tools/xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs @@ -38,6 +38,13 @@ public async Task Extract (string archivePath, string outputDirectory, Lis ProcessRunner runner = CreateProcessRunner ("x"); AddStandardArguments (runner); AddArguments (runner, extraArguments); + + // Ignore some "dangerous" symbolic symlinks in the ZIP archives. This allows 7zip to unpack Android NDK archives + // without error. The option appears to be undocumented, but was mentioned by the 7zip author here: + // + // https://sourceforge.net/p/sevenzip/discussion/45798/thread/187ce54fb0/ + // + runner.AddArgument ("-snld"); runner.AddQuotedArgument ($"-o{outputDirectory}"); runner.AddQuotedArgument (archivePath); From b1084618a1aa9cd81502760390382ea74c08d08a Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 19 Feb 2024 18:14:05 +0100 Subject: [PATCH 2/3] CI doesn't like it, try in a different spot? --- .../xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build-tools/xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs b/build-tools/xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs index 68d038826da..b39644adf81 100644 --- a/build-tools/xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs +++ b/build-tools/xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs @@ -36,15 +36,15 @@ public async Task Extract (string archivePath, string outputDirectory, Lis throw new ArgumentException ("must not be null or empty", nameof (outputDirectory)); ProcessRunner runner = CreateProcessRunner ("x"); - AddStandardArguments (runner); - AddArguments (runner, extraArguments); - // Ignore some "dangerous" symbolic symlinks in the ZIP archives. This allows 7zip to unpack Android NDK archives // without error. The option appears to be undocumented, but was mentioned by the 7zip author here: // // https://sourceforge.net/p/sevenzip/discussion/45798/thread/187ce54fb0/ // runner.AddArgument ("-snld"); + + AddStandardArguments (runner); + AddArguments (runner, extraArguments); runner.AddQuotedArgument ($"-o{outputDirectory}"); runner.AddQuotedArgument (archivePath); From c1358800a5888413b9a6747eae8c6b0fc7662453 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 20 Feb 2024 18:10:29 +0100 Subject: [PATCH 3/3] Check version before passing `-snld` --- .../Application/RegexProgramVersionParser.cs | 120 +++++++++--------- .../Application/SevenZipVersionParser.cs | 70 ++++++++++ .../xaprepare/Application/VersionFetchers.cs | 9 +- .../xaprepare/ToolRunners/SevenZipRunner.cs | 22 +++- .../xaprepare/xaprepare/xaprepare.csproj | 5 +- 5 files changed, 154 insertions(+), 72 deletions(-) create mode 100644 build-tools/xaprepare/xaprepare/Application/SevenZipVersionParser.cs diff --git a/build-tools/xaprepare/xaprepare/Application/RegexProgramVersionParser.cs b/build-tools/xaprepare/xaprepare/Application/RegexProgramVersionParser.cs index 90295cc8504..67dc9cdc971 100644 --- a/build-tools/xaprepare/xaprepare/Application/RegexProgramVersionParser.cs +++ b/build-tools/xaprepare/xaprepare/Application/RegexProgramVersionParser.cs @@ -12,78 +12,78 @@ namespace Xamarin.Android.Prepare /// is set to any other value only that line is taken into consideration. This is /// done to make processing less ambiguous and faster. /// - class RegexProgramVersionParser : ProgramVersionParser - { - const string VersionGroupName = "Version"; - static readonly char[] LineSeparator = new [] { '\n' }; + class RegexProgramVersionParser : ProgramVersionParser + { + public const string VersionGroupName = "Version"; + public static readonly char[] LineSeparator = new [] { '\n' }; - Regex rx; + Regex rx; - public RegexProgramVersionParser (string programName, string versionArguments, Regex regex, uint versionOutputLine = 0, Log? log = null) - : base (programName, versionArguments, versionOutputLine, log) - { - if (regex == null) - throw new ArgumentNullException (nameof (regex)); - rx = regex; - } + public RegexProgramVersionParser (string programName, string versionArguments, Regex regex, uint versionOutputLine = 0, Log? log = null) + : base (programName, versionArguments, versionOutputLine, log) + { + if (regex == null) + throw new ArgumentNullException (nameof (regex)); + rx = regex; + } - public RegexProgramVersionParser (string programName, string versionArguments, string regex, uint versionOutputLine = 0, Log? log = null) - : base (programName, versionArguments, versionOutputLine, log) - { - if (String.IsNullOrEmpty (regex)) - throw new ArgumentException ("must not be null or empty", nameof (regex)); + public RegexProgramVersionParser (string programName, string versionArguments, string regex, uint versionOutputLine = 0, Log? log = null) + : base (programName, versionArguments, versionOutputLine, log) + { + if (String.IsNullOrEmpty (regex)) + throw new ArgumentException ("must not be null or empty", nameof (regex)); - rx = new Regex (regex, RegexOptions.Compiled); - } + rx = new Regex (regex, RegexOptions.Compiled); + } - protected override string ParseVersion (string programOutput) - { - string output = programOutput.Trim (); - if (String.IsNullOrEmpty (output)) { - Log.WarningLine ($"Unable to parse version of {ProgramName} because version output was empty"); - return DefaultVersionString; - } + protected override string ParseVersion (string programOutput) + { + string output = programOutput.Trim (); + if (String.IsNullOrEmpty (output)) { + Log.WarningLine ($"Unable to parse version of {ProgramName} because version output was empty"); + return DefaultVersionString; + } - string ret = String.Empty; - string[] lines = programOutput.Split (LineSeparator); - if (VersionOutputLine > 0) { - if (lines.Length < VersionOutputLine) { - Log.WarningLine ($"Not enough lines in version output of {ProgramName}: version number was supposed to be found on line {VersionOutputLine} but there are only {lines.Length} lines"); - return DefaultVersionString; - } + string ret = String.Empty; + string[] lines = programOutput.Split (LineSeparator); + if (VersionOutputLine > 0) { + if (lines.Length < VersionOutputLine) { + Log.WarningLine ($"Not enough lines in version output of {ProgramName}: version number was supposed to be found on line {VersionOutputLine} but there are only {lines.Length} lines"); + return DefaultVersionString; + } - if (TryMatch (lines [VersionOutputLine - 1], out ret) && !String.IsNullOrEmpty (ret)) { - return ret; - } + if (TryMatch (rx, lines [VersionOutputLine - 1], out ret) && !String.IsNullOrEmpty (ret)) { + return ret; + } - return DefaultVersionString; - } + return DefaultVersionString; + } - foreach (string line in lines) { - if (TryMatch (line, out ret)) - break; - } + foreach (string line in lines) { + if (TryMatch (rx, line, out ret)) + break; + } - return ret ?? DefaultVersionString; - } + return ret ?? DefaultVersionString; + } - bool TryMatch (string line, out string version) - { - version = String.Empty; + public static bool TryMatch (Regex regex, string line, out string version) + { + version = String.Empty; - Match match = rx.Match (line); - if (!match.Success || match.Groups.Count <= 0) { - return false; - } + Match match = regex.Match (line); + if (!match.Success || match.Groups.Count <= 0) { + return false; + } - foreach (Group group in match.Groups) { - if (String.Compare (group.Name, VersionGroupName, StringComparison.OrdinalIgnoreCase) == 0) { - version = group.Value; - return true; - } - } + foreach (Group group in match.Groups) { + if (String.Compare (group.Name, VersionGroupName, StringComparison.OrdinalIgnoreCase) == 0) { + version = group.Value; + return true; + } + } - return false; - } - } + return false; + } + } } diff --git a/build-tools/xaprepare/xaprepare/Application/SevenZipVersionParser.cs b/build-tools/xaprepare/xaprepare/Application/SevenZipVersionParser.cs new file mode 100644 index 00000000000..86bc518071a --- /dev/null +++ b/build-tools/xaprepare/xaprepare/Application/SevenZipVersionParser.cs @@ -0,0 +1,70 @@ +using System; +using System.Text.RegularExpressions; + +namespace Xamarin.Android.Prepare; + +class SevenZipVersionParser : ProgramVersionParser +{ + const string VersionArgument = "--help"; + readonly Regex fallbackRegex; + readonly Regex modernRegex; + + public SevenZipVersionParser (string programName, Regex fallbackRegex, Log? log = null) + : base (programName, VersionArgument, 0, log) + { + this.fallbackRegex = fallbackRegex; + modernRegex = VersionFetchers.MakeRegex (@"^7-Zip (\(a\) ){0,1}(?[\d]+\.[\d]+)"); + } + + protected override string ParseVersion (string programOutput) + { + string output = programOutput.Trim (); + if (String.IsNullOrEmpty (output)) { + Log.WarningLine ($"Unable to parse version of {ProgramName} because version output was empty"); + return DefaultVersionString; + } + + string ret = String.Empty; + string[] lines = programOutput.Split (RegexProgramVersionParser.LineSeparator); + + // First try to find the official 7zip release version + foreach (string l in lines) { + string line = l.Trim (); + + if (line.Length == 0) { + continue; + } + + if (line.StartsWith ("7-Zip", StringComparison.OrdinalIgnoreCase)) { + // Strings of the form: + // 7-Zip 23.01 (x64) : Copyright (c) 1999-2023 Igor Pavlov : 2023-06-20 + // 7-Zip (a) 23.01 (x64) : Copyright (c) 1999-2023 Igor Pavlov : 2023-06-20 + // 7-Zip (a) 18.01 (x64) : Copyright (c) 1999-2018 Igor Pavlov : 2018-01-28 + // 7-Zip (a) 18.01 (x86) : Copyright (c) 1999-2018 Igor Pavlov : 2018-01-28 + if (RegexProgramVersionParser.TryMatch (modernRegex, line, out ret)) { + return ret; + } + } + + // Since we know we're dealing with `--help` option output, we can short-circuit things + if (line.StartsWith ("Usage:", StringComparison.OrdinalIgnoreCase)) { + break; + } + } + + // Modern version wasn't found, try again with the fallback one + foreach (string l in lines) { + string line = l.Trim (); + + if (line.Length == 0) { + continue; + } + + if (RegexProgramVersionParser.TryMatch (fallbackRegex, line, out ret)) { + return ret; + } + } + + return DefaultVersionString; + } +} diff --git a/build-tools/xaprepare/xaprepare/Application/VersionFetchers.cs b/build-tools/xaprepare/xaprepare/Application/VersionFetchers.cs index 0125ff33b06..7b0a95b639e 100644 --- a/build-tools/xaprepare/xaprepare/Application/VersionFetchers.cs +++ b/build-tools/xaprepare/xaprepare/Application/VersionFetchers.cs @@ -18,7 +18,7 @@ class VersionFetchers public Dictionary Fetchers => GetFetchers (); - static Regex MakeRegex (string regex) + internal static Regex MakeRegex (string regex) { return new Regex (regex, RegexOptions.Compiled | RegexOptions.Singleline); } @@ -30,8 +30,11 @@ static Regex MakeRegex (string regex) return fetchers; fetchers = new Dictionary (Context.Instance.OS.DefaultStringComparer) { - {"7z", "--help", MakeRegex ($"Version {StandardVersionRegex}"), 3}, - {"7za", "--help", MakeRegex ($"Version {StandardVersionRegex}"), 3}, + // Program-specific parsers + {"7z", new SevenZipVersionParser ("7z", MakeRegex ($"Version {StandardVersionRegex}"))}, + {"7za", new SevenZipVersionParser ("7za", MakeRegex ($"Version {StandardVersionRegex}"))}, + + // Regex parsers {"autoconf", "--version", StandardVersionAtEOL, 1}, {"automake", "--version", StandardVersionAtEOL, 1}, {"brew", "--version", MakeRegex ($"^Homebrew {StandardVersionRegex}"), 1}, diff --git a/build-tools/xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs b/build-tools/xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs index b39644adf81..e27b85d5b26 100644 --- a/build-tools/xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs +++ b/build-tools/xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs @@ -10,6 +10,10 @@ partial class SevenZipRunner : ToolRunner { const double DefaultTimeout = 30; // minutes static readonly Version bsoepMinVersion = new Version (15, 5); + + // Just an educated guess. The official download page had versions 19 and then 23+ available + // and the 19 one didn't support the `-snld` switch + static readonly Version snldMinVersion = new Version (20, 0); Version version; protected override string DefaultToolExecutableName => "7za"; @@ -36,12 +40,6 @@ public async Task Extract (string archivePath, string outputDirectory, Lis throw new ArgumentException ("must not be null or empty", nameof (outputDirectory)); ProcessRunner runner = CreateProcessRunner ("x"); - // Ignore some "dangerous" symbolic symlinks in the ZIP archives. This allows 7zip to unpack Android NDK archives - // without error. The option appears to be undocumented, but was mentioned by the 7zip author here: - // - // https://sourceforge.net/p/sevenzip/discussion/45798/thread/187ce54fb0/ - // - runner.AddArgument ("-snld"); AddStandardArguments (runner); AddArguments (runner, extraArguments); @@ -121,6 +119,18 @@ protected override TextWriter CreateLogSink (string? logFilePath) void AddStandardArguments (ProcessRunner runner) { + Log.DebugLine ($"7-zip standard arguments, for 7z version {version}"); + + // Ignore some "dangerous" symbolic symlinks in the ZIP archives. This allows 7zip to unpack Android NDK archives + // without error. The option appears to be undocumented, but was mentioned by the 7zip author here: + // + // https://sourceforge.net/p/sevenzip/discussion/45798/thread/187ce54fb0/ + // + if (version >= snldMinVersion) { + Log.DebugLine ("Adding option to ignore dangerous symlinks"); + runner.AddArgument ("-snld"); + } + // Disable progress indicator (doesn't appear to have any effect with some versions of 7z) runner.AddArgument ("-bd"); diff --git a/build-tools/xaprepare/xaprepare/xaprepare.csproj b/build-tools/xaprepare/xaprepare/xaprepare.csproj index 41766677fdd..ffa96b5c34c 100644 --- a/build-tools/xaprepare/xaprepare/xaprepare.csproj +++ b/build-tools/xaprepare/xaprepare/xaprepare.csproj @@ -1,17 +1,16 @@ + $(DotNetStableTargetFramework) Exe - 8.0 + $(LangVersion) Xamarin.Android.Prepare xaprepare true enable - -