package pkgtools import ( "fmt" "io" "os" "os/exec" "path" "path/filepath" "sort" "strings" "github.com/juju/fslock" "github.com/pkg/errors" ) const UninstallScript = "var/lib/pkgtools/douinst.sh" func getPackageName(pkg string) string { return PackageBase(pkg) } // Official flags // [ ROOT=/mnt ] removepkg [--copy] [--keep] [--preserve] [--skip-douninst] [--terse] [--warn] packagename ... type RemovePkgFlags struct { LockDir string Root string } var DefaultRemovePkgFlags = RemovePkgFlags{ LockDir: InstallLockDir, Root: "/", } func (s *RemovePkgFlags) SetEnvValues() { if v := os.Getenv("INSTLOCKDIR"); v != "" { s.LockDir = v } // Official installpkg prefers the argument of the environment // variable. if v := os.Getenv("ROOT"); v != "" { s.Root = v } } func guessPackagename(root string, str string) (string, bool) { // TODO: check if package exists return str, true } func runUninstallScript(root string) error { cwd, err := os.Getwd() if err != nil { return err } err = os.Chdir(root) if err != nil { return err } defer os.Chdir(cwd) uninstallScript := path.Join(root, PackageUninstallScript) if _, err := os.Stat(uninstallScript); !os.IsNotExist(err) { cmd := exec.Command("/bin/sh", uninstallScript) cmd.Stdin = os.Stdout cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Run() if err != nil { return err } } return nil } func runLDConfig(lockdir string) error { l := fslock.New(FileLockPath(lockdir, "ldconfig")) if err := l.Lock(); err != nil { return err } defer l.Unlock() if _, err := os.Stat("/sbin/ldconfig"); !os.IsNotExist(err) { cmd := exec.Command("/sbin/ldconfig") cmd.Stdin = os.Stdout cmd.Stdout = os.Stdout cmd.Stderr = io.Discard err = cmd.Run() if err != nil { return err } } return nil } func deletePackageFiles( flags *RemovePkgFlags, pkg string, ) error { info, err := GetPackageInfo(flags.Root, pkg) if err != nil { return err } pkgFiles := info.FileList links, err := GetPackageSoftLinks(flags.Root, pkg) if err != nil { return err } pkgFiles = append(pkgFiles, links...) pkgFiles = append(pkgFiles, catPaths(pkgFiles)...) // get all other package files otherPkgFiles := make(map[string]bool) otherPkgs, err := ListPackages(flags.Root) if err != nil { return err } for _, o := range otherPkgs { if o == pkg { continue } info, err := GetPackageInfo(flags.Root, o) if err != nil { return err } paths := info.FileList links, err := GetPackageSoftLinks(flags.Root, o) paths = append(paths, links...) paths = append(paths, catPaths(paths)...) for _, p := range paths { otherPkgFiles[p] = true } } var dirs []string for _, fp := range pkgFiles { if ok := otherPkgFiles[fp]; ok { continue } tfp := filepath.Join(flags.Root, fp) if ok, _ := IsDir(tfp); ok { dirs = append(dirs, fp) continue } // Bravely attempt removal ignoring errors. The os.Stat // returns a NotExist error soft links whose target is missing, // resulting in error. _ = os.Remove(tfp) } sort.Sort(sort.Reverse(sort.StringSlice(dirs))) for _, fp := range dirs { tfp := filepath.Join(flags.Root, fp) // Remove deletes only empty directories _ = os.Remove(tfp) } return nil } func catPaths(fps []string) []string { var cats []string for _, p := range fps { if !strings.HasPrefix(p, "usr/man/man") { continue } cat := strings.Replace(p, "usr/man/man", "usr/man/cat", -1) cats = append(cats, cat) } return cats } func RemovePkg( flags *RemovePkgFlags, pkgNames ...string, ) error { // TODO: Apply default flag values err := os.MkdirAll(flags.LockDir, DefaultPerms) if err != nil { return err } tmpDir := TargetTmpDir(flags.Root) if ok, _ := IsDir(tmpDir); !ok { err := os.MkdirAll(tmpDir, DefaultPerms) if err != nil { return err } err = os.Chmod(tmpDir, TmpDirPerms) if err != nil { return err } } var pkgs []string for _, pkgName := range pkgNames { pkg, ok, err := GetFullPackageName(flags.Root, pkgName) if err != nil { return errors.Wrap(err, "getting package information") } if !ok { return errors.Errorf("package does not exist '%s'", pkgName) } pkgs = append(pkgs, pkg) } for _, pkg := range pkgs { fmt.Printf("removing %s\n", pkg) tmpUninstallScript := "" if ok, _ := IsFile(filepath.Join(flags.Root, UninstallScript, pkg)); ok { tmpUninstallScript = filepath.Join(tmpDir, pkg) err := CopyFile( tmpUninstallScript, filepath.Join(flags.Root, UninstallScript, pkg), ) if err != nil { return errors.Wrap(err, "removing package "+pkg) } } err := deletePackageFiles(flags, pkg) if err != nil { // TODO: should removepkg bail out on remove error return errors.Wrap(err, "deleting package files") } err = runUninstallScript(flags.Root) if err != nil { return err } err = os.Rename( TargetPackageInfoPath(flags.Root, pkg), filepath.Join(flags.Root, InstalledRemovedPackagePath, pkg), ) if err != nil { return errors.Wrap(err, "removing package "+pkg) } if ok, _ := IsFile(TargetScriptPath(flags.Root, pkg)); ok { err := os.Rename( TargetScriptPath(flags.Root, pkg), filepath.Join(flags.Root, InstalledRemovedScriptsPath, pkg), ) if err != nil { return errors.Wrap(err, "removing package "+pkg) } } if ok, _ := IsFile(filepath.Join(tmpDir, pkg)); ok { err := os.Rename( tmpUninstallScript, TargetRemovedUninstalllScriptPath(flags.Root, pkg), ) if err != nil { return errors.Wrap(err, "removing package "+pkg) } } if flags.Root == "/" { err = runLDConfig(flags.LockDir) if err != nil { return err } } } return nil }