refactor kernel_repack with new features to ease usage and maintenance

fairly large change, but has been tested and in use for a while.

* new command: getchangelog -
* new command: updatekernel - run an update
This commit is contained in:
Neil Hanlon 2024-08-15 21:10:58 -04:00
parent 850969184d
commit f3f72c3af0
Signed by: neil
GPG Key ID: 705BC21EC3C70F34
33 changed files with 8684 additions and 7973 deletions

View File

@ -0,0 +1,75 @@
package main
import (
"encoding/json"
"fmt"
"os"
"strings"
"github.com/pkg/errors"
repack_v1 "go.resf.org/peridot/tools/kernelmanager/packager/v1"
)
func readChangelogFromSpec(specBytes []byte) []*repack_v1.ChangelogEntry {
// Parse spec down to %changelog
var changelogLines []string
lines := strings.Split(string(specBytes), "\n")
for i, line := range lines {
if strings.Contains(line, "%changelog") {
changelogLines = lines[i+1:]
break
}
}
// Parse changelog
var entries []*repack_v1.ChangelogEntry
currentEntry := &repack_v1.ChangelogEntry{}
for _, line := range changelogLines {
line = strings.TrimSpace(line)
if line == "" {
if currentEntry.Subject != "" {
entries = append(entries, currentEntry)
currentEntry = &repack_v1.ChangelogEntry{}
}
continue
}
if strings.HasPrefix(line, "* ") {
currentEntry.Subject = strings.TrimPrefix(line, "* ")
} else if strings.HasPrefix(line, "- ") {
currentEntry.Messages = append(currentEntry.Messages, strings.TrimPrefix(line, "- "))
}
}
if currentEntry.Subject != "" {
entries = append(entries, currentEntry)
}
return entries
}
func main() {
args := os.Args[1:]
if len(args) == 0 {
panic("usage: getchangelog <path to spec>")
}
specPath := args[0]
specBytes, err := os.ReadFile(specPath)
if err != nil {
panic(errors.Wrap(err, "failed to read spec file"))
}
entries := readChangelogFromSpec(specBytes)
jsonBytes, err := json.Marshal(entries)
if err != nil {
panic(errors.Wrap(err, "failed to marshal entries"))
}
_, err = fmt.Println(string(jsonBytes))
if err != nil {
panic(errors.Wrap(err, "failed to write entries"))
}
}

View File

@ -0,0 +1,42 @@
package main
import (
"fmt"
"os"
"testing"
"github.com/stretchr/testify/require"
)
func readSpec(t *testing.T, name string) []byte {
specBytes, err := os.ReadFile(fmt.Sprintf("testdata/%s.spec", name))
require.Nil(t, err)
return specBytes
}
func TestReadChangelogFromSpec_1(t *testing.T) {
specBytes := readSpec(t, "1")
entries := readChangelogFromSpec(specBytes)
require.Len(t, entries, 1)
require.Equal(t, "Tue Feb 22 2024 Mustafa Gezen - 1.0-123", entries[0].Subject)
require.Len(t, entries[0].Messages, 1)
require.Equal(t, "Test changelog", entries[0].Messages[0])
}
func TestReadChangelogFromSpec_2(t *testing.T) {
specBytes := readSpec(t, "2")
entries := readChangelogFromSpec(specBytes)
require.Len(t, entries, 2)
require.Equal(t, "Tue Feb 22 2024 Mustafa Gezen - 1.0-123", entries[0].Subject)
require.Len(t, entries[0].Messages, 1)
require.Equal(t, "Test changelog", entries[0].Messages[0])
require.Equal(t, "Tue Feb 22 2024 Mustafa Gezen - 1.0-123", entries[1].Subject)
require.Len(t, entries[1].Messages, 2)
require.Equal(t, "msg1 2", entries[1].Messages[0])
require.Equal(t, "msg2", entries[1].Messages[1])
}

View File

