Compare commits

...

15 Commits

Author SHA1 Message Date
Caleb Gardner e8a8c531a9 Tweaks to make FromReader work 2021-09-27 01:27:58 -05:00
Caleb Gardner 80ff4466ae Added new test 2021-09-26 22:36:30 -05:00
Caleb Gardner 64055a8a63 Improved testing 2021-09-26 19:10:43 -05:00
Caleb Gardner 0402b0a2ee Bringing rawreader from expiremental branch.
Now allows creation of a squashfs.Reader from an io.Reader
2021-09-26 18:30:08 -05:00
Caleb Gardner 305f261d10 Add Lzo decompressor and Xz decompressor with filters 2021-09-12 05:26:47 -05:00
Caleb Gardner 70e3d81427 Some musings on what to do. 2021-04-30 03:32:31 -05:00
Caleb Gardner 6ad6857d8d Renamed files to make them more clear
Trying to figure out how to write. Might have to keep tables uncompressed for now.
2021-04-30 02:52:27 -05:00
Caleb Gardner 7cf15d48c7 Getting back into it. Maybe. 2021-04-04 09:39:28 -05:00
Caleb Gardner 28f39cf315 Updated libraries.
Replaced builtin zlib with faster library.
2021-04-03 10:14:12 -05:00
Caleb Gardner 7f5fa3ba1f Some quick fixes for "correctness" 2021-04-03 01:17:09 -05:00
Caleb Gardner 2a8310a724 Updated README 2021-02-27 01:58:01 -06:00
Caleb Gardner 8ad4d30bc7 Merge branch 'main' of https://github.com/CalebQ42/squashfs 2021-02-27 00:50:40 -06:00
Caleb Gardner b6fbd63ba4 Makes sure folders are created when files are added
Added some comments
2021-02-27 00:49:40 -06:00
Caleb Gardner 9913b848c6 Added some data writing logic. 2021-02-26 01:53:16 -06:00
Caleb Gardner 65bc4a5d78 More work on fragments. 2021-02-25 06:22:57 -06:00
23 changed files with 707 additions and 810 deletions
+15 -5
View File
@@ -4,15 +4,25 @@
A PURE Go library to read and write squashfs.
Currently has support for reading squashfs files and extracting files and folders. Supports all compression types except LZO, but additional compression options are hit or miss.
The only major thing missing from squashfs reading is Xattr parsing.
Currently has support for reading squashfs files and extracting files and folders.
Special thanks to <https://dr-emann.github.io/squashfs/> for some VERY important information in an easy to understand format.
Thanks also to [distri's squashfs library](https://github.com/distr1/distri/tree/master/internal/squashfs) as I referenced it to figure some things out (and double check others).
## [TODO](https://github.com/CalebQ42/squashfs/projects/1?fullscreen=true)
## Limitations
This library is pure Go (including external libraries) which can cause some issues, which are listed below. Right now this library is also not feature complete, so check out the TODO list above for what I'm still planning on adding.
* No LZO compression. This is purely due to a lack of a good LZO pure golang library. If one is made, it would be a simple job to add it in.
* GZIP Compression
* Strategies might or might not work.
* Custom window sizes might or might not work.
* XZ Compression
* LZMA executable filters are NOT supported.
* No Xattr parsing. This is simply because I haven't done any research on it and how to apply these in a pure go way.
## Performane
This library, decompressing the Firefox AppImage and using go tests, takes about twice as long as `unsquashfs` on my quad core laptop. (~1 second with the library and about half a second with `unsquashfs`)
## [TODO](https://github.com/CalebQ42/squashfs/projects/1?fullscreen=true)
-63
View File
@@ -1,63 +0,0 @@
package squashfs
import (
"io"
)
type bufferedBytes struct {
data []byte
r offsetRange
}
type offsetRange struct {
beg int
end int
}
func (o *offsetRange) offset(off int) {
o.beg += off
o.end += off
}
func (o offsetRange) within(check int) bool {
return check >= o.beg || check <= o.end
}
type bufferedWriter struct {
w io.Writer
buffer []bufferedBytes
mainOffset int
}
func newBufferedWriter(w io.Writer) *bufferedWriter {
var out bufferedWriter
out.w = w
return &out
}
func (b *bufferedWriter) WriteTo(data []byte, offset int64) (n int, err error) {
if int(offset) == b.mainOffset {
n, err = b.Write(data)
if err != nil {
return
}
}
newBuff := bufferedBytes{
data: data,
r: offsetRange{
beg: int(offset),
end: int(offset) + len(data),
},
}
b.buffer = append(b.buffer, newBuff)
return 0, nil
}
func (b *bufferedWriter) Write(data []byte) (int, error) {
n, err := b.w.Write(data)
b.mainOffset += n
if err != nil {
return n, err
}
return n, err
}
+2 -2
View File
@@ -12,7 +12,7 @@ import (
type fragmentEntry struct {
Start uint64
Size uint32
// Unused uint32
_ uint32 //unused
}
//GetFragmentDataFromInode returns the fragment data for a given inode.
@@ -46,7 +46,7 @@ func (r *Reader) getFragmentDataFromInode(in *inode.Inode) ([]byte, error) {
fragIndex = bf.FragmentIndex
fragOffset = bf.FragmentOffset
} else {
return nil, errors.New("Inode type not supported")
return nil, errors.New("inode type not supported")
}
//reading the fragment entry first
fragEntryRdr, err := r.newMetadataReader(int64(r.fragOffsets[int(fragIndex/512)]))
+15 -11
View File
@@ -1,19 +1,23 @@
module github.com/CalebQ42/squashfs
go 1.15
go 1.17
require (
github.com/CalebQ42/GoAppImage v0.5.0
github.com/adrg/xdg v0.2.3 // indirect
github.com/google/go-cmp v0.5.4 // indirect
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
github.com/klauspost/compress v1.11.6
github.com/kr/text v0.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.3
github.com/adrg/xdg v0.3.3 // indirect
github.com/gopherjs/gopherjs v0.0.0-20210420193930-a4630ec28c79 // indirect
github.com/klauspost/compress v1.12.2
github.com/pierrec/lz4/v4 v4.1.6
github.com/smartystreets/assertions v1.2.0 // indirect
github.com/stretchr/testify v1.7.0 // indirect
github.com/ulikunitz/xz v0.5.9
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
github.com/ulikunitz/xz v0.5.10
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)
require (
github.com/golang/snappy v0.0.3 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e
github.com/therootcompany/xz v1.0.1
go.lsp.dev/uri v0.3.0 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
)
+308 -36
View File
@@ -1,76 +1,348 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/CalebQ42/GoAppImage v0.5.0 h1:znoKNXtliH754tS9sYwyOIg/0wFDjFN5Twc7PAh1rSM=
github.com/CalebQ42/GoAppImage v0.5.0/go.mod h1:qHudJKAn/dlkNWNnH4h1YKXp29EZ7Bppsn7sNP2HuvU=
github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
github.com/adrg/xdg v0.2.3 h1:GxXngdYxNDkoUvZXjNJGwqZxWXi43MKbOOlA/00qZi4=
github.com/adrg/xdg v0.2.3/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/adrg/xdg v0.3.3 h1:s/tV7MdqQnzB1nKY8aqHvAMD+uCiuEDzVB5HLRY849U=
github.com/adrg/xdg v0.3.3/go.mod h1:61xAR2VZcggl2St4O9ohF5qCKe08+JDmE4VNzPFQvOQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20210420193930-a4630ec28c79 h1:ATVz3rDvK4xX0nHx57zYSHRVIK/+lFwln9KJr8wvuk0=
github.com/gopherjs/gopherjs v0.0.0-20210420193930-a4630ec28c79/go.mod h1:Opf9rtYVq0eTyX+aRVmRO9hE8ERAozcdrBxWG9Q6mkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/compress v1.11.6 h1:EgWPCW6O3n1D5n99Zq3xXBt9uCwRGvpwGOusOLNBRSQ=
github.com/klauspost/compress v1.11.6/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8=
github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pierrec/lz4/v4 v4.1.3 h1:/dvQpkb0o1pVlSgKNQqfkavlnXaIK+hJ0LXsKRUN9D4=
github.com/pierrec/lz4/v4 v4.1.3/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pierrec/lz4/v4 v4.1.6 h1:ueMTcBBFrbT8K4uGDNNZPa8Z7LtPV7Cl0TDjaeHxP44=
github.com/pierrec/lz4/v4 v4.1.6/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e h1:dCWirM5F3wMY+cmRda/B1BiPsFtmzXqV9b0hLWtVBMs=
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e/go.mod h1:9leZcVcItj6m9/CfHY5Em/iBrCz7js8LcRQGTKEEv2M=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo=
go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+2 -1
View File
@@ -2,9 +2,10 @@ package compression
import (
"bytes"
"compress/zlib"
"encoding/binary"
"io"
"github.com/klauspost/compress/zlib"
)
type gzipInit struct {
+30
View File
@@ -0,0 +1,30 @@
package compression
import (
"encoding/binary"
"io"
lzo "github.com/rasky/go-lzo"
)
type Lzo struct {
Algorithm int32
Level int32
}
func NewLzoCompressorWithOptions(rdr io.Reader) (*Lzo, error) {
var lz Lzo
err := binary.Read(rdr, binary.LittleEndian, &lz)
if err != nil {
return nil, err
}
return &lz, nil
}
func (l Lzo) Decompress(rdr io.Reader) ([]byte, error) {
byt, err := lzo.Decompress1X(rdr, 0, 0)
if err != nil {
return nil, err
}
return byt, nil
}
+7 -22
View File
@@ -5,44 +5,29 @@ import (
"encoding/binary"
"io"
"github.com/ulikunitz/xz"
"github.com/therootcompany/xz"
wrtXz "github.com/ulikunitz/xz"
)
type xzInit struct {
DictionarySize int32
Filters int32
}
//Xz is a Xz decompressor.
type Xz struct {
DictionarySize int32
HasFilters bool
Filters int32
}
//NewXzCompressorWithOptions creates a new Xz compressor/decompressor that reads the compressor options from the given reader.
func NewXzCompressorWithOptions(rdr io.Reader) (*Xz, error) {
var x Xz
var init xzInit
err := binary.Read(rdr, binary.LittleEndian, &init)
err := binary.Read(rdr, binary.LittleEndian, &x)
if err != nil {
return nil, err
}
x.DictionarySize = init.DictionarySize
//TODO: When I can do filters, parse the filters
if init.Filters != 0 {
x.HasFilters = true
}
return &x, nil
}
//Decompress decompresses all the data from the rdr and returns the uncompressed bytes.
func (x *Xz) Decompress(rdr io.Reader) ([]byte, error) {
r, err := xz.NewReader(rdr)
if err != nil {
return nil, err
}
r.DictCap = int(x.DictionarySize)
err = r.Verify()
r, err := xz.NewReader(rdr, 0)
if err != nil {
return nil, err
}
@@ -57,7 +42,7 @@ func (x *Xz) Decompress(rdr io.Reader) ([]byte, error) {
//Compress implements compression.Compress
func (x *Xz) Compress(data []byte) ([]byte, error) {
var buf bytes.Buffer
w, err := xz.NewWriter(&buf)
w, err := wrtXz.NewWriter(&buf)
if err != nil {
return nil, err
}
+1 -1
View File
@@ -11,8 +11,8 @@ import (
//
//Info holds the actual Inode. Due to each inode type being a different type, it's store as an interface{}
type Inode struct {
Info interface{} //Info is the parsed specific data. It's type is defined by Type.
Header
Info interface{} //Info is the parsed specific data. It's type is defined by Type.
}
//ProcessInode tries to read an inode from the BlockReader
+135
View File
@@ -0,0 +1,135 @@
package rawreader
import (
"bytes"
"errors"
"io"
)
func ConvertReader(r io.Reader) (RawReader, error) {
if rr, ok := r.(RawReader); ok {
return rr, nil
}
if rs, is := r.(io.ReadSeeker); is {
return &fromReadSeeker{
ReadSeeker: rs,
}, nil
}
buf := new(bytes.Buffer)
_, err := io.Copy(buf, r)
if err != nil {
return nil, err
}
return &fromReader{
data: buf.Bytes(),
}, nil
}
func ConvertReaderAt(r io.ReaderAt) RawReader {
if rr, ok := r.(RawReader); ok {
return rr
}
return &fromReaderAt{
ReaderAt: r,
}
}
//TODO: Add way to discard data from fromReader
//RawReader implements the needed interfaces for reading a squashfs archive.
type RawReader interface {
io.ReadSeeker
io.ReaderAt
}
type fromReader struct {
data []byte
off int
}
func (r *fromReader) ReadAt(p []byte, off int64) (n int, err error) {
n = len(p)
if int(off)+len(p) > len(r.data) {
n = len(r.data) - int(off)
err = io.EOF
}
if n < 0 {
n = 0
}
for i := 0; i < n; i++ {
p[i] = r.data[int(off)+i]
}
return
}
func (r *fromReader) Seek(off int64, whence int) (n int64, err error) {
switch whence {
case io.SeekEnd:
r.off = len(r.data) - int(off)
if r.off < 0 {
r.off = 0
err = io.EOF
}
case io.SeekCurrent:
r.off += int(off)
case io.SeekStart:
r.off = int(off)
}
if r.off > len(r.data) {
r.off = len(r.data)
return int64(r.off), io.EOF
}
return int64(r.off), err
}
func (r *fromReader) Read(p []byte) (n int, err error) {
n = len(p)
if r.off+len(p) > len(r.data) {
n = len(r.data) - r.off
err = io.EOF
}
if n < 0 {
n = 0
}
for i := 0; i < n; i++ {
p[i] = r.data[r.off+i]
}
return
}
type fromReadSeeker struct {
io.ReadSeeker
}
func (r *fromReadSeeker) ReadAt(p []byte, off int64) (n int, err error) {
tmp, _ := r.Seek(0, io.SeekCurrent)
defer r.Seek(tmp, io.SeekStart)
_, err = r.Seek(off, io.SeekStart)
if err != nil {
return
}
return r.Read(p)
}
type fromReaderAt struct {
io.ReaderAt
off int
}
func (r *fromReaderAt) Read(p []byte) (n int, err error) {
n, err = r.ReadAt(p, int64(r.off))
r.off += n
return
}
func (r *fromReaderAt) Seek(off int64, whence int) (n int64, err error) {
switch whence {
case io.SeekEnd:
return 0, errors.New("cannot SeekEnd RawReader")
case io.SeekCurrent:
r.off += int(off)
case io.SeekStart:
r.off = int(off)
}
return int64(r.off), nil
}
+2 -2
View File
@@ -133,7 +133,7 @@ func (br *metadataReader) Read(p []byte) (int, error) {
}
br.readOffset += read
if read != len(p) {
return read, errors.New("Didn't read enough data")
return read, errors.New("didn't read enough data")
}
return read, nil
}
@@ -181,7 +181,7 @@ func (br *metadataReader) Seek(offset int64, whence int) (int64, error) {
br.readOffset = len(br.data) - int(offset)
if br.readOffset < 0 {
br.readOffset = 0
return int64(br.readOffset), errors.New("Trying to seek to a negative value")
return int64(br.readOffset), errors.New("trying to seek to a negative value")
}
}
return int64(br.readOffset), nil
+95 -72
View File
@@ -9,6 +9,7 @@ import (
"github.com/CalebQ42/squashfs/internal/compression"
"github.com/CalebQ42/squashfs/internal/inode"
"github.com/CalebQ42/squashfs/internal/rawreader"
)
const (
@@ -17,23 +18,16 @@ const (
var (
//ErrNoMagic is returned if the magic number in the superblock isn't correct.
errNoMagic = errors.New("Magic number doesn't match. Either isn't a squashfs or corrupted")
errNoMagic = errors.New("magic number doesn't match. Either isn't a squashfs or corrupted")
//ErrIncompatibleCompression is returned if the compression type in the superblock doesn't work.
errIncompatibleCompression = errors.New("Compression type unsupported")
//ErrCompressorOptions is returned if compressor options is present. It's not currently supported.
errCompressorOptions = errors.New("Compressor options is not currently supported")
//ErrOptions is returned when compression options that I haven't tested is set. When this is returned, the Reader is also returned.
ErrOptions = errors.New("Possibly incompatible compressor options")
errIncompatibleCompression = errors.New("compression type unsupported")
)
//TODO: implement fs.FS, possibly more FS types for compatibility. Most of this work will mostly be handed off to root anyway so this shouldn't be too difficult.
//Reader processes and reads a squashfs archive.
type Reader struct {
FS
r *io.SectionReader
r rawreader.RawReader
decompressor compression.Decompressor
root *File
fragOffsets []uint64
idTable []uint32
super superblock
@@ -43,134 +37,163 @@ type Reader struct {
//NewSquashfsReader returns a new squashfs.Reader from an io.ReaderAt
func NewSquashfsReader(r io.ReaderAt) (*Reader, error) {
var rdr Reader
err := binary.Read(io.NewSectionReader(r, 0, int64(binary.Size(rdr.super))), binary.LittleEndian, &rdr.super)
rdr.r = rawreader.ConvertReaderAt(r)
err := rdr.Init()
if err != nil {
return nil, err
}
rdr.r = io.NewSectionReader(r, 0, int64(rdr.super.BytesUsed))
if rdr.super.Magic != magic {
return nil, errNoMagic
return &rdr, nil
}
//NewSquashfsReaderFromReader returns a new squashfs.Reader from an io.Reader.
//If the io.Reader implements io.Seeker, the seek functions are used.
//It is NOT recommended to use a pure io.Reader as due to how squashfs
//archives are formatted, the ENTIRETY of the io.Reader's data is loaded into
//memory first before it can be used.
func NewSquashfsReaderFromReader(r io.Reader) (*Reader, error) {
var rdr Reader
var err error
rdr.r, err = rawreader.ConvertReader(r)
if err != nil {
return nil, err
}
if rdr.super.BlockLog != uint16(math.Log2(float64(rdr.super.BlockSize))) {
return nil, errors.New("BlockSize and BlockLog doesn't match. The archive is probably corrupt")
err = rdr.Init()
if err != nil {
return nil, err
}
rdr.r.Seek(96, io.SeekStart)
hasUnsupportedOptions := false
rdr.flags = rdr.super.GetFlags()
if rdr.flags.compressorOptions {
switch rdr.super.CompressionType {
return &rdr, nil
}
func (r *Reader) Init() error {
err := binary.Read(r.r, binary.LittleEndian, &r.super)
if err != nil {
return err
}
if r.super.Magic != magic {
return errNoMagic
}
if r.super.BlockLog != uint16(math.Log2(float64(r.super.BlockSize))) {
return errors.New("BlockSize and BlockLog doesn't match. The archive is probably corrupt")
}
r.r.Seek(96, io.SeekStart)
r.flags = r.super.GetFlags()
if r.flags.compressorOptions {
switch r.super.CompressionType {
case GzipCompression:
var gzip *compression.Gzip
gzip, err = compression.NewGzipCompressorWithOptions(rdr.r)
gzip, err = compression.NewGzipCompressorWithOptions(r.r)
if err != nil {
return nil, err
return err
}
if gzip.HasCustomWindow || gzip.HasStrategies {
hasUnsupportedOptions = true
}
rdr.decompressor = gzip
r.decompressor = gzip
case XzCompression:
var xz *compression.Xz
xz, err = compression.NewXzCompressorWithOptions(rdr.r)
xz, err = compression.NewXzCompressorWithOptions(r.r)
if err != nil {
return nil, err
return err
}
if xz.HasFilters {
return nil, errors.New("XZ compression options has filters. These are not yet supported")
r.decompressor = xz
case LzoCompression:
var lz *compression.Lzo
lz, err = compression.NewLzoCompressorWithOptions(r.r)
if err != nil {
return err
}
rdr.decompressor = xz
r.decompressor = lz
case Lz4Compression:
var lz4 *compression.Lz4
lz4, err = compression.NewLz4CompressorWithOptions(rdr.r)
lz4, err = compression.NewLz4CompressorWithOptions(r.r)
if err != nil {
return nil, err
return err
}
rdr.decompressor = lz4
r.decompressor = lz4
case ZstdCompression:
var zstd *compression.Zstd
zstd, err = compression.NewZstdCompressorWithOptions(rdr.r)
zstd, err = compression.NewZstdCompressorWithOptions(r.r)
if err != nil {
return nil, err
return err
}
rdr.decompressor = zstd
r.decompressor = zstd
default:
return nil, errIncompatibleCompression
return errIncompatibleCompression
}
} else {
switch rdr.super.CompressionType {
switch r.super.CompressionType {
case GzipCompression:
rdr.decompressor = &compression.Gzip{}
r.decompressor = &compression.Gzip{}
case LzmaCompression:
rdr.decompressor = &compression.Lzma{}
r.decompressor = &compression.Lzma{}
case LzoCompression:
r.decompressor = &compression.Lzo{}
case XzCompression:
rdr.decompressor = &compression.Xz{}
r.decompressor = &compression.Xz{}
case Lz4Compression:
rdr.decompressor = &compression.Lz4{}
r.decompressor = &compression.Lz4{}
case ZstdCompression:
rdr.decompressor = &compression.Zstd{}
r.decompressor = &compression.Zstd{}
default:
//TODO: all compression types.
return nil, errIncompatibleCompression
return errIncompatibleCompression
}
}
fragBlocks := int(math.Ceil(float64(rdr.super.FragCount) / 512))
fragBlocks := int(math.Ceil(float64(r.super.FragCount) / 512))
if fragBlocks > 0 {
offset := int64(rdr.super.FragTableStart)
offset := int64(r.super.FragTableStart)
for i := 0; i < fragBlocks; i++ {
tmp := make([]byte, 8)
_, err = r.ReadAt(tmp, offset)
_, err = r.r.ReadAt(tmp, offset)
if err != nil {
return nil, err
return err
}
rdr.fragOffsets = append(rdr.fragOffsets, binary.LittleEndian.Uint64(tmp))
r.fragOffsets = append(r.fragOffsets, binary.LittleEndian.Uint64(tmp))
offset += 8
}
}
unread := rdr.super.IDCount
blockOffsets := make([]uint64, int(math.Ceil(float64(rdr.super.IDCount)/2048)))
rdr.r.Seek(int64(rdr.super.IDTableStart), io.SeekStart)
unread := r.super.IDCount
blockOffsets := make([]uint64, int(math.Ceil(float64(r.super.IDCount)/2048)))
_, err = r.r.Seek(int64(r.super.IDTableStart), io.SeekStart)
if err != nil {
return err
}
for i := range blockOffsets {
err = binary.Read(rdr.r, binary.LittleEndian, &blockOffsets[i])
err = binary.Read(r.r, binary.LittleEndian, &blockOffsets[i])
if err != nil {
return nil, err
return err
}
var idRdr *metadataReader
idRdr, err = rdr.newMetadataReader(int64(blockOffsets[i]))
idRdr, err = r.newMetadataReader(int64(blockOffsets[i]))
if err != nil {
return nil, err
return err
}
read := uint16(math.Min(float64(unread), 2048))
for i := uint16(0); i < read; i++ {
var tmp uint32
err = binary.Read(idRdr, binary.LittleEndian, &tmp)
if err != nil {
return nil, err
return err
}
rdr.idTable = append(rdr.idTable, tmp)
r.idTable = append(r.idTable, tmp)
}
unread -= read
}
metaRdr, err := rdr.newMetadataReaderFromInodeRef(rdr.super.RootInodeRef)
metaRdr, err := r.newMetadataReaderFromInodeRef(r.super.RootInodeRef)
if err != nil {
return nil, err
return err
}
i, err := inode.ProcessInode(metaRdr, rdr.super.BlockSize)
i, err := inode.ProcessInode(metaRdr, r.super.BlockSize)
if err != nil {
return nil, err
return err
}
entries, err := rdr.readDirFromInode(i)
entries, err := r.readDirFromInode(i)
if err != nil {
return nil, err
return err
}
rdr.FS = FS{
r: &rdr,
r.FS = FS{
r: r,
name: "/",
entries: entries,
}
if hasUnsupportedOptions {
return &rdr, ErrOptions
}
return &rdr, nil
return nil
}
//ModTime is the last time the file was modified/created.
+5 -10
View File
@@ -10,9 +10,9 @@ import (
var (
//ErrInodeNotFile is given when giving an inode, but the function requires a file inode.
errInodeNotFile = errors.New("Given inode is NOT a file type")
errInodeNotFile = errors.New("given inode is NOT a file type")
//ErrInodeOnlyFragment is given when trying to make a DataReader from an inode, but the inode only had data in a fragment
errInodeOnlyFragment = errors.New("Given inode ONLY has fragment data")
errInodeOnlyFragment = errors.New("given inode ONLY has fragment data")
)
//DataReader reads data from data blocks.
@@ -49,9 +49,7 @@ func (r *Reader) newDataReaderFromInode(i *inode.Inode) (*dataReader, error) {
return nil, errInodeOnlyFragment
}
rdr.offset = int64(fil.BlockStart)
for _, sizes := range fil.BlockSizes {
rdr.sizes = append(rdr.sizes, sizes)
}
rdr.sizes = append(rdr.sizes, fil.BlockSizes...)
if fil.Fragmented {
rdr.sizes = rdr.sizes[:len(rdr.sizes)-1]
}
@@ -61,9 +59,7 @@ func (r *Reader) newDataReaderFromInode(i *inode.Inode) (*dataReader, error) {
return nil, errInodeOnlyFragment
}
rdr.offset = int64(fil.BlockStart)
for _, sizes := range fil.BlockSizes {
rdr.sizes = append(rdr.sizes, sizes)
}
rdr.sizes = append(rdr.sizes, fil.BlockSizes...)
if fil.Fragmented {
rdr.sizes = rdr.sizes[:len(rdr.sizes)-1]
}
@@ -173,7 +169,7 @@ func (d *dataReader) Read(p []byte) (int, error) {
}
}
if read != len(p) {
return read, errors.New("Didn't read enough data")
return read, errors.New("didn't read enough data")
}
return read, nil
}
@@ -199,7 +195,6 @@ func (d *dataReader) WriteTo(w io.Writer) (int64, error) {
return
}
cache.data = data
return
}(i, dataChan)
}
curIndex := 0
+8 -6
View File
@@ -78,7 +78,7 @@ func (f File) Read(p []byte) (int, error) {
}
return f.reader.Read(p)
}
return 0, errors.New("Can only read files")
return 0, errors.New("can only read files")
}
//WriteTo writes all data from the file to the writer. This is multi-threaded.
@@ -89,7 +89,7 @@ func (f File) WriteTo(w io.Writer) (int64, error) {
}
return f.reader.WriteTo(w)
}
return 0, errors.New("Can only read files")
return 0, errors.New("can only read files")
}
//Close simply nils the underlying reader. Here mostly to satisfy fs.File
@@ -236,6 +236,9 @@ func (f File) ExtractWithOptions(folder string, op ExtractionOptions) error {
}
}
stat, err := f.Stat()
if err != nil {
return err
}
if f.IsDir() {
if op.notBase {
err = os.Mkdir(folder+"/"+f.name, stat.Mode())
@@ -264,7 +267,6 @@ func (f File) ExtractWithOptions(folder string, op ExtractionOptions) error {
}
errChan <- fil.ExtractWithOptions(folder+"/"+f.name, op)
fil.Close()
return
}(ents[i].(*DirEntry))
}
for i := 0; i < len(ents); i++ {
@@ -301,7 +303,7 @@ func (f File) ExtractWithOptions(folder string, op ExtractionOptions) error {
if op.Verbose {
log.Println("Symlink path(", symPath, ") is unobtainable:", folder+"/"+f.name)
}
return errors.New("Cannot get symlink target")
return errors.New("cannot get symlink target")
}
fil.name = f.name
err = fil.ExtractWithOptions(folder, op)
@@ -318,7 +320,7 @@ func (f File) ExtractWithOptions(folder string, op ExtractionOptions) error {
if op.Verbose {
log.Println("Symlink path(", symPath, ") is unobtainable:", folder+"/"+f.name)
}
return errors.New("Cannot get symlink target")
return errors.New("cannot get symlink target")
}
extractLoc := path.Clean(folder + "/" + path.Dir(symPath))
err = fil.ExtractWithOptions(extractLoc, op)
@@ -361,7 +363,7 @@ func (r *Reader) readDirFromInode(i *inode.Inode) ([]*directory.Entry, error) {
metaOffset = i.Info.(inode.ExtDir).DirectoryOffset
size = i.Info.(inode.ExtDir).DirectorySize
default:
return nil, errors.New("Not a directory inode")
return nil, errors.New("not a directory inode")
}
br, err := r.newMetadataReader(int64(r.super.DirTableStart + uint64(offset)))
if err != nil {
+7 -1
View File
@@ -22,7 +22,7 @@ type fileReader struct {
var (
//ErrPathIsNotFile returns when trying to read from a file, but the given path is NOT a file.
errPathIsNotFile = errors.New("The given path is not a file")
errPathIsNotFile = errors.New("the given path is not a file")
)
//ReadFile provides a squashfs.FileReader for the file at the given location.
@@ -53,6 +53,9 @@ func (r *Reader) newFileReader(in *inode.Inode) (*fileReader, error) {
}
if !rdr.fragOnly {
rdr.data, err = r.newDataReaderFromInode(in)
if err != nil {
return nil, err
}
}
return &rdr, nil
}
@@ -72,6 +75,9 @@ func (f *fileReader) Read(p []byte) (int, error) {
if f.fragged && err == io.EOF {
if f.fragmentData == nil {
f.fragmentData, err = f.r.getFragmentDataFromInode(f.in)
if err != nil {
return read, err
}
}
n, err = bytes.NewBuffer(f.fragmentData).Read(p[read:])
read += n
+10 -6
View File
@@ -39,7 +39,7 @@ func (f FS) Open(name string) (fs.File, error) {
Op: "open",
Path: name,
//TODO: make error clearer
Err: errors.New("Trying to get file outside of squashfs"),
Err: errors.New("trying to get file outside of squashfs"),
}
}
return f.parent.Open(strings.Join(split[1:], "/"))
@@ -107,7 +107,7 @@ func (f FS) Glob(pattern string) (out []string, err error) {
Op: "readdir",
Path: pattern,
//TODO: make error clearer
Err: errors.New("Trying to get file outside of squashfs"),
Err: errors.New("trying to get file outside of squashfs"),
}
}
return f.parent.Glob(strings.Join(split[1:], "/"))
@@ -178,7 +178,7 @@ func (f FS) ReadDir(name string) ([]fs.DirEntry, error) {
Op: "readdir",
Path: name,
//TODO: make error clearer
Err: errors.New("Trying to get file outside of squashfs"),
Err: errors.New("trying to get file outside of squashfs"),
}
}
return f.parent.ReadDir(strings.Join(split[1:], "/"))
@@ -294,7 +294,7 @@ func (f FS) Stat(name string) (fs.FileInfo, error) {
Op: "stat",
Path: name,
//TODO: make error clearer
Err: errors.New("Trying to get file outside of squashfs"),
Err: errors.New("trying to get file outside of squashfs"),
}
}
return f.parent.Stat(strings.Join(split[1:], "/"))
@@ -377,7 +377,7 @@ func (f FS) Sub(dir string) (fs.FS, error) {
Op: "sub",
Path: dir,
//TODO: make error clearer
Err: errors.New("Trying to get file outside of squashfs"),
Err: errors.New("trying to get file outside of squashfs"),
}
}
return f.parent.Sub(strings.Join(split[1:], "/"))
@@ -435,6 +435,11 @@ func (f FS) Sub(dir string) (fs.FS, error) {
}
func (f FS) path() string {
if f.name == "/" {
return f.name
} else if f.parent.name == "/" {
return f.name
}
return f.parent.path() + "/" + f.name
}
@@ -472,7 +477,6 @@ func (f FS) ExtractWithOptions(folder string, op ExtractionOptions) error {
}
errChan <- fil.ExtractWithOptions(folder, op)
fil.Close()
return
}(&DirEntry{
en: f.entries[i],
parent: &f,
+65 -16
View File
@@ -14,9 +14,10 @@ import (
)
const (
downloadURL = "https://github.com/srevinsaju/Firefox-Appimage/releases/download/firefox-v84.0.r20201221152838/firefox-84.0.r20201221152838-x86_64.AppImage"
appImageURL = "https://github.com/srevinsaju/Firefox-Appimage/releases/download/firefox-v84.0.r20201221152838/firefox-84.0.r20201221152838-x86_64.AppImage"
appImageName = "firefox-84.0.r20201221152838-x86_64.AppImage"
squashfsName = "balenaEtcher-1.5.113-x64.AppImage.sfs"
squashfsURL = "https://darkstorm.tech/LinuxPATest.sfs"
squashfsName = "LinuxPATest.sfs"
)
func TestSquashfs(t *testing.T) {
@@ -25,6 +26,13 @@ func TestSquashfs(t *testing.T) {
t.Fatal(err)
}
squashFil, err := os.Open(wd + "/testing/" + squashfsName)
if os.IsNotExist(err) {
err = downloadTestSquash(wd + "/testing")
if err != nil {
t.Fatal(err)
}
squashFil, err = os.Open(wd + "/testing/" + squashfsName)
}
if err != nil {
t.Fatal(err)
}
@@ -32,19 +40,33 @@ func TestSquashfs(t *testing.T) {
if err != nil {
t.Fatal(err)
}
fmt.Println("stuff", rdr.super.CompressionType)
// fil := rdr.GetFileAtPath("*.desktop")
// if fil == nil {
// t.Fatal("Can't find desktop fil")
// }
// errs := fil.ExtractTo(wd + "/testing")
// if len(errs) > 0 {
// t.Fatal(errs)
// }
// errs = rdr.ExtractTo(wd + "/testing/" + squashfsName + ".d")
// if len(errs) > 0 {
// t.Fatal(errs)
// }
os.RemoveAll(wd + "/testing/" + squashfsName + ".d")
op := DefaultOptions()
op.Verbose = true
err = rdr.ExtractWithOptions(wd+"/testing/"+squashfsName+".d", op)
if err != nil {
t.Fatal(err)
}
t.Fatal("No Problems")
}
func TestSquashfsFromReader(t *testing.T) {
resp, err := http.DefaultClient.Get(squashfsURL)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
rdr, err := NewSquashfsReaderFromReader(resp.Body)
if err != nil {
t.Fatal(err)
}
os.RemoveAll("testing/" + squashfsName + ".d")
op := DefaultOptions()
op.Verbose = true
err = rdr.ExtractWithOptions("testing/"+squashfsName+".d", op)
if err != nil {
t.Fatal(err)
}
t.Fatal("No Problems")
}
@@ -154,6 +176,7 @@ func BenchmarkDragRace(b *testing.B) {
b.Log("Unsqushfs:", unsquashTime.Round(time.Millisecond))
b.Log("Library:", libTime.Round(time.Millisecond))
b.Log("unsquashfs is", strconv.FormatFloat(float64(libTime.Milliseconds())/float64(unsquashTime.Milliseconds()), 'f', 2, 64)+"x faster")
b.Error("STOP ALREADY!")
}
func downloadTestAppImage(dir string) error {
@@ -170,7 +193,7 @@ func downloadTestAppImage(dir string) error {
return nil
},
}
resp, err := check.Get(downloadURL)
resp, err := check.Get(appImageURL)
if err != nil {
return err
}
@@ -182,6 +205,32 @@ func downloadTestAppImage(dir string) error {
return nil
}
func downloadTestSquash(dir string) error {
//seems to time out on slow connections. Might fix that at some point... or not. It's just a test...
os.Mkdir(dir, os.ModePerm)
sfs, err := os.Create(dir + "/" + squashfsName)
if err != nil {
return err
}
defer sfs.Close()
check := http.Client{
CheckRedirect: func(r *http.Request, _ []*http.Request) error {
r.URL.Opaque = r.URL.Path
return nil
},
}
resp, err := check.Get(squashfsURL)
if err != nil {
return err
}
defer resp.Body.Close()
_, err = io.Copy(sfs, resp.Body)
if err != nil {
return err
}
return nil
}
func TestCreateSquashFromAppImage(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
-390
View File
@@ -1,390 +0,0 @@
package squashfs
import (
"errors"
"io"
"io/fs"
"log"
"os"
"path"
"sort"
"strings"
"syscall"
"github.com/CalebQ42/squashfs/internal/compression"
)
//Writer is used to creaste squashfs archives. Currently unusable.
//TODO: Make usable
type Writer struct {
compressor compression.Compressor
structure map[string][]*fileHolder
symlinkTable map[string]string
folders []string
uidGUIDTable []int
frags []fragment
superblock superblock
compressionType int
//BlockSize is how large the data blocks are. Can be between 4096 (4KB) and 1048576 (1 MB).
//If BlockSize is not inside that range, it will be set to within the range before writing.
//Default is 1048576.
BlockSize uint32
//Flags are the SuperblockFlags used when writing the archive.
//Currently Duplicates, Exportable, UncompressedXattr, NoXattr values are ignored
Flags SuperblockFlags
allowErrors bool
}
//NewWriter creates a new with the default options (Gzip compression and allow errors)
func NewWriter() (*Writer, error) {
return NewWriterWithOptions(GzipCompression, true)
}
//NewWriterWithOptions creates a new squashfs.Writer with the given options.
//compressionType can be of any types, except LZO (which this library doesn't have support for yet)
//allowErrors determines if, when adding folders, it allows errors encountered with it's sub-directories and instead logs the errors.
func NewWriterWithOptions(compressionType int, allowErrors bool) (*Writer, error) {
if compressionType < 0 || compressionType > 6 {
return nil, errors.New("Incorrect compression type")
}
if compressionType == 3 {
return nil, errors.New("Lzo compression is not (currently) supported")
}
writer := &Writer{
structure: map[string][]*fileHolder{
"/": make([]*fileHolder, 0),
},
folders: []string{
"/",
},
symlinkTable: make(map[string]string),
compressionType: compressionType,
allowErrors: allowErrors,
BlockSize: uint32(1048576),
Flags: DefaultFlags,
}
switch compressionType {
case 1:
writer.compressor = &compression.Gzip{}
case 2:
writer.compressor = &compression.Lzma{}
case 4:
writer.compressor = &compression.Xz{}
case 5:
writer.compressor = &compression.Lz4{}
case 6:
writer.compressor = &compression.Zstd{}
}
return writer, nil
}
//fileHolder holds the necessary information about a given file inside of a squashfs
type fileHolder struct {
reader io.Reader
path string
name string
symLocation string
blockSizes []uint32
GUID int
perm int
size uint64
UID int
folder bool
symlink bool
fragIndex int
fragOffset int
}
//AddFile attempts to add an os.File to the archive's root directory.
func (w *Writer) AddFile(file *os.File) error {
return w.AddFileToFolder("/", file)
}
//AddFileToFolder adds the given file to the squashfs archive, placing it inside the given folder.
func (w *Writer) AddFileToFolder(folder string, file *os.File) error {
name := path.Base(file.Name())
if !strings.HasSuffix(folder, "/") {
folder += "/"
}
return w.AddFileTo(folder+name, file)
}
//AddFileTo adds the given file to the squashfs archive at the given filepath.
func (w *Writer) AddFileTo(filepath string, file *os.File) error {
filepath = path.Clean(filepath)
if !strings.HasPrefix(filepath, "/") {
filepath = "/" + filepath
}
if w.Contains(filepath) {
return errors.New("File already exists at " + filepath)
}
var holder fileHolder
holder.path = path.Dir(filepath)
holder.name = path.Base(filepath)
holder.reader = file
stat, err := file.Stat()
if err != nil {
return err
}
holder.folder = stat.IsDir()
holder.symlink = (stat.Mode()&os.ModeSymlink == os.ModeSymlink)
holder.perm = int(stat.Mode().Perm())
//Thanks to https://stackoverflow.com/questions/58179647/getting-uid-and-gid-of-a-file for uid and guid getting
if stat, ok := stat.Sys().(*syscall.Stat_t); ok {
holder.UID = int(stat.Uid)
holder.GUID = int(stat.Gid)
}
if sort.SearchInts(w.uidGUIDTable, holder.UID) == len(w.uidGUIDTable) {
w.uidGUIDTable = append(w.uidGUIDTable, holder.UID)
sort.Ints(w.uidGUIDTable)
}
if sort.SearchInts(w.uidGUIDTable, holder.GUID) == len(w.uidGUIDTable) {
w.uidGUIDTable = append(w.uidGUIDTable, holder.GUID)
sort.Ints(w.uidGUIDTable)
}
if holder.symlink {
holder.reader = file
target, err := os.Readlink(file.Name())
if err != nil {
return err
}
holder.symLocation = target
} else if holder.folder {
subDirNames, err := file.Readdirnames(-1)
if err != nil {
return err
}
dirsAdded := make([]string, 0)
for _, subDir := range subDirNames {
fil, err := os.Open(file.Name() + subDir)
if err != nil {
return err
}
err = w.AddFileToFolder(holder.path+"/"+holder.name, fil)
if err != nil && !w.allowErrors {
for _, dir := range dirsAdded {
w.Remove(dir)
}
return err
} else if err != nil {
log.Println("Error while adding", fil.Name())
log.Println(err)
}
if !w.allowErrors {
dirsAdded = append(dirsAdded, holder.path+"/"+holder.name)
}
}
} else if stat.Mode().IsRegular() {
holder.reader = file
} else {
return errors.New("Unsupported file type " + file.Name())
}
if _, ok := w.structure[holder.path]; ok {
w.folders = append(w.folders, holder.path)
sort.Strings(w.folders)
}
w.structure[holder.path] = append(w.structure[holder.path], &holder)
return nil
}
//AddReaderTo adds the data from the given reader to the archive as a file located at the given filepath.
//Data from the reader is not read until the squashfs archive is writen.
//If the given reader implements io.Closer, it will be closed after it is fully read.
func (w *Writer) AddReaderTo(filepath string, reader io.Reader, size uint64) error {
filepath = path.Clean(filepath)
if !strings.HasPrefix(filepath, "/") {
filepath = "/" + filepath
}
if w.Contains(filepath) {
return errors.New("File already exists at " + filepath)
}
var holder fileHolder
holder.path = path.Dir(filepath)
holder.name = path.Base(filepath)
holder.size = size
holder.reader = reader
if _, ok := w.structure[holder.path]; ok {
w.folders = append(w.folders, holder.path)
sort.Strings(w.folders)
}
w.structure[holder.path] = append(w.structure[holder.path], &holder)
return nil
}
//AddFolderTo adds a folder at the given path. IF the folder is already present, it sets the folder's permissions.
//If the path points to a non-folder (such as a file or symlink), an error is returned
func (w *Writer) AddFolderTo(folderpath string, permission fs.FileMode) error {
folderpath = path.Clean(folderpath)
tmp := w.holderAt(folderpath)
if tmp != nil {
if !tmp.folder {
return errors.New("Path is not a folder: " + folderpath)
}
tmp.perm = int(permission.Perm())
return nil
}
file := fileHolder{
path: path.Dir(folderpath),
name: path.Base(folderpath),
perm: int(permission | fs.ModePerm),
folder: true,
}
w.structure[file.path] = append(w.structure[file.path], &file)
return nil
}
//Remove tries to remove the file(s) at the given filepath. If wildcards are used, it will remove all files that match.
//Returns true if one or more files are removed.
func (w *Writer) Remove(filepath string) bool {
var matchFound bool
filepath = path.Clean(filepath)
if !strings.HasPrefix(filepath, "/") {
filepath = "/" + filepath
}
dir, name := path.Split(filepath)
for structDir, files := range w.structure {
if match, _ := path.Match(dir, structDir); match {
for i, fil := range files {
if match, _ = path.Match(name, fil.name); match {
matchFound = true
if len(w.structure[structDir]) > 1 {
w.structure[structDir][i] = w.structure[structDir][len(w.structure[structDir])-1]
w.structure[structDir] = w.structure[structDir][:len(w.structure[structDir])-1]
} else {
w.structure[structDir] = nil
}
}
}
}
}
return matchFound
}
//FixSymlinks will scan through the squashfs archive and try to find broken symlinks and fix them.
//This done by replacing the symlink with the target file and then pointing other symlinks to that file.
//If all symlinks can be resolved, the error slice will be nil, and the bool false, otherwise all errors occured will be in the slice.
func (w *Writer) FixSymlinks() (errs []error, problems bool) {
for dir, holderSlice := range w.structure {
for i, holder := range holderSlice {
if !holder.symlink {
continue
}
sym := holder.symLocation
if !path.IsAbs(holder.symLocation) {
sym = path.Join(dir, holder.symLocation)
}
if path, ok := w.symlinkTable[sym]; ok {
w.structure[dir][i].symLocation = path
continue
}
if path.IsAbs(sym) || strings.HasPrefix(sym, "../") {
var symFil *os.File
var err error
if strings.HasPrefix(sym, "../") {
holderFil, ok := holder.reader.(*os.File)
if !ok {
problems = true
errs = append(errs, errors.New("Cannot resolve symlink at "+dir+holder.name))
continue
}
symFilPath := path.Dir(holderFil.Name())
symFilPath = path.Join(symFilPath, holder.symLocation)
symFil, err = os.Open(symFilPath)
} else {
symFil, err = os.Open(sym)
}
if err != nil {
problems = true
errs = append(errs, err)
continue
}
suc := w.Remove(dir + holder.name)
if !suc {
problems = true
errs = append(errs, errors.New("Cannot resolve symlink at "+dir+holder.name))
continue
}
err = w.AddFileTo(dir+holder.name, symFil)
if err != nil {
w.structure[dir] = append(w.structure[dir], holder)
problems = true
errs = append(errs, err)
continue
}
w.symlinkTable[sym] = dir + holder.name
} else {
symHolder := w.holderAt(sym)
if symHolder != nil {
w.symlinkTable[sym] = sym
continue
}
holderFil, ok := holder.reader.(*os.File)
if !ok {
problems = true
errs = append(errs, errors.New("Cannot resolve symlink at "+dir+holder.name))
continue
}
symFilPath := path.Dir(holderFil.Name())
symFilPath = path.Join(symFilPath, holder.symLocation)
symFil, err := os.Open(symFilPath)
if err != nil {
problems = true
errs = append(errs, err)
continue
}
err = w.AddFileTo(sym, symFil)
if err != nil {
problems = true
errs = append(errs, err)
continue
}
w.symlinkTable[sym] = sym
}
}
}
return
}
func (w *Writer) holderAt(filepath string) *fileHolder {
filepath = path.Clean(filepath)
if !strings.HasPrefix(filepath, "/") {
filepath = "/" + filepath
}
dir, name := path.Split(filepath)
if holderSlice, ok := w.structure[dir]; ok {
for _, holder := range holderSlice {
if holder.name == name {
return holder
}
}
}
return nil
}
//Contains returns whether a file is present at the given filepath
func (w *Writer) Contains(filepath string) bool {
filepath = path.Clean(filepath)
if !strings.HasPrefix(filepath, "/") {
filepath = "/" + filepath
}
dir, name := path.Split(filepath)
if holderSlice, ok := w.structure[dir]; ok {
for _, holder := range holderSlice {
if holder.name == name {
return true
}
}
}
return false
}
//WriteToFilename creates the squashfs archive with the given filepath.
func (w *Writer) WriteToFilename(filepath string) error {
newFil, err := os.Create(filepath)
if err != nil {
return err
}
_, err = w.WriteTo(newFil)
return err
}
-17
View File
@@ -1,17 +0,0 @@
package squashfs
import "reflect"
func (w *Writer) compressData(data []byte) ([]byte, error) {
if reflect.DeepEqual(data, make([]byte, len(data))) {
return nil, nil
}
compressedData, err := w.compressor.Compress(data)
if err != nil {
return nil, err
}
if len(data) <= len(compressedData) {
return data, nil
}
return compressedData, nil
}
-15
View File
@@ -1,15 +0,0 @@
package squashfs
//TODO: Allow settings the options
// func (w *Writer) SetGzipOptions() error {}
// func (w *Writer) SetLzmaOptions() error {}
// func (w *Writer) SetLzoOptions() error {}
// func (w *Writer) SetXzOptions() error {}
// func (w *Writer) SetLz4Options() error {}
// func (w *Writer) SetZstdOptions() error {}
-69
View File
@@ -1,69 +0,0 @@
package squashfs
type fragment struct {
w *Writer
files []*fileHolder
sizes []uint32
}
func (f *fragment) SizeLeft() uint32 {
totalSize := uint32(0)
for _, siz := range f.sizes {
totalSize += siz
}
return f.w.BlockSize - uint32(totalSize)
}
func (f *fragment) AddFragment(fil *fileHolder) {
//SizeLeft should already be checked
fil.fragOffset = len(f.files)
f.files = append(f.files, fil)
f.sizes = append(f.sizes, fil.blockSizes[len(fil.blockSizes)-1])
}
func (w *Writer) addToFragments(fil *fileHolder) {
fragSize := fil.blockSizes[len(fil.blockSizes)-1]
//only fragment if the final block is less then 80% of a full block or AlwaysFragment
if w.Flags.AlwaysFragment || fragSize < uint32(float32(w.BlockSize)*0.8) {
var possibleFrags []int
for i := range w.frags {
left := w.frags[i].SizeLeft()
if left == fragSize {
fil.fragIndex = i
w.frags[i].AddFragment(fil)
return
} else if left > fragSize {
possibleFrags = append(possibleFrags, i)
}
}
if len(possibleFrags) > 0 {
fil.fragIndex = possibleFrags[0]
} else {
fil.fragIndex = len(w.frags)
w.frags = append(w.frags, fragment{
w: w,
files: []*fileHolder{fil},
sizes: []uint32{fragSize},
})
}
}
}
func (w *Writer) calculateFragsAndBlockSizes() {
for _, files := range w.structure {
for i := range files {
files[i].fragIndex = -1
files[i].blockSizes = make([]uint32, files[i].size/uint64(w.BlockSize))
for j := range files[i].blockSizes {
files[i].blockSizes[j] = w.BlockSize
}
fragSize := uint32(files[i].size % uint64(w.BlockSize))
if fragSize > 0 {
files[i].blockSizes = append(files[i].blockSizes, fragSize)
if !w.Flags.NoFragments {
w.addToFragments(files[i])
}
}
}
}
}
-9
View File
@@ -1,9 +0,0 @@
package squashfs
func (w *Writer) countInodes() (out uint32) {
for _, files := range w.structure {
out++
out += uint32(len(files))
}
return
}
-56
View File
@@ -1,56 +0,0 @@
package squashfs
import (
"errors"
"io"
"io/fs"
"math"
"time"
)
func (w *Writer) fixFolders() error {
for folder := range w.structure {
if folder == "/" || w.Contains(folder) {
continue
}
err := w.AddFolderTo(folder, fs.ModePerm)
if err != nil {
return err
}
}
return nil
}
//WriteTo attempts to write the archive to the given io.Writer.
//Folder that aren't present (such as if you add a file at /folder/file, but not the folder /folder)
//are added with full permission (777).
//
//Not working. Yet.
func (w *Writer) WriteTo(write io.Writer) (int64, error) {
err := w.fixFolders()
if err != nil {
return 0, err
}
if w.BlockSize > 1048576 {
w.BlockSize = 1048576
} else if w.BlockSize < 4096 {
w.BlockSize = 4096
}
w.Flags.RemoveDuplicates = false
w.Flags.Exportable = false
w.Flags.NoXattr = true
w.calculateFragsAndBlockSizes()
w.superblock = superblock{
Magic: magic,
InodeCount: w.countInodes(),
CreationTime: uint32(time.Now().Unix()),
BlockSize: w.BlockSize,
CompressionType: uint16(w.compressionType),
BlockLog: uint16(math.Log2(float64(w.BlockSize))),
Flags: w.Flags.ToUint(),
IDCount: uint16(len(w.uidGUIDTable)),
MajorVersion: 4,
MinorVersion: 0,
}
return 0, errors.New("I SAID DON'T")
}