github.com/apptainer/singularity@v3.1.1+incompatible/pkg/image/unpacker/squashfs.go (about)

     1  // Copyright (c) 2019, Sylabs Inc. All rights reserved.
     2  // This software is licensed under a 3-clause BSD license. Please consult the
     3  // LICENSE.md file distributed with the sources of this project regarding your
     4  // rights to use or distribute this software.
     5  
     6  package unpacker
     7  
     8  import (
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"os/exec"
    14  )
    15  
    16  // Squashfs represents a squashfs unpacker
    17  type Squashfs struct {
    18  	UnsquashfsPath string
    19  }
    20  
    21  // NewSquashfs initializes and returns a Squahfs unpacker instance
    22  func NewSquashfs() *Squashfs {
    23  	s := &Squashfs{}
    24  	s.UnsquashfsPath, _ = exec.LookPath("unsquashfs")
    25  	return s
    26  }
    27  
    28  // HasUnsquashfs returns if unsquashfs binary has been found or not
    29  func (s *Squashfs) HasUnsquashfs() bool {
    30  	return s.UnsquashfsPath != ""
    31  }
    32  
    33  func (s *Squashfs) extract(files []string, reader io.Reader, dest string) error {
    34  	if !s.HasUnsquashfs() {
    35  		return fmt.Errorf("could not extract squashfs data, unsquashfs not found")
    36  	}
    37  
    38  	// pipe over stdin by default
    39  	stdin := true
    40  	filename := "/proc/self/fd/0"
    41  
    42  	if _, ok := reader.(*os.File); !ok {
    43  		// unsquashfs doesn't support to send file content over
    44  		// a stdin pipe since it use lseek for every read it does
    45  		tmp, err := ioutil.TempFile("", "archive-")
    46  		if err != nil {
    47  			return fmt.Errorf("failed to create staging file: %s", err)
    48  		}
    49  		filename = tmp.Name()
    50  		stdin = false
    51  		defer os.Remove(filename)
    52  
    53  		if _, err := io.Copy(tmp, reader); err != nil {
    54  			return fmt.Errorf("failed to copy content in staging file: %s", err)
    55  		}
    56  		if err := tmp.Close(); err != nil {
    57  			return fmt.Errorf("failed to close staging file: %s", err)
    58  		}
    59  	}
    60  
    61  	args := []string{"-f", "-d", dest, filename}
    62  	for _, f := range files {
    63  		args = append(args, f)
    64  	}
    65  	cmd := exec.Command(s.UnsquashfsPath, args...)
    66  	if stdin {
    67  		cmd.Stdin = reader
    68  	}
    69  	if err := cmd.Run(); err != nil {
    70  		return fmt.Errorf("extract command failed: %s", err)
    71  	}
    72  	return nil
    73  }
    74  
    75  // ExtractAll extracts a squashfs filesystem read from reader to a
    76  // destination directory
    77  func (s *Squashfs) ExtractAll(reader io.Reader, dest string) error {
    78  	return s.extract([]string{}, reader, dest)
    79  }
    80  
    81  // ExtractFiles extracts provided files from a squashfs filesystem
    82  // read from reader to a destination directory
    83  func (s *Squashfs) ExtractFiles(files []string, reader io.Reader, dest string) error {
    84  	if len(files) == 0 {
    85  		return fmt.Errorf("no files to extract")
    86  	}
    87  	return s.extract(files, reader, dest)
    88  }