pax_global_header00006660000000000000000000000064145510362600014514gustar00rootroot0000000000000052 comment=400e5c17736e61781545c173d8f73fdf2d7f0e4b golang-github-sassoftware-go-rpmutils-0.2.0/000077500000000000000000000000001455103626000210615ustar00rootroot00000000000000golang-github-sassoftware-go-rpmutils-0.2.0/.gitignore000066400000000000000000000000331455103626000230450ustar00rootroot00000000000000coverage.out *.html .*.swp golang-github-sassoftware-go-rpmutils-0.2.0/ContributorAgreement.txt000066400000000000000000000050121455103626000257620ustar00rootroot00000000000000Contributor Agreement Version 1.0 Contributions of changes to this software are accepted only when they are properly accompanied by a Contributor Agreement. The Contributor Agreement for this software is the Developer's Certificate of Origin 1.1 (DCO) as provided with and required for accepting contributions to the Linux kernel. In each change proposed to be included in this software, the developer must include a "sign-off" that denotes consent to the terms of the Developer's Certificate of Origin. The sign-off is a line of text in the description that accompanies the change, certifying that you have the right to provide the contribution to be included. For changes provided in source code control (for example, via a Git pull request) the sign-off must be included in the commit message in source code control. For changes provided in email or issue tracking, the sign-off must be included in the email or the issue, and the sign-off will be incorporated into the permanent commit message if the contribution is accepted into the official source code. If you can certify the below: Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. then you just add a line saying Signed-off-by: Random J Developer using your real name (sorry, no pseudonyms or anonymous contributions.) golang-github-sassoftware-go-rpmutils-0.2.0/LICENSE000066400000000000000000000261361455103626000220760ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. golang-github-sassoftware-go-rpmutils-0.2.0/README.md000066400000000000000000000032751455103626000223470ustar00rootroot00000000000000# Go RPM Utils [![Go Reference](https://pkg.go.dev/badge/github.com/sassoftware/go-rpmutils.svg)](https://pkg.go.dev/github.com/sassoftware/go-rpmutils) go-rpmutils is a library written in [go](http://golang.org) for parsing and extracting content from [RPMs](http://www.rpm.org). ## Overview go-rpmutils provides a few interfaces for handling RPM packages. There is a highlevel `Rpm` struct that provides access to the RPM header and [CPIO](https://en.wikipedia.org/wiki/Cpio) payload. The CPIO payload can be extracted to a filesystem location via the `ExpandPayload` function or through a Reader interface, similar to the [tar implementation](https://golang.org/pkg/archive/tar/) in the go standard library. ## Example ```go // Opening a RPM file f, err := os.Open("foo.rpm") if err != nil { panic(err) } rpm, err := rpmutils.ReadRpm(f) if err != nil { panic(err) } // Getting metadata nevra, err := rpm.Header.GetNEVRA() if err != nil { panic(err) } fmt.Println(nevra) provides, err := rpm.Header.GetStrings(rpmutils.PROVIDENAME) if err != nil { panic(err) } fmt.Println("Provides:") for _, p := range provides { fmt.Println(p) } // Extracting payload if err := rpm.ExpandPayload("destdir"); err != nil { panic(err) } ``` ## Contributing 1. Read contributor agreement 2. Fork it 3. Create your feature branch (`git checkout -b my-new-feature`) 4. Commit your changes (`git commit -a`). Make sure to include a Signed-off-by line per the contributor agreement. 5. Push to the branch (`git push origin my-new-feature`) 6. Create new Pull Request ## License go-rpmutils is released under the Apache 2.0 license. See [LICENSE](https://github.com/sassoftware/go-rpmutils/blob/master/LICENSE). golang-github-sassoftware-go-rpmutils-0.2.0/SUPPORT.md000066400000000000000000000001731455103626000225600ustar00rootroot00000000000000# Support We use GitHub for tracking bugs and feature requests. Please submit a GitHub issue or pull request for support. golang-github-sassoftware-go-rpmutils-0.2.0/cpio/000077500000000000000000000000001455103626000220135ustar00rootroot00000000000000golang-github-sassoftware-go-rpmutils-0.2.0/cpio/cpio.go000066400000000000000000000076631455103626000233100ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cpio import ( "errors" "fmt" "io" ) // TRAILER is the filename found on the last entry of a cpio archive const TRAILER = "TRAILER!!!" // ErrStrippedHeader indicates that a RPM-style archive was read without calling SetFileSizes() var ErrStrippedHeader = errors.New("invalid cpio header: rpm-style stripped cpio requires supplemental size info") // CpioEntry points to a single file within a cpio stream type CpioEntry struct { Header *Cpio_newc_header payload *file_stream } // CpioStream reads file metadata and contents from a cpio archive type CpioStream struct { stream *countingReader nextPos int64 sizes []int64 } type countingReader struct { stream io.Reader curPos int64 } // NewCpioStream starts reading files from a cpio archive func NewCpioStream(stream io.Reader) *CpioStream { return &CpioStream{ stream: &countingReader{ stream: stream, curPos: 0, }, nextPos: 0, } } // SetFileSizes provides supplemental file size info so that RPMs with files > 4GiB can be read func (cs *CpioStream) SetFileSizes(sizes []int64) { cs.sizes = sizes } // ReadNextEntry returns the metadata of the next file in the archive. // // The final file in the archive can be detected by checking for a Filename of TRAILER. func (cs *CpioStream) ReadNextEntry() (*CpioEntry, error) { if cs.nextPos != cs.stream.curPos { _, err := cs.stream.Seek(cs.nextPos-cs.stream.curPos, 1) if err != nil { return nil, err } } // Read header hdr, err := readHeader(cs.stream) if err != nil { return nil, err } else if hdr.stripped { return cs.readStrippedEntry(hdr) } // Read filename buf := make([]byte, hdr.namesize) if _, err = io.ReadFull(cs.stream, buf); err != nil { return nil, err } filename := string(buf[:len(buf)-1]) offset := pad(newcHeaderLength+int(hdr.namesize)) - newcHeaderLength - int(hdr.namesize) if offset > 0 { _, err := cs.stream.Seek(int64(offset), 1) if err != nil { return nil, err } } // Find the next entry cs.nextPos = pad64(cs.stream.curPos + int64(hdr.filesize)) // Find the payload payload, err := newFileStream(cs.stream, int64(hdr.filesize)) if err != nil { return nil, err } // Create then entry hdr.filename = filename entry := CpioEntry{ Header: hdr, payload: payload, } return &entry, nil } func (cs *CpioStream) readStrippedEntry(hdr *Cpio_newc_header) (*CpioEntry, error) { // magic has already been read if cs.sizes == nil { return nil, ErrStrippedHeader } else if hdr.index >= len(cs.sizes) { return nil, fmt.Errorf("stripped cpio refers to invalid file index %d", hdr.index) } size := cs.sizes[hdr.index] cs.nextPos = pad64(cs.stream.curPos + size) payload, err := newFileStream(cs.stream, size) if err != nil { return nil, err } return &CpioEntry{Header: hdr, payload: payload}, nil } func (cr *countingReader) Read(p []byte) (n int, err error) { n, err = cr.stream.Read(p) cr.curPos += int64(n) return } func (cr *countingReader) Seek(offset int64, whence int) (int64, error) { if whence != 1 { return 0, fmt.Errorf("only seeking from current location supported") } if offset == 0 { return cr.curPos, nil } b := make([]byte, offset) n, err := io.ReadFull(cr, b) if err != nil && err != io.EOF { return 0, err } return int64(n), nil } func pad(num int) int { return num + 3 - (num+3)%4 } func pad64(num int64) int64 { return num + 3 - (num+3)%4 } golang-github-sassoftware-go-rpmutils-0.2.0/cpio/extract.go000066400000000000000000000075441455103626000240260ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cpio import ( "fmt" "io" "os" "path" "path/filepath" "strings" "github.com/sassoftware/go-rpmutils/fileutil" ) // Standard set of permission bit masks. const ( S_ISUID = 04000 // Set uid S_ISGID = 02000 // Set gid S_ISVTX = 01000 // Save text (sticky bit) S_ISDIR = 040000 // Directory S_ISFIFO = 010000 // FIFO S_ISREG = 0100000 // Regular file S_ISLNK = 0120000 // Symbolic link S_ISBLK = 060000 // Block special file S_ISCHR = 020000 // Character special file S_ISSOCK = 0140000 // Socket ) // Extract the contents of a cpio stream from r to the destination directory dest func Extract(rs io.Reader, dest string) error { dest = filepath.Clean(filepath.FromSlash(dest)) linkMap := make(map[int][]string) stream := NewCpioStream(rs) for { entry, err := stream.ReadNextEntry() if err != nil { return err } if entry.Header.filename == TRAILER { break } // sanitize path target := path.Clean(entry.Header.filename) for strings.HasPrefix(target, "../") { target = target[3:] } target = filepath.Join(dest, filepath.FromSlash(target)) if !strings.HasPrefix(target, dest+string(filepath.Separator)) && dest != target { // this shouldn't happen due to the sanitization above but always check return fmt.Errorf("invalid cpio path %q", entry.Header.filename) } // Create the parent directory if it doesn't exist. parent := filepath.Dir(target) if err := os.MkdirAll(parent, 0755); err != nil { return err } // FIXME: Need a makedev implementation in go. switch entry.Header.Mode() &^ 07777 { case S_ISCHR: // FIXME: skipping due to lack of makedev. continue case S_ISBLK: // FIXME: skipping due to lack of makedev. continue case S_ISDIR: m := os.FileMode(entry.Header.Mode()).Perm() if err := os.Mkdir(target, m); err != nil && !os.IsExist(err) { return err } case S_ISFIFO: if err := fileutil.Mkfifo(target, uint32(entry.Header.Mode())); err != nil { return err } case S_ISLNK: buf := make([]byte, entry.Header.filesize) if _, err := entry.payload.Read(buf); err != nil { return err } if err := os.Symlink(string(buf), target); err != nil { return err } case S_ISREG: // save hardlinks until after the taget is written if entry.Header.nlink > 1 && entry.Header.filesize == 0 { l, ok := linkMap[entry.Header.ino] if !ok { l = make([]string, 0) } l = append(l, target) linkMap[entry.Header.ino] = l continue } // FIXME: Set permissions on files when creating. f, err := os.Create(target) if err != nil { return err } written, err := io.Copy(f, entry.payload) if err != nil { return err } if written != int64(entry.Header.filesize) { return fmt.Errorf("short write") } if err := f.Close(); err != nil { return err } // Create hardlinks after the file content is written. if entry.Header.nlink > 1 && entry.Header.filesize > 0 { l, ok := linkMap[entry.Header.ino] if !ok { return fmt.Errorf("hardlinks missing") } for _, t := range l { if err := os.Link(target, t); err != nil { return err } } } default: return fmt.Errorf("unknown file mode 0%o for %s", entry.Header.mode, entry.Header.filename) } } return nil } golang-github-sassoftware-go-rpmutils-0.2.0/cpio/extract_test.go000066400000000000000000000031571455103626000250610ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cpio import ( "io/ioutil" "os" "path/filepath" "testing" "testing/iotest" ) func TestExtract(t *testing.T) { f, err := os.Open("../testdata/foo.cpio") if err != nil { t.Fatal(err) } defer f.Close() tmpdir, err := ioutil.TempDir("", "cpio") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpdir) hf := iotest.HalfReader(f) if err := Extract(hf, tmpdir); err != nil { t.Fatal(err) } if f, err = os.Open("../testdata/foo.cpio"); err != nil { t.Fatal(err) } hf = iotest.HalfReader(f) if err := Extract(hf, tmpdir); err != nil { t.Fatal(err) } } func TestExtractDotdot(t *testing.T) { f, err := os.Open("../testdata/dotdot.cpio") if err != nil { t.Fatal(err) } defer f.Close() tmpdir, err := ioutil.TempDir("", "cpio") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpdir) err = Extract(f, tmpdir) if err != nil { t.Fatal(err) } if _, err := os.Stat(filepath.Join(tmpdir, "aaaaaaaaa")); err != nil { t.Error("expected file with ../ to extract into top of destdir:", err) } } golang-github-sassoftware-go-rpmutils-0.2.0/cpio/header.go000066400000000000000000000125741455103626000236030ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cpio import ( "fmt" "io" "strconv" ) // reference http://people.freebsd.org/~kientzle/libarchive/man/cpio.5.txt const ( newcHeaderLength = 110 newcMagic = "070701" strippedMagic = "07070X" ) // Cpio_newc_header is the raw header of a newc-style cpio archive type Cpio_newc_header struct { magic string ino int mode int uid int gid int nlink int mtime int filesize int devmajor int devminor int rdevmajor int rdevminor int namesize int check int stripped bool filename string index int size64 int64 } type binaryReader struct { r io.Reader buf [8]byte } func (br *binaryReader) Read16(buf *int) error { bb := br.buf[:8] if _, err := io.ReadFull(br.r, bb); err != nil { return err } i, err := strconv.ParseInt(string(bb), 16, 0) if err != nil { return err } *buf = int(i) return nil } func readHeader(r io.Reader) (*Cpio_newc_header, error) { hdr := Cpio_newc_header{} br := binaryReader{r: r} magic := make([]byte, 6) if _, err := io.ReadFull(r, magic); err != nil { return nil, err } if string(magic) == strippedMagic { return readStrippedHeader(br) } else if string(magic) != newcMagic { return nil, fmt.Errorf("bad magic") } hdr.magic = newcMagic if err := br.Read16(&hdr.ino); err != nil { return nil, err } if err := br.Read16(&hdr.mode); err != nil { return nil, err } if err := br.Read16(&hdr.uid); err != nil { return nil, err } if err := br.Read16(&hdr.gid); err != nil { return nil, err } if err := br.Read16(&hdr.nlink); err != nil { return nil, err } if err := br.Read16(&hdr.mtime); err != nil { return nil, err } if err := br.Read16(&hdr.filesize); err != nil { return nil, err } hdr.size64 = int64(hdr.filesize) if err := br.Read16(&hdr.devmajor); err != nil { return nil, err } if err := br.Read16(&hdr.devminor); err != nil { return nil, err } if err := br.Read16(&hdr.rdevmajor); err != nil { return nil, err } if err := br.Read16(&hdr.rdevminor); err != nil { return nil, err } if err := br.Read16(&hdr.namesize); err != nil { return nil, err } if err := br.Read16(&hdr.check); err != nil { return nil, err } return &hdr, nil } func readStrippedHeader(br binaryReader) (*Cpio_newc_header, error) { hdr := &Cpio_newc_header{ magic: strippedMagic, stripped: true, } if err := br.Read16(&hdr.index); err != nil { return nil, err } return hdr, nil } // Magic returns the magic number preceding the file entry func (hdr *Cpio_newc_header) Magic() string { return hdr.magic } // Ino returns the inode number of the file func (hdr *Cpio_newc_header) Ino() int { return hdr.ino } // Mode returns the file's permissions and file type func (hdr *Cpio_newc_header) Mode() int { return hdr.mode } // Uid returns the file's owner user ID func (hdr *Cpio_newc_header) Uid() int { return hdr.uid } // Gid returns the file's owner group ID func (hdr *Cpio_newc_header) Gid() int { return hdr.gid } // Nlink returns the number of hardlinks to the file func (hdr *Cpio_newc_header) Nlink() int { return hdr.nlink } // Mtime returns the file's modification time in seconds since the UNIX epoch func (hdr *Cpio_newc_header) Mtime() int { return hdr.mtime } // Filesize returns the size of the file in bytes func (hdr *Cpio_newc_header) Filesize() int { return hdr.filesize } // Devmajor returns the major device number of a character or block device func (hdr *Cpio_newc_header) Devmajor() int { return hdr.devmajor } // Devminor returns the minor device number of a character or block device func (hdr *Cpio_newc_header) Devminor() int { return hdr.devminor } // Rdevmajor returns the major device number of a character or block device func (hdr *Cpio_newc_header) Rdevmajor() int { return hdr.rdevmajor } // Rdevminor returns the minor device number of a character or block device func (hdr *Cpio_newc_header) Rdevminor() int { return hdr.rdevminor } // Namesize returns the length of the filename func (hdr *Cpio_newc_header) Namesize() int { return hdr.namesize } // Check returns the checksum of the entry, if present func (hdr *Cpio_newc_header) Check() int { return hdr.check } // Filename returns the name of the file entry func (hdr *Cpio_newc_header) Filename() string { return hdr.filename } // stripped header functions // IsStripped returns true if the file header is missing info that must come // from the preceding RPM header func (hdr *Cpio_newc_header) IsStripped() bool { return hdr.stripped } // Index returns the position in the RPM header file info array corresponding to // this file func (hdr *Cpio_newc_header) Index() int { return hdr.index } // Filesize64 contains the file's size as a 64-bit integer, coming from either // SetFileSizes() if used or from the regular cpio file entry func (hdr *Cpio_newc_header) Filesize64() int64 { return hdr.size64 } golang-github-sassoftware-go-rpmutils-0.2.0/cpio/header_test.go000066400000000000000000000031721455103626000246340ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cpio import ( "os" "testing" "testing/iotest" ) func TestReadHeader(t *testing.T) { f, err := os.Open("../testdata/foo.cpio") if err != nil { t.Fatal(err) } defer f.Close() hdr, err := readHeader(iotest.HalfReader(f)) if err != nil { t.Fatal(err) } if hdr.magic != newcMagic { t.Fatal("bad magic") } if hdr.ino != 512785 { t.Fatal("bad inode") } if hdr.mode != 33188 { t.Fatal("bad mode") } if hdr.uid != 0 { t.Fatal("incorrect uid") } if hdr.gid != 0 { t.Fatal("incorrect gid") } if hdr.nlink != 1 { t.Fatal("incorrect nlink") } if hdr.mtime != 1263588698 { t.Fatal("incorrect mtime") } if hdr.filesize != 7 { t.Fatal("incorrect filesize") } if hdr.devmajor != 8 { t.Fatal("incorrect devmajor") } if hdr.devminor != 6 { t.Fatal("incorrect devminor") } if hdr.rdevmajor != 0 { t.Fatal("incorrect rdevmajor") } if hdr.rdevminor != 0 { t.Fatal("incorrect rdevminor") } if hdr.namesize != 9 { t.Fatal("incorrect namesize") } if hdr.check != 0 { t.Fatal("incorrect check") } } golang-github-sassoftware-go-rpmutils-0.2.0/cpio/reader.go000066400000000000000000000034751455103626000236150ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cpio import "io" // Reader accesses a cpio archive stream using a simple interface similar to the archive/tar package type Reader struct { stream *CpioStream cur *CpioEntry } // NewReader starts reading a cpio archive stream func NewReader(stream io.Reader) *Reader { return NewReaderWithSizes(stream, nil) } // NewReaderWithSizes starts reading a stripped cpio archive from a RPM payload using the provided file sizes func NewReaderWithSizes(stream io.Reader, sizes []int64) *Reader { cstream := NewCpioStream(stream) cstream.SetFileSizes(sizes) return &Reader{ stream: cstream, cur: nil, } } // Next returns the metadata of the next file in the archive, which can then be // read with Read(). // // Returns io.EOF upon encountering the archive trailer. func (r *Reader) Next() (*Cpio_newc_header, error) { ent, err := r.stream.ReadNextEntry() if err != nil { return nil, err } else if ent.Header.filename == TRAILER { return nil, io.EOF } r.cur = ent return r.cur.Header, nil } // Read bytes from the file returned by the preceding call to Next(). // // Returns io.EOF when the current file has been read in its entirety. func (r *Reader) Read(p []byte) (n int, err error) { return r.cur.payload.Read(p) } golang-github-sassoftware-go-rpmutils-0.2.0/cpio/reader_test.go000066400000000000000000000022751455103626000246510ustar00rootroot00000000000000/* * Copyright (c) SAS Institute Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cpio import ( "io" "os" "testing" "testing/iotest" ) func TestReadStripped(t *testing.T) { f, err := os.Open("../testdata/stripped.cpio") if err != nil { t.Fatal(err) } defer f.Close() r := NewReaderWithSizes(iotest.HalfReader(f), []int64{3, 6}) h, err := r.Next() if err != nil { t.Fatal(err) } if !h.IsStripped() || h.Index() != 0 { t.Fatalf("wrong header: %#v", h) } h, err = r.Next() if err != nil { t.Fatal(err) } if !h.IsStripped() || h.Index() != 1 { t.Fatalf("wrong header: %#v", h) } _, err = r.Next() if err != io.EOF { t.Fatalf("wrong error: %v", err) } } golang-github-sassoftware-go-rpmutils-0.2.0/cpio/stream.go000066400000000000000000000026001455103626000236330ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cpio import ( "fmt" "io" ) type file_stream struct { stream io.ReadSeeker start_pos int64 curr_pos int64 size int64 } func newFileStream(stream io.ReadSeeker, size int64) (*file_stream, error) { pos, err := stream.Seek(0, 1) if err != nil { return nil, err } return &file_stream{ stream: stream, start_pos: pos, curr_pos: 0, size: size, }, nil } func (fs *file_stream) Read(p []byte) (n int, err error) { if fs.curr_pos >= fs.size { return 0, io.EOF } pos, err := fs.stream.Seek(0, 1) if err != nil { return 0, err } if fs.start_pos+fs.curr_pos != pos { return 0, fmt.Errorf("read out of order") } if int64(len(p)) > fs.size-fs.curr_pos { p = p[0 : fs.size-fs.curr_pos] } n, err = fs.stream.Read(p) fs.curr_pos += int64(n) return } golang-github-sassoftware-go-rpmutils-0.2.0/errors.go000066400000000000000000000017331455103626000227300ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rpmutils import "fmt" // NoSuchTagError is returned when a tag does not exist in the header type NoSuchTagError struct { Tag int } func (err NoSuchTagError) Error() string { return fmt.Sprintf("No such entry %d", err.Tag) } // NewNoSuchTagError creates a NoSuchTagError for a given tag func NewNoSuchTagError(tag int) NoSuchTagError { return NoSuchTagError{Tag: tag} } golang-github-sassoftware-go-rpmutils-0.2.0/fileinfo.go000066400000000000000000000047331455103626000232120ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rpmutils // FileInfo describes a file in the RPM payload type FileInfo interface { Name() string Size() int64 UserName() string GroupName() string Flags() int Mtime() int Digest() string Mode() int Linkname() string Device() int Inode() int } type fileInfo struct { name string size uint64 userName string groupName string flags uint32 mtime uint32 digest string mode uint32 linkName string device uint32 inode uint32 } // Name returns the full path of the file func (fi *fileInfo) Name() string { return fi.name } // Size of the file in bytes func (fi *fileInfo) Size() int64 { return int64(fi.size) } // UserName returns the file's owner user name func (fi *fileInfo) UserName() string { return fi.userName } // GroupName returns the file's owner group name func (fi *fileInfo) GroupName() string { return fi.groupName } // Flags returns RPM-specific file flags (config, ghost etc.) func (fi *fileInfo) Flags() int { return int(fi.flags) } // Mtime returns the modification time of the file as a UNIX epoch time func (fi *fileInfo) Mtime() int { return int(fi.mtime) } // Digest of the file, according to FILEDIGESTALGO func (fi *fileInfo) Digest() string { return fi.digest } // Mode of the file, holding both permissions and file type func (fi *fileInfo) Mode() int { return int(fi.mode) } // Linkname returns the target of a symlink func (fi *fileInfo) Linkname() string { return fi.linkName } // Device returns the major and minor device number of a character or block device func (fi *fileInfo) Device() int { return int(fi.device) } // Inode returns a inode number used to tie hardlinked files together func (fi *fileInfo) Inode() int { return int(fi.inode) } func (fi *fileInfo) fileType() uint32 { return fi.mode &^ 07777 } func (fi *fileInfo) inode64() uint64 { return (uint64(fi.device) << 32) | uint64(fi.inode) } golang-github-sassoftware-go-rpmutils-0.2.0/filesigs.go000066400000000000000000000031451455103626000232200ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rpmutils // PGP Hash Algorithms const ( PGPHASHALGO_MD5 = 1 // MD5 PGPHASHALGO_SHA1 = 2 // SHA1 PGPHASHALGO_RIPEMD160 = 3 // RIPEMD160 PGPHASHALGO_MD2 = 5 // MD2 PGPHASHALGO_TIGER192 = 6 // TIGER192 PGPHASHALGO_HAVAL_5_160 = 7 // HAVAL-5-160 PGPHASHALGO_SHA256 = 8 // SHA256 PGPHASHALGO_SHA384 = 9 // SHA384 PGPHASHALGO_SHA512 = 10 // SHA512 PGPHASHALGO_SHA224 = 11 // SHA224 ) // GetFileAlgoName returns the name of a digest algorithm func GetFileAlgoName(algo int) string { switch algo { case PGPHASHALGO_MD5: return "md5" case PGPHASHALGO_SHA1: return "sha1" case PGPHASHALGO_RIPEMD160: return "rmd160" case PGPHASHALGO_MD2: return "md2" case PGPHASHALGO_TIGER192: return "tgr192" case PGPHASHALGO_HAVAL_5_160: return "haval5160" case PGPHASHALGO_SHA256: return "sha256" case PGPHASHALGO_SHA384: return "sha384" case PGPHASHALGO_SHA512: return "sha512" case PGPHASHALGO_SHA224: return "sha224" default: return "md5" } } golang-github-sassoftware-go-rpmutils-0.2.0/fileutil/000077500000000000000000000000001455103626000226765ustar00rootroot00000000000000golang-github-sassoftware-go-rpmutils-0.2.0/fileutil/fileutil_unix.go000066400000000000000000000017701455103626000261120ustar00rootroot00000000000000// +build !windows /* * Copyright (c) SAS Institute Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package fileutil import ( "os" "syscall" ) // HasLinks returns true if the given file has Nlink > 1 func HasLinks(info os.FileInfo) bool { stat, ok := info.Sys().(*syscall.Stat_t) if !ok { return false } return stat.Nlink != 1 } // Mkfifo creates a named pipe with the specified path and permissions func Mkfifo(path string, mode uint32) error { return syscall.Mkfifo(path, mode) } golang-github-sassoftware-go-rpmutils-0.2.0/fileutil/fileutil_windows.go000066400000000000000000000014651455103626000266220ustar00rootroot00000000000000/* * Copyright (c) SAS Institute Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package fileutil /* Stubs for file operations that aren't meaningful on windows */ import ( "os" ) func HasLinks(info os.FileInfo) bool { return false } func Mkfifo(path string, mode uint32) error { return nil } golang-github-sassoftware-go-rpmutils-0.2.0/go.mod000066400000000000000000000005251455103626000221710ustar00rootroot00000000000000module github.com/sassoftware/go-rpmutils go 1.13 require ( github.com/DataDog/zstd v1.4.5 github.com/klauspost/compress v1.11.7 github.com/stretchr/testify v1.7.0 github.com/ulikunitz/xz v0.5.9 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 go.uber.org/goleak v1.1.10 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad ) golang-github-sassoftware-go-rpmutils-0.2.0/go.sum000066400000000000000000000107011455103626000222130ustar00rootroot00000000000000github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I= github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11 h1:Yq9t9jnGoR+dBuitxdo9l6Q7xh/zOyNnYUtDKaQ3x0E= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-sassoftware-go-rpmutils-0.2.0/header.go000066400000000000000000000262541455103626000226510ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rpmutils import ( "bytes" "crypto/sha1" "encoding/binary" "errors" "fmt" "io" "path" "strconv" "strings" ) const introMagic = 0x8eade801 type entry struct { dataType, count int32 contents []byte } type rpmHeader struct { entries map[int]entry isSource bool origSize int } type headerIntro struct { Magic, Reserved, Entries, Size uint32 } type headerTag struct { Tag, DataType, Offset, Count int32 } var typeAlign = map[int32]int{ RPM_INT16_TYPE: 2, RPM_INT32_TYPE: 4, RPM_INT64_TYPE: 8, } var typeSizes = map[int32]int{ RPM_NULL_TYPE: 0, RPM_CHAR_TYPE: 1, RPM_INT8_TYPE: 1, RPM_INT16_TYPE: 2, RPM_INT32_TYPE: 4, RPM_INT64_TYPE: 8, RPM_BIN_TYPE: 1, } func readExact(f io.Reader, n int) ([]byte, error) { buf := make([]byte, n) _, err := io.ReadFull(f, buf) return buf, err } func readHeader(f io.Reader, hash string, isSource bool, sigBlock bool) (*rpmHeader, error) { var intro headerIntro if err := binary.Read(f, binary.BigEndian, &intro); err != nil { return nil, fmt.Errorf("error reading RPM header: %s", err.Error()) } if intro.Magic != introMagic { return nil, fmt.Errorf("bad magic for header") } entryTable, err := readExact(f, int(intro.Entries*16)) if err != nil { return nil, fmt.Errorf("error reading RPM header table: %s", err.Error()) } size := intro.Size if sigBlock { // signature block is padded to 8 byte alignment size = (size + 7) / 8 * 8 } data, err := readExact(f, int(size)) if err != nil { return nil, fmt.Errorf("error reading RPM header data: %s", err.Error()) } // Check sha1 if it was specified if len(hash) > 1 { h := sha1.New() if err = binary.Write(h, binary.BigEndian, &intro); err != nil { return nil, err } if _, err = h.Write(entryTable); err != nil { return nil, err } if _, err = h.Write(data); err != nil { return nil, err } if fmt.Sprintf("%x", h.Sum(nil)) != hash { return nil, fmt.Errorf("bad header sha1") } } ents := make(map[int]entry) buf := bytes.NewReader(entryTable) for i := 0; i < int(intro.Entries); i++ { var tag headerTag if err := binary.Read(buf, binary.BigEndian, &tag); err != nil { return nil, err } typeSize, ok := typeSizes[tag.DataType] var end int if ok { end = int(tag.Offset) + typeSize*int(tag.Count) } else { // String types are null-terminated end = int(tag.Offset) for i := 0; i < int(tag.Count); i++ { next := bytes.IndexByte(data[end:], 0) if next < 0 { return nil, fmt.Errorf("tag %d is truncated", tag.Tag) } end += next + 1 } } ents[int(tag.Tag)] = entry{ dataType: tag.DataType, count: tag.Count, contents: data[tag.Offset:end], } } return &rpmHeader{ entries: ents, isSource: isSource, origSize: 16 + len(entryTable) + len(data), }, nil } // HasTag returns true if the given tag exists in the header func (hdr *rpmHeader) HasTag(tag int) bool { _, ok := hdr.entries[tag] return ok } // Get the value of a tag. Returns whichever type most closely represents how // the tag was stored, or NoSuchTagError if the tag was not found. If tag is // OLDFILENAMES, special handling is provided to splice together DIRNAMES and // BASENAMES if it is not present. func (hdr *rpmHeader) Get(tag int) (interface{}, error) { ent, ok := hdr.entries[tag] if !ok && tag == OLDFILENAMES { return hdr.GetStrings(tag) } if !ok { return nil, NewNoSuchTagError(tag) } switch ent.dataType { case RPM_STRING_TYPE, RPM_STRING_ARRAY_TYPE, RPM_I18NSTRING_TYPE: return hdr.GetStrings(tag) case RPM_INT8_TYPE, RPM_INT16_TYPE, RPM_INT32_TYPE, RPM_INT64_TYPE, RPM_CHAR_TYPE: out, _, err := hdr.getInts(tag) return out, err case RPM_BIN_TYPE: return hdr.GetBytes(tag) default: return nil, fmt.Errorf("unsupported data type") } } // GetStrings fetches the given tag holding a string or array of strings. If tag // is OLDFILENAMES, special handling is provided to splice together DIRNAMES and // BASENAMES if it is not present. func (hdr *rpmHeader) GetStrings(tag int) ([]string, error) { ent, ok := hdr.entries[tag] if tag == OLDFILENAMES && !ok { dirs, err := hdr.GetStrings(DIRNAMES) if err != nil { return nil, err } dirIdxs, err := hdr.GetUint32s(DIRINDEXES) if err != nil { return nil, err } baseNames, err := hdr.GetStrings(BASENAMES) if err != nil { return nil, err } paths := make([]string, 0, len(baseNames)) for i, base := range baseNames { paths = append(paths, path.Join(dirs[dirIdxs[i]], base)) } return paths, nil } if !ok { return nil, NewNoSuchTagError(tag) } if ent.dataType != RPM_STRING_TYPE && ent.dataType != RPM_STRING_ARRAY_TYPE && ent.dataType != RPM_I18NSTRING_TYPE { return nil, fmt.Errorf("unsupported datatype for string: %d, tag: %d", ent.dataType, tag) } strs := strings.Split(string(ent.contents), "\x00") return strs[:ent.count], nil } // get an int array using whatever the appropriate sized type is func (hdr *rpmHeader) getInts(tag int) (buf interface{}, n int, err error) { ent, ok := hdr.entries[tag] if !ok { return nil, 0, NewNoSuchTagError(tag) } n = len(ent.contents) switch ent.dataType { case RPM_INT8_TYPE, RPM_CHAR_TYPE: buf = make([]uint8, n) case RPM_INT16_TYPE: n >>= 1 buf = make([]uint16, n) case RPM_INT32_TYPE: n >>= 2 buf = make([]uint32, n) case RPM_INT64_TYPE: n >>= 3 buf = make([]uint64, n) default: return nil, 0, fmt.Errorf("tag %d isn't an int type", tag) } if err := binary.Read(bytes.NewReader(ent.contents), binary.BigEndian, buf); err != nil { return nil, 0, err } return } // GetInts gets an integer array using the default 'int' type. // // DEPRECATED: large int32s and int64s can overflow. Use GetUint32s or GetUint64s instead. func (hdr *rpmHeader) GetInts(tag int) ([]int, error) { buf, n, err := hdr.getInts(tag) if err != nil { return nil, err } out := make([]int, n) switch bvals := buf.(type) { case []uint8: for i, v := range bvals { out[i] = int(v) } case []uint16: for i, v := range bvals { out[i] = int(v) } case []uint32: for i, v := range bvals { if v > (1<<31)-1 { return nil, fmt.Errorf("value %d out of range for int32 array in tag %d", i, tag) } out[i] = int(v) } default: return nil, fmt.Errorf("tag %d is too big for int type", tag) } return out, nil } // GetUint32s gets an int array as a uint32 slice. This can accomodate any int // type other than INT64. Returns an error in case of overflow. func (hdr *rpmHeader) GetUint32s(tag int) ([]uint32, error) { buf, n, err := hdr.getInts(tag) if err != nil { return nil, err } if out, ok := buf.([]uint32); ok { return out, nil } out := make([]uint32, n) switch bvals := buf.(type) { case []uint8: for i, v := range bvals { out[i] = uint32(v) } case []uint16: for i, v := range bvals { out[i] = uint32(v) } default: return nil, fmt.Errorf("tag %d is too big for int type", tag) } return out, nil } // GetUint64s gets an int array as a uint64 slice. This can accomodate all int // types. func (hdr *rpmHeader) GetUint64s(tag int) ([]uint64, error) { buf, n, err := hdr.getInts(tag) if err != nil { return nil, err } if out, ok := buf.([]uint64); ok { return out, nil } out := make([]uint64, n) switch bvals := buf.(type) { case []uint8: for i, v := range bvals { out[i] = uint64(v) } case []uint16: for i, v := range bvals { out[i] = uint64(v) } case []uint32: for i, v := range bvals { out[i] = uint64(v) } } return out, nil } // GetUint64Fallback gets longTag if it exists, otherwise intTag, and returns // the value as an array of uint64s. This can accomodate all int types and is // normally used when a int32 tag was later replaced with a int64 tag. func (hdr *rpmHeader) GetUint64Fallback(intTag, longTag int) ([]uint64, error) { if _, ok := hdr.entries[longTag]; ok { return hdr.GetUint64s(longTag) } return hdr.GetUint64s(intTag) } // GetBytes gets a tag as a byte array. func (hdr *rpmHeader) GetBytes(tag int) ([]byte, error) { ent, ok := hdr.entries[tag] if !ok { return nil, NewNoSuchTagError(tag) } if ent.dataType != RPM_BIN_TYPE { return nil, fmt.Errorf("unsupported datatype for bytes: %d, tag: %d", ent.dataType, tag) } return ent.contents, nil } // GetNEVRA gets the name, epoch, version, release and arch of the RPM. func (hdr *rpmHeader) GetNEVRA() (*NEVRA, error) { name, err := hdr.GetStrings(NAME) if err != nil { return nil, err } epoch, err := hdr.GetUint64s(EPOCH) if _, absent := err.(NoSuchTagError); !absent && err != nil { return nil, err } else if len(epoch) == 0 { // no epoch is treated as 0 epoch = []uint64{0} } version, err := hdr.GetStrings(VERSION) if err != nil { return nil, err } release, err := hdr.GetStrings(RELEASE) if err != nil { return nil, err } arch, err := hdr.GetStrings(ARCH) if err != nil { return nil, err } return &NEVRA{ Name: name[0], Epoch: strconv.FormatUint(epoch[0], 10), Version: version[0], Release: release[0], Arch: arch[0], }, nil } // GetFiles returns an array of FileInfo objects holding file-related attributes // held in parallel arrays of tags // // If the RPM has no files, GetFiles returns an empty list. func (hdr *rpmHeader) GetFiles() ([]FileInfo, error) { paths, err := hdr.GetStrings(OLDFILENAMES) if err != nil { if !errors.As(err, &NoSuchTagError{}) { return nil, err } // RPM has no files return nil, nil } fileSizes, err := hdr.GetUint64Fallback(FILESIZES, LONGFILESIZES) if err != nil { return nil, err } fileUserName, err := hdr.GetStrings(FILEUSERNAME) if err != nil { return nil, err } fileGroupName, err := hdr.GetStrings(FILEGROUPNAME) if err != nil { return nil, err } fileFlags, err := hdr.GetUint32s(FILEFLAGS) if err != nil { return nil, err } fileMtimes, err := hdr.GetUint32s(FILEMTIMES) if err != nil { return nil, err } fileDigests, err := hdr.GetStrings(FILEDIGESTS) if err != nil { return nil, err } fileModes, err := hdr.GetUint32s(FILEMODES) if err != nil { return nil, err } linkTos, err := hdr.GetStrings(FILELINKTOS) if err != nil { return nil, err } devices, err := hdr.GetUint32s(FILEDEVICES) if err != nil { devices = make([]uint32, len(paths)) } inodes, err := hdr.GetUint32s(FILEINODES) if err != nil { inodes = make([]uint32, len(paths)) } files := make([]FileInfo, len(paths)) for i := 0; i < len(paths); i++ { files[i] = &fileInfo{ name: paths[i], size: fileSizes[i], userName: fileUserName[i], groupName: fileGroupName[i], flags: fileFlags[i], mtime: fileMtimes[i], digest: fileDigests[i], mode: fileModes[i], linkName: linkTos[i], device: devices[i], inode: inodes[i], } } return files, nil } golang-github-sassoftware-go-rpmutils-0.2.0/nevra.go000066400000000000000000000033461455103626000225310ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rpmutils import ( "fmt" "sort" ) // NEVRA holds the name, epoch, version, release and arch of the RPM, which uniquely identifies it type NEVRA struct { Name string Epoch string Version string Release string Arch string } // TODO: in v2 change epoch to an int func (nevra *NEVRA) String() string { return fmt.Sprintf("%s-%s:%s-%s.%s.rpm", nevra.Name, nevra.Epoch, nevra.Version, nevra.Release, nevra.Arch) } // NEVRAcmp compares two RPM versions. It returns -1 if a < b, 1 if a > b, and 0 if a == b func NEVRAcmp(a NEVRA, b NEVRA) int { if res := Vercmp(a.Epoch, b.Epoch); res != 0 { return res } if res := Vercmp(a.Version, b.Version); res != 0 { return res } if res := Vercmp(a.Release, b.Release); res != 0 { return res } return 0 } // NEVRASlice is used to sort a list of NEVRAs and implements sort.Interface type NEVRASlice []NEVRA func (s NEVRASlice) Len() int { return len(s) } func (s NEVRASlice) Less(i, j int) bool { return NEVRAcmp(s[i], s[j]) == -1 } func (s NEVRASlice) Swap(i, j int) { n := s[i] s[i] = s[j] s[j] = n } // Sort a list of NEVRAs in-place func (s NEVRASlice) Sort() { sort.Sort(s) } golang-github-sassoftware-go-rpmutils-0.2.0/payload.go000066400000000000000000000064761455103626000230560ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rpmutils import ( "errors" "fmt" "io" "github.com/sassoftware/go-rpmutils/cpio" ) // TODO version 2: // - Make PayloadReader and FileInfo regular structs // - Promote IsLink to a method of FileInfo // - Add Close() that must be called to clean up decompressors // PayloadReader is used to sequentially access the file contents of a RPM payload type PayloadReader interface { Next() (FileInfo, error) Read([]byte) (int, error) IsLink() bool } type payloadReader struct { stream io.Reader cr *cpio.Reader files []*fileInfo fileMap map[string]int isLink []bool index int } func newPayloadReader(r io.Reader, files []FileInfo) *payloadReader { pr := &payloadReader{ stream: r, files: make([]*fileInfo, len(files)), fileMap: make(map[string]int, len(files)), isLink: make([]bool, len(files)), } fileSizes := make([]int64, len(files)) var lastInode uint64 for i, info := range files { fileSt := info.(*fileInfo) pr.files[i] = fileSt pr.fileMap[fileSt.name] = i switch fileSt.fileType() { case cpio.S_ISREG: fileSizes[i] = fileSt.Size() // all but the last file in a link group will have no contents. flag // them so we don't try to read the nonexistent payload. ino := fileSt.inode64() if ino == lastInode && ino != 0 { pr.isLink[i-1] = true fileSizes[i-1] = 0 } lastInode = ino case cpio.S_ISLNK: fileSizes[i] = int64(len(fileSt.linkName)) } } pr.cr = cpio.NewReaderWithSizes(r, fileSizes) return pr } // Next returns the info of the next file in the payload. After calling Next(), // Read() can be used to read the contents of the file. Returns io.EOF when all // files have been consumed. func (pr *payloadReader) Next() (FileInfo, error) { hdr, err := pr.cr.Next() if err != nil { // close decompressor on EOF, zstd in particular leaks goroutines otherwise if c, ok := pr.stream.(io.Closer); ok { c.Close() } return nil, err } var index int if hdr.IsStripped() { index = hdr.Index() } else { var ok bool name := hdr.Filename() if len(name) > 1 && name[0] == '.' && name[1] == '/' { name = name[1:] } index, ok = pr.fileMap[name] if !ok { return nil, fmt.Errorf("invalid file \"%s\" in payload", name) } } if index >= len(pr.files) { return nil, errors.New("invalid file index") } pr.index = index return pr.files[index], nil } // Read bytes from the file returned by the preceding call to Next() func (pr *payloadReader) Read(d []byte) (int, error) { return pr.cr.Read(d) } // IsLink returns true if the current file is a hard-link with no contents. A // subsequent file with the same FileInfo.Inode and for which IsLink() returns // false will have the contents. func (pr *payloadReader) IsLink() bool { return pr.isLink[pr.index] } golang-github-sassoftware-go-rpmutils-0.2.0/rpmutils.go000066400000000000000000000204201455103626000232650ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rpmutils import ( "encoding/binary" "errors" "fmt" "io" "github.com/sassoftware/go-rpmutils/cpio" ) // Rpm is an open RPM header and payload type Rpm struct { Header *RpmHeader f io.Reader } // RpmHeader holds the signature header and general header of a RPM // // Tags are drawn from both header areas, with IDs between _GENERAL_TAG_BASE and // _SIGHEADER_TAG_BASE coming from the general header and all others coming from // the signature header. type RpmHeader struct { lead []byte sigHeader *rpmHeader genHeader *rpmHeader isSource bool } // ReadRpm reads the header from a RPM file and prepares to read payload contents func ReadRpm(f io.Reader) (*Rpm, error) { hdr, err := ReadHeader(f) if err != nil { return nil, err } return &Rpm{ Header: hdr, f: f, }, nil } // ExpandPayload extracts the payload of a RPM to the specified directory func (rpm *Rpm) ExpandPayload(dest string) error { pld, err := uncompressRpmPayloadReader(rpm.f, rpm.Header) if err != nil { return err } if c, ok := pld.(io.Closer); ok { defer c.Close() } return cpio.Extract(pld, dest) } // PayloadReader accesses the payload cpio archive within the RPM. // // DEPRECATED: Use PayloadReaderExtended instead in order to handle files larger than 4GiB. func (rpm *Rpm) PayloadReader() (*cpio.Reader, error) { pld, err := uncompressRpmPayloadReader(rpm.f, rpm.Header) if err != nil { return nil, err } return cpio.NewReader(pld), nil } // PayloadReaderExtended accesses payload file contents sequentially func (rpm *Rpm) PayloadReaderExtended() (PayloadReader, error) { pld, err := uncompressRpmPayloadReader(rpm.f, rpm.Header) if err != nil { return nil, err } files, err := rpm.Header.GetFiles() if err != nil { return nil, err } return newPayloadReader(pld, files), nil } // ReadHeader reads the signature and general headers from a RPM. // // The stream is positioned for reading the compressed payload following the headers. func ReadHeader(f io.Reader) (*RpmHeader, error) { lead, sigHeader, err := readSignatureHeader(f) if err != nil { return nil, err } genHeader, err := readHeader(f, getSha1(sigHeader), sigHeader.isSource, false) if err != nil { return nil, err } return &RpmHeader{ lead: lead, sigHeader: sigHeader, genHeader: genHeader, isSource: sigHeader.isSource, }, nil } func readSignatureHeader(f io.Reader) ([]byte, *rpmHeader, error) { // Read signature header lead, err := readExact(f, 96) if err != nil { return nil, nil, fmt.Errorf("error reading RPM lead: %s", err.Error()) } // Check file magic magic := binary.BigEndian.Uint32(lead[0:4]) if magic&0xffffffff != 0xedabeedb { return nil, nil, fmt.Errorf("file is not an RPM") } // Check source flag isSource := binary.BigEndian.Uint16(lead[6:8]) == 1 // Return signature header hdr, err := readHeader(f, "", isSource, true) return lead, hdr, err } // HeaderRange indicates the byte offsets that the RPM header spans type HeaderRange struct { // Start is the byte offset of the signature header Start int // End is the byte offset of the end of the general header and start of the payload End int } // GetRange returns the byte offsets that the RPM header spans within the original RPM file func (hdr *RpmHeader) GetRange() HeaderRange { start := 96 + hdr.sigHeader.origSize end := start + hdr.genHeader.origSize return HeaderRange{ Start: start, End: end, } } // HasTag returns true if the given tag exists in the header func (hdr *RpmHeader) HasTag(tag int) bool { h, t := hdr.getHeader(tag) return h.HasTag(t) } // Get the value of a tag. Returns whichever type most closely represents how // the tag was stored, or NoSuchTagError if the tag was not found. If tag is // OLDFILENAMES, special handling is provided to splice together DIRNAMES and // BASENAMES if it is not present. func (hdr *RpmHeader) Get(tag int) (interface{}, error) { h, t := hdr.getHeader(tag) return h.Get(t) } // GetString returns the value of a tag holding a single string func (hdr *RpmHeader) GetString(tag int) (string, error) { vals, err := hdr.GetStrings(tag) if err != nil { return "", err } if len(vals) != 1 { return "", fmt.Errorf("incorrect number of values") } return vals[0], nil } // GetStrings fetches the given tag holding a string or array of strings. If tag // is OLDFILENAMES, special handling is provided to splice together DIRNAMES and // BASENAMES if it is not present. func (hdr *RpmHeader) GetStrings(tag int) ([]string, error) { h, t := hdr.getHeader(tag) return h.GetStrings(t) } // GetInt gets an integer using the default 'int' type. // // DEPRECATED: large int32s and int64s can overflow. Use GetUint32s or GetUint64s instead. func (hdr *RpmHeader) GetInt(tag int) (int, error) { vals, err := hdr.GetInts(tag) if err != nil { return -1, err } if len(vals) != 1 { return -1, fmt.Errorf("incorrect number of values") } return vals[0], nil } // GetInts gets an integer array using the default 'int' type. // // DEPRECATED: large int32s and int64s can overflow. Use GetUint32s or GetUint64s instead. func (hdr *RpmHeader) GetInts(tag int) ([]int, error) { h, t := hdr.getHeader(tag) return h.GetInts(t) } // GetUint32s gets an int array as a uint32 slice. This can accomodate any int // type other than INT64. Returns an error in case of overflow. func (hdr *RpmHeader) GetUint32s(tag int) ([]uint32, error) { h, t := hdr.getHeader(tag) return h.GetUint32s(t) } // GetUint64s gets an int array as a uint64 slice. This can accomodate all int // types func (hdr *RpmHeader) GetUint64s(tag int) ([]uint64, error) { h, t := hdr.getHeader(tag) return h.GetUint64s(t) } // GetUint64Fallback gets longTag if it exists, otherwise intTag, and returns // the value as an array of uint64s. This can accomodate all int types and is // normally used when a int32 tag was later replaced with a int64 tag. func (hdr *RpmHeader) GetUint64Fallback(intTag, longTag int) (uint64, error) { h, t := hdr.getHeader(longTag) vals, err := h.GetUint64s(t) if err == nil && len(vals) == 1 { return vals[0], nil } h, t = hdr.getHeader(intTag) vals, err = h.GetUint64s(t) if err != nil { return 0, err } else if len(vals) != 1 { return 0, errors.New("incorrect number of values") } return vals[0], nil } // GetBytes gets a tag as a byte array. func (hdr *RpmHeader) GetBytes(tag int) ([]byte, error) { h, t := hdr.getHeader(tag) return h.GetBytes(t) } // getHeader decides whether the conventional tag ID is within the signature // header range and returns the appropriate sub-header struct and raw tag // identifier func (hdr *RpmHeader) getHeader(tag int) (*rpmHeader, int) { if tag > _SIGHEADER_TAG_BASE { return hdr.sigHeader, tag - _SIGHEADER_TAG_BASE } if tag < _GENERAL_TAG_BASE { return hdr.sigHeader, tag } return hdr.genHeader, tag } // GetNEVRA gets the name, epoch, version, release and arch of the RPM. func (hdr *RpmHeader) GetNEVRA() (*NEVRA, error) { return hdr.genHeader.GetNEVRA() } // GetFiles returns an array of FileInfo objects holding file-related attributes // held in parallel arrays of tags func (hdr *RpmHeader) GetFiles() ([]FileInfo, error) { return hdr.genHeader.GetFiles() } // InstalledSize returns the approximate disk space needed to install the package func (hdr *RpmHeader) InstalledSize() (int64, error) { u, err := hdr.GetUint64Fallback(SIZE, LONGSIZE) if err != nil { return -1, err } return int64(u), nil } // PayloadSize returns the size of the uncompressed payload in bytes func (hdr *RpmHeader) PayloadSize() (int64, error) { u, err := hdr.sigHeader.GetUint64Fallback(SIG_PAYLOADSIZE-_SIGHEADER_TAG_BASE, SIG_LONGARCHIVESIZE) if err != nil { return -1, err } else if len(u) != 1 { return -1, errors.New("incorrect number of values") } return int64(u[0]), err } golang-github-sassoftware-go-rpmutils-0.2.0/rpmutils_test.go000066400000000000000000000061601455103626000243310ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rpmutils import ( "io/ioutil" "os" "testing" "testing/iotest" ) func TestReadHeader(t *testing.T) { f, err := os.Open("./testdata/simple-1.0.1-1.i386.rpm") if err != nil { t.Fatal(err) } defer f.Close() hdr, err := ReadHeader(iotest.HalfReader(f)) if err != nil { t.Fatal(err) } if hdr == nil { t.Fatal("no header found") } nevra, err := hdr.GetNEVRA() if err != nil { t.Fatal(err) } expectedNevra := NEVRA{ Epoch: "0", Name: "simple", Version: "1.0.1", Release: "1", Arch: "i386", } if expectedNevra != *nevra { t.Fatalf("incorrect nevra: %s (expected %s)", nevra.String(), expectedNevra.String()) } files, err := hdr.GetFiles() if err != nil { t.Fatal(err) } if len(files) != 3 { t.Fatalf("incorrect number of files %d", len(files)) } hdrRange := hdr.GetRange() expectedRange := HeaderRange{ Start: 280, End: 1764, } if hdrRange != expectedRange { t.Errorf("incorrect header range %+v (expected %+v)", hdrRange, expectedRange) } } func TestEpoch(t *testing.T) { items := [][]string{ {"testdata/zero-epoch-0.1-1.x86_64.rpm", "0"}, {"testdata/one-epoch-0.1-1.x86_64.rpm", "1"}, } for _, item := range items { filename, expected := item[0], item[1] t.Run(expected, func(t *testing.T) { f, err := os.Open(filename) if err != nil { t.Fatal(err) } defer f.Close() hdr, err := ReadHeader(f) if err != nil { t.Fatal(err) } nevra, err := hdr.GetNEVRA() if err != nil { t.Fatal(err) } if nevra.Epoch != expected { t.Errorf("%s: expected %q got %q", filename, expected, nevra.Epoch) } }) } } func TestPayloadReader(t *testing.T) { f, err := os.Open("./testdata/simple-1.0.1-1.i386.rpm") if err != nil { t.Fatal(err) } defer f.Close() rpm, err := ReadRpm(iotest.HalfReader(f)) if err != nil { t.Fatal(err) } pldr, err := rpm.PayloadReader() if err != nil { t.Fatal(err) } hdr, err := pldr.Next() if err != nil { t.Fatal(err) } if hdr.Filesize() != 7 { t.Fatalf("wrong file size %d", hdr.Filesize()) } if hdr.Filename() != "./config" { t.Fatalf("wrong file name %s", hdr.Filename()) } } func TestExpandPayload(t *testing.T) { f, err := os.Open("./testdata/simple-1.0.1-1.i386.rpm") if err != nil { t.Fatal(err) } defer f.Close() rpm, err := ReadRpm(iotest.HalfReader(f)) if err != nil { t.Fatal(err) } tmpdir, err := ioutil.TempDir("", "rpmutil") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpdir) if err := rpm.ExpandPayload(tmpdir); err != nil { t.Fatal(err) } } golang-github-sassoftware-go-rpmutils-0.2.0/signatures.go000066400000000000000000000164441455103626000236050ustar00rootroot00000000000000/* * Copyright (c) SAS Institute Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rpmutils import ( "bytes" "crypto" "crypto/md5" "errors" "hash" "io" "io/ioutil" "os" "path" "time" "github.com/sassoftware/go-rpmutils/fileutil" "golang.org/x/crypto/openpgp/packet" ) // SignatureOptions describes additional configuration for SignRpm methods type SignatureOptions struct { // Hash algorithm for the signature. If not set, defaults to SHA-256 Hash crypto.Hash // CreationTime for the signature. If not set, defaults to the current time CreationTime time.Time } func (opts *SignatureOptions) hash() crypto.Hash { if opts != nil { return opts.Hash } return crypto.SHA256 } func (opts *SignatureOptions) creationTime() time.Time { if opts != nil { return opts.CreationTime } return time.Now() } func makeSignature(stream io.Reader, key *packet.PrivateKey, opts *SignatureOptions) ([]byte, error) { hash := opts.hash() sig := &packet.Signature{ SigType: packet.SigTypeBinary, CreationTime: opts.creationTime(), PubKeyAlgo: key.PublicKey.PubKeyAlgo, Hash: hash, IssuerKeyId: &key.KeyId, } h := hash.New() _, err := io.Copy(h, stream) if err != nil { return nil, err } err = sig.Sign(h, key, nil) if err != nil { return nil, err } var buf bytes.Buffer err = sig.Serialize(&buf) if err != nil { return nil, err } return buf.Bytes(), nil } func insertSignature(sigHeader *rpmHeader, tag int, value []byte) { sigHeader.entries[tag] = entry{ dataType: RPM_BIN_TYPE, count: int32(len(value)), contents: value, } } func insertSignatures(sigHeader *rpmHeader, sigPgp, sigRsa []byte) { insertSignature(sigHeader, SIG_PGP-_SIGHEADER_TAG_BASE, sigPgp) insertSignature(sigHeader, SIG_RSA, sigRsa) delete(sigHeader.entries, SIG_GPG-_SIGHEADER_TAG_BASE) delete(sigHeader.entries, SIG_DSA) } func getSha1(sigHeader *rpmHeader) string { vals, err := sigHeader.GetStrings(SIG_SHA1) if err != nil { return "" } return vals[0] } func checkMd5(sigHeader *rpmHeader, h hash.Hash) bool { sigmd5, err := sigHeader.GetBytes(SIG_MD5 - _SIGHEADER_TAG_BASE) if err != nil { return true } return bytes.Equal(sigmd5, h.Sum(nil)) } // SignRpmStream reads an RPM and signs it, returning the set of headers updated with the new signature. func SignRpmStream(stream io.Reader, key *packet.PrivateKey, opts *SignatureOptions) (header *RpmHeader, err error) { lead, sigHeader, err := readSignatureHeader(stream) if err != nil { return } // parse the general header and also tee it into a buffer genHeaderBuf := new(bytes.Buffer) headerTee := io.TeeReader(stream, genHeaderBuf) genHeader, err := readHeader(headerTee, getSha1(sigHeader), sigHeader.isSource, false) if err != nil { return } genHeaderBlob := genHeaderBuf.Bytes() // chain the buffered general header to the rest of the payload, and digest the whole lot of it genHeaderAndPayload := io.MultiReader(bytes.NewReader(genHeaderBlob), stream) payloadDigest := md5.New() payloadTee := io.TeeReader(genHeaderAndPayload, payloadDigest) sigPgp, err := makeSignature(payloadTee, key, opts) if err != nil { return } if !checkMd5(sigHeader, payloadDigest) { return nil, errors.New("md5 digest mismatch") } sigRsa, err := makeSignature(bytes.NewReader(genHeaderBlob), key, opts) if err != nil { return } insertSignatures(sigHeader, sigPgp, sigRsa) return &RpmHeader{ lead: lead, sigHeader: sigHeader, genHeader: genHeader, isSource: sigHeader.isSource, }, nil } func canOverwrite(ininfo, outinfo os.FileInfo) bool { if !outinfo.Mode().IsRegular() { return false } if !os.SameFile(ininfo, outinfo) { return false } if fileutil.HasLinks(outinfo) { return false } return true } // SignRpmFile signs infile and writes it to outpath, which may be the same file func SignRpmFile(infile *os.File, outpath string, key *packet.PrivateKey, opts *SignatureOptions) (header *RpmHeader, err error) { header, err = SignRpmStream(infile, key, opts) if err != nil { return } return header, rewriteRpm(infile, outpath, header) } // RewriteWithSignatures inserts raw signatures into a RPM header. // // DEPRECATED: To perform a detached signature, use SignRpmStream and call // DumpSignatureHeader to export the result. func RewriteWithSignatures(infile *os.File, outpath string, sigPgp, sigRsa []byte) (*RpmHeader, error) { header, err := ReadHeader(infile) if err != nil { return nil, err } insertSignatures(header.sigHeader, sigPgp, sigRsa) err = rewriteRpm(infile, outpath, header) if err != nil { return nil, err } return header, nil } func rewriteRpm(infile *os.File, outpath string, header *RpmHeader) error { delete(header.sigHeader.entries, SIG_RESERVEDSPACE-_SIGHEADER_TAG_BASE) ininfo, err := infile.Stat() if err != nil { return err } var outstream io.Writer if outpath == "-" { outstream = os.Stdout } else { outinfo, err := os.Lstat(outpath) if err == nil && canOverwrite(ininfo, outinfo) { ok, err := writeInPlace(outpath, header) if err != nil || ok { return err } // in-place didn't work; fallback to rewrite } else if err == nil && !outinfo.Mode().IsRegular() { // pipe or something else. open for writing. outfile, err := os.Create(outpath) if err != nil { return err } defer outfile.Close() outstream = outfile } if outstream == nil { // write-rename tempfile, err := ioutil.TempFile(path.Dir(outpath), path.Base(outpath)) if err != nil { return err } defer func() { if err != nil { os.Remove(tempfile.Name()) } else { _ = tempfile.Chmod(0644) if err = tempfile.Close(); err != nil { return } err = os.Rename(tempfile.Name(), outpath) } }() outstream = tempfile } } return writeRpm(infile, outstream, header.sigHeader) } func writeInPlace(path string, header *RpmHeader) (ok bool, err error) { blob, err := header.DumpSignatureHeader(true) if err != nil { return false, err } orig := header.OriginalSignatureHeaderSize() if orig != len(blob) { // size changed; can't rewrite in place return false, nil } outfile, err := os.OpenFile(path, os.O_RDWR, 0) if err != nil { return } defer outfile.Close() n, err := outfile.Write(blob) if err != nil { return false, err } else if n != len(blob) { return false, io.ErrShortWrite } return true, nil } func writeRpm(infile io.ReadSeeker, outstream io.Writer, sigHeader *rpmHeader) error { if _, err := infile.Seek(0, 0); err != nil { return err } lead, err := readExact(infile, 96) if err != nil { return err } if _, err = outstream.Write(lead); err != nil { return err } if err = sigHeader.WriteTo(outstream, RPMTAG_HEADERSIGNATURES); err != nil { return err } if _, err := infile.Seek(int64(len(lead)+sigHeader.origSize), 0); err != nil { return err } _, err = io.Copy(outstream, infile) return err } golang-github-sassoftware-go-rpmutils-0.2.0/signatures_test.go000066400000000000000000000077321455103626000246440ustar00rootroot00000000000000/* * Copyright (c) SAS Institute Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rpmutils import ( "bytes" "io" "os" "testing" "golang.org/x/crypto/openpgp" ) func TestSign(t *testing.T) { keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(testkey))) if err != nil { t.Fatal("failed to parse test key:", err) } entity := keyring[0] f, err := os.Open("testdata/simple-1.0.1-1.i386.rpm") if err != nil { t.Fatal("failed to open test rpm:", err) } defer f.Close() h, err := SignRpmStream(f, entity.PrivateKey, nil) if err != nil { t.Fatal("error signing rpm:", err) } sigblob, err := h.DumpSignatureHeader(false) if err != nil { t.Fatal("error writing sig header:", err) } if len(sigblob)%8 != 0 { t.Fatalf("incorrect padding: got %d bytes, expected a multiple of 8", len(sigblob)) } // verify by merging the new sig header with the original file if _, err = f.Seek(int64(h.OriginalSignatureHeaderSize()), io.SeekStart); err != nil { t.Fatal("error seeking:", err) } signed := io.MultiReader(bytes.NewReader(sigblob), f) _, sigs, err := Verify(signed, keyring) if err != nil { t.Fatal("error verifying signature:", err) } if len(sigs) != 2 || sigs[0].Signer != entity || sigs[1].Signer != entity { t.Fatalf("error verifying signature: incorrect signers. found: %#v", sigs) } // check padding for odd sized signature tags h.sigHeader.entries[1234] = entry{dataType: RPM_BIN_TYPE, count: 3, contents: []byte("foo")} sigblob, err = h.DumpSignatureHeader(false) if err != nil { t.Fatal("error writing sig header:", err) } if len(sigblob)%8 != 0 { t.Fatalf("incorrect padding: got %d bytes, expected a multiple of 8", len(sigblob)) } } const testkey = ` -----BEGIN PGP PRIVATE KEY BLOCK----- lQOYBFnCxAYBCACsNEYGCSm1uh9PDiB2L7TudKRqrLBiArETQagzbyNuSHHNibLa 85u9X1ZqcPspqISQjTmk7zYCFWzXlMDzPvAeLqiLX/0NHsqMuFFCGSE5jH0uS+KN P4eLBYYgAJFCa4foyIGpESg52GA2/wZfvF5NOen0irh9XaA869jcWjb3c1euKLKo 0DEJU6OoHeiAo9SHJOicVddVUz+pigJ/++4bCwPxTH6ohx72ZwTknCXifjeqcasI t76eTgTzwdSaOKLB1HWasauH5R7AW1oCgvqGBXNRKq1aR85avEVrUEEAyymk9Moy 9Hfm8XfZ2zsEMlJUsw9/F/oO3vqLkzSCCcTXABEBAAEAB/wN/5vXnsQQvUYRR500 7lDfd4TsFQirlvttDM/PCpBPRT1XD4QGD3qQDOF5+qA4NTY9h/VxJm72AWbdKX77 5xhe470Yw19PQzsE8HDOljtgsb51Vn7eq5TppLPQAyvLwfEE59O+eiISfbfokJek jav+zB/sHKC9tDAz85on43+HYutLTS53AfJMdhzCMxpt2jwEyGPH0Ti+4yAeOsSI v+J8YMHYeqMMp5Z1uWBEo4Kdh3R5BMNg2ovmW311ZW3dK363TG84jnhumU0yAaKy DOsLy6xM4Sm617JQn4oe4YWgfjcmAsFo5Ek78UHqnHA6qJtHmQqUtGJFXPhvR2Mq 0tstBADKReEkQiTsIYoQvJmmu6ShiNVJ2KtSezkE/ZtK+Ne9ww+5upAwWkB+FOxM +m53LkuKe8wK8ucIPb2ybVL3bqQb1REFbhf6o1H5mYnMKizcL3p0THuabtG9BG2Y /wt+hNw9nAhPuS8yQ7tETYHGPfdl7221qxhDO5QDlDBRmqXHKwQA2fHId+1po4BV ovRdbJxJ2uNhx+93RJORR3XnIs3tOrwD7bmt/B8zqoxi3FZ/414bwV2VPo6TMWV4 bNC6S0D+j3z2QLkGVp9woRaiC1+ZULwjugMl4Ou6oZNXT69wcGjdLw6rrvEl09y0 /qw3GzMgCn2ePVI16yqwV18wN662IwUD/1WvLpIyoCSALdp2lc17we+qbz/3Js/g tfkkBj/xP8GVZd+xnFHHoQ6EO8RFTstC6mCIDMKjkvaPJmqxOLdJeK1gpRIjIoj1 o6JvpEfapy/xb/XV9EVikmIjt+wNY9V1JkU0u8o85uirHdzi3atXd8EVR5u/Zejb ll2lNE7o1ltLRIu0CHRlc3Qga2V5iQFOBBMBCAA4FiEEttzyqc+V4Go/Bkjc3/2e kI1I8igFAlnCxAYCGwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQ3/2ekI1I 8ijrmwf6A1Bixs6NwT/LPW3MqjHW5n6FmoiZXBzNnOeBHk6FPI1qAADeZAQPMTq3 gKG2J5ciBQhpKGGqT31ovKkhlnpKaGUIaj8IAA7rI5UlbOTfTqVmjtpfYm43IGdl gccZvlxtWWKGYZSyMHg2DEC6SJYpR9AHxbh4UvKFuTx9hnpWjVasOqqIl0Zs+fT4 W5FHS9C5kxrA67+9Wn7V8RY0aXn0zPvg8KUzmGMeovt7bYRvK+l58MVMupQ/m01S pGgCzr9O7MAYsuJiWG7QoNriR8QsbAfsD70eNFSk4xKbpqXCqARfnHkDBU95WC57 bCw9mwgJ2r0mQLqjrXjEYBhaE49I8A== =+d52 -----END PGP PRIVATE KEY BLOCK----- ` golang-github-sassoftware-go-rpmutils-0.2.0/tags.go000066400000000000000000000133751455103626000223570ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rpmutils // tag data types const ( RPM_NULL_TYPE = 0 RPM_CHAR_TYPE = 1 RPM_INT8_TYPE = 2 RPM_INT16_TYPE = 3 RPM_INT32_TYPE = 4 RPM_INT64_TYPE = 5 RPM_STRING_TYPE = 6 RPM_BIN_TYPE = 7 RPM_STRING_ARRAY_TYPE = 8 RPM_I18NSTRING_TYPE = 9 ) // RPM header tags found in the general header const ( _GENERAL_TAG_BASE = 1000 NAME = 1000 VERSION = 1001 RELEASE = 1002 EPOCH = 1003 SUMMARY = 1004 DESCRIPTION = 1005 BUILDTIME = 1006 BUILDHOST = 1007 SIZE = 1009 DISTRIBUTION = 1010 VENDOR = 1011 GIF = 1012 XPM = 1013 LICENSE = 1014 PACKAGER = 1015 GROUP = 1016 CHANGELOG = 1017 SOURCE = 1018 PATCH = 1019 URL = 1020 OS = 1021 ARCH = 1022 PREIN = 1023 POSTIN = 1024 PREUN = 1025 POSTUN = 1026 OLDFILENAMES = 1027 FILESIZES = 1028 FILEMODES = 1030 FILERDEVS = 1033 FILEMTIMES = 1034 FILEDIGESTS = 1035 // AKA FILEMD5S FILELINKTOS = 1036 FILEFLAGS = 1037 // bitmask: RPMFILE_* are bitmasks to interpret FILEUSERNAME = 1039 FILEGROUPNAME = 1040 ICON = 1043 SOURCERPM = 1044 FILEVERIFYFLAGS = 1045 // bitmask: RPMVERIFY_* are bitmasks to interpret ARCHIVESIZE = 1046 PROVIDENAME = 1047 REQUIREFLAGS = 1048 REQUIRENAME = 1049 REQUIREVERSION = 1050 RPMVERSION = 1064 TRIGGERSCRIPTS = 1065 TRIGGERNAME = 1066 TRIGGERVERSION = 1067 TRIGGERFLAGS = 1068 // bitmask: RPMSENSE_* are bitmasks to interpret TRIGGERINDEX = 1069 VERIFYSCRIPT = 1079 CHANGELOGTIME = 1080 CHANGELOGNAME = 1081 CHANGELOGTEXT = 1082 PREINPROG = 1085 POSTINPROG = 1086 PREUNPROG = 1087 POSTUNPROG = 1088 OBSOLETENAME = 1090 FILEDEVICES = 1095 FILEINODES = 1096 PROVIDEFLAGS = 1112 PROVIDEVERSION = 1113 OBSOLETEFLAGS = 1114 OBSOLETEVERSION = 1115 VERIFYSCRIPTPROG = 1091 TRIGGERSCRIPTPROG = 1092 DIRINDEXES = 1116 BASENAMES = 1117 DIRNAMES = 1118 PAYLOADFORMAT = 1124 PAYLOADCOMPRESSOR = 1125 FILECOLORS = 1140 // BLINK*, FLINK*, and TRIGGERPREIN included from SUSE fork of RPM BLINKPKGID = 1164 BLINKHDRID = 1165 BLINKNEVRA = 1166 FLINKPKGID = 1167 FLINKHDRID = 1168 FLINKNEVRA = 1169 TRIGGERPREIN = 1170 LONGFILESIZES = 5008 LONGSIZE = 5009 FILECAPS = 5010 FILEDIGESTALGO = 5011 BUGURL = 5012 VCS = 5034 ENCODING = 5062 ) // RPM header tags found in the signature header const ( SIG_BASE = 256 SIG_DSA = SIG_BASE + 11 // DSA signature over header only SIG_RSA = SIG_BASE + 12 // RSA signature over header only SIG_SHA1 = SIG_BASE + 13 // SHA1 over header only (hex) SIG_LONGSIZE = SIG_BASE + 15 // header + compressed payload (uint64) SIG_LONGARCHIVESIZE = SIG_BASE + 15 // uncompressed payload bytes (uint64) // Given that there is overlap between signature tag headers and general tag // headers, we offset the signature ones by some amount _SIGHEADER_TAG_BASE = 16384 SIG_SIZE = _SIGHEADER_TAG_BASE + 1000 // Header + Payload size SIG_PGP = _SIGHEADER_TAG_BASE + 1002 // Signature over header + payload SIG_MD5 = _SIGHEADER_TAG_BASE + 1004 // MD5SUM of header + payload SIG_GPG = _SIGHEADER_TAG_BASE + 1005 // (same as SIG_PGP) SIG_PAYLOADSIZE = _SIGHEADER_TAG_BASE + 1007 // uncompressed payload bytes (uint32) SIG_RESERVEDSPACE = _SIGHEADER_TAG_BASE + 1008 // blank space that can be replaced by a signature ) // FILEFLAGS bitmask elements const ( RPMFILE_NONE = 0 RPMFILE_CONFIG = 1 << 0 RPMFILE_DOC = 1 << 1 RPMFILE_ICON = 1 << 2 RPMFILE_MISSINGOK = 1 << 3 RPMFILE_NOREPLACE = 1 << 4 RPMFILE_SPECFILE = 1 << 5 RPMFILE_GHOST = 1 << 6 RPMFILE_LICENSE = 1 << 7 RPMFILE_README = 1 << 8 RPMFILE_EXCLUDE = 1 << 9 RPMFILE_UNPATCHED = 1 << 10 RPMFILE_PUBKEY = 1 << 11 RPMFILE_POLICY = 1 << 12 ) // FILEVERIFYFLAGS bitmask elements const ( RPMVERIFY_NONE = 0 RPMVERIFY_MD5 = 1 << 0 RPMVERIFY_FILEDIGEST = 1 << 0 RPMVERIFY_FILESIZE = 1 << 1 RPMVERIFY_LINKTO = 1 << 2 RPMVERIFY_USER = 1 << 3 RPMVERIFY_GROUP = 1 << 4 RPMVERIFY_MTIME = 1 << 5 RPMVERIFY_MODE = 1 << 6 RPMVERIFY_RDEV = 1 << 7 RPMVERIFY_CAPS = 1 << 8 RPMVERIFY_CONTEXTS = 1 << 15 ) // TRIGGERFLAGS bitmask elements -- not all rpmsenseFlags make sense in TRIGGERFLAGS const ( RPMSENSE_ANY = 0 RPMSENSE_LESS = 1 << 1 RPMSENSE_GREATER = 1 << 2 RPMSENSE_EQUAL = 1 << 3 RPMSENSE_TRIGGERIN = 1 << 16 RPMSENSE_TRIGGERUN = 1 << 17 RPMSENSE_TRIGGERPOSTUN = 1 << 18 RPMSENSE_TRIGGERPREIN = 1 << 25 ) // Header region tags const ( RPMTAG_HEADERSIGNATURES = 62 RPMTAG_HEADERIMMUTABLE = 63 RPMTAG_HEADERREGIONS = 64 ) golang-github-sassoftware-go-rpmutils-0.2.0/testdata/000077500000000000000000000000001455103626000226725ustar00rootroot00000000000000golang-github-sassoftware-go-rpmutils-0.2.0/testdata/dotdot.cpio000066400000000000000000000010011455103626000250330ustar00rootroot0000000000000007070108ADFF22000081B4000003E8000003E8000000015EE38E8D00000000000000FD0000000000000000000000000000001000000000../../aaaaaaaaa07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!! golang-github-sassoftware-go-rpmutils-0.2.0/testdata/empty-0.1-1.x86_64.rpm000066400000000000000000000135411455103626000261430ustar00rootroot00000000000000empty-0:0.1-1> )lp 623404c4b29af89ecb630b8d93d1881e2fa6f8e45c2c96544fbdef9cd0d881100c296278349242d29f65a0963ec618a7ca13c8c3ɜmlF9]6YWb|>!}?md   ,0DH V\bi} ((X0Y8bHdefl(,Cempty0.11empty RPMDescription`o$hobgen.na.sas.comPublic DomainDummylinuxx86_64empty-0.1-1.src.rpmemptyempty(x86-64)    rpmlib(CompressedFileNames)rpmlib(FileDigests)rpmlib(PayloadFilesHavePrefix)rpmlib(PayloadIsZstd)3.0.4-14.6.0-14.0-15.4.18-14.16.00:0.1-10:0.1-1-O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protectioncpiozstd19x86_64-redhat-linux-gnuutf-8aaada29e7a9cab643ee3cc4eee876ea240668b776a129e9787b275f57c1e91d523d0422b4fea28f771e872741bb370790b3cd0538eafb461233e820b84b57a2e?(/h070701010bTRAILER!!!,8golang-github-sassoftware-go-rpmutils-0.2.0/testdata/empty.spec000066400000000000000000000002471455103626000247070ustar00rootroot00000000000000Name: empty Epoch: 0 Version: 0.1 Release: 1 Group: Dummy License: Public Domain Summary: empty RPM %description Description %prep %build %install %clean %files golang-github-sassoftware-go-rpmutils-0.2.0/testdata/foo.cpio000066400000000000000000000007601455103626000243340ustar00rootroot000000000000000707010007d311000081a40000000000000000000000014b50d55a00000007000000080000000600000000000000000000000900000000./configconfig 0707010007f2ce000041ed0000000000000000000000024b50d55a00000000000000080000000600000000000000000000000600000000./dir0707010007d314000081a40000000000000000000000014b50d55a00000007000000080000000600000000000000000000000900000000./normalnormal 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000b00000000TRAILER!!!golang-github-sassoftware-go-rpmutils-0.2.0/testdata/one-epoch-0.1-1.x86_64.rpm000066400000000000000000000144431455103626000266640ustar00rootroot00000000000000one-epoch-1:0.1-1> )lp ad2930d1fbfc98882f6291e82137def75730483658eb1a559a48e3cc6ce2e8279fc52c5894a2baa98c4035547c428e098afab4bd򳤾}/+>3?d   *8<PT bhnx| ~     (GHIXY\]^bd>eCfHlKtduhvlx|Cone-epoch0.11RPM with an epochDescription^㰯hobgen.na.sas.com Public DomainDummylinuxx86_64 ^㰯8557122088c994ba8aa5540ccbb9a3d2d8ae2887046c2db23d65f40ae63abaderootrootone-epoch-0.1-1.src.rpmone-epochone-epoch(x86-64)    rpmlib(CompressedFileNames)rpmlib(FileDigests)rpmlib(PayloadFilesHavePrefix)rpmlib(PayloadIsZstd)3.0.4-14.6.0-14.0-15.4.18-14.15.11:0.1-11:0.1-1one-epoch.txt/usr/share/-O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector-strong -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protectioncpiozstd19x86_64-redhat-linux-gnuASCII textutf-8a35e35b2fa351d0a850624df7bab3bb9ee245b414aef96e5e7e862983634e064?(/hU070701081a4015ee3b0afa01./usr/share/one-epoch.txtSome data 0bTRAILER!!! .!F KiJ 0golang-github-sassoftware-go-rpmutils-0.2.0/testdata/one-epoch.spec000066400000000000000000000010431455103626000254210ustar00rootroot00000000000000Name: one-epoch Epoch: 1 Version: 0.1 Release: 1 Group: Dummy License: Public Domain #Source: %{name}-%{version}.tar.gz BuildRoot: /var/tmp/%{name}-%{version}-root Summary: RPM with an epoch %description Description %global debug_package %{nil} %prep %setup -c -T %build %install rm -rf $RPM_BUILD_ROOT install -d $RPM_BUILD_ROOT # A regular file install -d $RPM_BUILD_ROOT/%{_datadir} cat > $RPM_BUILD_ROOT/%{_datadir}/%{name}.txt << EOF Some data EOF %clean rm -rf $RPM_BUILD_ROOT %files %defattr(0644,root,root) %{_datadir}/%{name}.txt golang-github-sassoftware-go-rpmutils-0.2.0/testdata/payload-test-0.1-w.ufdio.x86_64.rpm000066400000000000000000000150011455103626000306170ustar00rootroot00000000000000payload-test-0.1-w.ufdio> )lp eeba3ea26fe7fcc6bc3de2e21f8d2407352434161a6282733185e1aac5b057a12ab0e6e57314c7725b90b3c512357bd1e82e564ciK°S\5">2%?d  %48LP ^djtx z |   (w(GHIXY\]^bdOfTlUtputvxCpayload-test0.1w.ufdioDummy RPMDescription`hobgen.na.sas.com Public DomainDummylinuxx86_64 `8557122088c994ba8aa5540ccbb9a3d2d8ae2887046c2db23d65f40ae63abaderootrootpayload-test-0.1-w.ufdio.src.rpmpayload-testpayload-test(x86-64)   rpmlib(CompressedFileNames)rpmlib(FileDigests)rpmlib(PayloadFilesHavePrefix)3.0.4-14.6.0-14.0-14.16.00.1-w.ufdio0.1-w.ufdiopayload-test.txt/usr/share/-O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protectioncpiox86_64-redhat-linux-gnuASCII textutf-81b57b024194ad2c320abf57614af5d686ff4d3b991c33b733f90e01ca1b49c061b57b024194ad2c320abf57614af5d686ff4d3b991c33b733f90e01ca1b49c06?07070100000001000081a40000000000000000000000016010a0910000000a000000000000000000000000000000000000001d00000000./usr/share/payload-test.txtSome data 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000b00000000TRAILER!!!golang-github-sassoftware-go-rpmutils-0.2.0/testdata/payload-test-0.1-w3.zstdio.x86_64.rpm000066400000000000000000000146431455103626000311230ustar00rootroot00000000000000payload-test-0.1-w3.zstdio> )lp dae29dfdc576bd4f206ac8a12d2701c057f7490ffeeb94888564526eea20b4c54edc27284d5801cea5e73eff3dd0471877cbf42c {nח O*>3U?Ed  '48LP ^djtx z |    0(GHIXY\]^b d{efltuvCpayload-test0.1w3.zstdioDummy RPMDescription`hobgen.na.sas.com Public DomainDummylinuxx86_64 `8557122088c994ba8aa5540ccbb9a3d2d8ae2887046c2db23d65f40ae63abaderootrootpayload-test-0.1-w3.zstdio.src.rpmpayload-testpayload-test(x86-64)    rpmlib(CompressedFileNames)rpmlib(FileDigests)rpmlib(PayloadFilesHavePrefix)rpmlib(PayloadIsZstd)3.0.4-14.6.0-14.0-15.4.18-14.16.00.1-w3.zstdio0.1-w3.zstdiopayload-test.txt/usr/share/-O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protectioncpiozstd3x86_64-redhat-linux-gnuASCII textutf-83634fb6463e571d3b5f58f8a44868781d7456f0f77399c92a713bee0f81513fbe4b7529c8b06452a3390301595e37216c453a29f7606b4a068ed9566749478ea?(/Xm2p ted% VcBm[JQ  &$M>ƈq}=@1 TDrm)Sx  `golang-github-sassoftware-go-rpmutils-0.2.0/testdata/payload-test-0.1-w6.lzdio.x86_64.rpm000066400000000000000000000146361455103626000307350ustar00rootroot00000000000000payload-test-0.1-w6.lzdio> )lp 5779a69b2b98242e37fc30f31d839205eff89d7408c1abcbb0487ca00e3d321b8f74b87a287f8ba56edcaf1eabbe3d6ffb1fa718~eTL0բbS>3Q?Ad  &48LP ^djtx z |   ,(GHIXY\]^b dwe|fltuvCpayload-test0.1w6.lzdioDummy RPMDescription`hobgen.na.sas.com Public DomainDummylinuxx86_64 `8557122088c994ba8aa5540ccbb9a3d2d8ae2887046c2db23d65f40ae63abaderootrootpayload-test-0.1-w6.lzdio.src.rpmpayload-testpayload-test(x86-64)    rpmlib(CompressedFileNames)rpmlib(FileDigests)rpmlib(PayloadFilesHavePrefix)rpmlib(PayloadIsLzma)3.0.4-14.6.0-14.0-14.4.6-14.16.00.1-w6.lzdio0.1-w6.lzdiopayload-test.txt/usr/share/-O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protectioncpiolzma6x86_64-redhat-linux-gnuASCII textutf-8b81665bdddb67b1d81b1bd65ef5b0645e27a3eba9903d1d1a9e473c078f99272554441f9001c0de2eac0c9242b55c8c4775df57266f388d3af96dcdef8ed0672?] crv(vX0r. =V#^PD`x!b1[W;,m9nGΓ$J4L.6fhYInTؔu;0Tgolang-github-sassoftware-go-rpmutils-0.2.0/testdata/payload-test-0.1-w6.xzdio.x86_64.rpm000066400000000000000000000147411455103626000307460ustar00rootroot00000000000000payload-test-0.1-w6.xzdio> )lp c05a64b00b8378800962d3863b0e86d9bb7709aeb1799061b38989fea6e7f4ff4d00054b5036aee548b4f40933840e6aba023ee5I:K+qC.|>3M?=d  &48LP ^djtx z |   ,(GHIXY\]^bdsexf{l}tuvCpayload-test0.1w6.xzdioDummy RPMDescription`~hobgen.na.sas.com Public DomainDummylinuxx86_64 `~8557122088c994ba8aa5540ccbb9a3d2d8ae2887046c2db23d65f40ae63abaderootrootpayload-test-0.1-w6.xzdio.src.rpmpayload-testpayload-test(x86-64)    rpmlib(CompressedFileNames)rpmlib(FileDigests)rpmlib(PayloadFilesHavePrefix)rpmlib(PayloadIsXz)3.0.4-14.6.0-14.0-15.2-14.16.00.1-w6.xzdio0.1-w6.xzdiopayload-test.txt/usr/share/-O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protectioncpioxz6x86_64-redhat-linux-gnuASCII textutf-805451ac20785d23ed51da6c2b3ac5f2df8df3e0621649c418986d26e94e509f140c315474ab32fb99a5a92749198487643b38ed58022810c87a65c20b42174f9?7zXZ !t/b] crv(vX0r-lݯCzYkMFbu 3ɂ+D+g0eyv*+YRSWv~m@GJ/ZtHvCՀ" \ !t YZgolang-github-sassoftware-go-rpmutils-0.2.0/testdata/payload-test-0.1-w9.bzdio.x86_64.rpm000066400000000000000000000146641455103626000307270ustar00rootroot00000000000000payload-test-0.1-w9.bzdio> )lp 4c4c03b9a555f38b07fb08f431d371a405bef0c5a3ba8682e0467c903bed8515bb73af0aea51f4c1e58f75b0b4391e623ab49092ki7XĆ>3Q?Ad  &48LP ^djtx z |   ,(GHIXY\]^b dwe|fltuvCpayload-test0.1w9.bzdioDummy RPMDescription`xhobgen.na.sas.com Public DomainDummylinuxx86_64 `x8557122088c994ba8aa5540ccbb9a3d2d8ae2887046c2db23d65f40ae63abaderootrootpayload-test-0.1-w9.bzdio.src.rpmpayload-testpayload-test(x86-64)    rpmlib(CompressedFileNames)rpmlib(FileDigests)rpmlib(PayloadFilesHavePrefix)rpmlib(PayloadIsBzip2)3.0.4-14.6.0-14.0-13.0.5-14.16.00.1-w9.bzdio0.1-w9.bzdiopayload-test.txt/usr/share/-O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protectioncpiobzip29x86_64-redhat-linux-gnuASCII textutf-84bd689d0598c539ed838649518fb8d98d7722df0dd90f7fe44c4b7534892dc4c128765d59be55e5d719a768295eb34ac522163b432995de3ac9756aa5d12310f?BZh91AY&SYbJ"Lx"$6F` t*4z&P44i4jeSABSabtkN@/[@1@Ps+,B1nk`A3C_ DOH  [)_@golang-github-sassoftware-go-rpmutils-0.2.0/testdata/payload-test-0.1-w9.gzdio.x86_64.rpm000066400000000000000000000145751455103626000307350ustar00rootroot00000000000000payload-test-0.1-w9.gzdio> )lp a6b71fb0fbf5f0015d87ea1b290071a6a64eff432e8cf2f7e9c8277af6ba5c1f0e718c5dc4b2bc85534bbca6a416ba765d1ee9acG.ƚQ~,<>3-?d  &48LP ^djtx z |   (w(GHIXY\]^bdSeXf]l_txu|vCpayload-test0.1w9.gzdioDummy RPMDescription`ghobgen.na.sas.com Public DomainDummylinuxx86_64 `g8557122088c994ba8aa5540ccbb9a3d2d8ae2887046c2db23d65f40ae63abaderootrootpayload-test-0.1-w9.gzdio.src.rpmpayload-testpayload-test(x86-64)   rpmlib(CompressedFileNames)rpmlib(FileDigests)rpmlib(PayloadFilesHavePrefix)3.0.4-14.6.0-14.0-14.16.00.1-w9.gzdio0.1-w9.gzdiopayload-test.txt/usr/share/-O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protectioncpiogzip9x86_64-redhat-linux-gnuASCII textutf-847de7a7a8146d2adc56ea69aad82e1d9a264dd61cfeafc3e1a342708c6e9118c475183c3512aea2c7993e43796f50b552d66a37bd93a358bea89746dff002b1e?30707040mahb$CDKOH8#(U 2'?1E$D!8?7U!%$]-0 $!A>A @ś@golang-github-sassoftware-go-rpmutils-0.2.0/testdata/payload-test.spec000066400000000000000000000010461455103626000261550ustar00rootroot00000000000000Name: payload-test Version: 0.1 Group: Dummy Release: %{_binary_payload} License: Public Domain #Source: %{name}-%{version}.tar.gz BuildRoot: /var/tmp/%{name}-%{version}-root Summary: Dummy RPM %description Description %global debug_package %{nil} %prep %setup -c -T %build %install rm -rf $RPM_BUILD_ROOT install -d $RPM_BUILD_ROOT # A regular file install -d $RPM_BUILD_ROOT/%{_datadir} cat > $RPM_BUILD_ROOT/%{_datadir}/%{name}.txt << EOF Some data EOF %clean rm -rf $RPM_BUILD_ROOT %files %defattr(0644,root,root) %{_datadir}/%{name}.txt golang-github-sassoftware-go-rpmutils-0.2.0/testdata/simple-1.0.1-1.i386.rpm000066400000000000000000000035671455103626000261770ustar00rootroot00000000000000simple-1.0.1-1T>D ,0@ce490c5df5a6911e44177aca969c1e0a72323056_cx R?-#&>0?d   +04LP Zdjp|      ,T`(GHIXY\]^&b(dGeLfQlStdupv|wxCsimple1.0.11Test of owners and groupsjunkKPZbradley.rdu.rpath.comsomethingsomethinglinuxi386A큤KPZKPZKPZc71c9c5c45b37cdeda0702e35fc8dd262f3c3dd012fff07506bc8e641ab7ba13rootrootrootrootrootrootsimple-1.0.1-1.src.rpmconfig(simple)simplesimple(x86-32)  config(simple)rpmlib(CompressedFileNames)rpmlib(PayloadFilesHavePrefix)1.0.1-13.0.4-14.0-14.7.11.0.1-11.0.1-11.0.1-1configdirnormal/-O2 -g -march=i386 -mtune=i686cpiogzip9i386-rpath-linuxASCII textdirectory?3070704000O16&؁IAi"o-}0~r~^Zf:b0۟f Rcb#4 aJf@?yE9 bա$#$5HQQ,golang-github-sassoftware-go-rpmutils-0.2.0/testdata/stripped.cpio000066400000000000000000000002441455103626000254000ustar00rootroot0000000000000007070X00000000foo07070X00000001foobar07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000b00000000TRAILER!!!golang-github-sassoftware-go-rpmutils-0.2.0/testdata/zero-epoch-0.1-1.x86_64.rpm000066400000000000000000000144471455103626000270660ustar00rootroot00000000000000zero-epoch-0:0.1-1> )lp 7aa8020fdc207ee14973fb56538a27397269d6fb84a9dbe9149dcd56fe067d4a05a341949dbd95151d4155e6936ab9379b7b9745tY2pO>4ʋB>3?d   ,8<PT bhnx| ~    $(GHIXY\]^bdCeHfMlPthulvp|Czero-epoch0.11RPM with zero epochDescription^㰱hobgen.na.sas.com Public DomainDummylinuxx86_64 ^㰱8557122088c994ba8aa5540ccbb9a3d2d8ae2887046c2db23d65f40ae63abaderootrootzero-epoch-0.1-1.src.rpmzero-epochzero-epoch(x86-64)    rpmlib(CompressedFileNames)rpmlib(FileDigests)rpmlib(PayloadFilesHavePrefix)rpmlib(PayloadIsZstd)3.0.4-14.6.0-14.0-15.4.18-14.15.10:0.1-10:0.1-1zero-epoch.txt/usr/share/-O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector-strong -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protectioncpiozstd19x86_64-redhat-linux-gnuASCII textutf-8a42c847834dfb08ff03dfde29a0aa5d6a4c29cc42c9c8f15abaa9f6bcd42be08?(/hUEpiNjFh&=q-}YAYۆF5{pێZ!qoi6L$Β%h2ϫ nCF WZ}m*"ݱ0golang-github-sassoftware-go-rpmutils-0.2.0/testdata/zero-epoch.spec000066400000000000000000000010461455103626000256220ustar00rootroot00000000000000Name: zero-epoch Epoch: 0 Version: 0.1 Release: 1 Group: Dummy License: Public Domain #Source: %{name}-%{version}.tar.gz BuildRoot: /var/tmp/%{name}-%{version}-root Summary: RPM with zero epoch %description Description %global debug_package %{nil} %prep %setup -c -T %build %install rm -rf $RPM_BUILD_ROOT install -d $RPM_BUILD_ROOT # A regular file install -d $RPM_BUILD_ROOT/%{_datadir} cat > $RPM_BUILD_ROOT/%{_datadir}/%{name}.txt << EOF Some data EOF %clean rm -rf $RPM_BUILD_ROOT %files %defattr(0644,root,root) %{_datadir}/%{name}.txt golang-github-sassoftware-go-rpmutils-0.2.0/uncompress.go000066400000000000000000000046111455103626000236100ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rpmutils import ( "bytes" "compress/bzip2" "compress/gzip" "fmt" "io" "github.com/ulikunitz/xz/lzma" "github.com/xi2/xz" ) // Wrap RPM payload with uncompress reader, assumes that header has // already been read. func uncompressRpmPayloadReader(r io.Reader, hdr *RpmHeader) (io.Reader, error) { // Check to make sure payload format is a cpio archive. If the tag does // not exist, assume archive is cpio. if hdr.HasTag(PAYLOADFORMAT) { val, err := hdr.GetString(PAYLOADFORMAT) if err != nil { return nil, err } if val != "cpio" { return nil, fmt.Errorf("Unknown payload format %s", val) } } // Check to see how the payload was compressed. If the tag does not // exist, check if it is gzip, if not it is uncompressed. var compression string if hdr.HasTag(PAYLOADCOMPRESSOR) { val, err := hdr.GetString(PAYLOADCOMPRESSOR) if err != nil { return nil, err } compression = val } else { // peek at the start of the payload to see if it's compressed b := make([]byte, 2) _, err := io.ReadFull(r, b) if err != nil { return nil, err } if b[0] == 0x1f && b[1] == 0x8b { compression = "gzip" } else { compression = "uncompressed" } // splice the peeked bytes back in r = io.MultiReader(bytes.NewReader(b), r) } switch compression { case "zstd": return newZstdReader(r) case "gzip": return gzip.NewReader(r) case "bzip2": return bzip2.NewReader(r), nil case "lzma": return lzma.NewReader(r) case "xz": return xz.NewReader(r, 0) case "uncompressed": // prevent ExpandPayload from closing the underlying file return noCloseWrapper{r}, nil default: return nil, fmt.Errorf("Unknown compression type %s", compression) } } type noCloseWrapper struct { r io.Reader } func (w noCloseWrapper) Read(d []byte) (int, error) { return w.r.Read(d) } golang-github-sassoftware-go-rpmutils-0.2.0/uncompress_cgo.go000066400000000000000000000002511455103626000244340ustar00rootroot00000000000000// +build cgo package rpmutils import ( "io" "github.com/DataDog/zstd" ) func newZstdReader(r io.Reader) (io.ReadCloser, error) { return zstd.NewReader(r), nil } golang-github-sassoftware-go-rpmutils-0.2.0/uncompress_nocgo.go000066400000000000000000000006561455103626000250020ustar00rootroot00000000000000// +build !cgo package rpmutils import ( "io" "github.com/klauspost/compress/zstd" ) func newZstdReader(r io.Reader) (io.ReadCloser, error) { decoder, err := zstd.NewReader(r) if err != nil { return nil, err } return zstdCloser{Decoder: decoder}, nil } // wrap Decoder so it implements io.Closer properly type zstdCloser struct { *zstd.Decoder } func (d zstdCloser) Close() error { d.Decoder.Close() return nil } golang-github-sassoftware-go-rpmutils-0.2.0/uncompress_test.go000066400000000000000000000025651455103626000246550ustar00rootroot00000000000000package rpmutils import ( "io" "io/ioutil" "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/goleak" ) func TestUncompress(t *testing.T) { defer goleak.VerifyNone(t) // built with e.g.: rpmbuild -bb payload-test.spec -D'_binary_payload w.ufdio' payloadTypes := []string{"w3.zstdio", "w6.lzdio", "w6.xzdio", "w9.bzdio", "w9.gzdio", "w.ufdio"} for _, payloadType := range payloadTypes { t.Run(payloadType, func(t *testing.T) { // open rpm fp := filepath.Join("testdata", "payload-test-0.1-"+payloadType+".x86_64.rpm") f, err := os.Open(fp) require.NoError(t, err) defer f.Close() rpm, err := ReadRpm(f) require.NoError(t, err) // consume payload var files int payload, err := rpm.PayloadReaderExtended() require.NoError(t, err) for { _, err := payload.Next() if err == io.EOF { break } require.NoError(t, err) _, err = io.Copy(ioutil.Discard, payload) require.NoError(t, err) files++ } assert.Equal(t, 1, files) }) } } func TestUncompressEmpty(t *testing.T) { f, err := os.Open("testdata/empty-0.1-1.x86_64.rpm") require.NoError(t, err) defer f.Close() rpm, err := ReadRpm(f) require.NoError(t, err) payload, err := rpm.PayloadReaderExtended() require.NoError(t, err) _, err = payload.Next() assert.ErrorIs(t, err, io.EOF) } golang-github-sassoftware-go-rpmutils-0.2.0/vercmp.go000066400000000000000000000072051455103626000227100ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rpmutils import ( "regexp" "strings" ) var ( R_NONALNUMTILDE = regexp.MustCompile(`^([^a-zA-Z0-9~]*)(.*)$`) R_NUM = regexp.MustCompile(`^([\d]+)(.*)$`) R_ALPHA = regexp.MustCompile(`^([a-zA-Z]+)(.*)$`) ) // VersionSlice provides the Sort interface for sorting version strings. type VersionSlice []string // Len is the number of elements in the collection. func (vs VersionSlice) Len() int { return len(vs) } // Less reports wheather the element with index i should sort before the // element with index j. func (vs VersionSlice) Less(i, j int) bool { return Vercmp(vs[i], vs[j]) == -1 } // Swap swaps the elements with indexes i and j. func (vs VersionSlice) Swap(i, j int) { s1 := vs[i] vs[i] = vs[j] vs[j] = s1 } // Vercmp compares two version strings using the same algorithm as rpm uses. // Returns -1 if first < second, 1 if first > second, and 0 if first == second. func Vercmp(first, second string) int { var m1Head, m2Head string for first != "" || second != "" { m1 := R_NONALNUMTILDE.FindStringSubmatch(first) m2 := R_NONALNUMTILDE.FindStringSubmatch(second) // This probably needs to return something different. if m1 == nil || m2 == nil { return 0 } m1Head, first = m1[1], m1[2] m2Head, second = m2[1], m2[2] // Ignore junk at begining of version. if m1Head != "" || m2Head != "" { continue } // Hnalde the tolde seporator, it sorts before everything else. if strings.HasPrefix(first, "~") { if !strings.HasPrefix(second, "~") { return -1 } first, second = first[1:], second[1:] continue } if strings.HasPrefix(second, "~") { return 1 } // If we ran to the end of either, we are finished with the loop. if first == "" || second == "" { break } // Grab the first completely alpha or completely numeric segment. isnum := false m1 = R_NUM.FindStringSubmatch(first) m2 = R_NUM.FindStringSubmatch(second) if R_NUM.MatchString(first) { if !R_NUM.MatchString(second) { // numeric segments are always newer than alpha segments. return 1 } isnum = true } else { m1 = R_ALPHA.FindStringSubmatch(first) m2 = R_ALPHA.FindStringSubmatch(second) } if len(m1) == 0 { // This condition should not be reached since we previously // tested to make sure that the first string has a non-nill // segment. return -1 } if len(m2) == 0 { if isnum { return 1 } return -1 } m1Head, first = m1[1], m1[2] m2Head, second = m2[1], m2[2] if isnum { // Throw away any leading zeros. m1Head = strings.TrimLeft(m1Head, "0") m2Head = strings.TrimLeft(m2Head, "0") // Whichever number has more digits wins m1Hlen := len(m1Head) m2Hlen := len(m2Head) if m1Hlen < m2Hlen { return -1 } if m1Hlen > m2Hlen { return 1 } } // Same number of chars if m1Head < m2Head { return -1 } else if m1Head > m2Head { return 1 } // Both segments equal continue } m1Hlen := len(first) m2Hlen := len(second) if m1Hlen == 0 && m2Hlen == 0 { return 0 } if m1Hlen != 0 { return 1 } return -1 } golang-github-sassoftware-go-rpmutils-0.2.0/vercmp_test.go000066400000000000000000000020711455103626000237430ustar00rootroot00000000000000package rpmutils import ( "testing" "github.com/stretchr/testify/assert" ) func TestVercmp(t *testing.T) { // from rpm/tests/rpmvercmp.at var values = [][]string{ {"1.0", "2.0"}, {"2.0", "2.0.1"}, {"2.0.1", "2.0.1a"}, {"5.5p1", "5.5p2"}, {"5.5p1", "5.5p10"}, {"10xyz", "10.1xyz"}, {"xyz10", "xyz10.1"}, {"xyz.4", "8"}, {"xyz.4", "2"}, {"5.5p2", "5.6p1"}, {"5.6p1", "6.5p1"}, {"6.0", "6.0.rc1"}, {"10a2", "10b2"}, {"1.0a", "1.0aa"}, {"10.0001", "10.0039"}, {"4.999.9", "5.0"}, {"20101121", "20101122"}, {"1.0~rc1", "1.0"}, {"1.0~rc1", "1.0~rc2"}, {"1.0~rc1~git123", "1.0~rc1"}, // {"1.0", "1.0^"}, {"1.0", "1.0^git1"}, {"1.0^git1", "1.0^git2"}, {"1.0^git1", "1.01"}, // {"1.0^20160101", "1.0.1"}, {"1.0^20160101^git1", "1.0^20160102"}, {"1.0~rc1", "1.0~rc1^git1"}, {"1.0^git1~pre", "1.0^git1"}, } for _, v := range values { assert.Equal(t, -1, Vercmp(v[0], v[1]), "expected: %s should be less than %s", v[0], v[1]) assert.Equal(t, 1, Vercmp(v[1], v[0]), "expected: %s should be greater than %s", v[1], v[0]) } } golang-github-sassoftware-go-rpmutils-0.2.0/verify.go000066400000000000000000000134351455103626000227220ustar00rootroot00000000000000/* * Copyright (c) SAS Institute Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rpmutils import ( "bytes" "crypto" "crypto/md5" "errors" "fmt" "hash" "io" "time" "golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp/packet" ) var headerSigTags = []int{SIG_RSA, SIG_DSA} var payloadSigTags = []int{ SIG_PGP - _SIGHEADER_TAG_BASE, SIG_GPG - _SIGHEADER_TAG_BASE, } // Signature describes a PGP signature found within a RPM while verifying it. type Signature struct { // Signer is the PGP identity that created the signature. It may be nil if // the public key is not available at verification time, but KeyId will // always be set. Signer *openpgp.Entity // Hash is the algorithm used to digest the signature contents Hash crypto.Hash // CreationTime is when the signature was created CreationTime time.Time // HeaderOnly is true for signatures that only cover the general RPM header, // and false for signatures that cover the general header plus the payload HeaderOnly bool // KeyId is the PGP key that created the signature. KeyId uint64 packet packet.Packet hash hash.Hash } // Verify the PGP signature over a RPM file. knownKeys should enumerate public // keys to check against, otherwise the signature validity cannot be verified. // If knownKeys is nil then digests will be checked but only the raw key ID will // be available. func Verify(stream io.Reader, knownKeys openpgp.EntityList) (header *RpmHeader, sigs []*Signature, err error) { lead, sigHeader, err := readSignatureHeader(stream) if err != nil { return nil, nil, err } payloadDigest := md5.New() sigs, headerMulti, payloadMulti, err := setupDigesters(sigHeader, payloadDigest) if err != nil { return nil, nil, err } // parse the general header and also tee it into a buffer genHeaderBuf := new(bytes.Buffer) headerTee := io.TeeReader(stream, genHeaderBuf) genHeader, err := readHeader(headerTee, getSha1(sigHeader), sigHeader.isSource, false) if err != nil { return nil, nil, err } genHeaderBlob := genHeaderBuf.Bytes() if _, err := headerMulti.Write(genHeaderBlob); err != nil { return nil, nil, err } // chain the buffered general header to the rest of the payload, and digest the whole lot of it genHeaderAndPayload := io.MultiReader(bytes.NewReader(genHeaderBlob), stream) if _, err := io.Copy(payloadMulti, genHeaderAndPayload); err != nil { return nil, nil, err } if !checkMd5(sigHeader, payloadDigest) { return nil, nil, errors.New("md5 digest mismatch") } if knownKeys != nil { for _, sig := range sigs { if err := checkSig(sig, knownKeys); err != nil { return nil, nil, err } } } hdr := &RpmHeader{ lead: lead, sigHeader: sigHeader, genHeader: genHeader, isSource: sigHeader.isSource, } return hdr, sigs, nil } func setupDigester(sigHeader *rpmHeader, tag int) (*Signature, error) { blob, err := sigHeader.GetBytes(tag) if _, ok := err.(NoSuchTagError); ok { return nil, nil } else if err != nil { return nil, err } packetReader := packet.NewReader(bytes.NewReader(blob)) genpkt, err := packetReader.Next() if err != nil { return nil, err } var sig *Signature switch pkt := genpkt.(type) { case *packet.SignatureV3: sig = &Signature{ Hash: pkt.Hash, CreationTime: pkt.CreationTime, KeyId: pkt.IssuerKeyId, } case *packet.Signature: if pkt.IssuerKeyId == nil { return nil, errors.New("Missing keyId in signature") } sig = &Signature{ Hash: pkt.Hash, CreationTime: pkt.CreationTime, KeyId: *pkt.IssuerKeyId, } default: return nil, fmt.Errorf("tag %d does not contain a PGP signature", tag) } _, err = packetReader.Next() if err != io.EOF { return nil, fmt.Errorf("trailing garbage after signature in tag %d", tag) } sig.packet = genpkt if !sig.Hash.Available() { return nil, errors.New("signature uses unknown digest") } sig.hash = sig.Hash.New() return sig, nil } func setupDigesters(sigHeader *rpmHeader, payloadDigest io.Writer) ([]*Signature, io.Writer, io.Writer, error) { sigs := make([]*Signature, 0) headerWriters := make([]io.Writer, 0) payloadWriters := []io.Writer{payloadDigest} for _, tag := range headerSigTags { sig, err := setupDigester(sigHeader, tag) if err != nil { return nil, nil, nil, err } else if sig == nil { continue } sig.HeaderOnly = true headerWriters = append(headerWriters, sig.hash) sigs = append(sigs, sig) } for _, tag := range payloadSigTags { sig, err := setupDigester(sigHeader, tag) if err != nil { return nil, nil, nil, err } else if sig == nil { continue } sig.HeaderOnly = false payloadWriters = append(payloadWriters, sig.hash) sigs = append(sigs, sig) } headerMulti := io.MultiWriter(headerWriters...) payloadMulti := io.MultiWriter(payloadWriters...) return sigs, headerMulti, payloadMulti, nil } func checkSig(sig *Signature, knownKeys openpgp.EntityList) error { keys := knownKeys.KeysById(sig.KeyId) if keys == nil { return fmt.Errorf("keyid %x not found", sig.KeyId) } key := keys[0] sig.Signer = key.Entity var err error switch pkt := sig.packet.(type) { case *packet.Signature: err = key.PublicKey.VerifySignature(sig.hash, pkt) case *packet.SignatureV3: err = key.PublicKey.VerifySignatureV3(sig.hash, pkt) } if err != nil { return err } sig.packet = nil sig.hash = nil return nil } golang-github-sassoftware-go-rpmutils-0.2.0/writeheader.go000066400000000000000000000120701455103626000237130ustar00rootroot00000000000000/* * Copyright (c) SAS Institute, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rpmutils import ( "bytes" "encoding/binary" "errors" "io" "sort" ) func writeTag(entries io.Writer, blobs *bytes.Buffer, tag int, e entry) error { align, ok := typeAlign[e.dataType] if ok { n := blobs.Len() % align if n != 0 { if _, err := blobs.Write(make([]byte, align-n)); err != nil { return err } } } ht := headerTag{ Tag: int32(tag), DataType: int32(e.dataType), Offset: int32(blobs.Len()), Count: int32(e.count), } if err := binary.Write(entries, binary.BigEndian, &ht); err != nil { return err } if _, err := blobs.Write(e.contents); err != nil { return err } return nil } func writeRegion(entries io.Writer, blobs *bytes.Buffer, regionTag int, tagCount int) error { // The data for a region tag is also in the format of a tag, and its offset // points backwards to the first tag that's part of the region. This one // covers the whole header. regionValue := headerTag{ Tag: int32(regionTag), DataType: RPM_BIN_TYPE, Offset: int32(-16 * (1 + tagCount)), Count: 16, } regionBuf := bytes.NewBuffer(make([]byte, 0, 16)) if err := binary.Write(regionBuf, binary.BigEndian, ®ionValue); err != nil { return err } regionEntry := entry{dataType: RPM_BIN_TYPE, count: 16, contents: regionBuf.Bytes()} return writeTag(entries, blobs, regionTag, regionEntry) } // WriteTo writes the header out, adding a region tag encompassing all the existing tags func (hdr *rpmHeader) WriteTo(outfile io.Writer, regionTag int) error { if regionTag != 0 && regionTag >= RPMTAG_HEADERREGIONS { return errors.New("invalid region tag") } // sort tags var keys []int for k := range hdr.entries { if k < RPMTAG_HEADERREGIONS { // discard existing regions continue } keys = append(keys, k) } sort.Ints(keys) entries := bytes.NewBuffer(make([]byte, 0, 16*len(keys))) blobs := bytes.NewBuffer(make([]byte, 0, hdr.origSize)) for _, k := range keys { if k == regionTag { continue } if err := writeTag(entries, blobs, k, hdr.entries[k]); err != nil { return err } } intro := headerIntro{ Magic: introMagic, Reserved: 0, Entries: uint32(len(keys) + 1), Size: uint32(blobs.Len() + 16), } if err := binary.Write(outfile, binary.BigEndian, &intro); err != nil { return err } if err := writeRegion(outfile, blobs, regionTag, len(keys)); err != nil { return err } totalSize := 96 + blobs.Len() + entries.Len() if _, err := io.Copy(outfile, entries); err != nil { return err } if _, err := io.Copy(outfile, blobs); err != nil { return err } if regionTag == RPMTAG_HEADERSIGNATURES { alignment := totalSize % 8 if alignment != 0 { if _, err := outfile.Write(make([]byte, 8-alignment)); err != nil { return err } } } return nil } func (hdr *rpmHeader) size(regionTag int) (uint64, error) { var sink byteCountSink if err := hdr.WriteTo(&sink, regionTag); err != nil { return 0, err } return uint64(sink), nil } type byteCountSink uint64 func (sink *byteCountSink) Write(data []byte) (int, error) { *sink += byteCountSink(len(data)) return len(data), nil } // OriginalSignatureHeaderSize returns the size of the lead and signature header // area as originally read from the file. func (hdr *RpmHeader) OriginalSignatureHeaderSize() int { return hdr.sigHeader.origSize + 96 } // DumpSignatureHeader dumps the lead and signature header, optionally adding or // changing padding to make it the same size as when it was originally read. // Otherwise padding is removed to make it as small as possible. // // A RPM can be signed by removing the first OriginalSignatureHeaderSize() bytes // of the file and replacing it with the result of DumpSignatureHeader(). func (hdr *RpmHeader) DumpSignatureHeader(sameSize bool) ([]byte, error) { if len(hdr.lead) != 96 { return nil, errors.New("invalid or missing RPM lead") } sigh := hdr.sigHeader regionTag := RPMTAG_HEADERSIGNATURES delete(sigh.entries, SIG_RESERVEDSPACE-_SIGHEADER_TAG_BASE) if sameSize { needed, err := sigh.size(regionTag) if err != nil { return nil, err } available := uint64(sigh.origSize) if needed+16 <= available { // Fill unused space with a RESERVEDSPACE tag padding := make([]byte, available-needed-16) sigh.entries[SIG_RESERVEDSPACE-_SIGHEADER_TAG_BASE] = entry{ dataType: RPM_BIN_TYPE, count: int32(len(padding)), contents: padding, } } } buf := new(bytes.Buffer) buf.Write(hdr.lead) if err := sigh.WriteTo(buf, regionTag); err != nil { return nil, err } return buf.Bytes(), nil }