parent
6f18d35abf
commit
184295c57e
@ -0,0 +1,24 @@
|
||||
{
|
||||
"remotePath": "/usr/local/gopath/src/github.com/soopsio/gosuv/",
|
||||
"host": "10.10.99.177",
|
||||
"username": "root",
|
||||
"password": "111111",
|
||||
"port": 22,
|
||||
"secure": false,
|
||||
"protocol": "sftp",
|
||||
"uploadOnSave": true,
|
||||
"passive": false,
|
||||
"debug": true,
|
||||
"privateKeyPath": null,
|
||||
"passphrase": null,
|
||||
"ignore": [
|
||||
"\\.vscode",
|
||||
"\\.git",
|
||||
"\\.DS_Store"
|
||||
],
|
||||
"generatedFiles": {
|
||||
"uploadOnSave": false,
|
||||
"extensionsToInclude": [],
|
||||
"path": ""
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
hash: ad7063d34838040bf683183342f3814298179801e2e055f29bca796456c88300
|
||||
updated: 2017-08-11T19:08:31.193345249+08:00
|
||||
imports:
|
||||
- name: github.com/codeskyblue/kexec
|
||||
version: 863094f94c7fb7c235764bf8f0f79cccea78c8eb
|
||||
- name: github.com/equinox-io/equinox
|
||||
version: 6f97d0d3970881d3e53dd6f547a41109eb055e54
|
||||
subpackages:
|
||||
- internal/go-update
|
||||
- internal/go-update/internal/binarydist
|
||||
- internal/go-update/internal/osext
|
||||
- internal/osext
|
||||
- proto
|
||||
- name: github.com/franela/goreq
|
||||
version: b5b0f5eb2d16f20345cce0a544a75163579c0b00
|
||||
- name: github.com/glycerine/rbuf
|
||||
version: 96ad00d7fa74f7dd9857f2b6068451062b4ebc5d
|
||||
- name: github.com/go-yaml/yaml
|
||||
version: 25c4ec802a7d637f88d584ab26798e94ad14c13b
|
||||
- name: github.com/goji/httpauth
|
||||
version: 2da839ab0f4df05a6db5eb277995589dadbd4fb9
|
||||
- name: github.com/gorilla/context
|
||||
version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
|
||||
- name: github.com/gorilla/mux
|
||||
version: ac112f7d75a0714af1bd86ab17749b31f7809640
|
||||
- name: github.com/gorilla/websocket
|
||||
version: a69d9f6de432e2c6b296a947d8a5ee88f68522cf
|
||||
- name: github.com/kennygrant/sanitize
|
||||
version: 6a0bfdde8629a3a3a7418a7eae45c54154692514
|
||||
- name: github.com/mitchellh/go-ps
|
||||
version: 4fdf99ab29366514c69ccccddab5dc58b8d84062
|
||||
- name: github.com/qiniu/log
|
||||
version: a304a74568d6982c5b89de1c68ac8fca3add196a
|
||||
- name: github.com/shurcooL/httpfs
|
||||
version: bc35257962c2dea93e81c976b72c7c6eac45fd8a
|
||||
subpackages:
|
||||
- vfsutil
|
||||
- name: github.com/shurcooL/vfsgen
|
||||
version: 385e5833a54aaba5860ca26036b8e8b72135ab96
|
||||
- name: github.com/urfave/cli
|
||||
version: cfb38830724cc34fedffe9a2a29fb54fa9169cd1
|
||||
- name: golang.org/x/net
|
||||
version: 1c05540f6879653db88113bc4a2b70aec4bd491f
|
||||
subpackages:
|
||||
- html
|
||||
- html/atom
|
||||
- name: golang.org/x/tools
|
||||
version: 5831d16d18029819d39f99bdc2060b8eff410b6b
|
||||
subpackages:
|
||||
- godoc/vfs
|
||||
testImports:
|
||||
- name: github.com/gopherjs/gopherjs
|
||||
version: 2b1d432c8a82c9bff0b0baffaeb3ec6e92974112
|
||||
subpackages:
|
||||
- js
|
||||
- name: github.com/jtolds/gls
|
||||
version: 77f18212c9c7edc9bd6a33d383a7b545ce62f064
|
||||
- name: github.com/smartystreets/assertions
|
||||
version: 1540c14c9f1bd1abeba90f29762a4c6e50582303
|
||||
subpackages:
|
||||
- internal/go-render/render
|
||||
- internal/oglematchers
|
||||
- name: github.com/smartystreets/goconvey
|
||||
version: 9e8dc3f972df6c8fcc0375ef492c24d0bb204857
|
||||
subpackages:
|
||||
- convey
|
||||
- convey/gotest
|
||||
- convey/reporting
|
||||
- name: github.com/smartystreets/logging
|
||||
version: ac3a674540761aa0b4382094ba4795f917e85c7f
|
@ -1,587 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap v3.3.5 (http://getbootstrap.com)
|
||||
* Copyright 2011-2015 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
*/
|
||||
.btn-default,
|
||||
.btn-primary,
|
||||
.btn-success,
|
||||
.btn-info,
|
||||
.btn-warning,
|
||||
.btn-danger {
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
|
||||
}
|
||||
.btn-default:active,
|
||||
.btn-primary:active,
|
||||
.btn-success:active,
|
||||
.btn-info:active,
|
||||
.btn-warning:active,
|
||||
.btn-danger:active,
|
||||
.btn-default.active,
|
||||
.btn-primary.active,
|
||||
.btn-success.active,
|
||||
.btn-info.active,
|
||||
.btn-warning.active,
|
||||
.btn-danger.active {
|
||||
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
|
||||
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
|
||||
}
|
||||
.btn-default.disabled,
|
||||
.btn-primary.disabled,
|
||||
.btn-success.disabled,
|
||||
.btn-info.disabled,
|
||||
.btn-warning.disabled,
|
||||
.btn-danger.disabled,
|
||||
.btn-default[disabled],
|
||||
.btn-primary[disabled],
|
||||
.btn-success[disabled],
|
||||
.btn-info[disabled],
|
||||
.btn-warning[disabled],
|
||||
.btn-danger[disabled],
|
||||
fieldset[disabled] .btn-default,
|
||||
fieldset[disabled] .btn-primary,
|
||||
fieldset[disabled] .btn-success,
|
||||
fieldset[disabled] .btn-info,
|
||||
fieldset[disabled] .btn-warning,
|
||||
fieldset[disabled] .btn-danger {
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.btn-default .badge,
|
||||
.btn-primary .badge,
|
||||
.btn-success .badge,
|
||||
.btn-info .badge,
|
||||
.btn-warning .badge,
|
||||
.btn-danger .badge {
|
||||
text-shadow: none;
|
||||
}
|
||||
.btn:active,
|
||||
.btn.active {
|
||||
background-image: none;
|
||||
}
|
||||
.btn-default {
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
|
||||
background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
|
||||
background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #dbdbdb;
|
||||
border-color: #ccc;
|
||||
}
|
||||
.btn-default:hover,
|
||||
.btn-default:focus {
|
||||
background-color: #e0e0e0;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-default:active,
|
||||
.btn-default.active {
|
||||
background-color: #e0e0e0;
|
||||
border-color: #dbdbdb;
|
||||
}
|
||||
.btn-default.disabled,
|
||||
.btn-default[disabled],
|
||||
fieldset[disabled] .btn-default,
|
||||
.btn-default.disabled:hover,
|
||||
.btn-default[disabled]:hover,
|
||||
fieldset[disabled] .btn-default:hover,
|
||||
.btn-default.disabled:focus,
|
||||
.btn-default[disabled]:focus,
|
||||
fieldset[disabled] .btn-default:focus,
|
||||
.btn-default.disabled.focus,
|
||||
.btn-default[disabled].focus,
|
||||
fieldset[disabled] .btn-default.focus,
|
||||
.btn-default.disabled:active,
|
||||
.btn-default[disabled]:active,
|
||||
fieldset[disabled] .btn-default:active,
|
||||
.btn-default.disabled.active,
|
||||
.btn-default[disabled].active,
|
||||
fieldset[disabled] .btn-default.active {
|
||||
background-color: #e0e0e0;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-primary {
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #245580;
|
||||
}
|
||||
.btn-primary:hover,
|
||||
.btn-primary:focus {
|
||||
background-color: #265a88;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-primary:active,
|
||||
.btn-primary.active {
|
||||
background-color: #265a88;
|
||||
border-color: #245580;
|
||||
}
|
||||
.btn-primary.disabled,
|
||||
.btn-primary[disabled],
|
||||
fieldset[disabled] .btn-primary,
|
||||
.btn-primary.disabled:hover,
|
||||
.btn-primary[disabled]:hover,
|
||||
fieldset[disabled] .btn-primary:hover,
|
||||
.btn-primary.disabled:focus,
|
||||
.btn-primary[disabled]:focus,
|
||||
fieldset[disabled] .btn-primary:focus,
|
||||
.btn-primary.disabled.focus,
|
||||
.btn-primary[disabled].focus,
|
||||
fieldset[disabled] .btn-primary.focus,
|
||||
.btn-primary.disabled:active,
|
||||
.btn-primary[disabled]:active,
|
||||
fieldset[disabled] .btn-primary:active,
|
||||
.btn-primary.disabled.active,
|
||||
.btn-primary[disabled].active,
|
||||
fieldset[disabled] .btn-primary.active {
|
||||
background-color: #265a88;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-success {
|
||||
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
|
||||
background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
|
||||
background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #3e8f3e;
|
||||
}
|
||||
.btn-success:hover,
|
||||
.btn-success:focus {
|
||||
background-color: #419641;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-success:active,
|
||||
.btn-success.active {
|
||||
background-color: #419641;
|
||||
border-color: #3e8f3e;
|
||||
}
|
||||
.btn-success.disabled,
|
||||
.btn-success[disabled],
|
||||
fieldset[disabled] .btn-success,
|
||||
.btn-success.disabled:hover,
|
||||
.btn-success[disabled]:hover,
|
||||
fieldset[disabled] .btn-success:hover,
|
||||
.btn-success.disabled:focus,
|
||||
.btn-success[disabled]:focus,
|
||||
fieldset[disabled] .btn-success:focus,
|
||||
.btn-success.disabled.focus,
|
||||
.btn-success[disabled].focus,
|
||||
fieldset[disabled] .btn-success.focus,
|
||||
.btn-success.disabled:active,
|
||||
.btn-success[disabled]:active,
|
||||
fieldset[disabled] .btn-success:active,
|
||||
.btn-success.disabled.active,
|
||||
.btn-success[disabled].active,
|
||||
fieldset[disabled] .btn-success.active {
|
||||
background-color: #419641;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-info {
|
||||
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
|
||||
background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
|
||||
background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #28a4c9;
|
||||
}
|
||||
.btn-info:hover,
|
||||
.btn-info:focus {
|
||||
background-color: #2aabd2;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-info:active,
|
||||
.btn-info.active {
|
||||
background-color: #2aabd2;
|
||||
border-color: #28a4c9;
|
||||
}
|
||||
.btn-info.disabled,
|
||||
.btn-info[disabled],
|
||||
fieldset[disabled] .btn-info,
|
||||
.btn-info.disabled:hover,
|
||||
.btn-info[disabled]:hover,
|
||||
fieldset[disabled] .btn-info:hover,
|
||||
.btn-info.disabled:focus,
|
||||
.btn-info[disabled]:focus,
|
||||
fieldset[disabled] .btn-info:focus,
|
||||
.btn-info.disabled.focus,
|
||||
.btn-info[disabled].focus,
|
||||
fieldset[disabled] .btn-info.focus,
|
||||
.btn-info.disabled:active,
|
||||
.btn-info[disabled]:active,
|
||||
fieldset[disabled] .btn-info:active,
|
||||
.btn-info.disabled.active,
|
||||
.btn-info[disabled].active,
|
||||
fieldset[disabled] .btn-info.active {
|
||||
background-color: #2aabd2;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-warning {
|
||||
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
|
||||
background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
|
||||
background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #e38d13;
|
||||
}
|
||||
.btn-warning:hover,
|
||||
.btn-warning:focus {
|
||||
background-color: #eb9316;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-warning:active,
|
||||
.btn-warning.active {
|
||||
background-color: #eb9316;
|
||||
border-color: #e38d13;
|
||||
}
|
||||
.btn-warning.disabled,
|
||||
.btn-warning[disabled],
|
||||
fieldset[disabled] .btn-warning,
|
||||
.btn-warning.disabled:hover,
|
||||
.btn-warning[disabled]:hover,
|
||||
fieldset[disabled] .btn-warning:hover,
|
||||
.btn-warning.disabled:focus,
|
||||
.btn-warning[disabled]:focus,
|
||||
fieldset[disabled] .btn-warning:focus,
|
||||
.btn-warning.disabled.focus,
|
||||
.btn-warning[disabled].focus,
|
||||
fieldset[disabled] .btn-warning.focus,
|
||||
.btn-warning.disabled:active,
|
||||
.btn-warning[disabled]:active,
|
||||
fieldset[disabled] .btn-warning:active,
|
||||
.btn-warning.disabled.active,
|
||||
.btn-warning[disabled].active,
|
||||
fieldset[disabled] .btn-warning.active {
|
||||
background-color: #eb9316;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-danger {
|
||||
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
|
||||
background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
|
||||
background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #b92c28;
|
||||
}
|
||||
.btn-danger:hover,
|
||||
.btn-danger:focus {
|
||||
background-color: #c12e2a;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-danger:active,
|
||||
.btn-danger.active {
|
||||
background-color: #c12e2a;
|
||||
border-color: #b92c28;
|
||||
}
|
||||
.btn-danger.disabled,
|
||||
.btn-danger[disabled],
|
||||
fieldset[disabled] .btn-danger,
|
||||
.btn-danger.disabled:hover,
|
||||
.btn-danger[disabled]:hover,
|
||||
fieldset[disabled] .btn-danger:hover,
|
||||
.btn-danger.disabled:focus,
|
||||
.btn-danger[disabled]:focus,
|
||||
fieldset[disabled] .btn-danger:focus,
|
||||
.btn-danger.disabled.focus,
|
||||
.btn-danger[disabled].focus,
|
||||
fieldset[disabled] .btn-danger.focus,
|
||||
.btn-danger.disabled:active,
|
||||
.btn-danger[disabled]:active,
|
||||
fieldset[disabled] .btn-danger:active,
|
||||
.btn-danger.disabled.active,
|
||||
.btn-danger[disabled].active,
|
||||
fieldset[disabled] .btn-danger.active {
|
||||
background-color: #c12e2a;
|
||||
background-image: none;
|
||||
}
|
||||
.thumbnail,
|
||||
.img-thumbnail {
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
||||
}
|
||||
.dropdown-menu > li > a:hover,
|
||||
.dropdown-menu > li > a:focus {
|
||||
background-color: #e8e8e8;
|
||||
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
|
||||
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.dropdown-menu > .active > a,
|
||||
.dropdown-menu > .active > a:hover,
|
||||
.dropdown-menu > .active > a:focus {
|
||||
background-color: #2e6da4;
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.navbar-default {
|
||||
background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
|
||||
background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));
|
||||
background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
|
||||
}
|
||||
.navbar-default .navbar-nav > .open > a,
|
||||
.navbar-default .navbar-nav > .active > a {
|
||||
background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
|
||||
background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
|
||||
background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
|
||||
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
|
||||
}
|
||||
.navbar-brand,
|
||||
.navbar-nav > li > a {
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
|
||||
}
|
||||
.navbar-inverse {
|
||||
background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
|
||||
background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
|
||||
background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.navbar-inverse .navbar-nav > .open > a,
|
||||
.navbar-inverse .navbar-nav > .active > a {
|
||||
background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
|
||||
background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
|
||||
background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
|
||||
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
|
||||
}
|
||||
.navbar-inverse .navbar-brand,
|
||||
.navbar-inverse .navbar-nav > li > a {
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
|
||||
}
|
||||
.navbar-static-top,
|
||||
.navbar-fixed-top,
|
||||
.navbar-fixed-bottom {
|
||||
border-radius: 0;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.navbar .navbar-nav .open .dropdown-menu > .active > a,
|
||||
.navbar .navbar-nav .open .dropdown-menu > .active > a:hover,
|
||||
.navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
|
||||
color: #fff;
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
}
|
||||
.alert {
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
|
||||
}
|
||||
.alert-success {
|
||||
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
|
||||
background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
|
||||
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #b2dba1;
|
||||
}
|
||||
.alert-info {
|
||||
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
|
||||
background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
|
||||
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #9acfea;
|
||||
}
|
||||
.alert-warning {
|
||||
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
|
||||
background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
|
||||
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #f5e79e;
|
||||
}
|
||||
.alert-danger {
|
||||
background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
|
||||
background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
|
||||
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #dca7a7;
|
||||
}
|
||||
.progress {
|
||||
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
|
||||
background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
|
||||
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.progress-bar {
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.progress-bar-success {
|
||||
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
|
||||
background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
|
||||
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.progress-bar-info {
|
||||
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
|
||||
background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
|
||||
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.progress-bar-warning {
|
||||
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
|
||||
background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
|
||||
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.progress-bar-danger {
|
||||
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
|
||||
background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
|
||||
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.progress-bar-striped {
|
||||
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
|
||||
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
|
||||
background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
|
||||
}
|
||||
.list-group {
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
||||
}
|
||||
.list-group-item.active,
|
||||
.list-group-item.active:hover,
|
||||
.list-group-item.active:focus {
|
||||
text-shadow: 0 -1px 0 #286090;
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #2b669a;
|
||||
}
|
||||
.list-group-item.active .badge,
|
||||
.list-group-item.active:hover .badge,
|
||||
.list-group-item.active:focus .badge {
|
||||
text-shadow: none;
|
||||
}
|
||||
.panel {
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
|
||||
}
|
||||
.panel-default > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
|
||||
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.panel-primary > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.panel-success > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
|
||||
background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
|
||||
background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.panel-info > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
|
||||
background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
|
||||
background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.panel-warning > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
|
||||
background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
|
||||
background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.panel-danger > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
|
||||
background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
|
||||
background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.well {
|
||||
background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
|
||||
background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
|
||||
background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #dcdcdc;
|
||||
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
|
||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-theme.css.map */
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Before Width: | Height: | Size: 106 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -1,13 +0,0 @@
|
||||
// This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.
|
||||
require('../../js/transition.js')
|
||||
require('../../js/alert.js')
|
||||
require('../../js/button.js')
|
||||
require('../../js/carousel.js')
|
||||
require('../../js/collapse.js')
|
||||
require('../../js/dropdown.js')
|
||||
require('../../js/modal.js')
|
||||
require('../../js/tooltip.js')
|
||||
require('../../js/popover.js')
|
||||
require('../../js/scrollspy.js')
|
||||
require('../../js/tab.js')
|
||||
require('../../js/affix.js')
|
@ -1,22 +0,0 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
@ -1,22 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2016 codeskyblue
|
||||
|
||||
|
||||
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.
|
||||
|
@ -1,67 +0,0 @@
|
||||
# kexec
|
||||
[![GoDoc](https://godoc.org/github.com/codeskyblue/kexec?status.svg)](https://godoc.org/github.com/codeskyblue/kexec)
|
||||
|
||||
This is a golang lib, add a `Terminate` command to exec.
|
||||
|
||||
Tested on _windows, linux, darwin._
|
||||
|
||||
This lib has been used in [fswatch](https://github.com/codeskyblue/fswatch).
|
||||
|
||||
## Usage
|
||||
|
||||
go get -v github.com/codeskyblue/kexec
|
||||
|
||||
|
||||
example1:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "github.com/codeskyblue/kexec"
|
||||
|
||||
func main(){
|
||||
p := kexec.Command("python", "flask_main.py")
|
||||
p.Start()
|
||||
p.Terminate(syscall.SIGINT)
|
||||
}
|
||||
```
|
||||
|
||||
example2: see more [examples](examples)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/codeskyblue/kexec"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// In unix will call: bash -c "python flask_main.py"
|
||||
// In windows will call: cmd /c "python flask_main.py"
|
||||
p := kexec.CommandString("python flask_main.py")
|
||||
p.Stdout = os.Stdout
|
||||
p.Stderr = os.Stderr
|
||||
p.Start()
|
||||
p.Terminate(syscall.SIGKILL)
|
||||
}
|
||||
```
|
||||
|
||||
example3:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "github.com/codeskyblue/kexec"
|
||||
|
||||
func main() {
|
||||
p := kexec.Command("whoami")
|
||||
p.SetUser("codeskyblue") // Only works on darwin and linux
|
||||
p.Run()
|
||||
}
|
||||
```
|
||||
|
||||
## PS
|
||||
This lib also support you call `Wait()` twice, which is not support by `os/exec`
|
||||
|
||||
## LICENSE
|
||||
[MIT](LICENSE)
|
@ -1,19 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/codeskyblue/kexec"
|
||||
)
|
||||
|
||||
func main() {
|
||||
p := kexec.CommandString("python flask_main.py")
|
||||
p.Start()
|
||||
time.Sleep(3 * time.Second)
|
||||
err := p.Terminate(syscall.SIGKILL)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import flask
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=46732, debug=True)
|
@ -1,54 +0,0 @@
|
||||
package kexec
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os/exec"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type KCommand struct {
|
||||
*exec.Cmd
|
||||
|
||||
errCs []chan error
|
||||
err error
|
||||
finished bool
|
||||
once sync.Once
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (c *KCommand) Run() error {
|
||||
if err := c.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Wait()
|
||||
}
|
||||
|
||||
// This Wait wraps exec.Wait, but support multi call
|
||||
func (k *KCommand) Wait() error {
|
||||
if k.Process == nil {
|
||||
return errors.New("exec: not started")
|
||||
}
|
||||
k.once.Do(func() {
|
||||
if k.errCs == nil {
|
||||
k.errCs = make([]chan error, 0)
|
||||
}
|
||||
go func() {
|
||||
k.err = k.Cmd.Wait()
|
||||
k.mu.Lock()
|
||||
k.finished = true
|
||||
for _, errC := range k.errCs {
|
||||
errC <- k.err
|
||||
}
|
||||
k.mu.Unlock()
|
||||
}()
|
||||
})
|
||||
k.mu.Lock()
|
||||
if k.finished {
|
||||
k.mu.Unlock()
|
||||
return k.err
|
||||
}
|
||||
errC := make(chan error, 1)
|
||||
k.errCs = append(k.errCs, errC)
|
||||
k.mu.Unlock()
|
||||
return <-errC
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package kexec
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func setupCmd(cmd *exec.Cmd) {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
cmd.SysProcAttr.Setsid = true
|
||||
}
|
||||
|
||||
func Command(name string, arg ...string) *KCommand {
|
||||
cmd := exec.Command(name, arg...)
|
||||
setupCmd(cmd)
|
||||
return &KCommand{
|
||||
Cmd: cmd,
|
||||
}
|
||||
}
|
||||
|
||||
func CommandString(command string) *KCommand {
|
||||
cmd := exec.Command("/bin/bash", "-c", command)
|
||||
setupCmd(cmd)
|
||||
//cmd.Stdout = os.Stdout
|
||||
//cmd.Stderr = os.Stderr
|
||||
return &KCommand{
|
||||
Cmd: cmd,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *KCommand) Terminate(sig os.Signal) (err error) {
|
||||
if p.Process == nil {
|
||||
return
|
||||
}
|
||||
// find pgid, ref: http://unix.stackexchange.com/questions/14815/process-descendants
|
||||
group, err := os.FindProcess(-1 * p.Process.Pid)
|
||||
//log.Println(group)
|
||||
if err == nil {
|
||||
err = group.Signal(sig)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Ref: http://stackoverflow.com/questions/21705950/running-external-commands-through-os-exec-under-another-user
|
||||
func (k *KCommand) SetUser(name string) (err error) {
|
||||
u, err := user.Lookup(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uid, err := strconv.Atoi(u.Uid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gid, err := strconv.Atoi(u.Gid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if k.SysProcAttr == nil {
|
||||
k.SysProcAttr = &syscall.SysProcAttr{}
|
||||
}
|
||||
k.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
|
||||
return nil
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
package kexec
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestCommand(t *testing.T) {
|
||||
Convey("1 should equal 1", t, func() {
|
||||
So(1, ShouldEqual, 1)
|
||||
})
|
||||
|
||||
Convey("kexec should work as normal os/exec", t, func() {
|
||||
cmd := Command("echo", "-n", "123")
|
||||
data, err := cmd.Output()
|
||||
So(err, ShouldBeNil)
|
||||
So(string(data), ShouldEqual, "123")
|
||||
})
|
||||
|
||||
Convey("the terminate should kill proc", t, func() {
|
||||
cmd := CommandString("sleep 51")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Start()
|
||||
time.Sleep(time.Millisecond * 50)
|
||||
cmd.Terminate(syscall.SIGINT)
|
||||
err := cmd.Wait()
|
||||
So(err, ShouldNotBeNil)
|
||||
//So(err.Error(), ShouldEqual, "signal: interrupt")
|
||||
})
|
||||
|
||||
Convey("Should ok with call Wait twice", t, func() {
|
||||
cmd := CommandString("not-exists-command-xxl213 true")
|
||||
var err error
|
||||
err = cmd.Start()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err1 := cmd.Wait()
|
||||
So(err1, ShouldNotBeNil)
|
||||
err2 := cmd.Wait()
|
||||
So(err1, ShouldEqual, err2)
|
||||
})
|
||||
|
||||
Convey("Set user works", t, func() {
|
||||
u, err := user.Current()
|
||||
So(err, ShouldBeNil)
|
||||
// Set user must be root
|
||||
if u.Uid != "0" {
|
||||
return
|
||||
}
|
||||
|
||||
cmd := Command("whoami")
|
||||
err = cmd.SetUser("qard2")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
output, err := cmd.Output()
|
||||
So(err, ShouldBeNil)
|
||||
So(string(output), ShouldEqual, "qard2\n")
|
||||
})
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package kexec
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func Command(name string, arg ...string) *KCommand {
|
||||
return &KCommand{
|
||||
Cmd: exec.Command(name, arg...),
|
||||
}
|
||||
}
|
||||
|
||||
func CommandString(command string) *KCommand {
|
||||
cmd := exec.Command("cmd", "/c", command)
|
||||
//cmd.Stdout = os.Stdout
|
||||
//cmd.Stderr = os.Stderr
|
||||
return &KCommand{
|
||||
Cmd: cmd,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *KCommand) Terminate(sig os.Signal) (err error) {
|
||||
if p.Process == nil {
|
||||
return nil
|
||||
}
|
||||
pid := p.Process.Pid
|
||||
c := exec.Command("taskkill", "/t", "/f", "/pid", strconv.Itoa(pid))
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
return c.Run()
|
||||
}
|
||||
|
||||
// SetUser not support on windws
|
||||
func (k *KCommand) SetUser(name string) (err error) {
|
||||
log.Printf("Can not set user(%s) on windows", name)
|
||||
return nil
|
||||
}
|
@ -1 +0,0 @@
|
||||
web: python flask_main.py
|
@ -1,11 +0,0 @@
|
||||
import flask
|
||||
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def homepage():
|
||||
return 'Home'
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host='0.0.0.0')
|
@ -1,22 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"kproc"
|
||||
"log"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
p := kproc.ProcString("python flask_main.py")
|
||||
p.Start()
|
||||
time.Sleep(10 * time.Second)
|
||||
err := p.Terminate(syscall.SIGKILL)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
out, _ := exec.Command("lsof", "-i:5000").CombinedOutput()
|
||||
fmt.Println(string(out))
|
||||
}
|
Binary file not shown.
@ -1,5 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.4
|
||||
- 1.5
|
||||
script: go test -v github.com/equinox-io/equinox github.com/equinox-io/equinox/proto
|
@ -1,99 +0,0 @@
|
||||
# equinox client SDK [![godoc reference](https://godoc.org/github.com/equinox-io/equinox?status.png)](https://godoc.org/github.com/equinox-io/equinox)
|
||||
|
||||
Package equinox allows applications to remotely update themselves with the [equinox.io](https://equinox.io) service.
|
||||
|
||||
## Minimal Working Example
|
||||
|
||||
```go
|
||||
import "github.com/equinox-io/equinox"
|
||||
|
||||
const appID = "<YOUR EQUINOX APP ID>"
|
||||
|
||||
var publicKey = []byte(`
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEtrVmBxQvheRArXjg2vG1xIprWGuCyESx
|
||||
MMY8pjmjepSy2kuz+nl9aFLqmr+rDNdYvEBqQaZrYMc6k29gjvoQnQ==
|
||||
-----END PUBLIC KEY-----
|
||||
`)
|
||||
|
||||
func update(channel string) error {
|
||||
opts := equinox.Options{Channel: channel}
|
||||
if err := opts.SetPublicKeyPEM(publicKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check for the update
|
||||
resp, err := equinox.Check(appID, opts)
|
||||
switch {
|
||||
case err == equinox.NotAvailableErr:
|
||||
fmt.Println("No update available, already at the latest version!")
|
||||
return nil
|
||||
case err != nil:
|
||||
return err
|
||||
}
|
||||
|
||||
// fetch the update and apply it
|
||||
err = resp.Apply()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Updated to new version: %s!\n", resp.ReleaseVersion)
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Update To Specific Version
|
||||
|
||||
When you specify a channel in the update options, equinox will try to update the application
|
||||
to the latest release of your application published to that channel. Instead, you may wish to
|
||||
update the application to a specific (possibly older) version. You can do this by explicitly setting
|
||||
Version in the Options struct:
|
||||
|
||||
```go
|
||||
opts := equinox.Options{Version: "0.1.2"}
|
||||
```
|
||||
|
||||
## Prompt For Update
|
||||
|
||||
You may wish to ask the user for approval before updating to a new version. This is as simple
|
||||
as calling the Check function and only calling Apply on the returned result if the user approves.
|
||||
Example:
|
||||
|
||||
```go
|
||||
// check for the update
|
||||
resp, err := equinox.Check(appID, opts)
|
||||
switch {
|
||||
case err == equinox.NotAvailableErr:
|
||||
fmt.Println("No update available, already at the latest version!")
|
||||
return nil
|
||||
case err != nil:
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("New version available!")
|
||||
fmt.Println("Version:", resp.ReleaseVersion)
|
||||
fmt.Println("Name:", resp.ReleaseTitle)
|
||||
fmt.Println("Details:", resp.ReleaseDescription)
|
||||
|
||||
ok := prompt("Would you like to update?")
|
||||
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
err = resp.Apply()
|
||||
// ...
|
||||
```
|
||||
|
||||
## Generating Keys
|
||||
|
||||
All equinox releases must be signed with a private ECDSA key, and all updates verified with the
|
||||
public key portion. To do that, you'll need to generate a key pair. The equinox release tool can
|
||||
generate an ecdsa key pair for you easily:
|
||||
|
||||
```shell
|
||||
equinox genkey
|
||||
```
|
||||
|
@ -1,92 +0,0 @@
|
||||
/*
|
||||
Package equinox allows applications to remotely update themselves with the equinox.io service.
|
||||
|
||||
Minimal Working Example
|
||||
|
||||
import "github.com/equinox-io/equinox"
|
||||
|
||||
const appID = "<YOUR EQUINOX APP ID>"
|
||||
|
||||
var publicKey = []byte(`
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEtrVmBxQvheRArXjg2vG1xIprWGuCyESx
|
||||
MMY8pjmjepSy2kuz+nl9aFLqmr+rDNdYvEBqQaZrYMc6k29gjvoQnQ==
|
||||
-----END PUBLIC KEY-----
|
||||
`)
|
||||
|
||||
func update(channel string) error {
|
||||
opts := equinox.Options{Channel: channel}
|
||||
if err := opts.SetPublicKeyPEM(publicKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check for the update
|
||||
resp, err := equinox.Check(appID, opts)
|
||||
switch {
|
||||
case err == equinox.NotAvailableErr:
|
||||
fmt.Println("No update available, already at the latest version!")
|
||||
return nil
|
||||
case err != nil:
|
||||
return err
|
||||
}
|
||||
|
||||
// fetch the update and apply it
|
||||
err = resp.Apply()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Updated to new version: %s!\n", resp.ReleaseVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Update To Specific Version
|
||||
|
||||
When you specify a channel in the update options, equinox will try to update the application
|
||||
to the latest release of your application published to that channel. Instead, you may wish to
|
||||
update the application to a specific (possibly older) version. You can do this by explicitly setting
|
||||
Version in the Options struct:
|
||||
|
||||
opts := equinox.Options{Version: "0.1.2"}
|
||||
|
||||
Prompt For Update
|
||||
|
||||
You may wish to ask the user for approval before updating to a new version. This is as simple
|
||||
as calling the Check function and only calling Apply on the returned result if the user approves.
|
||||
Example:
|
||||
|
||||
// check for the update
|
||||
resp, err := equinox.Check(appID, opts)
|
||||
switch {
|
||||
case err == equinox.NotAvailableErr:
|
||||
fmt.Println("No update available, already at the latest version!")
|
||||
return nil
|
||||
case err != nil:
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("New version available!")
|
||||
fmt.Println("Version:", resp.ReleaseVersion)
|
||||
fmt.Println("Name:", resp.ReleaseTitle)
|
||||
fmt.Println("Details:", resp.ReleaseDescription)
|
||||
|
||||
ok := prompt("Would you like to update?")
|
||||
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
err = resp.Apply()
|
||||
// ...
|
||||
|
||||
Generating Keys
|
||||
|
||||
All equinox releases must be signed with a private ECDSA key, and all updates verified with the
|
||||
public key portion. To do that, you'll need to generate a key pair. The equinox release tool can
|
||||
generate an ecdsa key pair for you easily:
|
||||
|
||||
equinox genkey
|
||||
|
||||
*/
|
||||
package equinox
|
@ -1,13 +0,0 @@
|
||||
Copyright 2015 Alan Shreve
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -1,65 +0,0 @@
|
||||
# go-update: Build self-updating Go programs [![godoc reference](https://godoc.org/github.com/inconshreveable/go-update?status.png)](https://godoc.org/github.com/inconshreveable/go-update)
|
||||
|
||||
Package update provides functionality to implement secure, self-updating Go programs (or other single-file targets)
|
||||
A program can update itself by replacing its executable file with a new version.
|
||||
|
||||
It provides the flexibility to implement different updating user experiences
|
||||
like auto-updating, or manual user-initiated updates. It also boasts
|
||||
advanced features like binary patching and code signing verification.
|
||||
|
||||
Example of updating from a URL:
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/inconshreveable/go-update"
|
||||
)
|
||||
|
||||
func doUpdate(url string) error {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
err := update.Apply(resp.Body, update.Options{})
|
||||
if err != nil {
|
||||
// error handling
|
||||
}
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Cross platform support (Windows too!)
|
||||
- Binary patch application
|
||||
- Checksum verification
|
||||
- Code signing verification
|
||||
- Support for updating arbitrary files
|
||||
|
||||
## [equinox.io](https://equinox.io)
|
||||
[equinox.io](https://equinox.io) is a complete ready-to-go updating solution built on top of go-update that provides:
|
||||
|
||||
- Hosted updates
|
||||
- Update channels (stable, beta, nightly, ...)
|
||||
- Dynamically computed binary diffs
|
||||
- Automatic key generation and code
|
||||
- Release tooling with proper code signing
|
||||
- Update/download metrics
|
||||
|
||||
## API Compatibility Promises
|
||||
The master branch of `go-update` is *not* guaranteed to have a stable API over time. For any production application, you should vendor
|
||||
your dependency on `go-update` with a tool like git submodules, [gb](http://getgb.io/) or [govendor](https://github.com/kardianos/govendor).
|
||||
|
||||
The `go-update` package makes the following promises about API compatibility:
|
||||
1. A list of all API-breaking changes will be documented in this README.
|
||||
1. `go-update` will strive for as few API-breaking changes as possible.
|
||||
|
||||
## API Breaking Changes
|
||||
- **Sept 3, 2015**: The `Options` struct passed to `Apply` was changed to be passed by value instead of passed by pointer. Old API at `28de026`.
|
||||
- **Aug 9, 2015**: 2.0 API. Old API at `221d034` or `gopkg.in/inconshreveable/go-update.v0`.
|
||||
|
||||
## License
|
||||
Apache
|
@ -1,322 +0,0 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/equinox-io/equinox/internal/go-update/internal/osext"
|
||||
)
|
||||
|
||||
var (
|
||||
openFile = os.OpenFile
|
||||
)
|
||||
|
||||
// Apply performs an update of the current executable (or opts.TargetFile, if set) with the contents of the given io.Reader.
|
||||
//
|
||||
// Apply performs the following actions to ensure a safe cross-platform update:
|
||||
//
|
||||
// 1. If configured, applies the contents of the update io.Reader as a binary patch.
|
||||
//
|
||||
// 2. If configured, computes the checksum of the new executable and verifies it matches.
|
||||
//
|
||||
// 3. If configured, verifies the signature with a public key.
|
||||
//
|
||||
// 4. Creates a new file, /path/to/.target.new with the TargetMode with the contents of the updated file
|
||||
//
|
||||
// 5. Renames /path/to/target to /path/to/.target.old
|
||||
//
|
||||
// 6. Renames /path/to/.target.new to /path/to/target
|
||||
//
|
||||
// 7. If the final rename is successful, deletes /path/to/.target.old, returns no error. On Windows,
|
||||
// the removal of /path/to/target.old always fails, so instead Apply hides the old file instead.
|
||||
//
|
||||
// 8. If the final rename fails, attempts to roll back by renaming /path/to/.target.old
|
||||
// back to /path/to/target.
|
||||
//
|
||||
// If the roll back operation fails, the file system is left in an inconsistent state (betweet steps 5 and 6) where
|
||||
// there is no new executable file and the old executable file could not be be moved to its original location. In this
|
||||
// case you should notify the user of the bad news and ask them to recover manually. Applications can determine whether
|
||||
// the rollback failed by calling RollbackError, see the documentation on that function for additional detail.
|
||||
func Apply(update io.Reader, opts Options) error {
|
||||
// validate
|
||||
verify := false
|
||||
switch {
|
||||
case opts.Signature != nil && opts.PublicKey != nil:
|
||||
// okay
|
||||
verify = true
|
||||
case opts.Signature != nil:
|
||||
return errors.New("no public key to verify signature with")
|
||||
case opts.PublicKey != nil:
|
||||
return errors.New("No signature to verify with")
|
||||
}
|
||||
|
||||
// set defaults
|
||||
if opts.Hash == 0 {
|
||||
opts.Hash = crypto.SHA256
|
||||
}
|
||||
if opts.Verifier == nil {
|
||||
opts.Verifier = NewECDSAVerifier()
|
||||
}
|
||||
if opts.TargetMode == 0 {
|
||||
opts.TargetMode = 0755
|
||||
}
|
||||
|
||||
// get target path
|
||||
var err error
|
||||
opts.TargetPath, err = opts.getPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var newBytes []byte
|
||||
if opts.Patcher != nil {
|
||||
if newBytes, err = opts.applyPatch(update); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// no patch to apply, go on through
|
||||
if newBytes, err = ioutil.ReadAll(update); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// verify checksum if requested
|
||||
if opts.Checksum != nil {
|
||||
if err = opts.verifyChecksum(newBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if verify {
|
||||
if err = opts.verifySignature(newBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// get the directory the executable exists in
|
||||
updateDir := filepath.Dir(opts.TargetPath)
|
||||
filename := filepath.Base(opts.TargetPath)
|
||||
|
||||
// Copy the contents of newbinary to a new executable file
|
||||
newPath := filepath.Join(updateDir, fmt.Sprintf(".%s.new", filename))
|
||||
fp, err := openFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, opts.TargetMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
_, err = io.Copy(fp, bytes.NewReader(newBytes))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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
|
||||
oldPath := opts.OldSavePath
|
||||
removeOld := opts.OldSavePath == ""
|
||||
if removeOld {
|
||||
oldPath = filepath.Join(updateDir, fmt.Sprintf(".%s.old", filename))
|
||||
}
|
||||
|
||||
// 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(oldPath)
|
||||
|
||||
// move the existing executable to a new file in the same directory
|
||||
err = os.Rename(opts.TargetPath, oldPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// move the new exectuable in to become the new program
|
||||
err = os.Rename(newPath, opts.TargetPath)
|
||||
|
||||
if err != nil {
|
||||
// move unsuccessful
|
||||
//
|
||||
// The filesystem is now in a bad state. We have successfully
|
||||
// moved the existing binary to a new location, but we couldn't move the new
|
||||
// binary to take its place. That means there is no file where the current executable binary
|
||||
// used to be!
|
||||
// Try to rollback by restoring the old binary to its original path.
|
||||
rerr := os.Rename(oldPath, opts.TargetPath)
|
||||
if rerr != nil {
|
||||
return &rollbackErr{err, rerr}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// move successful, remove the old binary if needed
|
||||
if removeOld {
|
||||
errRemove := os.Remove(oldPath)
|
||||
|
||||
// windows has trouble with removing old binaries, so hide it instead
|
||||
if errRemove != nil {
|
||||
_ = hideFile(oldPath)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RollbackError takes an error value returned by Apply and returns the error, if any,
|
||||
// that occurred when attempting to roll back from a failed update. Applications should
|
||||
// always call this function on any non-nil errors returned by Apply.
|
||||
//
|
||||
// If no rollback was needed or if the rollback was successful, RollbackError returns nil,
|
||||
// otherwise it returns the error encountered when trying to roll back.
|
||||
func RollbackError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if rerr, ok := err.(*rollbackErr); ok {
|
||||
return rerr.rollbackErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type rollbackErr struct {
|
||||
error // original error
|
||||
rollbackErr error // error encountered while rolling back
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
// TargetPath defines the path to the file to update.
|
||||
// The emptry string means 'the executable file of the running program'.
|
||||
TargetPath string
|
||||
|
||||
// Create TargetPath replacement with this file mode. If zero, defaults to 0755.
|
||||
TargetMode os.FileMode
|
||||
|
||||
// Checksum of the new binary to verify against. If nil, no checksum or signature verification is done.
|
||||
Checksum []byte
|
||||
|
||||
// Public key to use for signature verification. If nil, no signature verification is done.
|
||||
PublicKey crypto.PublicKey
|
||||
|
||||
// Signature to verify the updated file. If nil, no signature verification is done.
|
||||
Signature []byte
|
||||
|
||||
// Pluggable signature verification algorithm. If nil, ECDSA is used.
|
||||
Verifier Verifier
|
||||
|
||||
// Use this hash function to generate the checksum. If not set, SHA256 is used.
|
||||
Hash crypto.Hash
|
||||
|
||||
// If nil, treat the update as a complete replacement for the contents of the file at TargetPath.
|
||||
// If non-nil, treat the update contents as a patch and use this object to apply the patch.
|
||||
Patcher Patcher
|
||||
|
||||
// Store the old executable file at this path after a successful update.
|
||||
// The empty string means the old executable file will be removed after the update.
|
||||
OldSavePath string
|
||||
}
|
||||
|
||||
// CheckPermissions determines whether the process has the correct permissions to
|
||||
// perform the requested update. If the update can proceed, it returns nil, otherwise
|
||||
// it returns the error that would occur if an update were attempted.
|
||||
func (o *Options) CheckPermissions() error {
|
||||
// get the directory the file exists in
|
||||
path, err := o.getPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileDir := filepath.Dir(path)
|
||||
fileName := filepath.Base(path)
|
||||
|
||||
// attempt to open a file in the file's directory
|
||||
newPath := filepath.Join(fileDir, fmt.Sprintf(".%s.new", fileName))
|
||||
fp, err := openFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, o.TargetMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fp.Close()
|
||||
|
||||
_ = os.Remove(newPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPublicKeyPEM is a convenience method to set the PublicKey property
|
||||
// used for checking a completed update's signature by parsing a
|
||||
// Public Key formatted as PEM data.
|
||||
func (o *Options) SetPublicKeyPEM(pembytes []byte) error {
|
||||
block, _ := pem.Decode(pembytes)
|
||||
if block == nil {
|
||||
return errors.New("couldn't parse PEM data")
|
||||
}
|
||||
|
||||
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PublicKey = pub
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Options) getPath() (string, error) {
|
||||
if o.TargetPath == "" {
|
||||
return osext.Executable()
|
||||
} else {
|
||||
return o.TargetPath, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Options) applyPatch(patch io.Reader) ([]byte, error) {
|
||||
// open the file to patch
|
||||
old, err := os.Open(o.TargetPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer old.Close()
|
||||
|
||||
// apply the patch
|
||||
var applied bytes.Buffer
|
||||
if err = o.Patcher.Patch(old, &applied, patch); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return applied.Bytes(), nil
|
||||
}
|
||||
|
||||
func (o *Options) verifyChecksum(updated []byte) error {
|
||||
checksum, err := checksumFor(o.Hash, updated)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !bytes.Equal(o.Checksum, checksum) {
|
||||
return fmt.Errorf("Updated file has wrong checksum. Expected: %x, got: %x", o.Checksum, checksum)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Options) verifySignature(updated []byte) error {
|
||||
checksum, err := checksumFor(o.Hash, updated)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return o.Verifier.VerifySignature(checksum, o.Signature, o.Hash, o.PublicKey)
|
||||
}
|
||||
|
||||
func checksumFor(h crypto.Hash, payload []byte) ([]byte, error) {
|
||||
if !h.Available() {
|
||||
return nil, errors.New("requested hash function not available")
|
||||
}
|
||||
hash := h.New()
|
||||
hash.Write(payload) // guaranteed not to error
|
||||
return hash.Sum([]byte{}), nil
|
||||
}
|
@ -1,426 +0,0 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/equinox-io/equinox/internal/go-update/internal/binarydist"
|
||||
)
|
||||
|
||||
var (
|
||||
oldFile = []byte{0xDE, 0xAD, 0xBE, 0xEF}
|
||||
newFile = []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}
|
||||
newFileChecksum = sha256.Sum256(newFile)
|
||||
)
|
||||
|
||||
func cleanup(path string) {
|
||||
os.Remove(path)
|
||||
os.Remove(fmt.Sprintf(".%s.new", path))
|
||||
}
|
||||
|
||||
// we write with a separate name for each test so that we can run them in parallel
|
||||
func writeOldFile(path string, t *testing.T) {
|
||||
if err := ioutil.WriteFile(path, oldFile, 0777); err != nil {
|
||||
t.Fatalf("Failed to write file for testing preparation: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func validateUpdate(path string, err error, t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to update: %v", err)
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read file post-update: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(buf, newFile) {
|
||||
t.Fatalf("File was not updated! Bytes read: %v, Bytes expected: %v", buf, newFile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplySimple(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fName := "TestApplySimple"
|
||||
defer cleanup(fName)
|
||||
writeOldFile(fName, t)
|
||||
|
||||
err := Apply(bytes.NewReader(newFile), Options{
|
||||
TargetPath: fName,
|
||||
})
|
||||
validateUpdate(fName, err, t)
|
||||
}
|
||||
|
||||
func TestApplyOldSavePath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fName := "TestApplyOldSavePath"
|
||||
defer cleanup(fName)
|
||||
writeOldFile(fName, t)
|
||||
|
||||
oldfName := "OldSavePath"
|
||||
|
||||
err := Apply(bytes.NewReader(newFile), Options{
|
||||
TargetPath: fName,
|
||||
OldSavePath: oldfName,
|
||||
})
|
||||
validateUpdate(fName, err, t)
|
||||
|
||||
if _, err := os.Stat(oldfName); os.IsNotExist(err) {
|
||||
t.Fatalf("Failed to find the old file: %v", err)
|
||||
}
|
||||
|
||||
cleanup(oldfName)
|
||||
}
|
||||
|
||||
func TestVerifyChecksum(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fName := "TestVerifyChecksum"
|
||||
defer cleanup(fName)
|
||||
writeOldFile(fName, t)
|
||||
|
||||
err := Apply(bytes.NewReader(newFile), Options{
|
||||
TargetPath: fName,
|
||||
Checksum: newFileChecksum[:],
|
||||
})
|
||||
validateUpdate(fName, err, t)
|
||||
}
|
||||
|
||||
func TestVerifyChecksumNegative(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fName := "TestVerifyChecksumNegative"
|
||||
defer cleanup(fName)
|
||||
writeOldFile(fName, t)
|
||||
|
||||
badChecksum := []byte{0x0A, 0x0B, 0x0C, 0xFF}
|
||||
err := Apply(bytes.NewReader(newFile), Options{
|
||||
TargetPath: fName,
|
||||
Checksum: badChecksum,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatalf("Failed to detect bad checksum!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyPatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fName := "TestApplyPatch"
|
||||
defer cleanup(fName)
|
||||
writeOldFile(fName, t)
|
||||
|
||||
patch := new(bytes.Buffer)
|
||||
err := binarydist.Diff(bytes.NewReader(oldFile), bytes.NewReader(newFile), patch)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create patch: %v", err)
|
||||
}
|
||||
|
||||
err = Apply(patch, Options{
|
||||
TargetPath: fName,
|
||||
Patcher: NewBSDiffPatcher(),
|
||||
})
|
||||
validateUpdate(fName, err, t)
|
||||
}
|
||||
|
||||
func TestCorruptPatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fName := "TestCorruptPatch"
|
||||
defer cleanup(fName)
|
||||
writeOldFile(fName, t)
|
||||
|
||||
badPatch := []byte{0x44, 0x38, 0x86, 0x3c, 0x4f, 0x8d, 0x26, 0x54, 0xb, 0x11, 0xce, 0xfe, 0xc1, 0xc0, 0xf8, 0x31, 0x38, 0xa0, 0x12, 0x1a, 0xa2, 0x57, 0x2a, 0xe1, 0x3a, 0x48, 0x62, 0x40, 0x2b, 0x81, 0x12, 0xb1, 0x21, 0xa5, 0x16, 0xed, 0x73, 0xd6, 0x54, 0x84, 0x29, 0xa6, 0xd6, 0xb2, 0x1b, 0xfb, 0xe6, 0xbe, 0x7b, 0x70}
|
||||
err := Apply(bytes.NewReader(badPatch), Options{
|
||||
TargetPath: fName,
|
||||
Patcher: NewBSDiffPatcher(),
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatalf("Failed to detect corrupt patch!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyChecksumPatchNegative(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fName := "TestVerifyChecksumPatchNegative"
|
||||
defer cleanup(fName)
|
||||
writeOldFile(fName, t)
|
||||
|
||||
patch := new(bytes.Buffer)
|
||||
anotherFile := []byte{0x77, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66}
|
||||
err := binarydist.Diff(bytes.NewReader(oldFile), bytes.NewReader(anotherFile), patch)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create patch: %v", err)
|
||||
}
|
||||
|
||||
err = Apply(patch, Options{
|
||||
TargetPath: fName,
|
||||
Checksum: newFileChecksum[:],
|
||||
Patcher: NewBSDiffPatcher(),
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatalf("Failed to detect patch to wrong file!")
|
||||
}
|
||||
}
|
||||
|
||||
const ecdsaPublicKey = `
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEL8ThbSyEucsCxnd4dCZR2hIy5nea54ko
|
||||
O+jUUfIjkvwhCWzASm0lpCVdVpXKZXIe+NZ+44RQRv3+OqJkCCGzUgJkPNI3lxdG
|
||||
9zu8rbrnxISV06VQ8No7Ei9wiTpqmTBB
|
||||
-----END PUBLIC KEY-----
|
||||
`
|
||||
|
||||
const ecdsaPrivateKey = `
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIGkAgEBBDBttCB/1NOY4T+WrG4FSV49Ayn3gK1DNzfGaJ01JUXeiNFCWQM2pqpU
|
||||
om8ATPP/dkegBwYFK4EEACKhZANiAAQvxOFtLIS5ywLGd3h0JlHaEjLmd5rniSg7
|
||||
6NRR8iOS/CEJbMBKbSWkJV1Wlcplch741n7jhFBG/f46omQIIbNSAmQ80jeXF0b3
|
||||
O7ytuufEhJXTpVDw2jsSL3CJOmqZMEE=
|
||||
-----END EC PRIVATE KEY-----
|
||||
`
|
||||
|
||||
const rsaPublicKey = `
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxSWmu7trWKAwDFjiCN2D
|
||||
Tk2jj2sgcr/CMlI4cSSiIOHrXCFxP1I8i9PvQkd4hasXQrLbT5WXKrRGv1HKUKab
|
||||
b9ead+kD0kxk7i2bFYvKX43oq66IW0mOLTQBO7I9UyT4L7svcMD+HUQ2BqHoaQe4
|
||||
y20C59dPr9Dpcz8DZkdLsBV6YKF6Ieb3iGk8oRLMWNaUqPa8f1BGgxAkvPHcqDjT
|
||||
x4xRnjgTRRRlZvRtALHMUkIChgxDOhoEzKpGiqnX7HtMJfrhV6h0PAXNA4h9Kjv5
|
||||
5fhJ08Rz7mmZmtH5JxTK5XTquo59sihSajR4bSjZbbkQ1uLkeFlY3eli3xdQ7Nrf
|
||||
fQIDAQAB
|
||||
-----END PUBLIC KEY-----`
|
||||
|
||||
const rsaPrivateKey = `
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEAxSWmu7trWKAwDFjiCN2DTk2jj2sgcr/CMlI4cSSiIOHrXCFx
|
||||
P1I8i9PvQkd4hasXQrLbT5WXKrRGv1HKUKabb9ead+kD0kxk7i2bFYvKX43oq66I
|
||||
W0mOLTQBO7I9UyT4L7svcMD+HUQ2BqHoaQe4y20C59dPr9Dpcz8DZkdLsBV6YKF6
|
||||
Ieb3iGk8oRLMWNaUqPa8f1BGgxAkvPHcqDjTx4xRnjgTRRRlZvRtALHMUkIChgxD
|
||||
OhoEzKpGiqnX7HtMJfrhV6h0PAXNA4h9Kjv55fhJ08Rz7mmZmtH5JxTK5XTquo59
|
||||
sihSajR4bSjZbbkQ1uLkeFlY3eli3xdQ7NrffQIDAQABAoIBAAkN+6RvrTR61voa
|
||||
Mvd5RQiZpEN4Bht/Fyo8gH8h0Zh1B9xJZOwlmMZLS5fdtHlfLEhR8qSrGDBL61vq
|
||||
I8KkhEsUufF78EL+YzxVN+Q7cWYGHIOWFokqza7hzpSxUQO6lPOMQ1eIZaNueJTB
|
||||
Zu07/47ISPPg/bXzgGVcpYlTCPTjUwKjtfyMqvX9AD7fIyYRm6zfE7EHj1J2sBFt
|
||||
Yz1OGELg6HfJwXfpnPfBvftD0hWGzJ78Bp71fPJe6n5gnqmSqRvrcXNWFnH/yqkN
|
||||
d6vPIxD6Z3LjvyZpkA7JillLva2L/zcIFhg4HZvQnWd8/PpDnUDonu36hcj4SC5j
|
||||
W4aVPLkCgYEA4XzNKWxqYcajzFGZeSxlRHupSAl2MT7Cc5085MmE7dd31wK2T8O4
|
||||
n7N4bkm/rjTbX85NsfWdKtWb6mpp8W3VlLP0rp4a/12OicVOkg4pv9LZDmY0sRlE
|
||||
YuDJk1FeCZ50UrwTZI3rZ9IhZHhkgVA6uWAs7tYndONkxNHG0pjqs4sCgYEA39MZ
|
||||
JwMqo3qsPntpgP940cCLflEsjS9hYNO3+Sv8Dq3P0HLVhBYajJnotf8VuU0fsQZG
|
||||
grmtVn1yThFbMq7X1oY4F0XBA+paSiU18c4YyUnwax2u4sw9U/Q9tmQUZad5+ueT
|
||||
qriMBwGv+ewO+nQxqvAsMUmemrVzrfwA5Oct+hcCgYAfiyXoNZJsOy2O15twqBVC
|
||||
j0oPGcO+/9iT89sg5lACNbI+EdMPNYIOVTzzsL1v0VUfAe08h++Enn1BPcG0VHkc
|
||||
ZFBGXTfJoXzfKQrkw7ZzbzuOGB4m6DH44xlP0oIlNlVvfX/5ASF9VJf3RiBJNsAA
|
||||
TsP6ZVr/rw/ZuL7nlxy+IQKBgDhL/HOXlE3yOQiuOec8WsNHTs7C1BXe6PtVxVxi
|
||||
988pYK/pclL6zEq5G5NLSceF4obAMVQIJ9UtUGbabrncyGUo9UrFPLsjYvprSZo8
|
||||
YHegpVwL50UcYgCP2kXZ/ldjPIcjYDz8lhvdDMor2cidGTEJn9P11HLNWP9V91Ob
|
||||
4jCZAoGAPNRSC5cC8iP/9j+s2/kdkfWJiNaolPYAUrmrkL6H39PYYZM5tnhaIYJV
|
||||
Oh9AgABamU0eb3p3vXTISClVgV7ifq1HyZ7BSUhMfaY2Jk/s3sUHCWFxPZe9sgEG
|
||||
KinIY/373KIkIV/5g4h2v1w330IWcfptxKcY/Er3DJr38f695GE=
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
|
||||
func signec(privatePEM string, source []byte, t *testing.T) []byte {
|
||||
parseFn := func(p []byte) (crypto.Signer, error) { return x509.ParseECPrivateKey(p) }
|
||||
return sign(parseFn, privatePEM, source, t)
|
||||
}
|
||||
|
||||
func signrsa(privatePEM string, source []byte, t *testing.T) []byte {
|
||||
parseFn := func(p []byte) (crypto.Signer, error) { return x509.ParsePKCS1PrivateKey(p) }
|
||||
return sign(parseFn, privatePEM, source, t)
|
||||
}
|
||||
|
||||
func sign(parsePrivKey func([]byte) (crypto.Signer, error), privatePEM string, source []byte, t *testing.T) []byte {
|
||||
block, _ := pem.Decode([]byte(privatePEM))
|
||||
if block == nil {
|
||||
t.Fatalf("Failed to parse private key PEM")
|
||||
}
|
||||
|
||||
priv, err := parsePrivKey(block.Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse private key DER: %v", err)
|
||||
}
|
||||
|
||||
checksum := sha256.Sum256(source)
|
||||
sig, err := priv.Sign(rand.Reader, checksum[:], crypto.SHA256)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to sign: %v", sig)
|
||||
}
|
||||
|
||||
return sig
|
||||
}
|
||||
|
||||
func TestVerifyECSignature(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fName := "TestVerifySignature"
|
||||
defer cleanup(fName)
|
||||
writeOldFile(fName, t)
|
||||
|
||||
opts := Options{TargetPath: fName}
|
||||
err := opts.SetPublicKeyPEM([]byte(ecdsaPublicKey))
|
||||
if err != nil {
|
||||
t.Fatalf("Could not parse public key: %v", err)
|
||||
}
|
||||
|
||||
opts.Signature = signec(ecdsaPrivateKey, newFile, t)
|
||||
err = Apply(bytes.NewReader(newFile), opts)
|
||||
validateUpdate(fName, err, t)
|
||||
}
|
||||
|
||||
func TestVerifyRSASignature(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fName := "TestVerifySignature"
|
||||
defer cleanup(fName)
|
||||
writeOldFile(fName, t)
|
||||
|
||||
opts := Options{
|
||||
TargetPath: fName,
|
||||
Verifier: NewRSAVerifier(),
|
||||
}
|
||||
err := opts.SetPublicKeyPEM([]byte(rsaPublicKey))
|
||||
if err != nil {
|
||||
t.Fatalf("Could not parse public key: %v", err)
|
||||
}
|
||||
|
||||
opts.Signature = signrsa(rsaPrivateKey, newFile, t)
|
||||
err = Apply(bytes.NewReader(newFile), opts)
|
||||
validateUpdate(fName, err, t)
|
||||
}
|
||||
|
||||
func TestVerifyFailBadSignature(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fName := "TestVerifyFailBadSignature"
|
||||
defer cleanup(fName)
|
||||
writeOldFile(fName, t)
|
||||
|
||||
opts := Options{
|
||||
TargetPath: fName,
|
||||
Signature: []byte{0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA},
|
||||
}
|
||||
err := opts.SetPublicKeyPEM([]byte(ecdsaPublicKey))
|
||||
if err != nil {
|
||||
t.Fatalf("Could not parse public key: %v", err)
|
||||
}
|
||||
|
||||
err = Apply(bytes.NewReader(newFile), opts)
|
||||
if err == nil {
|
||||
t.Fatalf("Did not fail with bad signature")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyFailNoSignature(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fName := "TestVerifySignatureWithPEM"
|
||||
defer cleanup(fName)
|
||||
writeOldFile(fName, t)
|
||||
|
||||
opts := Options{TargetPath: fName}
|
||||
err := opts.SetPublicKeyPEM([]byte(ecdsaPublicKey))
|
||||
if err != nil {
|
||||
t.Fatalf("Could not parse public key: %v", err)
|
||||
}
|
||||
|
||||
err = Apply(bytes.NewReader(newFile), opts)
|
||||
if err == nil {
|
||||
t.Fatalf("Did not fail with empty signature")
|
||||
}
|
||||
}
|
||||
|
||||
const wrongKey = `
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIGkAgEBBDBzqYp6N2s8YWYifBjS03/fFfmGeIPcxQEi+bbFeekIYt8NIKIkhD+r
|
||||
hpaIwSmot+qgBwYFK4EEACKhZANiAAR0EC8Usbkc4k30frfEB2ECmsIghu9DJSqE
|
||||
RbH7jfq2ULNv8tN/clRjxf2YXgp+iP3SQF1R1EYERKpWr8I57pgfIZtoZXjwpbQC
|
||||
VBbP/Ff+05HOqwPC7rJMy1VAJLKg7Cw=
|
||||
-----END EC PRIVATE KEY-----
|
||||
`
|
||||
|
||||
func TestVerifyFailWrongSignature(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fName := "TestVerifyFailWrongSignature"
|
||||
defer cleanup(fName)
|
||||
writeOldFile(fName, t)
|
||||
|
||||
opts := Options{TargetPath: fName}
|
||||
err := opts.SetPublicKeyPEM([]byte(ecdsaPublicKey))
|
||||
if err != nil {
|
||||
t.Fatalf("Could not parse public key: %v", err)
|
||||
}
|
||||
|
||||
opts.Signature = signec(wrongKey, newFile, t)
|
||||
err = Apply(bytes.NewReader(newFile), opts)
|
||||
if err == nil {
|
||||
t.Fatalf("Verified an update that was signed by an untrusted key!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignatureButNoPublicKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fName := "TestSignatureButNoPublicKey"
|
||||
defer cleanup(fName)
|
||||
writeOldFile(fName, t)
|
||||
|
||||
err := Apply(bytes.NewReader(newFile), Options{
|
||||
TargetPath: fName,
|
||||
Signature: signec(ecdsaPrivateKey, newFile, t),
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatalf("Allowed an update with a signautre verification when no public key was specified!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicKeyButNoSignature(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fName := "TestPublicKeyButNoSignature"
|
||||
defer cleanup(fName)
|
||||
writeOldFile(fName, t)
|
||||
|
||||
opts := Options{TargetPath: fName}
|
||||
if err := opts.SetPublicKeyPEM([]byte(ecdsaPublicKey)); err != nil {
|
||||
t.Fatalf("Could not parse public key: %v", err)
|
||||
}
|
||||
err := Apply(bytes.NewReader(newFile), opts)
|
||||
if err == nil {
|
||||
t.Fatalf("Allowed an update with no signautre when a public key was specified!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteError(t *testing.T) {
|
||||
t.Parallel()
|
||||
fName := "TestWriteError"
|
||||
defer cleanup(fName)
|
||||
writeOldFile(fName, t)
|
||||
|
||||
openFile = func(name string, flags int, perm os.FileMode) (*os.File, error) {
|
||||
f, err := os.OpenFile(name, flags, perm)
|
||||
|
||||
// simulate Write() error by closing the file prematurely
|
||||
f.Close()
|
||||
|
||||
return f, err
|
||||
}
|
||||
|
||||
err := Apply(bytes.NewReader(newFile), Options{TargetPath: fName})
|
||||
if err == nil {
|
||||
t.Fatalf("Allowed an update to an empty file")
|
||||
}
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
/*
|
||||
Package update provides functionality to implement secure, self-updating Go programs (or other single-file targets).
|
||||
|
||||
For complete updating solutions please see Equinox (https://equinox.io) and go-tuf (https://github.com/flynn/go-tuf).
|
||||
|
||||
Basic Example
|
||||
|
||||
This example shows how to update a program remotely from a URL.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/inconshreveable/go-update"
|
||||
)
|
||||
|
||||
func doUpdate(url string) error {
|
||||
// request the new file
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
err := update.Apply(resp.Body, update.Options{})
|
||||
if err != nil {
|
||||
if rerr := update.RollbackError(err); rerr != nil {
|
||||
fmt.Println("Failed to rollback from bad update: %v", rerr)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
Binary Patching
|
||||
|
||||
Go binaries can often be large. It can be advantageous to only ship a binary patch to a client
|
||||
instead of the complete program text of a new version.
|
||||
|
||||
This example shows how to update a program with a bsdiff binary patch. Other patch formats
|
||||
may be applied by implementing the Patcher interface.
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"io"
|
||||
|
||||
"github.com/inconshreveable/go-update"
|
||||
)
|
||||
|
||||
func updateWithPatch(patch io.Reader) error {
|
||||
err := update.Apply(patch, update.Options{
|
||||
Patcher: update.NewBSDiffPatcher()
|
||||
})
|
||||
if err != nil {
|
||||
// error handling
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
Checksum Verification
|
||||
|
||||
Updating executable code on a computer can be a dangerous operation unless you
|
||||
take the appropriate steps to guarantee the authenticity of the new code. While
|
||||
checksum verification is important, it should always be combined with signature
|
||||
verification (next section) to guarantee that the code came from a trusted party.
|
||||
|
||||
go-update validates SHA256 checksums by default, but this is pluggable via the Hash
|
||||
property on the Options struct.
|
||||
|
||||
This example shows how to guarantee that the newly-updated binary is verified to
|
||||
have an appropriate checksum (that was otherwise retrived via a secure channel)
|
||||
specified as a hex string.
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
_ "crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
|
||||
"github.com/inconshreveable/go-update"
|
||||
)
|
||||
|
||||
func updateWithChecksum(binary io.Reader, hexChecksum string) error {
|
||||
checksum, err := hex.DecodeString(hexChecksum)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = update.Apply(binary, update.Options{
|
||||
Hash: crypto.SHA256, // this is the default, you don't need to specify it
|
||||
Checksum: checksum,
|
||||
})
|
||||
if err != nil {
|
||||
// error handling
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
Cryptographic Signature Verification
|
||||
|
||||
Cryptographic verification of new code from an update is an extremely important way to guarantee the
|
||||
security and integrity of your updates.
|
||||
|
||||
Verification is performed by validating the signature of a hash of the new file. This
|
||||
means nothing changes if you apply your update with a patch.
|
||||
|
||||
This example shows how to add signature verification to your updates. To make all of this work
|
||||
an application distributor must first create a public/private key pair and embed the public key
|
||||
into their application. When they issue a new release, the issuer must sign the new executable file
|
||||
with the private key and distribute the signature along with the update.
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
_ "crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
|
||||
"github.com/inconshreveable/go-update"
|
||||
)
|
||||
|
||||
var publicKey = []byte(`
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEtrVmBxQvheRArXjg2vG1xIprWGuCyESx
|
||||
MMY8pjmjepSy2kuz+nl9aFLqmr+rDNdYvEBqQaZrYMc6k29gjvoQnQ==
|
||||
-----END PUBLIC KEY-----
|
||||
`)
|
||||
|
||||
func verifiedUpdate(binary io.Reader, hexChecksum, hexSignature string) {
|
||||
checksum, err := hex.DecodeString(hexChecksum)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signature, err := hex.DecodeString(hexSignature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts := update.Options{
|
||||
Checksum: checksum,
|
||||
Signature: signature,
|
||||
Hash: crypto.SHA256, // this is the default, you don't need to specify it
|
||||
Verifier: update.NewECDSAVerifier(), // this is the default, you don't need to specify it
|
||||
}
|
||||
err = opts.SetPublicKeyPEM(publicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = update.Apply(binary, opts)
|
||||
if err != nil {
|
||||
// error handling
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
Building Single-File Go Binaries
|
||||
|
||||
In order to update a Go application with go-update, you must distributed it as a single executable.
|
||||
This is often easy, but some applications require static assets (like HTML and CSS asset files or TLS certificates).
|
||||
In order to update applications like these, you'll want to make sure to embed those asset files into
|
||||
the distributed binary with a tool like go-bindata (my favorite): https://github.com/jteeuwen/go-bindata
|
||||
|
||||
Non-Goals
|
||||
|
||||
Mechanisms and protocols for determining whether an update should be applied and, if so, which one are
|
||||
out of scope for this package. Please consult go-tuf (https://github.com/flynn/go-tuf) or Equinox (https://equinox.io)
|
||||
for more complete solutions.
|
||||
|
||||
go-update only works for self-updating applications that are distributed as a single binary, i.e.
|
||||
applications that do not have additional assets or dependency files.
|
||||
Updating application that are distributed as mutliple on-disk files is out of scope, although this
|
||||
may change in future versions of this library.
|
||||
|
||||
*/
|
||||
package update
|
@ -1,7 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package update
|
||||
|
||||
func hideFile(path string) error {
|
||||
return nil
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func hideFile(path string) error {
|
||||
kernel32 := syscall.NewLazyDLL("kernel32.dll")
|
||||
setFileAttributes := kernel32.NewProc("SetFileAttributesW")
|
||||
|
||||
r1, _, err := setFileAttributes.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))), 2)
|
||||
|
||||
if r1 == 0 {
|
||||
return err
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
22
vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/License
generated
vendored
22
vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/License
generated
vendored
@ -1,22 +0,0 @@
|
||||
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.
|
@ -1,7 +0,0 @@
|
||||
# binarydist
|
||||
|
||||
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.
|
||||
|
||||
Documentation at <http://go.pkgdoc.org/github.com/kr/binarydist>.
|
40
vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/bzip2.go
generated
vendored
40
vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/bzip2.go
generated
vendored
@ -1,40 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
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
|
||||
}
|
408
vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/diff.go
generated
vendored
408
vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/diff.go
generated
vendored
@ -1,408 +0,0 @@
|
||||
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
|
||||
}
|
67
vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/diff_test.go
generated
vendored
67
vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/diff_test.go
generated
vendored
@ -1,67 +0,0 @@
|
||||
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])
|
||||
}
|
||||
}
|
||||
}
|
24
vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/doc.go
generated
vendored
24
vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/doc.go
generated
vendored
@ -1,24 +0,0 @@
|
||||
// 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
|
||||
}
|
53
vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/encoding.go
generated
vendored
53
vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/encoding.go
generated
vendored
@ -1,53 +0,0 @@
|
||||
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" }
|
109
vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/patch.go
generated
vendored
109
vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/patch.go
generated
vendored
@ -1,109 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
43
vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/seek.go
generated
vendored
43
vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/seek.go
generated
vendored
@ -1,43 +0,0 @@
|
||||
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
|
||||
}
|
33
vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/sort_test.go
generated
vendored
33
vendor/github.com/equinox-io/equinox/internal/go-update/internal/binarydist/sort_test.go
generated
vendored
@ -1,33 +0,0 @@
|
||||
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
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
27
vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/LICENSE
generated
vendored
27
vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/LICENSE
generated
vendored
@ -1,27 +0,0 @@
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
16
vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/README.md
generated
vendored
16
vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/README.md
generated
vendored
@ -1,16 +0,0 @@
|
||||
### Extensions to the "os" package.
|
||||
|
||||
## Find the current Executable and ExecutableFolder.
|
||||
|
||||
There is sometimes utility in finding the current executable file
|
||||
that is running. This can be used for upgrading the current executable
|
||||
or finding resources located relative to the executable file. Both
|
||||
working directory and the os.Args[0] value are arbitrary and cannot
|
||||
be relied on; os.Args[0] can be "faked".
|
||||
|
||||
Multi-platform and supports:
|
||||
* Linux
|
||||
* OS X
|
||||
* Windows
|
||||
* Plan 9
|
||||
* BSDs.
|
27
vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext.go
generated
vendored
27
vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext.go
generated
vendored
@ -1,27 +0,0 @@
|
||||
// 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 and any trailing slash.
|
||||
func ExecutableFolder() (string, error) {
|
||||
p, err := Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Dir(p), nil
|
||||
}
|
20
vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_plan9.go
generated
vendored
20
vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_plan9.go
generated
vendored
@ -1,20 +0,0 @@
|
||||
// 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 (
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
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()))
|
||||
}
|
36
vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_procfs.go
generated
vendored
36
vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_procfs.go
generated
vendored
@ -1,36 +0,0 @@
|
||||
// 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 solaris dragonfly
|
||||
|
||||
package osext
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func executable() (string, error) {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
const deletedTag = " (deleted)"
|
||||
execpath, err := os.Readlink("/proc/self/exe")
|
||||
if err != nil {
|
||||
return execpath, err
|
||||
}
|
||||
execpath = strings.TrimSuffix(execpath, deletedTag)
|
||||
execpath = strings.TrimPrefix(execpath, deletedTag)
|
||||
return execpath, nil
|
||||
case "netbsd":
|
||||
return os.Readlink("/proc/curproc/exe")
|
||||
case "openbsd", "dragonfly":
|
||||
return os.Readlink("/proc/curproc/file")
|
||||
case "solaris":
|
||||
return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid()))
|
||||
}
|
||||
return "", errors.New("ExecPath not implemented for " + runtime.GOOS)
|
||||
}
|
79
vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_sysctl.go
generated
vendored
79
vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_sysctl.go
generated
vendored
@ -1,79 +0,0 @@
|
||||
// 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
|
||||
}
|
203
vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_test.go
generated
vendored
203
vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_test.go
generated
vendored
@ -1,203 +0,0 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
executableEnvVar = "OSTEST_OUTPUT_EXECUTABLE"
|
||||
|
||||
executableEnvValueMatch = "match"
|
||||
executableEnvValueDelete = "delete"
|
||||
)
|
||||
|
||||
func TestPrintExecutable(t *testing.T) {
|
||||
ef, err := Executable()
|
||||
if err != nil {
|
||||
t.Fatalf("Executable failed: %v", err)
|
||||
}
|
||||
t.Log("Executable:", ef)
|
||||
}
|
||||
func TestPrintExecutableFolder(t *testing.T) {
|
||||
ef, err := ExecutableFolder()
|
||||
if err != nil {
|
||||
t.Fatalf("ExecutableFolder failed: %v", err)
|
||||
}
|
||||
t.Log("Executable Folder:", ef)
|
||||
}
|
||||
func TestExecutableFolder(t *testing.T) {
|
||||
ef, err := ExecutableFolder()
|
||||
if err != nil {
|
||||
t.Fatalf("ExecutableFolder failed: %v", err)
|
||||
}
|
||||
if ef[len(ef)-1] == filepath.Separator {
|
||||
t.Fatal("ExecutableFolder ends with a trailing slash.")
|
||||
}
|
||||
}
|
||||
func TestExecutableMatch(t *testing.T) {
|
||||
ep, err := Executable()
|
||||
if err != nil {
|
||||
t.Fatalf("Executable failed: %v", err)
|
||||
}
|
||||
|
||||
// fullpath to be of the form "dir/prog".
|
||||
dir := filepath.Dir(filepath.Dir(ep))
|
||||
fullpath, err := filepath.Rel(dir, ep)
|
||||
if err != nil {
|
||||
t.Fatalf("filepath.Rel: %v", err)
|
||||
}
|
||||
// Make child start with a relative program path.
|
||||
// Alter argv[0] for child to verify getting real path without argv[0].
|
||||
cmd := &exec.Cmd{
|
||||
Dir: dir,
|
||||
Path: fullpath,
|
||||
Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueMatch)},
|
||||
}
|
||||
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 TestExecutableDelete(t *testing.T) {
|
||||
if runtime.GOOS != "linux" {
|
||||
t.Skip()
|
||||
}
|
||||
fpath, err := Executable()
|
||||
if err != nil {
|
||||
t.Fatalf("Executable failed: %v", err)
|
||||
}
|
||||
|
||||
r, w := io.Pipe()
|
||||
stderrBuff := &bytes.Buffer{}
|
||||
stdoutBuff := &bytes.Buffer{}
|
||||
cmd := &exec.Cmd{
|
||||
Path: fpath,
|
||||
Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueDelete)},
|
||||
Stdin: r,
|
||||
Stderr: stderrBuff,
|
||||
Stdout: stdoutBuff,
|
||||
}
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("exec(self) start failed: %v", err)
|
||||
}
|
||||
|
||||
tempPath := fpath + "_copy"
|
||||
_ = os.Remove(tempPath)
|
||||
|
||||
err = copyFile(tempPath, fpath)
|
||||
if err != nil {
|
||||
t.Fatalf("copy file failed: %v", err)
|
||||
}
|
||||
err = os.Remove(fpath)
|
||||
if err != nil {
|
||||
t.Fatalf("remove running test file failed: %v", err)
|
||||
}
|
||||
err = os.Rename(tempPath, fpath)
|
||||
if err != nil {
|
||||
t.Fatalf("rename copy to previous name failed: %v", err)
|
||||
}
|
||||
|
||||
w.Write([]byte{0})
|
||||
w.Close()
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
t.Fatalf("exec wait failed: %v", err)
|
||||
}
|
||||
|
||||
childPath := stderrBuff.String()
|
||||
if !filepath.IsAbs(childPath) {
|
||||
t.Fatalf("Child returned %q, want an absolute path", childPath)
|
||||
}
|
||||
if !sameFile(childPath, fpath) {
|
||||
t.Fatalf("Child returned %q, not the same file as %q", childPath, fpath)
|
||||
}
|
||||
}
|
||||
|
||||
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 copyFile(dest, src string) error {
|
||||
df, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer df.Close()
|
||||
|
||||
sf, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sf.Close()
|
||||
|
||||
_, err = io.Copy(df, sf)
|
||||
return err
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
env := os.Getenv(executableEnvVar)
|
||||
switch env {
|
||||
case "":
|
||||
os.Exit(m.Run())
|
||||
case executableEnvValueMatch:
|
||||
// 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)
|
||||
}
|
||||
case executableEnvValueDelete:
|
||||
bb := make([]byte, 1)
|
||||
var err error
|
||||
n, err := os.Stdin.Read(bb)
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
if n != 1 {
|
||||
fmt.Fprint(os.Stderr, "ERROR: n != 1, n == ", n)
|
||||
os.Exit(2)
|
||||
}
|
||||
if ep, err := Executable(); err != nil {
|
||||
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
||||
} else {
|
||||
fmt.Fprint(os.Stderr, ep)
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
34
vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_windows.go
generated
vendored
34
vendor/github.com/equinox-io/equinox/internal/go-update/internal/osext/osext_windows.go
generated
vendored
@ -1,34 +0,0 @@
|
||||
// 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
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/equinox-io/equinox/internal/go-update/internal/binarydist"
|
||||
)
|
||||
|
||||
// Patcher defines an interface for applying binary patches to an old item to get an updated item.
|
||||
type Patcher interface {
|
||||
Patch(old io.Reader, new io.Writer, patch io.Reader) error
|
||||
}
|
||||
|
||||
type patchFn func(io.Reader, io.Writer, io.Reader) error
|
||||
|
||||
func (fn patchFn) Patch(old io.Reader, new io.Writer, patch io.Reader) error {
|
||||
return fn(old, new, patch)
|
||||
}
|
||||
|
||||
// NewBSDifferPatcher returns a new Patcher that applies binary patches using
|
||||
// the bsdiff algorithm. See http://www.daemonology.net/bsdiff/
|
||||
func NewBSDiffPatcher() Patcher {
|
||||
return patchFn(binarydist.Patch)
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rsa"
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Verifier defines an interface for verfiying an update's signature with a public key.
|
||||
type Verifier interface {
|
||||
VerifySignature(checksum, signature []byte, h crypto.Hash, publicKey crypto.PublicKey) error
|
||||
}
|
||||
|
||||
type verifyFn func([]byte, []byte, crypto.Hash, crypto.PublicKey) error
|
||||
|
||||
func (fn verifyFn) VerifySignature(checksum []byte, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error {
|
||||
return fn(checksum, signature, hash, publicKey)
|
||||
}
|
||||
|
||||
// NewRSAVerifier returns a Verifier that uses the RSA algorithm to verify updates.
|
||||
func NewRSAVerifier() Verifier {
|
||||
return verifyFn(func(checksum, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error {
|
||||
key, ok := publicKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return errors.New("not a valid RSA public key")
|
||||
}
|
||||
return rsa.VerifyPKCS1v15(key, hash, checksum, signature)
|
||||
})
|
||||
}
|
||||
|
||||
type rsDER struct {
|
||||
R *big.Int
|
||||
S *big.Int
|
||||
}
|
||||
|
||||
// NewECDSAVerifier returns a Verifier that uses the ECDSA algorithm to verify updates.
|
||||
func NewECDSAVerifier() Verifier {
|
||||
return verifyFn(func(checksum, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error {
|
||||
key, ok := publicKey.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return errors.New("not a valid ECDSA public key")
|
||||
}
|
||||
var rs rsDER
|
||||
if _, err := asn1.Unmarshal(signature, &rs); err != nil {
|
||||
return err
|
||||
}
|
||||
if !ecdsa.Verify(key, checksum, rs.R, rs.S) {
|
||||
return errors.New("failed to verify ecsda signature")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// NewDSAVerifier returns a Verifier that uses the DSA algorithm to verify updates.
|
||||
func NewDSAVerifier() Verifier {
|
||||
return verifyFn(func(checksum, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error {
|
||||
key, ok := publicKey.(*dsa.PublicKey)
|
||||
if !ok {
|
||||
return errors.New("not a valid DSA public key")
|
||||
}
|
||||
var rs rsDER
|
||||
if _, err := asn1.Unmarshal(signature, &rs); err != nil {
|
||||
return err
|
||||
}
|
||||
if !dsa.Verify(key, checksum, rs.R, rs.S) {
|
||||
return errors.New("failed to verify ecsda signature")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -1,16 +0,0 @@
|
||||
### Extensions to the "os" package.
|
||||
|
||||
## Find the current Executable and ExecutableFolder.
|
||||
|
||||
There is sometimes utility in finding the current executable file
|
||||
that is running. This can be used for upgrading the current executable
|
||||
or finding resources located relative to the executable file. Both
|
||||
working directory and the os.Args[0] value are arbitrary and cannot
|
||||
be relied on; os.Args[0] can be "faked".
|
||||
|
||||
Multi-platform and supports:
|
||||
* Linux
|
||||
* OS X
|
||||
* Windows
|
||||
* Plan 9
|
||||
* BSDs.
|
@ -1,27 +0,0 @@
|
||||
// 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 and any trailing slash.
|
||||
func ExecutableFolder() (string, error) {
|
||||
p, err := Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Dir(p), nil
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
// 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 (
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
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()))
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
// 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 solaris dragonfly
|
||||
|
||||
package osext
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func executable() (string, error) {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
const deletedTag = " (deleted)"
|
||||
execpath, err := os.Readlink("/proc/self/exe")
|
||||
if err != nil {
|
||||
return execpath, err
|
||||
}
|
||||
execpath = strings.TrimSuffix(execpath, deletedTag)
|
||||
execpath = strings.TrimPrefix(execpath, deletedTag)
|
||||
return execpath, nil
|
||||
case "netbsd":
|
||||
return os.Readlink("/proc/curproc/exe")
|
||||
case "openbsd", "dragonfly":
|
||||
return os.Readlink("/proc/curproc/file")
|
||||
case "solaris":
|
||||
return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid()))
|
||||
}
|
||||
return "", errors.New("ExecPath not implemented for " + runtime.GOOS)
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
// 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
|
||||
}
|
@ -1,203 +0,0 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
executableEnvVar = "OSTEST_OUTPUT_EXECUTABLE"
|
||||
|
||||
executableEnvValueMatch = "match"
|
||||
executableEnvValueDelete = "delete"
|
||||
)
|
||||
|
||||
func TestPrintExecutable(t *testing.T) {
|
||||
ef, err := Executable()
|
||||
if err != nil {
|
||||
t.Fatalf("Executable failed: %v", err)
|
||||
}
|
||||
t.Log("Executable:", ef)
|
||||
}
|
||||
func TestPrintExecutableFolder(t *testing.T) {
|
||||
ef, err := ExecutableFolder()
|
||||
if err != nil {
|
||||
t.Fatalf("ExecutableFolder failed: %v", err)
|
||||
}
|
||||
t.Log("Executable Folder:", ef)
|
||||
}
|
||||
func TestExecutableFolder(t *testing.T) {
|
||||
ef, err := ExecutableFolder()
|
||||
if err != nil {
|
||||
t.Fatalf("ExecutableFolder failed: %v", err)
|
||||
}
|
||||
if ef[len(ef)-1] == filepath.Separator {
|
||||
t.Fatal("ExecutableFolder ends with a trailing slash.")
|
||||
}
|
||||
}
|
||||
func TestExecutableMatch(t *testing.T) {
|
||||
ep, err := Executable()
|
||||
if err != nil {
|
||||
t.Fatalf("Executable failed: %v", err)
|
||||
}
|
||||
|
||||
// fullpath to be of the form "dir/prog".
|
||||
dir := filepath.Dir(filepath.Dir(ep))
|
||||
fullpath, err := filepath.Rel(dir, ep)
|
||||
if err != nil {
|
||||
t.Fatalf("filepath.Rel: %v", err)
|
||||
}
|
||||
// Make child start with a relative program path.
|
||||
// Alter argv[0] for child to verify getting real path without argv[0].
|
||||
cmd := &exec.Cmd{
|
||||
Dir: dir,
|
||||
Path: fullpath,
|
||||
Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueMatch)},
|
||||
}
|
||||
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 TestExecutableDelete(t *testing.T) {
|
||||
if runtime.GOOS != "linux" {
|
||||
t.Skip()
|
||||
}
|
||||
fpath, err := Executable()
|
||||
if err != nil {
|
||||
t.Fatalf("Executable failed: %v", err)
|
||||
}
|
||||
|
||||
r, w := io.Pipe()
|
||||
stderrBuff := &bytes.Buffer{}
|
||||
stdoutBuff := &bytes.Buffer{}
|
||||
cmd := &exec.Cmd{
|
||||
Path: fpath,
|
||||
Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueDelete)},
|
||||
Stdin: r,
|
||||
Stderr: stderrBuff,
|
||||
Stdout: stdoutBuff,
|
||||
}
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("exec(self) start failed: %v", err)
|
||||
}
|
||||
|
||||
tempPath := fpath + "_copy"
|
||||
_ = os.Remove(tempPath)
|
||||
|
||||
err = copyFile(tempPath, fpath)
|
||||
if err != nil {
|
||||
t.Fatalf("copy file failed: %v", err)
|
||||
}
|
||||
err = os.Remove(fpath)
|
||||
if err != nil {
|
||||
t.Fatalf("remove running test file failed: %v", err)
|
||||
}
|
||||
err = os.Rename(tempPath, fpath)
|
||||
if err != nil {
|
||||
t.Fatalf("rename copy to previous name failed: %v", err)
|
||||
}
|
||||
|
||||
w.Write([]byte{0})
|
||||
w.Close()
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
t.Fatalf("exec wait failed: %v", err)
|
||||
}
|
||||
|
||||
childPath := stderrBuff.String()
|
||||
if !filepath.IsAbs(childPath) {
|
||||
t.Fatalf("Child returned %q, want an absolute path", childPath)
|
||||
}
|
||||
if !sameFile(childPath, fpath) {
|
||||
t.Fatalf("Child returned %q, not the same file as %q", childPath, fpath)
|
||||
}
|
||||
}
|
||||
|
||||
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 copyFile(dest, src string) error {
|
||||
df, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer df.Close()
|
||||
|
||||
sf, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sf.Close()
|
||||
|
||||
_, err = io.Copy(df, sf)
|
||||
return err
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
env := os.Getenv(executableEnvVar)
|
||||
switch env {
|
||||
case "":
|
||||
os.Exit(m.Run())
|
||||
case executableEnvValueMatch:
|
||||
// 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)
|
||||
}
|
||||
case executableEnvValueDelete:
|
||||
bb := make([]byte, 1)
|
||||
var err error
|
||||
n, err := os.Stdin.Read(bb)
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
if n != 1 {
|
||||
fmt.Fprint(os.Stderr, "ERROR: n != 1, n == ", n)
|
||||
os.Exit(2)
|
||||
}
|
||||
if ep, err := Executable(); err != nil {
|
||||
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
||||
} else {
|
||||
fmt.Fprint(os.Stderr, ep)
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
// 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
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
package proto defines a set of structures used to negotiate an update between an
|
||||
an application (the client) and an Equinox update service.
|
||||
*/
|
||||
package proto
|
||||
|
||||
import "time"
|
||||
|
||||
type PatchKind string
|
||||
|
||||
const (
|
||||
PatchNone PatchKind = "none"
|
||||
PatchBSDiff PatchKind = "bsdiff"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
AppID string `json:"app_id"`
|
||||
Channel string `json:"channel"`
|
||||
OS string `json:"os"`
|
||||
Arch string `json:"arch"`
|
||||
GoARM string `json:"goarm"`
|
||||
TargetVersion string `json:"target_version"`
|
||||
|
||||
CurrentVersion string `json:"current_version"`
|
||||
CurrentSHA256 string `json:"current_sha256"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Available bool `json:"available"`
|
||||
DownloadURL string `json:"download_url"`
|
||||
Checksum string `json:"checksum"`
|
||||
Signature string `json:"signature"`
|
||||
Patch PatchKind `json:"patch_type"`
|
||||
Release Release `json:"release"`
|
||||
}
|
||||
|
||||
type Release struct {
|
||||
Title string `json:"title"`
|
||||
Version string `json:"version"`
|
||||
Description string `json:"description"`
|
||||
CreateDate time.Time `json:"create_date"`
|
||||
}
|
@ -1,305 +0,0 @@
|
||||
package equinox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/equinox-io/equinox/internal/go-update"
|
||||
"github.com/equinox-io/equinox/internal/osext"
|
||||
"github.com/equinox-io/equinox/proto"
|
||||
)
|
||||
|
||||
const protocolVersion = "1"
|
||||
const defaultCheckURL = "https://update.equinox.io/check"
|
||||
const userAgent = "EquinoxSDK/1.0"
|
||||
|
||||
var NotAvailableErr = errors.New("No update available")
|
||||
|
||||
type Options struct {
|
||||
// Channel specifies the name of an Equinox release channel to check for
|
||||
// a newer version of the application.
|
||||
//
|
||||
// If empty, defaults to 'stable'.
|
||||
Channel string
|
||||
|
||||
// Version requests an update to a specific version of the application.
|
||||
// If specified, `Channel` is ignored.
|
||||
Version string
|
||||
|
||||
// TargetPath defines the path to the file to update.
|
||||
// The emptry string means 'the executable file of the running program'.
|
||||
TargetPath string
|
||||
|
||||
// Create TargetPath replacement with this file mode. If zero, defaults to 0755.
|
||||
TargetMode os.FileMode
|
||||
|
||||
// Public key to use for signature verification. If nil, no signature
|
||||
// verification is done. Use `SetPublicKeyPEM` to set this field with PEM data.
|
||||
PublicKey crypto.PublicKey
|
||||
|
||||
// Target operating system of the update. Uses the same standard OS names used
|
||||
// by Go build tags (windows, darwin, linux, etc).
|
||||
// If empty, it will be populated by consulting runtime.GOOS
|
||||
OS string
|
||||
|
||||
// Target architecture of the update. Uses the same standard Arch names used
|
||||
// by Go build tags (amd64, 386, arm, etc).
|
||||
// If empty, it will be populated by consulting runtime.GOARCH
|
||||
Arch string
|
||||
|
||||
// Target ARM architecture, if a specific one if required. Uses the same names
|
||||
// as the GOARM environment variable (5, 6, 7).
|
||||
//
|
||||
// GoARM is ignored if Arch != 'arm'.
|
||||
// GoARM is ignored if it is the empty string. Omit it if you do not need
|
||||
// to distinguish between ARM versions.
|
||||
GoARM string
|
||||
|
||||
// The current application version. This is used for statistics and reporting only,
|
||||
// it is optional.
|
||||
CurrentVersion string
|
||||
|
||||
// CheckURL is the URL to request an update check from. You should only set
|
||||
// this if you are running an on-prem Equinox server.
|
||||
// If empty the default Equinox update service endpoint is used.
|
||||
CheckURL string
|
||||
|
||||
// HTTPClient is used to make all HTTP requests necessary for the update check protocol.
|
||||
// You may configure it to use custom timeouts, proxy servers or other behaviors.
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// Response is returned by Check when an update is available. It may be
|
||||
// passed to Apply to perform the update.
|
||||
type Response struct {
|
||||
// Version of the release that will be updated to if applied.
|
||||
ReleaseVersion string
|
||||
|
||||
// Title of the the release
|
||||
ReleaseTitle string
|
||||
|
||||
// Additional details about the release
|
||||
ReleaseDescription string
|
||||
|
||||
// Creation date of the release
|
||||
ReleaseDate time.Time
|
||||
|
||||
downloadURL string
|
||||
checksum []byte
|
||||
signature []byte
|
||||
patch proto.PatchKind
|
||||
opts Options
|
||||
}
|
||||
|
||||
// SetPublicKeyPEM is a convenience method to set the PublicKey property
|
||||
// used for checking a completed update's signature by parsing a
|
||||
// Public Key formatted as PEM data.
|
||||
func (o *Options) SetPublicKeyPEM(pembytes []byte) error {
|
||||
block, _ := pem.Decode(pembytes)
|
||||
if block == nil {
|
||||
return errors.New("couldn't parse PEM data")
|
||||
}
|
||||
|
||||
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PublicKey = pub
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check communicates with an Equinox update service to determine if
|
||||
// an update for the given application matching the specified options is
|
||||
// available. The returned error is nil only if an update is available.
|
||||
//
|
||||
// The appID is issued to you when creating an application at https://equinox.io
|
||||
//
|
||||
// You can compare the returned error to NotAvailableErr to differentiate between
|
||||
// a successful check that found no update from other errors like a failed
|
||||
// network connection.
|
||||
func Check(appID string, opts Options) (Response, error) {
|
||||
var r Response
|
||||
|
||||
if opts.Channel == "" {
|
||||
opts.Channel = "stable"
|
||||
}
|
||||
if opts.TargetPath == "" {
|
||||
var err error
|
||||
opts.TargetPath, err = osext.Executable()
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
}
|
||||
if opts.OS == "" {
|
||||
opts.OS = runtime.GOOS
|
||||
}
|
||||
if opts.Arch == "" {
|
||||
opts.Arch = runtime.GOARCH
|
||||
}
|
||||
if opts.CheckURL == "" {
|
||||
opts.CheckURL = defaultCheckURL
|
||||
}
|
||||
if opts.HTTPClient == nil {
|
||||
opts.HTTPClient = new(http.Client)
|
||||
}
|
||||
opts.HTTPClient.Transport = newUserAgentTransport(userAgent, opts.HTTPClient.Transport)
|
||||
|
||||
checksum := computeChecksum(opts.TargetPath)
|
||||
|
||||
payload, err := json.Marshal(proto.Request{
|
||||
AppID: appID,
|
||||
Channel: opts.Channel,
|
||||
OS: opts.OS,
|
||||
Arch: opts.Arch,
|
||||
GoARM: opts.GoARM,
|
||||
TargetVersion: opts.Version,
|
||||
CurrentVersion: opts.CurrentVersion,
|
||||
CurrentSHA256: checksum,
|
||||
})
|
||||
|
||||
req, err := http.NewRequest("POST", opts.CheckURL, bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
req.Header.Set("Accept", fmt.Sprintf("application/json; q=1; version=%s; charset=utf-8", protocolVersion))
|
||||
req.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||||
|
||||
resp, err := opts.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
return r, fmt.Errorf("Server responded with %s: %s", resp.Status, body)
|
||||
}
|
||||
|
||||
var protoResp proto.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&protoResp)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
|
||||
if !protoResp.Available {
|
||||
return r, NotAvailableErr
|
||||
}
|
||||
|
||||
r.ReleaseVersion = protoResp.Release.Version
|
||||
r.ReleaseTitle = protoResp.Release.Title
|
||||
r.ReleaseDescription = protoResp.Release.Description
|
||||
r.ReleaseDate = protoResp.Release.CreateDate
|
||||
r.downloadURL = protoResp.DownloadURL
|
||||
r.patch = protoResp.Patch
|
||||
r.opts = opts
|
||||
r.checksum, err = hex.DecodeString(protoResp.Checksum)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
r.signature, err = hex.DecodeString(protoResp.Signature)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func computeChecksum(path string) string {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer f.Close()
|
||||
h := sha256.New()
|
||||
_, err = io.Copy(h, f)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// Apply performs an update of the current executable (or TargetFile, if it was
|
||||
// set on the Options) with the update specified by Response.
|
||||
//
|
||||
// Error is nil if and only if the entire update completes successfully.
|
||||
func (r Response) Apply() error {
|
||||
opts := update.Options{
|
||||
TargetPath: r.opts.TargetPath,
|
||||
TargetMode: r.opts.TargetMode,
|
||||
Checksum: r.checksum,
|
||||
Signature: r.signature,
|
||||
Verifier: update.NewECDSAVerifier(),
|
||||
PublicKey: r.opts.PublicKey,
|
||||
}
|
||||
switch r.patch {
|
||||
case proto.PatchBSDiff:
|
||||
opts.Patcher = update.NewBSDiffPatcher()
|
||||
}
|
||||
|
||||
if err := opts.CheckPermissions(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", r.downloadURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// fetch the update
|
||||
resp, err := r.opts.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
// check that we got a patch
|
||||
if resp.StatusCode >= 400 {
|
||||
msg := "error downloading patch"
|
||||
|
||||
id := resp.Header.Get("Request-Id")
|
||||
if id != "" {
|
||||
msg += ", request " + id
|
||||
}
|
||||
|
||||
blob, err := ioutil.ReadAll(resp.Body)
|
||||
if err == nil {
|
||||
msg += ": " + string(bytes.TrimSpace(blob))
|
||||
}
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
return update.Apply(resp.Body, opts)
|
||||
}
|
||||
|
||||
type userAgentTransport struct {
|
||||
userAgent string
|
||||
http.RoundTripper
|
||||
}
|
||||
|
||||
func newUserAgentTransport(userAgent string, rt http.RoundTripper) *userAgentTransport {
|
||||
if rt == nil {
|
||||
rt = http.DefaultTransport
|
||||
}
|
||||
return &userAgentTransport{userAgent, rt}
|
||||
}
|
||||
|
||||
func (t *userAgentTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
if r.Header.Get("User-Agent") == "" {
|
||||
r.Header.Set("User-Agent", t.userAgent)
|
||||
}
|
||||
return t.RoundTripper.RoundTrip(r)
|
||||
}
|
@ -1,183 +0,0 @@
|
||||
package equinox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/equinox-io/equinox/proto"
|
||||
)
|
||||
|
||||
const fakeAppID = "fake_app_id"
|
||||
|
||||
var (
|
||||
fakeBinary = []byte{0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1}
|
||||
newFakeBinary = []byte{0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2}
|
||||
ts *httptest.Server
|
||||
key *ecdsa.PrivateKey
|
||||
sha string
|
||||
newSHA string
|
||||
signature string
|
||||
)
|
||||
|
||||
func init() {
|
||||
shaBytes := sha256.Sum256(fakeBinary)
|
||||
sha = hex.EncodeToString(shaBytes[:])
|
||||
newSHABytes := sha256.Sum256(newFakeBinary)
|
||||
newSHA = hex.EncodeToString(newSHABytes[:])
|
||||
|
||||
var err error
|
||||
key, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to generate ecdsa key: %v", err))
|
||||
}
|
||||
sig, err := key.Sign(rand.Reader, newSHABytes[:], nil)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to sign new binary: %v", err))
|
||||
}
|
||||
signature = hex.EncodeToString(sig)
|
||||
}
|
||||
|
||||
func TestNotAvailable(t *testing.T) {
|
||||
opts := setup(t, "TestNotAvailable", proto.Response{
|
||||
Available: false,
|
||||
})
|
||||
defer cleanup(opts)
|
||||
|
||||
_, err := Check(fakeAppID, opts)
|
||||
if err != NotAvailableErr {
|
||||
t.Fatalf("Expected not available error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndToEnd(t *testing.T) {
|
||||
opts := setup(t, "TestEndtoEnd", proto.Response{
|
||||
Available: true,
|
||||
Release: proto.Release{
|
||||
Version: "0.1.2.3",
|
||||
Title: "Release Title",
|
||||
Description: "Release Description",
|
||||
CreateDate: time.Now(),
|
||||
},
|
||||
Checksum: newSHA,
|
||||
Signature: signature,
|
||||
})
|
||||
defer cleanup(opts)
|
||||
|
||||
resp, err := Check(fakeAppID, opts)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed check: %v", err)
|
||||
}
|
||||
err = resp.Apply()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed apply: %v", err)
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadFile(opts.TargetPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read file: %v", err)
|
||||
}
|
||||
if !bytes.Equal(buf, newFakeBinary) {
|
||||
t.Fatalf("Binary did not update to new expected value. Got %v, expected %v", buf, newFakeBinary)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidPatch(t *testing.T) {
|
||||
opts := setup(t, "TestInavlidPatch", proto.Response{
|
||||
Available: true,
|
||||
Release: proto.Release{
|
||||
Version: "0.1.2.3",
|
||||
Title: "Release Title",
|
||||
Description: "Release Description",
|
||||
CreateDate: time.Now(),
|
||||
},
|
||||
DownloadURL: "bad-request",
|
||||
Checksum: newSHA,
|
||||
Signature: signature,
|
||||
Patch: proto.PatchBSDiff,
|
||||
})
|
||||
defer cleanup(opts)
|
||||
|
||||
resp, err := Check(fakeAppID, opts)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed check: %v", err)
|
||||
}
|
||||
err = resp.Apply()
|
||||
if err == nil {
|
||||
t.Fatalf("Apply succeeded")
|
||||
}
|
||||
if err.Error() != "error downloading patch: bad-request" {
|
||||
t.Fatalf("Expected a different error message: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func setup(t *testing.T, name string, resp proto.Response) Options {
|
||||
checkUserAgent := func(req *http.Request) {
|
||||
if req.Header.Get("User-Agent") != userAgent {
|
||||
t.Errorf("Expected user agent to be %s, not %s", userAgent, req.Header.Get("User-Agent"))
|
||||
}
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/check", func(w http.ResponseWriter, r *http.Request) {
|
||||
checkUserAgent(r)
|
||||
var req proto.Request
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to decode proto request: %v", err)
|
||||
}
|
||||
if resp.Available {
|
||||
if req.AppID != fakeAppID {
|
||||
t.Fatalf("Unexpected app ID. Got %v, expected %v", err)
|
||||
}
|
||||
if req.CurrentSHA256 != sha {
|
||||
t.Fatalf("Unexpected request SHA: %v", sha)
|
||||
}
|
||||
}
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
})
|
||||
|
||||
// Keying off the download URL may not be the best idea...
|
||||
if resp.DownloadURL == "bad-request" {
|
||||
mux.HandleFunc("/bin", func(w http.ResponseWriter, r *http.Request) {
|
||||
checkUserAgent(r)
|
||||
http.Error(w, "bad-request", http.StatusBadRequest)
|
||||
})
|
||||
} else {
|
||||
mux.HandleFunc("/bin", func(w http.ResponseWriter, r *http.Request) {
|
||||
checkUserAgent(r)
|
||||
w.Write(newFakeBinary)
|
||||
})
|
||||
}
|
||||
|
||||
ts = httptest.NewServer(mux)
|
||||
resp.DownloadURL = ts.URL + "/bin"
|
||||
|
||||
var opts Options
|
||||
opts.CheckURL = ts.URL + "/check"
|
||||
opts.PublicKey = key.Public()
|
||||
|
||||
if name != "" {
|
||||
opts.TargetPath = name
|
||||
ioutil.WriteFile(name, fakeBinary, 0644)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func cleanup(opts Options) {
|
||||
if opts.TargetPath != "" {
|
||||
os.Remove(opts.TargetPath)
|
||||
}
|
||||
ts.Close()
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
|
||||
src
|
@ -1,8 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.5.3
|
||||
- tip
|
||||
notifications:
|
||||
email:
|
||||
- ionathan@gmail.com
|
||||
- marcosnils@gmail.com
|
@ -1,20 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Jonathan Leibiusky and Marcos Lilljedahl
|
||||
|
||||
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.
|
@ -1,3 +0,0 @@
|
||||
test:
|
||||
go get -v -d -t ./...
|
||||
go test -v
|
@ -1,444 +0,0 @@
|
||||
[![Build Status](https://img.shields.io/travis/franela/goreq/master.svg)](https://travis-ci.org/franela/goreq)
|
||||
[![GoDoc](https://godoc.org/github.com/franela/goreq?status.svg)](https://godoc.org/github.com/franela/goreq)
|
||||
|
||||
GoReq
|
||||
=======
|
||||
|
||||
Simple and sane HTTP request library for Go language.
|
||||
|
||||
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
- [Why GoReq?](#user-content-why-goreq)
|
||||
- [How do I install it?](#user-content-how-do-i-install-it)
|
||||
- [What can I do with it?](#user-content-what-can-i-do-with-it)
|
||||
- [Making requests with different methods](#user-content-making-requests-with-different-methods)
|
||||
- [GET](#user-content-get)
|
||||
- [Tags](#user-content-tags)
|
||||
- [POST](#user-content-post)
|
||||
- [Sending payloads in the Body](#user-content-sending-payloads-in-the-body)
|
||||
- [Specifiying request headers](#user-content-specifiying-request-headers)
|
||||
- [Sending Cookies](#cookie-support)
|
||||
- [Setting timeouts](#user-content-setting-timeouts)
|
||||
- [Using the Response and Error](#user-content-using-the-response-and-error)
|
||||
- [Receiving JSON](#user-content-receiving-json)
|
||||
- [Sending/Receiving Compressed Payloads](#user-content-sendingreceiving-compressed-payloads)
|
||||
- [Using gzip compression:](#user-content-using-gzip-compression)
|
||||
- [Using deflate compression:](#user-content-using-deflate-compression)
|
||||
- [Using compressed responses:](#user-content-using-compressed-responses)
|
||||
- [Proxy](#proxy)
|
||||
- [Debugging requests](#debug)
|
||||
- [Getting raw Request & Response](#getting-raw-request--response)
|
||||
- [TODO:](#user-content-todo)
|
||||
|
||||
|
||||
|
||||
Why GoReq?
|
||||
==========
|
||||
|
||||
Go has very nice native libraries that allows you to do lots of cool things. But sometimes those libraries are too low level, which means that to do a simple thing, like an HTTP Request, it takes some time. And if you want to do something as simple as adding a timeout to a request, you will end up writing several lines of code.
|
||||
|
||||
This is why we think GoReq is useful. Because you can do all your HTTP requests in a very simple and comprehensive way, while enabling you to do more advanced stuff by giving you access to the native API.
|
||||
|
||||
How do I install it?
|
||||
====================
|
||||
|
||||
```bash
|
||||
go get github.com/franela/goreq
|
||||
```
|
||||
|
||||
What can I do with it?
|
||||
======================
|
||||
|
||||
## Making requests with different methods
|
||||
|
||||
#### GET
|
||||
```go
|
||||
res, err := goreq.Request{ Uri: "http://www.google.com" }.Do()
|
||||
```
|
||||
|
||||
GoReq default method is GET.
|
||||
|
||||
You can also set value to GET method easily
|
||||
|
||||
```go
|
||||
type Item struct {
|
||||
Limit int
|
||||
Skip int
|
||||
Fields string
|
||||
}
|
||||
|
||||
item := Item {
|
||||
Limit: 3,
|
||||
Skip: 5,
|
||||
Fields: "Value",
|
||||
}
|
||||
|
||||
res, err := goreq.Request{
|
||||
Uri: "http://localhost:3000/",
|
||||
QueryString: item,
|
||||
}.Do()
|
||||
```
|
||||
The sample above will send `http://localhost:3000/?limit=3&skip=5&fields=Value`
|
||||
|
||||
Alternatively the `url` tag can be used in struct fields to customize encoding properties
|
||||
|
||||
```go
|
||||
type Item struct {
|
||||
TheLimit int `url:"the_limit"`
|
||||
TheSkip string `url:"the_skip,omitempty"`
|
||||
TheFields string `url:"-"`
|
||||
}
|
||||
|
||||
item := Item {
|
||||
TheLimit: 3,
|
||||
TheSkip: "",
|
||||
TheFields: "Value",
|
||||
}
|
||||
|
||||
res, err := goreq.Request{
|
||||
Uri: "http://localhost:3000/",
|
||||
QueryString: item,
|
||||
}.Do()
|
||||
```
|
||||
The sample above will send `http://localhost:3000/?the_limit=3`
|
||||
|
||||
|
||||
QueryString also support url.Values
|
||||
|
||||
```go
|
||||
item := url.Values{}
|
||||
item.Set("Limit", 3)
|
||||
item.Add("Field", "somefield")
|
||||
item.Add("Field", "someotherfield")
|
||||
|
||||
res, err := goreq.Request{
|
||||
Uri: "http://localhost:3000/",
|
||||
QueryString: item,
|
||||
}.Do()
|
||||
```
|
||||
|
||||
The sample above will send `http://localhost:3000/?limit=3&field=somefield&field=someotherfield`
|
||||
|
||||
### Tags
|
||||
|
||||
Struct field `url` tag is mainly used as the request parameter name.
|
||||
Tags can be comma separated multiple values, 1st value is for naming and rest has special meanings.
|
||||
|
||||
- special tag for 1st value
|
||||
- `-`: value is ignored if set this
|
||||
|
||||
- special tag for rest 2nd value
|
||||
- `omitempty`: zero-value is ignored if set this
|
||||
- `squash`: the fields of embedded struct is used for parameter
|
||||
|
||||
#### Tag Examples
|
||||
|
||||
```go
|
||||
type Place struct {
|
||||
Country string `url:"country"`
|
||||
City string `url:"city"`
|
||||
ZipCode string `url:"zipcode,omitempty"`
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Place `url:",squash"`
|
||||
|
||||
FirstName string `url:"first_name"`
|
||||
LastName string `url:"last_name"`
|
||||
Age string `url:"age,omitempty"`
|
||||
Password string `url:"-"`
|
||||
}
|
||||
|
||||
johnbull := Person{
|
||||
Place: Place{ // squash the embedded struct value
|
||||
Country: "UK",
|
||||
City: "London",
|
||||
ZipCode: "SW1",
|
||||
},
|
||||
FirstName: "John",
|
||||
LastName: "Doe",
|
||||
Age: "35",
|
||||
Password: "my-secret", // ignored for parameter
|
||||
}
|
||||
|
||||
goreq.Request{
|
||||
Uri: "http://localhost/",
|
||||
QueryString: johnbull,
|
||||
}.Do()
|
||||
// => `http://localhost/?first_name=John&last_name=Doe&age=35&country=UK&city=London&zip_code=SW1`
|
||||
|
||||
|
||||
// age and zipcode will be ignored because of `omitempty`
|
||||
// but firstname isn't.
|
||||
samurai := Person{
|
||||
Place: Place{ // squash the embedded struct value
|
||||
Country: "Japan",
|
||||
City: "Tokyo",
|
||||
},
|
||||
LastName: "Yagyu",
|
||||
}
|
||||
|
||||
goreq.Request{
|
||||
Uri: "http://localhost/",
|
||||
QueryString: samurai,
|
||||
}.Do()
|
||||
// => `http://localhost/?first_name=&last_name=yagyu&country=Japan&city=Tokyo`
|
||||
```
|
||||
|
||||
|
||||
#### POST
|
||||
|
||||
```go
|
||||
res, err := goreq.Request{ Method: "POST", Uri: "http://www.google.com" }.Do()
|
||||
```
|
||||
|
||||
## Sending payloads in the Body
|
||||
|
||||
You can send ```string```, ```Reader``` or ```interface{}``` in the body. The first two will be sent as text. The last one will be marshalled to JSON, if possible.
|
||||
|
||||
```go
|
||||
type Item struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
|
||||
item := Item{ Id: 1111, Name: "foobar" }
|
||||
|
||||
res, err := goreq.Request{
|
||||
Method: "POST",
|
||||
Uri: "http://www.google.com",
|
||||
Body: item,
|
||||
}.Do()
|
||||
```
|
||||
|
||||
## Specifiying request headers
|
||||
|
||||
We think that most of the times the request headers that you use are: ```Host```, ```Content-Type```, ```Accept``` and ```User-Agent```. This is why we decided to make it very easy to set these headers.
|
||||
|
||||
```go
|
||||
res, err := goreq.Request{
|
||||
Uri: "http://www.google.com",
|
||||
Host: "foobar.com",
|
||||
Accept: "application/json",
|
||||
ContentType: "application/json",
|
||||
UserAgent: "goreq",
|
||||
}.Do()
|
||||
```
|
||||
|
||||
But sometimes you need to set other headers. You can still do it.
|
||||
|
||||
```go
|
||||
req := goreq.Request{ Uri: "http://www.google.com" }
|
||||
|
||||
req.AddHeader("X-Custom", "somevalue")
|
||||
|
||||
req.Do()
|
||||
```
|
||||
|
||||
Alternatively you can use the `WithHeader` function to keep the syntax short
|
||||
|
||||
```go
|
||||
res, err = goreq.Request{ Uri: "http://www.google.com" }.WithHeader("X-Custom", "somevalue").Do()
|
||||
```
|
||||
|
||||
## Cookie support
|
||||
|
||||
Cookies can be either set at the request level by sending a [CookieJar](http://golang.org/pkg/net/http/cookiejar/) in the `CookieJar` request field
|
||||
or you can use goreq's one-liner WithCookie method as shown below
|
||||
|
||||
```go
|
||||
res, err := goreq.Request{
|
||||
Uri: "http://www.google.com",
|
||||
}.
|
||||
WithCookie(&http.Cookie{Name: "c1", Value: "v1"}).
|
||||
Do()
|
||||
```
|
||||
|
||||
## Setting timeouts
|
||||
|
||||
GoReq supports 2 kind of timeouts. A general connection timeout and a request specific one. By default the connection timeout is of 1 second. There is no default for request timeout, which means it will wait forever.
|
||||
|
||||
You can change the connection timeout doing:
|
||||
|
||||
```go
|
||||
goreq.SetConnectTimeout(100 * time.Millisecond)
|
||||
```
|
||||
|
||||
And specify the request timeout doing:
|
||||
|
||||
```go
|
||||
res, err := goreq.Request{
|
||||
Uri: "http://www.google.com",
|
||||
Timeout: 500 * time.Millisecond,
|
||||
}.Do()
|
||||
```
|
||||
|
||||
## Using the Response and Error
|
||||
|
||||
GoReq will always return 2 values: a ```Response``` and an ```Error```.
|
||||
If ```Error``` is not ```nil``` it means that an error happened while doing the request and you shouldn't use the ```Response``` in any way.
|
||||
You can check what happened by getting the error message:
|
||||
|
||||
```go
|
||||
fmt.Println(err.Error())
|
||||
```
|
||||
And to make it easy to know if it was a timeout error, you can ask the error or return it:
|
||||
|
||||
```go
|
||||
if serr, ok := err.(*goreq.Error); ok {
|
||||
if serr.Timeout() {
|
||||
...
|
||||
}
|
||||
}
|
||||
return err
|
||||
```
|
||||
|
||||
If you don't get an error, you can safely use the ```Response```.
|
||||
|
||||
```go
|
||||
res.Uri // return final URL location of the response (fulfilled after redirect was made)
|
||||
res.StatusCode // return the status code of the response
|
||||
res.Body // gives you access to the body
|
||||
res.Body.ToString() // will return the body as a string
|
||||
res.Header.Get("Content-Type") // gives you access to all the response headers
|
||||
```
|
||||
Remember that you should **always** close `res.Body` if it's not `nil`
|
||||
|
||||
## Receiving JSON
|
||||
|
||||
GoReq will help you to receive and unmarshal JSON.
|
||||
|
||||
```go
|
||||
type Item struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
|
||||
var item Item
|
||||
|
||||
res.Body.FromJsonTo(&item)
|
||||
```
|
||||
|
||||
## Sending/Receiving Compressed Payloads
|
||||
GoReq supports gzip, deflate and zlib compression of requests' body and transparent decompression of responses provided they have a correct `Content-Encoding` header.
|
||||
|
||||
##### Using gzip compression:
|
||||
```go
|
||||
res, err := goreq.Request{
|
||||
Method: "POST",
|
||||
Uri: "http://www.google.com",
|
||||
Body: item,
|
||||
Compression: goreq.Gzip(),
|
||||
}.Do()
|
||||
```
|
||||
##### Using deflate/zlib compression:
|
||||
```go
|
||||
res, err := goreq.Request{
|
||||
Method: "POST",
|
||||
Uri: "http://www.google.com",
|
||||
Body: item,
|
||||
Compression: goreq.Deflate(),
|
||||
}.Do()
|
||||
```
|
||||
##### Using compressed responses:
|
||||
If servers replies a correct and matching `Content-Encoding` header (gzip requires `Content-Encoding: gzip` and deflate `Content-Encoding: deflate`) goreq transparently decompresses the response so the previous example should always work:
|
||||
```go
|
||||
type Item struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
res, err := goreq.Request{
|
||||
Method: "POST",
|
||||
Uri: "http://www.google.com",
|
||||
Body: item,
|
||||
Compression: goreq.Gzip(),
|
||||
}.Do()
|
||||
var item Item
|
||||
res.Body.FromJsonTo(&item)
|
||||
```
|
||||
If no `Content-Encoding` header is replied by the server GoReq will return the crude response.
|
||||
|
||||
## Proxy
|
||||
If you need to use a proxy for your requests GoReq supports the standard `http_proxy` env variable as well as manually setting the proxy for each request
|
||||
|
||||
```go
|
||||
res, err := goreq.Request{
|
||||
Method: "GET",
|
||||
Proxy: "http://myproxy:myproxyport",
|
||||
Uri: "http://www.google.com",
|
||||
}.Do()
|
||||
```
|
||||
|
||||
### Proxy basic auth is also supported
|
||||
|
||||
```go
|
||||
res, err := goreq.Request{
|
||||
Method: "GET",
|
||||
Proxy: "http://user:pass@myproxy:myproxyport",
|
||||
Uri: "http://www.google.com",
|
||||
}.Do()
|
||||
```
|
||||
|
||||
## Debug
|
||||
If you need to debug your http requests, it can print the http request detail.
|
||||
|
||||
```go
|
||||
res, err := goreq.Request{
|
||||
Method: "GET",
|
||||
Uri: "http://www.google.com",
|
||||
Compression: goreq.Gzip(),
|
||||
ShowDebug: true,
|
||||
}.Do()
|
||||
fmt.Println(res, err)
|
||||
```
|
||||
|
||||
and it will print the log:
|
||||
```
|
||||
GET / HTTP/1.1
|
||||
Host: www.google.com
|
||||
Accept:
|
||||
Accept-Encoding: gzip
|
||||
Content-Encoding: gzip
|
||||
Content-Type:
|
||||
```
|
||||
|
||||
|
||||
### Getting raw Request & Response
|
||||
|
||||
To get the Request:
|
||||
|
||||
```go
|
||||
req := goreq.Request{
|
||||
Host: "foobar.com",
|
||||
}
|
||||
|
||||
//req.Request will return a new instance of an http.Request so you can safely use it for something else
|
||||
request, _ := req.NewRequest()
|
||||
|
||||
```
|
||||
|
||||
|
||||
To get the Response:
|
||||
|
||||
```go
|
||||
res, err := goreq.Request{
|
||||
Method: "GET",
|
||||
Uri: "http://www.google.com",
|
||||
Compression: goreq.Gzip(),
|
||||
ShowDebug: true,
|
||||
}.Do()
|
||||
|
||||
// res.Response will contain the original http.Response structure
|
||||
fmt.Println(res.Response, err)
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
TODO:
|
||||
-----
|
||||
|
||||
We do have a couple of [issues](https://github.com/franela/goreq/issues) pending we'll be addressing soon. But feel free to
|
||||
contribute and send us PRs (with tests please :smile:).
|
@ -1,491 +0,0 @@
|
||||
package goreq
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"compress/zlib"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type itimeout interface {
|
||||
Timeout() bool
|
||||
}
|
||||
type Request struct {
|
||||
headers []headerTuple
|
||||
cookies []*http.Cookie
|
||||
Method string
|
||||
Uri string
|
||||
Body interface{}
|
||||
QueryString interface{}
|
||||
Timeout time.Duration
|
||||
ContentType string
|
||||
Accept string
|
||||
Host string
|
||||
UserAgent string
|
||||
Insecure bool
|
||||
MaxRedirects int
|
||||
RedirectHeaders bool
|
||||
Proxy string
|
||||
Compression *compression
|
||||
BasicAuthUsername string
|
||||
BasicAuthPassword string
|
||||
CookieJar http.CookieJar
|
||||
ShowDebug bool
|
||||
OnBeforeRequest func(goreq *Request, httpreq *http.Request)
|
||||
}
|
||||
|
||||
type compression struct {
|
||||
writer func(buffer io.Writer) (io.WriteCloser, error)
|
||||
reader func(buffer io.Reader) (io.ReadCloser, error)
|
||||
ContentEncoding string
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
*http.Response
|
||||
Uri string
|
||||
Body *Body
|
||||
req *http.Request
|
||||
}
|
||||
|
||||
func (r Response) CancelRequest() {
|
||||
cancelRequest(DefaultTransport, r.req)
|
||||
|
||||
}
|
||||
|
||||
func cancelRequest(transport interface{}, r *http.Request) {
|
||||
if tp, ok := transport.(transportRequestCanceler); ok {
|
||||
tp.CancelRequest(r)
|
||||
}
|
||||
}
|
||||
|
||||
type headerTuple struct {
|
||||
name string
|
||||
value string
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
reader io.ReadCloser
|
||||
compressedReader io.ReadCloser
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
timeout bool
|
||||
Err error
|
||||
}
|
||||
|
||||
type transportRequestCanceler interface {
|
||||
CancelRequest(*http.Request)
|
||||
}
|
||||
|
||||
func (e *Error) Timeout() bool {
|
||||
return e.timeout
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
func (b *Body) Read(p []byte) (int, error) {
|
||||
if b.compressedReader != nil {
|
||||
return b.compressedReader.Read(p)
|
||||
}
|
||||
return b.reader.Read(p)
|
||||
}
|
||||
|
||||
func (b *Body) Close() error {
|
||||
err := b.reader.Close()
|
||||
if b.compressedReader != nil {
|
||||
return b.compressedReader.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *Body) FromJsonTo(o interface{}) error {
|
||||
return json.NewDecoder(b).Decode(o)
|
||||
}
|
||||
|
||||
func (b *Body) ToString() (string, error) {
|
||||
body, err := ioutil.ReadAll(b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func Gzip() *compression {
|
||||
reader := func(buffer io.Reader) (io.ReadCloser, error) {
|
||||
return gzip.NewReader(buffer)
|
||||
}
|
||||
writer := func(buffer io.Writer) (io.WriteCloser, error) {
|
||||
return gzip.NewWriter(buffer), nil
|
||||
}
|
||||
return &compression{writer: writer, reader: reader, ContentEncoding: "gzip"}
|
||||
}
|
||||
|
||||
func Deflate() *compression {
|
||||
reader := func(buffer io.Reader) (io.ReadCloser, error) {
|
||||
return zlib.NewReader(buffer)
|
||||
}
|
||||
writer := func(buffer io.Writer) (io.WriteCloser, error) {
|
||||
return zlib.NewWriter(buffer), nil
|
||||
}
|
||||
return &compression{writer: writer, reader: reader, ContentEncoding: "deflate"}
|
||||
}
|
||||
|
||||
func Zlib() *compression {
|
||||
return Deflate()
|
||||
}
|
||||
|
||||
func paramParse(query interface{}) (string, error) {
|
||||
switch query.(type) {
|
||||
case url.Values:
|
||||
return query.(url.Values).Encode(), nil
|
||||
case *url.Values:
|
||||
return query.(*url.Values).Encode(), nil
|
||||
default:
|
||||
var v = &url.Values{}
|
||||
err := paramParseStruct(v, query)
|
||||
return v.Encode(), err
|
||||
}
|
||||
}
|
||||
|
||||
func paramParseStruct(v *url.Values, query interface{}) error {
|
||||
var (
|
||||
s = reflect.ValueOf(query)
|
||||
t = reflect.TypeOf(query)
|
||||
)
|
||||
for t.Kind() == reflect.Ptr || t.Kind() == reflect.Interface {
|
||||
s = s.Elem()
|
||||
t = s.Type()
|
||||
}
|
||||
|
||||
if t.Kind() != reflect.Struct {
|
||||
return errors.New("Can not parse QueryString.")
|
||||
}
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
var name string
|
||||
|
||||
field := s.Field(i)
|
||||
typeField := t.Field(i)
|
||||
|
||||
if !field.CanInterface() {
|
||||
continue
|
||||
}
|
||||
|
||||
urlTag := typeField.Tag.Get("url")
|
||||
if urlTag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
name, opts := parseTag(urlTag)
|
||||
|
||||
var omitEmpty, squash bool
|
||||
omitEmpty = opts.Contains("omitempty")
|
||||
squash = opts.Contains("squash")
|
||||
|
||||
if squash {
|
||||
err := paramParseStruct(v, field.Interface())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if urlTag == "" {
|
||||
name = strings.ToLower(typeField.Name)
|
||||
}
|
||||
|
||||
if val := fmt.Sprintf("%v", field.Interface()); !(omitEmpty && len(val) == 0) {
|
||||
v.Add(name, val)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareRequestBody(b interface{}) (io.Reader, error) {
|
||||
switch b.(type) {
|
||||
case string:
|
||||
// treat is as text
|
||||
return strings.NewReader(b.(string)), nil
|
||||
case io.Reader:
|
||||
// treat is as text
|
||||
return b.(io.Reader), nil
|
||||
case []byte:
|
||||
//treat as byte array
|
||||
return bytes.NewReader(b.([]byte)), nil
|
||||
case nil:
|
||||
return nil, nil
|
||||
default:
|
||||
// try to jsonify it
|
||||
j, err := json.Marshal(b)
|
||||
if err == nil {
|
||||
return bytes.NewReader(j), nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var DefaultDialer = &net.Dialer{Timeout: 1000 * time.Millisecond}
|
||||
var DefaultTransport http.RoundTripper = &http.Transport{Dial: DefaultDialer.Dial, Proxy: http.ProxyFromEnvironment}
|
||||
var DefaultClient = &http.Client{Transport: DefaultTransport}
|
||||
|
||||
var proxyTransport http.RoundTripper
|
||||
var proxyClient *http.Client
|
||||
|
||||
func SetConnectTimeout(duration time.Duration) {
|
||||
DefaultDialer.Timeout = duration
|
||||
}
|
||||
|
||||
func (r *Request) AddHeader(name string, value string) {
|
||||
if r.headers == nil {
|
||||
r.headers = []headerTuple{}
|
||||
}
|
||||
r.headers = append(r.headers, headerTuple{name: name, value: value})
|
||||
}
|
||||
|
||||
func (r Request) WithHeader(name string, value string) Request {
|
||||
r.AddHeader(name, value)
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Request) AddCookie(c *http.Cookie) {
|
||||
r.cookies = append(r.cookies, c)
|
||||
}
|
||||
|
||||
func (r Request) WithCookie(c *http.Cookie) Request {
|
||||
r.AddCookie(c)
|
||||
return r
|
||||
|
||||
}
|
||||
|
||||
func (r Request) Do() (*Response, error) {
|
||||
var client = DefaultClient
|
||||
var transport = DefaultTransport
|
||||
var resUri string
|
||||
var redirectFailed bool
|
||||
|
||||
r.Method = valueOrDefault(r.Method, "GET")
|
||||
|
||||
// use a client with a cookie jar if necessary. We create a new client not
|
||||
// to modify the default one.
|
||||
if r.CookieJar != nil {
|
||||
client = &http.Client{
|
||||
Transport: transport,
|
||||
Jar: r.CookieJar,
|
||||
}
|
||||
}
|
||||
|
||||
if r.Proxy != "" {
|
||||
proxyUrl, err := url.Parse(r.Proxy)
|
||||
if err != nil {
|
||||
// proxy address is in a wrong format
|
||||
return nil, &Error{Err: err}
|
||||
}
|
||||
|
||||
//If jar is specified new client needs to be built
|
||||
if proxyTransport == nil || client.Jar != nil {
|
||||
proxyTransport = &http.Transport{Dial: DefaultDialer.Dial, Proxy: http.ProxyURL(proxyUrl)}
|
||||
proxyClient = &http.Client{Transport: proxyTransport, Jar: client.Jar}
|
||||
} else if proxyTransport, ok := proxyTransport.(*http.Transport); ok {
|
||||
proxyTransport.Proxy = http.ProxyURL(proxyUrl)
|
||||
}
|
||||
transport = proxyTransport
|
||||
client = proxyClient
|
||||
}
|
||||
|
||||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
|
||||
if len(via) > r.MaxRedirects {
|
||||
redirectFailed = true
|
||||
return errors.New("Error redirecting. MaxRedirects reached")
|
||||
}
|
||||
|
||||
resUri = req.URL.String()
|
||||
|
||||
//By default Golang will not redirect request headers
|
||||
// https://code.google.com/p/go/issues/detail?id=4800&q=request%20header
|
||||
if r.RedirectHeaders {
|
||||
for key, val := range via[0].Header {
|
||||
req.Header[key] = val
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if transport, ok := transport.(*http.Transport); ok {
|
||||
if r.Insecure {
|
||||
if transport.TLSClientConfig != nil {
|
||||
transport.TLSClientConfig.InsecureSkipVerify = true
|
||||
} else {
|
||||
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
} else if transport.TLSClientConfig != nil {
|
||||
// the default TLS client (when transport.TLSClientConfig==nil) is
|
||||
// already set to verify, so do nothing in that case
|
||||
transport.TLSClientConfig.InsecureSkipVerify = false
|
||||
}
|
||||
}
|
||||
|
||||
req, err := r.NewRequest()
|
||||
|
||||
if err != nil {
|
||||
// we couldn't parse the URL.
|
||||
return nil, &Error{Err: err}
|
||||
}
|
||||
|
||||
timeout := false
|
||||
if r.Timeout > 0 {
|
||||
client.Timeout = r.Timeout
|
||||
}
|
||||
|
||||
if r.ShowDebug {
|
||||
dump, err := httputil.DumpRequest(req, true)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
log.Println(string(dump))
|
||||
}
|
||||
|
||||
if r.OnBeforeRequest != nil {
|
||||
r.OnBeforeRequest(&r, req)
|
||||
}
|
||||
res, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
if !timeout {
|
||||
if t, ok := err.(itimeout); ok {
|
||||
timeout = t.Timeout()
|
||||
}
|
||||
if ue, ok := err.(*url.Error); ok {
|
||||
if t, ok := ue.Err.(itimeout); ok {
|
||||
timeout = t.Timeout()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var response *Response
|
||||
//If redirect fails we still want to return response data
|
||||
if redirectFailed {
|
||||
if res != nil {
|
||||
response = &Response{res, resUri, &Body{reader: res.Body}, req}
|
||||
} else {
|
||||
response = &Response{res, resUri, nil, req}
|
||||
}
|
||||
}
|
||||
|
||||
//If redirect fails and we haven't set a redirect count we shouldn't return an error
|
||||
if redirectFailed && r.MaxRedirects == 0 {
|
||||
return response, nil
|
||||
}
|
||||
|
||||
return response, &Error{timeout: timeout, Err: err}
|
||||
}
|
||||
|
||||
if r.Compression != nil && strings.Contains(res.Header.Get("Content-Encoding"), r.Compression.ContentEncoding) {
|
||||
compressedReader, err := r.Compression.reader(res.Body)
|
||||
if err != nil {
|
||||
return nil, &Error{Err: err}
|
||||
}
|
||||
return &Response{res, resUri, &Body{reader: res.Body, compressedReader: compressedReader}, req}, nil
|
||||
}
|
||||
|
||||
return &Response{res, resUri, &Body{reader: res.Body}, req}, nil
|
||||
}
|
||||
|
||||
func (r Request) addHeaders(headersMap http.Header) {
|
||||
if len(r.UserAgent) > 0 {
|
||||
headersMap.Add("User-Agent", r.UserAgent)
|
||||
}
|
||||
if r.Accept != "" {
|
||||
headersMap.Add("Accept", r.Accept)
|
||||
}
|
||||
if r.ContentType != "" {
|
||||
headersMap.Add("Content-Type", r.ContentType)
|
||||
}
|
||||
}
|
||||
|
||||
func (r Request) NewRequest() (*http.Request, error) {
|
||||
|
||||
b, e := prepareRequestBody(r.Body)
|
||||
if e != nil {
|
||||
// there was a problem marshaling the body
|
||||
return nil, &Error{Err: e}
|
||||
}
|
||||
|
||||
if r.QueryString != nil {
|
||||
param, e := paramParse(r.QueryString)
|
||||
if e != nil {
|
||||
return nil, &Error{Err: e}
|
||||
}
|
||||
r.Uri = r.Uri + "?" + param
|
||||
}
|
||||
|
||||
var bodyReader io.Reader
|
||||
if b != nil && r.Compression != nil {
|
||||
buffer := bytes.NewBuffer([]byte{})
|
||||
readBuffer := bufio.NewReader(b)
|
||||
writer, err := r.Compression.writer(buffer)
|
||||
if err != nil {
|
||||
return nil, &Error{Err: err}
|
||||
}
|
||||
_, e = readBuffer.WriteTo(writer)
|
||||
writer.Close()
|
||||
if e != nil {
|
||||
return nil, &Error{Err: e}
|
||||
}
|
||||
bodyReader = buffer
|
||||
} else {
|
||||
bodyReader = b
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(r.Method, r.Uri, bodyReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// add headers to the request
|
||||
req.Host = r.Host
|
||||
|
||||
r.addHeaders(req.Header)
|
||||
if r.Compression != nil {
|
||||
req.Header.Add("Content-Encoding", r.Compression.ContentEncoding)
|
||||
req.Header.Add("Accept-Encoding", r.Compression.ContentEncoding)
|
||||
}
|
||||
if r.headers != nil {
|
||||
for _, header := range r.headers {
|
||||
req.Header.Add(header.name, header.value)
|
||||
}
|
||||
}
|
||||
|
||||
//use basic auth if required
|
||||
if r.BasicAuthUsername != "" {
|
||||
req.SetBasicAuth(r.BasicAuthUsername, r.BasicAuthPassword)
|
||||
}
|
||||
|
||||
for _, c := range r.cookies {
|
||||
req.AddCookie(c)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// Return value if nonempty, def otherwise.
|
||||
func valueOrDefault(value, def string) string {
|
||||
if value != "" {
|
||||
return value
|
||||
}
|
||||
return def
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,64 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found here: https://github.com/golang/go/blob/master/LICENSE
|
||||
|
||||
package goreq
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// tagOptions is the string following a comma in a struct field's "json"
|
||||
// tag, or the empty string. It does not include the leading comma.
|
||||
type tagOptions string
|
||||
|
||||
// parseTag splits a struct field's json tag into its name and
|
||||
// comma-separated options.
|
||||
func parseTag(tag string) (string, tagOptions) {
|
||||
if idx := strings.Index(tag, ","); idx != -1 {
|
||||
return tag[:idx], tagOptions(tag[idx+1:])
|
||||
}
|
||||
return tag, tagOptions("")
|
||||
}
|
||||
|
||||
// Contains reports whether a comma-separated list of options
|
||||
// contains a particular substr flag. substr must be surrounded by a
|
||||
// string boundary or commas.
|
||||
func (o tagOptions) Contains(optionName string) bool {
|
||||
if len(o) == 0 {
|
||||
return false
|
||||
}
|
||||
s := string(o)
|
||||
for s != "" {
|
||||
var next string
|
||||
i := strings.Index(s, ",")
|
||||
if i >= 0 {
|
||||
s, next = s[:i], s[i+1:]
|
||||
}
|
||||
if s == optionName {
|
||||
return true
|
||||
}
|
||||
s = next
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isValidTag(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
for _, c := range s {
|
||||
switch {
|
||||
case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
|
||||
// Backslash and quote chars are reserved, but
|
||||
// otherwise any punctuation chars are allowed
|
||||
// in a tag name.
|
||||
default:
|
||||
if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*~
|
@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014, 2015 Jason E. Aten, Ph.D.
|
||||
|
||||
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.
|
@ -1,34 +0,0 @@
|
||||
rbuf: a circular ring buffer in Golang
|
||||
====
|
||||
|
||||
|
||||
type FixedSizeRingBuf struct:
|
||||
|
||||
* is a fixed-size circular ring buffer. Yes, just what is says.
|
||||
This structure is only for bytes, as it was written to
|
||||
optimize I/O, but could be easily adapted to any other type.
|
||||
|
||||
* We keep a pair of ping/pong buffers so that we can linearize
|
||||
the circular buffer into a contiguous slice if need be.
|
||||
|
||||
For efficiency, a FixedSizeRingBuf may be vastly preferred to
|
||||
a bytes.Buffer. The ReadWithoutAdvance(), Advance(), and Adopt()
|
||||
methods are all non-standard methods written for speed.
|
||||
|
||||
For an I/O heavy application, I have replaced bytes.Buffer with
|
||||
FixedSizeRingBuf and seen memory consumption go from 8GB to 25MB.
|
||||
Yes, that is a 300x reduction in memory footprint. Everything ran
|
||||
faster too.
|
||||
|
||||
Note that Bytes(), while inescapable at times, is expensive: avoid
|
||||
it if possible. If all you need is len(Bytes()), then it is better
|
||||
to use the FixedSizeRingBuf.Readable member directly.
|
||||
Bytes() is expensive because it may copy the back and then
|
||||
the front of a wrapped buffer A[Use] into A[1-Use] in order to
|
||||
get a contiguous, unwrapped, slice. If possible use ContigLen()
|
||||
first to get the size that can be read without copying, Read() that
|
||||
amount, and then Read() a second time -- to avoid the copy.
|
||||
|
||||
copyright (c) 2014, Jason E. Aten
|
||||
|
||||
license: MIT
|
@ -1,481 +0,0 @@
|
||||
package rbuf
|
||||
|
||||
// AtomicFixedSizeRingBuf: Synchronized version of FixedSizeRingBuf,
|
||||
// safe for concurrent access.
|
||||
//
|
||||
// copyright (c) 2014, Jason E. Aten
|
||||
// license: MIT
|
||||
//
|
||||
// Some text from the Golang standard library doc is adapted and
|
||||
// reproduced in fragments below to document the expected behaviors
|
||||
// of the interface functions Read()/Write()/ReadFrom()/WriteTo() that
|
||||
// are implemented here. Those descriptions (see
|
||||
// http://golang.org/pkg/io/#Reader for example) are
|
||||
// copyright 2010 The Go Authors.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// AtomicFixedSizeRingBuf: see FixedSizeRingBuf for the full
|
||||
// details; this is the same, just safe for current access
|
||||
// (and thus paying the price of synchronization on each call
|
||||
// as well.)
|
||||
//
|
||||
type AtomicFixedSizeRingBuf struct {
|
||||
A [2][]byte // a pair of ping/pong buffers. Only one is active.
|
||||
Use int // which A buffer is in active use, 0 or 1
|
||||
N int // MaxViewInBytes, the size of A[0] and A[1] in bytes.
|
||||
Beg int // start of data in A[Use]
|
||||
readable int // number of bytes available to read in A[Use]
|
||||
tex sync.Mutex
|
||||
}
|
||||
|
||||
// Readable() returns the number of bytes available for reading.
|
||||
func (b *AtomicFixedSizeRingBuf) Readable() int {
|
||||
b.tex.Lock()
|
||||
defer b.tex.Unlock()
|
||||
return b.readable
|
||||
}
|
||||
|
||||
// get the length of the largest read that we can provide to a contiguous slice
|
||||
// without an extra linearizing copy of all bytes internally.
|
||||
func (b *AtomicFixedSizeRingBuf) ContigLen() int {
|
||||
b.tex.Lock()
|
||||
defer b.tex.Unlock()
|
||||
|
||||
extent := b.Beg + b.readable
|
||||
firstContigLen := intMin2(extent, b.N) - b.Beg
|
||||
return firstContigLen
|
||||
}
|
||||
|
||||
// constructor. NewAtomicFixedSizeRingBuf will allocate internally
|
||||
// two buffers of size maxViewInBytes.
|
||||
func NewAtomicFixedSizeRingBuf(maxViewInBytes int) *AtomicFixedSizeRingBuf {
|
||||
n := maxViewInBytes
|
||||
r := &AtomicFixedSizeRingBuf{
|
||||
Use: 0, // 0 or 1, whichever is actually in use at the moment.
|
||||
// If we are asked for Bytes() and we wrap, linearize into the other.
|
||||
|
||||
N: n,
|
||||
Beg: 0,
|
||||
readable: 0,
|
||||
}
|
||||
r.A[0] = make([]byte, n, n)
|
||||
r.A[1] = make([]byte, n, n)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Bytes() returns a slice of the contents of the unread portion of the buffer.
|
||||
//
|
||||
// To avoid copying, see the companion BytesTwo() call.
|
||||
//
|
||||
// Unlike the standard library Bytes() method (on bytes.Buffer for example),
|
||||
// the result of the AtomicFixedSizeRingBuf::Bytes(true) is a completely new
|
||||
// returned slice, so modifying that slice will have no impact on the contents
|
||||
// of the internal ring.
|
||||
//
|
||||
// Bytes(false) acts like the standard library bytes.Buffer::Bytes() call,
|
||||
// in that it returns a slice which is backed by the buffer itself (so
|
||||
// no copy is involved).
|
||||
//
|
||||
// The largest slice Bytes ever returns is bounded above by the maxViewInBytes
|
||||
// value used when calling NewAtomicFixedSizeRingBuf().
|
||||
//
|
||||
// Possible side-effect: may modify b.Use, the buffer in use.
|
||||
//
|
||||
func (b *AtomicFixedSizeRingBuf) Bytes(makeCopy bool) []byte {
|
||||
b.tex.Lock()
|
||||
defer b.tex.Unlock()
|
||||
|
||||
extent := b.Beg + b.readable
|
||||
if extent <= b.N {
|
||||
// we fit contiguously in this buffer without wrapping to the other
|
||||
return b.A[b.Use][b.Beg:(b.Beg + b.readable)]
|
||||
}
|
||||
|
||||
// wrap into the other buffer
|
||||
src := b.Use
|
||||
dest := 1 - b.Use
|
||||
|
||||
n := copy(b.A[dest], b.A[src][b.Beg:])
|
||||
n += copy(b.A[dest][n:], b.A[src][0:(extent%b.N)])
|
||||
|
||||
b.Use = dest
|
||||
b.Beg = 0
|
||||
|
||||
if makeCopy {
|
||||
ret := make([]byte, n)
|
||||
copy(ret, b.A[b.Use][:n])
|
||||
return ret
|
||||
}
|
||||
return b.A[b.Use][:n]
|
||||
}
|
||||
|
||||
// TwoBuffers: the return value of BytesTwo(). TwoBuffers
|
||||
// holds two slices to the contents of the readable
|
||||
// area of the internal buffer. The slices contents are logically
|
||||
// ordered First then Second, but the Second will actually
|
||||
// be physically before the First. Either or both of
|
||||
// First and Second may be empty slices.
|
||||
type TwoBuffers struct {
|
||||
First []byte // the first part of the contents
|
||||
Second []byte // the second part of the contents
|
||||
}
|
||||
|
||||
// BytesTwo returns all readable bytes, but in two separate slices,
|
||||
// to avoid copying. The two slices are from the same buffer, but
|
||||
// are not contiguous. Either or both may be empty slices.
|
||||
func (b *AtomicFixedSizeRingBuf) BytesTwo() TwoBuffers {
|
||||
b.tex.Lock()
|
||||
defer b.tex.Unlock()
|
||||
return b.unatomic_BytesTwo()
|
||||
}
|
||||
|
||||
func (b *AtomicFixedSizeRingBuf) unatomic_BytesTwo() TwoBuffers {
|
||||
extent := b.Beg + b.readable
|
||||
if extent <= b.N {
|
||||
// we fit contiguously in this buffer without wrapping to the other.
|
||||
// Let second stay an empty slice.
|
||||
return TwoBuffers{First: b.A[b.Use][b.Beg:(b.Beg + b.readable)], Second: []byte{}}
|
||||
}
|
||||
|
||||
return TwoBuffers{First: b.A[b.Use][b.Beg:(b.Beg + b.readable)], Second: b.A[b.Use][0:(extent % b.N)]}
|
||||
}
|
||||
|
||||
// Purpose of BytesTwo() and AdvanceBytesTwo(): avoid extra copying of data.
|
||||
//
|
||||
// AdvanceBytesTwo() takes a TwoBuffers as input, this must have been
|
||||
// from a previous call to BytesTwo(); no intervening calls to Bytes()
|
||||
// or Adopt() are allowed (or any other future routine or client data
|
||||
// access that changes the internal data location or contents) can have
|
||||
// been made.
|
||||
//
|
||||
// After sanity checks, AdvanceBytesTwo() advances the internal buffer, effectively
|
||||
// calling Advance( len(tb.First) + len(tb.Second)).
|
||||
//
|
||||
// If intervening-calls that changed the buffers (other than appending
|
||||
// data to the buffer) are detected, we will panic as a safety/sanity/
|
||||
// aid-to-debugging measure.
|
||||
//
|
||||
func (b *AtomicFixedSizeRingBuf) AdvanceBytesTwo(tb TwoBuffers) {
|
||||
b.tex.Lock()
|
||||
defer b.tex.Unlock()
|
||||
|
||||
tblen := len(tb.First) + len(tb.Second)
|
||||
|
||||
if tblen == 0 {
|
||||
return // nothing to do
|
||||
}
|
||||
|
||||
// sanity check: insure we have re-located in the meantime
|
||||
if tblen > b.readable {
|
||||
panic(fmt.Sprintf("tblen was %d, and this was greater than b.readerable = %d. Usage error detected and data loss may have occurred (available data appears to have shrunken out from under us!).", tblen, b.readable))
|
||||
}
|
||||
|
||||
tbnow := b.unatomic_BytesTwo()
|
||||
|
||||
if len(tb.First) > 0 {
|
||||
if tb.First[0] != tbnow.First[0] {
|
||||
panic(fmt.Sprintf("slice contents of First have changed out from under us!: '%s' vs '%s'", string(tb.First), string(tbnow.First)))
|
||||
}
|
||||
}
|
||||
if len(tb.Second) > 0 {
|
||||
if len(tb.First) > len(tbnow.First) {
|
||||
panic(fmt.Sprintf("slice contents of Second have changed out from under us! tbnow.First length(%d) is less than tb.First(%d.", len(tbnow.First), len(tb.First)))
|
||||
}
|
||||
if len(tbnow.Second) == 0 {
|
||||
panic(fmt.Sprintf("slice contents of Second have changed out from under us! tbnow.Second is empty, but tb.Second was not"))
|
||||
}
|
||||
if tb.Second[0] != tbnow.Second[0] {
|
||||
panic(fmt.Sprintf("slice contents of Second have changed out from under us!: '%s' vs '%s'", string(tb.Second), string(tbnow.Second)))
|
||||
}
|
||||
}
|
||||
|
||||
b.unatomic_advance(tblen)
|
||||
}
|
||||
|
||||
// Read():
|
||||
//
|
||||
// From bytes.Buffer.Read(): Read reads the next len(p) bytes
|
||||
// from the buffer or until the buffer is drained. The return
|
||||
// value n is the number of bytes read. If the buffer has no data
|
||||
// to return, err is io.EOF (unless len(p) is zero); otherwise it is nil.
|
||||
//
|
||||
// from the description of the Reader interface,
|
||||
// http://golang.org/pkg/io/#Reader
|
||||
//
|
||||
/*
|
||||
Reader is the interface that wraps the basic Read method.
|
||||
|
||||
Read reads up to len(p) bytes into p. It returns the number
|
||||
of bytes read (0 <= n <= len(p)) and any error encountered.
|
||||
Even if Read returns n < len(p), it may use all of p as scratch
|
||||
space during the call. If some data is available but not
|
||||
len(p) bytes, Read conventionally returns what is available
|
||||
instead of waiting for more.
|
||||
|
||||
When Read encounters an error or end-of-file condition after
|
||||
successfully reading n > 0 bytes, it returns the number of bytes
|
||||
read. It may return the (non-nil) error from the same call or
|
||||
return the error (and n == 0) from a subsequent call. An instance
|
||||
of this general case is that a Reader returning a non-zero number
|
||||
of bytes at the end of the input stream may return
|
||||
either err == EOF or err == nil. The next Read should
|
||||
return 0, EOF regardless.
|
||||
|
||||
Callers should always process the n > 0 bytes returned before
|
||||
considering the error err. Doing so correctly handles I/O errors
|
||||
that happen after reading some bytes and also both of the
|
||||
allowed EOF behaviors.
|
||||
|
||||
Implementations of Read are discouraged from returning a zero
|
||||
byte count with a nil error, and callers should treat that
|
||||
situation as a no-op.
|
||||
*/
|
||||
//
|
||||
func (b *AtomicFixedSizeRingBuf) Read(p []byte) (n int, err error) {
|
||||
return b.ReadAndMaybeAdvance(p, true)
|
||||
}
|
||||
|
||||
// ReadWithoutAdvance(): if you want to Read the data and leave
|
||||
// it in the buffer, so as to peek ahead for example.
|
||||
func (b *AtomicFixedSizeRingBuf) ReadWithoutAdvance(p []byte) (n int, err error) {
|
||||
return b.ReadAndMaybeAdvance(p, false)
|
||||
}
|
||||
|
||||
func (b *AtomicFixedSizeRingBuf) ReadAndMaybeAdvance(p []byte, doAdvance bool) (n int, err error) {
|
||||
b.tex.Lock()
|
||||
defer b.tex.Unlock()
|
||||
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if b.readable == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
extent := b.Beg + b.readable
|
||||
if extent <= b.N {
|
||||
n += copy(p, b.A[b.Use][b.Beg:extent])
|
||||
} else {
|
||||
n += copy(p, b.A[b.Use][b.Beg:b.N])
|
||||
if n < len(p) {
|
||||
n += copy(p[n:], b.A[b.Use][0:(extent%b.N)])
|
||||
}
|
||||
}
|
||||
if doAdvance {
|
||||
b.unatomic_advance(n)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Write writes len(p) bytes from p to the underlying data stream.
|
||||
// It returns the number of bytes written from p (0 <= n <= len(p))
|
||||
// and any error encountered that caused the write to stop early.
|
||||
// Write must return a non-nil error if it returns n < len(p).
|
||||
//
|
||||
// Write doesn't modify b.User, so once a []byte is pinned with
|
||||
// a call to Bytes(), it should remain valid even with additional
|
||||
// calls to Write() that come after the Bytes() call.
|
||||
//
|
||||
func (b *AtomicFixedSizeRingBuf) Write(p []byte) (n int, err error) {
|
||||
b.tex.Lock()
|
||||
defer b.tex.Unlock()
|
||||
|
||||
for {
|
||||
if len(p) == 0 {
|
||||
// nothing (left) to copy in; notice we shorten our
|
||||
// local copy p (below) as we read from it.
|
||||
return
|
||||
}
|
||||
|
||||
writeCapacity := b.N - b.readable
|
||||
if writeCapacity <= 0 {
|
||||
// we are all full up already.
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
if len(p) > writeCapacity {
|
||||
err = io.ErrShortWrite
|
||||
// leave err set and
|
||||
// keep going, write what we can.
|
||||
}
|
||||
|
||||
writeStart := (b.Beg + b.readable) % b.N
|
||||
|
||||
upperLim := intMin2(writeStart+writeCapacity, b.N)
|
||||
|
||||
k := copy(b.A[b.Use][writeStart:upperLim], p)
|
||||
|
||||
n += k
|
||||
b.readable += k
|
||||
p = p[k:]
|
||||
|
||||
// we can fill from b.A[b.Use][0:something] from
|
||||
// p's remainder, so loop
|
||||
}
|
||||
}
|
||||
|
||||
// WriteTo and ReadFrom avoid intermediate allocation and copies.
|
||||
|
||||
// WriteTo avoids intermediate allocation and copies.
|
||||
// WriteTo writes data to w until there's no more data to write
|
||||
// or when an error occurs. The return value n is the number of
|
||||
// bytes written. Any error encountered during the write is also returned.
|
||||
func (b *AtomicFixedSizeRingBuf) WriteTo(w io.Writer) (n int64, err error) {
|
||||
b.tex.Lock()
|
||||
defer b.tex.Unlock()
|
||||
|
||||
if b.readable == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
extent := b.Beg + b.readable
|
||||
firstWriteLen := intMin2(extent, b.N) - b.Beg
|
||||
secondWriteLen := b.readable - firstWriteLen
|
||||
if firstWriteLen > 0 {
|
||||
m, e := w.Write(b.A[b.Use][b.Beg:(b.Beg + firstWriteLen)])
|
||||
n += int64(m)
|
||||
b.unatomic_advance(m)
|
||||
|
||||
if e != nil {
|
||||
return n, e
|
||||
}
|
||||
// all bytes should have been written, by definition of
|
||||
// Write method in io.Writer
|
||||
if m != firstWriteLen {
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
}
|
||||
if secondWriteLen > 0 {
|
||||
m, e := w.Write(b.A[b.Use][0:secondWriteLen])
|
||||
n += int64(m)
|
||||
b.unatomic_advance(m)
|
||||
|
||||
if e != nil {
|
||||
return n, e
|
||||
}
|
||||
// all bytes should have been written, by definition of
|
||||
// Write method in io.Writer
|
||||
if m != secondWriteLen {
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// ReadFrom avoids intermediate allocation and copies.
|
||||
// ReadFrom() reads data from r until EOF or error. The return value n
|
||||
// is the number of bytes read. Any error except io.EOF encountered
|
||||
// during the read is also returned.
|
||||
func (b *AtomicFixedSizeRingBuf) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
b.tex.Lock()
|
||||
defer b.tex.Unlock()
|
||||
|
||||
for {
|
||||
writeCapacity := b.N - b.readable
|
||||
if writeCapacity <= 0 {
|
||||
// we are all full
|
||||
return n, nil
|
||||
}
|
||||
writeStart := (b.Beg + b.readable) % b.N
|
||||
upperLim := intMin2(writeStart+writeCapacity, b.N)
|
||||
|
||||
m, e := r.Read(b.A[b.Use][writeStart:upperLim])
|
||||
n += int64(m)
|
||||
b.readable += m
|
||||
if e == io.EOF {
|
||||
return n, nil
|
||||
}
|
||||
if e != nil {
|
||||
return n, e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset quickly forgets any data stored in the ring buffer. The
|
||||
// data is still there, but the ring buffer will ignore it and
|
||||
// overwrite those buffers as new data comes in.
|
||||
func (b *AtomicFixedSizeRingBuf) Reset() {
|
||||
b.tex.Lock()
|
||||
defer b.tex.Unlock()
|
||||
|
||||
b.Beg = 0
|
||||
b.readable = 0
|
||||
b.Use = 0
|
||||
}
|
||||
|
||||
// Advance(): non-standard, but better than Next(),
|
||||
// because we don't have to unwrap our buffer and pay the cpu time
|
||||
// for the copy that unwrapping may need.
|
||||
// Useful in conjuction/after ReadWithoutAdvance() above.
|
||||
func (b *AtomicFixedSizeRingBuf) Advance(n int) {
|
||||
b.tex.Lock()
|
||||
defer b.tex.Unlock()
|
||||
|
||||
b.unatomic_advance(n)
|
||||
}
|
||||
|
||||
// unatomic_advance(): private implementation of Advance() without
|
||||
// the locks. See Advance() above for description.
|
||||
// Necessary so that other methods that already hold
|
||||
// locks can advance, and there are no recursive mutexes
|
||||
// in Go.
|
||||
func (b *AtomicFixedSizeRingBuf) unatomic_advance(n int) {
|
||||
if n <= 0 {
|
||||
return
|
||||
}
|
||||
if n > b.readable {
|
||||
n = b.readable
|
||||
}
|
||||
b.readable -= n
|
||||
b.Beg = (b.Beg + n) % b.N
|
||||
}
|
||||
|
||||
// Adopt(): non-standard.
|
||||
//
|
||||
// For efficiency's sake, (possibly) take ownership of
|
||||
// already allocated slice offered in me.
|
||||
//
|
||||
// If me is large we will adopt it, and we will potentially then
|
||||
// write to the me buffer.
|
||||
// If we already have a bigger buffer, copy me into the existing
|
||||
// buffer instead.
|
||||
//
|
||||
// Side-effect: may change b.Use, among other internal state changes.
|
||||
//
|
||||
func (b *AtomicFixedSizeRingBuf) Adopt(me []byte) {
|
||||
b.tex.Lock()
|
||||
defer b.tex.Unlock()
|
||||
|
||||
n := len(me)
|
||||
if n > b.N {
|
||||
b.A[0] = me
|
||||
b.A[1] = make([]byte, n, n)
|
||||
b.N = n
|
||||
b.Use = 0
|
||||
b.Beg = 0
|
||||
b.readable = n
|
||||
} else {
|
||||
// we already have a larger buffer, reuse it.
|
||||
copy(b.A[0], me)
|
||||
b.Use = 0
|
||||
b.Beg = 0
|
||||
b.readable = n
|
||||
}
|
||||
}
|
||||
|
||||
// keep the atomic_rbuf.go standalone and usable without
|
||||
// the rbuf.go file, by simply duplicating intMin from rbuf.go
|
||||
//
|
||||
func intMin2(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
package rbuf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
cv "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
// new tests just for atomic version
|
||||
|
||||
// same set of tests for non-atomic rbuf:
|
||||
func TestAtomicRingBufReadWrite(t *testing.T) {
|
||||
b := NewAtomicFixedSizeRingBuf(5)
|
||||
|
||||
data := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
|
||||
cv.Convey("Given a AtomicFixedSizeRingBuf of size 5", t, func() {
|
||||
cv.Convey("Write(), Bytes(), and Read() should put and get bytes", func() {
|
||||
n, err := b.Write(data[0:5])
|
||||
cv.So(n, cv.ShouldEqual, 5)
|
||||
cv.So(err, cv.ShouldEqual, nil)
|
||||
cv.So(b.readable, cv.ShouldEqual, 5)
|
||||
if n != 5 {
|
||||
fmt.Printf("should have been able to write 5 bytes.\n")
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cv.So(b.Bytes(false), cv.ShouldResemble, data[0:5])
|
||||
|
||||
sink := make([]byte, 3)
|
||||
n, err = b.Read(sink)
|
||||
cv.So(n, cv.ShouldEqual, 3)
|
||||
cv.So(b.Bytes(false), cv.ShouldResemble, data[3:5])
|
||||
cv.So(sink, cv.ShouldResemble, data[0:3])
|
||||
})
|
||||
|
||||
cv.Convey("Write() more than 5 should give back ErrShortWrite", func() {
|
||||
b.Reset()
|
||||
cv.So(b.readable, cv.ShouldEqual, 0)
|
||||
n, err := b.Write(data[0:10])
|
||||
cv.So(n, cv.ShouldEqual, 5)
|
||||
cv.So(err, cv.ShouldEqual, io.ErrShortWrite)
|
||||
cv.So(b.readable, cv.ShouldEqual, 5)
|
||||
if n != 5 {
|
||||
fmt.Printf("should have been able to write 5 bytes.\n")
|
||||
}
|
||||
cv.So(b.Bytes(false), cv.ShouldResemble, data[0:5])
|
||||
|
||||
sink := make([]byte, 3)
|
||||
n, err = b.Read(sink)
|
||||
cv.So(n, cv.ShouldEqual, 3)
|
||||
cv.So(b.Bytes(false), cv.ShouldResemble, data[3:5])
|
||||
cv.So(sink, cv.ShouldResemble, data[0:3])
|
||||
})
|
||||
|
||||
cv.Convey("we should be able to wrap data and then get it back in Bytes(false)", func() {
|
||||
b.Reset()
|
||||
|
||||
n, err := b.Write(data[0:3])
|
||||
cv.So(n, cv.ShouldEqual, 3)
|
||||
cv.So(err, cv.ShouldEqual, nil)
|
||||
|
||||
sink := make([]byte, 3)
|
||||
n, err = b.Read(sink) // put b.beg at 3
|
||||
cv.So(n, cv.ShouldEqual, 3)
|
||||
cv.So(err, cv.ShouldEqual, nil)
|
||||
cv.So(b.readable, cv.ShouldEqual, 0)
|
||||
|
||||
n, err = b.Write(data[3:8]) // wrap 3 bytes around to the front
|
||||
cv.So(n, cv.ShouldEqual, 5)
|
||||
cv.So(err, cv.ShouldEqual, nil)
|
||||
|
||||
by := b.Bytes(false)
|
||||
cv.So(by, cv.ShouldResemble, data[3:8]) // but still get them back from the ping-pong buffering
|
||||
|
||||
})
|
||||
|
||||
cv.Convey("AtomicFixedSizeRingBuf::WriteTo() should work with wrapped data", func() {
|
||||
b.Reset()
|
||||
|
||||
n, err := b.Write(data[0:3])
|
||||
cv.So(n, cv.ShouldEqual, 3)
|
||||
cv.So(err, cv.ShouldEqual, nil)
|
||||
|
||||
sink := make([]byte, 3)
|
||||
n, err = b.Read(sink) // put b.beg at 3
|
||||
cv.So(n, cv.ShouldEqual, 3)
|
||||
cv.So(err, cv.ShouldEqual, nil)
|
||||
cv.So(b.readable, cv.ShouldEqual, 0)
|
||||
|
||||
n, err = b.Write(data[3:8]) // wrap 3 bytes around to the front
|
||||
|
||||
var bb bytes.Buffer
|
||||
m, err := b.WriteTo(&bb)
|
||||
|
||||
cv.So(m, cv.ShouldEqual, 5)
|
||||
cv.So(err, cv.ShouldEqual, nil)
|
||||
|
||||
by := bb.Bytes()
|
||||
cv.So(by, cv.ShouldResemble, data[3:8]) // but still get them back from the ping-pong buffering
|
||||
|
||||
})
|
||||
|
||||
cv.Convey("AtomicFixedSizeRingBuf::ReadFrom() should work with wrapped data", func() {
|
||||
b.Reset()
|
||||
var bb bytes.Buffer
|
||||
n, err := b.ReadFrom(&bb)
|
||||
cv.So(n, cv.ShouldEqual, 0)
|
||||
cv.So(err, cv.ShouldEqual, nil)
|
||||
|
||||
// write 4, then read 4 bytes
|
||||
m, err := b.Write(data[0:4])
|
||||
cv.So(m, cv.ShouldEqual, 4)
|
||||
cv.So(err, cv.ShouldEqual, nil)
|
||||
|
||||
sink := make([]byte, 4)
|
||||
k, err := b.Read(sink) // put b.beg at 4
|
||||
cv.So(k, cv.ShouldEqual, 4)
|
||||
cv.So(err, cv.ShouldEqual, nil)
|
||||
cv.So(b.readable, cv.ShouldEqual, 0)
|
||||
cv.So(b.Beg, cv.ShouldEqual, 4)
|
||||
|
||||
bbread := bytes.NewBuffer(data[4:9])
|
||||
n, err = b.ReadFrom(bbread) // wrap 4 bytes around to the front, 5 bytes total.
|
||||
|
||||
by := b.Bytes(false)
|
||||
cv.So(by, cv.ShouldResemble, data[4:9]) // but still get them back continguous from the ping-pong buffering
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
@ -1,236 +0,0 @@
|
||||
package rbuf
|
||||
|
||||
// copyright (c) 2016, Jason E. Aten
|
||||
// license: MIT
|
||||
|
||||
import "io"
|
||||
|
||||
// PointerRingBuf:
|
||||
//
|
||||
// a fixed-size circular ring buffer of interface{}
|
||||
//
|
||||
type PointerRingBuf struct {
|
||||
A []interface{}
|
||||
N int // MaxView, the total size of A, whether or not in use.
|
||||
Beg int // start of in-use data in A
|
||||
Readable int // number of pointers available in A (in use)
|
||||
}
|
||||
|
||||
// constructor. NewPointerRingBuf will allocate internally
|
||||
// a slice of size sliceN
|
||||
func NewPointerRingBuf(sliceN int) *PointerRingBuf {
|
||||
n := sliceN
|
||||
r := &PointerRingBuf{
|
||||
N: n,
|
||||
Beg: 0,
|
||||
Readable: 0,
|
||||
}
|
||||
r.A = make([]interface{}, n, n)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// TwoContig returns all readable pointers, but in two separate slices,
|
||||
// to avoid copying. The two slices are from the same buffer, but
|
||||
// are not contiguous. Either or both may be empty slices.
|
||||
func (b *PointerRingBuf) TwoContig() (first []interface{}, second []interface{}) {
|
||||
|
||||
extent := b.Beg + b.Readable
|
||||
if extent <= b.N {
|
||||
// we fit contiguously in this buffer without wrapping to the other.
|
||||
// Let second stay an empty slice.
|
||||
return b.A[b.Beg:(b.Beg + b.Readable)], second
|
||||
}
|
||||
|
||||
return b.A[b.Beg:b.N], b.A[0:(extent % b.N)]
|
||||
}
|
||||
|
||||
// ReadPtrs():
|
||||
//
|
||||
// from bytes.Buffer.Read(): Read reads the next len(p) interface{}
|
||||
// pointers from the buffer or until the buffer is drained. The return
|
||||
// value n is the number of bytes read. If the buffer has no data
|
||||
// to return, err is io.EOF (unless len(p) is zero); otherwise it is nil.
|
||||
func (b *PointerRingBuf) ReadPtrs(p []interface{}) (n int, err error) {
|
||||
return b.readAndMaybeAdvance(p, true)
|
||||
}
|
||||
|
||||
// ReadWithoutAdvance(): if you want to Read the data and leave
|
||||
// it in the buffer, so as to peek ahead for example.
|
||||
func (b *PointerRingBuf) ReadWithoutAdvance(p []interface{}) (n int, err error) {
|
||||
return b.readAndMaybeAdvance(p, false)
|
||||
}
|
||||
|
||||
func (b *PointerRingBuf) readAndMaybeAdvance(p []interface{}, doAdvance bool) (n int, err error) {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if b.Readable == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
extent := b.Beg + b.Readable
|
||||
if extent <= b.N {
|
||||
n += copy(p, b.A[b.Beg:extent])
|
||||
} else {
|
||||
n += copy(p, b.A[b.Beg:b.N])
|
||||
if n < len(p) {
|
||||
n += copy(p[n:], b.A[0:(extent%b.N)])
|
||||
}
|
||||
}
|
||||
if doAdvance {
|
||||
b.Advance(n)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// WritePtrs writes len(p) interface{} values from p to
|
||||
// the underlying data stream.
|
||||
// It returns the number of bytes written from p (0 <= n <= len(p))
|
||||
// and any error encountered that caused the write to stop early.
|
||||
// Write must return a non-nil error if it returns n < len(p).
|
||||
//
|
||||
func (b *PointerRingBuf) WritePtrs(p []interface{}) (n int, err error) {
|
||||
for {
|
||||
if len(p) == 0 {
|
||||
// nothing (left) to copy in; notice we shorten our
|
||||
// local copy p (below) as we read from it.
|
||||
return
|
||||
}
|
||||
|
||||
writeCapacity := b.N - b.Readable
|
||||
if writeCapacity <= 0 {
|
||||
// we are all full up already.
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
if len(p) > writeCapacity {
|
||||
err = io.ErrShortWrite
|
||||
// leave err set and
|
||||
// keep going, write what we can.
|
||||
}
|
||||
|
||||
writeStart := (b.Beg + b.Readable) % b.N
|
||||
|
||||
upperLim := intMin(writeStart+writeCapacity, b.N)
|
||||
|
||||
k := copy(b.A[writeStart:upperLim], p)
|
||||
|
||||
n += k
|
||||
b.Readable += k
|
||||
p = p[k:]
|
||||
|
||||
// we can fill from b.A[0:something] from
|
||||
// p's remainder, so loop
|
||||
}
|
||||
}
|
||||
|
||||
// Reset quickly forgets any data stored in the ring buffer. The
|
||||
// data is still there, but the ring buffer will ignore it and
|
||||
// overwrite those buffers as new data comes in.
|
||||
func (b *PointerRingBuf) Reset() {
|
||||
b.Beg = 0
|
||||
b.Readable = 0
|
||||
}
|
||||
|
||||
// Advance(): non-standard, but better than Next(),
|
||||
// because we don't have to unwrap our buffer and pay the cpu time
|
||||
// for the copy that unwrapping may need.
|
||||
// Useful in conjuction/after ReadWithoutAdvance() above.
|
||||
func (b *PointerRingBuf) Advance(n int) {
|
||||
if n <= 0 {
|
||||
return
|
||||
}
|
||||
if n > b.Readable {
|
||||
n = b.Readable
|
||||
}
|
||||
b.Readable -= n
|
||||
b.Beg = (b.Beg + n) % b.N
|
||||
}
|
||||
|
||||
// Adopt(): non-standard.
|
||||
//
|
||||
// For efficiency's sake, (possibly) take ownership of
|
||||
// already allocated slice offered in me.
|
||||
//
|
||||
// If me is large we will adopt it, and we will potentially then
|
||||
// write to the me buffer.
|
||||
// If we already have a bigger buffer, copy me into the existing
|
||||
// buffer instead.
|
||||
func (b *PointerRingBuf) Adopt(me []interface{}) {
|
||||
n := len(me)
|
||||
if n > b.N {
|
||||
b.A = me
|
||||
b.N = n
|
||||
b.Beg = 0
|
||||
b.Readable = n
|
||||
} else {
|
||||
// we already have a larger buffer, reuse it.
|
||||
copy(b.A, me)
|
||||
b.Beg = 0
|
||||
b.Readable = n
|
||||
}
|
||||
}
|
||||
|
||||
// Push writes len(p) pointers from p to the ring.
|
||||
// It returns the number of elements written from p (0 <= n <= len(p))
|
||||
// and any error encountered that caused the write to stop early.
|
||||
// Push must return a non-nil error if it returns n < len(p).
|
||||
//
|
||||
func (b *PointerRingBuf) Push(p []interface{}) (n int, err error) {
|
||||
for {
|
||||
if len(p) == 0 {
|
||||
// nothing (left) to copy in; notice we shorten our
|
||||
// local copy p (below) as we read from it.
|
||||
return
|
||||
}
|
||||
|
||||
writeCapacity := b.N - b.Readable
|
||||
if writeCapacity <= 0 {
|
||||
// we are all full up already.
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
if len(p) > writeCapacity {
|
||||
err = io.ErrShortWrite
|
||||
// leave err set and
|
||||
// keep going, write what we can.
|
||||
}
|
||||
|
||||
writeStart := (b.Beg + b.Readable) % b.N
|
||||
|
||||
upperLim := intMin(writeStart+writeCapacity, b.N)
|
||||
|
||||
k := copy(b.A[writeStart:upperLim], p)
|
||||
|
||||
n += k
|
||||
b.Readable += k
|
||||
p = p[k:]
|
||||
|
||||
// we can fill from b.A[0:something] from
|
||||
// p's remainder, so loop
|
||||
}
|
||||
}
|
||||
|
||||
// PushAndMaybeOverwriteOldestData always consumes the full
|
||||
// slice p, even if that means blowing away the oldest
|
||||
// unread pointers in the ring to make room. In reality, only the last
|
||||
// min(len(p),b.N) bytes of p will end up being written to the ring.
|
||||
//
|
||||
// This allows the ring to act as a record of the most recent
|
||||
// b.N bytes of data -- a kind of temporal LRU cache, so the
|
||||
// speak. The linux kernel's dmesg ring buffer is similar.
|
||||
//
|
||||
func (b *PointerRingBuf) PushAndMaybeOverwriteOldestData(p []interface{}) (n int, err error) {
|
||||
writeCapacity := b.N - b.Readable
|
||||
if len(p) > writeCapacity {
|
||||
b.Advance(len(p) - writeCapacity)
|
||||
}
|
||||
startPos := 0
|
||||
if len(p) > b.N {
|
||||
startPos = len(p) - b.N
|
||||
}
|
||||
n, err = b.Push(p[startPos:])
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
package rbuf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
cv "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestPointerReadWrite(t *testing.T) {
|
||||
b := NewPointerRingBuf(5)
|
||||
|
||||
data := []interface{}{}
|
||||
for i := 0; i < 10; i++ {
|
||||
data = append(data, interface{}(i))
|
||||
}
|
||||
|
||||
cv.Convey("PointerRingBuf::PushAndMaybeOverwriteOldestData() should auto advance", t, func() {
|
||||
b.Reset()
|
||||
n, err := b.PushAndMaybeOverwriteOldestData(data[:3])
|
||||
cv.So(err, cv.ShouldEqual, nil)
|
||||
cv.So(n, cv.ShouldEqual, 3)
|
||||
cv.So(b.Readable, cv.ShouldEqual, 3)
|
||||
|
||||
n, err = b.PushAndMaybeOverwriteOldestData(data[3:5])
|
||||
cv.So(n, cv.ShouldEqual, 2)
|
||||
cv.So(b.Readable, cv.ShouldEqual, 5)
|
||||
check := make([]interface{}, 5)
|
||||
n, err = b.ReadPtrs(check)
|
||||
cv.So(n, cv.ShouldEqual, 5)
|
||||
cv.So(check, cv.ShouldResemble, data[:5])
|
||||
|
||||
n, err = b.PushAndMaybeOverwriteOldestData(data[5:10])
|
||||
cv.So(err, cv.ShouldEqual, nil)
|
||||
cv.So(n, cv.ShouldEqual, 5)
|
||||
|
||||
n, err = b.ReadWithoutAdvance(check)
|
||||
cv.So(n, cv.ShouldEqual, 5)
|
||||
cv.So(check, cv.ShouldResemble, data[5:10])
|
||||
|
||||
// check TwoConfig
|
||||
q, r := b.TwoContig()
|
||||
|
||||
//p("len q = %v", len(q))
|
||||
//p("len r = %v", len(r))
|
||||
|
||||
found := make([]bool, 10)
|
||||
for _, iface := range q {
|
||||
q0 := iface.(int)
|
||||
found[q0] = true
|
||||
}
|
||||
|
||||
for _, iface := range r {
|
||||
r0 := iface.(int)
|
||||
found[r0] = true
|
||||
}
|
||||
|
||||
totTrue := 0
|
||||
for i := range found {
|
||||
if found[i] {
|
||||
totTrue++
|
||||
}
|
||||
}
|
||||
cv.So(totTrue, cv.ShouldEqual, 5)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func p(format string, a ...interface{}) {
|
||||
fmt.Printf("\n"+format+"\n", a...)
|
||||
}
|
@ -1,527 +0,0 @@
|
||||
package rbuf
|
||||
|
||||
// copyright (c) 2014, Jason E. Aten
|
||||
// license: MIT
|
||||
|
||||
// Some text from the Golang standard library doc is adapted and
|
||||
// reproduced in fragments below to document the expected behaviors
|
||||
// of the interface functions Read()/Write()/ReadFrom()/WriteTo() that
|
||||
// are implemented here. Those descriptions (see
|
||||
// http://golang.org/pkg/io/#Reader for example) are
|
||||
// copyright 2010 The Go Authors.
|
||||
|
||||
import "io"
|
||||
|
||||
// FixedSizeRingBuf:
|
||||
//
|
||||
// a fixed-size circular ring buffer. Yes, just what is says.
|
||||
//
|
||||
// We keep a pair of ping/pong buffers so that we can linearize
|
||||
// the circular buffer into a contiguous slice if need be.
|
||||
//
|
||||
// For efficiency, a FixedSizeRingBuf may be vastly preferred to
|
||||
// a bytes.Buffer. The ReadWithoutAdvance(), Advance(), and Adopt()
|
||||
// methods are all non-standard methods written for speed.
|
||||
//
|
||||
// For an I/O heavy application, I have replaced bytes.Buffer with
|
||||
// FixedSizeRingBuf and seen memory consumption go from 8GB to 25MB.
|
||||
// Yes, that is a 300x reduction in memory footprint. Everything ran
|
||||
// faster too.
|
||||
//
|
||||
// Note that Bytes(), while inescapable at times, is expensive: avoid
|
||||
// it if possible. Instead it is better to use the FixedSizeRingBuf.Readable
|
||||
// member to get the number of bytes available. Bytes() is expensive because
|
||||
// it may copy the back and then the front of a wrapped buffer A[Use]
|
||||
// into A[1-Use] in order to get a contiguous slice. If possible use ContigLen()
|
||||
// first to get the size that can be read without copying, Read() that
|
||||
// amount, and then Read() a second time -- to avoid the copy. See
|
||||
// BytesTwo() for a method that does this for you.
|
||||
//
|
||||
type FixedSizeRingBuf struct {
|
||||
A [2][]byte // a pair of ping/pong buffers. Only one is active.
|
||||
Use int // which A buffer is in active use, 0 or 1
|
||||
N int // MaxViewInBytes, the size of A[0] and A[1] in bytes.
|
||||
Beg int // start of data in A[Use]
|
||||
Readable int // number of bytes available to read in A[Use]
|
||||
}
|
||||
|
||||
// get the length of the largest read that we can provide to a contiguous slice
|
||||
// without an extra linearizing copy of all bytes internally.
|
||||
func (b *FixedSizeRingBuf) ContigLen() int {
|
||||
extent := b.Beg + b.Readable
|
||||
firstContigLen := intMin(extent, b.N) - b.Beg
|
||||
return firstContigLen
|
||||
}
|
||||
|
||||
// constructor. NewFixedSizeRingBuf will allocate internally
|
||||
// two buffers of size maxViewInBytes.
|
||||
func NewFixedSizeRingBuf(maxViewInBytes int) *FixedSizeRingBuf {
|
||||
n := maxViewInBytes
|
||||
r := &FixedSizeRingBuf{
|
||||
Use: 0, // 0 or 1, whichever is actually in use at the moment.
|
||||
// If we are asked for Bytes() and we wrap, linearize into the other.
|
||||
|
||||
N: n,
|
||||
Beg: 0,
|
||||
Readable: 0,
|
||||
}
|
||||
r.A[0] = make([]byte, n, n)
|
||||
r.A[1] = make([]byte, n, n)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// from the standard library description of Bytes():
|
||||
// Bytes() returns a slice of the contents of the unread portion of the buffer.
|
||||
// If the caller changes the contents of the
|
||||
// returned slice, the contents of the buffer will change provided there
|
||||
// are no intervening method calls on the Buffer.
|
||||
//
|
||||
// The largest slice Bytes ever returns is bounded above by the maxViewInBytes
|
||||
// value used when calling NewFixedSizeRingBuf().
|
||||
func (b *FixedSizeRingBuf) Bytes() []byte {
|
||||
|
||||
extent := b.Beg + b.Readable
|
||||
if extent <= b.N {
|
||||
// we fit contiguously in this buffer without wrapping to the other
|
||||
return b.A[b.Use][b.Beg:(b.Beg + b.Readable)]
|
||||
}
|
||||
|
||||
// wrap into the other buffer
|
||||
src := b.Use
|
||||
dest := 1 - b.Use
|
||||
|
||||
n := copy(b.A[dest], b.A[src][b.Beg:])
|
||||
n += copy(b.A[dest][n:], b.A[src][0:(extent%b.N)])
|
||||
|
||||
b.Use = dest
|
||||
b.Beg = 0
|
||||
|
||||
return b.A[b.Use][:n]
|
||||
}
|
||||
|
||||
// BytesTwo returns all readable bytes, but in two separate slices,
|
||||
// to avoid copying. The two slices are from the same buffer, but
|
||||
// are not contiguous. Either or both may be empty slices.
|
||||
func (b *FixedSizeRingBuf) BytesTwo(makeCopy bool) (first []byte, second []byte) {
|
||||
|
||||
extent := b.Beg + b.Readable
|
||||
if extent <= b.N {
|
||||
// we fit contiguously in this buffer without wrapping to the other.
|
||||
// Let second stay an empty slice.
|
||||
return b.A[b.Use][b.Beg:(b.Beg + b.Readable)], second
|
||||
}
|
||||
|
||||
return b.A[b.Use][b.Beg:b.N], b.A[b.Use][0:(extent % b.N)]
|
||||
}
|
||||
|
||||
// Read():
|
||||
//
|
||||
// from bytes.Buffer.Read(): Read reads the next len(p) bytes
|
||||
// from the buffer or until the buffer is drained. The return
|
||||
// value n is the number of bytes read. If the buffer has no data
|
||||
// to return, err is io.EOF (unless len(p) is zero); otherwise it is nil.
|
||||
//
|
||||
// from the description of the Reader interface,
|
||||
// http://golang.org/pkg/io/#Reader
|
||||
//
|
||||
/*
|
||||
Reader is the interface that wraps the basic Read method.
|
||||
|
||||
Read reads up to len(p) bytes into p. It returns the number
|
||||
of bytes read (0 <= n <= len(p)) and any error encountered.
|
||||
Even if Read returns n < len(p), it may use all of p as scratch
|
||||
space during the call. If some data is available but not
|
||||
len(p) bytes, Read conventionally returns what is available
|
||||
instead of waiting for more.
|
||||
|
||||
When Read encounters an error or end-of-file condition after
|
||||
successfully reading n > 0 bytes, it returns the number of bytes
|
||||
read. It may return the (non-nil) error from the same call or
|
||||
return the error (and n == 0) from a subsequent call. An instance
|
||||
of this general case is that a Reader returning a non-zero number
|
||||
of bytes at the end of the input stream may return
|
||||
either err == EOF or err == nil. The next Read should
|
||||
return 0, EOF regardless.
|
||||
|
||||
Callers should always process the n > 0 bytes returned before
|
||||
considering the error err. Doing so correctly handles I/O errors
|
||||
that happen after reading some bytes and also both of the
|
||||
allowed EOF behaviors.
|
||||
|
||||
Implementations of Read are discouraged from returning a zero
|
||||
byte count with a nil error, and callers should treat that
|
||||
situation as a no-op.
|
||||
*/
|
||||
//
|
||||
func (b *FixedSizeRingBuf) Read(p []byte) (n int, err error) {
|
||||
return b.ReadAndMaybeAdvance(p, true)
|
||||
}
|
||||
|
||||
// ReadWithoutAdvance(): if you want to Read the data and leave
|
||||
// it in the buffer, so as to peek ahead for example.
|
||||
func (b *FixedSizeRingBuf) ReadWithoutAdvance(p []byte) (n int, err error) {
|
||||
return b.ReadAndMaybeAdvance(p, false)
|
||||
}
|
||||
|
||||
func (b *FixedSizeRingBuf) ReadAndMaybeAdvance(p []byte, doAdvance bool) (n int, err error) {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if b.Readable == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
extent := b.Beg + b.Readable
|
||||
if extent <= b.N {
|
||||
n += copy(p, b.A[b.Use][b.Beg:extent])
|
||||
} else {
|
||||
n += copy(p, b.A[b.Use][b.Beg:b.N])
|
||||
if n < len(p) {
|
||||
n += copy(p[n:], b.A[b.Use][0:(extent%b.N)])
|
||||
}
|
||||
}
|
||||
if doAdvance {
|
||||
b.Advance(n)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Write writes len(p) bytes from p to the underlying data stream.
|
||||
// It returns the number of bytes written from p (0 <= n <= len(p))
|
||||
// and any error encountered that caused the write to stop early.
|
||||
// Write must return a non-nil error if it returns n < len(p).
|
||||
//
|
||||
func (b *FixedSizeRingBuf) Write(p []byte) (n int, err error) {
|
||||
for {
|
||||
if len(p) == 0 {
|
||||
// nothing (left) to copy in; notice we shorten our
|
||||
// local copy p (below) as we read from it.
|
||||
return
|
||||
}
|
||||
|
||||
writeCapacity := b.N - b.Readable
|
||||
if writeCapacity <= 0 {
|
||||
// we are all full up already.
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
if len(p) > writeCapacity {
|
||||
err = io.ErrShortWrite
|
||||
// leave err set and
|
||||
// keep going, write what we can.
|
||||
}
|
||||
|
||||
writeStart := (b.Beg + b.Readable) % b.N
|
||||
|
||||
upperLim := intMin(writeStart+writeCapacity, b.N)
|
||||
|
||||
k := copy(b.A[b.Use][writeStart:upperLim], p)
|
||||
|
||||
n += k
|
||||
b.Readable += k
|
||||
p = p[k:]
|
||||
|
||||
// we can fill from b.A[b.Use][0:something] from
|
||||
// p's remainder, so loop
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// WriteAndMaybeOverwriteOldestData always consumes the full
|
||||
// buffer p, even if that means blowing away the oldest
|
||||
// unread bytes in the ring to make room. In reality, only the last
|
||||
// min(len(p),b.N) bytes of p will end up being written to the ring.
|
||||
//
|
||||
// This allows the ring to act as a record of the most recent
|
||||
// b.N bytes of data -- a kind of temporal LRU cache, so the
|
||||
// speak. The linux kernel's dmesg ring buffer is similar.
|
||||
//
|
||||
func (b *FixedSizeRingBuf) WriteAndMaybeOverwriteOldestData(p []byte) (n int, err error) {
|
||||
writeCapacity := b.N - b.Readable
|
||||
if len(p) > writeCapacity {
|
||||
b.Advance(len(p) - writeCapacity)
|
||||
}
|
||||
startPos := 0
|
||||
if len(p) > b.N {
|
||||
startPos = len(p) - b.N
|
||||
}
|
||||
n, err = b.Write(p[startPos:])
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// WriteTo and ReadFrom avoid intermediate allocation and copies.
|
||||
|
||||
// WriteTo avoids intermediate allocation and copies.
|
||||
// WriteTo writes data to w until there's no more data to write
|
||||
// or when an error occurs. The return value n is the number of
|
||||
// bytes written. Any error encountered during the write is also returned.
|
||||
func (b *FixedSizeRingBuf) WriteTo(w io.Writer) (n int64, err error) {
|
||||
|
||||
if b.Readable == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
extent := b.Beg + b.Readable
|
||||
firstWriteLen := intMin(extent, b.N) - b.Beg
|
||||
secondWriteLen := b.Readable - firstWriteLen
|
||||
if firstWriteLen > 0 {
|
||||
m, e := w.Write(b.A[b.Use][b.Beg:(b.Beg + firstWriteLen)])
|
||||
n += int64(m)
|
||||
b.Advance(m)
|
||||
|
||||
if e != nil {
|
||||
return n, e
|
||||
}
|
||||
// all bytes should have been written, by definition of
|
||||
// Write method in io.Writer
|
||||
if m != firstWriteLen {
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
}
|
||||
if secondWriteLen > 0 {
|
||||
m, e := w.Write(b.A[b.Use][0:secondWriteLen])
|
||||
n += int64(m)
|
||||
b.Advance(m)
|
||||
|
||||
if e != nil {
|
||||
return n, e
|
||||
}
|
||||
// all bytes should have been written, by definition of
|
||||
// Write method in io.Writer
|
||||
if m != secondWriteLen {
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// ReadFrom avoids intermediate allocation and copies.
|
||||
// ReadFrom() reads data from r until EOF or error. The return value n
|
||||
// is the number of bytes read. Any error except io.EOF encountered
|
||||
// during the read is also returned.
|
||||
func (b *FixedSizeRingBuf) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
for {
|
||||
writeCapacity := b.N - b.Readable
|
||||
if writeCapacity <= 0 {
|
||||
// we are all full
|
||||
return n, nil
|
||||
}
|
||||
writeStart := (b.Beg + b.Readable) % b.N
|
||||
upperLim := intMin(writeStart+writeCapacity, b.N)
|
||||
|
||||
m, e := r.Read(b.A[b.Use][writeStart:upperLim])
|
||||
n += int64(m)
|
||||
b.Readable += m
|
||||
if e == io.EOF {
|
||||
return n, nil
|
||||
}
|
||||
if e != nil {
|
||||
return n, e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset quickly forgets any data stored in the ring buffer. The
|
||||
// data is still there, but the ring buffer will ignore it and
|
||||
// overwrite those buffers as new data comes in.
|
||||
func (b *FixedSizeRingBuf) Reset() {
|
||||
b.Beg = 0
|
||||
b.Readable = 0
|
||||
b.Use = 0
|
||||
}
|
||||
|
||||
// Advance(): non-standard, but better than Next(),
|
||||
// because we don't have to unwrap our buffer and pay the cpu time
|
||||
// for the copy that unwrapping may need.
|
||||
// Useful in conjuction/after ReadWithoutAdvance() above.
|
||||
func (b *FixedSizeRingBuf) Advance(n int) {
|
||||
if n <= 0 {
|
||||
return
|
||||
}
|
||||
if n > b.Readable {
|
||||
n = b.Readable
|
||||
}
|
||||
b.Readable -= n
|
||||
b.Beg = (b.Beg + n) % b.N
|
||||
}
|
||||
|
||||
// Adopt(): non-standard.
|
||||
//
|
||||
// For efficiency's sake, (possibly) take ownership of
|
||||
// already allocated slice offered in me.
|
||||
//
|
||||
// If me is large we will adopt it, and we will potentially then
|
||||
// write to the me buffer.
|
||||
// If we already have a bigger buffer, copy me into the existing
|
||||
// buffer instead.
|
||||
func (b *FixedSizeRingBuf) Adopt(me []byte) {
|
||||
n := len(me)
|
||||
if n > b.N {
|
||||
b.A[0] = me
|
||||
b.A[1] = make([]byte, n, n)
|
||||
b.N = n
|
||||
b.Use = 0
|
||||
b.Beg = 0
|
||||
b.Readable = n
|
||||
} else {
|
||||
// we already have a larger buffer, reuse it.
|
||||
copy(b.A[0], me)
|
||||
b.Use = 0
|
||||
b.Beg = 0
|
||||
b.Readable = n
|
||||
}
|
||||
}
|
||||
|
||||
func intMax(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
func intMin(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FixedSizeRingBuf) Avail() int {
|
||||
return f.Readable
|
||||
}
|
||||
|
||||
// returns the earliest index, or -1 if
|
||||
// the ring is empty
|
||||
func (f *FixedSizeRingBuf) First() int {
|
||||
if f.Readable == 0 {
|
||||
return -1
|
||||
}
|
||||
return f.Beg
|
||||
}
|
||||
|
||||
// Next returns the index of the element after
|
||||
// from, or -1 if no more. returns -2 if erroneous
|
||||
// input (bad from).
|
||||
func (f *FixedSizeRingBuf) Nextpos(from int) int {
|
||||
if from >= f.N || from < 0 {
|
||||
return -2
|
||||
}
|
||||
if f.Readable == 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
last := f.Last()
|
||||
if from == last {
|
||||
return -1
|
||||
}
|
||||
a0, a1, b0, b1 := f.LegalPos()
|
||||
switch {
|
||||
case from >= a0 && from < a1:
|
||||
return from + 1
|
||||
case from == a1:
|
||||
return b0 // can be -1
|
||||
case from >= b0 && from < b1:
|
||||
return from + 1
|
||||
case from == b1:
|
||||
return -1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// LegalPos returns the legal index positions,
|
||||
// [a0,aLast] and [b0,bLast] inclusive, where the
|
||||
// [a0,aLast] holds the first FIFO ordered segment,
|
||||
// and the [b0,bLast] holds the second ordered segment,
|
||||
// if any.
|
||||
// A position of -1 means the segment is not used,
|
||||
// perhaps because b.Readable is zero, or because
|
||||
// the second segment [b0,bLast] is not in use (when
|
||||
// everything fits in the first [a0,aLast] segment).
|
||||
//
|
||||
func (b *FixedSizeRingBuf) LegalPos() (a0, aLast, b0, bLast int) {
|
||||
a0 = -1
|
||||
aLast = -1
|
||||
b0 = -1
|
||||
bLast = -1
|
||||
if b.Readable == 0 {
|
||||
return
|
||||
}
|
||||
a0 = b.Beg
|
||||
last := b.Beg + b.Readable - 1
|
||||
if last < b.N {
|
||||
aLast = last
|
||||
return
|
||||
}
|
||||
aLast = b.N - 1
|
||||
b0 = 0
|
||||
bLast = last % b.N
|
||||
return
|
||||
}
|
||||
|
||||
// Prevpos returns the index of the element before
|
||||
// from, or -1 if no more and from is the
|
||||
// first in the ring. Returns -2 on bad
|
||||
// from position.
|
||||
func (f *FixedSizeRingBuf) Prevpos(from int) int {
|
||||
if from >= f.N || from < 0 {
|
||||
return -2
|
||||
}
|
||||
if f.Readable == 0 {
|
||||
return -1
|
||||
}
|
||||
if from == f.Beg {
|
||||
return -1
|
||||
}
|
||||
a0, a1, b0, b1 := f.LegalPos()
|
||||
switch {
|
||||
case from == a0:
|
||||
return -1
|
||||
case from > a0 && from <= a1:
|
||||
return from - 1
|
||||
case from == b0:
|
||||
return a1
|
||||
case from > b0 && from <= b1:
|
||||
return from - 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// returns the index of the last element,
|
||||
// or -1 if the ring is empty.
|
||||
func (f *FixedSizeRingBuf) Last() int {
|
||||
if f.Readable == 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
last := f.Beg + f.Readable - 1
|
||||
if last < f.N {
|
||||
// we fit without wrapping
|
||||
return last
|
||||
}
|
||||
|
||||
return last % f.N
|
||||
}
|
||||
|
||||
// Kth presents the contents of the
|
||||
// ring as a strictly linear sequence,
|
||||
// so the user doesn't need to think
|
||||
// about modular arithmetic. Here k indexes from
|
||||
// [0, f.Readable-1], assuming f.Avail()
|
||||
// is greater than 0. Kth() returns an
|
||||
// actual index where the logical k-th
|
||||
// element, starting from f.Beg, resides.
|
||||
// f.Beg itself lives at k = 0. If k is
|
||||
// out of bounds, or the ring is empty,
|
||||
// -1 is returned.
|
||||
func (f *FixedSizeRingBuf) Kth(k int) int {
|
||||
if f.Readable == 0 || k < 0 || k >= f.Readable {
|
||||
return -1
|
||||
}
|
||||
return (f.Beg + k) % f.N
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue