diff options
author | Slack Coder <slackcoder@server.ky> | 2022-03-11 13:08:24 -0500 |
---|---|---|
committer | Slack Coder <slackcoder@server.ky> | 2022-03-30 11:34:46 -0500 |
commit | 29589a24b13fb223b113e94eca2c4fff0e56a4d9 (patch) | |
tree | e1754d195463439ae2834cd502b170648e47cdb8 /archive.go | |
download | pkgtools-go-29589a24b13fb223b113e94eca2c4fff0e56a4d9.tar.xz |
Initial commit
Diffstat (limited to 'archive.go')
-rw-r--r-- | archive.go | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/archive.go b/archive.go new file mode 100644 index 0000000..fa1886f --- /dev/null +++ b/archive.go @@ -0,0 +1,166 @@ +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 +} |