@ -0,0 +1,172 @@
# All global changes to build and install should follow this line.
# Disable LTO in userspace packages.
%global _lto_cflags %{nil}
# The libexec directory is not used by the linker, so the shared object there
# should not be exported to RPM provides.
%global __provides_exclude_from ^%{_libexecdir}/kselftests
# Disable the find-provides.ksyms script.
%global __provided_ksyms_provides %{nil}
# All global wide changes should be above this line otherwise
# the %%install section will not see them.
%global __spec_install_pre %{___build_pre}
# Kernel has several large (hundreds of mbytes) rpms, they take ~5 mins
# to compress by single-threaded xz. Switch to threaded compression,
# and from level 2 to 3 to keep compressed sizes close to "w2" results.
#
# NB: if default compression in /usr/lib/rpm/redhat/macros ever changes,
# this one might need tweaking (e.g. if default changes to w3.xzdio,
# change below to w4T.xzdio):
%global _binary_payload w3T.xzdio
# Define the version of the Linux Kernel Archive tarball.
%global LKAver 1.0
# Define the buildid, if required.
%global buildid 123
# Determine the sublevel number and set pkg_version.
%define sublevel %(echo %{LKAver} | %{__awk} -F\. '{ print $3 }')
%if "%{sublevel}" == ""
%global pkg_version %{LKAver}.0
%else
%global pkg_version %{LKAver}
%endif
# Set pkg_release.
%global pkg_release 1%{?buildid}%{?dist}
# Architectures upon which we can sign the kernel
# for secure boot authentication.
%ifarch x86_64 || aarch64
%global signkernel 1
%else
%global signkernel 0
%endif
# Sign modules on all architectures that build modules.
%ifarch x86_64 || aarch64
%global signmodules 1
%else
%global signmodules 0
%endif
# Compress modules on all architectures that build modules.
%ifarch x86_64 || aarch64
%global zipmodules 1
%else
%global zipmodules 0
%endif
%if %{zipmodules}
%global zipsed -e 's/\.ko$/\.ko.xz/'
# For parallel xz processes. Replace with 1 to go back to single process.
%global zcpu `nproc --all`
%endif
# The following build options are enabled by default, but may become disabled
# by later architecture-specific checks. These can also be disabled by using
# --without <opt> in the rpmbuild command, or by forcing these values to 0.
#
# {{.KernelPackage}}
%define with_std %{?_without_std: 0} %{?!_without_std: 1}
#
# {{.KernelPackage}}-headers
%define with_headers %{?_without_headers: 0} %{?!_without_headers: 1}
#
# {{.KernelPackage}}-doc
%define with_doc %{?_without_doc: 0} %{?!_without_doc: 1}
#
# perf
%define with_perf %{?_without_perf: 0} %{?!_without_perf: 1}
#
# tools
%define with_tools %{?_without_tools: 0} %{?!_without_tools: 1}
#
# bpf tool
%define with_bpftool %{?_without_bpftool: 0} %{?!_without_bpftool: 1}
#
# control whether to install the vdso directories
%define with_vdso_install %{?_without_vdso_install: 0} %{?!_without_vdso_install: 1}
#
# Additional option for toracat-friendly, one-off, {{.KernelPackage}} building.
# Only build the base {{.KernelPackage}} (--with baseonly):
%define with_baseonly %{?_with_baseonly: 1} %{?!_with_baseonly: 0}
%global KVERREL %{pkg_version}-%{pkg_release}.%{_target_cpu}
# If requested, only build base {{.KernelPackage}} package.
%if %{with_baseonly}
%define with_doc 0
%define with_perf 0
%define with_tools 0
%define with_bpftool 0
%define with_vdso_install 0
%endif
%ifarch noarch
%define with_std 0
%define with_headers 0
%define with_perf 0
%define with_tools 0
%define with_bpftool 0
%define with_vdso_install 0
%endif
%ifarch x86_64 || aarch64
%define with_doc 0
### as of {{.KernelPackage}}-6.5.4, no more perf and bpftool -ay
%define with_perf 0
%define with_bpftool 0
%endif
%ifarch x86_64
%define asmarch x86
%define bldarch x86_64
%define hdrarch x86_64
%define make_target bzImage
%define kernel_image arch/x86/boot/bzImage
%endif
%ifarch aarch64
%define asmarch arm64
%define bldarch arm64
%define hdrarch arm64
%define make_target Image.gz
%define kernel_image arch/arm64/boot/Image.gz
%endif
%if %{with_vdso_install}
%define use_vdso 1
%define _use_vdso 1
%else
%define _use_vdso 0
%endif
#
# Packages that need to be installed before the kernel is installed,
# as they will be used by the %%post scripts.
#
%define kernel_ml_prereq coreutils, systemd >= 203-2, /usr/bin/kernel-install
%define initrd_prereq dracut >= 027
Name: kernel
Summary: The Linux kernel. (The core of any Linux kernel based operating system.)
License: GPLv2 and Redistributable, no modification permitted.
URL: https://www.kernel.org/
Version: %{pkg_version}
Release: %{pkg_release}
ExclusiveArch: x86_64 aarch64 noarch
ExclusiveOS: Linux
Provides: kernel = %{version}-%{release}
Provides: installonlypkg(kernel)
Requires: %{name}-core-uname-r = %{KVERREL}
Requires: %{name}-modules-uname-r = %{KVERREL}
%changelog
* Tue Feb 22 2024 Mustafa Gezen - 1.0-123
- Test changelog

