Files
bpm/src/bpmlib/installed_packages.go
T

360 lines
9.0 KiB
Go

package bpmlib
import (
"fmt"
"os"
"path"
"slices"
"strconv"
"strings"
"gopkg.in/yaml.v3"
)
var persistentDataVersion int = 1
var localPackageInformation map[string]map[string]*PackageInfo = make(map[string]map[string]*PackageInfo)
var installedVirtualPackages map[string]map[string][]*PackageInfo = make(map[string]map[string][]*PackageInfo)
func InitializeLocalPackageInformation(rootDir string) (err error) {
// Return if information is already initialized
if _, ok := localPackageInformation[rootDir]; ok {
return nil
}
tempPackageInformation := make(map[string]*PackageInfo)
tempInstalledVirtualPackages := make(map[string][]*PackageInfo)
// Get paths
persistentDataDir := path.Join(rootDir, "var/lib/bpm")
installedDir := path.Join(persistentDataDir, "installed")
// Ensure persistent data directory is up-to-date
if _, err := os.Stat(persistentDataDir); err == nil {
data, err := os.ReadFile(path.Join(persistentDataDir, ".version"))
if err != nil {
return fmt.Errorf("persistent data is not up-to-date! Please run 'bpm upgrade-persistent-data' first")
}
currentPersistentDataVersion, err := strconv.Atoi(strings.TrimSpace(string(data)))
if err != nil {
return fmt.Errorf("persistent data is not up-to-date! Please run 'bpm upgrade-persistent-data' first")
}
if currentPersistentDataVersion != persistentDataVersion {
return fmt.Errorf("persistent data is not up-to-date! Please run 'bpm upgrade-persistent-data' first")
}
}
// Get directory content
items, err := os.ReadDir(installedDir)
if os.IsNotExist(err) {
localPackageInformation[rootDir] = make(map[string]*PackageInfo)
return nil
}
if err != nil {
return err
}
// Loop through each subdirectory
for _, item := range items {
// Skip if not a directory
if !item.IsDir() {
continue
}
// Read package info
infoData, err := os.ReadFile(path.Join(installedDir, item.Name(), "info"))
if err != nil {
return err
}
info, err := ReadPackageInfo(string(infoData))
if err != nil {
return err
}
// Add package to slice
tempPackageInformation[info.Name] = info
// Add virtual packages
for _, vpkg := range info.Provides {
tempInstalledVirtualPackages[vpkg] = append(tempInstalledVirtualPackages[vpkg], info)
}
}
localPackageInformation[rootDir] = tempPackageInformation
installedVirtualPackages[rootDir] = tempInstalledVirtualPackages
return nil
}
func GetInstalledPackages(rootDir string) (ret []string, err error) {
// Initialize local package information
err = InitializeLocalPackageInformation(rootDir)
if err != nil {
return nil, err
}
// Loop through each package and add it to slice
for _, pkgInfo := range localPackageInformation[rootDir] {
ret = append(ret, pkgInfo.Name)
}
// Sort packages
slices.Sort(ret)
return ret, nil
}
func IsPackageInstalled(pkg, rootDir string) bool {
// Initialize local package information
err := InitializeLocalPackageInformation(rootDir)
if err != nil {
return false
}
if _, ok := localPackageInformation[rootDir][pkg]; !ok {
return false
}
return true
}
func GetVirtualPackageInfo(vpkg, rootDir string) []*PackageInfo {
err := InitializeLocalPackageInformation(rootDir)
if err != nil {
return nil
}
return installedVirtualPackages[rootDir][vpkg]
}
func GetPackageInfo(pkg string, rootDir string) *PackageInfo {
err := InitializeLocalPackageInformation(rootDir)
if err != nil {
return nil
}
return localPackageInformation[rootDir][pkg]
}
func GetPackage(pkg, rootDir string) *BPMPackage {
pkgInfo := GetPackageInfo(pkg, rootDir)
if pkgInfo == nil {
return nil
}
files := getPackageFiles(pkgInfo.Name, rootDir)
localInfo := getPackageLocalInfo(pkgInfo.Name, rootDir)
return &BPMPackage{
PkgInfo: pkgInfo,
PkgFiles: files,
LocalInfo: localInfo,
}
}
func GetAllPackageFiles(rootDir string, excludePackages ...string) (map[string][]*BPMPackage, error) {
ret := make(map[string][]*BPMPackage)
pkgNames, err := GetInstalledPackages(rootDir)
if err != nil {
return nil, err
}
for _, pkgName := range pkgNames {
if slices.Contains(excludePackages, pkgName) {
continue
}
bpmpkg := GetPackage(pkgName, rootDir)
if bpmpkg == nil {
return nil, fmt.Errorf("could not get BPM package (%s)", pkgName)
}
for _, entry := range bpmpkg.PkgFiles {
ret[entry.Path] = append(ret[entry.Path], bpmpkg)
}
}
return ret, nil
}
func getPackageFiles(pkg, rootDir string) []*PackageFileEntry {
var pkgFiles []*PackageFileEntry
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
pkgDir := path.Join(installedDir, pkg)
files := path.Join(pkgDir, "files")
if _, err := os.Stat(installedDir); os.IsNotExist(err) {
return nil
}
if _, err := os.Stat(pkgDir); os.IsNotExist(err) {
return nil
}
bs, err := os.ReadFile(files)
if err != nil {
return nil
}
for _, line := range strings.Split(string(bs), "\n") {
if strings.TrimSpace(line) == "" {
continue
}
stringEntry := strings.Split(strings.TrimSpace(line), " ")
if len(stringEntry) < 5 {
pkgFiles = append(pkgFiles, &PackageFileEntry{
Path: strings.TrimSuffix(line, "/"),
OctalPerms: 0,
UserID: 0,
GroupID: 0,
SizeInBytes: 0,
})
continue
}
uid, err := strconv.ParseInt(stringEntry[len(stringEntry)-4], 0, 32)
if err != nil {
return nil
}
octalPerms, err := strconv.ParseInt(stringEntry[len(stringEntry)-3], 0, 32)
if err != nil {
return nil
}
gid, err := strconv.ParseInt(stringEntry[len(stringEntry)-2], 0, 32)
if err != nil {
return nil
}
size, err := strconv.ParseInt(stringEntry[len(stringEntry)-1], 0, 64)
if err != nil {
return nil
}
pkgFiles = append(pkgFiles, &PackageFileEntry{
Path: strings.TrimSuffix(strings.Join(stringEntry[:len(stringEntry)-4], " "), "/"),
OctalPerms: uint32(octalPerms),
UserID: int(uid),
GroupID: int(gid),
SizeInBytes: size,
})
}
return pkgFiles
}
func getPackageLocalInfo(pkg, rootDir string) PackageLocalInfo {
localInfo := PackageLocalInfo{}
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
pkgDir := path.Join(installedDir, pkg)
localInfoFile := path.Join(path.Join(pkgDir, "local"))
if _, err := os.Stat(localInfoFile); os.IsNotExist(err) {
return localInfo
}
file, err := os.Open(localInfoFile)
if err != nil {
return localInfo
}
defer file.Close()
err = yaml.NewDecoder(file).Decode(&localInfo)
if err != nil {
return localInfo
}
return localInfo
}
func SetPackageLocalInfo(pkg string, localInfo PackageLocalInfo, rootDir string) error {
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
pkgDir := path.Join(installedDir, pkg)
localFile, err := os.OpenFile(path.Join(pkgDir, "local"), os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return err
}
defer localFile.Close()
err = yaml.NewEncoder(localFile).Encode(localInfo)
if err != nil {
return err
}
return nil
}
func UpgradePersistentData(rootDir string) error {
persistentDataDir := path.Join(rootDir, "var/lib/bpm")
// Create persistent data directory
os.MkdirAll(persistentDataDir, 0755)
// Upgrade installed package directories
dirEntries, err := os.ReadDir(path.Join(persistentDataDir, "installed"))
if err != nil && !os.IsNotExist(err) {
return err
} else if err == nil {
for _, entry := range dirEntries {
pkgDir := path.Join(persistentDataDir, "installed", entry.Name())
// Generate default local package information file
if _, err := os.Stat(path.Join(pkgDir, "local")); err != nil && !os.IsNotExist(err) {
return err
} else if os.IsNotExist(err) {
fmt.Printf("Generating local package information for package (%s)\n", entry.Name())
out, err := yaml.Marshal(PackageLocalInfo{
InstallationReason: "unknown",
InstalledOn: 0,
LastUpdatedOn: 0,
})
if err != nil {
return err
}
err = os.WriteFile(path.Join(pkgDir, "local"), out, 0644)
if err != nil {
return err
}
}
// Move installation reason to local package information file
if installationReason, err := os.ReadFile(path.Join(pkgDir, "installation_reason")); err != nil && !os.IsNotExist(err) {
return err
} else if err == nil {
fmt.Printf("Moving installation reason to local package information for package (%s)\n", entry.Name())
data, err := os.ReadFile(path.Join(pkgDir, "local"))
if err != nil {
return err
}
localInfo := &PackageLocalInfo{}
err = yaml.Unmarshal(data, localInfo)
if err != nil {
return err
}
localInfo.InstallationReason = strings.TrimSpace(string(installationReason))
out, err := yaml.Marshal(localInfo)
if err != nil {
return err
}
err = os.WriteFile(path.Join(pkgDir, "local"), out, 0644)
if err != nil {
return err
}
err = os.Remove(path.Join(pkgDir, "installation_reason"))
if err != nil {
return err
}
}
}
}
// Set persistent data version number
err = os.WriteFile(path.Join(persistentDataDir, ".version"), []byte(strconv.Itoa(persistentDataVersion)), 0644)
if err != nil {
return err
}
return nil
}