From 5d4066dd1e90fa704b67384327af72b3ec121102 Mon Sep 17 00:00:00 2001 From: Slack Coder Date: Sat, 23 Apr 2022 09:46:41 -0500 Subject: installpkg: Avoid using /install on target dir Use a temporary randomly named directory named like 'installpkg-2052825695' for the package's metadata in its '/install' directory. It will help allow running multiple instances on the same target root directory by removing a point of conflict. Attempt to place this directory in the host's default temporary directory. If it does not exist, use the host's root path. --- archive.go | 124 +++++++++++++++++++++++++++++++++++++++++----------------- filesystem.go | 15 +++++++ installpkg.go | 88 +++++++++++++++++++++++++++++------------ pkgtools.go | 2 +- 4 files changed, 167 insertions(+), 62 deletions(-) diff --git a/archive.go b/archive.go index fa1886f..6610936 100644 --- a/archive.go +++ b/archive.go @@ -12,7 +12,7 @@ import ( "golang.org/x/sys/unix" ) -type ArchiveReader interface { +type TarReader interface { Next() (*tar.Header, error) Read([]byte) (int, error) } @@ -107,15 +107,78 @@ func tarCreateSymlink(root string, header *tar.Header) error { return err } -type InstallArchiveCfg struct { +type TarFilter interface { + FilterTar(header *tar.Header, r io.Reader) error +} + +type TarFilterFunc func( + header *tar.Header, + r io.Reader, +) error + +func (f TarFilterFunc) FilterTar(h *tar.Header, r io.Reader) error { + return f(h, r) +} + +type TarCfg struct { Root string Chown bool Chmod bool } -func InstallArchive( - r ArchiveReader, - cfg *InstallArchiveCfg, +type TarExtracter struct { + cfg *TarCfg +} + +func NewTarExtractor(cfg *TarCfg) *TarExtracter { + return &TarExtracter{cfg} +} + +func (s *TarExtracter) FilterTar( + header *tar.Header, + r io.Reader, +) error { + target := path.Join(s.cfg.Root, header.Name) + + var err error + switch header.Typeflag { + case tar.TypeBlock: + err = tarCreateBlockDev(target, header) + case tar.TypeChar: + err = tarCreateCharDev(target, header) + case tar.TypeFifo: + err = tarCreateFifo(target, header) + case tar.TypeDir: + err = tarCreateDir(target) + case tar.TypeReg: + err = tarCreateReg(target, r, header) + case tar.TypeLink: + err = tarCreateLink(s.cfg.Root, header) + case tar.TypeSymlink: + err = tarCreateSymlink(s.cfg.Root, header) + default: + err = errors.Errorf("unhandled file type '%c'", header.Typeflag) + } + if err != nil { + return errors.Wrap(err, "unpacking tar archive") + } + + if s.cfg.Chown { + if err = os.Chown(target, header.Uid, header.Gid); err != nil { + fmt.Fprintln(os.Stderr, err) + } + } + if s.cfg.Chmod { + if err = os.Chmod(target, header.FileInfo().Mode()); err != nil { + fmt.Fprintln(os.Stderr, err) + } + } + return nil +} + +func FilterTar( + r TarReader, + filters ...TarFilter, ) error { for { header, err := r.Next() @@ -127,40 +190,29 @@ func InstallArchive( if header == nil { continue } - target := path.Join(cfg.Root, header.Name) - - switch header.Typeflag { - case tar.TypeBlock: - err = tarCreateBlockDev(target, header) - case tar.TypeChar: - err = tarCreateCharDev(target, header) - case tar.TypeFifo: - err = tarCreateFifo(target, header) - case tar.TypeDir: - err = tarCreateDir(target) - case tar.TypeReg: - err = tarCreateReg(target, r, header) - case tar.TypeLink: - err = tarCreateLink(cfg.Root, header) - case tar.TypeSymlink: - err = tarCreateSymlink(cfg.Root, header) - default: - err = errors.Errorf("unhandled file type '%c'", header.Typeflag) - } - if err != nil { - return errors.Wrap(err, "unpacking tar archive") - } - if cfg.Chown { - if err = os.Chown(target, header.Uid, header.Gid); err != nil { - fmt.Fprintln(os.Stderr, err) - } - } - if cfg.Chmod { - if err = os.Chmod(target, header.FileInfo().Mode()); err != nil { - fmt.Fprintln(os.Stderr, err) + + for _, f := range filters { + // TODO: how do we dup r? + err := f.FilterTar(header, r) + if err != nil { + return err } } } return nil } + +func ExtractTar( + cfg *TarCfg, + fp string, +) error { + f, err := os.Open(fp) + if err != nil { + return err + } + defer f.Close() + r := tar.NewReader(f) + err = FilterTar(r, NewTarExtractor(cfg)) + return errors.Wrap(err, "extracting tar file") +} diff --git a/filesystem.go b/filesystem.go index c33df15..dec07f3 100644 --- a/filesystem.go +++ b/filesystem.go @@ -4,7 +4,9 @@ import ( "io/fs" "math" "os" + "path/filepath" "strconv" + "strings" "time" ) @@ -46,6 +48,19 @@ func IsDir(path string) (bool, error) { return info.IsDir(), nil } +func IsParentDir(parent, sub string) (bool, error) { + up := ".." + string(os.PathSeparator) + + rel, err := filepath.Rel(parent, sub) + if err != nil { + return false, err + } + if !strings.HasPrefix(rel, up) && rel != ".." { + return true, nil + } + return false, nil +} + func IsSymlink(path string) (bool, error) { info, err := os.Stat(path) if err != nil { diff --git a/installpkg.go b/installpkg.go index 7354ff0..7e10e61 100644 --- a/installpkg.go +++ b/installpkg.go @@ -1,12 +1,14 @@ package pkgtools import ( + "archive/tar" "fmt" "io" "os" "os/exec" "path" "path/filepath" + "strings" "time" "github.com/juju/fslock" @@ -99,7 +101,12 @@ func (s *InstallPkgFlags) SetEnvValues() { } } -func runInstallScript(flags *InstallPkgFlags) error { +func runInstallScript(flags *InstallPkgFlags, installPath string) error { + installPath, err := filepath.Abs(installPath) + if err != nil { + return err + } + l := fslock.New(FileLockPath(flags.LockDir, filepath.Base(PackageInstallScript))) if err := l.Lock(); err != nil { return err @@ -116,7 +123,7 @@ func runInstallScript(flags *InstallPkgFlags) error { } defer os.Chdir(cwd) - installScript := path.Join(flags.Root, PackageInstallScript) + installScript := path.Join(installPath, filepath.Base(PackageInstallScript)) if _, err := os.Stat(installScript); !os.IsNotExist(err) { cmd := exec.Command("/bin/bash", installScript) cmd.Stdin = os.Stdout @@ -287,19 +294,65 @@ func writeToDatabase(target string, pkg string) error { return nil } +// ExtractSlackwarePkg unarchives the slackware package into the given +// directory. The package metadata, under the archives '/install' path, is put +// into a temporary directory and returned. +func ExtractSlackwarePkg(flags *InstallPkgFlags, fp string) (string, error) { + slackPkg, err := OpenSlackwarePkg(fp) + if err != nil { + return "", errors.Wrap(err, "extracting package") + } + defer slackPkg.Close() + + toRoot := NewTarExtractor(&TarCfg{ + Root: flags.Root, + Chmod: flags.chmod, + Chown: flags.chown, + }) + + tmpDir := os.TempDir() + if _, err := os.Stat(tmpDir); os.IsNotExist(err) { + tmpDir = "/" + } else if err != nil { + return "", errors.Wrap(err, "extracting package") + } + tempInstallDir, err := os.MkdirTemp(tmpDir, "installpkg-*") + if err != nil { + return "", errors.Wrap(err, "extracting package") + } + toTempInstallDir := NewTarExtractor(&TarCfg{ + Root: tempInstallDir, + Chmod: flags.chmod, + Chown: flags.chown, + }) + + err = FilterTar( + slackPkg, + TarFilterFunc(func(h *tar.Header, r io.Reader) error { + if ok, err := IsParentDir(PackageInstallPath, h.Name); ok { + h.Name = strings.TrimPrefix(h.Name, PackageInstallPath+"/") + if h.Name == "" { + h.Name = "." + } + return toTempInstallDir.FilterTar(h, r) + } else if err != nil { + return errors.Wrap(err, "installing package") + } + + return toRoot.FilterTar(h, r) + }), + ) + return tempInstallDir, errors.Wrap(err, "installing package") +} + func InstallPkg(flags *InstallPkgFlags, pkgs ...string) error { - // Apply default flag values + // TODO: Apply default flag values err := initializeDirectories(flags) if err != nil { return err } - installPath := path.Join(flags.Root, PackageInstallPath) - if _, err := os.Stat(installPath); !errors.Is(err, os.ErrNotExist) { - return errors.Errorf("The '%s' directory exists and would be used by installpkg and removed. Please consider renaming the directory or using Slackware's installpkg.", installPath) - } - for _, pkg := range pkgs { fmt.Printf("installing %s\n", PackageBase(pkg)) err = writeToDatabase(flags.Root, pkg) @@ -307,22 +360,7 @@ func InstallPkg(flags *InstallPkgFlags, pkgs ...string) error { return errors.Wrap(err, "writing package to database") } - err = func() error { - slackPkg, err := OpenSlackwarePkg(pkg) - if err != nil { - return err - } - defer slackPkg.Close() - err = InstallArchive( - slackPkg, - &InstallArchiveCfg{ - Root: flags.Root, - Chmod: flags.chmod, - Chown: flags.chown, - }, - ) - return errors.Wrap(err, "installing package") - }() + installPath, err := ExtractSlackwarePkg(flags, pkg) if err != nil { return errors.Wrap(err, "writing package to database") } @@ -336,7 +374,7 @@ func InstallPkg(flags *InstallPkgFlags, pkgs ...string) error { _ = runLDConfig(flags.LockDir) // TODO: command should still run after failed 'install script' execution - err = runInstallScript(flags) + err = runInstallScript(flags, installPath) if err != nil { fmt.Fprintln(os.Stderr, errors.Wrap(err, "running install script")) } diff --git a/pkgtools.go b/pkgtools.go index aaffb75..5c8e928 100644 --- a/pkgtools.go +++ b/pkgtools.go @@ -219,7 +219,7 @@ type SlackwarePkg struct { pkgInfo PackageInfo } -var _ ArchiveReader = (*SlackwarePkg)(nil) +var _ TarReader = (*SlackwarePkg)(nil) func OpenSlackwarePkg(fp string) (*SlackwarePkg, error) { var pkg SlackwarePkg -- cgit v1.2.3