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  }