github.com/leg100/ots@v0.0.7-0.20210919080622-034055ced4bd/unpack.go (about) 1 package ots 2 3 import ( 4 "archive/tar" 5 "compress/gzip" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 ) 11 12 // Unpack a .tar.gz byte stream to a directory 13 func Unpack(r io.Reader, dst string) error { 14 // Decompress as we read. 15 decompressed, err := gzip.NewReader(r) 16 if err != nil { 17 return fmt.Errorf("failed to decompress archive: %w", err) 18 } 19 20 // Untar as we read. 21 untar := tar.NewReader(decompressed) 22 23 // Unpackage all the contents into the directory. 24 for { 25 header, err := untar.Next() 26 if err == io.EOF { 27 break 28 } 29 if err != nil { 30 return fmt.Errorf("failed to untar archive: %w", err) 31 } 32 33 // Get rid of absolute paths. 34 path := header.Name 35 if path[0] == '/' { 36 path = path[1:] 37 } 38 path = filepath.Join(dst, path) 39 40 // Make the directories to the path. 41 dir := filepath.Dir(path) 42 if err := os.MkdirAll(dir, 0755); err != nil { 43 return fmt.Errorf("failed to create directory: %w", err) 44 } 45 46 // If we have a symlink, just link it. 47 if header.Typeflag == tar.TypeSymlink { 48 if err := os.Symlink(header.Linkname, path); err != nil { 49 return fmt.Errorf("failed creating symlink %q => %q: %w", 50 path, header.Linkname, err) 51 } 52 continue 53 } 54 55 // Only unpack regular files from this point on. 56 if header.Typeflag == tar.TypeDir { 57 continue 58 } else if header.Typeflag != tar.TypeReg && header.Typeflag != tar.TypeRegA { 59 return fmt.Errorf("failed creating %q: unsupported type %c", path, 60 header.Typeflag) 61 } 62 63 // Open a handle to the destination. 64 fh, err := os.Create(path) 65 if err != nil { 66 // This mimics tar's behavior wrt the tar file containing duplicate files 67 // and it allowing later ones to clobber earlier ones even if the file 68 // has perms that don't allow overwriting. 69 if os.IsPermission(err) { 70 os.Chmod(path, 0600) 71 fh, err = os.Create(path) 72 } 73 74 if err != nil { 75 return fmt.Errorf("failed creating file %q: %w", path, err) 76 } 77 } 78 79 // Copy the contents. 80 _, err = io.Copy(fh, untar) 81 fh.Close() 82 if err != nil { 83 return fmt.Errorf("failed to copy file %q: %w", path, err) 84 } 85 86 // Restore the file mode. We have to do this after writing the file, 87 // since it is possible we have a read-only mode. 88 mode := header.FileInfo().Mode() 89 if err := os.Chmod(path, mode); err != nil { 90 return fmt.Errorf("failed setting permissions on %q: %w", path, err) 91 } 92 } 93 return nil 94 }