View File

@ -0,0 +1,10 @@
%changelog
* Tue Feb 22 2024 Mustafa Gezen - 1.0-123
- Test changelog
* Tue Feb 22 2024 Mustafa Gezen - 1.0-123
- msg1 2
- msg2

View File

@ -0,0 +1,261 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"
"go.resf.org/peridot/tools/kernelmanager/packager/kernelorg"
"go.resf.org/peridot/tools/kernelmanager/packager"
repack_v1 "go.resf.org/peridot/tools/kernelmanager/packager/v1"
"github.com/go-git/go-billy/v5/osfs"
)
// readCustomConfigs reads custom kernel configs from a directory
// There are two options for declaring configs
// - A file with the name "variant-X.Y.config" where X.Y is the version. This is common arch-independent config
// - A file with the name "variant-X.Y-ARCH.config" where X.Y is the version and arch is the architecture. This is arch-specific config
// - Stable does not have a version, so the file name will be "stable.config" or "stable-ARCH.config"
//
// Both approaches can be used for the same variant and version. Both can be present at the same time and merges into the slice
func readCustomConfigs(directory string, variant string, version string) ([]*repack_v1.KernelConfig, error) {
version = strings.TrimSuffix(version, ".")
var configs []*repack_v1.KernelConfig
files, err := os.ReadDir(directory)
if err != nil {
return nil, err
}
for _, file := range files {
if file.IsDir() {
continue
}
if !strings.HasSuffix(file.Name(), ".config") {
continue
}
filePath := filepath.Join(directory, file.Name())
// Stable variant
if strings.HasPrefix(file.Name(), "stable") && variant == "stable" {
configBytes, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
configMap, _, err := repack_v1.ParseConfigFile(configBytes)
if err != nil {
return nil, err
}
// Check if arch-specific
if strings.Contains(file.Name(), "-") {
arch := strings.TrimSuffix(strings.Split(file.Name(), "-")[1], ".config")
log.Printf("applying arch-specific config: %s", arch)
configs = append(configs, &repack_v1.KernelConfig{
Arch: arch,
Map: configMap,
})
continue
} else {
log.Println("applying arch-independent config")
}
configs = append(configs, &repack_v1.KernelConfig{
Arch: "all",
Map: configMap,
})
continue
}
// Other variants
if strings.HasPrefix(file.Name(), variant+"-"+version) {
log.Printf("reading config: %s", file.Name())
configBytes, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
configMap, _, err := repack_v1.ParseConfigFile(configBytes)
if err != nil {
return nil, err
}
// Check if arch-specific
newName := strings.TrimPrefix(file.Name(), variant+"-"+version+"-")
if newName != ".config" {
arch := strings.TrimSuffix(newName, ".config")
log.Printf("applying arch-specific config: %s", arch)
configs = append(configs, &repack_v1.KernelConfig{
Arch: arch,
Map: configMap,
})
continue
} else {
log.Println("applying arch-independent config")
}
}
}
return configs, nil
}
func main() {
// variant can only be one of the following: "stable", "longterm"
allowedVariants := []string{"stable", "longterm"}
variant := flag.String("variant", "", "variant to use")
version := flag.String("version", "", "version to use")
changelogFile := flag.String("changelog", "/tmp/changelog.json", "changelog file to use")
kernelDirectory := flag.String("kernel-directory", "", "directory to store kernel")
configDirectory := flag.String("config-directory", "configs", "directory to store configs")
sourceOutputDirectory := flag.String("source-output-directory", "/tmp/sources", "directory to store source output")
flag.Parse()
// Validation
isAllowedVariant := false
for _, v := range allowedVariants {
if *variant == v {
isAllowedVariant = true
break
}
}
if !isAllowedVariant {
log.Fatalf("variant %s is not allowed", *variant)
}
if *variant != "stable" && len(*version) == 0 {
log.Fatalf("version is required")
}
if *variant == "stable" && len(*version) > 0 {
log.Fatalf("version is not allowed for stable variant")
}
if len(*version) > 0 && !strings.HasSuffix(*version, ".") {
*version = *version + "."
}
if len(*kernelDirectory) == 0 {
log.Fatalf("kernel directory is required")
}
// Read changelog file
changelog, err := os.ReadFile(*changelogFile)
if err != nil && !os.IsNotExist(err) {
log.Fatalf("failed to read changelog file: %v", err)
}
if err != nil && os.IsNotExist(err) {
log.Printf("changelog file %s does not exist, continuing without previous changelog", *changelogFile)
}
var changelogEntries []*repack_v1.ChangelogEntry
if changelog != nil && len(changelog) > 0 {
err = json.Unmarshal(changelog, &changelogEntries)
if err != nil {
log.Fatalf("failed to unmarshal changelog: %v", err)
}
}
// Get kernel tarball and version
var tarball []byte
var output *packager.Output
var kVersion string
// BuildID should be YYYYMMDDHHMM
buildID := time.Now().Format("200601021504")
// Check if there are any custom config files
customConfigs, err := readCustomConfigs(*configDirectory, *variant, *version)
if err != nil {
log.Fatalf("failed to read custom configs: %v", err)
}
switch *variant {
case "stable":
kVersion, tarball, _, err = kernelorg.GetLatestStable()
case "longterm":
kVersion, tarball, _, err = kernelorg.GetLatestLT(*version)
}
changelogEntry := &repack_v1.ChangelogEntry{
Date: time.Now().Format("Mon Jan 02 2006"),
Name: "RESF AutoPackager",
Version: kVersion,
BuildID: buildID,
Messages: []string{
fmt.Sprintf("Rebase to %s", kVersion),
},
}
// Prepend the new changelog entry
changelogEntries = append([]*repack_v1.ChangelogEntry{changelogEntry}, changelogEntries...)
input := &repack_v1.Input{
Version: kVersion,
BuildID: buildID,
KernelPackage: "kernel",
Changelog: changelogEntries,
AdditionalKernelConfig: customConfigs,
Tarball: tarball,
}
if err != nil {
log.Fatalf("failed to get kernel: %v", err)
}
output, err = repack_v1.ML(input)
if err != nil {
log.Fatalf("failed to repack: %v", err)
}
// Write files
_ = os.MkdirAll(*kernelDirectory, 0755)
fs := osfs.New(*kernelDirectory)
err = output.ToFS(fs)
if err != nil {
log.Fatalf("failed to write files: %v", err)
}
_ = os.MkdirAll(*sourceOutputDirectory, 0755)
f, err := os.Create(filepath.Join(*sourceOutputDirectory, output.TarballName))
if err != nil {
log.Fatalf("failed to create tarball: %v", err)
}
_, err = f.Write(output.Tarball)
if err != nil {
log.Fatalf("failed to write tarball: %v", err)
}
err = f.Close()
if err != nil {
log.Fatalf("failed to close tarball: %v", err)
}
for _, otherFile := range output.OtherFiles {
f, err := os.OpenFile(filepath.Join(*sourceOutputDirectory, otherFile.Name), os.O_CREATE|os.O_RDWR|os.O_TRUNC, otherFile.Permissions)
if err != nil {
log.Fatalf("failed to create file: %v", err)
}
_, err = f.Write(otherFile.Data)
if err != nil {
log.Fatalf("failed to write file: %v", err)
}
err = f.Close()
if err != nil {
log.Fatalf("failed to close file: %v", err)
}
}
log.Println("repack successful :)")
}

