package pkgtools import ( "archive/tar" "fmt" "io" "os" "os/exec" "path/filepath" "time" "github.com/juju/fslock" "github.com/pkg/errors" ) const umask = "022" const DefaultPerms = 0755 // ExitStatusTarError is when tar returned error code. const ExitStatusTarError = 1 // ExitStatusCorruptCompression is when corrupt compression envelope. const ExitStatusCorruptCompression = 2 // ExitStatusIncorrectExt is when does not end in .tgz. const ExitStatusIncorrectExt = 3 // ExitStatusNoSuchFile is when no such file. const ExitStatusNoSuchFile = 4 // ExitStatusMissingCompressionUtility is when external compression utility. // missing const ExitStatusMissingCompressionUtility = 5 // ExitStatusUserAbortFromMenu is when user abort from menu mode. const ExitStatusUserAbortFromMenu = 99 var ErrNotImplemented = errors.New("not implemented") // DefaultTerseLength is the default line length during terse mode. const DefaultTerseLength = 80 // Ended at line 232 const ( PriorityNone = "" PriorityAdditional = "ADD" PriorityRecommended = "REC" PriorityOptional = "OPT" PrioritySkip = "SKP" ) type InstallPkgFlags struct { Ask bool InfoBox bool LockDir string MD5Sum bool Menu bool NoOverwrite bool Priority string Root string TagFile string Terse bool TerseLength int Warn bool chown bool chmod bool Strict bool } var DefaultInstallPkgFlags = InstallPkgFlags{ // Official slackware configuration Ask: false, InfoBox: false, LockDir: InstallLockDir, MD5Sum: false, Menu: false, NoOverwrite: false, Priority: PriorityNone, Root: "/", TagFile: "", Terse: false, TerseLength: DefaultTerseLength, Warn: false, // ours chown: false, chmod: false, Strict: true, } func (s *InstallPkgFlags) 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 } if v := os.Getenv("PKGTOOLS_GO_STRICT"); v != "NO" { s.Strict = false } } func runInstallScript( flags *InstallPkgFlags, pkgBase string, ) error { l := fslock.New(FileLockPath(flags.LockDir, filepath.Base(PackageInstallScript))) if err := l.Lock(); err != nil { return err } defer l.Unlock() cwd, err := os.Getwd() if err != nil { return err } err = os.Chdir(flags.Root) if err != nil { return err } defer os.Chdir(cwd) installScript := filepath.Join(flags.Root, ADMDir, "scripts", pkgBase) if _, err := os.Stat(installScript); !os.IsNotExist(err) { cmd := exec.Command("/bin/bash", installScript) cmd.Stdin = os.Stdout cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Run() if err != nil { return err } } return nil } const ADMDirPerms = 0755 const LogDirPerms = 0755 const TmpDirPerms = 0700 func initializeDirectories(flags *InstallPkgFlags) error { err := os.MkdirAll(flags.LockDir, DefaultPerms) if err != nil { return err } dirs := []string{ "douninst.sh", "packages", "scripts", "setup", } for _, f := range dirs { fp := filepath.Join(flags.Root, ADMDir, f) err := os.MkdirAll(fp, DefaultPerms) if err != nil { return err } err = os.Chmod(fp, ADMDirPerms) if err != nil { return err } } dirs = []string{ "removed_packages", "removed_scripts", } for _, f := range dirs { fp := filepath.Join(flags.Root, LogDir, f) if ok, err := IsDir(fp); !ok && !os.IsNotExist(err) { err := os.Remove(fp) if err != nil { return err } } err := os.MkdirAll(fp, DefaultPerms) if err != nil { return err } err = os.Chmod(fp, LogDirPerms) if err != nil { return err } } dirs = []string{ "packages", "scripts", "setup", } for _, f := range dirs { fp := filepath.Join(flags.Root, LogDir, "..", f) isDir, _ := IsDir(fp) isSymlink, _ := IsSymlink(fp) if isDir || isSymlink { continue } err := os.Symlink(filepath.Join("..", "lib", "pkgtools", f), fp) if err != nil { return err } } fp := filepath.Join(flags.Root, TmpDir) if ok, _ := IsDir(TmpDir); !ok { err := os.MkdirAll(fp, DefaultPerms) if err != nil { return err } err = os.Chmod(fp, TmpDirPerms) if err != nil { return err } } return nil } func showSlackDesc(target string) error { return nil } // TODO: Check permissions for written files func writeToDatabase(target string, pkg string) error { // copy install script // TODO: include uninstall script installScript := filepath.Join(target, ADMDir, "scripts", PackageBase(pkg)) uninstallScript := filepath.Join(target, ADMDir, "douninst.sh", PackageBase(pkg)) slackPkg, err := OpenSlackwarePkg(pkg) if err != nil { return err } defer slackPkg.Close() for { entry, err := slackPkg.Next() if err == io.EOF { break } else if err != nil { return err } if entry == nil { continue } switch entry.Name { case PackageInstallScript: f, err := os.Create(installScript) if err != nil { return err } defer f.Close() _, err = io.Copy(f, slackPkg) if err != nil { return err } case PackageUninstallScript: f, err := os.Create(uninstallScript) if err != nil { return err } defer f.Close() _, err = io.Copy(f, slackPkg) if err != nil { return err } default: _, _ = io.Copy(io.Discard, slackPkg) } } fp := filepath.Join(target, ADMDir, "packages", PackageBase(pkg)) f, err := os.Create(fp) if err != nil { return err } defer f.Close() e := NewEncoder(f) err = e.Encode(slackPkg.PkgInfo()) if err != nil { return err } 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) 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, Strict: flags.Strict, }) err = FilterTar( slackPkg, TarFilterFunc(func(h *tar.Header, r io.Reader) error { if ok, err := IsParentDir(PackageInstallPath, h.Name); ok { // important install files are already written // to the targets package database. return nil } else if err != nil { return errors.Wrap(err, "installing package") } if flags.NoOverwrite { ok, err := IsExist(filepath.Join(flags.Root, h.Name)) if err != nil { return err } if ok { return nil } } return toRoot.FilterTar(h, r) }), ) return errors.Wrap(err, "installing package") } func InstallPkg(flags *InstallPkgFlags, pkgs ...string) error { // TODO: Apply default flag values err := initializeDirectories(flags) if err != nil { return err } for _, pkg := range pkgs { fmt.Printf("installing %s\n", PackageBase(pkg)) err = writeToDatabase(flags.Root, pkg) if err != nil { return errors.Wrap(err, "writing package to database") } err := extractSlackwarePkg(flags, pkg) if err != nil { return errors.Wrap(err, "writing package to database") } _ = os.Chtimes( filepath.Join(flags.Root, ADMDir, "packages", PackageBase(pkg)), time.Now(), time.Now(), ) _ = runLDConfig(flags.LockDir) err = runInstallScript(flags, PackageBase(pkg)) if err != nil { fmt.Fprintln(os.Stderr, errors.Wrap(err, "running install script")) } } return nil }