diff options
Diffstat (limited to 'removepkg.go')
-rw-r--r-- | removepkg.go | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/removepkg.go b/removepkg.go new file mode 100644 index 0000000..a1fab5e --- /dev/null +++ b/removepkg.go @@ -0,0 +1,278 @@ +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 +} |