diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/Microsoft.DotNet.Build.Tasks.Installers.csproj b/src/Microsoft.DotNet.Build.Tasks.Installers/Microsoft.DotNet.Build.Tasks.Installers.csproj
index 1053542f595..a65bb9b4b8a 100644
--- a/src/Microsoft.DotNet.Build.Tasks.Installers/Microsoft.DotNet.Build.Tasks.Installers.csproj
+++ b/src/Microsoft.DotNet.Build.Tasks.Installers/Microsoft.DotNet.Build.Tasks.Installers.csproj
@@ -19,6 +19,11 @@
+
+
+
+
+
"any",
+ "i386" => "x86",
+ "i486" => "x86",
+ "i586" => "x86",
+ "i686" => "x86",
+ "x86_64" => "x64",
+ "armv6hl" => "arm",
+ "armv7hl" => "arm",
+ "aarch64" => "arm64",
+ _ => rpmPackageArchitecture
+ };
+ }
+
private static short GetRpmOS(OSPlatform os)
{
// See /usr/lib/rpm/rpmrc for the canonical OS mapping
@@ -204,8 +221,8 @@ public RpmPackage Build()
foreach (var script in _scripts)
{
- entries.Add(new((RpmHeaderTag)Enum.Parse(typeof(RpmHeaderTag), script.Key), RpmHeaderEntryType.String, "/bin/sh"));
- entries.Add(new((RpmHeaderTag)Enum.Parse(typeof(RpmHeaderTag), $"{script.Key}prog"), RpmHeaderEntryType.String, script.Value));
+ entries.Add(new((RpmHeaderTag)Enum.Parse(typeof(RpmHeaderTag), script.Key), RpmHeaderEntryType.String, script.Value));
+ entries.Add(new((RpmHeaderTag)Enum.Parse(typeof(RpmHeaderTag), $"{script.Key}prog"), RpmHeaderEntryType.String, "/bin/sh"));
}
MemoryStream cpioArchive = new();
diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/src/RpmHeaderTag.cs b/src/Microsoft.DotNet.Build.Tasks.Installers/src/RpmHeaderTag.cs
index e3a1352e0ea..23985d65a56 100644
--- a/src/Microsoft.DotNet.Build.Tasks.Installers/src/RpmHeaderTag.cs
+++ b/src/Microsoft.DotNet.Build.Tasks.Installers/src/RpmHeaderTag.cs
@@ -31,7 +31,9 @@ public enum RpmHeaderTag
Url = 1020,
OperatingSystem = 1021,
Architecture = 1022,
+ Prein = 1023,
Postin = 1024,
+ Preun = 1025,
Postun = 1026,
FileSizes = 1028,
FileModes = 1030,
@@ -55,7 +57,9 @@ public enum RpmHeaderTag
ChangelogTimestamp = 1080,
ChangelogName = 1081,
ChangelogText = 1082,
+ Preinprog = 1085,
Postinprog = 1086,
+ Preunprog = 1087,
Postunprog = 1088,
FileDevices = 1095,
FileInode = 1096,
diff --git a/src/Microsoft.DotNet.SignTool.Tests/FakeSignTool.cs b/src/Microsoft.DotNet.SignTool.Tests/FakeSignTool.cs
index 893620354be..59526325d32 100644
--- a/src/Microsoft.DotNet.SignTool.Tests/FakeSignTool.cs
+++ b/src/Microsoft.DotNet.SignTool.Tests/FakeSignTool.cs
@@ -65,6 +65,11 @@ public override bool VerifySignedDeb(TaskLoggingHelper log, string filePath)
return true;
}
+ public override bool VerifySignedRpm(TaskLoggingHelper log, string filePath)
+ {
+ return true;
+ }
+
public override bool VerifySignedPowerShellFile(string filePath)
{
return true;
diff --git a/src/Microsoft.DotNet.SignTool.Tests/Resources/test.rpm b/src/Microsoft.DotNet.SignTool.Tests/Resources/test.rpm
new file mode 100644
index 00000000000..25f662be03a
Binary files /dev/null and b/src/Microsoft.DotNet.SignTool.Tests/Resources/test.rpm differ
diff --git a/src/Microsoft.DotNet.SignTool.Tests/SignToolTests.cs b/src/Microsoft.DotNet.SignTool.Tests/SignToolTests.cs
index 8a47b644f4c..b3595c36452 100644
--- a/src/Microsoft.DotNet.SignTool.Tests/SignToolTests.cs
+++ b/src/Microsoft.DotNet.SignTool.Tests/SignToolTests.cs
@@ -15,6 +15,7 @@
using Microsoft.Build.Utilities;
using Xunit;
using Xunit.Abstractions;
+using Microsoft.DotNet.Build.Tasks.Installers;
namespace Microsoft.DotNet.SignTool.Tests
{
@@ -34,6 +35,7 @@ public class SignToolTests : IDisposable
{".psc1", new List{ new SignInfo("PSCCertificate") } },
{".dylib", new List{ new SignInfo("DylibCertificate") } },
{".deb", new List{ new SignInfo("LinuxSign") } },
+ {".rpm", new List{ new SignInfo("LinuxSign") } },
{".dll", new List{ new SignInfo("Microsoft400") } }, // lgtm [cs/common-default-passwords] Safe, these are certificate names
{".exe", new List{ new SignInfo("Microsoft400") } }, // lgtm [cs/common-default-passwords] Safe, these are certificate names
{".msi", new List{ new SignInfo("Microsoft400") } }, // lgtm [cs/common-default-passwords] Safe, these are certificate names
@@ -447,6 +449,60 @@ private string ExtractArchiveFromDebPackage(string debianPackage, string archive
File.WriteAllBytes(archive, ((MemoryStream)content).ToArray());
return archive;
}
+
+ private void ValidateProducedRpmContent(
+ string rpmPackage,
+ (string, string)[] expectedFilesOriginalHashes,
+ string[] signableFiles,
+ string originalUncompressedPayloadChecksum)
+ {
+ string tempDir = Path.Combine(_tmpDir, "verification");
+ Directory.CreateDirectory(tempDir);
+
+ string layout = Path.Combine(tempDir, "layout");
+ Directory.CreateDirectory(layout);
+
+ ZipData.ExtractRpmPayloadContents(rpmPackage, layout);
+
+ // Checks:
+ // Expected files are present
+ // Signed files have hashes different than original
+ foreach ((string targetSystemFilePath, string originalHash) in expectedFilesOriginalHashes)
+ {
+ string layoutFilePath = Path.Combine(layout, targetSystemFilePath);
+ File.Exists(layoutFilePath).Should().BeTrue();
+
+ using MD5 md5 = MD5.Create(); // lgtm [cs/weak-crypto] Azure Storage specifies use of MD5
+ using FileStream fileStream = File.OpenRead(layoutFilePath);
+ string newHash = Convert.ToHexString(md5.ComputeHash(fileStream));
+
+ if (signableFiles.Contains(targetSystemFilePath))
+ {
+ newHash.Should().NotBe(originalHash);
+ }
+ else
+ {
+ newHash.Should().Be(originalHash);
+ }
+ }
+
+ // Checks:
+ // Header payload digest matches the hash of the payload
+ // Header payload digest is different than the hash of the original payload
+ IReadOnlyList.Entry> headerEntries = ZipData.GetRpmHeaderEntries(rpmPackage);
+ string uncompressedPayloadDigest = ((string[])headerEntries.FirstOrDefault(e => e.Tag == RpmHeaderTag.UncompressedPayloadDigest).Value)[0].ToString();
+ uncompressedPayloadDigest.Should().NotBe(originalUncompressedPayloadChecksum);
+
+ using var stream = File.Open(rpmPackage, FileMode.Open);
+ using RpmPackage package = RpmPackage.Read(stream);
+ package.ArchiveStream.Seek(0, SeekOrigin.Begin);
+ using (SHA256 sha256 = SHA256.Create())
+ {
+ byte[] hash = sha256.ComputeHash(package.ArchiveStream);
+ string checksum = Convert.ToHexString(hash).ToLower();
+ checksum.Should().Be(uncompressedPayloadDigest);
+ }
+ }
#endif
[Fact]
public void EmptySigningList()
@@ -1421,6 +1477,48 @@ public void CheckDebSigning()
ValidateProducedDebContent(Path.Combine(_tmpDir, "test.deb"), expectedFilesOriginalHashes, signableFiles, expectedControlFileContent);
}
+ [LinuxOnlyFact]
+ public void CheckRpmSigning()
+ {
+ // List of files to be considered for signing
+ var itemsToSign = new ITaskItem[]
+ {
+ new TaskItem(GetResourcePath("test.rpm"))
+ };
+
+ // Default signing information
+ var strongNameSignInfo = new Dictionary>();
+
+ // Overriding information
+ var fileSignInfo = new Dictionary();
+
+ ValidateFileSignInfos(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[]
+ {
+ "File 'mscorlib.dll' TargetFramework='.NETCoreApp,Version=v10.0' Certificate='Microsoft400'",
+ "File 'test.rpm' Certificate='LinuxSign'"
+ });
+
+ ValidateGeneratedProject(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[]
+ {
+$@"
+ Microsoft400
+",
+$@"
+ LinuxSign
+"
+ });
+
+ var expectedFilesOriginalHashes = new (string, string)[]
+ {
+ ("usr/local/bin/hello", "644981BBD6F4ED1B3CF68CD0F47981AA"),
+ ("usr/local/bin/mscorlib.dll", "B80EEBA2B8616B7C37E49B004D69BBB7")
+ };
+ string[] signableFiles = ["usr/local/bin/mscorlib.dll"];
+ string originalUncompressedPayloadChecksum = "216c2a99006d2e14d28a40c0f14a63f6462f533e89789a6f294186e0a0aad3fd";
+
+ ValidateProducedRpmContent(Path.Combine(_tmpDir, "test.rpm"), expectedFilesOriginalHashes, signableFiles, originalUncompressedPayloadChecksum);
+ }
+
[Fact]
public void VerifyDebIntegrity()
{
diff --git a/src/Microsoft.DotNet.SignTool/src/BatchSignUtil.cs b/src/Microsoft.DotNet.SignTool/src/BatchSignUtil.cs
index 7c5e6e92ed9..bab9051aee8 100644
--- a/src/Microsoft.DotNet.SignTool/src/BatchSignUtil.cs
+++ b/src/Microsoft.DotNet.SignTool/src/BatchSignUtil.cs
@@ -509,6 +509,17 @@ private void VerifyCertificates(TaskLoggingHelper log)
log.LogError($"Deb package {fileName} must be signed with a LinuxSign certificate.");
}
}
+ else if (fileName.IsRpm())
+ {
+ if (isInvalidEmptyCertificate)
+ {
+ log.LogError($"Rpm package {fileName} should have a certificate name.");
+ }
+ if (!IsLinuxSignCertificate(fileName.SignInfo.Certificate))
+ {
+ log.LogError($"Rpm package {fileName} must be signed with a LinuxSign certificate.");
+ }
+ }
else if (fileName.IsNupkg())
{
if(isInvalidEmptyCertificate)
@@ -577,8 +588,27 @@ private void VerifyAfterSign(TaskLoggingHelper log, FileSignInfo file)
{
_log.LogError($"Deb package {file.FullPath} is not signed properly.");
}
+ else
+ {
+ _log.LogMessage(MessageImportance.Low, $"Deb package {file.FullPath} is signed properly");
+ }
#endif
}
+ else if (file.IsRpm())
+ {
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ _log.LogMessage(MessageImportance.Low, $"Skipping signature verification of {file.FullPath} on non-Linux platform.");
+ }
+ else if (!_signTool.VerifySignedRpm(log, file.FullPath))
+ {
+ _log.LogError($"Rpm package {file.FullPath} is not signed properly.");
+ }
+ else
+ {
+ _log.LogMessage(MessageImportance.Low, $"Rpm package {file.FullPath} is signed properly");
+ }
+ }
else if (file.IsPowerShellScript())
{
if (!_signTool.VerifySignedPowerShellFile(file.FullPath))
diff --git a/src/Microsoft.DotNet.SignTool/src/Configuration.cs b/src/Microsoft.DotNet.SignTool/src/Configuration.cs
index c03765b1749..48f530c4676 100644
--- a/src/Microsoft.DotNet.SignTool/src/Configuration.cs
+++ b/src/Microsoft.DotNet.SignTool/src/Configuration.cs
@@ -465,7 +465,19 @@ private FileSignInfo ExtractSignInfo(
_log.LogMessage(MessageImportance.Low, $"File {file.FullPath} is signed.");
}
}
- else if(FileSignInfo.IsPowerShellScript(file.FullPath))
+ else if (FileSignInfo.IsRpm(file.FullPath))
+ {
+ isAlreadyAuthenticodeSigned = VerifySignatures.VerifySignedRpm(_log, file.FullPath);
+ if (!isAlreadyAuthenticodeSigned)
+ {
+ _log.LogMessage(MessageImportance.Low, $"File {file.FullPath} is not signed.");
+ }
+ else
+ {
+ _log.LogMessage(MessageImportance.Low, $"File {file.FullPath} is signed.");
+ }
+ }
+ else if (FileSignInfo.IsPowerShellScript(file.FullPath))
{
isAlreadyAuthenticodeSigned = VerifySignatures.VerifySignedPowerShellFile(file.FullPath);
if (!isAlreadyAuthenticodeSigned)
diff --git a/src/Microsoft.DotNet.SignTool/src/FileSignInfo.cs b/src/Microsoft.DotNet.SignTool/src/FileSignInfo.cs
index 820663130a0..c7694c1d1a7 100644
--- a/src/Microsoft.DotNet.SignTool/src/FileSignInfo.cs
+++ b/src/Microsoft.DotNet.SignTool/src/FileSignInfo.cs
@@ -24,6 +24,9 @@ internal readonly struct FileSignInfo
internal static bool IsDeb(string path)
=> Path.GetExtension(path) == ".deb";
+ internal static bool IsRpm(string path)
+ => Path.GetExtension(path) == ".rpm";
+
internal static bool IsPEFile(string path)
=> Path.GetExtension(path) == ".exe" || Path.GetExtension(path) == ".dll";
@@ -60,10 +63,12 @@ internal static bool IsPackage(string path)
=> IsVsix(path) || IsNupkg(path);
internal static bool IsZipContainer(string path)
- => IsPackage(path) || IsMPack(path) || IsZip(path) || IsTarGZip(path) || IsDeb(path);
+ => IsPackage(path) || IsMPack(path) || IsZip(path) || IsTarGZip(path) || IsDeb(path) || IsRpm(path);
internal bool IsDeb() => IsDeb(FileName);
+ internal bool IsRpm() => IsRpm(FileName);
+
internal bool IsPEFile() => IsPEFile(FileName);
internal bool IsManaged() => ContentUtil.IsManaged(FullPath);
diff --git a/src/Microsoft.DotNet.SignTool/src/RealSignTool.cs b/src/Microsoft.DotNet.SignTool/src/RealSignTool.cs
index 0ed82c06343..0113603d1e5 100644
--- a/src/Microsoft.DotNet.SignTool/src/RealSignTool.cs
+++ b/src/Microsoft.DotNet.SignTool/src/RealSignTool.cs
@@ -110,6 +110,11 @@ public override bool VerifySignedDeb(TaskLoggingHelper log, string filePath)
return VerifySignatures.VerifySignedDeb(log, filePath);
}
+ public override bool VerifySignedRpm(TaskLoggingHelper log, string filePath)
+ {
+ return VerifySignatures.VerifySignedRpm(log, filePath);
+ }
+
public override bool VerifySignedPowerShellFile(string filePath)
{
return VerifySignatures.VerifySignedPowerShellFile(filePath);
diff --git a/src/Microsoft.DotNet.SignTool/src/SignTool.cs b/src/Microsoft.DotNet.SignTool/src/SignTool.cs
index cac8b16df06..cb512121fee 100644
--- a/src/Microsoft.DotNet.SignTool/src/SignTool.cs
+++ b/src/Microsoft.DotNet.SignTool/src/SignTool.cs
@@ -33,6 +33,7 @@ internal SignTool(SignToolArgs args, TaskLoggingHelper log)
public abstract bool LocalStrongNameSign(IBuildEngine buildEngine, int round, IEnumerable files);
public abstract bool VerifySignedDeb(TaskLoggingHelper log, string filePath);
+ public abstract bool VerifySignedRpm(TaskLoggingHelper log, string filePath);
public abstract bool VerifySignedPEFile(Stream stream);
public abstract bool VerifySignedPowerShellFile(string filePath);
public abstract bool VerifySignedNugetFileMarker(string filePath);
diff --git a/src/Microsoft.DotNet.SignTool/src/SignToolConstants.cs b/src/Microsoft.DotNet.SignTool/src/SignToolConstants.cs
index 8a55ed8bc28..619bfb0a42d 100644
--- a/src/Microsoft.DotNet.SignTool/src/SignToolConstants.cs
+++ b/src/Microsoft.DotNet.SignTool/src/SignToolConstants.cs
@@ -32,7 +32,7 @@ internal static class SignToolConstants
///
/// List of known signable extensions. Copied, removing duplicates, from here:
/// https://microsoft.sharepoint.com/teams/prss/Codesign/SitePages/Signable%20Files.aspx
- /// ".deb" is not in the list linked above, but it is a known signable extension.
+ /// ".deb" and ".rpm" are not in the list linked above, but they are known signable extension.
///
public static readonly HashSet SignableExtensions = new HashSet(StringComparer.OrdinalIgnoreCase)
{
@@ -113,6 +113,7 @@ internal static class SignToolConstants
".pyd",
".deb",
+ ".rpm",
};
///
diff --git a/src/Microsoft.DotNet.SignTool/src/ValidationOnlySignTool.cs b/src/Microsoft.DotNet.SignTool/src/ValidationOnlySignTool.cs
index 4423c51089c..2e037a5c679 100644
--- a/src/Microsoft.DotNet.SignTool/src/ValidationOnlySignTool.cs
+++ b/src/Microsoft.DotNet.SignTool/src/ValidationOnlySignTool.cs
@@ -46,6 +46,9 @@ public override void RemoveStrongNameSign(string assemblyPath)
public override bool VerifySignedDeb(TaskLoggingHelper log, string filePath)
=> true;
+ public override bool VerifySignedRpm(TaskLoggingHelper log, string filePath)
+ => true;
+
public override bool VerifySignedPEFile(Stream assemblyStream)
=> true;
diff --git a/src/Microsoft.DotNet.SignTool/src/VerifySignatures.cs b/src/Microsoft.DotNet.SignTool/src/VerifySignatures.cs
index b0f28fd0697..4fda36bfd28 100644
--- a/src/Microsoft.DotNet.SignTool/src/VerifySignatures.cs
+++ b/src/Microsoft.DotNet.SignTool/src/VerifySignatures.cs
@@ -82,6 +82,13 @@ internal static bool VerifySignedDeb(TaskLoggingHelper log, string filePath)
# endif
}
+ internal static bool VerifySignedRpm(TaskLoggingHelper log, string filePath)
+ {
+ // RPM signature verification is not yet implemented
+ log.LogMessage(MessageImportance.Low, $"Skipping signature verification of {filePath} - not yet implemented.");
+ return true;
+ }
+
internal static bool VerifySignedPowerShellFile(string filePath)
{
return File.ReadLines(filePath).Any(line => line.IndexOf("# SIG # Begin Signature Block", StringComparison.OrdinalIgnoreCase) >= 0);
diff --git a/src/Microsoft.DotNet.SignTool/src/ZipData.cs b/src/Microsoft.DotNet.SignTool/src/ZipData.cs
index 3ac9ea06cd9..2893c60f3f6 100644
--- a/src/Microsoft.DotNet.SignTool/src/ZipData.cs
+++ b/src/Microsoft.DotNet.SignTool/src/ZipData.cs
@@ -12,6 +12,7 @@
using System.Data;
using System.Diagnostics;
using Microsoft.DotNet.Build.Tasks.Installers;
+using System.Runtime.InteropServices;
#if NET472
using System.IO.Packaging;
@@ -72,6 +73,19 @@ internal ZipData(FileSignInfo fileSignInfo, ImmutableDictionary
return ReadDebContainerEntries(archivePath, "data.tar");
#endif
}
+ else if (FileSignInfo.IsRpm(archivePath))
+ {
+#if NET472
+ throw new NotImplementedException("RPM signing is not supported on .NET Framework");
+#else
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ throw new NotImplementedException("RPM signing is only supported on Linux platform");
+ }
+
+ return ReadRpmContainerEntries(archivePath);
+#endif
+ }
return ReadZipEntries(archivePath);
}
@@ -102,6 +116,19 @@ public void Repack(TaskLoggingHelper log, string tempDir, string wixToolsPath, s
throw new NotImplementedException("Debian signing is not supported on .NET Framework");
#else
RepackDebContainer(log, tempDir);
+#endif
+ }
+ else if (FileSignInfo.IsRpm())
+ {
+#if NET472
+ throw new NotImplementedException("RPM signing is not supported on .NET Framework");
+#else
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ throw new NotImplementedException("RPM signing is only supported on Linux platform");
+ }
+
+ RepackRpmContainer(log, tempDir);
#endif
}
else
@@ -527,6 +554,136 @@ internal static void ExtractTarballContents(string file, string destination, boo
}
}
}
+
+ private static IEnumerable<(string relativePath, Stream content, long contentSize)> ReadRpmContainerEntries(string archivePath)
+ {
+ using var stream = File.Open(archivePath, FileMode.Open);
+ using RpmPackage rpmPackage = RpmPackage.Read(stream);
+ using var dataStream = File.OpenWrite(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()));
+ using var archive = new CpioReader(rpmPackage.ArchiveStream, leaveOpen: false);
+
+ while (archive.GetNextEntry() is CpioEntry entry)
+ {
+ yield return (entry.Name, entry.DataStream, entry.DataStream.Length);
+ }
+ }
+
+ private void RepackRpmContainer(TaskLoggingHelper log, string tempDir)
+ {
+ // Unpack original package - create the layout
+ string workingDir = Path.Combine(tempDir, Guid.NewGuid().ToString().Split('-')[0]);
+ Directory.CreateDirectory(workingDir);
+ string layout = Path.Combine(workingDir, "layout");
+ Directory.CreateDirectory(layout);
+ ExtractRpmPayloadContents(FileSignInfo.FullPath, layout);
+
+ // Update signed files in layout
+ foreach (var signedPart in NestedParts.Values)
+ {
+ File.Copy(signedPart.FileSignInfo.FullPath, Path.Combine(layout, signedPart.RelativeName), overwrite: true);
+ }
+
+ // Create payload.cpio
+ string payload = Path.Combine(workingDir, "payload.cpio");
+
+ RunExternalProcess("bash", $"-c \"find . -depth ! -wholename '.' -print | cpio -H newc -o --quiet > '{payload}'\"", out string _, layout);
+
+ // Collect file types for all files in layout
+ RunExternalProcess("bash", $"-c \"find . -depth ! -wholename '.' -exec file {{}} \\;\"", out string output, layout);
+ ITaskItem[] rawPayloadFileKinds =
+ output.Split('\n', StringSplitOptions.RemoveEmptyEntries)
+ .Select(t => new TaskItem(t))
+ .ToArray();
+
+ IReadOnlyList.Entry> headerEntries = GetRpmHeaderEntries(FileSignInfo.FullPath);
+ string[] requireNames = (string[])headerEntries.FirstOrDefault(e => e.Tag == RpmHeaderTag.RequireName).Value;
+ string[] requireVersions = (string[])headerEntries.FirstOrDefault(e => e.Tag == RpmHeaderTag.RequireVersion).Value;
+ string[] changelogLines = (string[])headerEntries.FirstOrDefault(e => e.Tag == RpmHeaderTag.ChangelogText).Value;
+ string[] conflictNames = (string[])headerEntries.FirstOrDefault(e => e.Tag == RpmHeaderTag.ConflictName).Value;
+
+ List scripts = [];
+ foreach (var scriptTag in new[] { RpmHeaderTag.Prein, RpmHeaderTag.Preun, RpmHeaderTag.Postin, RpmHeaderTag.Postun })
+ {
+ string contents = (string)headerEntries.FirstOrDefault(e => e.Tag == scriptTag).Value;
+ if (contents != null)
+ {
+ string kind = Enum.GetName(scriptTag);
+ string file = Path.Combine(workingDir, kind);
+ File.WriteAllText(file, contents);
+ scripts.Add(new TaskItem(file, new Dictionary { { "Kind", kind } }));
+ }
+ }
+
+ // Create RPM package
+ CreateRpmPackage createRpmPackageTask = new()
+ {
+ OutputRpmPackagePath = FileSignInfo.FullPath,
+ Vendor = headerEntries.FirstOrDefault(e => e.Tag == RpmHeaderTag.Vendor).Value.ToString(),
+ Packager = headerEntries.FirstOrDefault(e => e.Tag == RpmHeaderTag.Packager).Value.ToString(),
+ PackageName = headerEntries.FirstOrDefault(e => e.Tag == RpmHeaderTag.PackageName).Value.ToString(),
+ PackageVersion = headerEntries.FirstOrDefault(e => e.Tag == RpmHeaderTag.PackageVersion).Value.ToString(),
+ PackageRelease = headerEntries.FirstOrDefault(e => e.Tag == RpmHeaderTag.PackageRelease).Value.ToString(),
+ PackageOS = headerEntries.FirstOrDefault(e => e.Tag == RpmHeaderTag.OperatingSystem).Value.ToString(),
+ PackageArchitecture = RpmBuilder.GetDotNetArchitectureFromRpmHeaderArchitecture(headerEntries.FirstOrDefault(e => e.Tag == RpmHeaderTag.Architecture).Value.ToString()),
+ Payload = payload,
+ RawPayloadFileKinds = rawPayloadFileKinds,
+ Requires = requireNames != null ? requireNames.Zip(requireVersions, (name, version) => new TaskItem($"{name}", new Dictionary { { "Version", version } })).Where(t => !t.ItemSpec.StartsWith("rpmlib")).ToArray() : [],
+ Conflicts = conflictNames != null ? conflictNames.Select(c => new TaskItem(c)).ToArray() : [],
+ OwnedDirectories = ((string[])headerEntries.FirstOrDefault(e => e.Tag == RpmHeaderTag.DirectoryNames).Value).Select(d => new TaskItem(d)).ToArray(),
+ ChangelogLines = changelogLines != null ? changelogLines.Select(c => new TaskItem(c)).ToArray() : [],
+ License = headerEntries.FirstOrDefault(e => e.Tag == RpmHeaderTag.License).Value.ToString(),
+ Summary = headerEntries.FirstOrDefault(e => e.Tag == RpmHeaderTag.Summary).Value.ToString(),
+ Description = headerEntries.FirstOrDefault(e => e.Tag == RpmHeaderTag.Description).Value.ToString(),
+ PackageUrl = headerEntries.FirstOrDefault(e => e.Tag == RpmHeaderTag.Url).Value.ToString(),
+ Scripts = scripts.ToArray(),
+ };
+
+ if (!createRpmPackageTask.Execute())
+ {
+ throw new Exception($"Failed to create RPM package: {FileSignInfo.FileName}");
+ }
+ }
+
+ internal static IReadOnlyList.Entry> GetRpmHeaderEntries(string rpmPackage)
+ {
+ using var stream = File.Open(rpmPackage, FileMode.Open);
+ return RpmPackage.Read(stream).Header.Entries;
+ }
+
+ internal static void ExtractRpmPayloadContents(string rpmPackage, string layout)
+ {
+ foreach (var (relativePath, content, contentSize) in ReadRpmContainerEntries(rpmPackage))
+ {
+ string outputPath = Path.Combine(layout, relativePath);
+ Directory.CreateDirectory(Path.GetDirectoryName(outputPath)!);
+
+ if (content != null)
+ {
+ using FileStream outputFileStream = File.Create(outputPath);
+ content.CopyTo(outputFileStream);
+ }
+ }
+ }
+
+ private static bool RunExternalProcess(string cmd, string args, out string output, string workingDir = null)
+ {
+ ProcessStartInfo psi = new()
+ {
+ FileName = cmd,
+ Arguments = args,
+ RedirectStandardOutput = true,
+ RedirectStandardError = false,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ WorkingDirectory = workingDir
+ };
+
+ using Process process = Process.Start(psi);
+ output = process.StandardOutput.ReadToEnd();
+ process.WaitForExit();
+
+ return process.ExitCode == 0;
+ }
#endif
}
}