Exemplo n.º 1
0
        /// <summary>
        /// Adds the package-level provides to the metadata. These are basically statements
        /// indicating that the package provides, well, itself.
        /// </summary>
        /// <param name="metadata">
        /// The package to which to add the provides.
        /// </param>
        public void AddPackageProvides(RpmMetadata metadata)
        {
            var provides = metadata.Provides.ToList();

            var packageProvides = new PackageDependency(metadata.Name, RpmSense.RPMSENSE_EQUAL, $"{metadata.Version}-{metadata.Release}");

            var normalizedArch = metadata.Arch;

            if (normalizedArch == "x86_64")
            {
                normalizedArch = "x86-64";
            }

            var packageArchProvides = new PackageDependency($"{metadata.Name}({normalizedArch})", RpmSense.RPMSENSE_EQUAL, $"{metadata.Version}-{metadata.Release}");

            if (!provides.Contains(packageProvides))
            {
                provides.Add(packageProvides);
            }

            if (!provides.Contains(packageArchProvides))
            {
                provides.Add(packageArchProvides);
            }

            metadata.Provides = provides;
        }
Exemplo n.º 2
0
        /// <summary>
        /// Determines the offsets for all records in the header of a package.
        /// </summary>
        /// <param name="package">
        /// The package for which to generate the offsets.
        /// </param>
        public void CalculateHeaderOffsets(RpmPackage package)
        {
            var metadata = new RpmMetadata(package);

            metadata.ImmutableRegionSize = -1 * Marshal.SizeOf <IndexHeader>() * (package.Header.Records.Count + 1);

            this.CalculateSectionOffsets(package.Header, k => (int)k);
        }
Exemplo n.º 3
0
        /// <summary>
        /// Adds the dependency on ld to the RPM package. These dependencies cause <c>ldconfig</c> to run post installation
        /// and uninstallation of the RPM package.
        /// </summary>
        /// <param name="metadata">
        /// The <see cref="RpmMetadata"/> to which to add the dependencies.
        /// </param>
        public void AddLdDependencies(RpmMetadata metadata)
        {
            Collection <PackageDependency> ldDependencies = new Collection <PackageDependency>()
            {
                new PackageDependency("/sbin/ldconfig", RpmSense.RPMSENSE_INTERP | RpmSense.RPMSENSE_SCRIPT_POST, string.Empty),
                new PackageDependency("/sbin/ldconfig", RpmSense.RPMSENSE_INTERP | RpmSense.RPMSENSE_SCRIPT_POSTUN, string.Empty)
            };

            var dependencies = metadata.Dependencies.ToList();

            dependencies.AddRange(ldDependencies);
            metadata.Dependencies = dependencies;
        }
Exemplo n.º 4
0
        /// <summary>
        /// Adds the RPM dependencies to the package. These dependencies express dependencies on specific RPM features, such as compressed file names,
        /// file digets, and xz-compressed payloads.
        /// </summary>
        /// <param name="metadata">
        /// The <see cref="RpmMetadata"/> to which to add the dependencies.
        /// </param>
        public void AddRpmDependencies(RpmMetadata metadata, IEnumerable <PackageDependency> additionalDependencies)
        {
            // Somehow, three rpmlib dependencies come before the rtld(GNU_HASH) dependency and one after.
            // The rtld(GNU_HASH) indicates that hashes are stored in the .gnu_hash instead of the .hash section
            // in the ELF file, so it is a file-level dependency that bubbles up
            // http://lists.rpm.org/pipermail/rpm-maint/2014-September/003764.html
            // 9:34 PM 10/6/2017
            // To work around it, we remove the rtld(GNU_HASH) dependency on the files, remove it as a dependency,
            // and add it back once we're done.
            //
            // The sole purpose of this is to ensure binary compatibility, which is probably not required at runtime,
            // but makes certain regression tests more stable.
            //
            // Here we go:
            var files = metadata.Files.ToArray();

            var gnuHashFiles =
                files
                .Where(f => f.Requires.Any(r => string.Equals(r.Name, "rtld(GNU_HASH)", StringComparison.Ordinal)))
                .ToArray();

            foreach (var file in gnuHashFiles)
            {
                var rtldDependency = file.Requires.Where(r => string.Equals(r.Name, "rtld(GNU_HASH)", StringComparison.Ordinal)).Single();
                file.Requires.Remove(rtldDependency);
            }

            // Refresh
            metadata.Files = files;

            Collection <PackageDependency> rpmDependencies = new Collection <PackageDependency>()
            {
                new PackageDependency("rpmlib(CompressedFileNames)", RpmSense.RPMSENSE_LESS | RpmSense.RPMSENSE_EQUAL | RpmSense.RPMSENSE_RPMLIB, "3.0.4-1"),
                new PackageDependency("rpmlib(FileDigests)", RpmSense.RPMSENSE_LESS | RpmSense.RPMSENSE_EQUAL | RpmSense.RPMSENSE_RPMLIB, "4.6.0-1"),
                new PackageDependency("rpmlib(PayloadFilesHavePrefix)", RpmSense.RPMSENSE_LESS | RpmSense.RPMSENSE_EQUAL | RpmSense.RPMSENSE_RPMLIB, "4.0-1"),
                new PackageDependency("rtld(GNU_HASH)", RpmSense.RPMSENSE_FIND_REQUIRES, string.Empty),
            };

            // Inject any additional dependencies the user may have specified.
            if (additionalDependencies != null)
            {
                rpmDependencies.AddRange(additionalDependencies);
            }

            rpmDependencies.Add(new PackageDependency("rpmlib(PayloadIsXz)", RpmSense.RPMSENSE_LESS | RpmSense.RPMSENSE_EQUAL | RpmSense.RPMSENSE_RPMLIB, "5.2-1"));

            var dependencies = metadata.Dependencies.ToList();
            var last         = dependencies.Last();

            if (last.Name == "rtld(GNU_HASH)")
            {
                dependencies.Remove(last);
            }

            dependencies.AddRange(rpmDependencies);
            metadata.Dependencies = dependencies;

            // Add the rtld(GNU_HASH) dependency back to the files
            foreach (var file in gnuHashFiles)
            {
                file.Requires.Add(new PackageDependency("rtld(GNU_HASH)", RpmSense.RPMSENSE_FIND_REQUIRES, string.Empty));
            }

            // Refresh
            metadata.Files = files;
        }
Exemplo n.º 5
0
        /// <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="vendor">
        /// The package vendor.
        /// </param>
        /// <param name="description">
        /// The package description.
        /// </param>
        /// <param name="url">
        /// The package URL.
        /// </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 vendor,
            string description,
            string url,
            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 {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 = string.IsNullOrEmpty(description)
                ? $"{name} version {version}-{release}"
                : description;
            metadata.Summary = $"{name} version {version}-{release}";
            metadata.License = $"{name} License";

            metadata.Distribution = string.Empty;
            metadata.DistUrl      = string.Empty;
            metadata.Url          = url ?? string.Empty;
            metadata.Vendor       = 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,
                               this.CompressionThreads,
                               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,
                           XZOutputStream.DefaultThreads,
                           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);
        }