diff --git a/Makefile b/Makefile index 1b4a678..7c8a82e 100644 --- a/Makefile +++ b/Makefile @@ -6,9 +6,13 @@ SYSCONFDIR := $(PREFIX)/etc # Compilers and tools GO ?= $(shell which go) +# Build-time variables +ROOT_COMPILATION_UID ?= 65534 +ROOT_COMPILATION_GID ?= 65534 + build: mkdir -p build - cd src/bpm; $(GO) build -ldflags "-w" -o ../../build/bpm git.enumerated.dev/bubble-package-manager/bpm/src/bpm + cd src/bpm; $(GO) build -ldflags "-w -X 'git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib.rootCompilationUID=$(ROOT_COMPILATION_UID)' -X 'git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib.rootCompilationGID=$(ROOT_COMPILATION_GID)'" -o ../../build/bpm git.enumerated.dev/bubble-package-manager/bpm/src/bpm install: build/bpm config/ # Create directories diff --git a/README.md b/README.md index 231a717..527b23d 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ You are able to install bpm packages by typing the following: ```sh bpm install /path/to/package.bpm ``` -You can also use the package name directly if using repositories +You can also use the package name directly if using databases ```sh bpm install package_name ``` @@ -45,12 +45,12 @@ You can remove an installed package by typing the following bpm remove package_name ``` -To remove all unused dependencies try using the cleanup command +To remove all unused dependencies and clean cached files try using the cleanup command ```sh bpm cleanup ``` -If using repositories, all packages can be updated using this simple command +If using databases, all packages can be updated using this simple command ```sh bpm update ``` diff --git a/config/bpm.conf b/config/bpm.conf index 3e8e4ae..d594d35 100644 --- a/config/bpm.conf +++ b/config/bpm.conf @@ -1,5 +1,8 @@ ignore_packages: [] -repositories: - - name: example-repository - source: https://my-repo.xyz/ +privilege_escalator_cmd: "sudo" +compilation_env: [] +cleanup_make_dependencies: true +databases: + - name: example-database + source: https://my-database.xyz/ disabled: true \ No newline at end of file diff --git a/src/bpm/main.go b/src/bpm/main.go index 9ff4a10..8de0e8e 100644 --- a/src/bpm/main.go +++ b/src/bpm/main.go @@ -8,6 +8,8 @@ import ( "git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib" "log" "os" + "os/exec" + "path" "path/filepath" "slices" "strings" @@ -37,7 +39,15 @@ var installationReason = "" var nosync = true var removeUnused = false var doCleanup = false -var showRepoInfo = false +var showDatabaseInfo = false +var installSrcPkgDepends = false +var skipChecks = false +var outputDirectory = "" +var cleanupDependencies = false +var cleanupMakeDependencies = false +var cleanupCompilationFiles = false +var cleanupCompiledPackages = false +var cleanupFetchedPackages = false func main() { err := bpmlib.ReadConfig() @@ -62,6 +72,7 @@ const ( remove cleanup file + compile ) func getCommandType() commandType { @@ -86,6 +97,8 @@ func getCommandType() commandType { return cleanup case "file": return file + case "compile": + return compile default: return help } @@ -104,7 +117,7 @@ func resolveCommand() { } // Read local databases - err := bpmlib.ReadLocalDatabases() + err := bpmlib.ReadLocalDatabaseFiles() if err != nil { log.Fatalf("Error: could not read local databases: %s", err) } @@ -112,13 +125,14 @@ func resolveCommand() { for n, pkg := range packages { var info *bpmlib.PackageInfo isFile := false - if showRepoInfo { + showInstallationReason := false + if showDatabaseInfo { var err error - var entry *bpmlib.RepositoryEntry - entry, _, err = bpmlib.GetRepositoryEntry(pkg) + var entry *bpmlib.BPMDatabaseEntry + entry, _, err = bpmlib.GetDatabaseEntry(pkg) if err != nil { if entry = bpmlib.ResolveVirtualPackage(pkg); entry == nil { - log.Fatalf("Error: could not find package (%s) in any repository\n", pkg) + log.Fatalf("Error: could not find package (%s) in any database\n", pkg) } } info = entry.Info @@ -135,6 +149,7 @@ func resolveCommand() { } else { info = bpmlib.GetPackageInfo(pkg, rootDir) } + showInstallationReason = true } if info == nil { log.Fatalf("Error: package (%s) is not installed\n", pkg) @@ -149,11 +164,11 @@ func resolveCommand() { } fmt.Println("File: " + abs) } - fmt.Println(bpmlib.CreateReadableInfo(true, true, true, info, rootDir)) + fmt.Println(bpmlib.CreateReadableInfo(true, true, true, showInstallationReason, info, rootDir)) } case list: // Read local databases - err := bpmlib.ReadLocalDatabases() + err := bpmlib.ReadLocalDatabaseFiles() if err != nil { log.Fatalf("Error: could not read local databases: %s", err) } @@ -183,7 +198,7 @@ func resolveCommand() { if n != 0 { fmt.Println() } - fmt.Println(bpmlib.CreateReadableInfo(true, true, true, info, rootDir)) + fmt.Println(bpmlib.CreateReadableInfo(true, true, true, true, info, rootDir)) } } case search: @@ -193,7 +208,7 @@ func resolveCommand() { } // Read local databases - err := bpmlib.ReadLocalDatabases() + err := bpmlib.ReadLocalDatabaseFiles() if err != nil { log.Fatalf("Error: could not read local databases: %s", err) } @@ -201,8 +216,8 @@ func resolveCommand() { for i, term := range searchTerms { nameResults := make([]*bpmlib.PackageInfo, 0) descResults := make([]*bpmlib.PackageInfo, 0) - for _, repo := range bpmlib.BPMConfig.Repositories { - for _, entry := range repo.Entries { + for _, db := range bpmlib.BPMConfig.Databases { + for _, entry := range db.Entries { if strings.Contains(entry.Info.Name, term) { nameResults = append(nameResults, entry.Info) } else if strings.Contains(entry.Info.Description, term) { @@ -235,12 +250,14 @@ func resolveCommand() { } // Check if installationReason argument is valid - ir := bpmlib.InstallationReasonUnknown + ir := bpmlib.InstallationReasonManual switch installationReason { case "manual": ir = bpmlib.InstallationReasonManual case "dependency": ir = bpmlib.InstallationReasonDependency + case "make-dependency": + ir = bpmlib.InstallationReasonMakeDependency case "": default: log.Fatalf("Error: %s is not a valid installation reason", installationReason) @@ -257,7 +274,7 @@ func resolveCommand() { } // Read local databases - err := bpmlib.ReadLocalDatabases() + err := bpmlib.ReadLocalDatabaseFiles() if err != nil { log.Fatalf("Error: could not read local databases: %s", err) } @@ -315,7 +332,7 @@ func resolveCommand() { // Read local databases if no sync if nosync { - err := bpmlib.ReadLocalDatabases() + err := bpmlib.ReadLocalDatabaseFiles() if err != nil { log.Fatalf("Error: could not read local databases: %s", err) } @@ -397,13 +414,13 @@ func resolveCommand() { } // Read local databases - err := bpmlib.ReadLocalDatabases() + err := bpmlib.ReadLocalDatabaseFiles() if err != nil { log.Fatalf("Error: could not read local databases: %s", err) } // Create remove operation - operation, err := bpmlib.RemovePackages(rootDir, removeUnused, doCleanup, verbose, subcommandArgs...) + operation, err := bpmlib.RemovePackages(rootDir, removeUnused, doCleanup, subcommandArgs...) if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) { log.Fatalf("Error: %s", err) } else if err != nil { @@ -448,51 +465,58 @@ func resolveCommand() { log.Fatalf("Error: this subcommand needs to be run with superuser permissions") } - // Read local databases - err := bpmlib.ReadLocalDatabases() + err := bpmlib.CleanupCache(rootDir, cleanupCompilationFiles, cleanupCompiledPackages, cleanupFetchedPackages, verbose) if err != nil { - log.Fatalf("Error: could not read local databases: %s", err) + log.Fatalf("Error: could not complete cache cleanup: %s", err) } - // Create cleanup operation - operation, err := bpmlib.CleanupPackages(rootDir, verbose) - if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) { - log.Fatalf("Error: %s", err) - } else if err != nil { - log.Fatalf("Error: could not setup operation: %s\n", err) - } - - // Exit if operation contains no actions - if len(operation.Actions) == 0 { - fmt.Println("No action needs to be taken") - return - } - - // Show operation summary - operation.ShowOperationSummary() - - // Confirmation Prompt - if !yesAll { - fmt.Printf("Are you sure you wish to remove all %d packages? [y\\N] ", len(operation.Actions)) - reader := bufio.NewReader(os.Stdin) - text, _ := reader.ReadString('\n') - if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" { - fmt.Println("Cancelling package removal...") - os.Exit(1) + if cleanupDependencies || cleanupMakeDependencies { + // Read local databases + err := bpmlib.ReadLocalDatabaseFiles() + if err != nil { + log.Fatalf("Error: could not read local databases: %s", err) } - } - // Execute operation - err = operation.Execute(verbose, force) - if err != nil { - log.Fatalf("Error: could not complete operation: %s\n", err) - } + // Create cleanup operation + operation, err := bpmlib.CleanupPackages(cleanupMakeDependencies, rootDir) + if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) { + log.Fatalf("Error: %s", err) + } else if err != nil { + log.Fatalf("Error: could not setup operation: %s\n", err) + } - // Executing hooks - fmt.Println("Running hooks...") - err = operation.RunHooks(verbose) - if err != nil { - log.Fatalf("Error: could not run hooks: %s\n", err) + // Exit if operation contains no actions + if len(operation.Actions) == 0 { + fmt.Println("No action needs to be taken") + return + } + + // Show operation summary + operation.ShowOperationSummary() + + // Confirmation Prompt + if !yesAll { + fmt.Printf("Are you sure you wish to remove all %d packages? [y\\N] ", len(operation.Actions)) + reader := bufio.NewReader(os.Stdin) + text, _ := reader.ReadString('\n') + if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" { + fmt.Println("Cancelling package removal...") + os.Exit(1) + } + } + + // Execute operation + err = operation.Execute(verbose, force) + if err != nil { + log.Fatalf("Error: could not complete operation: %s\n", err) + } + + // Executing hooks + fmt.Println("Running hooks...") + err = operation.RunHooks(verbose) + if err != nil { + log.Fatalf("Error: could not run hooks: %s\n", err) + } } case file: files := subcommandArgs @@ -528,7 +552,7 @@ func resolveCommand() { var pkgList []string for _, pkg := range pkgs { - if slices.ContainsFunc(bpmlib.GetPackageFiles(pkg, rootDir), func(entry *bpmlib.PackageFileEntry) bool { + if slices.ContainsFunc(bpmlib.GetPackage(pkg, rootDir).PkgFiles, func(entry *bpmlib.PackageFileEntry) bool { return entry.Path == absFile }) { pkgList = append(pkgList, pkg) @@ -543,6 +567,169 @@ func resolveCommand() { } } } + case compile: + if len(subcommandArgs) == 0 { + fmt.Println("No source packages were given") + return + } + + // Read local databases + err := bpmlib.ReadLocalDatabaseFiles() + if err != nil { + log.Fatalf("Error: could not read local databases: %s", err) + } + + // Compile packages + for _, sourcePackage := range subcommandArgs { + if _, err := os.Stat(sourcePackage); os.IsNotExist(err) { + log.Fatalf("Error: file (%s) does not exist!", sourcePackage) + } + + // Read archive + bpmpkg, err := bpmlib.ReadPackage(sourcePackage) + if err != nil { + log.Fatalf("Could not read package (%s): %s", sourcePackage, err) + } + + // Ensure archive is source BPM package + if bpmpkg.PkgInfo.Type != "source" { + log.Fatalf("Error: cannot compile a non-source package!") + } + + // Get direct runtime and make dependencies + totalDepends := make([]string, 0) + for depend := range bpmpkg.PkgInfo.GetDependencies(true, false) { + if !slices.Contains(totalDepends, depend) { + totalDepends = append(totalDepends, depend) + } + } + + // Get unmet dependencies + unmetDepends := slices.Clone(totalDepends) + installedPackages, err := bpmlib.GetInstalledPackages("/") + if err != nil { + log.Fatalf("Error: could not get installed packages: %s\n", err) + } + for i := len(unmetDepends) - 1; i >= 0; i-- { + if slices.Contains(installedPackages, unmetDepends[i]) { + unmetDepends = append(unmetDepends[:i], unmetDepends[i+1:]...) + } else if ok, _ := bpmlib.IsVirtualPackage(unmetDepends[i], rootDir); ok { + unmetDepends = append(unmetDepends[:i], unmetDepends[i+1:]...) + } + } + + // Install missing source package dependencies + if installSrcPkgDepends && len(unmetDepends) > 0 { + // Get path to current executable + executable, err := os.Executable() + if err != nil { + log.Fatalf("Error: could not get path to executable: %s\n", err) + } + + // Run 'bpm install' using the set privilege escalator command + args := []string{executable, "install", "--installation-reason=make-dependency"} + args = append(args, unmetDepends...) + cmd := exec.Command(bpmlib.BPMConfig.PrivilegeEscalatorCmd, args...) + if yesAll { + cmd.Args = slices.Insert(cmd.Args, 3, "-y") + } + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + if verbose { + fmt.Println("Running command: " + cmd.String()) + } + err = cmd.Run() + if err != nil { + log.Fatalf("Error: dependency installation command failed: %s\n", err) + } + } else { + // Ensure the required dependencies are installed + if len(unmetDepends) != 0 { + log.Fatalf("Error: could not resolve dependencies: the following dependencies were not found in any databases: " + strings.Join(unmetDepends, ", ")) + } + } + + // Get current working directory + workdir, err := os.Getwd() + if err != nil { + log.Fatalf("Error: could not get working directory: %s", err) + } + + // Get user home directory + homedir, err := os.UserHomeDir() + if err != nil { + log.Fatalf("Error: could not get user home directory: %s", err) + } + + // Trim output directory + outputDirectory = strings.TrimSpace(outputDirectory) + if outputDirectory != "/" { + outputDirectory = strings.TrimSuffix(outputDirectory, "/") + } + + // Set output directory if empty + if outputDirectory == "" { + outputDirectory = workdir + } + + // Replace first tilde with user home directory + if strings.Split(outputDirectory, "/")[0] == "~" { + outputDirectory = strings.Replace(outputDirectory, "~", homedir, 1) + } + + // Prepend current working directory to output directory if not an absolute path + if outputDirectory != "" && !strings.HasPrefix(outputDirectory, "/") { + outputDirectory = filepath.Join(workdir, outputDirectory) + } + + // Clean path + path.Clean(outputDirectory) + + // Ensure output directory exists and is a directory + stat, err := os.Stat(outputDirectory) + if err != nil { + log.Fatalf("Error: could not stat output directory (%s): %s", outputDirectory, err) + } + if !stat.IsDir() { + log.Fatalf("Error: output directory (%s) is not a directory", outputDirectory) + } + + outputBpmPackages, err := bpmlib.CompileSourcePackage(sourcePackage, outputDirectory, skipChecks) + if err != nil { + log.Fatalf("Error: could not compile source package (%s): %s", sourcePackage, err) + } + + for k, v := range outputBpmPackages { + fmt.Printf("Package (%s) was successfully compiled! Binary package generated at: %s\n", k, v) + } + + // Remove unused packages + if installSrcPkgDepends && len(unmetDepends) > 0 { + // Get path to current executable + executable, err := os.Executable() + if err != nil { + log.Fatalf("Error: could not get path to executable: %s\n", err) + } + + // Run 'bpm cleanup' using the set privilege escalator command + cmd := exec.Command(bpmlib.BPMConfig.PrivilegeEscalatorCmd, executable, "cleanup") + if yesAll { + cmd.Args = slices.Insert(cmd.Args, 3, "-y") + } + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + if verbose { + fmt.Println("Running command: " + cmd.String()) + } + err = cmd.Run() + if err != nil { + log.Fatalf("Error: dependency cleanup command failed: %s\n", err) + } + } + } + default: printHelp() } @@ -554,25 +741,24 @@ func printHelp() { fmt.Println("-> flags will be read if passed right after the subcommand otherwise they will be read as subcommand arguments") fmt.Println("\033[1m---- Command List ----\033[0m") fmt.Println("-> bpm version | shows information on the installed version of bpm") - fmt.Println("-> bpm info [-R, --repos] | shows information on an installed package") + fmt.Println("-> bpm info [-R, --databases] | shows information on an installed package") fmt.Println(" -R= lets you define the root path which will be used") - fmt.Println(" --repos show information on package in repository") + fmt.Println(" --databases show information on package in configured databases") fmt.Println("-> bpm list [-R, -c, -n] | lists all installed packages") fmt.Println(" -R= lets you define the root path which will be used") fmt.Println(" -c lists the amount of installed packages") fmt.Println(" -n lists only the names of installed packages") - fmt.Println("-> bpm search | Searches for packages through declared repositories") - fmt.Println("-> bpm install [-R, -v, -y, -f, -o, -c, -b, -k, --reinstall, --reinstall-all, --no-optional, --installation-reason] | installs the following files") + fmt.Println("-> bpm search | Searches for packages through configured databases") + fmt.Println("-> bpm install [-R, -v, -y, -f, --reinstall, --reinstall-all, --no-optional, --installation-reason] | installs the following files") fmt.Println(" -R= lets you define the root path which will be used") fmt.Println(" -v Show additional information about what BPM is doing") fmt.Println(" -y skips the confirmation prompt") fmt.Println(" -f skips dependency, conflict and architecture checking") - fmt.Println(" -k keeps the compilation directory created by BPM after source package installation") fmt.Println(" --reinstall Reinstalls packages even if they do not have a newer version available") fmt.Println(" --reinstall-all Same as --reinstall but also reinstalls dependencies") fmt.Println(" --no-optional Prevents installation of optional dependencies") fmt.Println(" --installation-reason= sets the installation reason for all newly installed packages") - fmt.Println("-> bpm update [-R, -v, -y, -f, --reinstall, --no-sync] | updates all packages that are available in the repositories") + fmt.Println("-> bpm update [-R, -v, -y, -f, --reinstall, --no-sync] | updates all packages that are available in the configured databases") fmt.Println(" -R= lets you define the root path which will be used") fmt.Println(" -v Show additional information about what BPM is doing") fmt.Println(" -y skips the confirmation prompt") @@ -589,12 +775,24 @@ func printHelp() { fmt.Println(" -y skips the confirmation prompt") fmt.Println(" --unused removes only packages that aren't required as dependencies by other packages") fmt.Println(" --cleanup performs a dependency cleanup") - fmt.Println("-> bpm cleanup [-R, -v, -y] | remove all unused dependency packages") + fmt.Println("-> bpm cleanup [-R, -v, -y, --depends, --compilation-files, --compiled-pkgs, --fetched-pkgs] | remove all unused dependencies and cache directories") fmt.Println(" -v Show additional information about what BPM is doing") fmt.Println(" -R= lets you define the root path which will be used") fmt.Println(" -y skips the confirmation prompt") + fmt.Println(" --depends performs a dependency cleanup") + fmt.Println(" --make-depends performs a make dependency cleanup") + fmt.Println(" --compilation-files performs a cleanup of compilation files") + fmt.Println(" --compiled-pkgs performs a cleanup of compilation compiled binary packages") + fmt.Println(" --fetched-pkgs performs a cleanup of fetched packages from databases") fmt.Println("-> bpm file [-R] | shows what packages the following packages are managed by") fmt.Println(" -R= lets you define the root path which will be used") + fmt.Println("-> bpm compile [-d, -s, -o] | Compile source BPM package") + fmt.Println(" -v Show additional information about what BPM is doing") + fmt.Println(" -d installs required dependencies for package compilation") + fmt.Println(" -s skips the check function in source.sh scripts") + fmt.Println(" -o sets output directory") + fmt.Println(" -y skips the confirmation prompt") + fmt.Println("\033[1m----------------\033[0m") } @@ -608,7 +806,7 @@ func resolveFlags() { // Info flags infoFlagSet := flag.NewFlagSet("Info flags", flag.ExitOnError) infoFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root") - infoFlagSet.BoolVar(&showRepoInfo, "repos", false, "Show information on package in repository") + infoFlagSet.BoolVar(&showDatabaseInfo, "databases", false, "Show information on package in configured databases") infoFlagSet.Usage = printHelp // Install flags installFlagSet := flag.NewFlagSet("Install flags", flag.ExitOnError) @@ -648,11 +846,35 @@ func resolveFlags() { cleanupFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root") cleanupFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing") cleanupFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts") + cleanupFlagSet.BoolVar(&cleanupDependencies, "depends", false, "Perform a dependency cleanup") + cleanupFlagSet.BoolVar(&cleanupMakeDependencies, "make-depends", false, "Perform a make dependency cleanup") + cleanupFlagSet.BoolVar(&cleanupCompilationFiles, "compilation-files", false, "Perform a cleanup of compilation files") + cleanupFlagSet.BoolVar(&cleanupCompiledPackages, "compiled-pkgs", false, "Perform a cleanup of compilation compiled binary packages") + cleanupFlagSet.BoolVar(&cleanupFetchedPackages, "fetched-pkgs", false, "Perform a cleanup of fetched packages from databases") cleanupFlagSet.Usage = printHelp // File flags fileFlagSet := flag.NewFlagSet("Remove flags", flag.ExitOnError) fileFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root") fileFlagSet.Usage = printHelp + // Compile flags + compileFlagSet := flag.NewFlagSet("Compile flags", flag.ExitOnError) + compileFlagSet.BoolVar(&installSrcPkgDepends, "d", false, "Install required dependencies for package compilation") + compileFlagSet.BoolVar(&skipChecks, "s", false, "Skip the check function in source.sh scripts") + compileFlagSet.StringVar(&outputDirectory, "o", "", "Set output directory") + compileFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing") + compileFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts") + compileFlagSet.Usage = printHelp + + isFlagSet := func(flagSet *flag.FlagSet, name string) bool { + found := false + flagSet.Visit(func(f *flag.Flag) { + if f.Name == name { + found = true + } + }) + return found + } + if len(os.Args[1:]) <= 0 { subcommand = "help" } else { @@ -694,12 +916,31 @@ func resolveFlags() { return } subcommandArgs = removeFlagSet.Args() + } else if getCommandType() == cleanup { + err := cleanupFlagSet.Parse(subcommandArgs) + if err != nil { + return + } + if !isFlagSet(cleanupFlagSet, "depends") && !isFlagSet(cleanupFlagSet, "make-depends") && !isFlagSet(cleanupFlagSet, "compilation-files") && !isFlagSet(cleanupFlagSet, "compiled-pkgs") && !isFlagSet(cleanupFlagSet, "fetched-pkgs") { + cleanupDependencies = true + cleanupMakeDependencies = bpmlib.BPMConfig.CleanupMakeDependencies + cleanupCompilationFiles = true + cleanupCompiledPackages = true + cleanupFetchedPackages = true + } + subcommandArgs = cleanupFlagSet.Args() } else if getCommandType() == file { err := fileFlagSet.Parse(subcommandArgs) if err != nil { return } subcommandArgs = fileFlagSet.Args() + } else if getCommandType() == compile { + err := compileFlagSet.Parse(subcommandArgs) + if err != nil { + return + } + subcommandArgs = compileFlagSet.Args() } if reinstallAll { reinstall = true diff --git a/src/bpmlib/compilation.go b/src/bpmlib/compilation.go new file mode 100644 index 0000000..f8434e4 --- /dev/null +++ b/src/bpmlib/compilation.go @@ -0,0 +1,382 @@ +package bpmlib + +import ( + "errors" + "fmt" + "gopkg.in/yaml.v3" + "os" + "os/exec" + "path" + "strconv" + "strings" + "syscall" +) + +var rootCompilationUID = "65534" +var rootCompilationGID = "65534" + +func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bool) (outputBpmPackages map[string]string, err error) { + // Initialize map + outputBpmPackages = make(map[string]string) + + // Read BPM archive + bpmpkg, err := ReadPackage(archiveFilename) + if err != nil { + return nil, err + } + + // Ensure package type is 'source' + if bpmpkg.PkgInfo.Type != "source" { + return nil, errors.New("cannot compile a non-source package") + } + + // Read compilation options file in current directory + compilationOptions, err := readCompilationOptionsFile() + if err != nil { + return nil, err + } + + // Get HOME directory + homeDir, err := os.UserHomeDir() + if err != nil { + return nil, err + } + + // Get UID and GID to use for compilation + var uid, gid int + if os.Getuid() == 0 { + _uid, err := strconv.ParseInt(rootCompilationUID, 10, 32) + if err != nil { + return nil, fmt.Errorf("could not convert UID '%s' to int", rootCompilationUID) + } + _gid, err := strconv.ParseInt(rootCompilationGID, 10, 32) + if err != nil { + return nil, fmt.Errorf("could not convert GID '%s' to int", rootCompilationGID) + } + uid = int(_uid) + gid = int(_gid) + } else { + uid = os.Getuid() + gid = os.Getgid() + } + + // Set temporary directory + var tempDirectory string + if os.Getuid() == 0 { + tempDirectory = path.Join("/var/cache/bpm/compilation/", bpmpkg.PkgInfo.Name) + } else { + tempDirectory = path.Join(homeDir, ".cache/bpm/compilation/", bpmpkg.PkgInfo.Name) + } + + // Ensure temporary directory does not exist + if _, err := os.Stat(tempDirectory); err == nil { + err := os.RemoveAll(tempDirectory) + if err != nil { + return nil, err + } + } + + // Create temporary directory + err = os.MkdirAll(tempDirectory, 0755) + if err != nil { + return nil, err + } + + // Change temporary directory owner + err = os.Chown(tempDirectory, uid, gid) + if err != nil { + return nil, err + } + + // Extract source.sh file + err = extractTarballFile(archiveFilename, "source.sh", tempDirectory, uid, gid) + if err != nil { + return nil, err + } + + // Get package scripts and extract them + packageScripts := getPackageScripts(archiveFilename) + for _, script := range packageScripts { + err = extractTarballFile(archiveFilename, script, tempDirectory, uid, gid) + if err != nil { + return nil, err + } + } + + // Extract source files + err = extractTarballDirectory(archiveFilename, "source-files", tempDirectory, uid, gid) + if err != nil { + return nil, err + } + + // Create source directory + err = os.Mkdir(path.Join(tempDirectory, "source"), 0755) + if err != nil { + return nil, err + } + + // Change source directory owner + err = os.Chown(path.Join(tempDirectory, "source"), uid, gid) + if err != nil { + return nil, err + } + + // Setup environment for commands + env := os.Environ() + env = append(env, "HOME="+tempDirectory) + env = append(env, "BPM_WORKDIR="+tempDirectory) + env = append(env, "BPM_SOURCE="+path.Join(tempDirectory, "source")) + env = append(env, "BPM_OUTPUT="+path.Join(tempDirectory, "output")) + env = append(env, "BPM_PKG_NAME="+bpmpkg.PkgInfo.Name) + env = append(env, "BPM_PKG_VERSION="+bpmpkg.PkgInfo.Version) + env = append(env, "BPM_PKG_REVISION="+strconv.Itoa(bpmpkg.PkgInfo.Revision)) + // Check for architecture override in compilation options + if val, ok := compilationOptions["ARCH"]; ok { + env = append(env, "BPM_PKG_ARCH="+val) + } else { + env = append(env, "BPM_PKG_ARCH="+GetArch()) + } + env = append(env, BPMConfig.CompilationEnvironment...) + + // Execute prepare and build functions in source.sh script + cmd := exec.Command("bash", "-c", + "set -a\n"+ // Source and export functions and variables in source.sh script + ". \"${BPM_WORKDIR}\"/source.sh\n"+ + "set +a\n"+ + "[[ $(type -t prepare) == \"function\" ]] && { echo \"Running prepare() function\"; bash -e -c 'cd \"$BPM_WORKDIR\" && prepare' || exit 1; }\n"+ // Run prepare() function if it exists + "[[ $(type -t build) == \"function\" ]] && { echo \"Running build() function\"; bash -e -c 'cd \"$BPM_SOURCE\" && build' || exit 1; }\n"+ // Run build() function if it exists + "exit 0") + cmd.Dir = tempDirectory + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = env + if os.Getuid() == 0 { + cmd.SysProcAttr = &syscall.SysProcAttr{} + cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} + } + err = cmd.Run() + if err != nil { + return nil, err + } + + // Execute check function in source.sh script if not skipping checks + if !skipChecks { + cmd = exec.Command("bash", "-c", + "set -a\n"+ // Source and export functions and variables in source.sh script + ". \"${BPM_WORKDIR}\"/source.sh\n"+ + "set +a\n"+ + "[[ $(type -t check) == \"function\" ]] && { echo \"Running check() function\"; bash -e -c 'cd \"$BPM_SOURCE\" && check' || exit 1; }\n"+ // Run check() function if it exists + "exit 0") + cmd.Dir = tempDirectory + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = env + if os.Getuid() == 0 { + cmd.SysProcAttr = &syscall.SysProcAttr{} + cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} + } + err = cmd.Run() + if err != nil { + return nil, err + } + } + + // Get all packages to compile + packagesToCompile := bpmpkg.PkgInfo.SplitPackages + if !bpmpkg.PkgInfo.IsSplitPackage() { + packagesToCompile = append(packagesToCompile, bpmpkg.PkgInfo) + } + + // Compile each package + for _, pkg := range packagesToCompile { + // Get package function name + packageFunctionName := "package" + if bpmpkg.PkgInfo.IsSplitPackage() { + packageFunctionName = "package_" + pkg.Name + } + + // Remove output directory if it already exists + if _, err := os.Stat(path.Join(tempDirectory, "output")); err == nil { + err := os.RemoveAll(path.Join(tempDirectory, "output")) + if err != nil { + return nil, err + } + } + + // Create new output directory + err = os.Mkdir(path.Join(tempDirectory, "output"), 0755) + if err != nil { + return nil, err + } + + // Change output directory owner + err = os.Chown(path.Join(tempDirectory, "output"), uid, gid) + if err != nil { + return nil, err + } + + // Execute package function in source.sh script and generate package file list + cmd = exec.Command("bash", "-c", + "set -a\n"+ // Source and export functions and variables in source.sh script + ". \"${BPM_WORKDIR}\"/source.sh\n"+ + "set +a\n"+ + "echo \"Running "+packageFunctionName+"() function\"\n"+ + "( cd \"$BPM_SOURCE\" && fakeroot -s \"$BPM_WORKDIR\"/fakeroot_file bash -e -c '"+packageFunctionName+"' ) || exit 1\n"+ // Run package() function + "fakeroot -i \"$BPM_WORKDIR\"/fakeroot_file find \"$BPM_OUTPUT\" -mindepth 1 -printf \"%P %#m %U %G %s\\n\" > \"$BPM_WORKDIR\"/pkg.files") // Create package file list + cmd.Dir = tempDirectory + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = env + if os.Getuid() == 0 { + cmd.SysProcAttr = &syscall.SysProcAttr{} + cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} + } + err = cmd.Run() + if err != nil { + return nil, err + } + + // Create gzip-compressed archive for the package files + cmd = exec.Command("bash", "-c", "find output -printf \"%P\\n\" | fakeroot -i \"$BPM_WORKDIR\"/fakeroot_file tar -czf files.tar.gz --no-recursion -C output -T -") + cmd.Dir = tempDirectory + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = env + if os.Getuid() == 0 { + cmd.SysProcAttr = &syscall.SysProcAttr{} + cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} + } + err = cmd.Run() + if err != nil { + return nil, fmt.Errorf("files.tar.gz archive could not be created: %s", err) + } + + // Clone source package info + pkgInfo := *pkg + + // Set package type to binary + pkgInfo.Type = "binary" + + // Set package architecture + if val, ok := compilationOptions["ARCH"]; ok { + pkgInfo.Arch = val + } else { + pkgInfo.Arch = GetArch() + } + + // Remove split package field + pkgInfo.SplitPackages = nil + + // Marshal package info + pkgInfoBytes, err := yaml.Marshal(pkgInfo) + if err != nil { + return nil, err + } + pkgInfoBytes = append(pkgInfoBytes, '\n') + + // Create pkg.info file + err = os.WriteFile(path.Join(tempDirectory, "pkg.info"), pkgInfoBytes, 0644) + if err != nil { + return nil, err + } + + // Change pkg.info file owner + err = os.Chown(path.Join(tempDirectory, "pkg.info"), uid, gid) + if err != nil { + return nil, err + } + + // Get files to include in BPM archive + bpmArchiveFiles := make([]string, 0) + bpmArchiveFiles = append(bpmArchiveFiles, "pkg.info", "pkg.files", "files.tar.gz") // Base files + bpmArchiveFiles = append(bpmArchiveFiles, packageScripts...) // Package scripts + + // Create final BPM archive + cmd = exec.Command("bash", "-c", "tar -cf final-archive.bpm --owner=0 --group=0 -C \"$BPM_WORKDIR\" "+strings.Join(bpmArchiveFiles, " ")) + cmd.Dir = tempDirectory + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + currentDir, err := os.Getwd() + if err != nil { + return nil, err + } + cmd.Env = append(env, "CURRENT_DIR="+currentDir) + if os.Getuid() == 0 { + cmd.SysProcAttr = &syscall.SysProcAttr{} + cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} + } + err = cmd.Run() + if err != nil { + return nil, fmt.Errorf("BPM archive could not be created: %s", err) + } + + // Remove pkg.info file + err = os.Remove(path.Join(tempDirectory, "pkg.info")) + if err != nil { + return nil, err + } + + // Set output filename + outputFilename := path.Join(outputDirectory, fmt.Sprintf("%s-%s-%d-%s.bpm", pkgInfo.Name, pkgInfo.Version, pkgInfo.Revision, pkgInfo.Arch)) + + // Move final BPM archive + err = os.Rename(path.Join(tempDirectory, "final-archive.bpm"), outputFilename) + if err != nil { + return nil, err + } + + // Set final BPM archive owner + err = os.Chown(outputFilename, os.Getuid(), os.Getgid()) + if err != nil { + return nil, err + } + + outputBpmPackages[pkgInfo.Name] = outputFilename + } + + return outputBpmPackages, nil +} + +func readCompilationOptionsFile() (options map[string]string, err error) { + // Initialize options map + options = make(map[string]string) + + // Check if file compilation options file exists + stat, err := os.Stat(".compilation-options") + if err != nil { + return nil, nil + } + + // Ensure it is a regular file + if !stat.Mode().IsRegular() { + return nil, fmt.Errorf("%s is not a regular file", stat.Name()) + } + + // Read file data + data, err := os.ReadFile(stat.Name()) + if err != nil { + return nil, err + } + + for _, line := range strings.Split(string(data), "\n") { + // Trim line + line = strings.TrimSpace(line) + + // Skip empty lines + if line == "" { + continue + } + + // Split line + split := strings.SplitN(line, "=", 2) + + // Throw error if line isn't valid + if len(split) < 2 { + return nil, fmt.Errorf("invalid line in compilation-options file: '%s'", line) + } + + options[split[0]] = split[1] + } + + return options, nil +} diff --git a/src/bpmlib/config.go b/src/bpmlib/config.go index eee5d57..e5f33c8 100644 --- a/src/bpmlib/config.go +++ b/src/bpmlib/config.go @@ -6,8 +6,11 @@ import ( ) type BPMConfigStruct struct { - IgnorePackages []string `yaml:"ignore_packages"` - Repositories []*Repository `yaml:"repositories"` + IgnorePackages []string `yaml:"ignore_packages"` + PrivilegeEscalatorCmd string `yaml:"privilege_escalator_cmd"` + CompilationEnvironment []string `yaml:"compilation_env"` + CleanupMakeDependencies bool `yaml:"cleanup_make_dependencies"` + Databases []*BPMDatabase `yaml:"databases"` } var BPMConfig BPMConfigStruct @@ -22,15 +25,17 @@ func ReadConfig() (err error) { return err } - BPMConfig = BPMConfigStruct{} + BPMConfig = BPMConfigStruct{ + CleanupMakeDependencies: true, + } err = yaml.Unmarshal(bytes, &BPMConfig) if err != nil { return err } - for i := len(BPMConfig.Repositories) - 1; i >= 0; i-- { - if BPMConfig.Repositories[i].Disabled != nil && *BPMConfig.Repositories[i].Disabled { - BPMConfig.Repositories = append(BPMConfig.Repositories[:i], BPMConfig.Repositories[i+1:]...) + for i := len(BPMConfig.Databases) - 1; i >= 0; i-- { + if BPMConfig.Databases[i].Disabled != nil && *BPMConfig.Databases[i].Disabled { + BPMConfig.Databases = append(BPMConfig.Databases[:i], BPMConfig.Databases[i+1:]...) } } diff --git a/src/bpmlib/databases.go b/src/bpmlib/databases.go new file mode 100644 index 0000000..6b14195 --- /dev/null +++ b/src/bpmlib/databases.go @@ -0,0 +1,281 @@ +package bpmlib + +import ( + "errors" + "fmt" + "gopkg.in/yaml.v3" + "io" + "net/http" + "net/url" + "os" + "path" + "strings" +) + +type BPMDatabase struct { + Name string `yaml:"name"` + Source string `yaml:"source"` + Disabled *bool `yaml:"disabled"` + Entries map[string]*BPMDatabaseEntry + VirtualPackages map[string][]string +} + +type BPMDatabaseEntry struct { + Info *PackageInfo `yaml:"info"` + Download string `yaml:"download"` + DownloadSize uint64 `yaml:"download_size"` + InstalledSize uint64 `yaml:"installed_size"` + Database *BPMDatabase +} + +func (db *BPMDatabase) ContainsPackage(pkg string) bool { + _, ok := db.Entries[pkg] + return ok +} + +func (db *BPMDatabase) ReadLocalDatabase() error { + dbFile := "/var/lib/bpm/databases/" + db.Name + ".bpmdb" + if _, err := os.Stat(dbFile); err != nil { + return nil + } + + bytes, err := os.ReadFile(dbFile) + if err != nil { + return err + } + + data := string(bytes) + for _, b := range strings.Split(data, "---") { + entry := BPMDatabaseEntry{ + Info: &PackageInfo{ + Name: "", + Description: "", + Version: "", + Revision: 1, + Url: "", + License: "", + Arch: "", + Type: "", + Keep: make([]string, 0), + Depends: make([]string, 0), + MakeDepends: make([]string, 0), + OptionalDepends: make([]string, 0), + Conflicts: make([]string, 0), + Provides: make([]string, 0), + }, + Download: "", + DownloadSize: 0, + InstalledSize: 0, + Database: db, + } + err := yaml.Unmarshal([]byte(b), &entry) + if err != nil { + return err + } + + // Create database entries + if entry.Info.IsSplitPackage() { + for _, splitPkg := range entry.Info.SplitPackages { + // Turn split package into json data + splitPkgJson, err := yaml.Marshal(splitPkg) + if err != nil { + return err + } + + // Clone all main package fields onto split package + splitPkgClone := *entry.Info + + // Set split package field of split package to nil + splitPkgClone.SplitPackages = nil + + // Unmarshal json data back to struct + err = yaml.Unmarshal(splitPkgJson, &splitPkgClone) + if err != nil { + return err + } + + // Force set split package version, revision and URL + splitPkgClone.Version = entry.Info.Version + splitPkgClone.Revision = entry.Info.Revision + splitPkgClone.Url = entry.Info.Url + + // Create entry for split package + db.Entries[splitPkg.Name] = &BPMDatabaseEntry{ + Info: &splitPkgClone, + Download: entry.Download, + DownloadSize: entry.DownloadSize, + InstalledSize: 0, + Database: db, + } + + // Add virtual packages to database + for _, p := range splitPkg.Provides { + db.VirtualPackages[p] = append(db.VirtualPackages[p], splitPkg.Name) + } + } + } else { + // Create entry for package + db.Entries[entry.Info.Name] = &entry + + // Add virtual packages to database + for _, p := range entry.Info.Provides { + db.VirtualPackages[p] = append(db.VirtualPackages[p], entry.Info.Name) + } + } + + } + + return nil +} + +func (db *BPMDatabase) SyncLocalDatabaseFile() error { + dbFile := "/var/lib/bpm/databases/" + db.Name + ".bpmdb" + + // Get URL to database + u, err := url.JoinPath(db.Source, "database.bpmdb") + if err != nil { + return err + } + + // Retrieve data from URL + resp, err := http.Get(u) + if err != nil { + return err + } + defer resp.Body.Close() + + // Load data into byte buffer + buffer, err := io.ReadAll(resp.Body) + + // Unmarshal data to ensure it is a valid BPM database + err = yaml.Unmarshal(buffer, &BPMDatabase{}) + if err != nil { + return fmt.Errorf("could not decode database: %s", err) + } + + // Create parent directories to database file + err = os.MkdirAll(path.Dir(dbFile), 0755) + if err != nil { + return err + } + + // Create file and save database data + out, err := os.Create(dbFile) + if err != nil { + return err + } + defer out.Close() + + _, err = out.Write(buffer) + + return nil +} + +func ReadLocalDatabaseFiles() (err error) { + for _, db := range BPMConfig.Databases { + // Initialize struct values + db.Entries = make(map[string]*BPMDatabaseEntry) + db.VirtualPackages = make(map[string][]string) + + // Read database + err = db.ReadLocalDatabase() + if err != nil { + return err + } + } + + return nil +} + +func GetDatabase(name string) *BPMDatabase { + for _, db := range BPMConfig.Databases { + if db.Name == name { + return db + } + } + return nil +} + +func GetDatabaseEntry(str string) (*BPMDatabaseEntry, *BPMDatabase, error) { + split := strings.Split(str, "/") + if len(split) == 1 { + pkgName := strings.TrimSpace(split[0]) + if pkgName == "" { + return nil, nil, errors.New("could not find database entry for this package") + } + for _, db := range BPMConfig.Databases { + if db.ContainsPackage(pkgName) { + return db.Entries[pkgName], db, nil + } + } + return nil, nil, errors.New("could not find database entry for this package") + } else if len(split) == 2 { + dbName := strings.TrimSpace(split[0]) + pkgName := strings.TrimSpace(split[1]) + if dbName == "" || pkgName == "" { + return nil, nil, errors.New("could not find database entry for this package") + } + db := GetDatabase(dbName) + if db == nil || !db.ContainsPackage(pkgName) { + return nil, nil, errors.New("could not find database entry for this package") + } + return db.Entries[pkgName], db, nil + } else { + return nil, nil, errors.New("could not find database entry for this package") + } +} + +func FindReplacement(pkg string) *BPMDatabaseEntry { + for _, db := range BPMConfig.Databases { + for _, entry := range db.Entries { + for _, replaced := range entry.Info.Replaces { + if replaced == pkg { + return entry + } + } + } + } + + return nil +} + +func ResolveVirtualPackage(vpkg string) *BPMDatabaseEntry { + for _, db := range BPMConfig.Databases { + if v, ok := db.VirtualPackages[vpkg]; ok { + for _, pkg := range v { + return db.Entries[pkg] + } + } + } + + return nil +} + +func (db *BPMDatabase) FetchPackage(pkg string) (string, error) { + if !db.ContainsPackage(pkg) { + return "", errors.New("could not fetch package '" + pkg + "'") + } + entry := db.Entries[pkg] + URL, err := url.JoinPath(db.Source, entry.Download) + if err != nil { + return "", err + } + resp, err := http.Get(URL) + if err != nil { + return "", err + } + defer resp.Body.Close() + + err = os.MkdirAll("/var/cache/bpm/fetched/", 0755) + if err != nil { + return "", err + } + out, err := os.Create("/var/cache/bpm/fetched/" + path.Base(entry.Download)) + if err != nil { + return "", err + } + defer out.Close() + + _, err = io.Copy(out, resp.Body) + return "/var/cache/bpm/fetched/" + path.Base(entry.Download), nil +} diff --git a/src/bpmlib/dependencies.go b/src/bpmlib/dependencies.go new file mode 100644 index 0000000..d55a6a0 --- /dev/null +++ b/src/bpmlib/dependencies.go @@ -0,0 +1,173 @@ +package bpmlib + +import ( + "errors" + "fmt" + "slices" +) + +func (pkgInfo *PackageInfo) GetDependencies(includeMakeDepends, includeOptionalDepends bool) map[string]InstallationReason { + allDepends := make(map[string]InstallationReason) + + for _, depend := range pkgInfo.Depends { + allDepends[depend] = InstallationReasonDependency + } + if includeOptionalDepends { + for _, depend := range pkgInfo.OptionalDepends { + if _, ok := allDepends[depend]; !ok { + allDepends[depend] = InstallationReasonDependency + } + } + } + if includeMakeDepends { + for _, depend := range pkgInfo.MakeDepends { + if _, ok := allDepends[depend]; !ok { + allDepends[depend] = InstallationReasonMakeDependency + } + } + } + return allDepends +} + +func (pkgInfo *PackageInfo) GetAllDependencies(includeMakeDepends, includeOptionalDepends bool, rootDir string) (resolved []string) { + // Initialize slices + resolved = make([]string, 0) + unresolved := make([]string, 0) + + // Call unexported function + pkgInfo.getAllDependencies(&resolved, &unresolved, includeMakeDepends, includeOptionalDepends, rootDir) + + return resolved +} + +func (pkgInfo *PackageInfo) getAllDependencies(resolved *[]string, unresolved *[]string, includeMakeDepends, includeOptionalDepends bool, rootDir string) { + // Add current package name to unresolved slice + *unresolved = append(*unresolved, pkgInfo.Name) + + // Loop through all dependencies + for depend := range pkgInfo.GetDependencies(includeMakeDepends, includeOptionalDepends) { + if isVirtual, p := IsVirtualPackage(depend, rootDir); isVirtual { + depend = p + } + if !slices.Contains(*resolved, depend) { + // Add current dependency to resolved slice when circular dependency is detected + if slices.Contains(*unresolved, depend) { + if !slices.Contains(*resolved, depend) { + *resolved = append(*resolved, depend) + } + continue + } + + dependInfo := GetPackageInfo(depend, rootDir) + + if dependInfo != nil { + dependInfo.getAllDependencies(resolved, unresolved, includeMakeDepends, includeOptionalDepends, rootDir) + } + } + } + if !slices.Contains(*resolved, pkgInfo.Name) { + *resolved = append(*resolved, pkgInfo.Name) + } + *unresolved = stringSliceRemove(*unresolved, pkgInfo.Name) +} + +func ResolveAllPackageDependenciesFromDatabases(pkgInfo *PackageInfo, checkMake, checkOptional, ignoreInstalled, verbose bool, rootDir string) (resolved map[string]InstallationReason, unresolved []string) { + // Initialize slices + resolved = make(map[string]InstallationReason) + unresolved = make([]string, 0) + + // Call unexported function + resolvePackageDependenciesFromDatabase(resolved, &unresolved, pkgInfo, InstallationReasonDependency, checkMake, checkOptional, ignoreInstalled, verbose, rootDir) + + return resolved, unresolved +} + +func resolvePackageDependenciesFromDatabase(resolved map[string]InstallationReason, unresolved *[]string, pkgInfo *PackageInfo, installationReason InstallationReason, checkMake, checkOptional, ignoreInstalled, verbose bool, rootDir string) { + // Add current package name to unresolved slice + *unresolved = append(*unresolved, pkgInfo.Name) + + // Loop through all dependencies + for depend, ir := range pkgInfo.GetDependencies(pkgInfo.Type == "source", checkOptional) { + if _, ok := resolved[depend]; !ok { + // Add current dependency to resolved slice when circular dependency is detected + if slices.Contains(*unresolved, depend) { + if verbose { + fmt.Printf("Circular dependency was detected (%s -> %s). Installing %s first\n", pkgInfo.Name, depend, depend) + } + if _, ok := resolved[depend]; !ok { + resolved[depend] = ir + } + continue + } else if ignoreInstalled && IsPackageProvided(depend, rootDir) { + continue + } + var err error + var entry *BPMDatabaseEntry + entry, _, err = GetDatabaseEntry(depend) + if err != nil { + if entry = ResolveVirtualPackage(depend); entry == nil { + if !slices.Contains(*unresolved, depend) { + *unresolved = append(*unresolved, depend) + } + continue + } + } + resolvePackageDependenciesFromDatabase(resolved, unresolved, entry.Info, ir, checkMake, checkOptional, ignoreInstalled, verbose, rootDir) + } + } + + if _, ok := resolved[pkgInfo.Name]; !ok { + resolved[pkgInfo.Name] = installationReason + } + *unresolved = stringSliceRemove(*unresolved, pkgInfo.Name) +} + +func GetPackageDependants(pkgName string, rootDir string) ([]string, error) { + ret := make([]string, 0) + + // Get BPM package + pkg := GetPackage(pkgName, rootDir) + if pkg == nil { + return nil, errors.New("package not found: " + pkgName) + } + + // Get installed package names + pkgs, err := GetInstalledPackages(rootDir) + if err != nil { + return nil, errors.New("could not get installed packages") + } + + // Loop through all installed packages + for _, installedPkgName := range pkgs { + // Get installed BPM package + installedPkg := GetPackage(installedPkgName, rootDir) + if installedPkg == nil { + return nil, errors.New("package not found: " + installedPkgName) + } + + // Skip iteration if comparing the same packages + if installedPkg.PkgInfo.Name == pkgName { + continue + } + + // Get installed package dependencies + dependencies := installedPkg.PkgInfo.GetDependencies(false, true) + + // Add installed package to list if its dependencies include pkgName + if _, ok := dependencies[pkgName]; ok { + ret = append(ret, installedPkgName) + continue + } + + // Loop through each virtual package + for _, vpkg := range pkg.PkgInfo.Provides { + // Add installed package to list if its dependencies contain a provided virtual package + if _, ok := dependencies[vpkg]; ok { + ret = append(ret, installedPkgName) + break + } + } + } + + return ret, nil +} diff --git a/src/bpmlib/errors.go b/src/bpmlib/errors.go index b9d193e..618251a 100644 --- a/src/bpmlib/errors.go +++ b/src/bpmlib/errors.go @@ -10,7 +10,7 @@ type PackageNotFoundErr struct { } func (e PackageNotFoundErr) Error() string { - return "The following packages were not found in any repositories: " + strings.Join(e.packages, ", ") + return "The following packages were not found in any databases: " + strings.Join(e.packages, ", ") } type DependencyNotFoundErr struct { @@ -18,7 +18,7 @@ type DependencyNotFoundErr struct { } func (e DependencyNotFoundErr) Error() string { - return "The following dependencies were not found in any repositories: " + strings.Join(e.dependencies, ", ") + return "The following dependencies were not found in any databases: " + strings.Join(e.dependencies, ", ") } type PackageConflictErr struct { diff --git a/src/bpmlib/general.go b/src/bpmlib/general.go index c88ac32..ff80caa 100644 --- a/src/bpmlib/general.go +++ b/src/bpmlib/general.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "path" "slices" ) @@ -16,15 +17,15 @@ const ( ReinstallMethodAll ReinstallMethod = iota ) -// InstallPackages installs the specified packages into the given root directory by fetching them from repositories or directly from local bpm archives +// InstallPackages installs the specified packages into the given root directory by fetching them from databases or directly from local bpm archives func InstallPackages(rootDir string, installationReason InstallationReason, reinstallMethod ReinstallMethod, installOptionalDependencies, forceInstallation, verbose bool, packages ...string) (operation *BPMOperation, err error) { // Setup operation struct operation = &BPMOperation{ - Actions: make([]OperationAction, 0), - UnresolvedDepends: make([]string, 0), - Changes: make(map[string]string), - RootDir: rootDir, - ForceInstallationReason: installationReason, + Actions: make([]OperationAction, 0), + UnresolvedDepends: make([]string, 0), + Changes: make(map[string]string), + RootDir: rootDir, + compiledPackages: make(map[string]string), } // Resolve packages @@ -35,21 +36,39 @@ func InstallPackages(rootDir string, installationReason InstallationReason, rein if err != nil { return nil, fmt.Errorf("could not read package: %s", err) } + + if bpmpkg.PkgInfo.Type == "source" && bpmpkg.PkgInfo.IsSplitPackage() { + for _, splitPkg := range bpmpkg.PkgInfo.SplitPackages { + if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(splitPkg.Name, rootDir) && GetPackageInfo(splitPkg.Name, rootDir).GetFullVersion() == splitPkg.GetFullVersion() { + continue + } + + operation.AppendAction(&InstallPackageAction{ + File: pkg, + InstallationReason: installationReason, + BpmPackage: bpmpkg, + SplitPackageToInstall: splitPkg.Name, + }) + } + continue + } + if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) && GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() == bpmpkg.PkgInfo.GetFullVersion() { continue } + operation.AppendAction(&InstallPackageAction{ - File: pkg, - IsDependency: false, - BpmPackage: bpmpkg, + File: pkg, + InstallationReason: installationReason, + BpmPackage: bpmpkg, }) } else { - var entry *RepositoryEntry + var entry *BPMDatabaseEntry - if e, _, err := GetRepositoryEntry(pkg); err == nil { + if e, _, err := GetDatabaseEntry(pkg); err == nil { entry = e } else if isVirtual, p := IsVirtualPackage(pkg, rootDir); isVirtual { - entry, _, err = GetRepositoryEntry(p) + entry, _, err = GetDatabaseEntry(p) if err != nil { pkgsNotFound = append(pkgsNotFound, pkg) continue @@ -63,9 +82,10 @@ func InstallPackages(rootDir string, installationReason InstallationReason, rein if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(entry.Info.Name, rootDir) && GetPackageInfo(entry.Info.Name, rootDir).GetFullVersion() == entry.Info.GetFullVersion() { continue } + operation.AppendAction(&FetchPackageAction{ - IsDependency: false, - RepositoryEntry: entry, + InstallationReason: installationReason, + DatabaseEntry: entry, }) } } @@ -112,12 +132,13 @@ func InstallPackages(rootDir string, installationReason InstallationReason, rein } // RemovePackages removes the specified packages from the given root directory -func RemovePackages(rootDir string, removeUnusedPackagesOnly, cleanupDependencies, verbose bool, packages ...string) (operation *BPMOperation, err error) { +func RemovePackages(rootDir string, removeUnusedPackagesOnly, cleanupDependencies bool, packages ...string) (operation *BPMOperation, err error) { operation = &BPMOperation{ Actions: make([]OperationAction, 0), UnresolvedDepends: make([]string, 0), Changes: make(map[string]string), RootDir: rootDir, + compiledPackages: make(map[string]string), } // Search for packages @@ -139,7 +160,7 @@ func RemovePackages(rootDir string, removeUnusedPackagesOnly, cleanupDependencie // Do package cleanup if cleanupDependencies { - err := operation.Cleanup(verbose) + err := operation.Cleanup(true) if err != nil { return nil, fmt.Errorf("could not perform cleanup for operation: %s", err) } @@ -148,16 +169,17 @@ func RemovePackages(rootDir string, removeUnusedPackagesOnly, cleanupDependencie } // CleanupPackages finds packages installed as dependencies which are no longer required by the rest of the system in the given root directory -func CleanupPackages(rootDir string, verbose bool) (operation *BPMOperation, err error) { +func CleanupPackages(cleanupMakeDepends bool, rootDir string) (operation *BPMOperation, err error) { operation = &BPMOperation{ Actions: make([]OperationAction, 0), UnresolvedDepends: make([]string, 0), Changes: make(map[string]string), RootDir: rootDir, + compiledPackages: make(map[string]string), } // Do package cleanup - err = operation.Cleanup(verbose) + err = operation.Cleanup(cleanupMakeDepends) if err != nil { return nil, fmt.Errorf("could not perform cleanup for operation: %s", err) } @@ -165,9 +187,91 @@ func CleanupPackages(rootDir string, verbose bool) (operation *BPMOperation, err return operation, nil } +func CleanupCache(rootDir string, cleanupCompilationFiles, cleanupCompiledPackages, cleanupFetchedPackages, verbose bool) error { + if cleanupCompilationFiles { + globalCompilationCacheDir := path.Join(rootDir, "var/cache/bpm/compilation") + + // Ensure path exists and is a directory + if stat, err := os.Stat(globalCompilationCacheDir); err == nil && stat.IsDir() { + // Delete directory + if verbose { + log.Printf("Removing directory (%s)\n", globalCompilationCacheDir) + } + err = os.RemoveAll(globalCompilationCacheDir) + if err != nil { + return err + } + } + + // Get home directories of users in root directory + homeDirs, err := os.ReadDir(path.Join(rootDir, "home")) + if err != nil { + return err + } + + // Loop through all home directories + for _, homeDir := range homeDirs { + // Skip if not a directory + if !homeDir.IsDir() { + continue + } + + localCompilationDir := path.Join(rootDir, "home", homeDir.Name(), ".cache/bpm/compilation") + + // Ensure path exists and is a directory + if stat, err := os.Stat(localCompilationDir); err != nil || !stat.IsDir() { + continue + } + + // Delete directory + if verbose { + log.Printf("Removing directory (%s)\n", localCompilationDir) + } + err = os.RemoveAll(localCompilationDir) + if err != nil { + return err + } + } + } + + if cleanupCompiledPackages { + dirToRemove := path.Join(rootDir, "var/cache/bpm/compiled") + + // Ensure path exists and is a directory + if stat, err := os.Stat(dirToRemove); err == nil && stat.IsDir() { + // Delete directory + if verbose { + log.Printf("Removing directory (%s)\n", dirToRemove) + } + err = os.RemoveAll(dirToRemove) + if err != nil { + return err + } + } + } + + if cleanupFetchedPackages { + dirToRemove := path.Join(rootDir, "var/cache/bpm/fetched") + + // Ensure path exists and is a directory + if stat, err := os.Stat(dirToRemove); err == nil && stat.IsDir() { + // Delete directory + if verbose { + log.Printf("Removing directory (%s)\n", dirToRemove) + } + err = os.RemoveAll(dirToRemove) + if err != nil { + return err + } + } + } + + return nil +} + // UpdatePackages fetches the newest versions of all installed packages from func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependencies, forceInstallation, verbose bool) (operation *BPMOperation, err error) { - // Sync repositories + // Sync databases if syncDatabase { err := SyncDatabase(verbose) if err != nil { @@ -176,12 +280,16 @@ func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependenci if verbose { fmt.Println("All package databases synced successfully!") } - } - // Reload config and local databases - err = ReadConfig() - if err != nil { - return nil, fmt.Errorf("could not read BPM config: %s", err) + // Reload config and local databases + err = ReadConfig() + if err != nil { + return nil, fmt.Errorf("could not read BPM config: %s", err) + } + err = ReadLocalDatabaseFiles() + if err != nil { + return nil, fmt.Errorf("could not read local databases: %s", err) + } } // Get installed packages and check for updates @@ -191,11 +299,11 @@ func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependenci } operation = &BPMOperation{ - Actions: make([]OperationAction, 0), - UnresolvedDepends: make([]string, 0), - Changes: make(map[string]string), - RootDir: rootDir, - ForceInstallationReason: InstallationReasonUnknown, + Actions: make([]OperationAction, 0), + UnresolvedDepends: make([]string, 0), + Changes: make(map[string]string), + RootDir: rootDir, + compiledPackages: make(map[string]string), } // Search for packages @@ -203,11 +311,11 @@ func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependenci if slices.Contains(BPMConfig.IgnorePackages, pkg) { continue } - var entry *RepositoryEntry + var entry *BPMDatabaseEntry // Check if installed package can be replaced and install that instead if e := FindReplacement(pkg); e != nil { entry = e - } else if entry, _, err = GetRepositoryEntry(pkg); err != nil { + } else if entry, _, err = GetDatabaseEntry(pkg); err != nil { continue } @@ -218,8 +326,8 @@ func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependenci comparison := ComparePackageVersions(*entry.Info, *installedInfo) if comparison > 0 { operation.AppendAction(&FetchPackageAction{ - IsDependency: false, - RepositoryEntry: entry, + InstallationReason: GetInstallationReason(pkg, rootDir), + DatabaseEntry: entry, }) } } @@ -245,12 +353,12 @@ func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependenci // SyncDatabase syncs all databases declared in /etc/bpm.conf func SyncDatabase(verbose bool) (err error) { - for _, repo := range BPMConfig.Repositories { + for _, db := range BPMConfig.Databases { if verbose { - fmt.Printf("Fetching package database for repository (%s)...\n", repo.Name) + fmt.Printf("Fetching package database file for database (%s)...\n", db.Name) } - err := repo.SyncLocalDatabase() + err := db.SyncLocalDatabaseFile() if err != nil { return err } diff --git a/src/bpmlib/hooks.go b/src/bpmlib/hooks.go index a9bbff4..d6e6d23 100644 --- a/src/bpmlib/hooks.go +++ b/src/bpmlib/hooks.go @@ -99,7 +99,10 @@ func (hook *BPMHook) Execute(packageChanges map[string]string, verbose bool, roo // Get modified files slice modifiedFiles := make([]*PackageFileEntry, 0) for pkg := range packageChanges { - modifiedFiles = append(modifiedFiles, GetPackageFiles(pkg, rootDir)...) + if GetPackage(pkg, rootDir) != nil { + modifiedFiles = append(modifiedFiles, GetPackage(pkg, rootDir).PkgFiles...) + } + } // Check if any targets are met diff --git a/src/bpmlib/installed_packages.go b/src/bpmlib/installed_packages.go new file mode 100644 index 0000000..dc295f1 --- /dev/null +++ b/src/bpmlib/installed_packages.go @@ -0,0 +1,243 @@ +package bpmlib + +import ( + "errors" + "fmt" + "os" + "path" + "slices" + "strconv" + "strings" +) + +var localPackageInformation map[string]map[string]*BPMPackage = make(map[string]map[string]*BPMPackage) + +func initializeLocalPackageInformation(rootDir string) (err error) { + // Return if information is already initialized + if _, ok := localPackageInformation[rootDir]; ok { + return nil + } + + tempPackageInformation := make(map[string]*BPMPackage) + + // Get path to installed package information directory + installedDir := path.Join(rootDir, "var/lib/bpm/installed/") + + // Get directory content + items, err := os.ReadDir(installedDir) + if os.IsNotExist(err) { + localPackageInformation[rootDir] = make(map[string]*BPMPackage) + 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 + } + + // Read package files + files := getPackageFiles(info.Name, rootDir) + + // Add package to slice + tempPackageInformation[info.Name] = &BPMPackage{ + PkgInfo: info, + PkgFiles: files, + } + } + + localPackageInformation[rootDir] = tempPackageInformation + 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 _, bpmpkg := range localPackageInformation[rootDir] { + ret = append(ret, bpmpkg.PkgInfo.Name) + } + + 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 GetPackageInfo(pkg string, rootDir string) *PackageInfo { + // Get BPM package + bpmpkg := GetPackage(pkg, rootDir) + + // Return nil if not found + if bpmpkg == nil { + return nil + } + + return bpmpkg.PkgInfo +} + +func IsVirtualPackage(pkg, rootDir string) (bool, string) { + pkgs, err := GetInstalledPackages(rootDir) + if err != nil { + return false, "" + } + for _, p := range pkgs { + if p == pkg { + return false, "" + } + i := GetPackageInfo(p, rootDir) + if i == nil { + continue + } + if slices.Contains(i.Provides, pkg) { + return true, p + } + } + return false, "" +} + +func IsPackageProvided(pkg, rootDir string) bool { + pkgs, err := GetInstalledPackages(rootDir) + if err != nil { + return false + } + for _, p := range pkgs { + if p == pkg { + return true + } + i := GetPackageInfo(p, rootDir) + if i == nil { + continue + } + if slices.Contains(i.Provides, pkg) { + return true + } + } + return false +} + +func GetPackage(pkg, rootDir string) *BPMPackage { + err := initializeLocalPackageInformation(rootDir) + if err != nil { + return nil + } + + bpmpkg := localPackageInformation[rootDir][pkg] + + return bpmpkg +} + +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, errors.New(fmt.Sprintf("could not get BPM package (%s)", pkgName)) + } + for _, entry := range bpmpkg.PkgFiles { + if _, ok := ret[entry.Path]; ok { + ret[entry.Path] = append(ret[entry.Path], bpmpkg) + } else { + ret[entry.Path] = []*BPMPackage{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.ParseUint(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 +} diff --git a/src/bpmlib/operations.go b/src/bpmlib/operations.go index f072759..8e4ef23 100644 --- a/src/bpmlib/operations.go +++ b/src/bpmlib/operations.go @@ -11,11 +11,11 @@ import ( ) type BPMOperation struct { - Actions []OperationAction - UnresolvedDepends []string - Changes map[string]string - RootDir string - ForceInstallationReason InstallationReason + Actions []OperationAction + UnresolvedDepends []string + Changes map[string]string + RootDir string + compiledPackages map[string]string } func (operation *BPMOperation) ActionsContainPackage(pkg string) bool { @@ -25,7 +25,7 @@ func (operation *BPMOperation) ActionsContainPackage(pkg string) bool { return true } } else if action.GetActionType() == "fetch" { - if action.(*FetchPackageAction).RepositoryEntry.Info.Name == pkg { + if action.(*FetchPackageAction).DatabaseEntry.Info.Name == pkg { return true } } else if action.GetActionType() == "remove" { @@ -57,7 +57,7 @@ func (operation *BPMOperation) InsertActionAt(index int, action OperationAction) operation.Changes[pkgInfo.Name] = "upgrade" } } else if action.GetActionType() == "fetch" { - pkgInfo := action.(*FetchPackageAction).RepositoryEntry.Info + pkgInfo := action.(*FetchPackageAction).DatabaseEntry.Info if !IsPackageInstalled(pkgInfo.Name, operation.RootDir) { operation.Changes[pkgInfo.Name] = "install" } else { @@ -76,7 +76,7 @@ func (operation *BPMOperation) RemoveAction(pkg, actionType string) { if a.GetActionType() == "install" { return a.(*InstallPackageAction).BpmPackage.PkgInfo.Name == pkg } else if a.GetActionType() == "fetch" { - return a.(*FetchPackageAction).RepositoryEntry.Info.Name == pkg + return a.(*FetchPackageAction).DatabaseEntry.Info.Name == pkg } else if a.GetActionType() == "remove" { return a.(*RemovePackageAction).BpmPackage.PkgInfo.Name == pkg } @@ -88,7 +88,7 @@ func (operation *BPMOperation) GetTotalDownloadSize() uint64 { var ret uint64 = 0 for _, action := range operation.Actions { if action.GetActionType() == "fetch" { - ret += action.(*FetchPackageAction).RepositoryEntry.DownloadSize + ret += action.(*FetchPackageAction).DatabaseEntry.DownloadSize } } return ret @@ -100,7 +100,7 @@ func (operation *BPMOperation) GetTotalInstalledSize() uint64 { if action.GetActionType() == "install" { ret += action.(*InstallPackageAction).BpmPackage.GetInstalledSize() } else if action.GetActionType() == "fetch" { - ret += action.(*FetchPackageAction).RepositoryEntry.InstalledSize + ret += action.(*FetchPackageAction).DatabaseEntry.InstalledSize } } return ret @@ -115,7 +115,7 @@ func (operation *BPMOperation) GetFinalActionSize(rootDir string) int64 { ret -= int64(GetPackage(action.(*InstallPackageAction).BpmPackage.PkgInfo.Name, rootDir).GetInstalledSize()) } } else if action.GetActionType() == "fetch" { - ret += int64(action.(*FetchPackageAction).RepositoryEntry.InstalledSize) + ret += int64(action.(*FetchPackageAction).DatabaseEntry.InstalledSize) } else if action.GetActionType() == "remove" { ret -= int64(action.(*RemovePackageAction).BpmPackage.GetInstalledSize()) } @@ -132,28 +132,28 @@ func (operation *BPMOperation) ResolveDependencies(reinstallDependencies, instal pkgInfo = action.BpmPackage.PkgInfo } else if value.GetActionType() == "fetch" { action := value.(*FetchPackageAction) - pkgInfo = action.RepositoryEntry.Info + pkgInfo = action.DatabaseEntry.Info } else { pos++ continue } - resolved, unresolved := pkgInfo.ResolveDependencies(&[]string{}, &[]string{}, pkgInfo.Type == "source", installOptionalDependencies, !reinstallDependencies, verbose, operation.RootDir) + resolved, unresolved := ResolveAllPackageDependenciesFromDatabases(pkgInfo, pkgInfo.Type == "source", installOptionalDependencies, !reinstallDependencies, verbose, operation.RootDir) operation.UnresolvedDepends = append(operation.UnresolvedDepends, unresolved...) - for _, depend := range resolved { + for depend, installationReason := range resolved { if !operation.ActionsContainPackage(depend) && depend != pkgInfo.Name { if !reinstallDependencies && IsPackageInstalled(depend, operation.RootDir) { continue } - entry, _, err := GetRepositoryEntry(depend) + entry, _, err := GetDatabaseEntry(depend) if err != nil { - return errors.New("could not get repository entry for package (" + depend + ")") + return errors.New("could not get database entry for package (" + depend + ")") } operation.InsertActionAt(pos, &FetchPackageAction{ - IsDependency: true, - RepositoryEntry: entry, + InstallationReason: installationReason, + DatabaseEntry: entry, }) pos++ } @@ -173,7 +173,7 @@ func (operation *BPMOperation) RemoveNeededPackages() error { } for pkg, action := range removeActions { - dependants, err := action.BpmPackage.PkgInfo.GetDependants(operation.RootDir) + dependants, err := GetPackageDependants(action.BpmPackage.PkgInfo.Name, operation.RootDir) if err != nil { return errors.New("could not get dependant packages for package (" + pkg + ")") } @@ -191,7 +191,7 @@ func (operation *BPMOperation) RemoveNeededPackages() error { return nil } -func (operation *BPMOperation) Cleanup(verbose bool) error { +func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error { // Get all installed packages installedPackageNames, err := GetInstalledPackages(operation.RootDir) if err != nil { @@ -227,9 +227,9 @@ func (operation *BPMOperation) Cleanup(verbose bool) error { } keepPackages = append(keepPackages, pkg.Name) - resolved, _ := pkg.ResolveDependencies(&[]string{}, &[]string{}, false, true, false, verbose, operation.RootDir) + resolved := pkg.GetAllDependencies(!cleanupMakeDepends, true, operation.RootDir) for _, value := range resolved { - if !slices.Contains(keepPackages, value) && slices.Contains(installedPackageNames, value) { + if !slices.Contains(keepPackages, value) { keepPackages = append(keepPackages, value) } } @@ -262,7 +262,7 @@ func (operation *BPMOperation) ReplaceObsoletePackages() { } else if value.GetActionType() == "fetch" { action := value.(*FetchPackageAction) - pkgInfo = action.RepositoryEntry.Info + pkgInfo = action.DatabaseEntry.Info } else { continue } @@ -300,7 +300,7 @@ func (operation *BPMOperation) CheckForConflicts() (map[string][]string, error) allPackages = append(allPackages, pkgInfo) } else if value.GetActionType() == "fetch" { action := value.(*FetchPackageAction) - pkgInfo := action.RepositoryEntry.Info + pkgInfo := action.DatabaseEntry.Info allPackages = append(allPackages, pkgInfo) } else if value.GetActionType() == "remove" { action := value.(*RemovePackageAction) @@ -335,10 +335,16 @@ func (operation *BPMOperation) ShowOperationSummary() { for _, value := range operation.Actions { var pkgInfo *PackageInfo + var installationReason = InstallationReasonUnknown if value.GetActionType() == "install" { + installationReason = value.(*InstallPackageAction).InstallationReason pkgInfo = value.(*InstallPackageAction).BpmPackage.PkgInfo + if value.(*InstallPackageAction).SplitPackageToInstall != "" { + pkgInfo = pkgInfo.GetSplitPackageInfo(value.(*InstallPackageAction).SplitPackageToInstall) + } } else if value.GetActionType() == "fetch" { - pkgInfo = value.(*FetchPackageAction).RepositoryEntry.Info + installationReason = value.(*FetchPackageAction).InstallationReason + pkgInfo = value.(*FetchPackageAction).DatabaseEntry.Info } else { pkgInfo = value.(*RemovePackageAction).BpmPackage.PkgInfo fmt.Printf("%s: %s (Remove)\n", pkgInfo.Name, pkgInfo.GetFullVersion()) @@ -346,21 +352,32 @@ func (operation *BPMOperation) ShowOperationSummary() { } installedInfo := GetPackageInfo(pkgInfo.Name, operation.RootDir) - sourceInfo := "" + additionalInfo := "" + switch installationReason { + case InstallationReasonManual: + additionalInfo = "(Manual)" + case InstallationReasonDependency: + additionalInfo = "(Dependency)" + case InstallationReasonMakeDependency: + additionalInfo = "(Make dependency)" + default: + additionalInfo = "(Unknown)" + } + if pkgInfo.Type == "source" { - sourceInfo = "(From Source)" + additionalInfo += " (From Source)" } if installedInfo == nil { - fmt.Printf("%s: %s (Install) %s\n", pkgInfo.Name, pkgInfo.GetFullVersion(), sourceInfo) + fmt.Printf("%s: %s (Install) %s\n", pkgInfo.Name, pkgInfo.GetFullVersion(), additionalInfo) } else { comparison := ComparePackageVersions(*pkgInfo, *installedInfo) if comparison < 0 { - fmt.Printf("%s: %s -> %s (Downgrade) %s\n", pkgInfo.Name, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), sourceInfo) + fmt.Printf("%s: %s -> %s (Downgrade) %s\n", pkgInfo.Name, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), additionalInfo) } else if comparison > 0 { - fmt.Printf("%s: %s -> %s (Upgrade) %s\n", pkgInfo.Name, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), sourceInfo) + fmt.Printf("%s: %s -> %s (Upgrade) %s\n", pkgInfo.Name, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), additionalInfo) } else { - fmt.Printf("%s: %s (Reinstall) %s\n", pkgInfo.Name, pkgInfo.GetFullVersion(), sourceInfo) + fmt.Printf("%s: %s (Reinstall) %s\n", pkgInfo.Name, pkgInfo.GetFullVersion(), additionalInfo) } } } @@ -409,30 +426,68 @@ func (operation *BPMOperation) RunHooks(verbose bool) error { return nil } -func (operation *BPMOperation) Execute(verbose, force bool) error { - // Fetch packages from repositories +func (operation *BPMOperation) Execute(verbose, force bool) (err error) { + // Fetch packages from databases if slices.ContainsFunc(operation.Actions, func(action OperationAction) bool { return action.GetActionType() == "fetch" }) { - fmt.Println("Fetching packages from available repositories...") + fmt.Println("Fetching packages from available databases...") + + // Create map for fetched packages + fetchedPackages := make(map[string]string) + for i, action := range operation.Actions { if action.GetActionType() != "fetch" { continue } - entry := action.(*FetchPackageAction).RepositoryEntry - fetchedPackage, err := entry.Repository.FetchPackage(entry.Info.Name) - if err != nil { - return errors.New(fmt.Sprintf("could not fetch package (%s): %s\n", entry.Info.Name, err)) + + // Get database entry + entry := action.(*FetchPackageAction).DatabaseEntry + + // Create bpmpkg variable + var bpmpkg *BPMPackage + + // Check if package has already been fetched from download link + if _, ok := fetchedPackages[entry.Download]; !ok { + // Fetch package from database + fetchedPackage, err := entry.Database.FetchPackage(entry.Info.Name) + if err != nil { + return errors.New(fmt.Sprintf("could not fetch package (%s): %s\n", entry.Info.Name, err)) + } + + // Read fetched package + bpmpkg, err = ReadPackage(fetchedPackage) + if err != nil { + return errors.New(fmt.Sprintf("could not fetch package (%s): %s\n", entry.Info.Name, err)) + } + + // Add fetched package to map + fetchedPackages[entry.Download] = fetchedPackage + + fmt.Printf("Package (%s) was successfully fetched!\n", entry.Info.Name) + } else { + // Read fetched package + bpmpkg, err = ReadPackage(fetchedPackages[entry.Download]) + if err != nil { + return errors.New(fmt.Sprintf("could not read package (%s): %s\n", entry.Info.Name, err)) + } + + fmt.Printf("Package (%s) was successfully fetched!\n", entry.Info.Name) } - bpmpkg, err := ReadPackage(fetchedPackage) - if err != nil { - return errors.New(fmt.Sprintf("could not fetch package (%s): %s\n", entry.Info.Name, err)) - } - fmt.Printf("Package (%s) was successfully fetched!\n", bpmpkg.PkgInfo.Name) - operation.Actions[i] = &InstallPackageAction{ - File: fetchedPackage, - IsDependency: action.(*FetchPackageAction).IsDependency, - BpmPackage: bpmpkg, + + if bpmpkg.PkgInfo.IsSplitPackage() { + operation.Actions[i] = &InstallPackageAction{ + File: fetchedPackages[entry.Download], + InstallationReason: action.(*FetchPackageAction).InstallationReason, + BpmPackage: bpmpkg, + SplitPackageToInstall: entry.Info.Name, + } + } else { + operation.Actions[i] = &InstallPackageAction{ + File: fetchedPackages[entry.Download], + InstallationReason: action.(*FetchPackageAction).InstallationReason, + BpmPackage: bpmpkg, + } } } } @@ -466,24 +521,61 @@ func (operation *BPMOperation) Execute(verbose, force bool) error { } } else if action.GetActionType() == "install" { value := action.(*InstallPackageAction) + fileToInstall := value.File bpmpkg := value.BpmPackage isReinstall := IsPackageInstalled(bpmpkg.PkgInfo.Name, operation.RootDir) var err error - if value.IsDependency { - err = installPackage(value.File, operation.RootDir, verbose, true) + + // Compile package if type is 'source' + if bpmpkg.PkgInfo.Type == "source" { + // Get path to compiled package directory + compiledDir := path.Join(operation.RootDir, "/var/cache/bpm/compiled/") + + // Create compiled package directory if not exists + if _, err := os.Stat(compiledDir); err != nil { + err := os.MkdirAll(compiledDir, 0755) + if err != nil { + return err + } + } + + // Get package name to install + pkgNameToInstall := bpmpkg.PkgInfo.Name + if bpmpkg.PkgInfo.IsSplitPackage() { + pkgNameToInstall = value.SplitPackageToInstall + } + + // Compile source package if not compiled already + if _, ok := operation.compiledPackages[pkgNameToInstall]; !ok { + outputBpmPackages, err := CompileSourcePackage(value.File, compiledDir, false) + if err != nil { + return fmt.Errorf("could not compile source package (%s): %s\n", value.File, err) + } + + // Add compiled packages to slice + for pkgName, pkgFile := range outputBpmPackages { + operation.compiledPackages[pkgName] = pkgFile + } + } + + // Set values + fileToInstall = operation.compiledPackages[pkgNameToInstall] + bpmpkg, err = ReadPackage(fileToInstall) + if err != nil { + return fmt.Errorf("could not read package (%s): %s\n", fileToInstall, err) + } + } + + if value.InstallationReason != InstallationReasonManual { + err = installPackage(fileToInstall, operation.RootDir, verbose, true) } else { - err = installPackage(value.File, operation.RootDir, verbose, force) + err = installPackage(fileToInstall, operation.RootDir, verbose, force) } if err != nil { return errors.New(fmt.Sprintf("could not install package (%s): %s\n", bpmpkg.PkgInfo.Name, err)) } - if operation.ForceInstallationReason != InstallationReasonUnknown && !value.IsDependency { - err := SetInstallationReason(bpmpkg.PkgInfo.Name, operation.ForceInstallationReason, operation.RootDir) - if err != nil { - return errors.New(fmt.Sprintf("could not set installation reason for package (%s): %s\n", value.BpmPackage.PkgInfo.Name, err)) - } - } else if value.IsDependency && !isReinstall { - err := SetInstallationReason(bpmpkg.PkgInfo.Name, InstallationReasonDependency, operation.RootDir) + if !isReinstall { + err := SetInstallationReason(bpmpkg.PkgInfo.Name, value.InstallationReason, operation.RootDir) if err != nil { return errors.New(fmt.Sprintf("could not set installation reason for package (%s): %s\n", value.BpmPackage.PkgInfo.Name, err)) } @@ -501,9 +593,10 @@ type OperationAction interface { } type InstallPackageAction struct { - File string - IsDependency bool - BpmPackage *BPMPackage + File string + InstallationReason InstallationReason + SplitPackageToInstall string + BpmPackage *BPMPackage } func (action *InstallPackageAction) GetActionType() string { @@ -511,8 +604,8 @@ func (action *InstallPackageAction) GetActionType() string { } type FetchPackageAction struct { - IsDependency bool - RepositoryEntry *RepositoryEntry + InstallationReason InstallationReason + DatabaseEntry *BPMDatabaseEntry } func (action *FetchPackageAction) GetActionType() string { diff --git a/src/bpmlib/packages.go b/src/bpmlib/packages.go index da57201..ce69858 100644 --- a/src/bpmlib/packages.go +++ b/src/bpmlib/packages.go @@ -8,6 +8,7 @@ import ( version "github.com/knqyf263/go-rpm-version" "gopkg.in/yaml.v3" "io" + "log" "os" "os/exec" "path" @@ -23,21 +24,22 @@ type BPMPackage struct { } type PackageInfo struct { - Name string `yaml:"name,omitempty"` - Description string `yaml:"description,omitempty"` - Version string `yaml:"version,omitempty"` - Revision int `yaml:"revision,omitempty"` - Url string `yaml:"url,omitempty"` - License string `yaml:"license,omitempty"` - Arch string `yaml:"architecture,omitempty"` - Type string `yaml:"type,omitempty"` - Keep []string `yaml:"keep,omitempty"` - Depends []string `yaml:"depends,omitempty"` - MakeDepends []string `yaml:"make_depends,omitempty"` - OptionalDepends []string `yaml:"optional_depends,omitempty"` - Conflicts []string `yaml:"conflicts,omitempty"` - Replaces []string `yaml:"replaces,omitempty"` - Provides []string `yaml:"provides,omitempty"` + Name string `yaml:"name,omitempty"` + Description string `yaml:"description,omitempty"` + Version string `yaml:"version,omitempty"` + Revision int `yaml:"revision,omitempty"` + Url string `yaml:"url,omitempty"` + License string `yaml:"license,omitempty"` + Arch string `yaml:"architecture,omitempty"` + Type string `yaml:"type,omitempty"` + Keep []string `yaml:"keep,omitempty"` + Depends []string `yaml:"depends,omitempty"` + MakeDepends []string `yaml:"make_depends,omitempty"` + OptionalDepends []string `yaml:"optional_depends,omitempty"` + Conflicts []string `yaml:"conflicts,omitempty"` + Replaces []string `yaml:"replaces,omitempty"` + Provides []string `yaml:"provides,omitempty"` + SplitPackages []*PackageInfo `yaml:"split_packages,omitempty"` } type PackageFileEntry struct { @@ -68,12 +70,32 @@ func (pkgInfo *PackageInfo) GetFullVersion() string { return pkgInfo.Version + "-" + strconv.Itoa(pkgInfo.Revision) } +func (pkgInfo *PackageInfo) IsSplitPackage() bool { + // Return false if not a source package + if pkgInfo.Type != "source" { + return false + } + + return len(pkgInfo.SplitPackages) > 0 +} + +func (pkgInfo *PackageInfo) GetSplitPackageInfo(splitPkg string) *PackageInfo { + for _, splitPkgInfo := range pkgInfo.SplitPackages { + if splitPkgInfo.Name == splitPkg { + return splitPkgInfo + } + } + + return nil +} + type InstallationReason string const ( - InstallationReasonManual InstallationReason = "manual" - InstallationReasonDependency InstallationReason = "dependency" - InstallationReasonUnknown InstallationReason = "unknown" + InstallationReasonManual InstallationReason = "manual" + InstallationReasonDependency InstallationReason = "dependency" + InstallationReasonMakeDependency InstallationReason = "make_dependency" + InstallationReasonUnknown InstallationReason = "unknown" ) func ComparePackageVersions(info1, info2 PackageInfo) int { @@ -98,6 +120,8 @@ func GetInstallationReason(pkg, rootDir string) InstallationReason { return InstallationReasonManual } else if reason == "dependency" { return InstallationReasonDependency + } else if reason == "make_dependency" { + return InstallationReasonMakeDependency } return InstallationReasonUnknown } @@ -147,7 +171,6 @@ func ReadPackage(filename string) (*BPMPackage, error) { var pkgFiles []*PackageFileEntry if _, err := os.Stat(filename); os.IsNotExist(err) { - fmt.Println("a") return nil, err } @@ -218,6 +241,31 @@ func ReadPackage(filename string) (*BPMPackage, error) { }, nil } +func getPackageScripts(filename string) (packageScripts []string) { + content, err := listTarballContent(filename) + if err != nil { + return + } + + for _, file := range content { + if file == "pre_install.sh" { + packageScripts = append(packageScripts, "pre_install.sh") + } else if file == "post_install.sh" { + packageScripts = append(packageScripts, "post_install.sh") + } else if file == "pre_update.sh" { + packageScripts = append(packageScripts, "pre_update.sh") + } else if file == "post_update.sh" { + packageScripts = append(packageScripts, "post_update.sh") + } else if file == "pre_remove.sh" { + packageScripts = append(packageScripts, "pre_remove.sh") + } else if file == "post_remove.sh" { + packageScripts = append(packageScripts, "post_remove.sh") + } + } + + return packageScripts +} + func ReadPackageScripts(filename string) (map[string]string, error) { if _, err := os.Stat(filename); os.IsNotExist(err) { return nil, err @@ -237,31 +285,7 @@ func ReadPackageScripts(filename string) (map[string]string, error) { if err != nil { return nil, err } - if header.Name == "pre_install.sh" { - bs, err := io.ReadAll(tr) - if err != nil { - return nil, err - } - ret[header.Name] = string(bs) - } else if header.Name == "post_install.sh" { - bs, err := io.ReadAll(tr) - if err != nil { - return nil, err - } - ret[header.Name] = string(bs) - } else if header.Name == "pre_update.sh" { - bs, err := io.ReadAll(tr) - if err != nil { - return nil, err - } - ret[header.Name] = string(bs) - } else if header.Name == "post_update.sh" { - bs, err := io.ReadAll(tr) - if err != nil { - return nil, err - } - ret[header.Name] = string(bs) - } else if header.Name == "post_remove.sh" { + if header.Name == "pre_install.sh" || header.Name == "post_install.sh" || header.Name == "pre_update.sh" || header.Name == "post_update.sh" || header.Name == "pre_remove.sh" || header.Name == "post_remove.sh" { bs, err := io.ReadAll(tr) if err != nil { return nil, err @@ -385,7 +409,7 @@ func executePackageScripts(filename, rootDir string, operation packageOperation, } func ReadPackageInfo(contents string) (*PackageInfo, error) { - pkgInfo := PackageInfo{ + pkgInfo := &PackageInfo{ Name: "", Description: "", Version: "", @@ -401,11 +425,14 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) { Conflicts: make([]string, 0), Replaces: make([]string, 0), Provides: make([]string, 0), + SplitPackages: make([]*PackageInfo, 0), } err := yaml.Unmarshal([]byte(contents), &pkgInfo) if err != nil { return nil, err } + + // Ensure required fields are set properly if pkgInfo.Name == "" { return nil, errors.New("this package contains no name") } else if pkgInfo.Description == "" { @@ -419,13 +446,47 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) { } else if pkgInfo.Type == "" { return nil, errors.New("this package contains no type") } - for i := 0; i < len(pkgInfo.Keep); i++ { - pkgInfo.Keep[i] = strings.TrimPrefix(pkgInfo.Keep[i], "/") + for _, val := range pkgInfo.Keep { + if strings.HasPrefix(val, "/") { + return nil, fmt.Errorf("cannot keep file (%s) after update because it starts with a slash", val) + } } - return &pkgInfo, nil + + // Setup split package information + for i, splitPkg := range pkgInfo.SplitPackages { + // Ensure split package contains a name + if splitPkg.Name == "" { + return nil, fmt.Errorf("invalid split package name: %s", splitPkg.Name) + } + + // Turn split package into json data + splitPkgJson, err := yaml.Marshal(splitPkg) + if err != nil { + return nil, err + } + + // Clone all main package fields onto split package + pkgInfoClone := *pkgInfo + pkgInfo.SplitPackages[i] = &pkgInfoClone + + // Set split package field of split package to nil + pkgInfo.SplitPackages[i].SplitPackages = nil + + // Unmarshal json data back to struct + err = yaml.Unmarshal(splitPkgJson, &pkgInfo.SplitPackages[i]) + if err != nil { + return nil, err + } + + // Force set split package version, revision + pkgInfo.SplitPackages[i].Version = pkgInfo.Version + pkgInfo.SplitPackages[i].Revision = pkgInfo.Revision + } + + return pkgInfo, nil } -func CreateReadableInfo(showArchitecture, showType, showPackageRelations bool, pkgInfo *PackageInfo, rootDir string) string { +func CreateReadableInfo(showArchitecture, showType, showPackageRelations, showInstallationReason bool, pkgInfo *PackageInfo, rootDir string) string { ret := make([]string, 0) appendArray := func(label string, array []string) { if len(array) == 0 { @@ -450,7 +511,7 @@ func CreateReadableInfo(showArchitecture, showType, showPackageRelations bool, p appendArray("Make Dependencies", pkgInfo.MakeDepends) } appendArray("Optional dependencies", pkgInfo.OptionalDepends) - dependants, err := pkgInfo.GetDependants(rootDir) + dependants, err := GetPackageDependants(pkgInfo.Name, rootDir) if err == nil { appendArray("Dependant packages", dependants) } @@ -458,7 +519,28 @@ func CreateReadableInfo(showArchitecture, showType, showPackageRelations bool, p appendArray("Provided packages", pkgInfo.Provides) appendArray("Replaces packages", pkgInfo.Replaces) } - ret = append(ret, "Installation Reason: "+string(GetInstallationReason(pkgInfo.Name, rootDir))) + if pkgInfo.Type == "source" && len(pkgInfo.SplitPackages) != 0 { + splitPkgs := make([]string, len(pkgInfo.SplitPackages)) + for i, splitPkgInfo := range pkgInfo.SplitPackages { + splitPkgs[i] = splitPkgInfo.Name + } + appendArray("Split Packages", splitPkgs) + } + if IsPackageInstalled(pkgInfo.Name, rootDir) && showInstallationReason { + installationReason := GetInstallationReason(pkgInfo.Name, rootDir) + var installationReasonString string + switch installationReason { + case InstallationReasonManual: + installationReasonString = "Manual" + case InstallationReasonDependency: + installationReasonString = "Dependency" + case InstallationReasonMakeDependency: + installationReasonString = "Make dependency" + default: + installationReasonString = "Unknown" + } + ret = append(ret, "Installation Reason: "+installationReasonString) + } return strings.Join(ret, "\n") } @@ -480,7 +562,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string) return err } - tarballFile, err := readTarballContent(filename, "files.tar.gz") + tarballFile, err := readTarballFile(filename, "files.tar.gz") if err != nil { return err } @@ -605,11 +687,17 @@ func installPackage(filename, rootDir string, verbose, force bool) error { if err != nil { return err } + + // Ensure package type is 'binary' + if bpmpkg.PkgInfo.Type != "binary" { + return fmt.Errorf("can only extract binary packages") + } + packageInstalled := IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) // Check if package is installed and remove current files if packageInstalled { // Fetching and reversing package file entry list - fileEntries := GetPackageFiles(bpmpkg.PkgInfo.Name, rootDir) + fileEntries := GetPackage(bpmpkg.PkgInfo.Name, rootDir).PkgFiles sort.Slice(fileEntries, func(i, j int) bool { return fileEntries[i].Path < fileEntries[j].Path }) @@ -710,15 +798,10 @@ func installPackage(filename, rootDir string, verbose, force bool) error { fmt.Printf("Extracting files for package (%s)...\n", bpmpkg.PkgInfo.Name) } - if bpmpkg.PkgInfo.Type == "binary" { - err := extractPackage(bpmpkg, verbose, filename, rootDir) - if err != nil { - return err - } - } else if bpmpkg.PkgInfo.Type == "source" { - return errors.New("direct source package compilation in BPM has been temporarily removed and is being reworked on") - } else { - return errors.New("unknown package type: " + bpmpkg.PkgInfo.Type) + // Extract package files into rootDir + err = extractPackage(bpmpkg, verbose, filename, rootDir) + if err != nil { + return err } installedDir := path.Join(rootDir, "var/lib/bpm/installed/") @@ -738,7 +821,7 @@ func installPackage(filename, rootDir string, verbose, force bool) error { return err } - tarballFile, err := readTarballContent(filename, "pkg.files") + tarballFile, err := readTarballFile(filename, "pkg.files") if err != nil { return err } @@ -766,23 +849,30 @@ func installPackage(filename, rootDir string, verbose, force bool) error { return err } - scripts, err := ReadPackageScripts(filename) + // Save remove package scripts + packageScripts, err := ReadPackageScripts(filename) if err != nil { return err } - if val, ok := scripts["post_remove.sh"]; ok { - f, err = os.Create(path.Join(pkgDir, "post_remove.sh")) + for script, content := range packageScripts { + if !strings.HasSuffix(script, "_remove.sh") { + continue + } + + // Create file + f, err = os.Create(path.Join(pkgDir, script)) if err != nil { return err } - _, err = f.WriteString(val) - if err != nil { - return err - } - err = f.Close() + + // Write script contents to file + _, err = f.WriteString(content) if err != nil { return err } + + // Close file + f.Close() } if !packageInstalled { @@ -796,315 +886,19 @@ func installPackage(filename, rootDir string, verbose, force bool) error { return err } } + + // Ensure local package information has been initialized for rootDir + err = initializeLocalPackageInformation(rootDir) + if err != nil { + return err + } + + // Add or update package information for rootDir + localPackageInformation[rootDir][bpmpkg.PkgInfo.Name] = bpmpkg + return nil } -func (pkgInfo *PackageInfo) GetAllDependencies(checkMake, checkOptional bool) []string { - allDepends := make([]string, 0) - allDepends = append(allDepends, pkgInfo.Depends...) - if checkMake { - allDepends = append(allDepends, pkgInfo.MakeDepends...) - } - if checkOptional { - allDepends = append(allDepends, pkgInfo.OptionalDepends...) - } - return allDepends -} - -func (pkgInfo *PackageInfo) CheckDependencies(checkMake, checkOptional bool, rootDir string) []string { - var ret []string - for _, dependency := range pkgInfo.Depends { - if !IsPackageProvided(dependency, rootDir) { - ret = append(ret, dependency) - } - } - if checkMake { - for _, dependency := range pkgInfo.MakeDepends { - if !IsPackageProvided(dependency, rootDir) { - ret = append(ret, dependency) - } - } - } - if checkOptional { - for _, dependency := range pkgInfo.OptionalDepends { - if !IsPackageProvided(dependency, rootDir) { - ret = append(ret, dependency) - } - } - } - - return ret -} - -func (pkgInfo *PackageInfo) GetDependants(rootDir string) ([]string, error) { - ret := make([]string, 0) - - pkgs, err := GetInstalledPackages(rootDir) - if err != nil { - return nil, errors.New("could not get installed packages") - } - for _, pkg := range pkgs { - bpmpkg := GetPackage(pkg, rootDir) - if bpmpkg == nil { - return nil, errors.New("package not found: " + pkg) - } - if bpmpkg.PkgInfo.Name == pkgInfo.Name { - continue - } - - dependencies := bpmpkg.PkgInfo.GetAllDependencies(false, true) - - if slices.Contains(dependencies, pkgInfo.Name) { - ret = append(ret, pkg) - continue - } - for _, vpkg := range pkgInfo.Provides { - if slices.Contains(dependencies, vpkg) { - ret = append(ret, pkg) - break - } - } - } - - return ret, nil -} - -func (pkgInfo *PackageInfo) CheckConflicts(rootDir string) []string { - var ret []string - for _, conflict := range pkgInfo.Conflicts { - if IsPackageInstalled(conflict, rootDir) { - ret = append(ret, conflict) - } - } - return ret -} - -func (pkgInfo *PackageInfo) ResolveDependencies(resolved, unresolved *[]string, checkMake, checkOptional, ignoreInstalled, verbose bool, rootDir string) ([]string, []string) { - *unresolved = append(*unresolved, pkgInfo.Name) - for _, depend := range pkgInfo.GetAllDependencies(checkMake, checkOptional) { - depend = strings.TrimSpace(depend) - depend = strings.ToLower(depend) - if !slices.Contains(*resolved, depend) { - if slices.Contains(*unresolved, depend) { - if verbose { - fmt.Printf("Circular dependency was detected (%s -> %s). Installing %s first\n", pkgInfo.Name, depend, depend) - } - if !slices.Contains(*resolved, depend) { - *resolved = append(*resolved, depend) - } - continue - } else if ignoreInstalled && IsPackageProvided(depend, rootDir) { - continue - } - var err error - var entry *RepositoryEntry - entry, _, err = GetRepositoryEntry(depend) - if err != nil { - if entry = ResolveVirtualPackage(depend); entry == nil { - if !slices.Contains(*unresolved, depend) { - *unresolved = append(*unresolved, depend) - } - continue - } - } - entry.Info.ResolveDependencies(resolved, unresolved, checkMake, checkOptional, ignoreInstalled, verbose, rootDir) - } - } - if !slices.Contains(*resolved, pkgInfo.Name) { - *resolved = append(*resolved, pkgInfo.Name) - } - *unresolved = stringSliceRemove(*unresolved, pkgInfo.Name) - return *resolved, *unresolved -} - -func IsPackageInstalled(pkg, rootDir string) bool { - installedDir := path.Join(rootDir, "var/lib/bpm/installed/") - pkgDir := path.Join(installedDir, pkg) - if _, err := os.Stat(pkgDir); err != nil { - return false - } - return true -} - -func IsVirtualPackage(pkg, rootDir string) (bool, string) { - pkgs, err := GetInstalledPackages(rootDir) - if err != nil { - return false, "" - } - for _, p := range pkgs { - if p == pkg { - return false, "" - } - i := GetPackageInfo(p, rootDir) - if i == nil { - continue - } - if slices.Contains(i.Provides, pkg) { - return true, p - } - } - return false, "" -} - -func IsPackageProvided(pkg, rootDir string) bool { - pkgs, err := GetInstalledPackages(rootDir) - if err != nil { - return false - } - for _, p := range pkgs { - if p == pkg { - return true - } - i := GetPackageInfo(p, rootDir) - if i == nil { - continue - } - if slices.Contains(i.Provides, pkg) { - return true - } - } - return false -} - -func GetInstalledPackages(rootDir string) ([]string, error) { - installedDir := path.Join(rootDir, "var/lib/bpm/installed/") - items, err := os.ReadDir(installedDir) - if os.IsNotExist(err) { - return nil, nil - } - if err != nil { - return nil, err - } - var ret []string - for _, item := range items { - ret = append(ret, item.Name()) - } - 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.ParseUint(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 GetPackageInfo(pkg, rootDir string) *PackageInfo { - installedDir := path.Join(rootDir, "var/lib/bpm/installed/") - pkgDir := path.Join(installedDir, pkg) - files := path.Join(pkgDir, "info") - if _, err := os.Stat(installedDir); os.IsNotExist(err) { - return nil - } - if _, err := os.Stat(pkgDir); os.IsNotExist(err) { - return nil - } - file, err := os.Open(files) - if err != nil { - return nil - } - bs, err := io.ReadAll(file) - if err != nil { - return nil - } - info, err := ReadPackageInfo(string(bs)) - if err != nil { - return nil - } - return info -} - -func GetPackage(pkg, rootDir string) *BPMPackage { - if !IsPackageInstalled(pkg, rootDir) { - return nil - } - - return &BPMPackage{ - PkgInfo: GetPackageInfo(pkg, rootDir), - PkgFiles: GetPackageFiles(pkg, rootDir), - } -} - -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, errors.New(fmt.Sprintf("could not get BPM package (%s)", pkgName)) - } - for _, entry := range bpmpkg.PkgFiles { - if _, ok := ret[entry.Path]; ok { - ret[entry.Path] = append(ret[entry.Path], bpmpkg) - } else { - ret[entry.Path] = []*BPMPackage{bpmpkg} - } - } - } - - return ret, nil -} - func removePackage(pkg string, verbose bool, rootDir string) error { installedDir := path.Join(rootDir, "var/lib/bpm/installed/") pkgDir := path.Join(installedDir, pkg) @@ -1113,8 +907,26 @@ func removePackage(pkg string, verbose bool, rootDir string) error { return errors.New("could not get package info") } + // Executing pre_remove script + if _, err := os.Stat(path.Join(pkgDir, "pre_remove.sh")); err == nil { + cmd := exec.Command("/bin/bash", path.Join(pkgDir, "pre_remove.sh")) + cmd.Dir = rootDir + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_ROOT=%s", rootDir)) + cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_NAME=%s", pkgInfo.Name)) + cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_VERSION=%s", pkgInfo.Version)) + cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_REVISION=%d", pkgInfo.Revision)) + cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_URL=%s", pkgInfo.Url)) + cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_ARCH=%s", pkgInfo.Arch)) + + err = cmd.Run() + if err != nil { + log.Printf("Warning: could not run pre_remove.sh package script: %s", err) + } + } + // Fetching and reversing package file entry list - fileEntries := GetPackageFiles(pkg, rootDir) + fileEntries := GetPackage(pkg, rootDir).PkgFiles sort.Slice(fileEntries, func(i, j int) bool { return fileEntries[i].Path < fileEntries[j].Path }) @@ -1193,27 +1005,14 @@ func removePackage(pkg string, verbose bool, rootDir string) error { cmd.Env = os.Environ() cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_ROOT=%s", rootDir)) cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_NAME=%s", pkgInfo.Name)) - cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_DESC=%s", pkgInfo.Description)) cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_VERSION=%s", pkgInfo.Version)) cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_REVISION=%d", pkgInfo.Revision)) cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_URL=%s", pkgInfo.Url)) cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_ARCH=%s", pkgInfo.Arch)) - depends := make([]string, len(pkgInfo.Depends)) - copy(depends, pkgInfo.Depends) - for i := 0; i < len(depends); i++ { - depends[i] = fmt.Sprintf("\"%s\"", depends[i]) - } - makeDepends := make([]string, len(pkgInfo.MakeDepends)) - copy(makeDepends, pkgInfo.MakeDepends) - for i := 0; i < len(makeDepends); i++ { - makeDepends[i] = fmt.Sprintf("\"%s\"", makeDepends[i]) - } - cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_DEPENDS=(%s)", strings.Join(depends, " "))) - cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_MAKE_DEPENDS=(%s)", strings.Join(makeDepends, " "))) - cmd.Env = append(cmd.Env, "BPM_PKG_TYPE=source") + err = cmd.Run() if err != nil { - return err + log.Printf("Warning: could not run pre_remove.sh package script: %s", err) } } @@ -1226,5 +1025,14 @@ func removePackage(pkg string, verbose bool, rootDir string) error { return err } + // Ensure local package information has been initialized for rootDir + err = initializeLocalPackageInformation(rootDir) + if err != nil { + return err + } + + // Add or update package information for rootDir + delete(localPackageInformation[rootDir], pkgInfo.Name) + return nil } diff --git a/src/bpmlib/repositories.go b/src/bpmlib/repositories.go deleted file mode 100644 index c74e440..0000000 --- a/src/bpmlib/repositories.go +++ /dev/null @@ -1,235 +0,0 @@ -package bpmlib - -import ( - "errors" - "fmt" - "gopkg.in/yaml.v3" - "io" - "net/http" - "net/url" - "os" - "path" - "strings" -) - -type Repository struct { - Name string `yaml:"name"` - Source string `yaml:"source"` - Disabled *bool `yaml:"disabled"` - Entries map[string]*RepositoryEntry - VirtualPackages map[string][]string -} - -type RepositoryEntry struct { - Info *PackageInfo `yaml:"info"` - Download string `yaml:"download"` - DownloadSize uint64 `yaml:"download_size"` - InstalledSize uint64 `yaml:"installed_size"` - Repository *Repository -} - -func (repo *Repository) ContainsPackage(pkg string) bool { - _, ok := repo.Entries[pkg] - return ok -} - -func (repo *Repository) ReadLocalDatabase() error { - repoFile := "/var/lib/bpm/repositories/" + repo.Name + ".bpmdb" - if _, err := os.Stat(repoFile); err != nil { - return nil - } - - bytes, err := os.ReadFile(repoFile) - if err != nil { - return err - } - - data := string(bytes) - for _, b := range strings.Split(data, "---") { - entry := RepositoryEntry{ - Info: &PackageInfo{ - Name: "", - Description: "", - Version: "", - Revision: 1, - Url: "", - License: "", - Arch: "", - Type: "", - Keep: make([]string, 0), - Depends: make([]string, 0), - MakeDepends: make([]string, 0), - OptionalDepends: make([]string, 0), - Conflicts: make([]string, 0), - Provides: make([]string, 0), - }, - Download: "", - DownloadSize: 0, - InstalledSize: 0, - Repository: repo, - } - err := yaml.Unmarshal([]byte(b), &entry) - if err != nil { - return err - } - - for _, p := range entry.Info.Provides { - repo.VirtualPackages[p] = append(repo.VirtualPackages[p], entry.Info.Name) - } - repo.Entries[entry.Info.Name] = &entry - } - - return nil -} - -func (repo *Repository) SyncLocalDatabase() error { - repoFile := "/var/lib/bpm/repositories/" + repo.Name + ".bpmdb" - - // Get URL to database - u, err := url.JoinPath(repo.Source, "database.bpmdb") - if err != nil { - return err - } - - // Retrieve data from URL - resp, err := http.Get(u) - if err != nil { - return err - } - defer resp.Body.Close() - - // Load data into byte buffer - buffer, err := io.ReadAll(resp.Body) - - // Unmarshal data to ensure it is a valid BPM repository - err = yaml.Unmarshal(buffer, &Repository{}) - if err != nil { - return fmt.Errorf("could not decode repository: %s", err) - } - - // Create parent directories to repository file - err = os.MkdirAll(path.Dir(repoFile), 0755) - if err != nil { - return err - } - - // Create file and save repository data - out, err := os.Create(repoFile) - if err != nil { - return err - } - defer out.Close() - - _, err = out.Write(buffer) - - return nil -} - -func ReadLocalDatabases() (err error) { - for _, repo := range BPMConfig.Repositories { - // Initialize struct values - repo.Entries = make(map[string]*RepositoryEntry) - repo.VirtualPackages = make(map[string][]string) - - // Read database - err = repo.ReadLocalDatabase() - if err != nil { - return err - } - } - - return nil -} - -func GetRepository(name string) *Repository { - for _, repo := range BPMConfig.Repositories { - if repo.Name == name { - return repo - } - } - return nil -} - -func GetRepositoryEntry(str string) (*RepositoryEntry, *Repository, error) { - split := strings.Split(str, "/") - if len(split) == 1 { - pkgName := strings.TrimSpace(split[0]) - if pkgName == "" { - return nil, nil, errors.New("could not find repository entry for this package") - } - for _, repo := range BPMConfig.Repositories { - if repo.ContainsPackage(pkgName) { - return repo.Entries[pkgName], repo, nil - } - } - return nil, nil, errors.New("could not find repository entry for this package") - } else if len(split) == 2 { - repoName := strings.TrimSpace(split[0]) - pkgName := strings.TrimSpace(split[1]) - if repoName == "" || pkgName == "" { - return nil, nil, errors.New("could not find repository entry for this package") - } - repo := GetRepository(repoName) - if repo == nil || !repo.ContainsPackage(pkgName) { - return nil, nil, errors.New("could not find repository entry for this package") - } - return repo.Entries[pkgName], repo, nil - } else { - return nil, nil, errors.New("could not find repository entry for this package") - } -} - -func FindReplacement(pkg string) *RepositoryEntry { - for _, repo := range BPMConfig.Repositories { - for _, entry := range repo.Entries { - for _, replaced := range entry.Info.Replaces { - if replaced == pkg { - return entry - } - } - } - } - - return nil -} - -func ResolveVirtualPackage(vpkg string) *RepositoryEntry { - for _, repo := range BPMConfig.Repositories { - if v, ok := repo.VirtualPackages[vpkg]; ok { - for _, pkg := range v { - return repo.Entries[pkg] - } - } - } - - return nil -} - -func (repo *Repository) FetchPackage(pkg string) (string, error) { - if !repo.ContainsPackage(pkg) { - return "", errors.New("could not fetch package '" + pkg + "'") - } - entry := repo.Entries[pkg] - URL, err := url.JoinPath(repo.Source, entry.Download) - if err != nil { - return "", err - } - resp, err := http.Get(URL) - if err != nil { - return "", err - } - defer resp.Body.Close() - - err = os.MkdirAll("/var/cache/bpm/packages/", 0755) - if err != nil { - return "", err - } - out, err := os.Create("/var/cache/bpm/packages/" + path.Base(entry.Download)) - if err != nil { - return "", err - } - defer out.Close() - - _, err = io.Copy(out, resp.Body) - return "/var/cache/bpm/packages/" + path.Base(entry.Download), nil -} diff --git a/src/bpmlib/tarball.go b/src/bpmlib/tarball.go index 84a0008..df9876d 100644 --- a/src/bpmlib/tarball.go +++ b/src/bpmlib/tarball.go @@ -5,6 +5,8 @@ import ( "errors" "io" "os" + "path" + "strings" ) type tarballFileReader struct { @@ -12,7 +14,35 @@ type tarballFileReader struct { file *os.File } -func readTarballContent(tarballPath, fileToExtract string) (*tarballFileReader, error) { +func listTarballContent(tarballPath string) (content []string, err error) { + file, err := os.Open(tarballPath) + if err != nil { + return nil, err + } + defer file.Close() + + tr := tar.NewReader(file) + for { + header, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + + switch header.Typeflag { + case tar.TypeDir: + continue + default: + content = append(content, header.Name) + } + } + + return content, nil +} + +func readTarballFile(tarballPath, fileToExtract string) (*tarballFileReader, error) { file, err := os.Open(tarballPath) if err != nil { return nil, err @@ -41,3 +71,139 @@ func readTarballContent(tarballPath, fileToExtract string) (*tarballFileReader, return nil, errors.New("could not file in tarball") } + +func extractTarballFile(tarballPath, fileToExtract string, workingDirectory string, uid, gid int) (err error) { + file, err := os.Open(tarballPath) + if err != nil { + return err + } + defer file.Close() + + tr := tar.NewReader(file) + for { + header, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + + // Skip if filename does not match + if header.Name != fileToExtract { + continue + } + + // Trim directory name from header name + header.Name = strings.Split(header.Name, "/")[len(strings.Split(header.Name, "/"))-1] + outputPath := path.Join(workingDirectory, header.Name) + + switch header.Typeflag { + case tar.TypeReg: + // Create file and set permissions + file, err = os.Create(outputPath) + if err != nil { + return err + } + err := file.Chmod(header.FileInfo().Mode()) + if err != nil { + return err + } + if uid >= 0 && gid >= 0 { + err = file.Chown(uid, gid) + if err != nil { + return err + } + } + + // Copy data to file + _, err = io.Copy(file, tr) + if err != nil { + return err + } + + // Close file + file.Close() + default: + continue + } + } + + return nil +} + +func extractTarballDirectory(tarballPath, directoryToExtract, workingDirectory string, uid, gid int) (err error) { + file, err := os.Open(tarballPath) + if err != nil { + return err + } + defer file.Close() + + tr := tar.NewReader(file) + for { + header, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + + if strings.HasPrefix(header.Name, directoryToExtract+"/") { + // Skip directory to extract + if strings.TrimRight(header.Name, "/") == workingDirectory { + continue + } + + // Trim directory name from header name + header.Name = strings.TrimPrefix(header.Name, directoryToExtract+"/") + outputPath := path.Join(workingDirectory, header.Name) + + switch header.Typeflag { + case tar.TypeDir: + // Create directory + err := os.MkdirAll(outputPath, 0755) + if err != nil { + return err + } + + // Set directory owner + if uid >= 0 && gid >= 0 { + err = os.Chown(outputPath, uid, gid) + if err != nil { + return err + } + } + case tar.TypeReg: + // Create file and set permissions + file, err = os.Create(outputPath) + if err != nil { + return err + } + err := file.Chmod(header.FileInfo().Mode()) + if err != nil { + return err + } + if uid >= 0 && gid >= 0 { + err = file.Chown(uid, gid) + if err != nil { + return err + } + } + + // Copy data to file + _, err = io.Copy(file, tr) + if err != nil { + return err + } + + // Close file + file.Close() + default: + continue + } + } + } + + return nil +} diff --git a/src/bpmlib/utils.go b/src/bpmlib/utils.go index a89dff8..99275a0 100644 --- a/src/bpmlib/utils.go +++ b/src/bpmlib/utils.go @@ -2,9 +2,7 @@ package bpmlib import ( "fmt" - "io" "math" - "os" "syscall" ) @@ -12,7 +10,7 @@ func GetArch() string { uname := syscall.Utsname{} err := syscall.Uname(&uname) if err != nil { - return "" + return "unknown" } var byteString [65]byte @@ -23,29 +21,6 @@ func GetArch() string { return string(byteString[:indexLength]) } -func copyFileContents(src, dst string) (err error) { - in, err := os.Open(src) - if err != nil { - return - } - defer in.Close() - out, err := os.Create(dst) - if err != nil { - return - } - defer func() { - cerr := out.Close() - if err == nil { - err = cerr - } - }() - if _, err = io.Copy(out, in); err != nil { - return - } - err = out.Sync() - return -} - func stringSliceRemove(s []string, r string) []string { for i, v := range s { if v == r {