/// <summary>
        /// Creates a RPM Package.
        /// </summary>
        /// <param name="archiveEntries">
        /// The archive entries which make up the RPM package.
        /// </param>
        /// <param name="payloadStream">
        /// A <see cref="Stream"/> which contains the CPIO archive for the RPM package.
        /// </param>
        /// <param name="name">
        /// The name of the package.
        /// </param>
        /// <param name="version">
        /// The version of the software.
        /// </param>
        /// <param name="arch">
        /// The architecture targetted by the package.
        /// </param>
        /// <param name="release">
        /// The release version.
        /// </param>
        /// <param name="createUser">
        /// <see langword="true"/> to create a user account; otherwise, <see langword="false"/>.
        /// </param>
        /// <param name="userName">
        /// The name of the user account to create.
        /// </param>
        /// <param name="installService">
        /// <see langword="true"/> to install a system service, otherwise, <see langword="false"/>.
        /// </param>
        /// <param name="serviceName">
        /// The name of the system service to create.
        /// </param>
        /// <param name="prefix">
        /// A prefix to use.
        /// </param>
        /// <param name="preInstallScript">
        /// Pre-Install script
        /// </param>
        /// <param name="postInstallScript">
        /// Post-Install script
        /// </param>
        /// <param name="preRemoveScript">
        /// Pre-Remove script
        /// </param>
        /// <param name="postRemoveScript">
        /// Post-Remove script
        /// </param>
        /// <param name="additionalDependencies">
        /// Additional dependencies to add to the RPM package.
        /// </param>
        /// <param name="additionalMetadata">
        /// Any additional metadata.
        /// </param>
        /// <param name="signer">
        /// The signer to use when signing the package.
        /// </param>
        /// <param name="targetStream">
        /// The <see cref="Stream"/> to which to write the package.
        /// </param>
        /// <param name="includeVersionInName">
        /// <see langword="true"/> to include the version number and release number
        /// in the <see cref="RpmLead.Name"/>; <see langword="false"/> to only
        /// use the package name.
        /// </param>
        /// <param name="payloadIsCompressed">
        /// <see langword="true"/> if <paramref name="payloadStream"/> is already
        /// compressed. In this case, the <paramref name="payloadStream"/> will be
        /// copied "as is" to the resulting RPM package.
        /// </param>
        public void CreatePackage(
            List <ArchiveEntry> archiveEntries,
            Stream payloadStream,
            string name,
            string version,
            string arch,
            string release,
            bool createUser,
            string userName,
            bool installService,
            string serviceName,
            string prefix,
            string preInstallScript,
            string postInstallScript,
            string preRemoveScript,
            string postRemoveScript,
            IEnumerable <PackageDependency> additionalDependencies,
            Action <RpmMetadata> additionalMetadata,
            IPackageSigner signer,
            Stream targetStream,
            bool includeVersionInName = false,
            bool payloadIsCompressed  = false)
        {
            // This routine goes roughly like:
            // 1. Calculate all the metadata, including a signature,
            //    but use an empty compressed payload to calculate
            //    the signature
            // 2. Write out the rpm file, and compress the payload
            // 3. Update the signature
            //
            // This way, we avoid having to compress the payload into a temporary
            // file.

            // Core routine to populate files and dependencies (part of the metadata
            // in the header)
            RpmPackage package  = new RpmPackage();
            var        metadata = new RpmMetadata(package)
            {
                Name    = name,
                Version = version,
                Arch    = arch,
                Release = release,
            };

            this.AddPackageProvides(metadata);
            this.AddLdDependencies(metadata);

            var files = this.CreateFiles(archiveEntries);

            metadata.Files = files;

            this.AddRpmDependencies(metadata, additionalDependencies);

            // Try to define valid defaults for most metadata
            metadata.Locales = new Collection <string> {
                "C"
            };                                                 // Should come before any localizable data.
            metadata.BuildHost         = "dotnet-rpm";
            metadata.BuildTime         = DateTimeOffset.Now;
            metadata.Cookie            = "dotnet-rpm";
            metadata.FileDigetsAlgo    = PgpHashAlgo.PGPHASHALGO_SHA256;
            metadata.Group             = "System Environment/Libraries";
            metadata.OptFlags          = string.Empty;
            metadata.Os                = "linux";
            metadata.PayloadCompressor = "xz";
            metadata.PayloadFlags      = "2";
            metadata.PayloadFormat     = "cpio";
            metadata.Platform          = "x86_64-redhat-linux-gnu";
            metadata.RpmVersion        = "4.11.3";
            metadata.SourcePkgId       = new byte[0x10];
            metadata.SourceRpm         = $"{name}-{version}-{release}.src.rpm";

            // Scripts which run before & after installation and removal.
            var preIn  = preInstallScript ?? string.Empty;
            var postIn = postInstallScript ?? string.Empty;
            var preUn  = preRemoveScript ?? string.Empty;
            var postUn = postRemoveScript ?? string.Empty;

            if (createUser)
            {
                // Add the user and group, under which the service runs.
                // These users are never removed because UIDs are re-used on Linux.
                preIn += $"/usr/sbin/groupadd -r {userName} 2>/dev/null || :\n" +
                         $"/usr/sbin/useradd -g {userName} -s /sbin/nologin -r -d {prefix} {userName} 2>/dev/null || :\n";
            }

            if (installService)
            {
                // Install and activate the service.
                postIn +=
                    $"if [ $1 -eq 1 ] ; then \n" +
                    $"    systemctl enable --now {serviceName}.service >/dev/null 2>&1 || : \n" +
                    $"fi\n";

                preUn +=
                    $"if [ $1 -eq 0 ] ; then \n" +
                    $"    # Package removal, not upgrade \n" +
                    $"    systemctl --no-reload disable --now {serviceName}.service > /dev/null 2>&1 || : \n" +
                    $"fi\n";

                postUn +=
                    $"if [ $1 -ge 1 ] ; then \n" +
                    $"    # Package upgrade, not uninstall \n" +
                    $"    systemctl try-restart {serviceName}.service >/dev/null 2>&1 || : \n" +
                    $"fi\n";
            }

            // Remove all directories marked as such (these are usually directories which contain temporary files)
            foreach (var entryToRemove in archiveEntries.Where(e => e.RemoveOnUninstall))
            {
                preUn +=
                    $"if [ $1 -eq 0 ] ; then \n" +
                    $"    # Package removal, not upgrade \n" +
                    $"    /usr/bin/rm -rf {entryToRemove.TargetPath}\n" +
                    $"fi\n";
            }

            if (!string.IsNullOrEmpty(preIn))
            {
                metadata.PreInProg = "/bin/sh";
                metadata.PreIn     = preIn;
            }

            if (!string.IsNullOrEmpty(postIn))
            {
                metadata.PostInProg = "/bin/sh";
                metadata.PostIn     = postIn;
            }

            if (!string.IsNullOrEmpty(preUn))
            {
                metadata.PreUnProg = "/bin/sh";
                metadata.PreUn     = preUn;
            }

            if (!string.IsNullOrEmpty(postUn))
            {
                metadata.PostUnProg = "/bin/sh";
                metadata.PostUn     = postUn;
            }

            // Not providing these (or setting empty values) would cause rpmlint errors
            metadata.Description = $"{name} version {version}-{release}";
            metadata.Summary     = $"{name} version {version}-{release}";
            metadata.License     = $"{name} License";

            metadata.Distribution = string.Empty;
            metadata.DistUrl      = string.Empty;
            metadata.Url          = string.Empty;
            metadata.Vendor       = string.Empty;

            metadata.ChangelogEntries = new Collection <ChangelogEntry>()
            {
                new ChangelogEntry(DateTimeOffset.Now, "dotnet-rpm", "Created a RPM package using dotnet-rpm")
            };

            // User-set metadata
            if (additionalMetadata != null)
            {
                additionalMetadata(metadata);
            }

            this.CalculateHeaderOffsets(package);

            using (MemoryStream dummyCompressedPayload = new MemoryStream())
            {
                using (XZOutputStream dummyPayloadCompressor = new XZOutputStream(dummyCompressedPayload, 1, XZOutputStream.DefaultPreset, leaveOpen: true))
                {
                    dummyPayloadCompressor.Write(new byte[] { 0 }, 0, 1);
                }

                this.CalculateSignature(package, signer, dummyCompressedPayload);
            }

            this.CalculateSignatureOffsets(package);

            // Write out all the data - includes the lead
            byte[] nameBytes  = new byte[66];
            var    nameInLead = includeVersionInName ? $"{name}-{version}-{release}" : name;

            Encoding.UTF8.GetBytes(nameInLead, 0, nameInLead.Length, nameBytes, 0);

            var lead = new RpmLead()
            {
                ArchNum       = 1,
                Magic         = 0xedabeedb,
                Major         = 0x03,
                Minor         = 0x00,
                NameBytes     = nameBytes,
                OsNum         = 0x0001,
                Reserved      = new byte[16],
                SignatureType = 0x0005,
                Type          = 0x0000,
            };

            // Write out the lead, signature and header
            targetStream.Position = 0;
            targetStream.SetLength(0);

            targetStream.WriteStruct(lead);
            this.WriteSignature(package, targetStream);
            this.WriteHeader(package, targetStream);

            // Write out the compressed payload
            int compressedPayloadOffset = (int)targetStream.Position;

            // The user can choose to pass an already-comrpessed
            // payload. In this case, no need to re-compress.
            if (payloadIsCompressed)
            {
                payloadStream.CopyTo(targetStream);
            }
            else
            {
                using (XZOutputStream compressor = new XZOutputStream(targetStream, 1, XZOutputStream.DefaultPreset, leaveOpen: true))
                {
                    payloadStream.Position = 0;
                    payloadStream.CopyTo(compressor);
                }
            }

            using (SubStream compressedPayloadStream = new SubStream(
                       targetStream,
                       compressedPayloadOffset,
                       targetStream.Length - compressedPayloadOffset,
                       leaveParentOpen: true,
                       readOnly: true))
            {
                this.CalculateSignature(package, signer, compressedPayloadStream);
                this.CalculateSignatureOffsets(package);
            }

            // Update the lead and signature
            targetStream.Position = 0;

            targetStream.WriteStruct(lead);
            this.WriteSignature(package, targetStream);
        }
        public override bool Execute()
        {
            this.Log.LogMessage(
                MessageImportance.High,
                "Creating DEB package '{0}' from folder '{1}'",
                this.DebPath,
                this.PublishDir);

            using (var targetStream = File.Open(this.DebPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
                using (var tarStream = File.Open(this.DebTarPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
                {
                    ArchiveBuilder archiveBuilder = new ArchiveBuilder();
                    var            archiveEntries = archiveBuilder.FromDirectory(
                        this.PublishDir,
                        this.Prefix,
                        this.Content);

                    archiveEntries.AddRange(archiveBuilder.FromLinuxFolders(this.LinuxFolders));
                    this.EnsureDirectories(archiveEntries);

                    archiveEntries = archiveEntries
                                     .OrderBy(e => e.TargetPathWithFinalSlash, StringComparer.Ordinal)
                                     .ToList();

                    TarFileCreator.FromArchiveEntries(archiveEntries, tarStream);
                    tarStream.Position = 0;

                    // Prepare the list of dependencies
                    List <string> dependencies = new List <string>();

                    if (this.DebDependencies != null)
                    {
                        var debDependencies = this.DebDependencies.Select(d => d.ItemSpec).ToArray();

                        dependencies.AddRange(debDependencies);
                    }

                    if (this.DebDotNetDependencies != null)
                    {
                        var debDotNetDependencies = this.DebDotNetDependencies.Select(d => d.ItemSpec).ToArray();

                        dependencies.AddRange(debDotNetDependencies);
                    }

                    // XZOutputStream class has low quality (doesn't even know it's current position,
                    // needs to be disposed to finish compression, etc),
                    // So we are doing compression in a separate step
                    using (var tarXzStream = File.Open(this.DebTarXzPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
                        using (var xzStream = new XZOutputStream(tarXzStream))
                        {
                            tarStream.CopyTo(xzStream);
                        }

                    using (var tarXzStream = File.Open(this.DebTarXzPath, FileMode.Open, FileAccess.Read, FileShare.None))
                    {
                        var pkg = DebPackageCreator.BuildDebPackage(
                            archiveEntries,
                            this.PackageName,
                            this.Description,
                            this.Maintainer,
                            this.Version,
                            !string.IsNullOrWhiteSpace(this.DebPackageArchitecture) ? this.DebPackageArchitecture : GetPackageArchitecture(this.RuntimeIdentifier),
                            this.CreateUser,
                            this.UserName,
                            this.InstallService,
                            this.ServiceName,
                            this.Prefix,
                            this.Section,
                            this.Priority,
                            this.Homepage,
                            this.PreInstallScript,
                            this.PostInstallScript,
                            this.PreRemoveScript,
                            this.PostRemoveScript,
                            dependencies,
                            null);

                        DebPackageCreator.WriteDebPackage(
                            archiveEntries,
                            tarXzStream,
                            targetStream,
                            pkg);
                    }

                    this.Log.LogMessage(
                        MessageImportance.High,
                        "Created DEB package '{0}' from folder '{1}'",
                        this.DebPath,
                        this.PublishDir);

                    return(true);
                }
        }