426 lines
8.8 KiB
Go
426 lines
8.8 KiB
Go
package debian
|
|
|
|
import (
|
|
"errors"
|
|
"math"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Masterminds/semver"
|
|
"github.com/rs/zerolog"
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"code.dumpstack.io/tools/out-of-tree/cache"
|
|
"code.dumpstack.io/tools/out-of-tree/config"
|
|
"code.dumpstack.io/tools/out-of-tree/distro/debian/snapshot"
|
|
"code.dumpstack.io/tools/out-of-tree/distro/debian/snapshot/metasnap"
|
|
"code.dumpstack.io/tools/out-of-tree/fs"
|
|
)
|
|
|
|
type DebianKernelVersion struct {
|
|
// linux-headers-4.17.0-2-amd64_4.17.14-1_amd64.deb
|
|
|
|
// Package version, e.g. "4.17.14-1"
|
|
// See tags in https://salsa.debian.org/kernel-team/linux
|
|
Package string
|
|
|
|
// ABI version, e.g. "4.17.0-2"
|
|
ABI string
|
|
}
|
|
|
|
type DebianKernel struct {
|
|
Version DebianKernelVersion
|
|
Image snapshot.Package
|
|
Headers []snapshot.Package
|
|
Dependencies []snapshot.Package
|
|
|
|
// FIXME There is a better way
|
|
Internal struct {
|
|
Invalid bool
|
|
LastFetch time.Time
|
|
}
|
|
|
|
Release Release
|
|
}
|
|
|
|
func (dk DebianKernel) HasDependency(pkgname string) bool {
|
|
for _, deppkg := range dk.Dependencies {
|
|
if strings.Contains(deppkg.Name, pkgname) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (dk DebianKernel) Packages() (pkgs []snapshot.Package) {
|
|
pkgs = append(pkgs, dk.Image)
|
|
pkgs = append(pkgs, dk.Headers...)
|
|
pkgs = append(pkgs, dk.Dependencies...)
|
|
return
|
|
}
|
|
|
|
// use only for inline comparison
|
|
func kver(ver string) *semver.Version {
|
|
ver = strings.Replace(ver, "~", "-", -1)
|
|
ver = strings.Replace(ver, "+", "-", -1)
|
|
return semver.MustParse(ver)
|
|
}
|
|
|
|
var (
|
|
ErrNoBinaryPackages = errors.New("no binary packages found")
|
|
ErrNoHeadersPackage = errors.New("no headers package found")
|
|
ErrNoImagePackage = errors.New("no image package found")
|
|
)
|
|
|
|
func getDebianKernel(version string) (dk DebianKernel, err error) {
|
|
flog := log.With().
|
|
Str("version", version).
|
|
Logger()
|
|
|
|
dk.Version.Package = version
|
|
|
|
regex := `^(linux-(image|headers)-[a-z+~0-9\.\-]*-(common|amd64|amd64-unsigned)|linux-kbuild-.*|linux-compiler-.*-x86)$`
|
|
|
|
filter := []string{
|
|
"rt-amd64",
|
|
"cloud-amd64",
|
|
"all-amd64",
|
|
"dbg",
|
|
}
|
|
|
|
packages, err := snapshot.Packages("linux", version, regex,
|
|
[]string{"amd64", "all"}, filter)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if len(packages) == 0 {
|
|
err = ErrNoBinaryPackages
|
|
return
|
|
}
|
|
|
|
var imageFound, headersFound bool
|
|
for _, p := range packages {
|
|
if strings.Contains(p.Name, "image") {
|
|
imageFound = true
|
|
dk.Image = p
|
|
} else if strings.Contains(p.Name, "headers") {
|
|
headersFound = true
|
|
dk.Headers = append(dk.Headers, p)
|
|
} else {
|
|
dk.Dependencies = append(dk.Dependencies, p)
|
|
}
|
|
}
|
|
|
|
if !imageFound {
|
|
err = ErrNoImagePackage
|
|
return
|
|
}
|
|
|
|
if !headersFound {
|
|
err = ErrNoHeadersPackage
|
|
return
|
|
}
|
|
|
|
s := strings.Replace(dk.Image.Name, "linux-image-", "", -1)
|
|
dk.Version.ABI = strings.Replace(s, "-amd64", "", -1)
|
|
|
|
dk.Release = getRelease(dk.Image)
|
|
if dk.Release == None {
|
|
flog.Warn().Msg("release not found")
|
|
} else {
|
|
flog.Debug().Msgf("release is %s", dk.Release.Name())
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func getRelease(p snapshot.Package) Release {
|
|
repos, err := metasnap.GetRepos(p.Repo.Archive, p.Name, p.Arch, p.Version)
|
|
if err != nil {
|
|
log.Debug().Err(err).Msg("metasnap")
|
|
return None
|
|
}
|
|
|
|
for _, repo := range repos {
|
|
for _, rel := range ReleaseStrings[1:] {
|
|
switch repo.Suite {
|
|
case rel, rel + "-backports",
|
|
rel + "-updates",
|
|
rel + "-proposed-updates":
|
|
|
|
return ReleaseFromString(rel)
|
|
}
|
|
}
|
|
}
|
|
|
|
return None
|
|
}
|
|
|
|
// GetCachedKernel by deb package name
|
|
func getCachedKernel(deb string) (dk DebianKernel, err error) {
|
|
c, err := NewCache(CachePath)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("cache")
|
|
return
|
|
}
|
|
defer c.Close()
|
|
|
|
versions, err := c.GetVersions()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("get source package versions from cache")
|
|
return
|
|
}
|
|
|
|
for _, version := range versions {
|
|
var tmpdks []DebianKernel
|
|
tmpdks, err = c.Get(version)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
tmpdk := tmpdks[0]
|
|
|
|
if deb == tmpdk.Image.Deb.Name {
|
|
dk = tmpdk
|
|
return
|
|
}
|
|
|
|
for _, h := range tmpdk.Headers {
|
|
if deb == h.Deb.Name {
|
|
dk = tmpdk
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func kbuildVersion(versions []string, kpkgver string) string {
|
|
sort.Slice(versions, func(i, j int) bool {
|
|
return kver(versions[i]).GreaterThan(kver(versions[j]))
|
|
})
|
|
|
|
for _, v := range versions {
|
|
if v == kpkgver {
|
|
return v
|
|
}
|
|
}
|
|
|
|
ver := kver(kpkgver)
|
|
|
|
// Not able to find the exact version, try similar
|
|
for _, v := range versions {
|
|
cver := kver(v)
|
|
|
|
// It's certainly not fit for purpose if the major and
|
|
// minor versions aren't the same
|
|
|
|
if ver.Major() != cver.Major() {
|
|
continue
|
|
}
|
|
|
|
if ver.Minor() != cver.Minor() {
|
|
continue
|
|
}
|
|
|
|
// Use the first version that is newer than the kernel
|
|
|
|
if ver.LessThan(cver) {
|
|
continue
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func findKbuild(versions []string, kpkgver string) (
|
|
pkg snapshot.Package, err error) {
|
|
|
|
version := kbuildVersion(versions, kpkgver)
|
|
if version == "" {
|
|
err = errors.New("cannot find kbuild version")
|
|
return
|
|
}
|
|
|
|
packages, err := snapshot.Packages("linux-tools", version,
|
|
`^linux-kbuild`, []string{"amd64"}, []string{"dbg"})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if len(packages) == 0 {
|
|
err = errors.New("cannot find kbuild package")
|
|
}
|
|
|
|
pkg = packages[0]
|
|
return
|
|
}
|
|
|
|
func getKernelsByVersion(slog zerolog.Logger, c *Cache, toolsVersions []string,
|
|
version string, mode GetKernelsMode) (kernels []DebianKernel,
|
|
fromcache bool) {
|
|
|
|
var dk DebianKernel
|
|
dks, err := c.Get(version)
|
|
if err == nil {
|
|
dk = dks[0]
|
|
if !dk.Internal.Invalid {
|
|
slog.Trace().Msgf("found in cache")
|
|
if dk.Release == None && mode&UpdateRelease != 0 {
|
|
slog.Debug().Msg("update release")
|
|
dk.Release = getRelease(dk.Image)
|
|
if dk.Release != None {
|
|
slog.Debug().Msg("update cache")
|
|
err = c.Put([]DebianKernel{dk})
|
|
if err != nil {
|
|
slog.Error().Err(err).Msg("")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
kernels = append(kernels, dk)
|
|
fromcache = true
|
|
return
|
|
}
|
|
}
|
|
|
|
if dk.Internal.Invalid {
|
|
refetch := dk.Internal.LastFetch.AddDate(0, 0, RefetchDays)
|
|
if refetch.After(time.Now()) {
|
|
slog.Trace().Msgf("refetch at %v", refetch)
|
|
return
|
|
}
|
|
}
|
|
|
|
dk, err = getDebianKernel(version)
|
|
if err != nil {
|
|
if err == ErrNoBinaryPackages {
|
|
slog.Warn().Err(err).Msg("")
|
|
} else {
|
|
slog.Error().Err(err).Msg("get debian kernel")
|
|
}
|
|
|
|
dk.Internal.Invalid = true
|
|
}
|
|
|
|
if !dk.HasDependency("kbuild") {
|
|
if !kver(dk.Version.Package).LessThan(kver("4.5-rc0")) {
|
|
dk.Internal.Invalid = true
|
|
} else {
|
|
// Debian kernels prior to the 4.5 package
|
|
// version did not have a kbuild built from
|
|
// the linux source itself, but used the
|
|
// linux-tools source package.
|
|
kbuildpkg, err := findKbuild(
|
|
toolsVersions,
|
|
dk.Version.Package,
|
|
)
|
|
if err != nil {
|
|
dk.Internal.Invalid = true
|
|
} else {
|
|
dk.Dependencies = append(
|
|
dk.Dependencies,
|
|
kbuildpkg,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
dk.Internal.LastFetch = time.Now()
|
|
|
|
if !dk.Internal.Invalid {
|
|
kernels = append(kernels, dk)
|
|
}
|
|
|
|
err = c.Put([]DebianKernel{dk})
|
|
if err != nil {
|
|
slog.Error().Err(err).Msg("put to cache")
|
|
return
|
|
}
|
|
|
|
slog.Debug().Msgf("%s cached", version)
|
|
return
|
|
}
|
|
|
|
var (
|
|
CachePath string
|
|
RefetchDays int = 14
|
|
)
|
|
|
|
type GetKernelsMode int
|
|
|
|
const (
|
|
NoMode GetKernelsMode = iota
|
|
UpdateRelease
|
|
)
|
|
|
|
// GetKernelsWithLimit is workaround for testing and building the
|
|
// first cache, which is heavily rate limited by snapshot.debian.org
|
|
func GetKernelsWithLimit(limit int, mode GetKernelsMode) (kernels []DebianKernel,
|
|
err error) {
|
|
|
|
if CachePath == "" {
|
|
CachePath = config.File("debian.cache")
|
|
log.Debug().Msgf("Use default kernels cache path: %s", CachePath)
|
|
|
|
if !fs.PathExists(CachePath) {
|
|
log.Debug().Msgf("No cache, download")
|
|
err = cache.DownloadDebianCache(CachePath)
|
|
if err != nil {
|
|
log.Debug().Err(err).Msg(
|
|
"No remote cache, will take some time")
|
|
}
|
|
}
|
|
} else {
|
|
log.Debug().Msgf("Debian kernels cache path: %s", CachePath)
|
|
}
|
|
|
|
c, err := NewCache(CachePath)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("cache")
|
|
return
|
|
}
|
|
defer c.Close()
|
|
|
|
toolsVersions, err := snapshot.SourcePackageVersions("linux-tools")
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("get linux-tools source pkg versions")
|
|
return
|
|
}
|
|
|
|
versions, err := snapshot.SourcePackageVersions("linux")
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("get linux source package versions")
|
|
return
|
|
}
|
|
|
|
err = c.PutVersions(versions)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("put source package versions to cache")
|
|
return
|
|
}
|
|
|
|
for i, version := range versions {
|
|
slog := log.With().Str("version", version).Logger()
|
|
slog.Trace().Msgf("%03d/%03d", i, len(versions))
|
|
vkernels, fromcache := getKernelsByVersion(slog, c, toolsVersions, version, mode)
|
|
kernels = append(kernels, vkernels...)
|
|
if !fromcache {
|
|
limit--
|
|
}
|
|
if limit <= 0 {
|
|
return
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func GetKernels() (kernels []DebianKernel, err error) {
|
|
return GetKernelsWithLimit(math.MaxInt32, NoMode)
|
|
}
|