package pkgtools import ( "archive/tar" "fmt" "io" "os" "path" "path/filepath" "github.com/pkg/errors" "golang.org/x/sys/unix" ) type ArchiveReader interface { Next() (*tar.Header, error) Read([]byte) (int, error) } const NewDirMod = 0755 func tarCreateBlockDev(target string, header *tar.Header) error { mode := uint32(header.Mode & 07777) mode |= unix.S_IFBLK dev := unix.Mkdev(uint32(header.Devmajor), uint32(header.Devminor)) err := unix.Mknod(target, mode, int(dev)) return err } func tarCreateCharDev(target string, header *tar.Header) error { mode := uint32(header.Mode & 07777) mode |= unix.S_IFCHR dev := unix.Mkdev(uint32(header.Devmajor), uint32(header.Devminor)) err := unix.Mknod(target, mode, int(dev)) return err } func tarCreateDir(target string) error { err := os.MkdirAll(target, NewDirMod) return err } func tarCreateFifo(target string, header *tar.Header) error { mode := uint32(header.Mode & 07777) mode |= unix.S_IFIFO dev := unix.Mkdev(uint32(header.Devmajor), uint32(header.Devminor)) err := unix.Mknod(target, mode, int(dev)) return err } func tarCreateReg(target string, r io.Reader, header *tar.Header) error { f, err := os.OpenFile( target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode), ) if err != nil { // it could be an error due to writing over a running executable. err = os.Remove(target) if err != nil { return err } f, err = os.OpenFile( target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode), ) if err != nil { return err } } defer f.Close() // copy over contents if _, err := io.Copy(f, r); err != nil { return err } return nil } func tarCreateLink(root string, header *tar.Header) error { oldName := filepath.Join(root, header.Linkname) if filepath.IsAbs(header.Linkname) { filepath.Join(root, oldName) } newName := filepath.Join(root, header.Name) err := os.Link(oldName, newName) if err != nil { _ = os.Remove(newName) err = os.Link(oldName, newName) } return err } func tarCreateSymlink(root string, header *tar.Header) error { oldName := filepath.Join(root, header.Linkname) if filepath.IsAbs(header.Linkname) { filepath.Join(root, oldName) } newName := filepath.Join(root, header.Name) err := os.Symlink(oldName, newName) if err != nil { _ = os.Remove(newName) err = os.Symlink(oldName, newName) } return err } type InstallArchiveCfg struct { Root string Chown bool Chmod bool } func InstallArchive( r ArchiveReader, cfg *InstallArchiveCfg, ) error { for { header, err := r.Next() if err == io.EOF { break } else if err != nil { return err } 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) } } } return nil }