├── LICENSE
├── README.md
├── apply.go
├── apply_test.go
├── doc.go
├── hide_noop.go
├── hide_windows.go
├── internal
├── binarydist
│ ├── License
│ ├── Readme.md
│ ├── bzip2.go
│ ├── common_test.go
│ ├── diff.go
│ ├── diff_test.go
│ ├── doc.go
│ ├── encoding.go
│ ├── patch.go
│ ├── patch_test.go
│ ├── seek.go
│ ├── sort_test.go
│ └── testdata
│ │ ├── sample.new
│ │ ├── sample.old
│ │ └── sample.patch
└── osext
│ ├── LICENSE
│ ├── README.md
│ ├── osext.go
│ ├── osext_plan9.go
│ ├── osext_procfs.go
│ ├── osext_sysctl.go
│ ├── osext_test.go
│ └── osext_windows.go
├── patcher.go
└── verifier.go
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2015 Alan Shreve
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # go-update: Build self-updating Go programs [](https://godoc.org/github.com/inconshreveable/go-update)
2 |
3 | Package update provides functionality to implement secure, self-updating Go programs (or other single-file targets)
4 | A program can update itself by replacing its executable file with a new version.
5 |
6 | It provides the flexibility to implement different updating user experiences
7 | like auto-updating, or manual user-initiated updates. It also boasts
8 | advanced features like binary patching and code signing verification.
9 |
10 | Example of updating from a URL:
11 |
12 | ```go
13 | import (
14 | "fmt"
15 | "net/http"
16 |
17 | "github.com/inconshreveable/go-update"
18 | )
19 |
20 | func doUpdate(url string) error {
21 | resp, err := http.Get(url)
22 | if err != nil {
23 | return err
24 | }
25 | defer resp.Body.Close()
26 | err := update.Apply(resp.Body, update.Options{})
27 | if err != nil {
28 | // error handling
29 | }
30 | return err
31 | }
32 | ```
33 |
34 | ## Features
35 |
36 | - Cross platform support (Windows too!)
37 | - Binary patch application
38 | - Checksum verification
39 | - Code signing verification
40 | - Support for updating arbitrary files
41 |
42 | ## [equinox.io](https://equinox.io)
43 | [equinox.io](https://equinox.io) is a complete ready-to-go updating solution built on top of go-update that provides:
44 |
45 | - Hosted updates
46 | - Update channels (stable, beta, nightly, ...)
47 | - Dynamically computed binary diffs
48 | - Automatic key generation and code
49 | - Release tooling with proper code signing
50 | - Update/download metrics
51 |
52 | ## API Compatibility Promises
53 | The master branch of `go-update` is *not* guaranteed to have a stable API over time. For any production application, you should vendor
54 | your dependency on `go-update` with a tool like git submodules, [gb](http://getgb.io/) or [govendor](https://github.com/kardianos/govendor).
55 |
56 | The `go-update` package makes the following promises about API compatibility:
57 | 1. A list of all API-breaking changes will be documented in this README.
58 | 1. `go-update` will strive for as few API-breaking changes as possible.
59 |
60 | ## API Breaking Changes
61 | - **Sept 3, 2015**: The `Options` struct passed to `Apply` was changed to be passed by value instead of passed by pointer. Old API at `28de026`.
62 | - **Aug 9, 2015**: 2.0 API. Old API at `221d034` or `gopkg.in/inconshreveable/go-update.v0`.
63 |
64 | ## License
65 | Apache
66 |
--------------------------------------------------------------------------------
/apply.go:
--------------------------------------------------------------------------------
1 | package update
2 |
3 | import (
4 | "bytes"
5 | "crypto"
6 | "crypto/x509"
7 | "encoding/pem"
8 | "errors"
9 | "fmt"
10 | "io"
11 | "io/ioutil"
12 | "os"
13 | "path/filepath"
14 |
15 | "github.com/inconshreveable/go-update/internal/osext"
16 | )
17 |
18 | var (
19 | openFile = os.OpenFile
20 | )
21 |
22 | // Apply performs an update of the current executable (or opts.TargetFile, if set) with the contents of the given io.Reader.
23 | //
24 | // Apply performs the following actions to ensure a safe cross-platform update:
25 | //
26 | // 1. If configured, applies the contents of the update io.Reader as a binary patch.
27 | //
28 | // 2. If configured, computes the checksum of the new executable and verifies it matches.
29 | //
30 | // 3. If configured, verifies the signature with a public key.
31 | //
32 | // 4. Creates a new file, /path/to/.target.new with the TargetMode with the contents of the updated file
33 | //
34 | // 5. Renames /path/to/target to /path/to/.target.old
35 | //
36 | // 6. Renames /path/to/.target.new to /path/to/target
37 | //
38 | // 7. If the final rename is successful, deletes /path/to/.target.old, returns no error. On Windows,
39 | // the removal of /path/to/target.old always fails, so instead Apply hides the old file instead.
40 | //
41 | // 8. If the final rename fails, attempts to roll back by renaming /path/to/.target.old
42 | // back to /path/to/target.
43 | //
44 | // If the roll back operation fails, the file system is left in an inconsistent state (betweet steps 5 and 6) where
45 | // there is no new executable file and the old executable file could not be be moved to its original location. In this
46 | // case you should notify the user of the bad news and ask them to recover manually. Applications can determine whether
47 | // the rollback failed by calling RollbackError, see the documentation on that function for additional detail.
48 | func Apply(update io.Reader, opts Options) error {
49 | // validate
50 | verify := false
51 | switch {
52 | case opts.Signature != nil && opts.PublicKey != nil:
53 | // okay
54 | verify = true
55 | case opts.Signature != nil:
56 | return errors.New("no public key to verify signature with")
57 | case opts.PublicKey != nil:
58 | return errors.New("No signature to verify with")
59 | }
60 |
61 | // set defaults
62 | if opts.Hash == 0 {
63 | opts.Hash = crypto.SHA256
64 | }
65 | if opts.Verifier == nil {
66 | opts.Verifier = NewECDSAVerifier()
67 | }
68 | if opts.TargetMode == 0 {
69 | opts.TargetMode = 0755
70 | }
71 |
72 | // get target path
73 | var err error
74 | opts.TargetPath, err = opts.getPath()
75 | if err != nil {
76 | return err
77 | }
78 |
79 | var newBytes []byte
80 | if opts.Patcher != nil {
81 | if newBytes, err = opts.applyPatch(update); err != nil {
82 | return err
83 | }
84 | } else {
85 | // no patch to apply, go on through
86 | if newBytes, err = ioutil.ReadAll(update); err != nil {
87 | return err
88 | }
89 | }
90 |
91 | // verify checksum if requested
92 | if opts.Checksum != nil {
93 | if err = opts.verifyChecksum(newBytes); err != nil {
94 | return err
95 | }
96 | }
97 |
98 | if verify {
99 | if err = opts.verifySignature(newBytes); err != nil {
100 | return err
101 | }
102 | }
103 |
104 | // get the directory the executable exists in
105 | updateDir := filepath.Dir(opts.TargetPath)
106 | filename := filepath.Base(opts.TargetPath)
107 |
108 | // Copy the contents of newbinary to a new executable file
109 | newPath := filepath.Join(updateDir, fmt.Sprintf(".%s.new", filename))
110 | fp, err := openFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, opts.TargetMode)
111 | if err != nil {
112 | return err
113 | }
114 | defer fp.Close()
115 |
116 | _, err = io.Copy(fp, bytes.NewReader(newBytes))
117 | if err != nil {
118 | return err
119 | }
120 |
121 | // if we don't call fp.Close(), windows won't let us move the new executable
122 | // because the file will still be "in use"
123 | fp.Close()
124 |
125 | // this is where we'll move the executable to so that we can swap in the updated replacement
126 | oldPath := opts.OldSavePath
127 | removeOld := opts.OldSavePath == ""
128 | if removeOld {
129 | oldPath = filepath.Join(updateDir, fmt.Sprintf(".%s.old", filename))
130 | }
131 |
132 | // delete any existing old exec file - this is necessary on Windows for two reasons:
133 | // 1. after a successful update, Windows can't remove the .old file because the process is still running
134 | // 2. windows rename operations fail if the destination file already exists
135 | _ = os.Remove(oldPath)
136 |
137 | // move the existing executable to a new file in the same directory
138 | err = os.Rename(opts.TargetPath, oldPath)
139 | if err != nil {
140 | return err
141 | }
142 |
143 | // move the new exectuable in to become the new program
144 | err = os.Rename(newPath, opts.TargetPath)
145 |
146 | if err != nil {
147 | // move unsuccessful
148 | //
149 | // The filesystem is now in a bad state. We have successfully
150 | // moved the existing binary to a new location, but we couldn't move the new
151 | // binary to take its place. That means there is no file where the current executable binary
152 | // used to be!
153 | // Try to rollback by restoring the old binary to its original path.
154 | rerr := os.Rename(oldPath, opts.TargetPath)
155 | if rerr != nil {
156 | return &rollbackErr{err, rerr}
157 | }
158 |
159 | return err
160 | }
161 |
162 | // move successful, remove the old binary if needed
163 | if removeOld {
164 | errRemove := os.Remove(oldPath)
165 |
166 | // windows has trouble with removing old binaries, so hide it instead
167 | if errRemove != nil {
168 | _ = hideFile(oldPath)
169 | }
170 | }
171 |
172 | return nil
173 | }
174 |
175 | // RollbackError takes an error value returned by Apply and returns the error, if any,
176 | // that occurred when attempting to roll back from a failed update. Applications should
177 | // always call this function on any non-nil errors returned by Apply.
178 | //
179 | // If no rollback was needed or if the rollback was successful, RollbackError returns nil,
180 | // otherwise it returns the error encountered when trying to roll back.
181 | func RollbackError(err error) error {
182 | if err == nil {
183 | return nil
184 | }
185 | if rerr, ok := err.(*rollbackErr); ok {
186 | return rerr.rollbackErr
187 | }
188 | return nil
189 | }
190 |
191 | type rollbackErr struct {
192 | error // original error
193 | rollbackErr error // error encountered while rolling back
194 | }
195 |
196 | type Options struct {
197 | // TargetPath defines the path to the file to update.
198 | // The emptry string means 'the executable file of the running program'.
199 | TargetPath string
200 |
201 | // Create TargetPath replacement with this file mode. If zero, defaults to 0755.
202 | TargetMode os.FileMode
203 |
204 | // Checksum of the new binary to verify against. If nil, no checksum or signature verification is done.
205 | Checksum []byte
206 |
207 | // Public key to use for signature verification. If nil, no signature verification is done.
208 | PublicKey crypto.PublicKey
209 |
210 | // Signature to verify the updated file. If nil, no signature verification is done.
211 | Signature []byte
212 |
213 | // Pluggable signature verification algorithm. If nil, ECDSA is used.
214 | Verifier Verifier
215 |
216 | // Use this hash function to generate the checksum. If not set, SHA256 is used.
217 | Hash crypto.Hash
218 |
219 | // If nil, treat the update as a complete replacement for the contents of the file at TargetPath.
220 | // If non-nil, treat the update contents as a patch and use this object to apply the patch.
221 | Patcher Patcher
222 |
223 | // Store the old executable file at this path after a successful update.
224 | // The empty string means the old executable file will be removed after the update.
225 | OldSavePath string
226 | }
227 |
228 | // CheckPermissions determines whether the process has the correct permissions to
229 | // perform the requested update. If the update can proceed, it returns nil, otherwise
230 | // it returns the error that would occur if an update were attempted.
231 | func (o *Options) CheckPermissions() error {
232 | // get the directory the file exists in
233 | path, err := o.getPath()
234 | if err != nil {
235 | return err
236 | }
237 |
238 | fileDir := filepath.Dir(path)
239 | fileName := filepath.Base(path)
240 |
241 | // attempt to open a file in the file's directory
242 | newPath := filepath.Join(fileDir, fmt.Sprintf(".%s.new", fileName))
243 | fp, err := openFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, o.TargetMode)
244 | if err != nil {
245 | return err
246 | }
247 | fp.Close()
248 |
249 | _ = os.Remove(newPath)
250 | return nil
251 | }
252 |
253 | // SetPublicKeyPEM is a convenience method to set the PublicKey property
254 | // used for checking a completed update's signature by parsing a
255 | // Public Key formatted as PEM data.
256 | func (o *Options) SetPublicKeyPEM(pembytes []byte) error {
257 | block, _ := pem.Decode(pembytes)
258 | if block == nil {
259 | return errors.New("couldn't parse PEM data")
260 | }
261 |
262 | pub, err := x509.ParsePKIXPublicKey(block.Bytes)
263 | if err != nil {
264 | return err
265 | }
266 | o.PublicKey = pub
267 | return nil
268 | }
269 |
270 | func (o *Options) getPath() (string, error) {
271 | if o.TargetPath == "" {
272 | return osext.Executable()
273 | } else {
274 | return o.TargetPath, nil
275 | }
276 | }
277 |
278 | func (o *Options) applyPatch(patch io.Reader) ([]byte, error) {
279 | // open the file to patch
280 | old, err := os.Open(o.TargetPath)
281 | if err != nil {
282 | return nil, err
283 | }
284 | defer old.Close()
285 |
286 | // apply the patch
287 | var applied bytes.Buffer
288 | if err = o.Patcher.Patch(old, &applied, patch); err != nil {
289 | return nil, err
290 | }
291 |
292 | return applied.Bytes(), nil
293 | }
294 |
295 | func (o *Options) verifyChecksum(updated []byte) error {
296 | checksum, err := checksumFor(o.Hash, updated)
297 | if err != nil {
298 | return err
299 | }
300 |
301 | if !bytes.Equal(o.Checksum, checksum) {
302 | return fmt.Errorf("Updated file has wrong checksum. Expected: %x, got: %x", o.Checksum, checksum)
303 | }
304 | return nil
305 | }
306 |
307 | func (o *Options) verifySignature(updated []byte) error {
308 | checksum, err := checksumFor(o.Hash, updated)
309 | if err != nil {
310 | return err
311 | }
312 | return o.Verifier.VerifySignature(checksum, o.Signature, o.Hash, o.PublicKey)
313 | }
314 |
315 | func checksumFor(h crypto.Hash, payload []byte) ([]byte, error) {
316 | if !h.Available() {
317 | return nil, errors.New("requested hash function not available")
318 | }
319 | hash := h.New()
320 | hash.Write(payload) // guaranteed not to error
321 | return hash.Sum([]byte{}), nil
322 | }
323 |
--------------------------------------------------------------------------------
/apply_test.go:
--------------------------------------------------------------------------------
1 | package update
2 |
3 | import (
4 | "bytes"
5 | "crypto"
6 | "crypto/rand"
7 | "crypto/sha256"
8 | "crypto/x509"
9 | "encoding/pem"
10 | "fmt"
11 | "io/ioutil"
12 | "os"
13 | "testing"
14 |
15 | "github.com/inconshreveable/go-update/internal/binarydist"
16 | )
17 |
18 | var (
19 | oldFile = []byte{0xDE, 0xAD, 0xBE, 0xEF}
20 | newFile = []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}
21 | newFileChecksum = sha256.Sum256(newFile)
22 | )
23 |
24 | func cleanup(path string) {
25 | os.Remove(path)
26 | os.Remove(fmt.Sprintf(".%s.new", path))
27 | }
28 |
29 | // we write with a separate name for each test so that we can run them in parallel
30 | func writeOldFile(path string, t *testing.T) {
31 | if err := ioutil.WriteFile(path, oldFile, 0777); err != nil {
32 | t.Fatalf("Failed to write file for testing preparation: %v", err)
33 | }
34 | }
35 |
36 | func validateUpdate(path string, err error, t *testing.T) {
37 | if err != nil {
38 | t.Fatalf("Failed to update: %v", err)
39 | }
40 |
41 | buf, err := ioutil.ReadFile(path)
42 | if err != nil {
43 | t.Fatalf("Failed to read file post-update: %v", err)
44 | }
45 |
46 | if !bytes.Equal(buf, newFile) {
47 | t.Fatalf("File was not updated! Bytes read: %v, Bytes expected: %v", buf, newFile)
48 | }
49 | }
50 |
51 | func TestApplySimple(t *testing.T) {
52 | fName := "TestApplySimple"
53 | defer cleanup(fName)
54 | writeOldFile(fName, t)
55 |
56 | err := Apply(bytes.NewReader(newFile), Options{
57 | TargetPath: fName,
58 | })
59 | validateUpdate(fName, err, t)
60 | }
61 |
62 | func TestApplyOldSavePath(t *testing.T) {
63 | fName := "TestApplyOldSavePath"
64 | defer cleanup(fName)
65 | writeOldFile(fName, t)
66 |
67 | oldfName := "OldSavePath"
68 |
69 | err := Apply(bytes.NewReader(newFile), Options{
70 | TargetPath: fName,
71 | OldSavePath: oldfName,
72 | })
73 | validateUpdate(fName, err, t)
74 |
75 | if _, err := os.Stat(oldfName); os.IsNotExist(err) {
76 | t.Fatalf("Failed to find the old file: %v", err)
77 | }
78 |
79 | cleanup(oldfName)
80 | }
81 |
82 | func TestVerifyChecksum(t *testing.T) {
83 | fName := "TestVerifyChecksum"
84 | defer cleanup(fName)
85 | writeOldFile(fName, t)
86 |
87 | err := Apply(bytes.NewReader(newFile), Options{
88 | TargetPath: fName,
89 | Checksum: newFileChecksum[:],
90 | })
91 | validateUpdate(fName, err, t)
92 | }
93 |
94 | func TestVerifyChecksumNegative(t *testing.T) {
95 | fName := "TestVerifyChecksumNegative"
96 | defer cleanup(fName)
97 | writeOldFile(fName, t)
98 |
99 | badChecksum := []byte{0x0A, 0x0B, 0x0C, 0xFF}
100 | err := Apply(bytes.NewReader(newFile), Options{
101 | TargetPath: fName,
102 | Checksum: badChecksum,
103 | })
104 | if err == nil {
105 | t.Fatalf("Failed to detect bad checksum!")
106 | }
107 | }
108 |
109 | func TestApplyPatch(t *testing.T) {
110 | fName := "TestApplyPatch"
111 | defer cleanup(fName)
112 | writeOldFile(fName, t)
113 |
114 | patch := new(bytes.Buffer)
115 | err := binarydist.Diff(bytes.NewReader(oldFile), bytes.NewReader(newFile), patch)
116 | if err != nil {
117 | t.Fatalf("Failed to create patch: %v", err)
118 | }
119 |
120 | err = Apply(patch, Options{
121 | TargetPath: fName,
122 | Patcher: NewBSDiffPatcher(),
123 | })
124 | validateUpdate(fName, err, t)
125 | }
126 |
127 | func TestCorruptPatch(t *testing.T) {
128 | fName := "TestCorruptPatch"
129 | defer cleanup(fName)
130 | writeOldFile(fName, t)
131 |
132 | badPatch := []byte{0x44, 0x38, 0x86, 0x3c, 0x4f, 0x8d, 0x26, 0x54, 0xb, 0x11, 0xce, 0xfe, 0xc1, 0xc0, 0xf8, 0x31, 0x38, 0xa0, 0x12, 0x1a, 0xa2, 0x57, 0x2a, 0xe1, 0x3a, 0x48, 0x62, 0x40, 0x2b, 0x81, 0x12, 0xb1, 0x21, 0xa5, 0x16, 0xed, 0x73, 0xd6, 0x54, 0x84, 0x29, 0xa6, 0xd6, 0xb2, 0x1b, 0xfb, 0xe6, 0xbe, 0x7b, 0x70}
133 | err := Apply(bytes.NewReader(badPatch), Options{
134 | TargetPath: fName,
135 | Patcher: NewBSDiffPatcher(),
136 | })
137 | if err == nil {
138 | t.Fatalf("Failed to detect corrupt patch!")
139 | }
140 | }
141 |
142 | func TestVerifyChecksumPatchNegative(t *testing.T) {
143 | fName := "TestVerifyChecksumPatchNegative"
144 | defer cleanup(fName)
145 | writeOldFile(fName, t)
146 |
147 | patch := new(bytes.Buffer)
148 | anotherFile := []byte{0x77, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66}
149 | err := binarydist.Diff(bytes.NewReader(oldFile), bytes.NewReader(anotherFile), patch)
150 | if err != nil {
151 | t.Fatalf("Failed to create patch: %v", err)
152 | }
153 |
154 | err = Apply(patch, Options{
155 | TargetPath: fName,
156 | Checksum: newFileChecksum[:],
157 | Patcher: NewBSDiffPatcher(),
158 | })
159 | if err == nil {
160 | t.Fatalf("Failed to detect patch to wrong file!")
161 | }
162 | }
163 |
164 | const ecdsaPublicKey = `
165 | -----BEGIN PUBLIC KEY-----
166 | MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEL8ThbSyEucsCxnd4dCZR2hIy5nea54ko
167 | O+jUUfIjkvwhCWzASm0lpCVdVpXKZXIe+NZ+44RQRv3+OqJkCCGzUgJkPNI3lxdG
168 | 9zu8rbrnxISV06VQ8No7Ei9wiTpqmTBB
169 | -----END PUBLIC KEY-----
170 | `
171 |
172 | const ecdsaPrivateKey = `
173 | -----BEGIN EC PRIVATE KEY-----
174 | MIGkAgEBBDBttCB/1NOY4T+WrG4FSV49Ayn3gK1DNzfGaJ01JUXeiNFCWQM2pqpU
175 | om8ATPP/dkegBwYFK4EEACKhZANiAAQvxOFtLIS5ywLGd3h0JlHaEjLmd5rniSg7
176 | 6NRR8iOS/CEJbMBKbSWkJV1Wlcplch741n7jhFBG/f46omQIIbNSAmQ80jeXF0b3
177 | O7ytuufEhJXTpVDw2jsSL3CJOmqZMEE=
178 | -----END EC PRIVATE KEY-----
179 | `
180 |
181 | const rsaPublicKey = `
182 | -----BEGIN PUBLIC KEY-----
183 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxSWmu7trWKAwDFjiCN2D
184 | Tk2jj2sgcr/CMlI4cSSiIOHrXCFxP1I8i9PvQkd4hasXQrLbT5WXKrRGv1HKUKab
185 | b9ead+kD0kxk7i2bFYvKX43oq66IW0mOLTQBO7I9UyT4L7svcMD+HUQ2BqHoaQe4
186 | y20C59dPr9Dpcz8DZkdLsBV6YKF6Ieb3iGk8oRLMWNaUqPa8f1BGgxAkvPHcqDjT
187 | x4xRnjgTRRRlZvRtALHMUkIChgxDOhoEzKpGiqnX7HtMJfrhV6h0PAXNA4h9Kjv5
188 | 5fhJ08Rz7mmZmtH5JxTK5XTquo59sihSajR4bSjZbbkQ1uLkeFlY3eli3xdQ7Nrf
189 | fQIDAQAB
190 | -----END PUBLIC KEY-----`
191 |
192 | const rsaPrivateKey = `
193 | -----BEGIN RSA PRIVATE KEY-----
194 | MIIEogIBAAKCAQEAxSWmu7trWKAwDFjiCN2DTk2jj2sgcr/CMlI4cSSiIOHrXCFx
195 | P1I8i9PvQkd4hasXQrLbT5WXKrRGv1HKUKabb9ead+kD0kxk7i2bFYvKX43oq66I
196 | W0mOLTQBO7I9UyT4L7svcMD+HUQ2BqHoaQe4y20C59dPr9Dpcz8DZkdLsBV6YKF6
197 | Ieb3iGk8oRLMWNaUqPa8f1BGgxAkvPHcqDjTx4xRnjgTRRRlZvRtALHMUkIChgxD
198 | OhoEzKpGiqnX7HtMJfrhV6h0PAXNA4h9Kjv55fhJ08Rz7mmZmtH5JxTK5XTquo59
199 | sihSajR4bSjZbbkQ1uLkeFlY3eli3xdQ7NrffQIDAQABAoIBAAkN+6RvrTR61voa
200 | Mvd5RQiZpEN4Bht/Fyo8gH8h0Zh1B9xJZOwlmMZLS5fdtHlfLEhR8qSrGDBL61vq
201 | I8KkhEsUufF78EL+YzxVN+Q7cWYGHIOWFokqza7hzpSxUQO6lPOMQ1eIZaNueJTB
202 | Zu07/47ISPPg/bXzgGVcpYlTCPTjUwKjtfyMqvX9AD7fIyYRm6zfE7EHj1J2sBFt
203 | Yz1OGELg6HfJwXfpnPfBvftD0hWGzJ78Bp71fPJe6n5gnqmSqRvrcXNWFnH/yqkN
204 | d6vPIxD6Z3LjvyZpkA7JillLva2L/zcIFhg4HZvQnWd8/PpDnUDonu36hcj4SC5j
205 | W4aVPLkCgYEA4XzNKWxqYcajzFGZeSxlRHupSAl2MT7Cc5085MmE7dd31wK2T8O4
206 | n7N4bkm/rjTbX85NsfWdKtWb6mpp8W3VlLP0rp4a/12OicVOkg4pv9LZDmY0sRlE
207 | YuDJk1FeCZ50UrwTZI3rZ9IhZHhkgVA6uWAs7tYndONkxNHG0pjqs4sCgYEA39MZ
208 | JwMqo3qsPntpgP940cCLflEsjS9hYNO3+Sv8Dq3P0HLVhBYajJnotf8VuU0fsQZG
209 | grmtVn1yThFbMq7X1oY4F0XBA+paSiU18c4YyUnwax2u4sw9U/Q9tmQUZad5+ueT
210 | qriMBwGv+ewO+nQxqvAsMUmemrVzrfwA5Oct+hcCgYAfiyXoNZJsOy2O15twqBVC
211 | j0oPGcO+/9iT89sg5lACNbI+EdMPNYIOVTzzsL1v0VUfAe08h++Enn1BPcG0VHkc
212 | ZFBGXTfJoXzfKQrkw7ZzbzuOGB4m6DH44xlP0oIlNlVvfX/5ASF9VJf3RiBJNsAA
213 | TsP6ZVr/rw/ZuL7nlxy+IQKBgDhL/HOXlE3yOQiuOec8WsNHTs7C1BXe6PtVxVxi
214 | 988pYK/pclL6zEq5G5NLSceF4obAMVQIJ9UtUGbabrncyGUo9UrFPLsjYvprSZo8
215 | YHegpVwL50UcYgCP2kXZ/ldjPIcjYDz8lhvdDMor2cidGTEJn9P11HLNWP9V91Ob
216 | 4jCZAoGAPNRSC5cC8iP/9j+s2/kdkfWJiNaolPYAUrmrkL6H39PYYZM5tnhaIYJV
217 | Oh9AgABamU0eb3p3vXTISClVgV7ifq1HyZ7BSUhMfaY2Jk/s3sUHCWFxPZe9sgEG
218 | KinIY/373KIkIV/5g4h2v1w330IWcfptxKcY/Er3DJr38f695GE=
219 | -----END RSA PRIVATE KEY-----`
220 |
221 | func signec(privatePEM string, source []byte, t *testing.T) []byte {
222 | parseFn := func(p []byte) (crypto.Signer, error) { return x509.ParseECPrivateKey(p) }
223 | return sign(parseFn, privatePEM, source, t)
224 | }
225 |
226 | func signrsa(privatePEM string, source []byte, t *testing.T) []byte {
227 | parseFn := func(p []byte) (crypto.Signer, error) { return x509.ParsePKCS1PrivateKey(p) }
228 | return sign(parseFn, privatePEM, source, t)
229 | }
230 |
231 | func sign(parsePrivKey func([]byte) (crypto.Signer, error), privatePEM string, source []byte, t *testing.T) []byte {
232 | block, _ := pem.Decode([]byte(privatePEM))
233 | if block == nil {
234 | t.Fatalf("Failed to parse private key PEM")
235 | }
236 |
237 | priv, err := parsePrivKey(block.Bytes)
238 | if err != nil {
239 | t.Fatalf("Failed to parse private key DER: %v", err)
240 | }
241 |
242 | checksum := sha256.Sum256(source)
243 | sig, err := priv.Sign(rand.Reader, checksum[:], crypto.SHA256)
244 | if err != nil {
245 | t.Fatalf("Failed to sign: %v", sig)
246 | }
247 |
248 | return sig
249 | }
250 |
251 | func TestVerifyECSignature(t *testing.T) {
252 | fName := "TestVerifyECSignature"
253 | defer cleanup(fName)
254 | writeOldFile(fName, t)
255 |
256 | opts := Options{TargetPath: fName}
257 | err := opts.SetPublicKeyPEM([]byte(ecdsaPublicKey))
258 | if err != nil {
259 | t.Fatalf("Could not parse public key: %v", err)
260 | }
261 |
262 | opts.Signature = signec(ecdsaPrivateKey, newFile, t)
263 | err = Apply(bytes.NewReader(newFile), opts)
264 | validateUpdate(fName, err, t)
265 | }
266 |
267 | func TestVerifyRSASignature(t *testing.T) {
268 | fName := "TestVerifyRSASignature"
269 | defer cleanup(fName)
270 | writeOldFile(fName, t)
271 |
272 | opts := Options{
273 | TargetPath: fName,
274 | Verifier: NewRSAVerifier(),
275 | }
276 | err := opts.SetPublicKeyPEM([]byte(rsaPublicKey))
277 | if err != nil {
278 | t.Fatalf("Could not parse public key: %v", err)
279 | }
280 |
281 | opts.Signature = signrsa(rsaPrivateKey, newFile, t)
282 | err = Apply(bytes.NewReader(newFile), opts)
283 | validateUpdate(fName, err, t)
284 | }
285 |
286 | func TestVerifyFailBadSignature(t *testing.T) {
287 | fName := "TestVerifyFailBadSignature"
288 | defer cleanup(fName)
289 | writeOldFile(fName, t)
290 |
291 | opts := Options{
292 | TargetPath: fName,
293 | Signature: []byte{0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA},
294 | }
295 | err := opts.SetPublicKeyPEM([]byte(ecdsaPublicKey))
296 | if err != nil {
297 | t.Fatalf("Could not parse public key: %v", err)
298 | }
299 |
300 | err = Apply(bytes.NewReader(newFile), opts)
301 | if err == nil {
302 | t.Fatalf("Did not fail with bad signature")
303 | }
304 | }
305 |
306 | func TestVerifyFailNoSignature(t *testing.T) {
307 | fName := "TestVerifySignatureWithPEM"
308 | defer cleanup(fName)
309 | writeOldFile(fName, t)
310 |
311 | opts := Options{TargetPath: fName}
312 | err := opts.SetPublicKeyPEM([]byte(ecdsaPublicKey))
313 | if err != nil {
314 | t.Fatalf("Could not parse public key: %v", err)
315 | }
316 |
317 | err = Apply(bytes.NewReader(newFile), opts)
318 | if err == nil {
319 | t.Fatalf("Did not fail with empty signature")
320 | }
321 | }
322 |
323 | const wrongKey = `
324 | -----BEGIN EC PRIVATE KEY-----
325 | MIGkAgEBBDBzqYp6N2s8YWYifBjS03/fFfmGeIPcxQEi+bbFeekIYt8NIKIkhD+r
326 | hpaIwSmot+qgBwYFK4EEACKhZANiAAR0EC8Usbkc4k30frfEB2ECmsIghu9DJSqE
327 | RbH7jfq2ULNv8tN/clRjxf2YXgp+iP3SQF1R1EYERKpWr8I57pgfIZtoZXjwpbQC
328 | VBbP/Ff+05HOqwPC7rJMy1VAJLKg7Cw=
329 | -----END EC PRIVATE KEY-----
330 | `
331 |
332 | func TestVerifyFailWrongSignature(t *testing.T) {
333 | fName := "TestVerifyFailWrongSignature"
334 | defer cleanup(fName)
335 | writeOldFile(fName, t)
336 |
337 | opts := Options{TargetPath: fName}
338 | err := opts.SetPublicKeyPEM([]byte(ecdsaPublicKey))
339 | if err != nil {
340 | t.Fatalf("Could not parse public key: %v", err)
341 | }
342 |
343 | opts.Signature = signec(wrongKey, newFile, t)
344 | err = Apply(bytes.NewReader(newFile), opts)
345 | if err == nil {
346 | t.Fatalf("Verified an update that was signed by an untrusted key!")
347 | }
348 | }
349 |
350 | func TestSignatureButNoPublicKey(t *testing.T) {
351 | fName := "TestSignatureButNoPublicKey"
352 | defer cleanup(fName)
353 | writeOldFile(fName, t)
354 |
355 | err := Apply(bytes.NewReader(newFile), Options{
356 | TargetPath: fName,
357 | Signature: signec(ecdsaPrivateKey, newFile, t),
358 | })
359 | if err == nil {
360 | t.Fatalf("Allowed an update with a signautre verification when no public key was specified!")
361 | }
362 | }
363 |
364 | func TestPublicKeyButNoSignature(t *testing.T) {
365 | fName := "TestPublicKeyButNoSignature"
366 | defer cleanup(fName)
367 | writeOldFile(fName, t)
368 |
369 | opts := Options{TargetPath: fName}
370 | if err := opts.SetPublicKeyPEM([]byte(ecdsaPublicKey)); err != nil {
371 | t.Fatalf("Could not parse public key: %v", err)
372 | }
373 | err := Apply(bytes.NewReader(newFile), opts)
374 | if err == nil {
375 | t.Fatalf("Allowed an update with no signautre when a public key was specified!")
376 | }
377 | }
378 |
379 | func TestWriteError(t *testing.T) {
380 | fName := "TestWriteError"
381 | defer cleanup(fName)
382 | writeOldFile(fName, t)
383 |
384 | openFile = func(name string, flags int, perm os.FileMode) (*os.File, error) {
385 | f, err := os.OpenFile(name, flags, perm)
386 |
387 | // simulate Write() error by closing the file prematurely
388 | f.Close()
389 |
390 | return f, err
391 | }
392 | defer func() {
393 | openFile = os.OpenFile
394 | }()
395 |
396 | err := Apply(bytes.NewReader(newFile), Options{TargetPath: fName})
397 | if err == nil {
398 | t.Fatalf("Allowed an update to an empty file")
399 | }
400 | }
401 |
--------------------------------------------------------------------------------
/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package update provides functionality to implement secure, self-updating Go programs (or other single-file targets).
3 |
4 | For complete updating solutions please see Equinox (https://equinox.io) and go-tuf (https://github.com/flynn/go-tuf).
5 |
6 | Basic Example
7 |
8 | This example shows how to update a program remotely from a URL.
9 |
10 | import (
11 | "fmt"
12 | "net/http"
13 |
14 | "github.com/inconshreveable/go-update"
15 | )
16 |
17 | func doUpdate(url string) error {
18 | // request the new file
19 | resp, err := http.Get(url)
20 | if err != nil {
21 | return err
22 | }
23 | defer resp.Body.Close()
24 | err := update.Apply(resp.Body, update.Options{})
25 | if err != nil {
26 | if rerr := update.RollbackError(err); rerr != nil {
27 | fmt.Println("Failed to rollback from bad update: %v", rerr)
28 | }
29 | }
30 | return err
31 | }
32 |
33 |
34 | Binary Patching
35 |
36 | Go binaries can often be large. It can be advantageous to only ship a binary patch to a client
37 | instead of the complete program text of a new version.
38 |
39 | This example shows how to update a program with a bsdiff binary patch. Other patch formats
40 | may be applied by implementing the Patcher interface.
41 |
42 | import (
43 | "encoding/hex"
44 | "io"
45 |
46 | "github.com/inconshreveable/go-update"
47 | )
48 |
49 | func updateWithPatch(patch io.Reader) error {
50 | err := update.Apply(patch, update.Options{
51 | Patcher: update.NewBSDiffPatcher()
52 | })
53 | if err != nil {
54 | // error handling
55 | }
56 | return err
57 | }
58 |
59 | Checksum Verification
60 |
61 | Updating executable code on a computer can be a dangerous operation unless you
62 | take the appropriate steps to guarantee the authenticity of the new code. While
63 | checksum verification is important, it should always be combined with signature
64 | verification (next section) to guarantee that the code came from a trusted party.
65 |
66 | go-update validates SHA256 checksums by default, but this is pluggable via the Hash
67 | property on the Options struct.
68 |
69 | This example shows how to guarantee that the newly-updated binary is verified to
70 | have an appropriate checksum (that was otherwise retrived via a secure channel)
71 | specified as a hex string.
72 |
73 | import (
74 | "crypto"
75 | _ "crypto/sha256"
76 | "encoding/hex"
77 | "io"
78 |
79 | "github.com/inconshreveable/go-update"
80 | )
81 |
82 | func updateWithChecksum(binary io.Reader, hexChecksum string) error {
83 | checksum, err := hex.DecodeString(hexChecksum)
84 | if err != nil {
85 | return err
86 | }
87 | err = update.Apply(binary, update.Options{
88 | Hash: crypto.SHA256, // this is the default, you don't need to specify it
89 | Checksum: checksum,
90 | })
91 | if err != nil {
92 | // error handling
93 | }
94 | return err
95 | }
96 |
97 | Cryptographic Signature Verification
98 |
99 | Cryptographic verification of new code from an update is an extremely important way to guarantee the
100 | security and integrity of your updates.
101 |
102 | Verification is performed by validating the signature of a hash of the new file. This
103 | means nothing changes if you apply your update with a patch.
104 |
105 | This example shows how to add signature verification to your updates. To make all of this work
106 | an application distributor must first create a public/private key pair and embed the public key
107 | into their application. When they issue a new release, the issuer must sign the new executable file
108 | with the private key and distribute the signature along with the update.
109 |
110 | import (
111 | "crypto"
112 | _ "crypto/sha256"
113 | "encoding/hex"
114 | "io"
115 |
116 | "github.com/inconshreveable/go-update"
117 | )
118 |
119 | var publicKey = []byte(`
120 | -----BEGIN PUBLIC KEY-----
121 | MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEtrVmBxQvheRArXjg2vG1xIprWGuCyESx
122 | MMY8pjmjepSy2kuz+nl9aFLqmr+rDNdYvEBqQaZrYMc6k29gjvoQnQ==
123 | -----END PUBLIC KEY-----
124 | `)
125 |
126 | func verifiedUpdate(binary io.Reader, hexChecksum, hexSignature string) {
127 | checksum, err := hex.DecodeString(hexChecksum)
128 | if err != nil {
129 | return err
130 | }
131 | signature, err := hex.DecodeString(hexSignature)
132 | if err != nil {
133 | return err
134 | }
135 | opts := update.Options{
136 | Checksum: checksum,
137 | Signature: signature,
138 | Hash: crypto.SHA256, // this is the default, you don't need to specify it
139 | Verifier: update.NewECDSAVerifier(), // this is the default, you don't need to specify it
140 | }
141 | err = opts.SetPublicKeyPEM(publicKey)
142 | if err != nil {
143 | return err
144 | }
145 | err = update.Apply(binary, opts)
146 | if err != nil {
147 | // error handling
148 | }
149 | return err
150 | }
151 |
152 |
153 | Building Single-File Go Binaries
154 |
155 | In order to update a Go application with go-update, you must distributed it as a single executable.
156 | This is often easy, but some applications require static assets (like HTML and CSS asset files or TLS certificates).
157 | In order to update applications like these, you'll want to make sure to embed those asset files into
158 | the distributed binary with a tool like go-bindata (my favorite): https://github.com/jteeuwen/go-bindata
159 |
160 | Non-Goals
161 |
162 | Mechanisms and protocols for determining whether an update should be applied and, if so, which one are
163 | out of scope for this package. Please consult go-tuf (https://github.com/flynn/go-tuf) or Equinox (https://equinox.io)
164 | for more complete solutions.
165 |
166 | go-update only works for self-updating applications that are distributed as a single binary, i.e.
167 | applications that do not have additional assets or dependency files.
168 | Updating application that are distributed as mutliple on-disk files is out of scope, although this
169 | may change in future versions of this library.
170 |
171 | */
172 | package update
173 |
--------------------------------------------------------------------------------
/hide_noop.go:
--------------------------------------------------------------------------------
1 | // +build !windows
2 |
3 | package update
4 |
5 | func hideFile(path string) error {
6 | return nil
7 | }
8 |
--------------------------------------------------------------------------------
/hide_windows.go:
--------------------------------------------------------------------------------
1 | package update
2 |
3 | import (
4 | "syscall"
5 | "unsafe"
6 | )
7 |
8 | func hideFile(path string) error {
9 | kernel32 := syscall.NewLazyDLL("kernel32.dll")
10 | setFileAttributes := kernel32.NewProc("SetFileAttributesW")
11 |
12 | r1, _, err := setFileAttributes.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))), 2)
13 |
14 | if r1 == 0 {
15 | return err
16 | } else {
17 | return nil
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/internal/binarydist/License:
--------------------------------------------------------------------------------
1 | Copyright 2012 Keith Rarick
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/internal/binarydist/Readme.md:
--------------------------------------------------------------------------------
1 | # binarydist
2 |
3 | Package binarydist implements binary diff and patch as described on
4 | . It reads and writes files
5 | compatible with the tools there.
6 |
7 | Documentation at .
8 |
--------------------------------------------------------------------------------
/internal/binarydist/bzip2.go:
--------------------------------------------------------------------------------
1 | package binarydist
2 |
3 | import (
4 | "io"
5 | "os/exec"
6 | )
7 |
8 | type bzip2Writer struct {
9 | c *exec.Cmd
10 | w io.WriteCloser
11 | }
12 |
13 | func (w bzip2Writer) Write(b []byte) (int, error) {
14 | return w.w.Write(b)
15 | }
16 |
17 | func (w bzip2Writer) Close() error {
18 | if err := w.w.Close(); err != nil {
19 | return err
20 | }
21 | return w.c.Wait()
22 | }
23 |
24 | // Package compress/bzip2 implements only decompression,
25 | // so we'll fake it by running bzip2 in another process.
26 | func newBzip2Writer(w io.Writer) (wc io.WriteCloser, err error) {
27 | var bw bzip2Writer
28 | bw.c = exec.Command("bzip2", "-c")
29 | bw.c.Stdout = w
30 |
31 | if bw.w, err = bw.c.StdinPipe(); err != nil {
32 | return nil, err
33 | }
34 |
35 | if err = bw.c.Start(); err != nil {
36 | return nil, err
37 | }
38 |
39 | return bw, nil
40 | }
41 |
--------------------------------------------------------------------------------
/internal/binarydist/common_test.go:
--------------------------------------------------------------------------------
1 | package binarydist
2 |
3 | import (
4 | "crypto/rand"
5 | "io"
6 | "io/ioutil"
7 | "os"
8 | )
9 |
10 | func mustOpen(path string) *os.File {
11 | f, err := os.Open(path)
12 | if err != nil {
13 | panic(err)
14 | }
15 |
16 | return f
17 | }
18 |
19 | func mustReadAll(r io.Reader) []byte {
20 | b, err := ioutil.ReadAll(r)
21 | if err != nil {
22 | panic(err)
23 | }
24 | return b
25 | }
26 |
27 | func fileCmp(a, b *os.File) int64 {
28 | sa, err := a.Seek(0, 2)
29 | if err != nil {
30 | panic(err)
31 | }
32 |
33 | sb, err := b.Seek(0, 2)
34 | if err != nil {
35 | panic(err)
36 | }
37 |
38 | if sa != sb {
39 | return sa
40 | }
41 |
42 | _, err = a.Seek(0, 0)
43 | if err != nil {
44 | panic(err)
45 | }
46 |
47 | _, err = b.Seek(0, 0)
48 | if err != nil {
49 | panic(err)
50 | }
51 |
52 | pa, err := ioutil.ReadAll(a)
53 | if err != nil {
54 | panic(err)
55 | }
56 |
57 | pb, err := ioutil.ReadAll(b)
58 | if err != nil {
59 | panic(err)
60 | }
61 |
62 | for i := range pa {
63 | if pa[i] != pb[i] {
64 | return int64(i)
65 | }
66 | }
67 | return -1
68 | }
69 |
70 | func mustWriteRandFile(path string, size int) *os.File {
71 | p := make([]byte, size)
72 | _, err := rand.Read(p)
73 | if err != nil {
74 | panic(err)
75 | }
76 |
77 | f, err := os.Create(path)
78 | if err != nil {
79 | panic(err)
80 | }
81 |
82 | _, err = f.Write(p)
83 | if err != nil {
84 | panic(err)
85 | }
86 |
87 | _, err = f.Seek(0, 0)
88 | if err != nil {
89 | panic(err)
90 | }
91 |
92 | return f
93 | }
94 |
--------------------------------------------------------------------------------
/internal/binarydist/diff.go:
--------------------------------------------------------------------------------
1 | package binarydist
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "io"
7 | "io/ioutil"
8 | )
9 |
10 | func swap(a []int, i, j int) { a[i], a[j] = a[j], a[i] }
11 |
12 | func split(I, V []int, start, length, h int) {
13 | var i, j, k, x, jj, kk int
14 |
15 | if length < 16 {
16 | for k = start; k < start+length; k += j {
17 | j = 1
18 | x = V[I[k]+h]
19 | for i = 1; k+i < start+length; i++ {
20 | if V[I[k+i]+h] < x {
21 | x = V[I[k+i]+h]
22 | j = 0
23 | }
24 | if V[I[k+i]+h] == x {
25 | swap(I, k+i, k+j)
26 | j++
27 | }
28 | }
29 | for i = 0; i < j; i++ {
30 | V[I[k+i]] = k + j - 1
31 | }
32 | if j == 1 {
33 | I[k] = -1
34 | }
35 | }
36 | return
37 | }
38 |
39 | x = V[I[start+length/2]+h]
40 | jj = 0
41 | kk = 0
42 | for i = start; i < start+length; i++ {
43 | if V[I[i]+h] < x {
44 | jj++
45 | }
46 | if V[I[i]+h] == x {
47 | kk++
48 | }
49 | }
50 | jj += start
51 | kk += jj
52 |
53 | i = start
54 | j = 0
55 | k = 0
56 | for i < jj {
57 | if V[I[i]+h] < x {
58 | i++
59 | } else if V[I[i]+h] == x {
60 | swap(I, i, jj+j)
61 | j++
62 | } else {
63 | swap(I, i, kk+k)
64 | k++
65 | }
66 | }
67 |
68 | for jj+j < kk {
69 | if V[I[jj+j]+h] == x {
70 | j++
71 | } else {
72 | swap(I, jj+j, kk+k)
73 | k++
74 | }
75 | }
76 |
77 | if jj > start {
78 | split(I, V, start, jj-start, h)
79 | }
80 |
81 | for i = 0; i < kk-jj; i++ {
82 | V[I[jj+i]] = kk - 1
83 | }
84 | if jj == kk-1 {
85 | I[jj] = -1
86 | }
87 |
88 | if start+length > kk {
89 | split(I, V, kk, start+length-kk, h)
90 | }
91 | }
92 |
93 | func qsufsort(obuf []byte) []int {
94 | var buckets [256]int
95 | var i, h int
96 | I := make([]int, len(obuf)+1)
97 | V := make([]int, len(obuf)+1)
98 |
99 | for _, c := range obuf {
100 | buckets[c]++
101 | }
102 | for i = 1; i < 256; i++ {
103 | buckets[i] += buckets[i-1]
104 | }
105 | copy(buckets[1:], buckets[:])
106 | buckets[0] = 0
107 |
108 | for i, c := range obuf {
109 | buckets[c]++
110 | I[buckets[c]] = i
111 | }
112 |
113 | I[0] = len(obuf)
114 | for i, c := range obuf {
115 | V[i] = buckets[c]
116 | }
117 |
118 | V[len(obuf)] = 0
119 | for i = 1; i < 256; i++ {
120 | if buckets[i] == buckets[i-1]+1 {
121 | I[buckets[i]] = -1
122 | }
123 | }
124 | I[0] = -1
125 |
126 | for h = 1; I[0] != -(len(obuf) + 1); h += h {
127 | var n int
128 | for i = 0; i < len(obuf)+1; {
129 | if I[i] < 0 {
130 | n -= I[i]
131 | i -= I[i]
132 | } else {
133 | if n != 0 {
134 | I[i-n] = -n
135 | }
136 | n = V[I[i]] + 1 - i
137 | split(I, V, i, n, h)
138 | i += n
139 | n = 0
140 | }
141 | }
142 | if n != 0 {
143 | I[i-n] = -n
144 | }
145 | }
146 |
147 | for i = 0; i < len(obuf)+1; i++ {
148 | I[V[i]] = i
149 | }
150 | return I
151 | }
152 |
153 | func matchlen(a, b []byte) (i int) {
154 | for i < len(a) && i < len(b) && a[i] == b[i] {
155 | i++
156 | }
157 | return i
158 | }
159 |
160 | func search(I []int, obuf, nbuf []byte, st, en int) (pos, n int) {
161 | if en-st < 2 {
162 | x := matchlen(obuf[I[st]:], nbuf)
163 | y := matchlen(obuf[I[en]:], nbuf)
164 |
165 | if x > y {
166 | return I[st], x
167 | } else {
168 | return I[en], y
169 | }
170 | }
171 |
172 | x := st + (en-st)/2
173 | if bytes.Compare(obuf[I[x]:], nbuf) < 0 {
174 | return search(I, obuf, nbuf, x, en)
175 | } else {
176 | return search(I, obuf, nbuf, st, x)
177 | }
178 | panic("unreached")
179 | }
180 |
181 | // Diff computes the difference between old and new, according to the bsdiff
182 | // algorithm, and writes the result to patch.
183 | func Diff(old, new io.Reader, patch io.Writer) error {
184 | obuf, err := ioutil.ReadAll(old)
185 | if err != nil {
186 | return err
187 | }
188 |
189 | nbuf, err := ioutil.ReadAll(new)
190 | if err != nil {
191 | return err
192 | }
193 |
194 | pbuf, err := diffBytes(obuf, nbuf)
195 | if err != nil {
196 | return err
197 | }
198 |
199 | _, err = patch.Write(pbuf)
200 | return err
201 | }
202 |
203 | func diffBytes(obuf, nbuf []byte) ([]byte, error) {
204 | var patch seekBuffer
205 | err := diff(obuf, nbuf, &patch)
206 | if err != nil {
207 | return nil, err
208 | }
209 | return patch.buf, nil
210 | }
211 |
212 | func diff(obuf, nbuf []byte, patch io.WriteSeeker) error {
213 | var lenf int
214 | I := qsufsort(obuf)
215 | db := make([]byte, len(nbuf))
216 | eb := make([]byte, len(nbuf))
217 | var dblen, eblen int
218 |
219 | var hdr header
220 | hdr.Magic = magic
221 | hdr.NewSize = int64(len(nbuf))
222 | err := binary.Write(patch, signMagLittleEndian{}, &hdr)
223 | if err != nil {
224 | return err
225 | }
226 |
227 | // Compute the differences, writing ctrl as we go
228 | pfbz2, err := newBzip2Writer(patch)
229 | if err != nil {
230 | return err
231 | }
232 | var scan, pos, length int
233 | var lastscan, lastpos, lastoffset int
234 | for scan < len(nbuf) {
235 | var oldscore int
236 | scan += length
237 | for scsc := scan; scan < len(nbuf); scan++ {
238 | pos, length = search(I, obuf, nbuf[scan:], 0, len(obuf))
239 |
240 | for ; scsc < scan+length; scsc++ {
241 | if scsc+lastoffset < len(obuf) &&
242 | obuf[scsc+lastoffset] == nbuf[scsc] {
243 | oldscore++
244 | }
245 | }
246 |
247 | if (length == oldscore && length != 0) || length > oldscore+8 {
248 | break
249 | }
250 |
251 | if scan+lastoffset < len(obuf) && obuf[scan+lastoffset] == nbuf[scan] {
252 | oldscore--
253 | }
254 | }
255 |
256 | if length != oldscore || scan == len(nbuf) {
257 | var s, Sf int
258 | lenf = 0
259 | for i := 0; lastscan+i < scan && lastpos+i < len(obuf); {
260 | if obuf[lastpos+i] == nbuf[lastscan+i] {
261 | s++
262 | }
263 | i++
264 | if s*2-i > Sf*2-lenf {
265 | Sf = s
266 | lenf = i
267 | }
268 | }
269 |
270 | lenb := 0
271 | if scan < len(nbuf) {
272 | var s, Sb int
273 | for i := 1; (scan >= lastscan+i) && (pos >= i); i++ {
274 | if obuf[pos-i] == nbuf[scan-i] {
275 | s++
276 | }
277 | if s*2-i > Sb*2-lenb {
278 | Sb = s
279 | lenb = i
280 | }
281 | }
282 | }
283 |
284 | if lastscan+lenf > scan-lenb {
285 | overlap := (lastscan + lenf) - (scan - lenb)
286 | s := 0
287 | Ss := 0
288 | lens := 0
289 | for i := 0; i < overlap; i++ {
290 | if nbuf[lastscan+lenf-overlap+i] == obuf[lastpos+lenf-overlap+i] {
291 | s++
292 | }
293 | if nbuf[scan-lenb+i] == obuf[pos-lenb+i] {
294 | s--
295 | }
296 | if s > Ss {
297 | Ss = s
298 | lens = i + 1
299 | }
300 | }
301 |
302 | lenf += lens - overlap
303 | lenb -= lens
304 | }
305 |
306 | for i := 0; i < lenf; i++ {
307 | db[dblen+i] = nbuf[lastscan+i] - obuf[lastpos+i]
308 | }
309 | for i := 0; i < (scan-lenb)-(lastscan+lenf); i++ {
310 | eb[eblen+i] = nbuf[lastscan+lenf+i]
311 | }
312 |
313 | dblen += lenf
314 | eblen += (scan - lenb) - (lastscan + lenf)
315 |
316 | err = binary.Write(pfbz2, signMagLittleEndian{}, int64(lenf))
317 | if err != nil {
318 | pfbz2.Close()
319 | return err
320 | }
321 |
322 | val := (scan - lenb) - (lastscan + lenf)
323 | err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val))
324 | if err != nil {
325 | pfbz2.Close()
326 | return err
327 | }
328 |
329 | val = (pos - lenb) - (lastpos + lenf)
330 | err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val))
331 | if err != nil {
332 | pfbz2.Close()
333 | return err
334 | }
335 |
336 | lastscan = scan - lenb
337 | lastpos = pos - lenb
338 | lastoffset = pos - scan
339 | }
340 | }
341 | err = pfbz2.Close()
342 | if err != nil {
343 | return err
344 | }
345 |
346 | // Compute size of compressed ctrl data
347 | l64, err := patch.Seek(0, 1)
348 | if err != nil {
349 | return err
350 | }
351 | hdr.CtrlLen = int64(l64 - 32)
352 |
353 | // Write compressed diff data
354 | pfbz2, err = newBzip2Writer(patch)
355 | if err != nil {
356 | return err
357 | }
358 | n, err := pfbz2.Write(db[:dblen])
359 | if err != nil {
360 | pfbz2.Close()
361 | return err
362 | }
363 | if n != dblen {
364 | pfbz2.Close()
365 | return io.ErrShortWrite
366 | }
367 | err = pfbz2.Close()
368 | if err != nil {
369 | return err
370 | }
371 |
372 | // Compute size of compressed diff data
373 | n64, err := patch.Seek(0, 1)
374 | if err != nil {
375 | return err
376 | }
377 | hdr.DiffLen = n64 - l64
378 |
379 | // Write compressed extra data
380 | pfbz2, err = newBzip2Writer(patch)
381 | if err != nil {
382 | return err
383 | }
384 | n, err = pfbz2.Write(eb[:eblen])
385 | if err != nil {
386 | pfbz2.Close()
387 | return err
388 | }
389 | if n != eblen {
390 | pfbz2.Close()
391 | return io.ErrShortWrite
392 | }
393 | err = pfbz2.Close()
394 | if err != nil {
395 | return err
396 | }
397 |
398 | // Seek to the beginning, write the header, and close the file
399 | _, err = patch.Seek(0, 0)
400 | if err != nil {
401 | return err
402 | }
403 | err = binary.Write(patch, signMagLittleEndian{}, &hdr)
404 | if err != nil {
405 | return err
406 | }
407 | return nil
408 | }
409 |
--------------------------------------------------------------------------------
/internal/binarydist/diff_test.go:
--------------------------------------------------------------------------------
1 | package binarydist
2 |
3 | import (
4 | "bytes"
5 | "io/ioutil"
6 | "os"
7 | "os/exec"
8 | "testing"
9 | )
10 |
11 | var diffT = []struct {
12 | old *os.File
13 | new *os.File
14 | }{
15 | {
16 | old: mustWriteRandFile("test.old", 1e3),
17 | new: mustWriteRandFile("test.new", 1e3),
18 | },
19 | {
20 | old: mustOpen("testdata/sample.old"),
21 | new: mustOpen("testdata/sample.new"),
22 | },
23 | }
24 |
25 | func TestDiff(t *testing.T) {
26 | for _, s := range diffT {
27 | got, err := ioutil.TempFile("/tmp", "bspatch.")
28 | if err != nil {
29 | panic(err)
30 | }
31 | os.Remove(got.Name())
32 |
33 | exp, err := ioutil.TempFile("/tmp", "bspatch.")
34 | if err != nil {
35 | panic(err)
36 | }
37 |
38 | cmd := exec.Command("bsdiff", s.old.Name(), s.new.Name(), exp.Name())
39 | cmd.Stdout = os.Stdout
40 | err = cmd.Run()
41 | os.Remove(exp.Name())
42 | if err != nil {
43 | panic(err)
44 | }
45 |
46 | err = Diff(s.old, s.new, got)
47 | if err != nil {
48 | t.Fatal("err", err)
49 | }
50 |
51 | _, err = got.Seek(0, 0)
52 | if err != nil {
53 | panic(err)
54 | }
55 | gotBuf := mustReadAll(got)
56 | expBuf := mustReadAll(exp)
57 |
58 | if !bytes.Equal(gotBuf, expBuf) {
59 | t.Fail()
60 | t.Logf("diff %s %s", s.old.Name(), s.new.Name())
61 | t.Logf("%s: len(got) = %d", got.Name(), len(gotBuf))
62 | t.Logf("%s: len(exp) = %d", exp.Name(), len(expBuf))
63 | i := matchlen(gotBuf, expBuf)
64 | t.Logf("produced different output at pos %d; %d != %d", i, gotBuf[i], expBuf[i])
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/internal/binarydist/doc.go:
--------------------------------------------------------------------------------
1 | // Package binarydist implements binary diff and patch as described on
2 | // http://www.daemonology.net/bsdiff/. It reads and writes files
3 | // compatible with the tools there.
4 | package binarydist
5 |
6 | var magic = [8]byte{'B', 'S', 'D', 'I', 'F', 'F', '4', '0'}
7 |
8 | // File format:
9 | // 0 8 "BSDIFF40"
10 | // 8 8 X
11 | // 16 8 Y
12 | // 24 8 sizeof(newfile)
13 | // 32 X bzip2(control block)
14 | // 32+X Y bzip2(diff block)
15 | // 32+X+Y ??? bzip2(extra block)
16 | // with control block a set of triples (x,y,z) meaning "add x bytes
17 | // from oldfile to x bytes from the diff block; copy y bytes from the
18 | // extra block; seek forwards in oldfile by z bytes".
19 | type header struct {
20 | Magic [8]byte
21 | CtrlLen int64
22 | DiffLen int64
23 | NewSize int64
24 | }
25 |
--------------------------------------------------------------------------------
/internal/binarydist/encoding.go:
--------------------------------------------------------------------------------
1 | package binarydist
2 |
3 | // SignMagLittleEndian is the numeric encoding used by the bsdiff tools.
4 | // It implements binary.ByteOrder using a sign-magnitude format
5 | // and little-endian byte order. Only methods Uint64 and String
6 | // have been written; the rest panic.
7 | type signMagLittleEndian struct{}
8 |
9 | func (signMagLittleEndian) Uint16(b []byte) uint16 { panic("unimplemented") }
10 |
11 | func (signMagLittleEndian) PutUint16(b []byte, v uint16) { panic("unimplemented") }
12 |
13 | func (signMagLittleEndian) Uint32(b []byte) uint32 { panic("unimplemented") }
14 |
15 | func (signMagLittleEndian) PutUint32(b []byte, v uint32) { panic("unimplemented") }
16 |
17 | func (signMagLittleEndian) Uint64(b []byte) uint64 {
18 | y := int64(b[0]) |
19 | int64(b[1])<<8 |
20 | int64(b[2])<<16 |
21 | int64(b[3])<<24 |
22 | int64(b[4])<<32 |
23 | int64(b[5])<<40 |
24 | int64(b[6])<<48 |
25 | int64(b[7]&0x7f)<<56
26 |
27 | if b[7]&0x80 != 0 {
28 | y = -y
29 | }
30 | return uint64(y)
31 | }
32 |
33 | func (signMagLittleEndian) PutUint64(b []byte, v uint64) {
34 | x := int64(v)
35 | neg := x < 0
36 | if neg {
37 | x = -x
38 | }
39 |
40 | b[0] = byte(x)
41 | b[1] = byte(x >> 8)
42 | b[2] = byte(x >> 16)
43 | b[3] = byte(x >> 24)
44 | b[4] = byte(x >> 32)
45 | b[5] = byte(x >> 40)
46 | b[6] = byte(x >> 48)
47 | b[7] = byte(x >> 56)
48 | if neg {
49 | b[7] |= 0x80
50 | }
51 | }
52 |
53 | func (signMagLittleEndian) String() string { return "signMagLittleEndian" }
54 |
--------------------------------------------------------------------------------
/internal/binarydist/patch.go:
--------------------------------------------------------------------------------
1 | package binarydist
2 |
3 | import (
4 | "bytes"
5 | "compress/bzip2"
6 | "encoding/binary"
7 | "errors"
8 | "io"
9 | "io/ioutil"
10 | )
11 |
12 | var ErrCorrupt = errors.New("corrupt patch")
13 |
14 | // Patch applies patch to old, according to the bspatch algorithm,
15 | // and writes the result to new.
16 | func Patch(old io.Reader, new io.Writer, patch io.Reader) error {
17 | var hdr header
18 | err := binary.Read(patch, signMagLittleEndian{}, &hdr)
19 | if err != nil {
20 | return err
21 | }
22 | if hdr.Magic != magic {
23 | return ErrCorrupt
24 | }
25 | if hdr.CtrlLen < 0 || hdr.DiffLen < 0 || hdr.NewSize < 0 {
26 | return ErrCorrupt
27 | }
28 |
29 | ctrlbuf := make([]byte, hdr.CtrlLen)
30 | _, err = io.ReadFull(patch, ctrlbuf)
31 | if err != nil {
32 | return err
33 | }
34 | cpfbz2 := bzip2.NewReader(bytes.NewReader(ctrlbuf))
35 |
36 | diffbuf := make([]byte, hdr.DiffLen)
37 | _, err = io.ReadFull(patch, diffbuf)
38 | if err != nil {
39 | return err
40 | }
41 | dpfbz2 := bzip2.NewReader(bytes.NewReader(diffbuf))
42 |
43 | // The entire rest of the file is the extra block.
44 | epfbz2 := bzip2.NewReader(patch)
45 |
46 | obuf, err := ioutil.ReadAll(old)
47 | if err != nil {
48 | return err
49 | }
50 |
51 | nbuf := make([]byte, hdr.NewSize)
52 |
53 | var oldpos, newpos int64
54 | for newpos < hdr.NewSize {
55 | var ctrl struct{ Add, Copy, Seek int64 }
56 | err = binary.Read(cpfbz2, signMagLittleEndian{}, &ctrl)
57 | if err != nil {
58 | return err
59 | }
60 |
61 | // Sanity-check
62 | if newpos+ctrl.Add > hdr.NewSize {
63 | return ErrCorrupt
64 | }
65 |
66 | // Read diff string
67 | _, err = io.ReadFull(dpfbz2, nbuf[newpos:newpos+ctrl.Add])
68 | if err != nil {
69 | return ErrCorrupt
70 | }
71 |
72 | // Add old data to diff string
73 | for i := int64(0); i < ctrl.Add; i++ {
74 | if oldpos+i >= 0 && oldpos+i < int64(len(obuf)) {
75 | nbuf[newpos+i] += obuf[oldpos+i]
76 | }
77 | }
78 |
79 | // Adjust pointers
80 | newpos += ctrl.Add
81 | oldpos += ctrl.Add
82 |
83 | // Sanity-check
84 | if newpos+ctrl.Copy > hdr.NewSize {
85 | return ErrCorrupt
86 | }
87 |
88 | // Read extra string
89 | _, err = io.ReadFull(epfbz2, nbuf[newpos:newpos+ctrl.Copy])
90 | if err != nil {
91 | return ErrCorrupt
92 | }
93 |
94 | // Adjust pointers
95 | newpos += ctrl.Copy
96 | oldpos += ctrl.Seek
97 | }
98 |
99 | // Write the new file
100 | for len(nbuf) > 0 {
101 | n, err := new.Write(nbuf)
102 | if err != nil {
103 | return err
104 | }
105 | nbuf = nbuf[n:]
106 | }
107 |
108 | return nil
109 | }
110 |
--------------------------------------------------------------------------------
/internal/binarydist/patch_test.go:
--------------------------------------------------------------------------------
1 | package binarydist
2 |
3 | import (
4 | "io/ioutil"
5 | "os"
6 | "os/exec"
7 | "testing"
8 | )
9 |
10 | func TestPatch(t *testing.T) {
11 | mustWriteRandFile("test.old", 1e3)
12 | mustWriteRandFile("test.new", 1e3)
13 |
14 | got, err := ioutil.TempFile("/tmp", "bspatch.")
15 | if err != nil {
16 | panic(err)
17 | }
18 | os.Remove(got.Name())
19 |
20 | err = exec.Command("bsdiff", "test.old", "test.new", "test.patch").Run()
21 | if err != nil {
22 | panic(err)
23 | }
24 |
25 | err = Patch(mustOpen("test.old"), got, mustOpen("test.patch"))
26 | if err != nil {
27 | t.Fatal("err", err)
28 | }
29 |
30 | ref, err := got.Seek(0, 2)
31 | if err != nil {
32 | panic(err)
33 | }
34 |
35 | t.Logf("got %d bytes", ref)
36 | if n := fileCmp(got, mustOpen("test.new")); n > -1 {
37 | t.Fatalf("produced different output at pos %d", n)
38 | }
39 | }
40 |
41 | func TestPatchHk(t *testing.T) {
42 | got, err := ioutil.TempFile("/tmp", "bspatch.")
43 | if err != nil {
44 | panic(err)
45 | }
46 | os.Remove(got.Name())
47 |
48 | err = Patch(mustOpen("testdata/sample.old"), got, mustOpen("testdata/sample.patch"))
49 | if err != nil {
50 | t.Fatal("err", err)
51 | }
52 |
53 | ref, err := got.Seek(0, 2)
54 | if err != nil {
55 | panic(err)
56 | }
57 |
58 | t.Logf("got %d bytes", ref)
59 | if n := fileCmp(got, mustOpen("testdata/sample.new")); n > -1 {
60 | t.Fatalf("produced different output at pos %d", n)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/internal/binarydist/seek.go:
--------------------------------------------------------------------------------
1 | package binarydist
2 |
3 | import (
4 | "errors"
5 | )
6 |
7 | type seekBuffer struct {
8 | buf []byte
9 | pos int
10 | }
11 |
12 | func (b *seekBuffer) Write(p []byte) (n int, err error) {
13 | n = copy(b.buf[b.pos:], p)
14 | if n == len(p) {
15 | b.pos += n
16 | return n, nil
17 | }
18 | b.buf = append(b.buf, p[n:]...)
19 | b.pos += len(p)
20 | return len(p), nil
21 | }
22 |
23 | func (b *seekBuffer) Seek(offset int64, whence int) (ret int64, err error) {
24 | var abs int64
25 | switch whence {
26 | case 0:
27 | abs = offset
28 | case 1:
29 | abs = int64(b.pos) + offset
30 | case 2:
31 | abs = int64(len(b.buf)) + offset
32 | default:
33 | return 0, errors.New("binarydist: invalid whence")
34 | }
35 | if abs < 0 {
36 | return 0, errors.New("binarydist: negative position")
37 | }
38 | if abs >= 1<<31 {
39 | return 0, errors.New("binarydist: position out of range")
40 | }
41 | b.pos = int(abs)
42 | return abs, nil
43 | }
44 |
--------------------------------------------------------------------------------
/internal/binarydist/sort_test.go:
--------------------------------------------------------------------------------
1 | package binarydist
2 |
3 | import (
4 | "bytes"
5 | "crypto/rand"
6 | "testing"
7 | )
8 |
9 | var sortT = [][]byte{
10 | mustRandBytes(1000),
11 | mustReadAll(mustOpen("test.old")),
12 | []byte("abcdefabcdef"),
13 | }
14 |
15 | func TestQsufsort(t *testing.T) {
16 | for _, s := range sortT {
17 | I := qsufsort(s)
18 | for i := 1; i < len(I); i++ {
19 | if bytes.Compare(s[I[i-1]:], s[I[i]:]) > 0 {
20 | t.Fatalf("unsorted at %d", i)
21 | }
22 | }
23 | }
24 | }
25 |
26 | func mustRandBytes(n int) []byte {
27 | b := make([]byte, n)
28 | _, err := rand.Read(b)
29 | if err != nil {
30 | panic(err)
31 | }
32 | return b
33 | }
34 |
--------------------------------------------------------------------------------
/internal/binarydist/testdata/sample.new:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inconshreveable/go-update/8152e7eb6ccf8679a64582a66b78519688d156ad/internal/binarydist/testdata/sample.new
--------------------------------------------------------------------------------
/internal/binarydist/testdata/sample.old:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inconshreveable/go-update/8152e7eb6ccf8679a64582a66b78519688d156ad/internal/binarydist/testdata/sample.old
--------------------------------------------------------------------------------
/internal/binarydist/testdata/sample.patch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inconshreveable/go-update/8152e7eb6ccf8679a64582a66b78519688d156ad/internal/binarydist/testdata/sample.patch
--------------------------------------------------------------------------------
/internal/osext/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 The Go Authors. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above
10 | copyright notice, this list of conditions and the following disclaimer
11 | in the documentation and/or other materials provided with the
12 | distribution.
13 | * Neither the name of Google Inc. nor the names of its
14 | contributors may be used to endorse or promote products derived from
15 | this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/internal/osext/README.md:
--------------------------------------------------------------------------------
1 | ### Extensions to the "os" package.
2 |
3 | ## Find the current Executable and ExecutableFolder.
4 |
5 | There is sometimes utility in finding the current executable file
6 | that is running. This can be used for upgrading the current executable
7 | or finding resources located relative to the executable file. Both
8 | working directory and the os.Args[0] value are arbitrary and cannot
9 | be relied on; os.Args[0] can be "faked".
10 |
11 | Multi-platform and supports:
12 | * Linux
13 | * OS X
14 | * Windows
15 | * Plan 9
16 | * BSDs.
17 |
--------------------------------------------------------------------------------
/internal/osext/osext.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // Extensions to the standard "os" package.
6 | package osext
7 |
8 | import "path/filepath"
9 |
10 | // Executable returns an absolute path that can be used to
11 | // re-invoke the current program.
12 | // It may not be valid after the current program exits.
13 | func Executable() (string, error) {
14 | p, err := executable()
15 | return filepath.Clean(p), err
16 | }
17 |
18 | // Returns same path as Executable, returns just the folder
19 | // path. Excludes the executable name and any trailing slash.
20 | func ExecutableFolder() (string, error) {
21 | p, err := Executable()
22 | if err != nil {
23 | return "", err
24 | }
25 |
26 | return filepath.Dir(p), nil
27 | }
28 |
--------------------------------------------------------------------------------
/internal/osext/osext_plan9.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package osext
6 |
7 | import (
8 | "os"
9 | "strconv"
10 | "syscall"
11 | )
12 |
13 | func executable() (string, error) {
14 | f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text")
15 | if err != nil {
16 | return "", err
17 | }
18 | defer f.Close()
19 | return syscall.Fd2path(int(f.Fd()))
20 | }
21 |
--------------------------------------------------------------------------------
/internal/osext/osext_procfs.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build linux netbsd openbsd solaris dragonfly
6 |
7 | package osext
8 |
9 | import (
10 | "errors"
11 | "fmt"
12 | "os"
13 | "runtime"
14 | "strings"
15 | )
16 |
17 | func executable() (string, error) {
18 | switch runtime.GOOS {
19 | case "linux":
20 | const deletedTag = " (deleted)"
21 | execpath, err := os.Readlink("/proc/self/exe")
22 | if err != nil {
23 | return execpath, err
24 | }
25 | execpath = strings.TrimSuffix(execpath, deletedTag)
26 | execpath = strings.TrimPrefix(execpath, deletedTag)
27 | return execpath, nil
28 | case "netbsd":
29 | return os.Readlink("/proc/curproc/exe")
30 | case "openbsd", "dragonfly":
31 | return os.Readlink("/proc/curproc/file")
32 | case "solaris":
33 | return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid()))
34 | }
35 | return "", errors.New("ExecPath not implemented for " + runtime.GOOS)
36 | }
37 |
--------------------------------------------------------------------------------
/internal/osext/osext_sysctl.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build darwin freebsd
6 |
7 | package osext
8 |
9 | import (
10 | "os"
11 | "path/filepath"
12 | "runtime"
13 | "syscall"
14 | "unsafe"
15 | )
16 |
17 | var initCwd, initCwdErr = os.Getwd()
18 |
19 | func executable() (string, error) {
20 | var mib [4]int32
21 | switch runtime.GOOS {
22 | case "freebsd":
23 | mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1}
24 | case "darwin":
25 | mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1}
26 | }
27 |
28 | n := uintptr(0)
29 | // Get length.
30 | _, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0)
31 | if errNum != 0 {
32 | return "", errNum
33 | }
34 | if n == 0 { // This shouldn't happen.
35 | return "", nil
36 | }
37 | buf := make([]byte, n)
38 | _, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0)
39 | if errNum != 0 {
40 | return "", errNum
41 | }
42 | if n == 0 { // This shouldn't happen.
43 | return "", nil
44 | }
45 | for i, v := range buf {
46 | if v == 0 {
47 | buf = buf[:i]
48 | break
49 | }
50 | }
51 | var err error
52 | execPath := string(buf)
53 | // execPath will not be empty due to above checks.
54 | // Try to get the absolute path if the execPath is not rooted.
55 | if execPath[0] != '/' {
56 | execPath, err = getAbs(execPath)
57 | if err != nil {
58 | return execPath, err
59 | }
60 | }
61 | // For darwin KERN_PROCARGS may return the path to a symlink rather than the
62 | // actual executable.
63 | if runtime.GOOS == "darwin" {
64 | if execPath, err = filepath.EvalSymlinks(execPath); err != nil {
65 | return execPath, err
66 | }
67 | }
68 | return execPath, nil
69 | }
70 |
71 | func getAbs(execPath string) (string, error) {
72 | if initCwdErr != nil {
73 | return execPath, initCwdErr
74 | }
75 | // The execPath may begin with a "../" or a "./" so clean it first.
76 | // Join the two paths, trailing and starting slashes undetermined, so use
77 | // the generic Join function.
78 | return filepath.Join(initCwd, filepath.Clean(execPath)), nil
79 | }
80 |
--------------------------------------------------------------------------------
/internal/osext/osext_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build darwin linux freebsd netbsd windows
6 |
7 | package osext
8 |
9 | import (
10 | "bytes"
11 | "fmt"
12 | "io"
13 | "os"
14 | "os/exec"
15 | "path/filepath"
16 | "runtime"
17 | "testing"
18 | )
19 |
20 | const (
21 | executableEnvVar = "OSTEST_OUTPUT_EXECUTABLE"
22 |
23 | executableEnvValueMatch = "match"
24 | executableEnvValueDelete = "delete"
25 | )
26 |
27 | func TestPrintExecutable(t *testing.T) {
28 | ef, err := Executable()
29 | if err != nil {
30 | t.Fatalf("Executable failed: %v", err)
31 | }
32 | t.Log("Executable:", ef)
33 | }
34 | func TestPrintExecutableFolder(t *testing.T) {
35 | ef, err := ExecutableFolder()
36 | if err != nil {
37 | t.Fatalf("ExecutableFolder failed: %v", err)
38 | }
39 | t.Log("Executable Folder:", ef)
40 | }
41 | func TestExecutableFolder(t *testing.T) {
42 | ef, err := ExecutableFolder()
43 | if err != nil {
44 | t.Fatalf("ExecutableFolder failed: %v", err)
45 | }
46 | if ef[len(ef)-1] == filepath.Separator {
47 | t.Fatal("ExecutableFolder ends with a trailing slash.")
48 | }
49 | }
50 | func TestExecutableMatch(t *testing.T) {
51 | ep, err := Executable()
52 | if err != nil {
53 | t.Fatalf("Executable failed: %v", err)
54 | }
55 |
56 | // fullpath to be of the form "dir/prog".
57 | dir := filepath.Dir(filepath.Dir(ep))
58 | fullpath, err := filepath.Rel(dir, ep)
59 | if err != nil {
60 | t.Fatalf("filepath.Rel: %v", err)
61 | }
62 | // Make child start with a relative program path.
63 | // Alter argv[0] for child to verify getting real path without argv[0].
64 | cmd := &exec.Cmd{
65 | Dir: dir,
66 | Path: fullpath,
67 | Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueMatch)},
68 | }
69 | out, err := cmd.CombinedOutput()
70 | if err != nil {
71 | t.Fatalf("exec(self) failed: %v", err)
72 | }
73 | outs := string(out)
74 | if !filepath.IsAbs(outs) {
75 | t.Fatalf("Child returned %q, want an absolute path", out)
76 | }
77 | if !sameFile(outs, ep) {
78 | t.Fatalf("Child returned %q, not the same file as %q", out, ep)
79 | }
80 | }
81 |
82 | func TestExecutableDelete(t *testing.T) {
83 | if runtime.GOOS != "linux" {
84 | t.Skip()
85 | }
86 | fpath, err := Executable()
87 | if err != nil {
88 | t.Fatalf("Executable failed: %v", err)
89 | }
90 |
91 | r, w := io.Pipe()
92 | stderrBuff := &bytes.Buffer{}
93 | stdoutBuff := &bytes.Buffer{}
94 | cmd := &exec.Cmd{
95 | Path: fpath,
96 | Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueDelete)},
97 | Stdin: r,
98 | Stderr: stderrBuff,
99 | Stdout: stdoutBuff,
100 | }
101 | err = cmd.Start()
102 | if err != nil {
103 | t.Fatalf("exec(self) start failed: %v", err)
104 | }
105 |
106 | tempPath := fpath + "_copy"
107 | _ = os.Remove(tempPath)
108 |
109 | err = copyFile(tempPath, fpath)
110 | if err != nil {
111 | t.Fatalf("copy file failed: %v", err)
112 | }
113 | err = os.Remove(fpath)
114 | if err != nil {
115 | t.Fatalf("remove running test file failed: %v", err)
116 | }
117 | err = os.Rename(tempPath, fpath)
118 | if err != nil {
119 | t.Fatalf("rename copy to previous name failed: %v", err)
120 | }
121 |
122 | w.Write([]byte{0})
123 | w.Close()
124 |
125 | err = cmd.Wait()
126 | if err != nil {
127 | t.Fatalf("exec wait failed: %v", err)
128 | }
129 |
130 | childPath := stderrBuff.String()
131 | if !filepath.IsAbs(childPath) {
132 | t.Fatalf("Child returned %q, want an absolute path", childPath)
133 | }
134 | if !sameFile(childPath, fpath) {
135 | t.Fatalf("Child returned %q, not the same file as %q", childPath, fpath)
136 | }
137 | }
138 |
139 | func sameFile(fn1, fn2 string) bool {
140 | fi1, err := os.Stat(fn1)
141 | if err != nil {
142 | return false
143 | }
144 | fi2, err := os.Stat(fn2)
145 | if err != nil {
146 | return false
147 | }
148 | return os.SameFile(fi1, fi2)
149 | }
150 | func copyFile(dest, src string) error {
151 | df, err := os.Create(dest)
152 | if err != nil {
153 | return err
154 | }
155 | defer df.Close()
156 |
157 | sf, err := os.Open(src)
158 | if err != nil {
159 | return err
160 | }
161 | defer sf.Close()
162 |
163 | _, err = io.Copy(df, sf)
164 | return err
165 | }
166 |
167 | func TestMain(m *testing.M) {
168 | env := os.Getenv(executableEnvVar)
169 | switch env {
170 | case "":
171 | os.Exit(m.Run())
172 | case executableEnvValueMatch:
173 | // First chdir to another path.
174 | dir := "/"
175 | if runtime.GOOS == "windows" {
176 | dir = filepath.VolumeName(".")
177 | }
178 | os.Chdir(dir)
179 | if ep, err := Executable(); err != nil {
180 | fmt.Fprint(os.Stderr, "ERROR: ", err)
181 | } else {
182 | fmt.Fprint(os.Stderr, ep)
183 | }
184 | case executableEnvValueDelete:
185 | bb := make([]byte, 1)
186 | var err error
187 | n, err := os.Stdin.Read(bb)
188 | if err != nil {
189 | fmt.Fprint(os.Stderr, "ERROR: ", err)
190 | os.Exit(2)
191 | }
192 | if n != 1 {
193 | fmt.Fprint(os.Stderr, "ERROR: n != 1, n == ", n)
194 | os.Exit(2)
195 | }
196 | if ep, err := Executable(); err != nil {
197 | fmt.Fprint(os.Stderr, "ERROR: ", err)
198 | } else {
199 | fmt.Fprint(os.Stderr, ep)
200 | }
201 | }
202 | os.Exit(0)
203 | }
204 |
--------------------------------------------------------------------------------
/internal/osext/osext_windows.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package osext
6 |
7 | import (
8 | "syscall"
9 | "unicode/utf16"
10 | "unsafe"
11 | )
12 |
13 | var (
14 | kernel = syscall.MustLoadDLL("kernel32.dll")
15 | getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW")
16 | )
17 |
18 | // GetModuleFileName() with hModule = NULL
19 | func executable() (exePath string, err error) {
20 | return getModuleFileName()
21 | }
22 |
23 | func getModuleFileName() (string, error) {
24 | var n uint32
25 | b := make([]uint16, syscall.MAX_PATH)
26 | size := uint32(len(b))
27 |
28 | r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size))
29 | n = uint32(r0)
30 | if n == 0 {
31 | return "", e1
32 | }
33 | return string(utf16.Decode(b[0:n])), nil
34 | }
35 |
--------------------------------------------------------------------------------
/patcher.go:
--------------------------------------------------------------------------------
1 | package update
2 |
3 | import (
4 | "io"
5 |
6 | "github.com/inconshreveable/go-update/internal/binarydist"
7 | )
8 |
9 | // Patcher defines an interface for applying binary patches to an old item to get an updated item.
10 | type Patcher interface {
11 | Patch(old io.Reader, new io.Writer, patch io.Reader) error
12 | }
13 |
14 | type patchFn func(io.Reader, io.Writer, io.Reader) error
15 |
16 | func (fn patchFn) Patch(old io.Reader, new io.Writer, patch io.Reader) error {
17 | return fn(old, new, patch)
18 | }
19 |
20 | // NewBSDifferPatcher returns a new Patcher that applies binary patches using
21 | // the bsdiff algorithm. See http://www.daemonology.net/bsdiff/
22 | func NewBSDiffPatcher() Patcher {
23 | return patchFn(binarydist.Patch)
24 | }
25 |
--------------------------------------------------------------------------------
/verifier.go:
--------------------------------------------------------------------------------
1 | package update
2 |
3 | import (
4 | "crypto"
5 | "crypto/dsa"
6 | "crypto/ecdsa"
7 | "crypto/rsa"
8 | "encoding/asn1"
9 | "errors"
10 | "math/big"
11 | )
12 |
13 | // Verifier defines an interface for verfiying an update's signature with a public key.
14 | type Verifier interface {
15 | VerifySignature(checksum, signature []byte, h crypto.Hash, publicKey crypto.PublicKey) error
16 | }
17 |
18 | type verifyFn func([]byte, []byte, crypto.Hash, crypto.PublicKey) error
19 |
20 | func (fn verifyFn) VerifySignature(checksum []byte, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error {
21 | return fn(checksum, signature, hash, publicKey)
22 | }
23 |
24 | // NewRSAVerifier returns a Verifier that uses the RSA algorithm to verify updates.
25 | func NewRSAVerifier() Verifier {
26 | return verifyFn(func(checksum, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error {
27 | key, ok := publicKey.(*rsa.PublicKey)
28 | if !ok {
29 | return errors.New("not a valid RSA public key")
30 | }
31 | return rsa.VerifyPKCS1v15(key, hash, checksum, signature)
32 | })
33 | }
34 |
35 | type rsDER struct {
36 | R *big.Int
37 | S *big.Int
38 | }
39 |
40 | // NewECDSAVerifier returns a Verifier that uses the ECDSA algorithm to verify updates.
41 | func NewECDSAVerifier() Verifier {
42 | return verifyFn(func(checksum, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error {
43 | key, ok := publicKey.(*ecdsa.PublicKey)
44 | if !ok {
45 | return errors.New("not a valid ECDSA public key")
46 | }
47 | var rs rsDER
48 | if _, err := asn1.Unmarshal(signature, &rs); err != nil {
49 | return err
50 | }
51 | if !ecdsa.Verify(key, checksum, rs.R, rs.S) {
52 | return errors.New("failed to verify ecsda signature")
53 | }
54 | return nil
55 | })
56 | }
57 |
58 | // NewDSAVerifier returns a Verifier that uses the DSA algorithm to verify updates.
59 | func NewDSAVerifier() Verifier {
60 | return verifyFn(func(checksum, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error {
61 | key, ok := publicKey.(*dsa.PublicKey)
62 | if !ok {
63 | return errors.New("not a valid DSA public key")
64 | }
65 | var rs rsDER
66 | if _, err := asn1.Unmarshal(signature, &rs); err != nil {
67 | return err
68 | }
69 | if !dsa.Verify(key, checksum, rs.R, rs.S) {
70 | return errors.New("failed to verify ecsda signature")
71 | }
72 | return nil
73 | })
74 | }
75 |
--------------------------------------------------------------------------------