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 }