aboutsummaryrefslogtreecommitdiffsponsor
path: root/archive.go
diff options
context:
space:
mode:
authorSlack Coder <slackcoder@server.ky>2022-03-11 13:08:24 -0500
committerSlack Coder <slackcoder@server.ky>2022-03-30 11:34:46 -0500
commit29589a24b13fb223b113e94eca2c4fff0e56a4d9 (patch)
treee1754d195463439ae2834cd502b170648e47cdb8 /archive.go
downloadpkgtools-go-29589a24b13fb223b113e94eca2c4fff0e56a4d9.tar.xz
Initial commit
Diffstat (limited to 'archive.go')
-rw-r--r--archive.go166
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
+}