View File

@ -1,164 +0,0 @@
package repack_v1
import (
"bytes"
"crypto/sha256"
"embed"
"encoding/hex"
"fmt"
"go.resf.org/peridot/tools/kernelmanager/kernel_repack"
"io"
"strings"
"text/template"
)
//go:embed data/*
var Data embed.FS
type ChangelogEntry struct {
Date string
Name string
Version string
BuildID string
Text string
}
type Input struct {
Version string
BuildID string
KernelPackage string
Changelog []*ChangelogEntry
AdditionalKernelConfig []string
Tarball []byte
}
func kernel(kernelType string, in *Input) (*kernel_repack.Output, error) {
var spec *kernel_repack.File
var files []*kernel_repack.File
dir, err := Data.ReadDir("data")
if err != nil {
return nil, err
}
for _, file := range dir {
if strings.HasSuffix(file.Name(), ".spec") {
if file.Name() != kernelType+".spec" {
continue
}
// Read spec file
f, err := Data.Open("data/" + file.Name())
if err != nil {
return nil, err
}
defer f.Close()
specBytes, err := io.ReadAll(f)
if err != nil {
return nil, err
}
specName := fmt.Sprintf("%s.spec", in.KernelPackage)
spec = &kernel_repack.File{
Name: specName,
Data: specBytes,
}
continue
}
// Read other files
f, err := Data.Open("data/" + file.Name())
if err != nil {
return nil, err
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
return nil, err
}
// If the file starts with "config-", then it's a kernel config file.
// Append additional kernel config to the end of the file.
if strings.HasPrefix(file.Name(), "config-") {
data = append(data, []byte("\n")...)
for _, config := range in.AdditionalKernelConfig {
data = append(data, []byte(config)...)
data = append(data, []byte("\n")...)
}
}
stat, err := f.Stat()
if err != nil {
return nil, err
}
mode := stat.Mode()
// If file name ends with ".sh", set executable bit
if strings.HasSuffix(file.Name(), ".sh") {
mode |= 0111
}
files = append(files, &kernel_repack.File{
Name: file.Name(),
Data: data,
Permissions: mode,
})
}
// Get sha256sum of tarball
hash := sha256.New()
_, err = hash.Write(in.Tarball)
if err != nil {
return nil, err
}
sum := hex.EncodeToString(hash.Sum(nil))
// Create .metadata file
suffix := "xz"
if in.Tarball[0] == 0x1f && in.Tarball[1] == 0x8b {
suffix = "gz"
}
metadata := fmt.Sprintf("%s SOURCES/linux-%s.tar.%s", sum, in.Version, suffix)
// Create .[kernelpackage].metadata
metadataName := fmt.Sprintf(".%s.metadata", in.KernelPackage)
// Replace placeholders in spec file
var buf bytes.Buffer
txtTemplate, err := template.New("spec").Parse(string(spec.Data))
if err != nil {
return nil, err
}
err = txtTemplate.Execute(&buf, in)
if err != nil {
return nil, err
}
spec.Data = buf.Bytes()
output := &kernel_repack.Output{
Spec: spec,
Tarball: in.Tarball,
TarballSha256: sum,
Metadata: &kernel_repack.File{
Name: metadataName,
Data: []byte(metadata),
},
OtherFiles: files,
}
return output, nil
}
// LT creates a new kernel package for the LT kernel
// Returns spec and SOURCE files
func LT(in *Input) (*kernel_repack.Output, error) {
return kernel("lt", in)
}
// ML creates a new kernel package for the ML kernel
// Returns spec and SOURCE files
func ML(in *Input) (*kernel_repack.Output, error) {
return kernel("ml", in)
}

