From a88bde6abc75fd6ed00c2112b868366b47ead4bc Mon Sep 17 00:00:00 2001 From: Mark Sanborn Date: Fri, 8 Aug 2014 18:12:13 -0700 Subject: [PATCH] more deps --- Godeps/Readme | 5 + Godeps/_workspace/.gitignore | 2 + .../src/bitbucket.org/kardianos/osext/LICENSE | 20 + .../bitbucket.org/kardianos/osext/osext.go | 32 ++ .../kardianos/osext/osext_plan9.go | 20 + .../kardianos/osext/osext_procfs.go | 25 + .../kardianos/osext/osext_sysctl.go | 79 +++ .../kardianos/osext/osext_test.go | 79 +++ .../kardianos/osext/osext_windows.go | 34 ++ .../inconshreveable/go-update/README | 137 ++++++ .../inconshreveable/go-update/update.go | 455 ++++++++++++++++++ .../src/github.com/kr/binarydist/.gitignore | 1 + .../src/github.com/kr/binarydist/License | 22 + .../src/github.com/kr/binarydist/Readme.md | 7 + .../src/github.com/kr/binarydist/bzip2.go | 40 ++ .../github.com/kr/binarydist/common_test.go | 93 ++++ .../src/github.com/kr/binarydist/diff.go | 408 ++++++++++++++++ .../src/github.com/kr/binarydist/diff_test.go | 67 +++ .../src/github.com/kr/binarydist/doc.go | 24 + .../src/github.com/kr/binarydist/encoding.go | 53 ++ .../src/github.com/kr/binarydist/patch.go | 109 +++++ .../github.com/kr/binarydist/patch_test.go | 62 +++ .../src/github.com/kr/binarydist/seek.go | 43 ++ .../src/github.com/kr/binarydist/sort_test.go | 33 ++ .../kr/binarydist/testdata/sample.new | Bin 0 -> 10000 bytes .../kr/binarydist/testdata/sample.old | Bin 0 -> 11000 bytes .../kr/binarydist/testdata/sample.patch | Bin 0 -> 1090 bytes selfupdate/Godeps/Godeps.json | 19 + selfupdate/Godeps/Readme | 5 + selfupdate/Godeps/_workspace/.gitignore | 2 + .../src/bitbucket.org/kardianos/osext/LICENSE | 20 + .../bitbucket.org/kardianos/osext/osext.go | 32 ++ .../kardianos/osext/osext_plan9.go | 20 + .../kardianos/osext/osext_procfs.go | 25 + .../kardianos/osext/osext_sysctl.go | 79 +++ .../kardianos/osext/osext_test.go | 79 +++ .../kardianos/osext/osext_windows.go | 34 ++ .../inconshreveable/go-update/README | 137 ++++++ .../inconshreveable/go-update/update.go | 455 ++++++++++++++++++ .../src/github.com/kr/binarydist/.gitignore | 1 + .../src/github.com/kr/binarydist/License | 22 + .../src/github.com/kr/binarydist/Readme.md | 7 + .../src/github.com/kr/binarydist/bzip2.go | 40 ++ .../github.com/kr/binarydist/common_test.go | 93 ++++ .../src/github.com/kr/binarydist/diff.go | 408 ++++++++++++++++ .../src/github.com/kr/binarydist/diff_test.go | 67 +++ .../src/github.com/kr/binarydist/doc.go | 24 + .../src/github.com/kr/binarydist/encoding.go | 53 ++ .../src/github.com/kr/binarydist/patch.go | 109 +++++ .../github.com/kr/binarydist/patch_test.go | 62 +++ .../src/github.com/kr/binarydist/seek.go | 43 ++ .../src/github.com/kr/binarydist/sort_test.go | 33 ++ .../kr/binarydist/testdata/sample.new | Bin 0 -> 10000 bytes .../kr/binarydist/testdata/sample.old | Bin 0 -> 11000 bytes .../kr/binarydist/testdata/sample.patch | Bin 0 -> 1090 bytes 55 files changed, 3719 insertions(+) create mode 100644 Godeps/Readme create mode 100644 Godeps/_workspace/.gitignore create mode 100644 Godeps/_workspace/src/bitbucket.org/kardianos/osext/LICENSE create mode 100644 Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext.go create mode 100644 Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_plan9.go create mode 100644 Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_procfs.go create mode 100644 Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_sysctl.go create mode 100644 Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_test.go create mode 100644 Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_windows.go create mode 100644 Godeps/_workspace/src/github.com/inconshreveable/go-update/README create mode 100644 Godeps/_workspace/src/github.com/inconshreveable/go-update/update.go create mode 100644 Godeps/_workspace/src/github.com/kr/binarydist/.gitignore create mode 100644 Godeps/_workspace/src/github.com/kr/binarydist/License create mode 100644 Godeps/_workspace/src/github.com/kr/binarydist/Readme.md create mode 100644 Godeps/_workspace/src/github.com/kr/binarydist/bzip2.go create mode 100644 Godeps/_workspace/src/github.com/kr/binarydist/common_test.go create mode 100644 Godeps/_workspace/src/github.com/kr/binarydist/diff.go create mode 100644 Godeps/_workspace/src/github.com/kr/binarydist/diff_test.go create mode 100644 Godeps/_workspace/src/github.com/kr/binarydist/doc.go create mode 100644 Godeps/_workspace/src/github.com/kr/binarydist/encoding.go create mode 100644 Godeps/_workspace/src/github.com/kr/binarydist/patch.go create mode 100644 Godeps/_workspace/src/github.com/kr/binarydist/patch_test.go create mode 100644 Godeps/_workspace/src/github.com/kr/binarydist/seek.go create mode 100644 Godeps/_workspace/src/github.com/kr/binarydist/sort_test.go create mode 100644 Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.new create mode 100644 Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.old create mode 100644 Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.patch create mode 100644 selfupdate/Godeps/Godeps.json create mode 100644 selfupdate/Godeps/Readme create mode 100644 selfupdate/Godeps/_workspace/.gitignore create mode 100644 selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/LICENSE create mode 100644 selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext.go create mode 100644 selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_plan9.go create mode 100644 selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_procfs.go create mode 100644 selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_sysctl.go create mode 100644 selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_test.go create mode 100644 selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_windows.go create mode 100644 selfupdate/Godeps/_workspace/src/github.com/inconshreveable/go-update/README create mode 100644 selfupdate/Godeps/_workspace/src/github.com/inconshreveable/go-update/update.go create mode 100644 selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/.gitignore create mode 100644 selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/License create mode 100644 selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/Readme.md create mode 100644 selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/bzip2.go create mode 100644 selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/common_test.go create mode 100644 selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/diff.go create mode 100644 selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/diff_test.go create mode 100644 selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/doc.go create mode 100644 selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/encoding.go create mode 100644 selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/patch.go create mode 100644 selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/patch_test.go create mode 100644 selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/seek.go create mode 100644 selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/sort_test.go create mode 100644 selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.new create mode 100644 selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.old create mode 100644 selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.patch diff --git a/Godeps/Readme b/Godeps/Readme new file mode 100644 index 0000000..4cdaa53 --- /dev/null +++ b/Godeps/Readme @@ -0,0 +1,5 @@ +This directory tree is generated automatically by godep. + +Please do not edit. + +See https://github.com/tools/godep for more information. diff --git a/Godeps/_workspace/.gitignore b/Godeps/_workspace/.gitignore new file mode 100644 index 0000000..f037d68 --- /dev/null +++ b/Godeps/_workspace/.gitignore @@ -0,0 +1,2 @@ +/pkg +/bin diff --git a/Godeps/_workspace/src/bitbucket.org/kardianos/osext/LICENSE b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/LICENSE new file mode 100644 index 0000000..18527a2 --- /dev/null +++ b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2012 Daniel Theophanes + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. diff --git a/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext.go b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext.go new file mode 100644 index 0000000..37efbb2 --- /dev/null +++ b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext.go @@ -0,0 +1,32 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Extensions to the standard "os" package. +package osext + +import "path/filepath" + +// Executable returns an absolute path that can be used to +// re-invoke the current program. +// It may not be valid after the current program exits. +func Executable() (string, error) { + p, err := executable() + return filepath.Clean(p), err +} + +// Returns same path as Executable, returns just the folder +// path. Excludes the executable name. +func ExecutableFolder() (string, error) { + p, err := Executable() + if err != nil { + return "", err + } + folder, _ := filepath.Split(p) + return folder, nil +} + +// Depricated. Same as Executable(). +func GetExePath() (exePath string, err error) { + return Executable() +} diff --git a/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_plan9.go b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_plan9.go new file mode 100644 index 0000000..4468a73 --- /dev/null +++ b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_plan9.go @@ -0,0 +1,20 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package osext + +import ( + "syscall" + "os" + "strconv" +) + +func executable() (string, error) { + f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text") + if err != nil { + return "", err + } + defer f.Close() + return syscall.Fd2path(int(f.Fd())) +} diff --git a/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_procfs.go b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_procfs.go new file mode 100644 index 0000000..546fec9 --- /dev/null +++ b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_procfs.go @@ -0,0 +1,25 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux netbsd openbsd + +package osext + +import ( + "errors" + "os" + "runtime" +) + +func executable() (string, error) { + switch runtime.GOOS { + case "linux": + return os.Readlink("/proc/self/exe") + case "netbsd": + return os.Readlink("/proc/curproc/exe") + case "openbsd": + return os.Readlink("/proc/curproc/file") + } + return "", errors.New("ExecPath not implemented for " + runtime.GOOS) +} diff --git a/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_sysctl.go b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_sysctl.go new file mode 100644 index 0000000..b66cac8 --- /dev/null +++ b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_sysctl.go @@ -0,0 +1,79 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin freebsd + +package osext + +import ( + "os" + "path/filepath" + "runtime" + "syscall" + "unsafe" +) + +var initCwd, initCwdErr = os.Getwd() + +func executable() (string, error) { + var mib [4]int32 + switch runtime.GOOS { + case "freebsd": + mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1} + case "darwin": + mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1} + } + + n := uintptr(0) + // Get length. + _, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0) + if errNum != 0 { + return "", errNum + } + if n == 0 { // This shouldn't happen. + return "", nil + } + buf := make([]byte, n) + _, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0) + if errNum != 0 { + return "", errNum + } + if n == 0 { // This shouldn't happen. + return "", nil + } + for i, v := range buf { + if v == 0 { + buf = buf[:i] + break + } + } + var err error + execPath := string(buf) + // execPath will not be empty due to above checks. + // Try to get the absolute path if the execPath is not rooted. + if execPath[0] != '/' { + execPath, err = getAbs(execPath) + if err != nil { + return execPath, err + } + } + // For darwin KERN_PROCARGS may return the path to a symlink rather than the + // actual executable. + if runtime.GOOS == "darwin" { + if execPath, err = filepath.EvalSymlinks(execPath); err != nil { + return execPath, err + } + } + return execPath, nil +} + +func getAbs(execPath string) (string, error) { + if initCwdErr != nil { + return execPath, initCwdErr + } + // The execPath may begin with a "../" or a "./" so clean it first. + // Join the two paths, trailing and starting slashes undetermined, so use + // the generic Join function. + return filepath.Join(initCwd, filepath.Clean(execPath)), nil +} diff --git a/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_test.go b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_test.go new file mode 100644 index 0000000..dc661db --- /dev/null +++ b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_test.go @@ -0,0 +1,79 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin linux freebsd netbsd windows + +package osext + +import ( + "fmt" + "os" + oexec "os/exec" + "path/filepath" + "runtime" + "testing" +) + +const execPath_EnvVar = "OSTEST_OUTPUT_EXECPATH" + +func TestExecPath(t *testing.T) { + ep, err := Executable() + if err != nil { + t.Fatalf("ExecPath failed: %v", err) + } + // we want fn to be of the form "dir/prog" + dir := filepath.Dir(filepath.Dir(ep)) + fn, err := filepath.Rel(dir, ep) + if err != nil { + t.Fatalf("filepath.Rel: %v", err) + } + cmd := &oexec.Cmd{} + // make child start with a relative program path + cmd.Dir = dir + cmd.Path = fn + // forge argv[0] for child, so that we can verify we could correctly + // get real path of the executable without influenced by argv[0]. + cmd.Args = []string{"-", "-test.run=XXXX"} + cmd.Env = []string{fmt.Sprintf("%s=1", execPath_EnvVar)} + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("exec(self) failed: %v", err) + } + outs := string(out) + if !filepath.IsAbs(outs) { + t.Fatalf("Child returned %q, want an absolute path", out) + } + if !sameFile(outs, ep) { + t.Fatalf("Child returned %q, not the same file as %q", out, ep) + } +} + +func sameFile(fn1, fn2 string) bool { + fi1, err := os.Stat(fn1) + if err != nil { + return false + } + fi2, err := os.Stat(fn2) + if err != nil { + return false + } + return os.SameFile(fi1, fi2) +} + +func init() { + if e := os.Getenv(execPath_EnvVar); e != "" { + // first chdir to another path + dir := "/" + if runtime.GOOS == "windows" { + dir = filepath.VolumeName(".") + } + os.Chdir(dir) + if ep, err := Executable(); err != nil { + fmt.Fprint(os.Stderr, "ERROR: ", err) + } else { + fmt.Fprint(os.Stderr, ep) + } + os.Exit(0) + } +} diff --git a/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_windows.go b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_windows.go new file mode 100644 index 0000000..72d282c --- /dev/null +++ b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_windows.go @@ -0,0 +1,34 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package osext + +import ( + "syscall" + "unicode/utf16" + "unsafe" +) + +var ( + kernel = syscall.MustLoadDLL("kernel32.dll") + getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW") +) + +// GetModuleFileName() with hModule = NULL +func executable() (exePath string, err error) { + return getModuleFileName() +} + +func getModuleFileName() (string, error) { + var n uint32 + b := make([]uint16, syscall.MAX_PATH) + size := uint32(len(b)) + + r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size)) + n = uint32(r0) + if n == 0 { + return "", e1 + } + return string(utf16.Decode(b[0:n])), nil +} diff --git a/Godeps/_workspace/src/github.com/inconshreveable/go-update/README b/Godeps/_workspace/src/github.com/inconshreveable/go-update/README new file mode 100644 index 0000000..467def8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/inconshreveable/go-update/README @@ -0,0 +1,137 @@ +PACKAGE DOCUMENTATION + +package update + import "github.com/inconshreveable/go-update" + + Package update allows a program to "self-update", replacing its + executable file with new bytes. + + Package update provides the facility to create user experiences like + auto-updating or user-approved updates which manifest as user prompts in + commercial applications with copy similar to "Restart to being using the + new version of X". + + Updating your program to a new version is as easy as: + + err := update.FromUrl("http://release.example.com/2.0/myprogram") + if err != nil { + fmt.Printf("Update failed: %v", err) + } + + The most low-level API is FromStream() which updates the current + executable with the bytes read from an io.Reader. + + Additional APIs are provided for common update strategies which include + updating from a file with FromFile() and updating from the internet with + FromUrl(). + + Using the more advaced Download.UpdateFromUrl() API gives you the + ability to resume an interrupted download to enable large updates to + complete even over intermittent or slow connections. This API also + enables more fine-grained control over how the update is downloaded from + the internet as well as access to download progress, + + +VARIABLES + +var ( + // Returned when the remote server indicates that no download is available + UpdateUnavailable error +) + + +FUNCTIONS + +func FromFile(filepath string) (err error) + FromFile reads the contents of the given file and uses them to update + the current program's executable file by calling FromStream(). + +func FromStream(newBinary io.Reader) (err error) + FromStream reads the contents of the supplied io.Reader newBinary and + uses them to update the current program's executable file. + + FromStream performs the following actions to ensure a cross-platform + safe update: + + - Renames the current program's executable file from + /path/to/program-name to /path/to/.program-name.old + + - Opens the now-empty path /path/to/program-name with mode 0755 and + copies the contents of newBinary into the file. + + - If the copy is successful, it erases /path/to/.program.old. If this + operation fails, no error is reported. + + - If the copy is unsuccessful, it attempts to rename + /path/to/.program-name.old back to /path/to/program-name. If this + operation fails, the error is not reported in order to not mask the + error that caused the rename recovery attempt. + +func FromUrl(url string) error + FromUrl downloads the contents of the given url and uses them to update + the current program's executable file. It is a convenience function + which is equivalent to + + NewDownload().UpdateFromUrl(url) + + See Download.UpdateFromUrl for more details. + + +TYPES + +type Download struct { + // net/http.Client to use when downloading the update. + // If nil, a default http.Client is used + HttpClient *http.Client + + // Path on the file system to dowload the update to + // If empty, a temporary file is used. + // After the download begins, this path will be set + // so that the client can use it to resume aborted + // downloads + Path string + + // Progress returns the percentage of the download + // completed as an integer between 0 and 100 + Progress chan (int) + + // HTTP Method to use in the download request. Default is "GET" + Method string +} + Type Download encapsulates the necessary parameters and state needed to + download an update from the internet. Create an instance with the + NewDownload() factory function. + + +func NewDownload() *Download + NewDownload initializes a new Download object + + +func (d *Download) UpdateFromUrl(url string) (err error) + UpdateFromUrl downloads the given url from the internet to a file on + disk and then calls FromStream() to update the current program's + executable file with the contents of that file. + + If the update is successful, the downloaded file will be erased from + disk. Otherwise, it will remain in d.Path to allow the download to + resume later or be skipped entirely. + + Only HTTP/1.1 servers that implement the Range header are supported. + + UpdateFromUrl() uses HTTP status codes to determine what action to take. + + - The HTTP server should return 200 or 206 for the update to be + downloaded. + + - The HTTP server should return 204 if no update is available at this + time. This will cause UpdateFromUrl to return the error + UpdateUnavailable. + + - If the HTTP server returns a 3XX redirect, it will be followed + according to d.HttpClient's redirect policy. + + - Any other HTTP status code will cause UpdateFromUrl to return an + error. + + + diff --git a/Godeps/_workspace/src/github.com/inconshreveable/go-update/update.go b/Godeps/_workspace/src/github.com/inconshreveable/go-update/update.go new file mode 100644 index 0000000..eee72e2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/inconshreveable/go-update/update.go @@ -0,0 +1,455 @@ +/* +Package update allows a program to "self-update", replacing its executable file +with new bytes. + +Package update provides the facility to create user experiences like auto-updating +or user-approved updates which manifest as user prompts in commercial applications +with copy similar to "Restart to being using the new version of X". + +Updating your program to a new version is as easy as: + + err := update.FromUrl("http://release.example.com/2.0/myprogram") + if err != nil { + fmt.Printf("Update failed: %v", err) + } + +The most low-level API is FromStream() which updates the current executable +with the bytes read from an io.Reader. + +Additional APIs are provided for common update strategies which include +updating from a file with FromFile() and updating from the internet with +FromUrl(). + +Using the more advaced Download.UpdateFromUrl() API gives you the ability +to resume an interrupted download to enable large updates to complete even +over intermittent or slow connections. This API also enables more fine-grained +control over how the update is downloaded from the internet as well as access to +download progress, +*/ +package update + +import ( + "compress/gzip" + "fmt" + "bitbucket.org/kardianos/osext" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "runtime" +) + +type MeteredReader struct { + rd io.ReadCloser + totalSize int64 + progress chan int + totalRead int64 + ticks int64 +} + +func (m *MeteredReader) Close() error { + return m.rd.Close() +} + +func (m *MeteredReader) Read(b []byte) (n int, err error) { + chunkSize := (m.totalSize / 100) + 1 + lenB := int64(len(b)) + + var nChunk int + for start := int64(0); start < lenB; start += int64(nChunk) { + end := start + chunkSize + if end > lenB { + end = lenB + } + + nChunk, err = m.rd.Read(b[start:end]) + + n += nChunk + m.totalRead += int64(nChunk) + + if m.totalRead > (m.ticks * chunkSize) { + m.ticks += 1 + // try to send on channel, but don't block if it's full + select { + case m.progress <- int(m.ticks + 1): + default: + } + + // give the progress channel consumer a chance to run + runtime.Gosched() + } + + if err != nil { + return + } + } + + return +} + +// We wrap the round tripper when making requests +// because we need to add headers to the requests we make +// even when they are requests made after a redirect +type RoundTripper struct { + RoundTripFn func(*http.Request) (*http.Response, error) +} + +func (rt *RoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { + return rt.RoundTripFn(r) +} + +// Type Download encapsulates the necessary parameters and state +// needed to download an update from the internet. Create an instance +// with the NewDownload() factory function. +// +// You may only use a Download once, +type Download struct { + // net/http.Client to use when downloading the update. + // If nil, a default http.Client is used + HttpClient *http.Client + + // Path on the file system to dowload the update to + // If empty, a temporary file is used. + // After the download begins, this path will be set + // so that the client can use it to resume aborted + // downloads + Path string + + // Progress returns the percentage of the download + // completed as an integer between 0 and 100 + Progress chan (int) + + // HTTP Method to use in the download request. Default is "GET" + Method string + + // HTTP URL to issue the download request to + Url string + + // Set to true when the server confirms a new version is available + // even if the updating process encounters an error later on + Available bool +} + +// NewDownload initializes a new Download object +func NewDownload(url string) *Download { + return &Download{ + HttpClient: new(http.Client), + Progress: make(chan int), + Method: "GET", + Url: url, + } +} + +func (d *Download) sharedHttp(offset int64) (resp *http.Response, err error) { + // create the download request + req, err := http.NewRequest(d.Method, d.Url, nil) + if err != nil { + return + } + + // we have to add headers like this so they get used across redirects + trans := d.HttpClient.Transport + if trans == nil { + trans = http.DefaultTransport + } + + d.HttpClient.Transport = &RoundTripper{ + RoundTripFn: func(r *http.Request) (*http.Response, error) { + // add header for download continuation + if offset > 0 { + r.Header.Add("Range", fmt.Sprintf("%d-", offset)) + } + + // ask for gzipped content so that net/http won't unzip it for us + // and destroy the content length header we need for progress calculations + r.Header.Add("Accept-Encoding", "gzip") + + return trans.RoundTrip(r) + }, + } + + // issue the download request + return d.HttpClient.Do(req) +} + +func (d *Download) Check() (available bool, err error) { + resp, err := d.sharedHttp(0) + if err != nil { + return + } + resp.Body.Close() + + switch resp.StatusCode { + // ok + case 200, 206: + available = true + + // no update available + case 204: + available = false + + // server error + default: + err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status) + return + } + + return +} + +// Get() downloads the given url from the internet to a file on disk +// and then calls FromStream() to update the current program's executable file +// with the contents of that file. +// +// If the update is successful, the downloaded file will be erased from disk. +// Otherwise, it will remain in d.Path to allow the download to resume later +// or be skipped entirely. +// +// Only HTTP/1.1 servers that implement the Range header support resuming a +// partially completed download. +// +// UpdateFromUrl() uses HTTP status codes to determine what action to take. +// +// - The HTTP server should return 200 or 206 for the update to be downloaded. +// +// - The HTTP server should return 204 if no update is available at this time. +// +// - If the HTTP server returns a 3XX redirect, it will be followed +// according to d.HttpClient's redirect policy. +// +// - Any other HTTP status code will cause UpdateFromUrl to return an error. +func (d *Download) Get() (err error) { + var offset int64 = 0 + var fp *os.File + + // Close the progress channel whenever this function completes + defer close(d.Progress) + + // open a file where we will stream the downloaded update to + // we do this first because if the caller specified a non-empty dlpath + // we need to determine how large it is in order to resume the download + if d.Path == "" { + // no dlpath specified, use a random tempfile + fp, err = ioutil.TempFile("", "update") + if err != nil { + return + } + defer fp.Close() + + // remember the path + d.Path = fp.Name() + } else { + fp, err = os.OpenFile(d.Path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0600) + if err != nil { + return + } + defer fp.Close() + + // determine the file size so we can resume the download, if possible + var fi os.FileInfo + fi, err = fp.Stat() + if err != nil { + return + } + + offset = fi.Size() + } + + // start downloading the file + resp, err := d.sharedHttp(offset) + if err != nil { + return + } + defer resp.Body.Close() + + switch resp.StatusCode { + // ok + case 200, 206: + d.Available = true + + // no update available + case 204: + return + + // server error + default: + err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status) + return + } + + // Determine how much we have to download + // net/http sets this to -1 when it is unknown + clength := resp.ContentLength + + // Read the content from the response body + rd := resp.Body + + // meter the rate at which we download content for + // progress reporting if we know how much to expect + if clength > 0 { + rd = &MeteredReader{rd: rd, totalSize: clength, progress: d.Progress} + } + + // Decompress the content if necessary + if resp.Header.Get("Content-Encoding") == "gzip" { + rd, err = gzip.NewReader(rd) + if err != nil { + return + } + } + + // Download the update + _, err = io.Copy(fp, rd) + if err != nil { + return + } + + return +} + +func (d *Download) GetAndUpdate() (err error, errRecover error) { + // check before we download if this will work + if err = SanityCheck(); err != nil { + // keep the contract that d.Progress will close whenever Get() terminates + close(d.Progress) + return + } + + // download the update + if err = d.Get(); err != nil || !d.Available { + return + } + + // apply the update + if err, errRecover = FromFile(d.Path); err != nil || errRecover != nil { + return + } + + // remove the temporary file + os.Remove(d.Path) + return +} + +// FromUrl downloads the contents of the given url and uses them to update +// the current program's executable file. It is a convenience function which is equivalent to +// +// NewDownload(url).GetAndUpdate() +// +// See Download.Get() for more details. +func FromUrl(url string) (err error, errRecover error) { + return NewDownload(url).GetAndUpdate() +} + +// FromFile reads the contents of the given file and uses them +// to update the current program's executable file by calling FromStream(). +func FromFile(filepath string) (err error, errRecover error) { + // open the new binary + fp, err := os.Open(filepath) + if err != nil { + return + } + defer fp.Close() + + // do the update + return FromStream(fp) +} + +// FromStream reads the contents of the supplied io.Reader newBinary +// and uses them to update the current program's executable file. +// +// FromStream performs the following actions to ensure a cross-platform safe +// update: +// +// - Creates a new file, /path/to/.program-name.new with mode 0755 and copies +// the contents of newBinary into the file +// +// - Renames the current program's executable file from /path/to/program-name +// to /path/to/.program-name.old +// +// - Renames /path/to/.program-name.new to /path/to/program-name +// +// - If the rename is successful, it erases /path/to/.program.old. If this operation +// fails, no error is reported. +// +// - If the rename is unsuccessful, it attempts to rename /path/to/.program-name.old +// back to /path/to/program-name. If this operation fails, the error is not reported +// in order to not mask the error that caused the rename recovery attempt. +func FromStream(newBinary io.Reader) (err error, errRecover error) { + // get the path to the executable + thisExecPath, err := osext.Executable() + if err != nil { + return + } + + // get the directory the executable exists in + execDir := filepath.Dir(thisExecPath) + execName := filepath.Base(thisExecPath) + + // Copy the contents of of newbinary to a the new executable file + newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName)) + fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) + if err != nil { + return + } + defer fp.Close() + _, err = io.Copy(fp, newBinary) + + // if we don't call fp.Close(), windows won't let us move the new executable + // because the file will still be "in use" + fp.Close() + + // this is where we'll move the executable to so that we can swap in the updated replacement + oldExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.old", execName)) + + // delete any existing old exec file - this is necessary on Windows for two reasons: + // 1. after a successful update, windows can't remove the .old file because the process is still running + // 2. windows rename operations fail if the destination file already exists + _ = os.Remove(oldExecPath) + + // move the existing executable to a new file in the same directory + err = os.Rename(thisExecPath, oldExecPath) + if err != nil { + return + } + + // move the new exectuable in to become the new program + err = os.Rename(newExecPath, thisExecPath) + + if err != nil { + // copy unsuccessful + errRecover = os.Rename(oldExecPath, thisExecPath) + } else { + // copy successful, remove the old binary + _ = os.Remove(oldExecPath) + } + + return +} + +// SanityCheck() attempts to determine whether an in-place executable update could +// succeed by performing preliminary checks (to establish valid permissions, etc). +// This helps avoid downloading updates when we know the update can't be successfully +// applied later. +func SanityCheck() (err error) { + // get the path to the executable + thisExecPath, err := osext.Executable() + if err != nil { + return + } + + // get the directory the executable exists in + execDir := filepath.Dir(thisExecPath) + execName := filepath.Base(thisExecPath) + + // attempt to open a file in the executable's directory + newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName)) + fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) + if err != nil { + return + } + fp.Close() + + _ = os.Remove(newExecPath) + return +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/.gitignore b/Godeps/_workspace/src/github.com/kr/binarydist/.gitignore new file mode 100644 index 0000000..653f160 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/.gitignore @@ -0,0 +1 @@ +test.* diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/License b/Godeps/_workspace/src/github.com/kr/binarydist/License new file mode 100644 index 0000000..183c389 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/License @@ -0,0 +1,22 @@ +Copyright 2012 Keith Rarick + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/Readme.md b/Godeps/_workspace/src/github.com/kr/binarydist/Readme.md new file mode 100644 index 0000000..dadc368 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/Readme.md @@ -0,0 +1,7 @@ +# binarydist + +Package binarydist implements binary diff and patch as described on +. It reads and writes files +compatible with the tools there. + +Documentation at . diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/bzip2.go b/Godeps/_workspace/src/github.com/kr/binarydist/bzip2.go new file mode 100644 index 0000000..a2516b8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/bzip2.go @@ -0,0 +1,40 @@ +package binarydist + +import ( + "io" + "os/exec" +) + +type bzip2Writer struct { + c *exec.Cmd + w io.WriteCloser +} + +func (w bzip2Writer) Write(b []byte) (int, error) { + return w.w.Write(b) +} + +func (w bzip2Writer) Close() error { + if err := w.w.Close(); err != nil { + return err + } + return w.c.Wait() +} + +// Package compress/bzip2 implements only decompression, +// so we'll fake it by running bzip2 in another process. +func newBzip2Writer(w io.Writer) (wc io.WriteCloser, err error) { + var bw bzip2Writer + bw.c = exec.Command("bzip2", "-c") + bw.c.Stdout = w + + if bw.w, err = bw.c.StdinPipe(); err != nil { + return nil, err + } + + if err = bw.c.Start(); err != nil { + return nil, err + } + + return bw, nil +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/common_test.go b/Godeps/_workspace/src/github.com/kr/binarydist/common_test.go new file mode 100644 index 0000000..af51616 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/common_test.go @@ -0,0 +1,93 @@ +package binarydist + +import ( + "crypto/rand" + "io" + "io/ioutil" + "os" +) + +func mustOpen(path string) *os.File { + f, err := os.Open(path) + if err != nil { + panic(err) + } + + return f +} + +func mustReadAll(r io.Reader) []byte { + b, err := ioutil.ReadAll(r) + if err != nil { + panic(err) + } + return b +} + +func fileCmp(a, b *os.File) int64 { + sa, err := a.Seek(0, 2) + if err != nil { + panic(err) + } + + sb, err := b.Seek(0, 2) + if err != nil { + panic(err) + } + + if sa != sb { + return sa + } + + _, err = a.Seek(0, 0) + if err != nil { + panic(err) + } + + _, err = b.Seek(0, 0) + if err != nil { + panic(err) + } + + pa, err := ioutil.ReadAll(a) + if err != nil { + panic(err) + } + + pb, err := ioutil.ReadAll(b) + if err != nil { + panic(err) + } + + for i := range pa { + if pa[i] != pb[i] { + return int64(i) + } + } + return -1 +} + +func mustWriteRandFile(path string, size int) *os.File { + p := make([]byte, size) + _, err := rand.Read(p) + if err != nil { + panic(err) + } + + f, err := os.Create(path) + if err != nil { + panic(err) + } + + _, err = f.Write(p) + if err != nil { + panic(err) + } + + _, err = f.Seek(0, 0) + if err != nil { + panic(err) + } + + return f +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/diff.go b/Godeps/_workspace/src/github.com/kr/binarydist/diff.go new file mode 100644 index 0000000..1d2d951 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/diff.go @@ -0,0 +1,408 @@ +package binarydist + +import ( + "bytes" + "encoding/binary" + "io" + "io/ioutil" +) + +func swap(a []int, i, j int) { a[i], a[j] = a[j], a[i] } + +func split(I, V []int, start, length, h int) { + var i, j, k, x, jj, kk int + + if length < 16 { + for k = start; k < start+length; k += j { + j = 1 + x = V[I[k]+h] + for i = 1; k+i < start+length; i++ { + if V[I[k+i]+h] < x { + x = V[I[k+i]+h] + j = 0 + } + if V[I[k+i]+h] == x { + swap(I, k+i, k+j) + j++ + } + } + for i = 0; i < j; i++ { + V[I[k+i]] = k + j - 1 + } + if j == 1 { + I[k] = -1 + } + } + return + } + + x = V[I[start+length/2]+h] + jj = 0 + kk = 0 + for i = start; i < start+length; i++ { + if V[I[i]+h] < x { + jj++ + } + if V[I[i]+h] == x { + kk++ + } + } + jj += start + kk += jj + + i = start + j = 0 + k = 0 + for i < jj { + if V[I[i]+h] < x { + i++ + } else if V[I[i]+h] == x { + swap(I, i, jj+j) + j++ + } else { + swap(I, i, kk+k) + k++ + } + } + + for jj+j < kk { + if V[I[jj+j]+h] == x { + j++ + } else { + swap(I, jj+j, kk+k) + k++ + } + } + + if jj > start { + split(I, V, start, jj-start, h) + } + + for i = 0; i < kk-jj; i++ { + V[I[jj+i]] = kk - 1 + } + if jj == kk-1 { + I[jj] = -1 + } + + if start+length > kk { + split(I, V, kk, start+length-kk, h) + } +} + +func qsufsort(obuf []byte) []int { + var buckets [256]int + var i, h int + I := make([]int, len(obuf)+1) + V := make([]int, len(obuf)+1) + + for _, c := range obuf { + buckets[c]++ + } + for i = 1; i < 256; i++ { + buckets[i] += buckets[i-1] + } + copy(buckets[1:], buckets[:]) + buckets[0] = 0 + + for i, c := range obuf { + buckets[c]++ + I[buckets[c]] = i + } + + I[0] = len(obuf) + for i, c := range obuf { + V[i] = buckets[c] + } + + V[len(obuf)] = 0 + for i = 1; i < 256; i++ { + if buckets[i] == buckets[i-1]+1 { + I[buckets[i]] = -1 + } + } + I[0] = -1 + + for h = 1; I[0] != -(len(obuf) + 1); h += h { + var n int + for i = 0; i < len(obuf)+1; { + if I[i] < 0 { + n -= I[i] + i -= I[i] + } else { + if n != 0 { + I[i-n] = -n + } + n = V[I[i]] + 1 - i + split(I, V, i, n, h) + i += n + n = 0 + } + } + if n != 0 { + I[i-n] = -n + } + } + + for i = 0; i < len(obuf)+1; i++ { + I[V[i]] = i + } + return I +} + +func matchlen(a, b []byte) (i int) { + for i < len(a) && i < len(b) && a[i] == b[i] { + i++ + } + return i +} + +func search(I []int, obuf, nbuf []byte, st, en int) (pos, n int) { + if en-st < 2 { + x := matchlen(obuf[I[st]:], nbuf) + y := matchlen(obuf[I[en]:], nbuf) + + if x > y { + return I[st], x + } else { + return I[en], y + } + } + + x := st + (en-st)/2 + if bytes.Compare(obuf[I[x]:], nbuf) < 0 { + return search(I, obuf, nbuf, x, en) + } else { + return search(I, obuf, nbuf, st, x) + } + panic("unreached") +} + +// Diff computes the difference between old and new, according to the bsdiff +// algorithm, and writes the result to patch. +func Diff(old, new io.Reader, patch io.Writer) error { + obuf, err := ioutil.ReadAll(old) + if err != nil { + return err + } + + nbuf, err := ioutil.ReadAll(new) + if err != nil { + return err + } + + pbuf, err := diffBytes(obuf, nbuf) + if err != nil { + return err + } + + _, err = patch.Write(pbuf) + return err +} + +func diffBytes(obuf, nbuf []byte) ([]byte, error) { + var patch seekBuffer + err := diff(obuf, nbuf, &patch) + if err != nil { + return nil, err + } + return patch.buf, nil +} + +func diff(obuf, nbuf []byte, patch io.WriteSeeker) error { + var lenf int + I := qsufsort(obuf) + db := make([]byte, len(nbuf)) + eb := make([]byte, len(nbuf)) + var dblen, eblen int + + var hdr header + hdr.Magic = magic + hdr.NewSize = int64(len(nbuf)) + err := binary.Write(patch, signMagLittleEndian{}, &hdr) + if err != nil { + return err + } + + // Compute the differences, writing ctrl as we go + pfbz2, err := newBzip2Writer(patch) + if err != nil { + return err + } + var scan, pos, length int + var lastscan, lastpos, lastoffset int + for scan < len(nbuf) { + var oldscore int + scan += length + for scsc := scan; scan < len(nbuf); scan++ { + pos, length = search(I, obuf, nbuf[scan:], 0, len(obuf)) + + for ; scsc < scan+length; scsc++ { + if scsc+lastoffset < len(obuf) && + obuf[scsc+lastoffset] == nbuf[scsc] { + oldscore++ + } + } + + if (length == oldscore && length != 0) || length > oldscore+8 { + break + } + + if scan+lastoffset < len(obuf) && obuf[scan+lastoffset] == nbuf[scan] { + oldscore-- + } + } + + if length != oldscore || scan == len(nbuf) { + var s, Sf int + lenf = 0 + for i := 0; lastscan+i < scan && lastpos+i < len(obuf); { + if obuf[lastpos+i] == nbuf[lastscan+i] { + s++ + } + i++ + if s*2-i > Sf*2-lenf { + Sf = s + lenf = i + } + } + + lenb := 0 + if scan < len(nbuf) { + var s, Sb int + for i := 1; (scan >= lastscan+i) && (pos >= i); i++ { + if obuf[pos-i] == nbuf[scan-i] { + s++ + } + if s*2-i > Sb*2-lenb { + Sb = s + lenb = i + } + } + } + + if lastscan+lenf > scan-lenb { + overlap := (lastscan + lenf) - (scan - lenb) + s := 0 + Ss := 0 + lens := 0 + for i := 0; i < overlap; i++ { + if nbuf[lastscan+lenf-overlap+i] == obuf[lastpos+lenf-overlap+i] { + s++ + } + if nbuf[scan-lenb+i] == obuf[pos-lenb+i] { + s-- + } + if s > Ss { + Ss = s + lens = i + 1 + } + } + + lenf += lens - overlap + lenb -= lens + } + + for i := 0; i < lenf; i++ { + db[dblen+i] = nbuf[lastscan+i] - obuf[lastpos+i] + } + for i := 0; i < (scan-lenb)-(lastscan+lenf); i++ { + eb[eblen+i] = nbuf[lastscan+lenf+i] + } + + dblen += lenf + eblen += (scan - lenb) - (lastscan + lenf) + + err = binary.Write(pfbz2, signMagLittleEndian{}, int64(lenf)) + if err != nil { + pfbz2.Close() + return err + } + + val := (scan - lenb) - (lastscan + lenf) + err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val)) + if err != nil { + pfbz2.Close() + return err + } + + val = (pos - lenb) - (lastpos + lenf) + err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val)) + if err != nil { + pfbz2.Close() + return err + } + + lastscan = scan - lenb + lastpos = pos - lenb + lastoffset = pos - scan + } + } + err = pfbz2.Close() + if err != nil { + return err + } + + // Compute size of compressed ctrl data + l64, err := patch.Seek(0, 1) + if err != nil { + return err + } + hdr.CtrlLen = int64(l64 - 32) + + // Write compressed diff data + pfbz2, err = newBzip2Writer(patch) + if err != nil { + return err + } + n, err := pfbz2.Write(db[:dblen]) + if err != nil { + pfbz2.Close() + return err + } + if n != dblen { + pfbz2.Close() + return io.ErrShortWrite + } + err = pfbz2.Close() + if err != nil { + return err + } + + // Compute size of compressed diff data + n64, err := patch.Seek(0, 1) + if err != nil { + return err + } + hdr.DiffLen = n64 - l64 + + // Write compressed extra data + pfbz2, err = newBzip2Writer(patch) + if err != nil { + return err + } + n, err = pfbz2.Write(eb[:eblen]) + if err != nil { + pfbz2.Close() + return err + } + if n != eblen { + pfbz2.Close() + return io.ErrShortWrite + } + err = pfbz2.Close() + if err != nil { + return err + } + + // Seek to the beginning, write the header, and close the file + _, err = patch.Seek(0, 0) + if err != nil { + return err + } + err = binary.Write(patch, signMagLittleEndian{}, &hdr) + if err != nil { + return err + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/diff_test.go b/Godeps/_workspace/src/github.com/kr/binarydist/diff_test.go new file mode 100644 index 0000000..9baa492 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/diff_test.go @@ -0,0 +1,67 @@ +package binarydist + +import ( + "bytes" + "io/ioutil" + "os" + "os/exec" + "testing" +) + +var diffT = []struct { + old *os.File + new *os.File +}{ + { + old: mustWriteRandFile("test.old", 1e3), + new: mustWriteRandFile("test.new", 1e3), + }, + { + old: mustOpen("testdata/sample.old"), + new: mustOpen("testdata/sample.new"), + }, +} + +func TestDiff(t *testing.T) { + for _, s := range diffT { + got, err := ioutil.TempFile("/tmp", "bspatch.") + if err != nil { + panic(err) + } + os.Remove(got.Name()) + + exp, err := ioutil.TempFile("/tmp", "bspatch.") + if err != nil { + panic(err) + } + + cmd := exec.Command("bsdiff", s.old.Name(), s.new.Name(), exp.Name()) + cmd.Stdout = os.Stdout + err = cmd.Run() + os.Remove(exp.Name()) + if err != nil { + panic(err) + } + + err = Diff(s.old, s.new, got) + if err != nil { + t.Fatal("err", err) + } + + _, err = got.Seek(0, 0) + if err != nil { + panic(err) + } + gotBuf := mustReadAll(got) + expBuf := mustReadAll(exp) + + if !bytes.Equal(gotBuf, expBuf) { + t.Fail() + t.Logf("diff %s %s", s.old.Name(), s.new.Name()) + t.Logf("%s: len(got) = %d", got.Name(), len(gotBuf)) + t.Logf("%s: len(exp) = %d", exp.Name(), len(expBuf)) + i := matchlen(gotBuf, expBuf) + t.Logf("produced different output at pos %d; %d != %d", i, gotBuf[i], expBuf[i]) + } + } +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/doc.go b/Godeps/_workspace/src/github.com/kr/binarydist/doc.go new file mode 100644 index 0000000..3c92d87 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/doc.go @@ -0,0 +1,24 @@ +// Package binarydist implements binary diff and patch as described on +// http://www.daemonology.net/bsdiff/. It reads and writes files +// compatible with the tools there. +package binarydist + +var magic = [8]byte{'B', 'S', 'D', 'I', 'F', 'F', '4', '0'} + +// File format: +// 0 8 "BSDIFF40" +// 8 8 X +// 16 8 Y +// 24 8 sizeof(newfile) +// 32 X bzip2(control block) +// 32+X Y bzip2(diff block) +// 32+X+Y ??? bzip2(extra block) +// with control block a set of triples (x,y,z) meaning "add x bytes +// from oldfile to x bytes from the diff block; copy y bytes from the +// extra block; seek forwards in oldfile by z bytes". +type header struct { + Magic [8]byte + CtrlLen int64 + DiffLen int64 + NewSize int64 +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/encoding.go b/Godeps/_workspace/src/github.com/kr/binarydist/encoding.go new file mode 100644 index 0000000..75ba585 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/encoding.go @@ -0,0 +1,53 @@ +package binarydist + +// SignMagLittleEndian is the numeric encoding used by the bsdiff tools. +// It implements binary.ByteOrder using a sign-magnitude format +// and little-endian byte order. Only methods Uint64 and String +// have been written; the rest panic. +type signMagLittleEndian struct{} + +func (signMagLittleEndian) Uint16(b []byte) uint16 { panic("unimplemented") } + +func (signMagLittleEndian) PutUint16(b []byte, v uint16) { panic("unimplemented") } + +func (signMagLittleEndian) Uint32(b []byte) uint32 { panic("unimplemented") } + +func (signMagLittleEndian) PutUint32(b []byte, v uint32) { panic("unimplemented") } + +func (signMagLittleEndian) Uint64(b []byte) uint64 { + y := int64(b[0]) | + int64(b[1])<<8 | + int64(b[2])<<16 | + int64(b[3])<<24 | + int64(b[4])<<32 | + int64(b[5])<<40 | + int64(b[6])<<48 | + int64(b[7]&0x7f)<<56 + + if b[7]&0x80 != 0 { + y = -y + } + return uint64(y) +} + +func (signMagLittleEndian) PutUint64(b []byte, v uint64) { + x := int64(v) + neg := x < 0 + if neg { + x = -x + } + + b[0] = byte(x) + b[1] = byte(x >> 8) + b[2] = byte(x >> 16) + b[3] = byte(x >> 24) + b[4] = byte(x >> 32) + b[5] = byte(x >> 40) + b[6] = byte(x >> 48) + b[7] = byte(x >> 56) + if neg { + b[7] |= 0x80 + } +} + +func (signMagLittleEndian) String() string { return "signMagLittleEndian" } diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/patch.go b/Godeps/_workspace/src/github.com/kr/binarydist/patch.go new file mode 100644 index 0000000..eb03225 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/patch.go @@ -0,0 +1,109 @@ +package binarydist + +import ( + "bytes" + "compress/bzip2" + "encoding/binary" + "errors" + "io" + "io/ioutil" +) + +var ErrCorrupt = errors.New("corrupt patch") + +// Patch applies patch to old, according to the bspatch algorithm, +// and writes the result to new. +func Patch(old io.Reader, new io.Writer, patch io.Reader) error { + var hdr header + err := binary.Read(patch, signMagLittleEndian{}, &hdr) + if err != nil { + return err + } + if hdr.Magic != magic { + return ErrCorrupt + } + if hdr.CtrlLen < 0 || hdr.DiffLen < 0 || hdr.NewSize < 0 { + return ErrCorrupt + } + + ctrlbuf := make([]byte, hdr.CtrlLen) + _, err = io.ReadFull(patch, ctrlbuf) + if err != nil { + return err + } + cpfbz2 := bzip2.NewReader(bytes.NewReader(ctrlbuf)) + + diffbuf := make([]byte, hdr.DiffLen) + _, err = io.ReadFull(patch, diffbuf) + if err != nil { + return err + } + dpfbz2 := bzip2.NewReader(bytes.NewReader(diffbuf)) + + // The entire rest of the file is the extra block. + epfbz2 := bzip2.NewReader(patch) + + obuf, err := ioutil.ReadAll(old) + if err != nil { + return err + } + + nbuf := make([]byte, hdr.NewSize) + + var oldpos, newpos int64 + for newpos < hdr.NewSize { + var ctrl struct{ Add, Copy, Seek int64 } + err = binary.Read(cpfbz2, signMagLittleEndian{}, &ctrl) + if err != nil { + return err + } + + // Sanity-check + if newpos+ctrl.Add > hdr.NewSize { + return ErrCorrupt + } + + // Read diff string + _, err = io.ReadFull(dpfbz2, nbuf[newpos:newpos+ctrl.Add]) + if err != nil { + return ErrCorrupt + } + + // Add old data to diff string + for i := int64(0); i < ctrl.Add; i++ { + if oldpos+i >= 0 && oldpos+i < int64(len(obuf)) { + nbuf[newpos+i] += obuf[oldpos+i] + } + } + + // Adjust pointers + newpos += ctrl.Add + oldpos += ctrl.Add + + // Sanity-check + if newpos+ctrl.Copy > hdr.NewSize { + return ErrCorrupt + } + + // Read extra string + _, err = io.ReadFull(epfbz2, nbuf[newpos:newpos+ctrl.Copy]) + if err != nil { + return ErrCorrupt + } + + // Adjust pointers + newpos += ctrl.Copy + oldpos += ctrl.Seek + } + + // Write the new file + for len(nbuf) > 0 { + n, err := new.Write(nbuf) + if err != nil { + return err + } + nbuf = nbuf[n:] + } + + return nil +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/patch_test.go b/Godeps/_workspace/src/github.com/kr/binarydist/patch_test.go new file mode 100644 index 0000000..840a919 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/patch_test.go @@ -0,0 +1,62 @@ +package binarydist + +import ( + "io/ioutil" + "os" + "os/exec" + "testing" +) + +func TestPatch(t *testing.T) { + mustWriteRandFile("test.old", 1e3) + mustWriteRandFile("test.new", 1e3) + + got, err := ioutil.TempFile("/tmp", "bspatch.") + if err != nil { + panic(err) + } + os.Remove(got.Name()) + + err = exec.Command("bsdiff", "test.old", "test.new", "test.patch").Run() + if err != nil { + panic(err) + } + + err = Patch(mustOpen("test.old"), got, mustOpen("test.patch")) + if err != nil { + t.Fatal("err", err) + } + + ref, err := got.Seek(0, 2) + if err != nil { + panic(err) + } + + t.Logf("got %d bytes", ref) + if n := fileCmp(got, mustOpen("test.new")); n > -1 { + t.Fatalf("produced different output at pos %d", n) + } +} + +func TestPatchHk(t *testing.T) { + got, err := ioutil.TempFile("/tmp", "bspatch.") + if err != nil { + panic(err) + } + os.Remove(got.Name()) + + err = Patch(mustOpen("testdata/sample.old"), got, mustOpen("testdata/sample.patch")) + if err != nil { + t.Fatal("err", err) + } + + ref, err := got.Seek(0, 2) + if err != nil { + panic(err) + } + + t.Logf("got %d bytes", ref) + if n := fileCmp(got, mustOpen("testdata/sample.new")); n > -1 { + t.Fatalf("produced different output at pos %d", n) + } +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/seek.go b/Godeps/_workspace/src/github.com/kr/binarydist/seek.go new file mode 100644 index 0000000..96c0346 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/seek.go @@ -0,0 +1,43 @@ +package binarydist + +import ( + "errors" +) + +type seekBuffer struct { + buf []byte + pos int +} + +func (b *seekBuffer) Write(p []byte) (n int, err error) { + n = copy(b.buf[b.pos:], p) + if n == len(p) { + b.pos += n + return n, nil + } + b.buf = append(b.buf, p[n:]...) + b.pos += len(p) + return len(p), nil +} + +func (b *seekBuffer) Seek(offset int64, whence int) (ret int64, err error) { + var abs int64 + switch whence { + case 0: + abs = offset + case 1: + abs = int64(b.pos) + offset + case 2: + abs = int64(len(b.buf)) + offset + default: + return 0, errors.New("binarydist: invalid whence") + } + if abs < 0 { + return 0, errors.New("binarydist: negative position") + } + if abs >= 1<<31 { + return 0, errors.New("binarydist: position out of range") + } + b.pos = int(abs) + return abs, nil +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/sort_test.go b/Godeps/_workspace/src/github.com/kr/binarydist/sort_test.go new file mode 100644 index 0000000..be483c3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/sort_test.go @@ -0,0 +1,33 @@ +package binarydist + +import ( + "bytes" + "crypto/rand" + "testing" +) + +var sortT = [][]byte{ + mustRandBytes(1000), + mustReadAll(mustOpen("test.old")), + []byte("abcdefabcdef"), +} + +func TestQsufsort(t *testing.T) { + for _, s := range sortT { + I := qsufsort(s) + for i := 1; i < len(I); i++ { + if bytes.Compare(s[I[i-1]:], s[I[i]:]) > 0 { + t.Fatalf("unsorted at %d", i) + } + } + } +} + +func mustRandBytes(n int) []byte { + b := make([]byte, n) + _, err := rand.Read(b) + if err != nil { + panic(err) + } + return b +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.new b/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.new new file mode 100644 index 0000000000000000000000000000000000000000..592cdbe2dff6a7f7f9bcdbd80fdf30fb775d8e13 GIT binary patch literal 10000 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 Jc&U;-_-}XSHje-R literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.old b/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.old new file mode 100644 index 0000000000000000000000000000000000000000..7bc64dacb0a93810ef51ba9bba355b1755738734 GIT binary patch literal 11000 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 F1^_f>!+rn& literal 0 HcmV?d00001 diff --git a/selfupdate/Godeps/Godeps.json b/selfupdate/Godeps/Godeps.json new file mode 100644 index 0000000..29e6ec2 --- /dev/null +++ b/selfupdate/Godeps/Godeps.json @@ -0,0 +1,19 @@ +{ + "ImportPath": "github.com/sanbornm/go-selfupdate/selfupdate", + "GoVersion": "go1.3", + "Deps": [ + { + "ImportPath": "bitbucket.org/kardianos/osext", + "Comment": "null-13", + "Rev": "5d3ddcf53a508cc2f7404eaebf546ef2cb5cdb6e" + }, + { + "ImportPath": "github.com/inconshreveable/go-update", + "Rev": "3f0466666779bd2143f368a207b0641f0ed536e8" + }, + { + "ImportPath": "github.com/kr/binarydist", + "Rev": "9955b0ab8708602d411341e55fffd7e0700f86bd" + } + ] +} diff --git a/selfupdate/Godeps/Readme b/selfupdate/Godeps/Readme new file mode 100644 index 0000000..4cdaa53 --- /dev/null +++ b/selfupdate/Godeps/Readme @@ -0,0 +1,5 @@ +This directory tree is generated automatically by godep. + +Please do not edit. + +See https://github.com/tools/godep for more information. diff --git a/selfupdate/Godeps/_workspace/.gitignore b/selfupdate/Godeps/_workspace/.gitignore new file mode 100644 index 0000000..f037d68 --- /dev/null +++ b/selfupdate/Godeps/_workspace/.gitignore @@ -0,0 +1,2 @@ +/pkg +/bin diff --git a/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/LICENSE b/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/LICENSE new file mode 100644 index 0000000..18527a2 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2012 Daniel Theophanes + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. diff --git a/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext.go b/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext.go new file mode 100644 index 0000000..37efbb2 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext.go @@ -0,0 +1,32 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Extensions to the standard "os" package. +package osext + +import "path/filepath" + +// Executable returns an absolute path that can be used to +// re-invoke the current program. +// It may not be valid after the current program exits. +func Executable() (string, error) { + p, err := executable() + return filepath.Clean(p), err +} + +// Returns same path as Executable, returns just the folder +// path. Excludes the executable name. +func ExecutableFolder() (string, error) { + p, err := Executable() + if err != nil { + return "", err + } + folder, _ := filepath.Split(p) + return folder, nil +} + +// Depricated. Same as Executable(). +func GetExePath() (exePath string, err error) { + return Executable() +} diff --git a/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_plan9.go b/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_plan9.go new file mode 100644 index 0000000..4468a73 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_plan9.go @@ -0,0 +1,20 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package osext + +import ( + "syscall" + "os" + "strconv" +) + +func executable() (string, error) { + f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text") + if err != nil { + return "", err + } + defer f.Close() + return syscall.Fd2path(int(f.Fd())) +} diff --git a/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_procfs.go b/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_procfs.go new file mode 100644 index 0000000..546fec9 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_procfs.go @@ -0,0 +1,25 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux netbsd openbsd + +package osext + +import ( + "errors" + "os" + "runtime" +) + +func executable() (string, error) { + switch runtime.GOOS { + case "linux": + return os.Readlink("/proc/self/exe") + case "netbsd": + return os.Readlink("/proc/curproc/exe") + case "openbsd": + return os.Readlink("/proc/curproc/file") + } + return "", errors.New("ExecPath not implemented for " + runtime.GOOS) +} diff --git a/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_sysctl.go b/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_sysctl.go new file mode 100644 index 0000000..b66cac8 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_sysctl.go @@ -0,0 +1,79 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin freebsd + +package osext + +import ( + "os" + "path/filepath" + "runtime" + "syscall" + "unsafe" +) + +var initCwd, initCwdErr = os.Getwd() + +func executable() (string, error) { + var mib [4]int32 + switch runtime.GOOS { + case "freebsd": + mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1} + case "darwin": + mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1} + } + + n := uintptr(0) + // Get length. + _, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0) + if errNum != 0 { + return "", errNum + } + if n == 0 { // This shouldn't happen. + return "", nil + } + buf := make([]byte, n) + _, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0) + if errNum != 0 { + return "", errNum + } + if n == 0 { // This shouldn't happen. + return "", nil + } + for i, v := range buf { + if v == 0 { + buf = buf[:i] + break + } + } + var err error + execPath := string(buf) + // execPath will not be empty due to above checks. + // Try to get the absolute path if the execPath is not rooted. + if execPath[0] != '/' { + execPath, err = getAbs(execPath) + if err != nil { + return execPath, err + } + } + // For darwin KERN_PROCARGS may return the path to a symlink rather than the + // actual executable. + if runtime.GOOS == "darwin" { + if execPath, err = filepath.EvalSymlinks(execPath); err != nil { + return execPath, err + } + } + return execPath, nil +} + +func getAbs(execPath string) (string, error) { + if initCwdErr != nil { + return execPath, initCwdErr + } + // The execPath may begin with a "../" or a "./" so clean it first. + // Join the two paths, trailing and starting slashes undetermined, so use + // the generic Join function. + return filepath.Join(initCwd, filepath.Clean(execPath)), nil +} diff --git a/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_test.go b/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_test.go new file mode 100644 index 0000000..dc661db --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_test.go @@ -0,0 +1,79 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin linux freebsd netbsd windows + +package osext + +import ( + "fmt" + "os" + oexec "os/exec" + "path/filepath" + "runtime" + "testing" +) + +const execPath_EnvVar = "OSTEST_OUTPUT_EXECPATH" + +func TestExecPath(t *testing.T) { + ep, err := Executable() + if err != nil { + t.Fatalf("ExecPath failed: %v", err) + } + // we want fn to be of the form "dir/prog" + dir := filepath.Dir(filepath.Dir(ep)) + fn, err := filepath.Rel(dir, ep) + if err != nil { + t.Fatalf("filepath.Rel: %v", err) + } + cmd := &oexec.Cmd{} + // make child start with a relative program path + cmd.Dir = dir + cmd.Path = fn + // forge argv[0] for child, so that we can verify we could correctly + // get real path of the executable without influenced by argv[0]. + cmd.Args = []string{"-", "-test.run=XXXX"} + cmd.Env = []string{fmt.Sprintf("%s=1", execPath_EnvVar)} + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("exec(self) failed: %v", err) + } + outs := string(out) + if !filepath.IsAbs(outs) { + t.Fatalf("Child returned %q, want an absolute path", out) + } + if !sameFile(outs, ep) { + t.Fatalf("Child returned %q, not the same file as %q", out, ep) + } +} + +func sameFile(fn1, fn2 string) bool { + fi1, err := os.Stat(fn1) + if err != nil { + return false + } + fi2, err := os.Stat(fn2) + if err != nil { + return false + } + return os.SameFile(fi1, fi2) +} + +func init() { + if e := os.Getenv(execPath_EnvVar); e != "" { + // first chdir to another path + dir := "/" + if runtime.GOOS == "windows" { + dir = filepath.VolumeName(".") + } + os.Chdir(dir) + if ep, err := Executable(); err != nil { + fmt.Fprint(os.Stderr, "ERROR: ", err) + } else { + fmt.Fprint(os.Stderr, ep) + } + os.Exit(0) + } +} diff --git a/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_windows.go b/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_windows.go new file mode 100644 index 0000000..72d282c --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_windows.go @@ -0,0 +1,34 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package osext + +import ( + "syscall" + "unicode/utf16" + "unsafe" +) + +var ( + kernel = syscall.MustLoadDLL("kernel32.dll") + getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW") +) + +// GetModuleFileName() with hModule = NULL +func executable() (exePath string, err error) { + return getModuleFileName() +} + +func getModuleFileName() (string, error) { + var n uint32 + b := make([]uint16, syscall.MAX_PATH) + size := uint32(len(b)) + + r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size)) + n = uint32(r0) + if n == 0 { + return "", e1 + } + return string(utf16.Decode(b[0:n])), nil +} diff --git a/selfupdate/Godeps/_workspace/src/github.com/inconshreveable/go-update/README b/selfupdate/Godeps/_workspace/src/github.com/inconshreveable/go-update/README new file mode 100644 index 0000000..467def8 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/github.com/inconshreveable/go-update/README @@ -0,0 +1,137 @@ +PACKAGE DOCUMENTATION + +package update + import "github.com/inconshreveable/go-update" + + Package update allows a program to "self-update", replacing its + executable file with new bytes. + + Package update provides the facility to create user experiences like + auto-updating or user-approved updates which manifest as user prompts in + commercial applications with copy similar to "Restart to being using the + new version of X". + + Updating your program to a new version is as easy as: + + err := update.FromUrl("http://release.example.com/2.0/myprogram") + if err != nil { + fmt.Printf("Update failed: %v", err) + } + + The most low-level API is FromStream() which updates the current + executable with the bytes read from an io.Reader. + + Additional APIs are provided for common update strategies which include + updating from a file with FromFile() and updating from the internet with + FromUrl(). + + Using the more advaced Download.UpdateFromUrl() API gives you the + ability to resume an interrupted download to enable large updates to + complete even over intermittent or slow connections. This API also + enables more fine-grained control over how the update is downloaded from + the internet as well as access to download progress, + + +VARIABLES + +var ( + // Returned when the remote server indicates that no download is available + UpdateUnavailable error +) + + +FUNCTIONS + +func FromFile(filepath string) (err error) + FromFile reads the contents of the given file and uses them to update + the current program's executable file by calling FromStream(). + +func FromStream(newBinary io.Reader) (err error) + FromStream reads the contents of the supplied io.Reader newBinary and + uses them to update the current program's executable file. + + FromStream performs the following actions to ensure a cross-platform + safe update: + + - Renames the current program's executable file from + /path/to/program-name to /path/to/.program-name.old + + - Opens the now-empty path /path/to/program-name with mode 0755 and + copies the contents of newBinary into the file. + + - If the copy is successful, it erases /path/to/.program.old. If this + operation fails, no error is reported. + + - If the copy is unsuccessful, it attempts to rename + /path/to/.program-name.old back to /path/to/program-name. If this + operation fails, the error is not reported in order to not mask the + error that caused the rename recovery attempt. + +func FromUrl(url string) error + FromUrl downloads the contents of the given url and uses them to update + the current program's executable file. It is a convenience function + which is equivalent to + + NewDownload().UpdateFromUrl(url) + + See Download.UpdateFromUrl for more details. + + +TYPES + +type Download struct { + // net/http.Client to use when downloading the update. + // If nil, a default http.Client is used + HttpClient *http.Client + + // Path on the file system to dowload the update to + // If empty, a temporary file is used. + // After the download begins, this path will be set + // so that the client can use it to resume aborted + // downloads + Path string + + // Progress returns the percentage of the download + // completed as an integer between 0 and 100 + Progress chan (int) + + // HTTP Method to use in the download request. Default is "GET" + Method string +} + Type Download encapsulates the necessary parameters and state needed to + download an update from the internet. Create an instance with the + NewDownload() factory function. + + +func NewDownload() *Download + NewDownload initializes a new Download object + + +func (d *Download) UpdateFromUrl(url string) (err error) + UpdateFromUrl downloads the given url from the internet to a file on + disk and then calls FromStream() to update the current program's + executable file with the contents of that file. + + If the update is successful, the downloaded file will be erased from + disk. Otherwise, it will remain in d.Path to allow the download to + resume later or be skipped entirely. + + Only HTTP/1.1 servers that implement the Range header are supported. + + UpdateFromUrl() uses HTTP status codes to determine what action to take. + + - The HTTP server should return 200 or 206 for the update to be + downloaded. + + - The HTTP server should return 204 if no update is available at this + time. This will cause UpdateFromUrl to return the error + UpdateUnavailable. + + - If the HTTP server returns a 3XX redirect, it will be followed + according to d.HttpClient's redirect policy. + + - Any other HTTP status code will cause UpdateFromUrl to return an + error. + + + diff --git a/selfupdate/Godeps/_workspace/src/github.com/inconshreveable/go-update/update.go b/selfupdate/Godeps/_workspace/src/github.com/inconshreveable/go-update/update.go new file mode 100644 index 0000000..eee72e2 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/github.com/inconshreveable/go-update/update.go @@ -0,0 +1,455 @@ +/* +Package update allows a program to "self-update", replacing its executable file +with new bytes. + +Package update provides the facility to create user experiences like auto-updating +or user-approved updates which manifest as user prompts in commercial applications +with copy similar to "Restart to being using the new version of X". + +Updating your program to a new version is as easy as: + + err := update.FromUrl("http://release.example.com/2.0/myprogram") + if err != nil { + fmt.Printf("Update failed: %v", err) + } + +The most low-level API is FromStream() which updates the current executable +with the bytes read from an io.Reader. + +Additional APIs are provided for common update strategies which include +updating from a file with FromFile() and updating from the internet with +FromUrl(). + +Using the more advaced Download.UpdateFromUrl() API gives you the ability +to resume an interrupted download to enable large updates to complete even +over intermittent or slow connections. This API also enables more fine-grained +control over how the update is downloaded from the internet as well as access to +download progress, +*/ +package update + +import ( + "compress/gzip" + "fmt" + "bitbucket.org/kardianos/osext" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "runtime" +) + +type MeteredReader struct { + rd io.ReadCloser + totalSize int64 + progress chan int + totalRead int64 + ticks int64 +} + +func (m *MeteredReader) Close() error { + return m.rd.Close() +} + +func (m *MeteredReader) Read(b []byte) (n int, err error) { + chunkSize := (m.totalSize / 100) + 1 + lenB := int64(len(b)) + + var nChunk int + for start := int64(0); start < lenB; start += int64(nChunk) { + end := start + chunkSize + if end > lenB { + end = lenB + } + + nChunk, err = m.rd.Read(b[start:end]) + + n += nChunk + m.totalRead += int64(nChunk) + + if m.totalRead > (m.ticks * chunkSize) { + m.ticks += 1 + // try to send on channel, but don't block if it's full + select { + case m.progress <- int(m.ticks + 1): + default: + } + + // give the progress channel consumer a chance to run + runtime.Gosched() + } + + if err != nil { + return + } + } + + return +} + +// We wrap the round tripper when making requests +// because we need to add headers to the requests we make +// even when they are requests made after a redirect +type RoundTripper struct { + RoundTripFn func(*http.Request) (*http.Response, error) +} + +func (rt *RoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { + return rt.RoundTripFn(r) +} + +// Type Download encapsulates the necessary parameters and state +// needed to download an update from the internet. Create an instance +// with the NewDownload() factory function. +// +// You may only use a Download once, +type Download struct { + // net/http.Client to use when downloading the update. + // If nil, a default http.Client is used + HttpClient *http.Client + + // Path on the file system to dowload the update to + // If empty, a temporary file is used. + // After the download begins, this path will be set + // so that the client can use it to resume aborted + // downloads + Path string + + // Progress returns the percentage of the download + // completed as an integer between 0 and 100 + Progress chan (int) + + // HTTP Method to use in the download request. Default is "GET" + Method string + + // HTTP URL to issue the download request to + Url string + + // Set to true when the server confirms a new version is available + // even if the updating process encounters an error later on + Available bool +} + +// NewDownload initializes a new Download object +func NewDownload(url string) *Download { + return &Download{ + HttpClient: new(http.Client), + Progress: make(chan int), + Method: "GET", + Url: url, + } +} + +func (d *Download) sharedHttp(offset int64) (resp *http.Response, err error) { + // create the download request + req, err := http.NewRequest(d.Method, d.Url, nil) + if err != nil { + return + } + + // we have to add headers like this so they get used across redirects + trans := d.HttpClient.Transport + if trans == nil { + trans = http.DefaultTransport + } + + d.HttpClient.Transport = &RoundTripper{ + RoundTripFn: func(r *http.Request) (*http.Response, error) { + // add header for download continuation + if offset > 0 { + r.Header.Add("Range", fmt.Sprintf("%d-", offset)) + } + + // ask for gzipped content so that net/http won't unzip it for us + // and destroy the content length header we need for progress calculations + r.Header.Add("Accept-Encoding", "gzip") + + return trans.RoundTrip(r) + }, + } + + // issue the download request + return d.HttpClient.Do(req) +} + +func (d *Download) Check() (available bool, err error) { + resp, err := d.sharedHttp(0) + if err != nil { + return + } + resp.Body.Close() + + switch resp.StatusCode { + // ok + case 200, 206: + available = true + + // no update available + case 204: + available = false + + // server error + default: + err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status) + return + } + + return +} + +// Get() downloads the given url from the internet to a file on disk +// and then calls FromStream() to update the current program's executable file +// with the contents of that file. +// +// If the update is successful, the downloaded file will be erased from disk. +// Otherwise, it will remain in d.Path to allow the download to resume later +// or be skipped entirely. +// +// Only HTTP/1.1 servers that implement the Range header support resuming a +// partially completed download. +// +// UpdateFromUrl() uses HTTP status codes to determine what action to take. +// +// - The HTTP server should return 200 or 206 for the update to be downloaded. +// +// - The HTTP server should return 204 if no update is available at this time. +// +// - If the HTTP server returns a 3XX redirect, it will be followed +// according to d.HttpClient's redirect policy. +// +// - Any other HTTP status code will cause UpdateFromUrl to return an error. +func (d *Download) Get() (err error) { + var offset int64 = 0 + var fp *os.File + + // Close the progress channel whenever this function completes + defer close(d.Progress) + + // open a file where we will stream the downloaded update to + // we do this first because if the caller specified a non-empty dlpath + // we need to determine how large it is in order to resume the download + if d.Path == "" { + // no dlpath specified, use a random tempfile + fp, err = ioutil.TempFile("", "update") + if err != nil { + return + } + defer fp.Close() + + // remember the path + d.Path = fp.Name() + } else { + fp, err = os.OpenFile(d.Path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0600) + if err != nil { + return + } + defer fp.Close() + + // determine the file size so we can resume the download, if possible + var fi os.FileInfo + fi, err = fp.Stat() + if err != nil { + return + } + + offset = fi.Size() + } + + // start downloading the file + resp, err := d.sharedHttp(offset) + if err != nil { + return + } + defer resp.Body.Close() + + switch resp.StatusCode { + // ok + case 200, 206: + d.Available = true + + // no update available + case 204: + return + + // server error + default: + err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status) + return + } + + // Determine how much we have to download + // net/http sets this to -1 when it is unknown + clength := resp.ContentLength + + // Read the content from the response body + rd := resp.Body + + // meter the rate at which we download content for + // progress reporting if we know how much to expect + if clength > 0 { + rd = &MeteredReader{rd: rd, totalSize: clength, progress: d.Progress} + } + + // Decompress the content if necessary + if resp.Header.Get("Content-Encoding") == "gzip" { + rd, err = gzip.NewReader(rd) + if err != nil { + return + } + } + + // Download the update + _, err = io.Copy(fp, rd) + if err != nil { + return + } + + return +} + +func (d *Download) GetAndUpdate() (err error, errRecover error) { + // check before we download if this will work + if err = SanityCheck(); err != nil { + // keep the contract that d.Progress will close whenever Get() terminates + close(d.Progress) + return + } + + // download the update + if err = d.Get(); err != nil || !d.Available { + return + } + + // apply the update + if err, errRecover = FromFile(d.Path); err != nil || errRecover != nil { + return + } + + // remove the temporary file + os.Remove(d.Path) + return +} + +// FromUrl downloads the contents of the given url and uses them to update +// the current program's executable file. It is a convenience function which is equivalent to +// +// NewDownload(url).GetAndUpdate() +// +// See Download.Get() for more details. +func FromUrl(url string) (err error, errRecover error) { + return NewDownload(url).GetAndUpdate() +} + +// FromFile reads the contents of the given file and uses them +// to update the current program's executable file by calling FromStream(). +func FromFile(filepath string) (err error, errRecover error) { + // open the new binary + fp, err := os.Open(filepath) + if err != nil { + return + } + defer fp.Close() + + // do the update + return FromStream(fp) +} + +// FromStream reads the contents of the supplied io.Reader newBinary +// and uses them to update the current program's executable file. +// +// FromStream performs the following actions to ensure a cross-platform safe +// update: +// +// - Creates a new file, /path/to/.program-name.new with mode 0755 and copies +// the contents of newBinary into the file +// +// - Renames the current program's executable file from /path/to/program-name +// to /path/to/.program-name.old +// +// - Renames /path/to/.program-name.new to /path/to/program-name +// +// - If the rename is successful, it erases /path/to/.program.old. If this operation +// fails, no error is reported. +// +// - If the rename is unsuccessful, it attempts to rename /path/to/.program-name.old +// back to /path/to/program-name. If this operation fails, the error is not reported +// in order to not mask the error that caused the rename recovery attempt. +func FromStream(newBinary io.Reader) (err error, errRecover error) { + // get the path to the executable + thisExecPath, err := osext.Executable() + if err != nil { + return + } + + // get the directory the executable exists in + execDir := filepath.Dir(thisExecPath) + execName := filepath.Base(thisExecPath) + + // Copy the contents of of newbinary to a the new executable file + newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName)) + fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) + if err != nil { + return + } + defer fp.Close() + _, err = io.Copy(fp, newBinary) + + // if we don't call fp.Close(), windows won't let us move the new executable + // because the file will still be "in use" + fp.Close() + + // this is where we'll move the executable to so that we can swap in the updated replacement + oldExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.old", execName)) + + // delete any existing old exec file - this is necessary on Windows for two reasons: + // 1. after a successful update, windows can't remove the .old file because the process is still running + // 2. windows rename operations fail if the destination file already exists + _ = os.Remove(oldExecPath) + + // move the existing executable to a new file in the same directory + err = os.Rename(thisExecPath, oldExecPath) + if err != nil { + return + } + + // move the new exectuable in to become the new program + err = os.Rename(newExecPath, thisExecPath) + + if err != nil { + // copy unsuccessful + errRecover = os.Rename(oldExecPath, thisExecPath) + } else { + // copy successful, remove the old binary + _ = os.Remove(oldExecPath) + } + + return +} + +// SanityCheck() attempts to determine whether an in-place executable update could +// succeed by performing preliminary checks (to establish valid permissions, etc). +// This helps avoid downloading updates when we know the update can't be successfully +// applied later. +func SanityCheck() (err error) { + // get the path to the executable + thisExecPath, err := osext.Executable() + if err != nil { + return + } + + // get the directory the executable exists in + execDir := filepath.Dir(thisExecPath) + execName := filepath.Base(thisExecPath) + + // attempt to open a file in the executable's directory + newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName)) + fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) + if err != nil { + return + } + fp.Close() + + _ = os.Remove(newExecPath) + return +} diff --git a/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/.gitignore b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/.gitignore new file mode 100644 index 0000000..653f160 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/.gitignore @@ -0,0 +1 @@ +test.* diff --git a/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/License b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/License new file mode 100644 index 0000000..183c389 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/License @@ -0,0 +1,22 @@ +Copyright 2012 Keith Rarick + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/Readme.md b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/Readme.md new file mode 100644 index 0000000..dadc368 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/Readme.md @@ -0,0 +1,7 @@ +# binarydist + +Package binarydist implements binary diff and patch as described on +. It reads and writes files +compatible with the tools there. + +Documentation at . diff --git a/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/bzip2.go b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/bzip2.go new file mode 100644 index 0000000..a2516b8 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/bzip2.go @@ -0,0 +1,40 @@ +package binarydist + +import ( + "io" + "os/exec" +) + +type bzip2Writer struct { + c *exec.Cmd + w io.WriteCloser +} + +func (w bzip2Writer) Write(b []byte) (int, error) { + return w.w.Write(b) +} + +func (w bzip2Writer) Close() error { + if err := w.w.Close(); err != nil { + return err + } + return w.c.Wait() +} + +// Package compress/bzip2 implements only decompression, +// so we'll fake it by running bzip2 in another process. +func newBzip2Writer(w io.Writer) (wc io.WriteCloser, err error) { + var bw bzip2Writer + bw.c = exec.Command("bzip2", "-c") + bw.c.Stdout = w + + if bw.w, err = bw.c.StdinPipe(); err != nil { + return nil, err + } + + if err = bw.c.Start(); err != nil { + return nil, err + } + + return bw, nil +} diff --git a/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/common_test.go b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/common_test.go new file mode 100644 index 0000000..af51616 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/common_test.go @@ -0,0 +1,93 @@ +package binarydist + +import ( + "crypto/rand" + "io" + "io/ioutil" + "os" +) + +func mustOpen(path string) *os.File { + f, err := os.Open(path) + if err != nil { + panic(err) + } + + return f +} + +func mustReadAll(r io.Reader) []byte { + b, err := ioutil.ReadAll(r) + if err != nil { + panic(err) + } + return b +} + +func fileCmp(a, b *os.File) int64 { + sa, err := a.Seek(0, 2) + if err != nil { + panic(err) + } + + sb, err := b.Seek(0, 2) + if err != nil { + panic(err) + } + + if sa != sb { + return sa + } + + _, err = a.Seek(0, 0) + if err != nil { + panic(err) + } + + _, err = b.Seek(0, 0) + if err != nil { + panic(err) + } + + pa, err := ioutil.ReadAll(a) + if err != nil { + panic(err) + } + + pb, err := ioutil.ReadAll(b) + if err != nil { + panic(err) + } + + for i := range pa { + if pa[i] != pb[i] { + return int64(i) + } + } + return -1 +} + +func mustWriteRandFile(path string, size int) *os.File { + p := make([]byte, size) + _, err := rand.Read(p) + if err != nil { + panic(err) + } + + f, err := os.Create(path) + if err != nil { + panic(err) + } + + _, err = f.Write(p) + if err != nil { + panic(err) + } + + _, err = f.Seek(0, 0) + if err != nil { + panic(err) + } + + return f +} diff --git a/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/diff.go b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/diff.go new file mode 100644 index 0000000..1d2d951 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/diff.go @@ -0,0 +1,408 @@ +package binarydist + +import ( + "bytes" + "encoding/binary" + "io" + "io/ioutil" +) + +func swap(a []int, i, j int) { a[i], a[j] = a[j], a[i] } + +func split(I, V []int, start, length, h int) { + var i, j, k, x, jj, kk int + + if length < 16 { + for k = start; k < start+length; k += j { + j = 1 + x = V[I[k]+h] + for i = 1; k+i < start+length; i++ { + if V[I[k+i]+h] < x { + x = V[I[k+i]+h] + j = 0 + } + if V[I[k+i]+h] == x { + swap(I, k+i, k+j) + j++ + } + } + for i = 0; i < j; i++ { + V[I[k+i]] = k + j - 1 + } + if j == 1 { + I[k] = -1 + } + } + return + } + + x = V[I[start+length/2]+h] + jj = 0 + kk = 0 + for i = start; i < start+length; i++ { + if V[I[i]+h] < x { + jj++ + } + if V[I[i]+h] == x { + kk++ + } + } + jj += start + kk += jj + + i = start + j = 0 + k = 0 + for i < jj { + if V[I[i]+h] < x { + i++ + } else if V[I[i]+h] == x { + swap(I, i, jj+j) + j++ + } else { + swap(I, i, kk+k) + k++ + } + } + + for jj+j < kk { + if V[I[jj+j]+h] == x { + j++ + } else { + swap(I, jj+j, kk+k) + k++ + } + } + + if jj > start { + split(I, V, start, jj-start, h) + } + + for i = 0; i < kk-jj; i++ { + V[I[jj+i]] = kk - 1 + } + if jj == kk-1 { + I[jj] = -1 + } + + if start+length > kk { + split(I, V, kk, start+length-kk, h) + } +} + +func qsufsort(obuf []byte) []int { + var buckets [256]int + var i, h int + I := make([]int, len(obuf)+1) + V := make([]int, len(obuf)+1) + + for _, c := range obuf { + buckets[c]++ + } + for i = 1; i < 256; i++ { + buckets[i] += buckets[i-1] + } + copy(buckets[1:], buckets[:]) + buckets[0] = 0 + + for i, c := range obuf { + buckets[c]++ + I[buckets[c]] = i + } + + I[0] = len(obuf) + for i, c := range obuf { + V[i] = buckets[c] + } + + V[len(obuf)] = 0 + for i = 1; i < 256; i++ { + if buckets[i] == buckets[i-1]+1 { + I[buckets[i]] = -1 + } + } + I[0] = -1 + + for h = 1; I[0] != -(len(obuf) + 1); h += h { + var n int + for i = 0; i < len(obuf)+1; { + if I[i] < 0 { + n -= I[i] + i -= I[i] + } else { + if n != 0 { + I[i-n] = -n + } + n = V[I[i]] + 1 - i + split(I, V, i, n, h) + i += n + n = 0 + } + } + if n != 0 { + I[i-n] = -n + } + } + + for i = 0; i < len(obuf)+1; i++ { + I[V[i]] = i + } + return I +} + +func matchlen(a, b []byte) (i int) { + for i < len(a) && i < len(b) && a[i] == b[i] { + i++ + } + return i +} + +func search(I []int, obuf, nbuf []byte, st, en int) (pos, n int) { + if en-st < 2 { + x := matchlen(obuf[I[st]:], nbuf) + y := matchlen(obuf[I[en]:], nbuf) + + if x > y { + return I[st], x + } else { + return I[en], y + } + } + + x := st + (en-st)/2 + if bytes.Compare(obuf[I[x]:], nbuf) < 0 { + return search(I, obuf, nbuf, x, en) + } else { + return search(I, obuf, nbuf, st, x) + } + panic("unreached") +} + +// Diff computes the difference between old and new, according to the bsdiff +// algorithm, and writes the result to patch. +func Diff(old, new io.Reader, patch io.Writer) error { + obuf, err := ioutil.ReadAll(old) + if err != nil { + return err + } + + nbuf, err := ioutil.ReadAll(new) + if err != nil { + return err + } + + pbuf, err := diffBytes(obuf, nbuf) + if err != nil { + return err + } + + _, err = patch.Write(pbuf) + return err +} + +func diffBytes(obuf, nbuf []byte) ([]byte, error) { + var patch seekBuffer + err := diff(obuf, nbuf, &patch) + if err != nil { + return nil, err + } + return patch.buf, nil +} + +func diff(obuf, nbuf []byte, patch io.WriteSeeker) error { + var lenf int + I := qsufsort(obuf) + db := make([]byte, len(nbuf)) + eb := make([]byte, len(nbuf)) + var dblen, eblen int + + var hdr header + hdr.Magic = magic + hdr.NewSize = int64(len(nbuf)) + err := binary.Write(patch, signMagLittleEndian{}, &hdr) + if err != nil { + return err + } + + // Compute the differences, writing ctrl as we go + pfbz2, err := newBzip2Writer(patch) + if err != nil { + return err + } + var scan, pos, length int + var lastscan, lastpos, lastoffset int + for scan < len(nbuf) { + var oldscore int + scan += length + for scsc := scan; scan < len(nbuf); scan++ { + pos, length = search(I, obuf, nbuf[scan:], 0, len(obuf)) + + for ; scsc < scan+length; scsc++ { + if scsc+lastoffset < len(obuf) && + obuf[scsc+lastoffset] == nbuf[scsc] { + oldscore++ + } + } + + if (length == oldscore && length != 0) || length > oldscore+8 { + break + } + + if scan+lastoffset < len(obuf) && obuf[scan+lastoffset] == nbuf[scan] { + oldscore-- + } + } + + if length != oldscore || scan == len(nbuf) { + var s, Sf int + lenf = 0 + for i := 0; lastscan+i < scan && lastpos+i < len(obuf); { + if obuf[lastpos+i] == nbuf[lastscan+i] { + s++ + } + i++ + if s*2-i > Sf*2-lenf { + Sf = s + lenf = i + } + } + + lenb := 0 + if scan < len(nbuf) { + var s, Sb int + for i := 1; (scan >= lastscan+i) && (pos >= i); i++ { + if obuf[pos-i] == nbuf[scan-i] { + s++ + } + if s*2-i > Sb*2-lenb { + Sb = s + lenb = i + } + } + } + + if lastscan+lenf > scan-lenb { + overlap := (lastscan + lenf) - (scan - lenb) + s := 0 + Ss := 0 + lens := 0 + for i := 0; i < overlap; i++ { + if nbuf[lastscan+lenf-overlap+i] == obuf[lastpos+lenf-overlap+i] { + s++ + } + if nbuf[scan-lenb+i] == obuf[pos-lenb+i] { + s-- + } + if s > Ss { + Ss = s + lens = i + 1 + } + } + + lenf += lens - overlap + lenb -= lens + } + + for i := 0; i < lenf; i++ { + db[dblen+i] = nbuf[lastscan+i] - obuf[lastpos+i] + } + for i := 0; i < (scan-lenb)-(lastscan+lenf); i++ { + eb[eblen+i] = nbuf[lastscan+lenf+i] + } + + dblen += lenf + eblen += (scan - lenb) - (lastscan + lenf) + + err = binary.Write(pfbz2, signMagLittleEndian{}, int64(lenf)) + if err != nil { + pfbz2.Close() + return err + } + + val := (scan - lenb) - (lastscan + lenf) + err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val)) + if err != nil { + pfbz2.Close() + return err + } + + val = (pos - lenb) - (lastpos + lenf) + err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val)) + if err != nil { + pfbz2.Close() + return err + } + + lastscan = scan - lenb + lastpos = pos - lenb + lastoffset = pos - scan + } + } + err = pfbz2.Close() + if err != nil { + return err + } + + // Compute size of compressed ctrl data + l64, err := patch.Seek(0, 1) + if err != nil { + return err + } + hdr.CtrlLen = int64(l64 - 32) + + // Write compressed diff data + pfbz2, err = newBzip2Writer(patch) + if err != nil { + return err + } + n, err := pfbz2.Write(db[:dblen]) + if err != nil { + pfbz2.Close() + return err + } + if n != dblen { + pfbz2.Close() + return io.ErrShortWrite + } + err = pfbz2.Close() + if err != nil { + return err + } + + // Compute size of compressed diff data + n64, err := patch.Seek(0, 1) + if err != nil { + return err + } + hdr.DiffLen = n64 - l64 + + // Write compressed extra data + pfbz2, err = newBzip2Writer(patch) + if err != nil { + return err + } + n, err = pfbz2.Write(eb[:eblen]) + if err != nil { + pfbz2.Close() + return err + } + if n != eblen { + pfbz2.Close() + return io.ErrShortWrite + } + err = pfbz2.Close() + if err != nil { + return err + } + + // Seek to the beginning, write the header, and close the file + _, err = patch.Seek(0, 0) + if err != nil { + return err + } + err = binary.Write(patch, signMagLittleEndian{}, &hdr) + if err != nil { + return err + } + return nil +} diff --git a/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/diff_test.go b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/diff_test.go new file mode 100644 index 0000000..9baa492 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/diff_test.go @@ -0,0 +1,67 @@ +package binarydist + +import ( + "bytes" + "io/ioutil" + "os" + "os/exec" + "testing" +) + +var diffT = []struct { + old *os.File + new *os.File +}{ + { + old: mustWriteRandFile("test.old", 1e3), + new: mustWriteRandFile("test.new", 1e3), + }, + { + old: mustOpen("testdata/sample.old"), + new: mustOpen("testdata/sample.new"), + }, +} + +func TestDiff(t *testing.T) { + for _, s := range diffT { + got, err := ioutil.TempFile("/tmp", "bspatch.") + if err != nil { + panic(err) + } + os.Remove(got.Name()) + + exp, err := ioutil.TempFile("/tmp", "bspatch.") + if err != nil { + panic(err) + } + + cmd := exec.Command("bsdiff", s.old.Name(), s.new.Name(), exp.Name()) + cmd.Stdout = os.Stdout + err = cmd.Run() + os.Remove(exp.Name()) + if err != nil { + panic(err) + } + + err = Diff(s.old, s.new, got) + if err != nil { + t.Fatal("err", err) + } + + _, err = got.Seek(0, 0) + if err != nil { + panic(err) + } + gotBuf := mustReadAll(got) + expBuf := mustReadAll(exp) + + if !bytes.Equal(gotBuf, expBuf) { + t.Fail() + t.Logf("diff %s %s", s.old.Name(), s.new.Name()) + t.Logf("%s: len(got) = %d", got.Name(), len(gotBuf)) + t.Logf("%s: len(exp) = %d", exp.Name(), len(expBuf)) + i := matchlen(gotBuf, expBuf) + t.Logf("produced different output at pos %d; %d != %d", i, gotBuf[i], expBuf[i]) + } + } +} diff --git a/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/doc.go b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/doc.go new file mode 100644 index 0000000..3c92d87 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/doc.go @@ -0,0 +1,24 @@ +// Package binarydist implements binary diff and patch as described on +// http://www.daemonology.net/bsdiff/. It reads and writes files +// compatible with the tools there. +package binarydist + +var magic = [8]byte{'B', 'S', 'D', 'I', 'F', 'F', '4', '0'} + +// File format: +// 0 8 "BSDIFF40" +// 8 8 X +// 16 8 Y +// 24 8 sizeof(newfile) +// 32 X bzip2(control block) +// 32+X Y bzip2(diff block) +// 32+X+Y ??? bzip2(extra block) +// with control block a set of triples (x,y,z) meaning "add x bytes +// from oldfile to x bytes from the diff block; copy y bytes from the +// extra block; seek forwards in oldfile by z bytes". +type header struct { + Magic [8]byte + CtrlLen int64 + DiffLen int64 + NewSize int64 +} diff --git a/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/encoding.go b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/encoding.go new file mode 100644 index 0000000..75ba585 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/encoding.go @@ -0,0 +1,53 @@ +package binarydist + +// SignMagLittleEndian is the numeric encoding used by the bsdiff tools. +// It implements binary.ByteOrder using a sign-magnitude format +// and little-endian byte order. Only methods Uint64 and String +// have been written; the rest panic. +type signMagLittleEndian struct{} + +func (signMagLittleEndian) Uint16(b []byte) uint16 { panic("unimplemented") } + +func (signMagLittleEndian) PutUint16(b []byte, v uint16) { panic("unimplemented") } + +func (signMagLittleEndian) Uint32(b []byte) uint32 { panic("unimplemented") } + +func (signMagLittleEndian) PutUint32(b []byte, v uint32) { panic("unimplemented") } + +func (signMagLittleEndian) Uint64(b []byte) uint64 { + y := int64(b[0]) | + int64(b[1])<<8 | + int64(b[2])<<16 | + int64(b[3])<<24 | + int64(b[4])<<32 | + int64(b[5])<<40 | + int64(b[6])<<48 | + int64(b[7]&0x7f)<<56 + + if b[7]&0x80 != 0 { + y = -y + } + return uint64(y) +} + +func (signMagLittleEndian) PutUint64(b []byte, v uint64) { + x := int64(v) + neg := x < 0 + if neg { + x = -x + } + + b[0] = byte(x) + b[1] = byte(x >> 8) + b[2] = byte(x >> 16) + b[3] = byte(x >> 24) + b[4] = byte(x >> 32) + b[5] = byte(x >> 40) + b[6] = byte(x >> 48) + b[7] = byte(x >> 56) + if neg { + b[7] |= 0x80 + } +} + +func (signMagLittleEndian) String() string { return "signMagLittleEndian" } diff --git a/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/patch.go b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/patch.go new file mode 100644 index 0000000..eb03225 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/patch.go @@ -0,0 +1,109 @@ +package binarydist + +import ( + "bytes" + "compress/bzip2" + "encoding/binary" + "errors" + "io" + "io/ioutil" +) + +var ErrCorrupt = errors.New("corrupt patch") + +// Patch applies patch to old, according to the bspatch algorithm, +// and writes the result to new. +func Patch(old io.Reader, new io.Writer, patch io.Reader) error { + var hdr header + err := binary.Read(patch, signMagLittleEndian{}, &hdr) + if err != nil { + return err + } + if hdr.Magic != magic { + return ErrCorrupt + } + if hdr.CtrlLen < 0 || hdr.DiffLen < 0 || hdr.NewSize < 0 { + return ErrCorrupt + } + + ctrlbuf := make([]byte, hdr.CtrlLen) + _, err = io.ReadFull(patch, ctrlbuf) + if err != nil { + return err + } + cpfbz2 := bzip2.NewReader(bytes.NewReader(ctrlbuf)) + + diffbuf := make([]byte, hdr.DiffLen) + _, err = io.ReadFull(patch, diffbuf) + if err != nil { + return err + } + dpfbz2 := bzip2.NewReader(bytes.NewReader(diffbuf)) + + // The entire rest of the file is the extra block. + epfbz2 := bzip2.NewReader(patch) + + obuf, err := ioutil.ReadAll(old) + if err != nil { + return err + } + + nbuf := make([]byte, hdr.NewSize) + + var oldpos, newpos int64 + for newpos < hdr.NewSize { + var ctrl struct{ Add, Copy, Seek int64 } + err = binary.Read(cpfbz2, signMagLittleEndian{}, &ctrl) + if err != nil { + return err + } + + // Sanity-check + if newpos+ctrl.Add > hdr.NewSize { + return ErrCorrupt + } + + // Read diff string + _, err = io.ReadFull(dpfbz2, nbuf[newpos:newpos+ctrl.Add]) + if err != nil { + return ErrCorrupt + } + + // Add old data to diff string + for i := int64(0); i < ctrl.Add; i++ { + if oldpos+i >= 0 && oldpos+i < int64(len(obuf)) { + nbuf[newpos+i] += obuf[oldpos+i] + } + } + + // Adjust pointers + newpos += ctrl.Add + oldpos += ctrl.Add + + // Sanity-check + if newpos+ctrl.Copy > hdr.NewSize { + return ErrCorrupt + } + + // Read extra string + _, err = io.ReadFull(epfbz2, nbuf[newpos:newpos+ctrl.Copy]) + if err != nil { + return ErrCorrupt + } + + // Adjust pointers + newpos += ctrl.Copy + oldpos += ctrl.Seek + } + + // Write the new file + for len(nbuf) > 0 { + n, err := new.Write(nbuf) + if err != nil { + return err + } + nbuf = nbuf[n:] + } + + return nil +} diff --git a/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/patch_test.go b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/patch_test.go new file mode 100644 index 0000000..840a919 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/patch_test.go @@ -0,0 +1,62 @@ +package binarydist + +import ( + "io/ioutil" + "os" + "os/exec" + "testing" +) + +func TestPatch(t *testing.T) { + mustWriteRandFile("test.old", 1e3) + mustWriteRandFile("test.new", 1e3) + + got, err := ioutil.TempFile("/tmp", "bspatch.") + if err != nil { + panic(err) + } + os.Remove(got.Name()) + + err = exec.Command("bsdiff", "test.old", "test.new", "test.patch").Run() + if err != nil { + panic(err) + } + + err = Patch(mustOpen("test.old"), got, mustOpen("test.patch")) + if err != nil { + t.Fatal("err", err) + } + + ref, err := got.Seek(0, 2) + if err != nil { + panic(err) + } + + t.Logf("got %d bytes", ref) + if n := fileCmp(got, mustOpen("test.new")); n > -1 { + t.Fatalf("produced different output at pos %d", n) + } +} + +func TestPatchHk(t *testing.T) { + got, err := ioutil.TempFile("/tmp", "bspatch.") + if err != nil { + panic(err) + } + os.Remove(got.Name()) + + err = Patch(mustOpen("testdata/sample.old"), got, mustOpen("testdata/sample.patch")) + if err != nil { + t.Fatal("err", err) + } + + ref, err := got.Seek(0, 2) + if err != nil { + panic(err) + } + + t.Logf("got %d bytes", ref) + if n := fileCmp(got, mustOpen("testdata/sample.new")); n > -1 { + t.Fatalf("produced different output at pos %d", n) + } +} diff --git a/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/seek.go b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/seek.go new file mode 100644 index 0000000..96c0346 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/seek.go @@ -0,0 +1,43 @@ +package binarydist + +import ( + "errors" +) + +type seekBuffer struct { + buf []byte + pos int +} + +func (b *seekBuffer) Write(p []byte) (n int, err error) { + n = copy(b.buf[b.pos:], p) + if n == len(p) { + b.pos += n + return n, nil + } + b.buf = append(b.buf, p[n:]...) + b.pos += len(p) + return len(p), nil +} + +func (b *seekBuffer) Seek(offset int64, whence int) (ret int64, err error) { + var abs int64 + switch whence { + case 0: + abs = offset + case 1: + abs = int64(b.pos) + offset + case 2: + abs = int64(len(b.buf)) + offset + default: + return 0, errors.New("binarydist: invalid whence") + } + if abs < 0 { + return 0, errors.New("binarydist: negative position") + } + if abs >= 1<<31 { + return 0, errors.New("binarydist: position out of range") + } + b.pos = int(abs) + return abs, nil +} diff --git a/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/sort_test.go b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/sort_test.go new file mode 100644 index 0000000..be483c3 --- /dev/null +++ b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/sort_test.go @@ -0,0 +1,33 @@ +package binarydist + +import ( + "bytes" + "crypto/rand" + "testing" +) + +var sortT = [][]byte{ + mustRandBytes(1000), + mustReadAll(mustOpen("test.old")), + []byte("abcdefabcdef"), +} + +func TestQsufsort(t *testing.T) { + for _, s := range sortT { + I := qsufsort(s) + for i := 1; i < len(I); i++ { + if bytes.Compare(s[I[i-1]:], s[I[i]:]) > 0 { + t.Fatalf("unsorted at %d", i) + } + } + } +} + +func mustRandBytes(n int) []byte { + b := make([]byte, n) + _, err := rand.Read(b) + if err != nil { + panic(err) + } + return b +} diff --git a/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.new b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.new new file mode 100644 index 0000000000000000000000000000000000000000..592cdbe2dff6a7f7f9bcdbd80fdf30fb775d8e13 GIT binary patch literal 10000 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 Jc&U;-_-}XSHje-R literal 0 HcmV?d00001 diff --git a/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.old b/selfupdate/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.old new file mode 100644 index 0000000000000000000000000000000000000000..7bc64dacb0a93810ef51ba9bba355b1755738734 GIT binary patch literal 11000 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 F1^_f>!+rn& literal 0 HcmV?d00001