github.com/jaypipes/ghw@v0.21.1/pkg/snapshot/unpack.go (about)

     1  //
     2  // Use and distribution licensed under the Apache license version 2.
     3  //
     4  // See the COPYING file in the root project directory for full text.
     5  //
     6  
     7  package snapshot
     8  
     9  import (
    10  	"archive/tar"
    11  	"compress/gzip"
    12  	"io"
    13  	"os"
    14  	"path/filepath"
    15  
    16  	"github.com/jaypipes/ghw/pkg/option"
    17  )
    18  
    19  const (
    20  	TargetRoot = "ghw-snapshot-*"
    21  )
    22  
    23  const (
    24  	// If set, `ghw` will not unpack the snapshot in the user-supplied directory
    25  	// unless the aforementioned directory is empty.
    26  	OwnTargetDirectory = 1 << iota
    27  )
    28  
    29  // Clanup removes the unpacket snapshot from the target root.
    30  // Please not that the environs variable `GHW_SNAPSHOT_PRESERVE`, if set,
    31  // will make this function silently skip.
    32  func Cleanup(targetRoot string) error {
    33  	if option.EnvOrDefaultSnapshotPreserve() {
    34  		return nil
    35  	}
    36  	return os.RemoveAll(targetRoot)
    37  }
    38  
    39  // Unpack expands the given snapshot in a temporary directory managed by `ghw`. Returns the path of that directory.
    40  func Unpack(snapshotName string) (string, error) {
    41  	targetRoot, err := os.MkdirTemp("", TargetRoot)
    42  	if err != nil {
    43  		return "", err
    44  	}
    45  	_, err = UnpackInto(snapshotName, targetRoot, 0)
    46  	return targetRoot, err
    47  }
    48  
    49  // UnpackInto expands the given snapshot in a client-supplied directory.
    50  // Returns true if the snapshot was actually unpacked, false otherwise
    51  func UnpackInto(snapshotName, targetRoot string, flags uint) (bool, error) {
    52  	if (flags&OwnTargetDirectory) == OwnTargetDirectory && !isEmptyDir(targetRoot) {
    53  		return false, nil
    54  	}
    55  	snap, err := os.Open(snapshotName)
    56  	if err != nil {
    57  		return false, err
    58  	}
    59  	defer snap.Close()
    60  	return true, Untar(targetRoot, snap)
    61  }
    62  
    63  // Untar extracts data from the given reader (providing data in tar.gz format) and unpacks it in the given directory.
    64  func Untar(root string, r io.Reader) error {
    65  	var err error
    66  	gzr, err := gzip.NewReader(r)
    67  	if err != nil {
    68  		return err
    69  	}
    70  	defer gzr.Close()
    71  
    72  	tr := tar.NewReader(gzr)
    73  	for {
    74  		header, err := tr.Next()
    75  		if err == io.EOF {
    76  			// we are done
    77  			return nil
    78  		}
    79  
    80  		if err != nil {
    81  			// bail out
    82  			return err
    83  		}
    84  
    85  		if header == nil {
    86  			// TODO: how come?
    87  			continue
    88  		}
    89  
    90  		target := filepath.Join(root, header.Name)
    91  		mode := os.FileMode(header.Mode)
    92  
    93  		switch header.Typeflag {
    94  		case tar.TypeDir:
    95  			err = os.MkdirAll(target, mode)
    96  			if err != nil {
    97  				return err
    98  			}
    99  
   100  		case tar.TypeReg:
   101  			dst, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, mode)
   102  			if err != nil {
   103  				return err
   104  			}
   105  
   106  			_, err = io.Copy(dst, tr)
   107  			if err != nil {
   108  				return err
   109  			}
   110  
   111  			dst.Close()
   112  
   113  		case tar.TypeSymlink:
   114  			err = os.Symlink(header.Linkname, target)
   115  			if err != nil {
   116  				return err
   117  			}
   118  		}
   119  	}
   120  }
   121  
   122  func isEmptyDir(name string) bool {
   123  	entries, err := os.ReadDir(name)
   124  	if err != nil {
   125  		return false
   126  	}
   127  	return len(entries) == 0
   128  }