diff --git a/main.go b/main.go index a2fce92..c9f2946 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,6 @@ package main import ( "context" - "errors" "fmt" "os" "os/exec" @@ -34,7 +33,7 @@ type config struct { txInfo []packageInfo // parsed packages from dry-run } -// CLI +// CLI func printHelp() { // Ensure cfg is available for colorStart @@ -53,7 +52,7 @@ func printHelp() { } usage := fmt.Sprintf(`%susage%s: vlcro [-h] [-v] [-y] [-j N] [--debug] [--no-color] -%s {refresh,ref,dist-upgrade,dup,update,up,install,in,install-new-recommends,inr,search,se} ... +%s {refresh,ref,dist-upgrade,dup,install,in,install-new-recommends,inr} ... %s%s%s (%sv%s%s) makes zypper faster by running slow operations in parallel. @@ -72,12 +71,6 @@ func printHelp() { %sdist-upgrade%s (%sdup%s) perform distribution upgrade %s-d, --download-only%s download packages without installing - %supdate%s (%sup%s) update all installed packages - %s-d, --download-only%s download packages without installing - - %ssearch%s (%sse%s) search for packages matching pattern - %s%s pattern(s) to search for - %sinstall%s (%sin%s) install one or more packages %s-d, --download-only%s download packages without installing %s%s package name(s) to install @@ -93,8 +86,6 @@ func printHelp() { hi, d, hd, d, hd, d, he, d, hd, d, hd, d, he, d, - hd, d, hd, d, he, d, - hd, d, hd, d, hd, d, hd, d, hd, d, he, d, hd, d, hd, d, hd, d, he, d) @@ -130,14 +121,10 @@ func isInstallCommand(cmd string) bool { return cmd == "install" || cmd == "in" } -func isSearchCommand(cmd string) bool { - return cmd == "search" || cmd == "se" -} - func isKnownCommand(cmd string) bool { switch cmd { - case "refresh", "ref", "dist-upgrade", "dup", "update", "up", - "install", "in", "install-new-recommends", "inr", "search", "se": + case "refresh", "ref", "dist-upgrade", "dup", "install", "in", + "install-new-recommends", "inr": return true } return false @@ -151,15 +138,6 @@ func parseArgsInto(args []string) (*config, error) { for i < len(args) { arg := args[i] - - // search/se passes all remaining patterns and zypper search - // options verbatim to zypper. - if cmdFound && isSearchCommand(c.command) { - c.packages = append(c.packages, arg) - i++ - continue - } - consumedNext := false switch { @@ -362,19 +340,6 @@ func main() { if err := handleDistUpgrade(ctx, tmpDir); err != nil { exitCode = 1 } - case "update", "up": - if err := handleUpdate(ctx, tmpDir); err != nil { - exitCode = 1 - } - case "search", "se": - if err := handleSearch(ctx, tmpDir); err != nil { - var see *searchExitError - if errors.As(err, &see) { - exitCode = see.code - } else { - exitCode = 1 - } - } case "install", "in": if err := handleInstall(ctx, tmpDir); err != nil { exitCode = 1 diff --git a/zypper.go b/zypper.go index cfe4e3d..36ff478 100644 --- a/zypper.go +++ b/zypper.go @@ -10,9 +10,7 @@ import ( "os" "os/exec" "path/filepath" - "regexp" "sort" - "strconv" "strings" "sync" "syscall" @@ -744,186 +742,6 @@ func handleDistUpgrade(ctx context.Context, tmpDir string) error { return nil } -func handleUpdate(ctx context.Context, tmpDir string) error { - tracker := &MountTracker{} - defer vlcroCleanup(tmpDir, tracker) - - statusMsg("Reading package lists") - statusMsg("Building dependency tree") - info, err := dryRunAndParse([]string{"update", "--dry-run"}) - if err != nil { - errorf("%s", colorText(colorError, fmt.Sprintf("Error: %v", err))) - return err - } - - if info.nothingToDo { - infof("%s", colorText(colorInfo, "Nothing to do. Exiting...")) - return nil - } - - cfg.txInfo = info.installs - - if !showTransactionAndConfirm(info) { - return nil - } - - getZyppLock() - - if info.downloadSize > 0 { - statusMsg("Downloading packages") - err := mainTask(ctx, pkgNames(info.installs), tmpDir, false, tracker) - if err == context.Canceled { - return err - } - if err != nil { - warningf("%s", colorText(colorWarning, fmt.Sprintf("Parallel execution failed (%v). Falling back to standard zypper...", err))) - } - } - - vlcroCleanup(tmpDir, tracker) - - if !cfg.downloadOnly { - infof("%s", colorText(colorInfo, "Vlcro has finished its tasks. Handing you over to zypper...")) - nonInteractive := "" - if cfg.noConfirm { - nonInteractive = "--non-interactive" - } - code := handover(fmt.Sprintf("env ZYPP_CURL2=1 ZYPP_PCK_PRELOAD=1 ZYPP_SINGLE_RPMTRANS=1 zypper %s --no-cd update", nonInteractive)) - if code != 0 { - return fmt.Errorf("zypper update failed with exit code %d", code) - } - } - - return nil -} - -// search / se - -type searchExitError struct { - code int -} - -func (e *searchExitError) Error() string { - return fmt.Sprintf("zypper search failed with exit code %d", e.code) -} - -// repoRefreshDelayMinutes returns the configured repo.refresh.delay (minutes) -// from zypp.conf, or the default of 10 when unset. -func repoRefreshDelayMinutes() int { - delay := -1 - parse := func(path string) { - data, err := os.ReadFile(path) - if err != nil { - return - } - re := regexp.MustCompile(`^\s*repo\.refresh\.delay\s*[:=]\s*(\d+)\s*$`) - for _, line := range strings.Split(string(data), "\n") { - if m := re.FindStringSubmatch(line); m != nil { - if n, err := strconv.Atoi(m[1]); err == nil { - delay = n - } - } - } - } - parse("/etc/zypp/zypp.conf") - if entries, err := filepath.Glob("/etc/zypp/zypp.conf.d/*.conf"); err == nil { - sort.Strings(entries) - for _, e := range entries { - parse(e) - } - } - if delay < 0 { - delay = 10 - } - return delay -} - -// repoNeedsRefresh mirrors zypper's auto-refresh policy: a repo needs -// refreshing when its cached index file timestamp is older than -// repo.refresh.delay. Repos never refreshed must be refreshed. -func repoNeedsRefresh(alias string) bool { - delay := repoRefreshDelayMinutes() - rawPath := filepath.Join("/var/cache/zypp/raw", alias) - repomd := filepath.Join(rawPath, "repodata", "repomd.xml") - - var last time.Time - if st, err := os.Stat(repomd); err == nil { - last = st.ModTime() - } else if info, err := os.Stat(rawPath); err == nil && info.IsDir() { - filepath.Walk(rawPath, func(_ string, fi os.FileInfo, err error) error { - if err == nil && fi.ModTime().After(last) { - last = fi.ModTime() - } - return nil - }) - } else { - return true - } - if last.IsZero() { - return true - } - return time.Since(last) > time.Duration(delay)*time.Minute -} - -// runSearch runs the real zypper search with terminal passthrough so the -// output is identical to stock zypper. -func runSearch(args []string) int { - cmd := exec.Command("zypper", append([]string{"--no-refresh", "search"}, args...)...) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - if exitErr, ok := err.(*exec.ExitError); ok { - return exitErr.ExitCode() - } - return 1 - } - return 0 -} - -func handleSearch(ctx context.Context, tmpDir string) error { - if len(cfg.packages) == 0 { - errorf("%s", colorText(colorError, "Search must have at least one search pattern")) - return fmt.Errorf("no search pattern specified") - } - - statusMsg("Reading repository list") - xmlOutput, _ := zypperDryRun([]string{"repos"}) - repos := findElements(xmlOutput, "repo") - - var staleRepos []string - for _, r := range repos { - if r["enabled"] != "1" || r["autorefresh"] != "1" { - continue - } - if repoNeedsRefresh(r["alias"]) { - staleRepos = append(staleRepos, r["alias"]) - } - } - debugf("Stale repos: %v", staleRepos) - - if len(staleRepos) > 0 { - tracker := &MountTracker{} - statusMsg(fmt.Sprintf("Refreshing %d stale repo(s)", len(staleRepos))) - getZyppLock() - err := mainTask(ctx, staleRepos, tmpDir, true, tracker) - if err == context.Canceled { - vlcroCleanup(tmpDir, tracker) - return err - } - if err != nil { - warningf("%s", colorText(colorWarning, fmt.Sprintf("Parallel refresh failed (%v). Falling back to standard zypper...", err))) - } - vlcroCleanup(tmpDir, tracker) - } - - code := runSearch(cfg.packages) - if code != 0 { - return &searchExitError{code: code} - } - return nil -} - func handleInstall(ctx context.Context, tmpDir string) error { if len(cfg.packages) == 0 { errorf("%s", colorText(colorError, "No packages specified for installation"))