github.com/grailbio/base@v0.0.11/embedbin/embedbin.go (about)

     1  // Copyright 2019 GRAIL, Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache 2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  package embedbin
     6  
     7  import (
     8  	"archive/zip"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"os"
    14  	"sync"
    15  )
    16  
    17  var (
    18  	selfOnce sync.Once
    19  	self     *Reader
    20  	selfErr  error
    21  )
    22  
    23  var (
    24  	// ErrNoSuchFile is returned when the embedbin does not contain an
    25  	// embedded file with the requested name.
    26  	ErrNoSuchFile = errors.New("embedded file does not exist")
    27  	// ErrCorruptedImage is returned when the embedbin image has been
    28  	// corrupted.
    29  	ErrCorruptedImage = errors.New("corrupted embedbin image")
    30  )
    31  
    32  // Info provides information for an embedded file.
    33  type Info struct {
    34  	Name string
    35  	Size int64
    36  }
    37  
    38  func (info Info) String() string {
    39  	return fmt.Sprintf("%s: %d", info.Name, info.Size)
    40  }
    41  
    42  // Reader reads images from an embedbin.
    43  type Reader struct {
    44  	base io.ReaderAt
    45  
    46  	embedOffset int64
    47  	embedZ      *zip.Reader
    48  }
    49  
    50  // Self reads the currently executing binary image as an embedbin and
    51  // returns a reader to it.
    52  func Self() (*Reader, error) {
    53  	selfOnce.Do(func() {
    54  		filename, err := os.Executable()
    55  		if err != nil {
    56  			selfErr = err
    57  			return
    58  		}
    59  		f, err := os.Open(filename)
    60  		if err != nil {
    61  			selfErr = err
    62  			return
    63  		}
    64  		info, err := f.Stat()
    65  		if err != nil {
    66  			selfErr = err
    67  			return
    68  		}
    69  		embedOffset, err := Sniff(f, info.Size())
    70  		if err != nil {
    71  			selfErr = err
    72  			return
    73  		}
    74  		self, selfErr = NewReader(f, embedOffset, info.Size())
    75  	})
    76  	return self, selfErr
    77  }
    78  
    79  // OpenFile parses the provided ReaderAt with the provided size. The
    80  // file's contents are parsed to determine the offset of the embedbin's
    81  // archive. OpenFile returns an error if the file is not an embedbin.
    82  func OpenFile(r io.ReaderAt, size int64) (*Reader, error) {
    83  	offset, err := Sniff(r, size)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	return NewReader(r, offset, size)
    88  }
    89  
    90  // NewReader returns a new embedbin reader from the provided reader.
    91  func NewReader(r io.ReaderAt, embedOffset, totalSize int64) (*Reader, error) {
    92  	rd := &Reader{
    93  		base:        io.NewSectionReader(r, 0, embedOffset),
    94  		embedOffset: embedOffset,
    95  	}
    96  	if embedOffset == totalSize {
    97  		return rd, nil
    98  	}
    99  	var err error
   100  	rd.embedZ, err = zip.NewReader(io.NewSectionReader(r, embedOffset, totalSize-embedOffset), totalSize-embedOffset)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	return rd, nil
   105  }
   106  
   107  // List returns information about embedded files.
   108  func (r *Reader) List() []Info {
   109  	if r.embedZ == nil {
   110  		return nil
   111  	}
   112  	infos := make([]Info, len(r.embedZ.File))
   113  	for i, f := range r.embedZ.File {
   114  		infos[i] = Info{
   115  			Name: f.Name,
   116  			Size: int64(f.UncompressedSize64),
   117  		}
   118  	}
   119  	return infos
   120  }
   121  
   122  // Open returns a ReadCloser for the original executable, without appended
   123  // embedded files.
   124  func (r *Reader) OpenBase() (io.ReadCloser, error) {
   125  	return ioutil.NopCloser(io.NewSectionReader(r.base, 0, 1<<63-1)), nil
   126  }
   127  
   128  // Open returns a ReadCloser for the named embedded file.
   129  // Open returns ErrNoSuchImage if the embedbin does not contain the file.
   130  func (r *Reader) Open(name string) (io.ReadCloser, error) {
   131  	if r.embedZ == nil {
   132  		return nil, ErrNoSuchFile
   133  	}
   134  	for _, f := range r.embedZ.File {
   135  		if f.Name == name {
   136  			return f.Open()
   137  		}
   138  	}
   139  	return nil, ErrNoSuchFile
   140  }
   141  
   142  // StatBase returns the information for the base image.
   143  func (r *Reader) StatBase() Info {
   144  	return Info{Size: r.embedOffset}
   145  }
   146  
   147  // Stat returns the information for the named embedded file.
   148  // It returns a boolean indicating whether the requested file was found.
   149  func (r *Reader) Stat(name string) (info Info, ok bool) {
   150  	info.Name = name
   151  	for _, f := range r.embedZ.File {
   152  		if f.Name == name {
   153  			info.Size = int64(f.UncompressedSize64)
   154  			ok = true
   155  			return
   156  		}
   157  	}
   158  	return
   159  }
   160  
   161  // Sniff sniffs a binary's embedbin offset. Sniff returns errors
   162  // returned by the provided reader, or ErrCorruptedImage if the binary is identified
   163  // as an embedbin image with a checksum mismatch.
   164  func Sniff(r io.ReaderAt, size int64) (offset int64, err error) {
   165  	offset, err = readFooter(r, size)
   166  	if err == errNoFooter {
   167  		err = nil
   168  		offset = size
   169  	}
   170  	return
   171  }