View File

@ -1,9 +1,9 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "kernel_repack",
name = "packager",
srcs = ["repack.go"],
importpath = "go.resf.org/peridot/tools/kernelmanager/kernel_repack",
importpath = "go.resf.org/peridot/tools/kernelmanager/packager",
visibility = ["//visibility:public"],
deps = ["//vendor/github.com/go-git/go-billy/v5:go-billy"],
)

View File

@ -10,7 +10,7 @@ go_library(
"gregkh.asc",
"torvalds.asc",
],
importpath = "go.resf.org/peridot/tools/kernelmanager/kernel_repack/kernelorg",
importpath = "go.resf.org/peridot/tools/kernelmanager/packager/kernelorg",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/xi2/xz",

View File

@ -1,8 +1,18 @@
package kernel_repack
package packager
import (
"github.com/go-git/go-billy/v5"
"crypto/sha256"
"encoding/hex"
"fmt"
"os"
"github.com/go-git/go-billy/v5"
"github.com/pkg/errors"
)
var (
ErrTarballEmpty = errors.New("tarball is empty")
ErrTarballHashMismatch = errors.New("tarball hash mismatch")
)
type File struct {
@ -14,12 +24,39 @@ type File struct {
type Output struct {
Spec *File
Tarball []byte
TarballName string
TarballSha256 string
Metadata *File
OtherFiles []*File
}
func (o *Output) ToFS(fs billy.Filesystem) error {
// Verify that tarball is not empty
if len(o.Tarball) == 0 {
return ErrTarballEmpty
}
// Verify that tarball hash matches
hash := sha256.New()
_, err := hash.Write(o.Tarball)
if err != nil {
return err
}
sum := hash.Sum(nil)
if o.TarballSha256 != hex.EncodeToString(sum) {
return errors.Wrap(ErrTarballHashMismatch, fmt.Sprintf("expected %s, got %s", o.TarballSha256, hex.EncodeToString(sum)))
}
// Verify that spec file is not empty
if o.Spec == nil || len(o.Spec.Data) == 0 {
return errors.New("spec file is empty")
}
// Verify that the metadata file has a name
if o.Metadata == nil || len(o.Metadata.Name) == 0 {
return errors.New("metadata file has no name")
}
// Create directories first
dirs := []string{"SPECS", "SOURCES"}
for _, dir := range dirs {

View File

@ -1,7 +1,7 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "kernel_repack",
name = "packager",
srcs = ["v1.go"],
embedsrcs = [
"data/config-x86_64",
@ -23,7 +23,7 @@ go_library(
"data/x509.genkey",
"data/rockydup1.x509",
],
importpath = "go.resf.org/peridot/tools/kernelmanager/kernel_repack/v1",
importpath = "go.resf.org/peridot/tools/kernelmanager/packager/v1",
visibility = ["//visibility:public"],
deps = ["//tools/kernelmanager/kernel_repack"],
deps = ["//tools/kernelmanager/packager"],
)

View File

@ -1407,7 +1407,11 @@ fi
### BCAT
%files -n %{name}-tools-libs
{{if (eq (compareVersion .Version "6.2") 1)}}
%{_libdir}/libcpupower.so.1
{{else}}
%{_libdir}/libcpupower.so.0
{{end}}
%{_libdir}/libcpupower.so.0.0.1
%files -n %{name}-tools-libs-devel
@ -1491,7 +1495,7 @@ fi
%kernel_ml_variant_files %{_use_vdso} %{with_std}
%changelog
{{range $val := .Changelog}}
* {{$val.Date}} {{$val.Name}} - {{$val.Version}}-{{$val.BuildID}}
- {{$val.Text}}
{{range $val := .Changelog}}* {{if $val.Subject}}{{$val.Subject}}{{else}}{{$val.Date}} {{$val.Name}} - {{$val.Version}}-{{$val.BuildID}}{{end}}
{{range $text := $val.Messages}}- {{$text}}
{{end}}
{{end}}

View File

@ -0,0 +1,274 @@
package repack_v1
import (
"bytes"
"crypto/sha256"
"embed"
"encoding/hex"
"fmt"
"io"
"strings"
"text/template"
packager "go.resf.org/peridot/tools/kernelmanager/kernel_repack"
)
//go:embed data/*
var Data embed.FS
type ChangelogEntry struct {
Date string `json:"-"`
Name string `json:"-"`
Version string `json:"-"`
BuildID string `json:"-"`
Messages []string `json:"messages"`
// Subject covers the first line of the changelog entry
Subject string `json:"subject"`
}
type KernelConfig struct {
Arch string
Map map[string]string
}
type Input struct {
Version string
BuildID string
KernelPackage string
Changelog []*ChangelogEntry
AdditionalKernelConfig []*KernelConfig
Tarball []byte
}
// compareVersion takes in two kernel version strings either in the form of "X.Y" or "X.Y.Z" and returns:
// 1 if a > b
// 0 if a == b
// -1 if a < b
func compareVersion(a, b string) int {
// Split version strings into parts
aParts := strings.Split(a, ".")
bParts := strings.Split(b, ".")
// Compare major version
if aParts[0] != bParts[0] {
if aParts[0] > bParts[0] {
return 1
}
return -1
}
// Compare minor version
if aParts[1] != bParts[1] {
if aParts[1] > bParts[1] {
return 1
}
return -1
}
// If there's a third part, compare it
if len(aParts) == 3 && len(bParts) == 3 {
if aParts[2] != bParts[2] {
if aParts[2] > bParts[2] {
return 1
}
return -1
}
}
return 0
}
func kernel(kernelType string, in *Input) (*packager.Output, error) {
var spec *packager.File
var files []*packager.File
dir, err := Data.ReadDir("data")
if err != nil {
return nil, err
}
for _, file := range dir {
if strings.HasSuffix(file.Name(), ".spec") {
if file.Name() != kernelType+".spec" {
continue
}
// Read spec file
f, err := Data.Open("data/" + file.Name())
if err != nil {
return nil, err
}
defer f.Close()
specBytes, err := io.ReadAll(f)
if err != nil {
return nil, err
}
specName := fmt.Sprintf("%s.spec", in.KernelPackage)
spec = &packager.File{
Name: specName,
Data: specBytes,
}
continue
}
// Read other files
f, err := Data.Open("data/" + file.Name())
if err != nil {
return nil, err
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
return nil, err
}
// If the file starts with "config-", then it's a kernel config file.
// Merge additional kernel config
if strings.HasPrefix(file.Name(), "config-") {
parsedConfig, comments, err := ParseConfigFile(data)
if err != nil {
return nil, err
}
for _, conf := range in.AdditionalKernelConfig {
if conf.Arch == strings.TrimPrefix(file.Name(), "config-") || conf.Arch == "all" {
for k, v := range conf.Map {
parsedConfig[k] = v
}
}
}
// Write the config back to the data slice
data = WriteConfigFile(parsedConfig, comments)
}
stat, err := f.Stat()
if err != nil {
return nil, err
}
mode := stat.Mode()
// If file name ends with ".sh", set executable bit
if strings.HasSuffix(file.Name(), ".sh") {
mode |= 0111
}
// Read first line of file. If it starts with "#!", set executable bit
if len(data) > 2 && data[0] == 0x23 && data[1] == 0x21 {
mode |= 0111
}
files = append(files, &packager.File{
Name: file.Name(),
Data: data,
Permissions: mode,
})
}
// Get sha256sum of tarball
hash := sha256.New()
_, err = hash.Write(in.Tarball)
if err != nil {
return nil, err
}
sum := hex.EncodeToString(hash.Sum(nil))
// Create .metadata file
suffix := "xz"
if in.Tarball[0] == 0x1f && in.Tarball[1] == 0x8b {
suffix = "gz"
}
tarballName := fmt.Sprintf("linux-%s.tar.%s", in.Version, suffix)
metadata := fmt.Sprintf("%s SOURCES/%s", tarballName)
// Create .[kernelpackage].metadata
metadataName := fmt.Sprintf(".%s.metadata", in.KernelPackage)
// Replace placeholders in spec file
var buf bytes.Buffer
txtTemplate, err := template.
New("spec").
Funcs(template.FuncMap{
"compareVersion": compareVersion,
}).
Parse(string(spec.Data))
if err != nil {
return nil, err
}
err = txtTemplate.Execute(&buf, in)
if err != nil {
return nil, err
}
spec.Data = buf.Bytes()
output := &packager.Output{
Spec: spec,
Tarball: in.Tarball,
TarballName: tarballName,
TarballSha256: sum,
Metadata: &packager.File{
Name: metadataName,
Data: []byte(metadata),
},
OtherFiles: files,
}
return output, nil
}
func ParseConfigFile(data []byte) (map[string]string, []string, error) {
// Split into lines so we can parse it
lines := strings.Split(string(data), "\n")
var comments []string
var config []string
// Loop, and add all lines that start with "#" to the comment slice
// and all other lines to the config slice
for _, line := range lines {
// Ignore empty lines
if len(line) == 0 {
continue
}
if strings.HasPrefix(line, "#") {
comments = append(comments, line)
} else {
config = append(config, line)
}
}
parsedConfig := make(map[string]string)
for _, line := range config {
// Split the line into key and value
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
return nil, nil, fmt.Errorf("invalid kernel config line: %s", line)
}
parsedConfig[parts[0]] = parts[1]
}
return parsedConfig, comments, nil
}
func WriteConfigFile(config map[string]string, comments []string) []byte {
var newConfig []string
for _, comment := range comments {
newConfig = append(newConfig, comment)
}
for k, v := range config {
newConfig = append(newConfig, fmt.Sprintf("%s=%s", k, v))
}
data := []byte(strings.Join(newConfig, "\n"))
return data
}
// ML creates a new kernel package for the ML kernel
// Returns spec and SOURCE files
func ML(in *Input) (*packager.Output, error) {
return kernel("ml", in)
}