1
1
mirror of https://github.com/goreleaser/nfpm synced 2024-10-01 06:39:37 +02:00
nfpm/internal/rpmpack/header.go
2023-03-04 12:54:42 -03:00

210 lines
6.0 KiB
Go

// Copyright 2019 Google LLC
//
// 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 rpmpack
import (
"bytes"
"encoding/binary"
"fmt"
"sort"
)
const (
signatures = 0x3e
immutable = 0x3f
typeInt16 = 0x03
typeInt32 = 0x04
typeString = 0x06
typeBinary = 0x07
typeStringArray = 0x08
)
// Only integer types are aligned. This is not just an optimization - some versions
// of rpm fail when integers are not aligned. Other versions fail when non-integers are aligned.
var boundaries = map[int]int{
typeInt16: 2,
typeInt32: 4,
}
type IndexEntry struct {
rpmtype, count int
data []byte
}
func (e IndexEntry) indexBytes(tag, contentOffset int) []byte {
b := &bytes.Buffer{}
if err := binary.Write(b, binary.BigEndian, []int32{int32(tag), int32(e.rpmtype), int32(contentOffset), int32(e.count)}); err != nil {
// binary.Write can fail if the underlying Write fails, or the types are invalid.
// bytes.Buffer's write never error out, it can only panic with OOM.
panic(err)
}
return b.Bytes()
}
func intEntry(rpmtype, size int, value interface{}) IndexEntry {
b := &bytes.Buffer{}
if err := binary.Write(b, binary.BigEndian, value); err != nil {
// binary.Write can fail if the underlying Write fails, or the types are invalid.
// bytes.Buffer's write never error out, it can only panic with OOM.
panic(err)
}
return IndexEntry{rpmtype, size, b.Bytes()}
}
func EntryInt16(value []int16) IndexEntry {
return intEntry(typeInt16, len(value), value)
}
func EntryUint16(value []uint16) IndexEntry {
return intEntry(typeInt16, len(value), value)
}
func EntryInt32(value []int32) IndexEntry {
return intEntry(typeInt32, len(value), value)
}
func EntryUint32(value []uint32) IndexEntry {
return intEntry(typeInt32, len(value), value)
}
func EntryString(value string) IndexEntry {
return IndexEntry{typeString, 1, append([]byte(value), byte(0o0))}
}
func EntryBytes(value []byte) IndexEntry {
return IndexEntry{typeBinary, len(value), value}
}
func EntryStringSlice(value []string) IndexEntry {
b := [][]byte{}
for _, v := range value {
b = append(b, []byte(v))
}
bb := append(bytes.Join(b, []byte{0o0}), byte(0o0))
return IndexEntry{typeStringArray, len(value), bb}
}
type index struct {
entries map[int]IndexEntry
h int
}
func newIndex(h int) *index {
return &index{entries: make(map[int]IndexEntry), h: h}
}
func (i *index) Add(tag int, e IndexEntry) {
i.entries[tag] = e
}
func (i *index) AddEntries(m map[int]IndexEntry) {
for t, e := range m {
i.Add(t, e)
}
}
func (i *index) sortedTags() []int {
t := []int{}
for k := range i.entries {
t = append(t, k)
}
sort.Ints(t)
return t
}
func pad(w *bytes.Buffer, rpmtype, offset int) {
// We need to align integer entries...
if b, ok := boundaries[rpmtype]; ok && offset%b != 0 {
if _, err := w.Write(make([]byte, b-offset%b)); err != nil {
// binary.Write can fail if the underlying Write fails, or the types are invalid.
// bytes.Buffer's write never error out, it can only panic with OOM.
panic(err)
}
}
}
// Bytes returns the bytes of the index.
func (i *index) Bytes() ([]byte, error) {
w := &bytes.Buffer{}
// Even the header has three parts: The lead, the index entries, and the entries.
// Because of alignment, we can only tell the actual size and offset after writing
// the entries.
entryData := &bytes.Buffer{}
tags := i.sortedTags()
offsets := make([]int, len(tags))
for ii, tag := range tags {
e := i.entries[tag]
pad(entryData, e.rpmtype, entryData.Len())
offsets[ii] = entryData.Len()
entryData.Write(e.data)
}
entryData.Write(i.eigenHeader().data)
// 4 magic and 4 reserved
w.Write([]byte{0x8e, 0xad, 0xe8, 0x01, 0, 0, 0, 0})
// 4 count and 4 size
// We add the pseudo-entry "eigenHeader" to count.
if err := binary.Write(w, binary.BigEndian, []int32{int32(len(i.entries)) + 1, int32(entryData.Len())}); err != nil {
return nil, fmt.Errorf("failed to write eigenHeader: %w", err)
}
// Write the eigenHeader index entry
w.Write(i.eigenHeader().indexBytes(i.h, entryData.Len()-0x10))
// Write all of the other index entries
for ii, tag := range tags {
e := i.entries[tag]
w.Write(e.indexBytes(tag, offsets[ii]))
}
w.Write(entryData.Bytes())
return w.Bytes(), nil
}
// the eigenHeader is a weird entry. Its index entry is sorted first, but its content
// is last. The content is a 16 byte index entry, which is almost the same as the index
// entry except for the offset. The offset here is ... minus the length of the index entry region.
// Which is always 0x10 * number of entries.
// I kid you not.
func (i *index) eigenHeader() IndexEntry {
b := &bytes.Buffer{}
if err := binary.Write(b, binary.BigEndian, []int32{int32(i.h), int32(typeBinary), -int32(0x10 * (len(i.entries) + 1)), int32(0x10)}); err != nil {
// binary.Write can fail if the underlying Write fails, or the types are invalid.
// bytes.Buffer's write never error out, it can only panic with OOM.
panic(err)
}
return EntryBytes(b.Bytes())
}
func lead(name, fullVersion string) []byte {
// RPM format = 0xedabeedb
// version 3.0 = 0x0300
// type binary = 0x0000
// machine archnum (i386?) = 0x0001
// name ( 66 bytes, with null termination)
// osnum (linux?) = 0x0001
// sig type (header-style) = 0x0005
// reserved 16 bytes of 0x00
n := []byte(fmt.Sprintf("%s-%s", name, fullVersion))
if len(n) > 65 {
n = n[:65]
}
n = append(n, make([]byte, 66-len(n))...)
b := []byte{0xed, 0xab, 0xee, 0xdb, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01}
b = append(b, n...)
b = append(b, []byte{0x00, 0x01, 0x00, 0x05}...)
b = append(b, make([]byte, 16)...)
return b
}