mirror of
https://github.com/minio/minio.git
synced 2025-04-12 07:22:18 -04:00
Support in-place upgrades of new minio binary and releases. (#4961)
This PR allows 'minio update' to not only shows update banner but also allows for in-place upgrades. Updates are done safely by validating the downloaded sha256 of the binary. Fixes #4781
This commit is contained in:
parent
8c08571cd9
commit
eb7c690ea9
@ -5,6 +5,7 @@ MAINTAINER Minio Inc <dev@minio.io>
|
|||||||
ENV GOPATH /go
|
ENV GOPATH /go
|
||||||
ENV PATH $PATH:$GOPATH/bin
|
ENV PATH $PATH:$GOPATH/bin
|
||||||
ENV CGO_ENABLED 0
|
ENV CGO_ENABLED 0
|
||||||
|
ENV MINIO_UPDATE off
|
||||||
|
|
||||||
WORKDIR /go/src/github.com/minio/
|
WORKDIR /go/src/github.com/minio/
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ MAINTAINER Minio Inc <dev@minio.io>
|
|||||||
ENV GOPATH /go
|
ENV GOPATH /go
|
||||||
ENV PATH $PATH:$GOPATH/bin
|
ENV PATH $PATH:$GOPATH/bin
|
||||||
ENV CGO_ENABLED 0
|
ENV CGO_ENABLED 0
|
||||||
|
ENV MINIO_UPDATE off
|
||||||
|
|
||||||
WORKDIR /go/src/github.com/minio/
|
WORKDIR /go/src/github.com/minio/
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ MAINTAINER Minio Inc <dev@minio.io>
|
|||||||
|
|
||||||
COPY dockerscripts/docker-entrypoint.sh dockerscripts/healthcheck.sh /usr/bin/
|
COPY dockerscripts/docker-entrypoint.sh dockerscripts/healthcheck.sh /usr/bin/
|
||||||
|
|
||||||
|
ENV MINIO_UPDATE off
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk add --no-cache ca-certificates && \
|
apk add --no-cache ca-certificates && \
|
||||||
apk add --no-cache --virtual .build-deps curl && \
|
apk add --no-cache --virtual .build-deps curl && \
|
||||||
|
@ -29,10 +29,12 @@ import (
|
|||||||
|
|
||||||
// Check for updates and print a notification message
|
// Check for updates and print a notification message
|
||||||
func checkUpdate(mode string) {
|
func checkUpdate(mode string) {
|
||||||
// Its OK to ignore any errors during getUpdateInfo() here.
|
// Its OK to ignore any errors during doUpdate() here.
|
||||||
if older, downloadURL, err := getUpdateInfo(1*time.Second, mode); err == nil {
|
if updateMsg, _, currentReleaseTime, latestReleaseTime, err := getUpdateInfo(2*time.Second, mode); err == nil {
|
||||||
if updateMsg := computeUpdateMessage(downloadURL, older); updateMsg != "" {
|
if globalInplaceUpdateDisabled {
|
||||||
log.Println(updateMsg)
|
log.Println(updateMsg)
|
||||||
|
} else {
|
||||||
|
log.Println(prepareUpdateMessage("Run `minio update`", latestReleaseTime.Sub(currentReleaseTime)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,4 +108,9 @@ func handleCommonEnvVars() {
|
|||||||
if globalDomainName != "" {
|
if globalDomainName != "" {
|
||||||
globalIsEnvDomainName = true
|
globalIsEnvDomainName = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In place update is true by default if the MINIO_UPDATE is not set
|
||||||
|
// or is not set to 'off', if MINIO_UPDATE is set to 'off' then
|
||||||
|
// in-place update is off.
|
||||||
|
globalInplaceUpdateDisabled = strings.EqualFold(os.Getenv("MINIO_UPDATE"), "off")
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,9 @@ ENVIRONMENT VARIABLES:
|
|||||||
BROWSER:
|
BROWSER:
|
||||||
MINIO_BROWSER: To disable web browser access, set this value to "off".
|
MINIO_BROWSER: To disable web browser access, set this value to "off".
|
||||||
|
|
||||||
|
UPDATE:
|
||||||
|
MINIO_UPDATE: To turn off in-place upgrades, set this value to "off".
|
||||||
|
|
||||||
EXAMPLES:
|
EXAMPLES:
|
||||||
1. Start minio gateway server for Azure Blob Storage backend.
|
1. Start minio gateway server for Azure Blob Storage backend.
|
||||||
$ export MINIO_ACCESS_KEY=azureaccountname
|
$ export MINIO_ACCESS_KEY=azureaccountname
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -63,6 +64,9 @@ ENVIRONMENT VARIABLES:
|
|||||||
BROWSER:
|
BROWSER:
|
||||||
MINIO_BROWSER: To disable web browser access, set this value to "off".
|
MINIO_BROWSER: To disable web browser access, set this value to "off".
|
||||||
|
|
||||||
|
UPDATE:
|
||||||
|
MINIO_UPDATE: To turn off in-place upgrades, set this value to "off".
|
||||||
|
|
||||||
EXAMPLES:
|
EXAMPLES:
|
||||||
1. Start minio gateway server for B2 backend.
|
1. Start minio gateway server for B2 backend.
|
||||||
$ export MINIO_ACCESS_KEY=accountID
|
$ export MINIO_ACCESS_KEY=accountID
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -109,6 +110,9 @@ ENVIRONMENT VARIABLES:
|
|||||||
BROWSER:
|
BROWSER:
|
||||||
MINIO_BROWSER: To disable web browser access, set this value to "off".
|
MINIO_BROWSER: To disable web browser access, set this value to "off".
|
||||||
|
|
||||||
|
UPDATE:
|
||||||
|
MINIO_UPDATE: To turn off in-place upgrades, set this value to "off".
|
||||||
|
|
||||||
GCS credentials file:
|
GCS credentials file:
|
||||||
GOOGLE_APPLICATION_CREDENTIALS: Path to credentials.json
|
GOOGLE_APPLICATION_CREDENTIALS: Path to credentials.json
|
||||||
|
|
||||||
|
@ -55,6 +55,9 @@ ENVIRONMENT VARIABLES:
|
|||||||
BROWSER:
|
BROWSER:
|
||||||
MINIO_BROWSER: To disable web browser access, set this value to "off".
|
MINIO_BROWSER: To disable web browser access, set this value to "off".
|
||||||
|
|
||||||
|
UPDATE:
|
||||||
|
MINIO_UPDATE: To turn off in-place upgrades, set this value to "off".
|
||||||
|
|
||||||
EXAMPLES:
|
EXAMPLES:
|
||||||
1. Start minio gateway server for AWS S3 backend.
|
1. Start minio gateway server for AWS S3 backend.
|
||||||
$ export MINIO_ACCESS_KEY=accesskey
|
$ export MINIO_ACCESS_KEY=accesskey
|
||||||
|
@ -69,6 +69,12 @@ ENVIRONMENT VARIABLES: (Default values in parenthesis)
|
|||||||
MINIO_ACCESS_KEY: Custom access key (Do not reuse same access keys on all instances)
|
MINIO_ACCESS_KEY: Custom access key (Do not reuse same access keys on all instances)
|
||||||
MINIO_SECRET_KEY: Custom secret key (Do not reuse same secret keys on all instances)
|
MINIO_SECRET_KEY: Custom secret key (Do not reuse same secret keys on all instances)
|
||||||
|
|
||||||
|
BROWSER:
|
||||||
|
MINIO_BROWSER: To disable web browser access, set this value to "off".
|
||||||
|
|
||||||
|
UPDATE:
|
||||||
|
MINIO_UPDATE: To turn off in-place upgrades, set this value to "off".
|
||||||
|
|
||||||
SIA_TEMP_DIR: The name of the local Sia temporary storage directory. (.sia_temp)
|
SIA_TEMP_DIR: The name of the local Sia temporary storage directory. (.sia_temp)
|
||||||
SIA_API_PASSWORD: API password for Sia daemon. (default is empty)
|
SIA_API_PASSWORD: API password for Sia daemon. (default is empty)
|
||||||
|
|
||||||
|
@ -89,9 +89,12 @@ var (
|
|||||||
// Set to true if credentials were passed from env, default is false.
|
// Set to true if credentials were passed from env, default is false.
|
||||||
globalIsEnvCreds = false
|
globalIsEnvCreds = false
|
||||||
|
|
||||||
// This flag is set to 'true' wen MINIO_REGION env is set.
|
// This flag is set to 'true' when MINIO_REGION env is set.
|
||||||
globalIsEnvRegion = false
|
globalIsEnvRegion = false
|
||||||
|
|
||||||
|
// This flag is set to 'true' when MINIO_UPDATE env is set to 'off'. Default is false.
|
||||||
|
globalInplaceUpdateDisabled = false
|
||||||
|
|
||||||
// This flag is set to 'us-east-1' by default
|
// This flag is set to 'us-east-1' by default
|
||||||
globalServerRegion = globalMinioDefaultRegion
|
globalServerRegion = globalMinioDefaultRegion
|
||||||
|
|
||||||
|
@ -62,6 +62,9 @@ ENVIRONMENT VARIABLES:
|
|||||||
REGION:
|
REGION:
|
||||||
MINIO_REGION: To set custom region. By default it is "us-east-1".
|
MINIO_REGION: To set custom region. By default it is "us-east-1".
|
||||||
|
|
||||||
|
UPDATE:
|
||||||
|
MINIO_UPDATE: To turn off in-place upgrades, set this value to "off".
|
||||||
|
|
||||||
EXAMPLES:
|
EXAMPLES:
|
||||||
1. Start minio server on "/home/shared" directory.
|
1. Start minio server on "/home/shared" directory.
|
||||||
$ {{.HelpName}} /home/shared
|
$ {{.HelpName}} /home/shared
|
||||||
|
@ -18,6 +18,9 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"crypto"
|
||||||
|
_ "crypto/sha256" // Needed for sha256 hash verifier.
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -28,7 +31,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
"github.com/inconshreveable/go-update"
|
||||||
"github.com/minio/cli"
|
"github.com/minio/cli"
|
||||||
|
"github.com/segmentio/go-prompt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check for new software updates.
|
// Check for new software updates.
|
||||||
@ -53,18 +58,19 @@ FLAGS:
|
|||||||
{{end}}{{end}}
|
{{end}}{{end}}
|
||||||
EXIT STATUS:
|
EXIT STATUS:
|
||||||
0 - You are already running the most recent version.
|
0 - You are already running the most recent version.
|
||||||
1 - New update is available.
|
1 - New update was applied successfully.
|
||||||
-1 - Error in getting update information.
|
-1 - Error in getting update information.
|
||||||
|
|
||||||
EXAMPLES:
|
EXAMPLES:
|
||||||
1. Check if there is a new update available:
|
1. Check and update minio:
|
||||||
$ {{.HelpName}}
|
$ {{.HelpName}
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
minioReleaseTagTimeLayout = "2006-01-02T15-04-05Z"
|
minioReleaseTagTimeLayout = "2006-01-02T15-04-05Z"
|
||||||
minioReleaseURL = "https://dl.minio.io/server/minio/release/" + runtime.GOOS + "-" + runtime.GOARCH + "/"
|
minioOSARCH = runtime.GOOS + "-" + runtime.GOARCH
|
||||||
|
minioReleaseURL = "https://dl.minio.io/server/minio/release/" + minioOSARCH + "/"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -335,23 +341,24 @@ func DownloadReleaseData(timeout time.Duration, mode string) (data string, err e
|
|||||||
// fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z
|
// fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z
|
||||||
//
|
//
|
||||||
// The second word must be `minio.` appended to a standard release tag.
|
// The second word must be `minio.` appended to a standard release tag.
|
||||||
func parseReleaseData(data string) (releaseTime time.Time, err error) {
|
func parseReleaseData(data string) (sha256Hex string, releaseTime time.Time, err error) {
|
||||||
fields := strings.Fields(data)
|
fields := strings.Fields(data)
|
||||||
if len(fields) != 2 {
|
if len(fields) != 2 {
|
||||||
err = fmt.Errorf("Unknown release data `%s`", data)
|
err = fmt.Errorf("Unknown release data `%s`", data)
|
||||||
return releaseTime, err
|
return sha256Hex, releaseTime, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sha256Hex = fields[0]
|
||||||
releaseInfo := fields[1]
|
releaseInfo := fields[1]
|
||||||
|
|
||||||
fields = strings.SplitN(releaseInfo, ".", 2)
|
fields = strings.SplitN(releaseInfo, ".", 2)
|
||||||
if len(fields) != 2 {
|
if len(fields) != 2 {
|
||||||
err = fmt.Errorf("Unknown release information `%s`", releaseInfo)
|
err = fmt.Errorf("Unknown release information `%s`", releaseInfo)
|
||||||
return releaseTime, err
|
return sha256Hex, releaseTime, err
|
||||||
}
|
}
|
||||||
if fields[0] != "minio" {
|
if fields[0] != "minio" {
|
||||||
err = fmt.Errorf("Unknown release `%s`", releaseInfo)
|
err = fmt.Errorf("Unknown release `%s`", releaseInfo)
|
||||||
return releaseTime, err
|
return sha256Hex, releaseTime, err
|
||||||
}
|
}
|
||||||
|
|
||||||
releaseTime, err = releaseTagToReleaseTime(fields[1])
|
releaseTime, err = releaseTagToReleaseTime(fields[1])
|
||||||
@ -359,13 +366,13 @@ func parseReleaseData(data string) (releaseTime time.Time, err error) {
|
|||||||
err = fmt.Errorf("Unknown release tag format. %s", err)
|
err = fmt.Errorf("Unknown release tag format. %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return releaseTime, err
|
return sha256Hex, releaseTime, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLatestReleaseTime(timeout time.Duration, mode string) (releaseTime time.Time, err error) {
|
func getLatestReleaseTime(timeout time.Duration, mode string) (sha256Hex string, releaseTime time.Time, err error) {
|
||||||
data, err := DownloadReleaseData(timeout, mode)
|
data, err := DownloadReleaseData(timeout, mode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return releaseTime, err
|
return sha256Hex, releaseTime, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseReleaseData(data)
|
return parseReleaseData(data)
|
||||||
@ -406,49 +413,105 @@ func getDownloadURL(releaseTag string) (downloadURL string) {
|
|||||||
return minioReleaseURL + "minio"
|
return minioReleaseURL + "minio"
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUpdateInfo(timeout time.Duration, mode string) (older time.Duration, downloadURL string, err error) {
|
func getUpdateInfo(timeout time.Duration, mode string) (updateMsg string, sha256Hex string, currentReleaseTime, latestReleaseTime time.Time, err error) {
|
||||||
var currentReleaseTime, latestReleaseTime time.Time
|
|
||||||
currentReleaseTime, err = GetCurrentReleaseTime()
|
currentReleaseTime, err = GetCurrentReleaseTime()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return older, downloadURL, err
|
return updateMsg, sha256Hex, currentReleaseTime, latestReleaseTime, err
|
||||||
}
|
}
|
||||||
|
|
||||||
latestReleaseTime, err = getLatestReleaseTime(timeout, mode)
|
sha256Hex, latestReleaseTime, err = getLatestReleaseTime(timeout, mode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return older, downloadURL, err
|
return updateMsg, sha256Hex, currentReleaseTime, latestReleaseTime, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var older time.Duration
|
||||||
|
var downloadURL string
|
||||||
if latestReleaseTime.After(currentReleaseTime) {
|
if latestReleaseTime.After(currentReleaseTime) {
|
||||||
older = latestReleaseTime.Sub(currentReleaseTime)
|
older = latestReleaseTime.Sub(currentReleaseTime)
|
||||||
downloadURL = getDownloadURL(releaseTimeToReleaseTag(latestReleaseTime))
|
downloadURL = getDownloadURL(releaseTimeToReleaseTag(latestReleaseTime))
|
||||||
}
|
}
|
||||||
|
|
||||||
return older, downloadURL, nil
|
return prepareUpdateMessage(downloadURL, older), sha256Hex, currentReleaseTime, latestReleaseTime, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mainUpdate(ctx *cli.Context) {
|
func doUpdate(sha256Hex string, latestReleaseTime time.Time, ok bool) (successMsg string, err error) {
|
||||||
|
if !ok {
|
||||||
|
successMsg = greenColorSprintf("Minio update to version RELEASE.%s cancelled.",
|
||||||
|
latestReleaseTime.Format(minioReleaseTagTimeLayout))
|
||||||
|
return successMsg, nil
|
||||||
|
}
|
||||||
|
var sha256Sum []byte
|
||||||
|
sha256Sum, err = hex.DecodeString(sha256Hex)
|
||||||
|
if err != nil {
|
||||||
|
return successMsg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(getDownloadURL(releaseTimeToReleaseTag(latestReleaseTime)))
|
||||||
|
if err != nil {
|
||||||
|
return successMsg, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// FIXME: add support for gpg verification as well.
|
||||||
|
if err = update.RollbackError(update.Apply(resp.Body,
|
||||||
|
update.Options{
|
||||||
|
Hash: crypto.SHA256,
|
||||||
|
Checksum: sha256Sum,
|
||||||
|
},
|
||||||
|
)); err != nil {
|
||||||
|
return successMsg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return greenColorSprintf("Minio updated to version RELEASE.%s successfully.",
|
||||||
|
latestReleaseTime.Format(minioReleaseTagTimeLayout)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldUpdate(quiet bool, sha256Hex string, latestReleaseTime time.Time) (ok bool) {
|
||||||
|
ok = true
|
||||||
|
if !quiet {
|
||||||
|
ok = prompt.Confirm(greenColorSprintf("Update to RELEASE.%s [%s]", latestReleaseTime.Format(minioReleaseTagTimeLayout), "yes"))
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
var greenColorSprintf = color.New(color.FgGreen, color.Bold).SprintfFunc()
|
||||||
|
|
||||||
|
func mainUpdate(ctx *cli.Context) error {
|
||||||
if len(ctx.Args()) != 0 {
|
if len(ctx.Args()) != 0 {
|
||||||
cli.ShowCommandHelpAndExit(ctx, "update", -1)
|
cli.ShowCommandHelpAndExit(ctx, "update", -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleCommonEnvVars()
|
||||||
|
|
||||||
quiet := ctx.Bool("quiet") || ctx.GlobalBool("quiet")
|
quiet := ctx.Bool("quiet") || ctx.GlobalBool("quiet")
|
||||||
if quiet {
|
if quiet {
|
||||||
log.EnableQuiet()
|
log.EnableQuiet()
|
||||||
}
|
}
|
||||||
|
|
||||||
minioMode := ""
|
minioMode := ""
|
||||||
older, downloadURL, err := getUpdateInfo(10*time.Second, minioMode)
|
updateMsg, sha256Hex, _, latestReleaseTime, err := getUpdateInfo(10*time.Second, minioMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if updateMsg := computeUpdateMessage(downloadURL, older); updateMsg != "" {
|
// Nothing to update running the latest release.
|
||||||
log.Println(updateMsg)
|
if updateMsg == "" {
|
||||||
os.Exit(1)
|
log.Println(greenColorSprintf("You are already running the most recent version of ‘minio’."))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
colorSprintf := color.New(color.FgGreen, color.Bold).SprintfFunc()
|
log.Println(updateMsg)
|
||||||
log.Println(colorSprintf("You are already running the most recent version of ‘minio’."))
|
// if the in-place update is disabled then we shouldn't ask the
|
||||||
os.Exit(0)
|
// user to update the binaries.
|
||||||
|
if strings.Contains(updateMsg, minioReleaseURL) && !globalInplaceUpdateDisabled {
|
||||||
|
var successMsg string
|
||||||
|
successMsg, err = doUpdate(sha256Hex, latestReleaseTime, shouldUpdate(quiet, sha256Hex, latestReleaseTime))
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
log.Println(successMsg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -265,18 +265,19 @@ func TestParseReleaseData(t *testing.T) {
|
|||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
data string
|
data string
|
||||||
expectedResult time.Time
|
expectedResult time.Time
|
||||||
|
expectedSha256hex string
|
||||||
expectedErr error
|
expectedErr error
|
||||||
}{
|
}{
|
||||||
{"more than two fields", time.Time{}, fmt.Errorf("Unknown release data `more than two fields`")},
|
{"more than two fields", time.Time{}, "", fmt.Errorf("Unknown release data `more than two fields`")},
|
||||||
{"more than", time.Time{}, fmt.Errorf("Unknown release information `than`")},
|
{"more than", time.Time{}, "", fmt.Errorf("Unknown release information `than`")},
|
||||||
{"more than.two.fields", time.Time{}, fmt.Errorf("Unknown release `than.two.fields`")},
|
{"more than.two.fields", time.Time{}, "", fmt.Errorf("Unknown release `than.two.fields`")},
|
||||||
{"more minio.RELEASE.fields", time.Time{}, fmt.Errorf(`Unknown release tag format. parsing time "fields" as "2006-01-02T15-04-05Z": cannot parse "fields" as "2006"`)},
|
{"more minio.RELEASE.fields", time.Time{}, "", fmt.Errorf(`Unknown release tag format. parsing time "fields" as "2006-01-02T15-04-05Z": cannot parse "fields" as "2006"`)},
|
||||||
{"more minio.RELEASE.2016-10-07T01-16-39Z", releaseTime, nil},
|
{"more minio.RELEASE.2016-10-07T01-16-39Z", releaseTime, "more", nil},
|
||||||
{"fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z\n", releaseTime, nil},
|
{"fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z\n", releaseTime, "fbe246edbd382902db9a4035df7dce8cb441357d", nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
result, err := parseReleaseData(testCase.data)
|
sha256Hex, result, err := parseReleaseData(testCase.data)
|
||||||
if testCase.expectedErr == nil {
|
if testCase.expectedErr == nil {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error case %d: expected: %v, got: %v", i+1, testCase.expectedErr, err)
|
t.Errorf("error case %d: expected: %v, got: %v", i+1, testCase.expectedErr, err)
|
||||||
@ -286,9 +287,13 @@ func TestParseReleaseData(t *testing.T) {
|
|||||||
} else if testCase.expectedErr.Error() != err.Error() {
|
} else if testCase.expectedErr.Error() != err.Error() {
|
||||||
t.Errorf("error case %d: expected: %v, got: %v", i+1, testCase.expectedErr, err)
|
t.Errorf("error case %d: expected: %v, got: %v", i+1, testCase.expectedErr, err)
|
||||||
}
|
}
|
||||||
|
if err == nil {
|
||||||
|
if sha256Hex != testCase.expectedSha256hex {
|
||||||
|
t.Errorf("case %d: result: expected: %v, got: %v", i+1, testCase.expectedSha256hex, sha256Hex)
|
||||||
|
}
|
||||||
if !testCase.expectedResult.Equal(result) {
|
if !testCase.expectedResult.Equal(result) {
|
||||||
t.Errorf("case %d: result: expected: %v, got: %v", i+1, testCase.expectedResult, result)
|
t.Errorf("case %d: result: expected: %v, got: %v", i+1, testCase.expectedResult, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -28,9 +28,9 @@ import (
|
|||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
)
|
)
|
||||||
|
|
||||||
// computeUpdateMessage - calculates the update message, only if a
|
// prepareUpdateMessage - prepares the update message, only if a
|
||||||
// newer version is available.
|
// newer version is available.
|
||||||
func computeUpdateMessage(downloadURL string, older time.Duration) string {
|
func prepareUpdateMessage(downloadURL string, older time.Duration) string {
|
||||||
if downloadURL == "" || older <= 0 {
|
if downloadURL == "" || older <= 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Tests update notifier string builder.
|
// Tests update notifier string builder.
|
||||||
func TestComputeUpdateMessage(t *testing.T) {
|
func TestPrepareUpdateMessage(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
older time.Duration
|
older time.Duration
|
||||||
dlURL string
|
dlURL string
|
||||||
@ -71,7 +71,7 @@ func TestComputeUpdateMessage(t *testing.T) {
|
|||||||
cyan := color.New(color.FgCyan, color.Bold).SprintFunc()
|
cyan := color.New(color.FgCyan, color.Bold).SprintFunc()
|
||||||
|
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
output := computeUpdateMessage(testCase.dlURL, testCase.older)
|
output := prepareUpdateMessage(testCase.dlURL, testCase.older)
|
||||||
line1 := fmt.Sprintf("%s %s", plainMsg, yellow(testCase.expectedSubStr))
|
line1 := fmt.Sprintf("%s %s", plainMsg, yellow(testCase.expectedSubStr))
|
||||||
line2 := fmt.Sprintf("Update: %s", cyan(testCase.dlURL))
|
line2 := fmt.Sprintf("Update: %s", cyan(testCase.dlURL))
|
||||||
// Uncomment below to see message appearance:
|
// Uncomment below to see message appearance:
|
||||||
|
15
vendor/github.com/howeyc/gopass/LICENSE.txt
generated
vendored
Normal file
15
vendor/github.com/howeyc/gopass/LICENSE.txt
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
ISC License
|
||||||
|
|
||||||
|
Copyright (c) 2012 Chris Howey
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
384
vendor/github.com/howeyc/gopass/OPENSOLARIS.LICENSE
generated
vendored
Normal file
384
vendor/github.com/howeyc/gopass/OPENSOLARIS.LICENSE
generated
vendored
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
Unless otherwise noted, all files in this distribution are released
|
||||||
|
under the Common Development and Distribution License (CDDL).
|
||||||
|
Exceptions are noted within the associated source files.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
COMMON DEVELOPMENT AND DISTRIBUTION LICENSE Version 1.0
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
1.1. "Contributor" means each individual or entity that creates
|
||||||
|
or contributes to the creation of Modifications.
|
||||||
|
|
||||||
|
1.2. "Contributor Version" means the combination of the Original
|
||||||
|
Software, prior Modifications used by a Contributor (if any),
|
||||||
|
and the Modifications made by that particular Contributor.
|
||||||
|
|
||||||
|
1.3. "Covered Software" means (a) the Original Software, or (b)
|
||||||
|
Modifications, or (c) the combination of files containing
|
||||||
|
Original Software with files containing Modifications, in
|
||||||
|
each case including portions thereof.
|
||||||
|
|
||||||
|
1.4. "Executable" means the Covered Software in any form other
|
||||||
|
than Source Code.
|
||||||
|
|
||||||
|
1.5. "Initial Developer" means the individual or entity that first
|
||||||
|
makes Original Software available under this License.
|
||||||
|
|
||||||
|
1.6. "Larger Work" means a work which combines Covered Software or
|
||||||
|
portions thereof with code not governed by the terms of this
|
||||||
|
License.
|
||||||
|
|
||||||
|
1.7. "License" means this document.
|
||||||
|
|
||||||
|
1.8. "Licensable" means having the right to grant, to the maximum
|
||||||
|
extent possible, whether at the time of the initial grant or
|
||||||
|
subsequently acquired, any and all of the rights conveyed
|
||||||
|
herein.
|
||||||
|
|
||||||
|
1.9. "Modifications" means the Source Code and Executable form of
|
||||||
|
any of the following:
|
||||||
|
|
||||||
|
A. Any file that results from an addition to, deletion from or
|
||||||
|
modification of the contents of a file containing Original
|
||||||
|
Software or previous Modifications;
|
||||||
|
|
||||||
|
B. Any new file that contains any part of the Original
|
||||||
|
Software or previous Modifications; or
|
||||||
|
|
||||||
|
C. Any new file that is contributed or otherwise made
|
||||||
|
available under the terms of this License.
|
||||||
|
|
||||||
|
1.10. "Original Software" means the Source Code and Executable
|
||||||
|
form of computer software code that is originally released
|
||||||
|
under this License.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" means any patent claim(s), now owned or
|
||||||
|
hereafter acquired, including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by
|
||||||
|
grantor.
|
||||||
|
|
||||||
|
1.12. "Source Code" means (a) the common form of computer software
|
||||||
|
code in which modifications are made and (b) associated
|
||||||
|
documentation included in or with such code.
|
||||||
|
|
||||||
|
1.13. "You" (or "Your") means an individual or a legal entity
|
||||||
|
exercising rights under, and complying with all of the terms
|
||||||
|
of, this License. For legal entities, "You" includes any
|
||||||
|
entity which controls, is controlled by, or is under common
|
||||||
|
control with You. For purposes of this definition,
|
||||||
|
"control" means (a) the power, direct or indirect, to cause
|
||||||
|
the direction or management of such entity, whether by
|
||||||
|
contract or otherwise, or (b) ownership of more than fifty
|
||||||
|
percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants.
|
||||||
|
|
||||||
|
2.1. The Initial Developer Grant.
|
||||||
|
|
||||||
|
Conditioned upon Your compliance with Section 3.1 below and
|
||||||
|
subject to third party intellectual property claims, the Initial
|
||||||
|
Developer hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or
|
||||||
|
trademark) Licensable by Initial Developer, to use,
|
||||||
|
reproduce, modify, display, perform, sublicense and
|
||||||
|
distribute the Original Software (or portions thereof),
|
||||||
|
with or without Modifications, and/or as part of a Larger
|
||||||
|
Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims infringed by the making, using or
|
||||||
|
selling of Original Software, to make, have made, use,
|
||||||
|
practice, sell, and offer for sale, and/or otherwise
|
||||||
|
dispose of the Original Software (or portions thereof).
|
||||||
|
|
||||||
|
(c) The licenses granted in Sections 2.1(a) and (b) are
|
||||||
|
effective on the date Initial Developer first distributes
|
||||||
|
or otherwise makes the Original Software available to a
|
||||||
|
third party under the terms of this License.
|
||||||
|
|
||||||
|
(d) Notwithstanding Section 2.1(b) above, no patent license is
|
||||||
|
granted: (1) for code that You delete from the Original
|
||||||
|
Software, or (2) for infringements caused by: (i) the
|
||||||
|
modification of the Original Software, or (ii) the
|
||||||
|
combination of the Original Software with other software
|
||||||
|
or devices.
|
||||||
|
|
||||||
|
2.2. Contributor Grant.
|
||||||
|
|
||||||
|
Conditioned upon Your compliance with Section 3.1 below and
|
||||||
|
subject to third party intellectual property claims, each
|
||||||
|
Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or
|
||||||
|
trademark) Licensable by Contributor to use, reproduce,
|
||||||
|
modify, display, perform, sublicense and distribute the
|
||||||
|
Modifications created by such Contributor (or portions
|
||||||
|
thereof), either on an unmodified basis, with other
|
||||||
|
Modifications, as Covered Software and/or as part of a
|
||||||
|
Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims infringed by the making, using, or
|
||||||
|
selling of Modifications made by that Contributor either
|
||||||
|
alone and/or in combination with its Contributor Version
|
||||||
|
(or portions of such combination), to make, use, sell,
|
||||||
|
offer for sale, have made, and/or otherwise dispose of:
|
||||||
|
(1) Modifications made by that Contributor (or portions
|
||||||
|
thereof); and (2) the combination of Modifications made by
|
||||||
|
that Contributor with its Contributor Version (or portions
|
||||||
|
of such combination).
|
||||||
|
|
||||||
|
(c) The licenses granted in Sections 2.2(a) and 2.2(b) are
|
||||||
|
effective on the date Contributor first distributes or
|
||||||
|
otherwise makes the Modifications available to a third
|
||||||
|
party.
|
||||||
|
|
||||||
|
(d) Notwithstanding Section 2.2(b) above, no patent license is
|
||||||
|
granted: (1) for any code that Contributor has deleted
|
||||||
|
from the Contributor Version; (2) for infringements caused
|
||||||
|
by: (i) third party modifications of Contributor Version,
|
||||||
|
or (ii) the combination of Modifications made by that
|
||||||
|
Contributor with other software (except as part of the
|
||||||
|
Contributor Version) or other devices; or (3) under Patent
|
||||||
|
Claims infringed by Covered Software in the absence of
|
||||||
|
Modifications made by that Contributor.
|
||||||
|
|
||||||
|
3. Distribution Obligations.
|
||||||
|
|
||||||
|
3.1. Availability of Source Code.
|
||||||
|
|
||||||
|
Any Covered Software that You distribute or otherwise make
|
||||||
|
available in Executable form must also be made available in Source
|
||||||
|
Code form and that Source Code form must be distributed only under
|
||||||
|
the terms of this License. You must include a copy of this
|
||||||
|
License with every copy of the Source Code form of the Covered
|
||||||
|
Software You distribute or otherwise make available. You must
|
||||||
|
inform recipients of any such Covered Software in Executable form
|
||||||
|
as to how they can obtain such Covered Software in Source Code
|
||||||
|
form in a reasonable manner on or through a medium customarily
|
||||||
|
used for software exchange.
|
||||||
|
|
||||||
|
3.2. Modifications.
|
||||||
|
|
||||||
|
The Modifications that You create or to which You contribute are
|
||||||
|
governed by the terms of this License. You represent that You
|
||||||
|
believe Your Modifications are Your original creation(s) and/or
|
||||||
|
You have sufficient rights to grant the rights conveyed by this
|
||||||
|
License.
|
||||||
|
|
||||||
|
3.3. Required Notices.
|
||||||
|
|
||||||
|
You must include a notice in each of Your Modifications that
|
||||||
|
identifies You as the Contributor of the Modification. You may
|
||||||
|
not remove or alter any copyright, patent or trademark notices
|
||||||
|
contained within the Covered Software, or any notices of licensing
|
||||||
|
or any descriptive text giving attribution to any Contributor or
|
||||||
|
the Initial Developer.
|
||||||
|
|
||||||
|
3.4. Application of Additional Terms.
|
||||||
|
|
||||||
|
You may not offer or impose any terms on any Covered Software in
|
||||||
|
Source Code form that alters or restricts the applicable version
|
||||||
|
of this License or the recipients' rights hereunder. You may
|
||||||
|
choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of
|
||||||
|
Covered Software. However, you may do so only on Your own behalf,
|
||||||
|
and not on behalf of the Initial Developer or any Contributor.
|
||||||
|
You must make it absolutely clear that any such warranty, support,
|
||||||
|
indemnity or liability obligation is offered by You alone, and You
|
||||||
|
hereby agree to indemnify the Initial Developer and every
|
||||||
|
Contributor for any liability incurred by the Initial Developer or
|
||||||
|
such Contributor as a result of warranty, support, indemnity or
|
||||||
|
liability terms You offer.
|
||||||
|
|
||||||
|
3.5. Distribution of Executable Versions.
|
||||||
|
|
||||||
|
You may distribute the Executable form of the Covered Software
|
||||||
|
under the terms of this License or under the terms of a license of
|
||||||
|
Your choice, which may contain terms different from this License,
|
||||||
|
provided that You are in compliance with the terms of this License
|
||||||
|
and that the license for the Executable form does not attempt to
|
||||||
|
limit or alter the recipient's rights in the Source Code form from
|
||||||
|
the rights set forth in this License. If You distribute the
|
||||||
|
Covered Software in Executable form under a different license, You
|
||||||
|
must make it absolutely clear that any terms which differ from
|
||||||
|
this License are offered by You alone, not by the Initial
|
||||||
|
Developer or Contributor. You hereby agree to indemnify the
|
||||||
|
Initial Developer and every Contributor for any liability incurred
|
||||||
|
by the Initial Developer or such Contributor as a result of any
|
||||||
|
such terms You offer.
|
||||||
|
|
||||||
|
3.6. Larger Works.
|
||||||
|
|
||||||
|
You may create a Larger Work by combining Covered Software with
|
||||||
|
other code not governed by the terms of this License and
|
||||||
|
distribute the Larger Work as a single product. In such a case,
|
||||||
|
You must make sure the requirements of this License are fulfilled
|
||||||
|
for the Covered Software.
|
||||||
|
|
||||||
|
4. Versions of the License.
|
||||||
|
|
||||||
|
4.1. New Versions.
|
||||||
|
|
||||||
|
Sun Microsystems, Inc. is the initial license steward and may
|
||||||
|
publish revised and/or new versions of this License from time to
|
||||||
|
time. Each version will be given a distinguishing version number.
|
||||||
|
Except as provided in Section 4.3, no one other than the license
|
||||||
|
steward has the right to modify this License.
|
||||||
|
|
||||||
|
4.2. Effect of New Versions.
|
||||||
|
|
||||||
|
You may always continue to use, distribute or otherwise make the
|
||||||
|
Covered Software available under the terms of the version of the
|
||||||
|
License under which You originally received the Covered Software.
|
||||||
|
If the Initial Developer includes a notice in the Original
|
||||||
|
Software prohibiting it from being distributed or otherwise made
|
||||||
|
available under any subsequent version of the License, You must
|
||||||
|
distribute and make the Covered Software available under the terms
|
||||||
|
of the version of the License under which You originally received
|
||||||
|
the Covered Software. Otherwise, You may also choose to use,
|
||||||
|
distribute or otherwise make the Covered Software available under
|
||||||
|
the terms of any subsequent version of the License published by
|
||||||
|
the license steward.
|
||||||
|
|
||||||
|
4.3. Modified Versions.
|
||||||
|
|
||||||
|
When You are an Initial Developer and You want to create a new
|
||||||
|
license for Your Original Software, You may create and use a
|
||||||
|
modified version of this License if You: (a) rename the license
|
||||||
|
and remove any references to the name of the license steward
|
||||||
|
(except to note that the license differs from this License); and
|
||||||
|
(b) otherwise make it clear that the license contains terms which
|
||||||
|
differ from this License.
|
||||||
|
|
||||||
|
5. DISCLAIMER OF WARRANTY.
|
||||||
|
|
||||||
|
COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS"
|
||||||
|
BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
|
||||||
|
INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED
|
||||||
|
SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR
|
||||||
|
PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND
|
||||||
|
PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY
|
||||||
|
COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE
|
||||||
|
INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY
|
||||||
|
NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF
|
||||||
|
WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
|
||||||
|
ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS
|
||||||
|
DISCLAIMER.
|
||||||
|
|
||||||
|
6. TERMINATION.
|
||||||
|
|
||||||
|
6.1. This License and the rights granted hereunder will terminate
|
||||||
|
automatically if You fail to comply with terms herein and fail to
|
||||||
|
cure such breach within 30 days of becoming aware of the breach.
|
||||||
|
Provisions which, by their nature, must remain in effect beyond
|
||||||
|
the termination of this License shall survive.
|
||||||
|
|
||||||
|
6.2. If You assert a patent infringement claim (excluding
|
||||||
|
declaratory judgment actions) against Initial Developer or a
|
||||||
|
Contributor (the Initial Developer or Contributor against whom You
|
||||||
|
assert such claim is referred to as "Participant") alleging that
|
||||||
|
the Participant Software (meaning the Contributor Version where
|
||||||
|
the Participant is a Contributor or the Original Software where
|
||||||
|
the Participant is the Initial Developer) directly or indirectly
|
||||||
|
infringes any patent, then any and all rights granted directly or
|
||||||
|
indirectly to You by such Participant, the Initial Developer (if
|
||||||
|
the Initial Developer is not the Participant) and all Contributors
|
||||||
|
under Sections 2.1 and/or 2.2 of this License shall, upon 60 days
|
||||||
|
notice from Participant terminate prospectively and automatically
|
||||||
|
at the expiration of such 60 day notice period, unless if within
|
||||||
|
such 60 day period You withdraw Your claim with respect to the
|
||||||
|
Participant Software against such Participant either unilaterally
|
||||||
|
or pursuant to a written agreement with Participant.
|
||||||
|
|
||||||
|
6.3. In the event of termination under Sections 6.1 or 6.2 above,
|
||||||
|
all end user licenses that have been validly granted by You or any
|
||||||
|
distributor hereunder prior to termination (excluding licenses
|
||||||
|
granted to You by any distributor) shall survive termination.
|
||||||
|
|
||||||
|
7. LIMITATION OF LIABILITY.
|
||||||
|
|
||||||
|
UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
|
||||||
|
(INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE
|
||||||
|
INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF
|
||||||
|
COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE
|
||||||
|
LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
|
||||||
|
CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT
|
||||||
|
LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK
|
||||||
|
STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
|
||||||
|
COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
|
||||||
|
INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
|
||||||
|
LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL
|
||||||
|
INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT
|
||||||
|
APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO
|
||||||
|
NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR
|
||||||
|
CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT
|
||||||
|
APPLY TO YOU.
|
||||||
|
|
||||||
|
8. U.S. GOVERNMENT END USERS.
|
||||||
|
|
||||||
|
The Covered Software is a "commercial item," as that term is
|
||||||
|
defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial
|
||||||
|
computer software" (as that term is defined at 48
|
||||||
|
C.F.R. 252.227-7014(a)(1)) and "commercial computer software
|
||||||
|
documentation" as such terms are used in 48 C.F.R. 12.212
|
||||||
|
(Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48
|
||||||
|
C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all
|
||||||
|
U.S. Government End Users acquire Covered Software with only those
|
||||||
|
rights set forth herein. This U.S. Government Rights clause is in
|
||||||
|
lieu of, and supersedes, any other FAR, DFAR, or other clause or
|
||||||
|
provision that addresses Government rights in computer software
|
||||||
|
under this License.
|
||||||
|
|
||||||
|
9. MISCELLANEOUS.
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. This License shall be governed
|
||||||
|
by the law of the jurisdiction specified in a notice contained
|
||||||
|
within the Original Software (except to the extent applicable law,
|
||||||
|
if any, provides otherwise), excluding such jurisdiction's
|
||||||
|
conflict-of-law provisions. Any litigation relating to this
|
||||||
|
License shall be subject to the jurisdiction of the courts located
|
||||||
|
in the jurisdiction and venue specified in a notice contained
|
||||||
|
within the Original Software, with the losing party responsible
|
||||||
|
for costs, including, without limitation, court costs and
|
||||||
|
reasonable attorneys' fees and expenses. The application of the
|
||||||
|
United Nations Convention on Contracts for the International Sale
|
||||||
|
of Goods is expressly excluded. Any law or regulation which
|
||||||
|
provides that the language of a contract shall be construed
|
||||||
|
against the drafter shall not apply to this License. You agree
|
||||||
|
that You alone are responsible for compliance with the United
|
||||||
|
States export administration regulations (and the export control
|
||||||
|
laws and regulation of any other countries) when You use,
|
||||||
|
distribute or otherwise make available any Covered Software.
|
||||||
|
|
||||||
|
10. RESPONSIBILITY FOR CLAIMS.
|
||||||
|
|
||||||
|
As between Initial Developer and the Contributors, each party is
|
||||||
|
responsible for claims and damages arising, directly or
|
||||||
|
indirectly, out of its utilization of rights under this License
|
||||||
|
and You agree to work with Initial Developer and Contributors to
|
||||||
|
distribute such responsibility on an equitable basis. Nothing
|
||||||
|
herein is intended or shall be deemed to constitute any admission
|
||||||
|
of liability.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
|
NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND
|
||||||
|
DISTRIBUTION LICENSE (CDDL)
|
||||||
|
|
||||||
|
For Covered Software in this distribution, this License shall
|
||||||
|
be governed by the laws of the State of California (excluding
|
||||||
|
conflict-of-law provisions).
|
||||||
|
|
||||||
|
Any litigation relating to this License shall be subject to the
|
||||||
|
jurisdiction of the Federal Courts of the Northern District of
|
||||||
|
California and the state courts of the State of California, with
|
||||||
|
venue lying in Santa Clara County, California.
|
27
vendor/github.com/howeyc/gopass/README.md
generated
vendored
Normal file
27
vendor/github.com/howeyc/gopass/README.md
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# getpasswd in Go [](https://godoc.org/github.com/howeyc/gopass) [](http://travis-ci.org/howeyc/gopass)
|
||||||
|
|
||||||
|
Retrieve password from user terminal or piped input without echo.
|
||||||
|
|
||||||
|
Verified on BSD, Linux, and Windows.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
import "github.com/howeyc/gopass"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Printf("Password: ")
|
||||||
|
|
||||||
|
// Silent. For printing *'s use gopass.GetPasswdMasked()
|
||||||
|
pass, err := gopass.GetPasswd()
|
||||||
|
if err != nil {
|
||||||
|
// Handle gopass.ErrInterrupted or getch() read error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do something with pass
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Caution: Multi-byte characters not supported!
|
110
vendor/github.com/howeyc/gopass/pass.go
generated
vendored
Normal file
110
vendor/github.com/howeyc/gopass/pass.go
generated
vendored
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package gopass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FdReader interface {
|
||||||
|
io.Reader
|
||||||
|
Fd() uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultGetCh = func(r io.Reader) (byte, error) {
|
||||||
|
buf := make([]byte, 1)
|
||||||
|
if n, err := r.Read(buf); n == 0 || err != nil {
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
return buf[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
maxLength = 512
|
||||||
|
ErrInterrupted = errors.New("interrupted")
|
||||||
|
ErrMaxLengthExceeded = fmt.Errorf("maximum byte limit (%v) exceeded", maxLength)
|
||||||
|
|
||||||
|
// Provide variable so that tests can provide a mock implementation.
|
||||||
|
getch = defaultGetCh
|
||||||
|
)
|
||||||
|
|
||||||
|
// getPasswd returns the input read from terminal.
|
||||||
|
// If prompt is not empty, it will be output as a prompt to the user
|
||||||
|
// If masked is true, typing will be matched by asterisks on the screen.
|
||||||
|
// Otherwise, typing will echo nothing.
|
||||||
|
func getPasswd(prompt string, masked bool, r FdReader, w io.Writer) ([]byte, error) {
|
||||||
|
var err error
|
||||||
|
var pass, bs, mask []byte
|
||||||
|
if masked {
|
||||||
|
bs = []byte("\b \b")
|
||||||
|
mask = []byte("*")
|
||||||
|
}
|
||||||
|
|
||||||
|
if isTerminal(r.Fd()) {
|
||||||
|
if oldState, err := makeRaw(r.Fd()); err != nil {
|
||||||
|
return pass, err
|
||||||
|
} else {
|
||||||
|
defer func() {
|
||||||
|
restore(r.Fd(), oldState)
|
||||||
|
fmt.Fprintln(w)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if prompt != "" {
|
||||||
|
fmt.Fprint(w, prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track total bytes read, not just bytes in the password. This ensures any
|
||||||
|
// errors that might flood the console with nil or -1 bytes infinitely are
|
||||||
|
// capped.
|
||||||
|
var counter int
|
||||||
|
for counter = 0; counter <= maxLength; counter++ {
|
||||||
|
if v, e := getch(r); e != nil {
|
||||||
|
err = e
|
||||||
|
break
|
||||||
|
} else if v == 127 || v == 8 {
|
||||||
|
if l := len(pass); l > 0 {
|
||||||
|
pass = pass[:l-1]
|
||||||
|
fmt.Fprint(w, string(bs))
|
||||||
|
}
|
||||||
|
} else if v == 13 || v == 10 {
|
||||||
|
break
|
||||||
|
} else if v == 3 {
|
||||||
|
err = ErrInterrupted
|
||||||
|
break
|
||||||
|
} else if v != 0 {
|
||||||
|
pass = append(pass, v)
|
||||||
|
fmt.Fprint(w, string(mask))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if counter > maxLength {
|
||||||
|
err = ErrMaxLengthExceeded
|
||||||
|
}
|
||||||
|
|
||||||
|
return pass, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPasswd returns the password read from the terminal without echoing input.
|
||||||
|
// The returned byte array does not include end-of-line characters.
|
||||||
|
func GetPasswd() ([]byte, error) {
|
||||||
|
return getPasswd("", false, os.Stdin, os.Stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPasswdMasked returns the password read from the terminal, echoing asterisks.
|
||||||
|
// The returned byte array does not include end-of-line characters.
|
||||||
|
func GetPasswdMasked() ([]byte, error) {
|
||||||
|
return getPasswd("", true, os.Stdin, os.Stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPasswdPrompt prompts the user and returns the password read from the terminal.
|
||||||
|
// If mask is true, then asterisks are echoed.
|
||||||
|
// The returned byte array does not include end-of-line characters.
|
||||||
|
func GetPasswdPrompt(prompt string, mask bool, r FdReader, w io.Writer) ([]byte, error) {
|
||||||
|
return getPasswd(prompt, mask, r, w)
|
||||||
|
}
|
25
vendor/github.com/howeyc/gopass/terminal.go
generated
vendored
Normal file
25
vendor/github.com/howeyc/gopass/terminal.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// +build !solaris
|
||||||
|
|
||||||
|
package gopass
|
||||||
|
|
||||||
|
import "golang.org/x/crypto/ssh/terminal"
|
||||||
|
|
||||||
|
type terminalState struct {
|
||||||
|
state *terminal.State
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTerminal(fd uintptr) bool {
|
||||||
|
return terminal.IsTerminal(int(fd))
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRaw(fd uintptr) (*terminalState, error) {
|
||||||
|
state, err := terminal.MakeRaw(int(fd))
|
||||||
|
|
||||||
|
return &terminalState{
|
||||||
|
state: state,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func restore(fd uintptr, oldState *terminalState) error {
|
||||||
|
return terminal.Restore(int(fd), oldState.state)
|
||||||
|
}
|
69
vendor/github.com/howeyc/gopass/terminal_solaris.go
generated
vendored
Normal file
69
vendor/github.com/howeyc/gopass/terminal_solaris.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* CDDL HEADER START
|
||||||
|
*
|
||||||
|
* The contents of this file are subject to the terms of the
|
||||||
|
* Common Development and Distribution License, Version 1.0 only
|
||||||
|
* (the "License"). You may not use this file except in compliance
|
||||||
|
* with the License.
|
||||||
|
*
|
||||||
|
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||||
|
* or http://www.opensolaris.org/os/licensing.
|
||||||
|
* See the License for the specific language governing permissions
|
||||||
|
* and limitations under the License.
|
||||||
|
*
|
||||||
|
* When distributing Covered Code, include this CDDL HEADER in each
|
||||||
|
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||||
|
* If applicable, add the following below this CDDL HEADER, with the
|
||||||
|
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||||
|
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||||
|
*
|
||||||
|
* CDDL HEADER END
|
||||||
|
*/
|
||||||
|
// Below is derived from Solaris source, so CDDL license is included.
|
||||||
|
|
||||||
|
package gopass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type terminalState struct {
|
||||||
|
state *unix.Termios
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTerminal returns true if there is a terminal attached to the given
|
||||||
|
// file descriptor.
|
||||||
|
// Source: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c
|
||||||
|
func isTerminal(fd uintptr) bool {
|
||||||
|
var termio unix.Termio
|
||||||
|
err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeRaw puts the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
// Source: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libast/common/uwin/getpass.c
|
||||||
|
func makeRaw(fd uintptr) (*terminalState, error) {
|
||||||
|
oldTermiosPtr, err := unix.IoctlGetTermios(int(fd), unix.TCGETS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
oldTermios := *oldTermiosPtr
|
||||||
|
|
||||||
|
newTermios := oldTermios
|
||||||
|
newTermios.Lflag &^= syscall.ECHO | syscall.ECHOE | syscall.ECHOK | syscall.ECHONL
|
||||||
|
if err := unix.IoctlSetTermios(int(fd), unix.TCSETS, &newTermios); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &terminalState{
|
||||||
|
state: oldTermiosPtr,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func restore(fd uintptr, oldState *terminalState) error {
|
||||||
|
return unix.IoctlSetTermios(int(fd), unix.TCSETS, oldState.state)
|
||||||
|
}
|
13
vendor/github.com/inconshreveable/go-update/LICENSE
generated
vendored
Normal file
13
vendor/github.com/inconshreveable/go-update/LICENSE
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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.
|
65
vendor/github.com/inconshreveable/go-update/README.md
generated
vendored
Normal file
65
vendor/github.com/inconshreveable/go-update/README.md
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# go-update: Build self-updating Go programs [](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
|
322
vendor/github.com/inconshreveable/go-update/apply.go
generated
vendored
Normal file
322
vendor/github.com/inconshreveable/go-update/apply.go
generated
vendored
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
package update
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/inconshreveable/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
|
||||||
|
}
|
172
vendor/github.com/inconshreveable/go-update/doc.go
generated
vendored
Normal file
172
vendor/github.com/inconshreveable/go-update/doc.go
generated
vendored
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
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
|
7
vendor/github.com/inconshreveable/go-update/hide_noop.go
generated
vendored
Normal file
7
vendor/github.com/inconshreveable/go-update/hide_noop.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package update
|
||||||
|
|
||||||
|
func hideFile(path string) error {
|
||||||
|
return nil
|
||||||
|
}
|
19
vendor/github.com/inconshreveable/go-update/hide_windows.go
generated
vendored
Normal file
19
vendor/github.com/inconshreveable/go-update/hide_windows.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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/inconshreveable/go-update/internal/binarydist/License
generated
vendored
Normal file
22
vendor/github.com/inconshreveable/go-update/internal/binarydist/License
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
Copyright 2012 Keith Rarick
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
7
vendor/github.com/inconshreveable/go-update/internal/binarydist/Readme.md
generated
vendored
Normal file
7
vendor/github.com/inconshreveable/go-update/internal/binarydist/Readme.md
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# 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/inconshreveable/go-update/internal/binarydist/bzip2.go
generated
vendored
Normal file
40
vendor/github.com/inconshreveable/go-update/internal/binarydist/bzip2.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package binarydist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type bzip2Writer struct {
|
||||||
|
c *exec.Cmd
|
||||||
|
w io.WriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w bzip2Writer) Write(b []byte) (int, error) {
|
||||||
|
return w.w.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w bzip2Writer) Close() error {
|
||||||
|
if err := w.w.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return w.c.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Package compress/bzip2 implements only decompression,
|
||||||
|
// so we'll fake it by running bzip2 in another process.
|
||||||
|
func newBzip2Writer(w io.Writer) (wc io.WriteCloser, err error) {
|
||||||
|
var bw bzip2Writer
|
||||||
|
bw.c = exec.Command("bzip2", "-c")
|
||||||
|
bw.c.Stdout = w
|
||||||
|
|
||||||
|
if bw.w, err = bw.c.StdinPipe(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bw.c.Start(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bw, nil
|
||||||
|
}
|
408
vendor/github.com/inconshreveable/go-update/internal/binarydist/diff.go
generated
vendored
Normal file
408
vendor/github.com/inconshreveable/go-update/internal/binarydist/diff.go
generated
vendored
Normal file
@ -0,0 +1,408 @@
|
|||||||
|
package binarydist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func swap(a []int, i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
|
||||||
|
func split(I, V []int, start, length, h int) {
|
||||||
|
var i, j, k, x, jj, kk int
|
||||||
|
|
||||||
|
if length < 16 {
|
||||||
|
for k = start; k < start+length; k += j {
|
||||||
|
j = 1
|
||||||
|
x = V[I[k]+h]
|
||||||
|
for i = 1; k+i < start+length; i++ {
|
||||||
|
if V[I[k+i]+h] < x {
|
||||||
|
x = V[I[k+i]+h]
|
||||||
|
j = 0
|
||||||
|
}
|
||||||
|
if V[I[k+i]+h] == x {
|
||||||
|
swap(I, k+i, k+j)
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i = 0; i < j; i++ {
|
||||||
|
V[I[k+i]] = k + j - 1
|
||||||
|
}
|
||||||
|
if j == 1 {
|
||||||
|
I[k] = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
x = V[I[start+length/2]+h]
|
||||||
|
jj = 0
|
||||||
|
kk = 0
|
||||||
|
for i = start; i < start+length; i++ {
|
||||||
|
if V[I[i]+h] < x {
|
||||||
|
jj++
|
||||||
|
}
|
||||||
|
if V[I[i]+h] == x {
|
||||||
|
kk++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jj += start
|
||||||
|
kk += jj
|
||||||
|
|
||||||
|
i = start
|
||||||
|
j = 0
|
||||||
|
k = 0
|
||||||
|
for i < jj {
|
||||||
|
if V[I[i]+h] < x {
|
||||||
|
i++
|
||||||
|
} else if V[I[i]+h] == x {
|
||||||
|
swap(I, i, jj+j)
|
||||||
|
j++
|
||||||
|
} else {
|
||||||
|
swap(I, i, kk+k)
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for jj+j < kk {
|
||||||
|
if V[I[jj+j]+h] == x {
|
||||||
|
j++
|
||||||
|
} else {
|
||||||
|
swap(I, jj+j, kk+k)
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if jj > start {
|
||||||
|
split(I, V, start, jj-start, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 0; i < kk-jj; i++ {
|
||||||
|
V[I[jj+i]] = kk - 1
|
||||||
|
}
|
||||||
|
if jj == kk-1 {
|
||||||
|
I[jj] = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if start+length > kk {
|
||||||
|
split(I, V, kk, start+length-kk, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func qsufsort(obuf []byte) []int {
|
||||||
|
var buckets [256]int
|
||||||
|
var i, h int
|
||||||
|
I := make([]int, len(obuf)+1)
|
||||||
|
V := make([]int, len(obuf)+1)
|
||||||
|
|
||||||
|
for _, c := range obuf {
|
||||||
|
buckets[c]++
|
||||||
|
}
|
||||||
|
for i = 1; i < 256; i++ {
|
||||||
|
buckets[i] += buckets[i-1]
|
||||||
|
}
|
||||||
|
copy(buckets[1:], buckets[:])
|
||||||
|
buckets[0] = 0
|
||||||
|
|
||||||
|
for i, c := range obuf {
|
||||||
|
buckets[c]++
|
||||||
|
I[buckets[c]] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
I[0] = len(obuf)
|
||||||
|
for i, c := range obuf {
|
||||||
|
V[i] = buckets[c]
|
||||||
|
}
|
||||||
|
|
||||||
|
V[len(obuf)] = 0
|
||||||
|
for i = 1; i < 256; i++ {
|
||||||
|
if buckets[i] == buckets[i-1]+1 {
|
||||||
|
I[buckets[i]] = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
I[0] = -1
|
||||||
|
|
||||||
|
for h = 1; I[0] != -(len(obuf) + 1); h += h {
|
||||||
|
var n int
|
||||||
|
for i = 0; i < len(obuf)+1; {
|
||||||
|
if I[i] < 0 {
|
||||||
|
n -= I[i]
|
||||||
|
i -= I[i]
|
||||||
|
} else {
|
||||||
|
if n != 0 {
|
||||||
|
I[i-n] = -n
|
||||||
|
}
|
||||||
|
n = V[I[i]] + 1 - i
|
||||||
|
split(I, V, i, n, h)
|
||||||
|
i += n
|
||||||
|
n = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n != 0 {
|
||||||
|
I[i-n] = -n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 0; i < len(obuf)+1; i++ {
|
||||||
|
I[V[i]] = i
|
||||||
|
}
|
||||||
|
return I
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchlen(a, b []byte) (i int) {
|
||||||
|
for i < len(a) && i < len(b) && a[i] == b[i] {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func search(I []int, obuf, nbuf []byte, st, en int) (pos, n int) {
|
||||||
|
if en-st < 2 {
|
||||||
|
x := matchlen(obuf[I[st]:], nbuf)
|
||||||
|
y := matchlen(obuf[I[en]:], nbuf)
|
||||||
|
|
||||||
|
if x > y {
|
||||||
|
return I[st], x
|
||||||
|
} else {
|
||||||
|
return I[en], y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x := st + (en-st)/2
|
||||||
|
if bytes.Compare(obuf[I[x]:], nbuf) < 0 {
|
||||||
|
return search(I, obuf, nbuf, x, en)
|
||||||
|
} else {
|
||||||
|
return search(I, obuf, nbuf, st, x)
|
||||||
|
}
|
||||||
|
panic("unreached")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diff computes the difference between old and new, according to the bsdiff
|
||||||
|
// algorithm, and writes the result to patch.
|
||||||
|
func Diff(old, new io.Reader, patch io.Writer) error {
|
||||||
|
obuf, err := ioutil.ReadAll(old)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nbuf, err := ioutil.ReadAll(new)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pbuf, err := diffBytes(obuf, nbuf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = patch.Write(pbuf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func diffBytes(obuf, nbuf []byte) ([]byte, error) {
|
||||||
|
var patch seekBuffer
|
||||||
|
err := diff(obuf, nbuf, &patch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return patch.buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func diff(obuf, nbuf []byte, patch io.WriteSeeker) error {
|
||||||
|
var lenf int
|
||||||
|
I := qsufsort(obuf)
|
||||||
|
db := make([]byte, len(nbuf))
|
||||||
|
eb := make([]byte, len(nbuf))
|
||||||
|
var dblen, eblen int
|
||||||
|
|
||||||
|
var hdr header
|
||||||
|
hdr.Magic = magic
|
||||||
|
hdr.NewSize = int64(len(nbuf))
|
||||||
|
err := binary.Write(patch, signMagLittleEndian{}, &hdr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the differences, writing ctrl as we go
|
||||||
|
pfbz2, err := newBzip2Writer(patch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var scan, pos, length int
|
||||||
|
var lastscan, lastpos, lastoffset int
|
||||||
|
for scan < len(nbuf) {
|
||||||
|
var oldscore int
|
||||||
|
scan += length
|
||||||
|
for scsc := scan; scan < len(nbuf); scan++ {
|
||||||
|
pos, length = search(I, obuf, nbuf[scan:], 0, len(obuf))
|
||||||
|
|
||||||
|
for ; scsc < scan+length; scsc++ {
|
||||||
|
if scsc+lastoffset < len(obuf) &&
|
||||||
|
obuf[scsc+lastoffset] == nbuf[scsc] {
|
||||||
|
oldscore++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length == oldscore && length != 0) || length > oldscore+8 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if scan+lastoffset < len(obuf) && obuf[scan+lastoffset] == nbuf[scan] {
|
||||||
|
oldscore--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if length != oldscore || scan == len(nbuf) {
|
||||||
|
var s, Sf int
|
||||||
|
lenf = 0
|
||||||
|
for i := 0; lastscan+i < scan && lastpos+i < len(obuf); {
|
||||||
|
if obuf[lastpos+i] == nbuf[lastscan+i] {
|
||||||
|
s++
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
if s*2-i > Sf*2-lenf {
|
||||||
|
Sf = s
|
||||||
|
lenf = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lenb := 0
|
||||||
|
if scan < len(nbuf) {
|
||||||
|
var s, Sb int
|
||||||
|
for i := 1; (scan >= lastscan+i) && (pos >= i); i++ {
|
||||||
|
if obuf[pos-i] == nbuf[scan-i] {
|
||||||
|
s++
|
||||||
|
}
|
||||||
|
if s*2-i > Sb*2-lenb {
|
||||||
|
Sb = s
|
||||||
|
lenb = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastscan+lenf > scan-lenb {
|
||||||
|
overlap := (lastscan + lenf) - (scan - lenb)
|
||||||
|
s := 0
|
||||||
|
Ss := 0
|
||||||
|
lens := 0
|
||||||
|
for i := 0; i < overlap; i++ {
|
||||||
|
if nbuf[lastscan+lenf-overlap+i] == obuf[lastpos+lenf-overlap+i] {
|
||||||
|
s++
|
||||||
|
}
|
||||||
|
if nbuf[scan-lenb+i] == obuf[pos-lenb+i] {
|
||||||
|
s--
|
||||||
|
}
|
||||||
|
if s > Ss {
|
||||||
|
Ss = s
|
||||||
|
lens = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lenf += lens - overlap
|
||||||
|
lenb -= lens
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < lenf; i++ {
|
||||||
|
db[dblen+i] = nbuf[lastscan+i] - obuf[lastpos+i]
|
||||||
|
}
|
||||||
|
for i := 0; i < (scan-lenb)-(lastscan+lenf); i++ {
|
||||||
|
eb[eblen+i] = nbuf[lastscan+lenf+i]
|
||||||
|
}
|
||||||
|
|
||||||
|
dblen += lenf
|
||||||
|
eblen += (scan - lenb) - (lastscan + lenf)
|
||||||
|
|
||||||
|
err = binary.Write(pfbz2, signMagLittleEndian{}, int64(lenf))
|
||||||
|
if err != nil {
|
||||||
|
pfbz2.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
val := (scan - lenb) - (lastscan + lenf)
|
||||||
|
err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val))
|
||||||
|
if err != nil {
|
||||||
|
pfbz2.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
val = (pos - lenb) - (lastpos + lenf)
|
||||||
|
err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val))
|
||||||
|
if err != nil {
|
||||||
|
pfbz2.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lastscan = scan - lenb
|
||||||
|
lastpos = pos - lenb
|
||||||
|
lastoffset = pos - scan
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = pfbz2.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute size of compressed ctrl data
|
||||||
|
l64, err := patch.Seek(0, 1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hdr.CtrlLen = int64(l64 - 32)
|
||||||
|
|
||||||
|
// Write compressed diff data
|
||||||
|
pfbz2, err = newBzip2Writer(patch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n, err := pfbz2.Write(db[:dblen])
|
||||||
|
if err != nil {
|
||||||
|
pfbz2.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != dblen {
|
||||||
|
pfbz2.Close()
|
||||||
|
return io.ErrShortWrite
|
||||||
|
}
|
||||||
|
err = pfbz2.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute size of compressed diff data
|
||||||
|
n64, err := patch.Seek(0, 1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hdr.DiffLen = n64 - l64
|
||||||
|
|
||||||
|
// Write compressed extra data
|
||||||
|
pfbz2, err = newBzip2Writer(patch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n, err = pfbz2.Write(eb[:eblen])
|
||||||
|
if err != nil {
|
||||||
|
pfbz2.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != eblen {
|
||||||
|
pfbz2.Close()
|
||||||
|
return io.ErrShortWrite
|
||||||
|
}
|
||||||
|
err = pfbz2.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek to the beginning, write the header, and close the file
|
||||||
|
_, err = patch.Seek(0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = binary.Write(patch, signMagLittleEndian{}, &hdr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
24
vendor/github.com/inconshreveable/go-update/internal/binarydist/doc.go
generated
vendored
Normal file
24
vendor/github.com/inconshreveable/go-update/internal/binarydist/doc.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Package binarydist implements binary diff and patch as described on
|
||||||
|
// http://www.daemonology.net/bsdiff/. It reads and writes files
|
||||||
|
// compatible with the tools there.
|
||||||
|
package binarydist
|
||||||
|
|
||||||
|
var magic = [8]byte{'B', 'S', 'D', 'I', 'F', 'F', '4', '0'}
|
||||||
|
|
||||||
|
// File format:
|
||||||
|
// 0 8 "BSDIFF40"
|
||||||
|
// 8 8 X
|
||||||
|
// 16 8 Y
|
||||||
|
// 24 8 sizeof(newfile)
|
||||||
|
// 32 X bzip2(control block)
|
||||||
|
// 32+X Y bzip2(diff block)
|
||||||
|
// 32+X+Y ??? bzip2(extra block)
|
||||||
|
// with control block a set of triples (x,y,z) meaning "add x bytes
|
||||||
|
// from oldfile to x bytes from the diff block; copy y bytes from the
|
||||||
|
// extra block; seek forwards in oldfile by z bytes".
|
||||||
|
type header struct {
|
||||||
|
Magic [8]byte
|
||||||
|
CtrlLen int64
|
||||||
|
DiffLen int64
|
||||||
|
NewSize int64
|
||||||
|
}
|
53
vendor/github.com/inconshreveable/go-update/internal/binarydist/encoding.go
generated
vendored
Normal file
53
vendor/github.com/inconshreveable/go-update/internal/binarydist/encoding.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package binarydist
|
||||||
|
|
||||||
|
// SignMagLittleEndian is the numeric encoding used by the bsdiff tools.
|
||||||
|
// It implements binary.ByteOrder using a sign-magnitude format
|
||||||
|
// and little-endian byte order. Only methods Uint64 and String
|
||||||
|
// have been written; the rest panic.
|
||||||
|
type signMagLittleEndian struct{}
|
||||||
|
|
||||||
|
func (signMagLittleEndian) Uint16(b []byte) uint16 { panic("unimplemented") }
|
||||||
|
|
||||||
|
func (signMagLittleEndian) PutUint16(b []byte, v uint16) { panic("unimplemented") }
|
||||||
|
|
||||||
|
func (signMagLittleEndian) Uint32(b []byte) uint32 { panic("unimplemented") }
|
||||||
|
|
||||||
|
func (signMagLittleEndian) PutUint32(b []byte, v uint32) { panic("unimplemented") }
|
||||||
|
|
||||||
|
func (signMagLittleEndian) Uint64(b []byte) uint64 {
|
||||||
|
y := int64(b[0]) |
|
||||||
|
int64(b[1])<<8 |
|
||||||
|
int64(b[2])<<16 |
|
||||||
|
int64(b[3])<<24 |
|
||||||
|
int64(b[4])<<32 |
|
||||||
|
int64(b[5])<<40 |
|
||||||
|
int64(b[6])<<48 |
|
||||||
|
int64(b[7]&0x7f)<<56
|
||||||
|
|
||||||
|
if b[7]&0x80 != 0 {
|
||||||
|
y = -y
|
||||||
|
}
|
||||||
|
return uint64(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (signMagLittleEndian) PutUint64(b []byte, v uint64) {
|
||||||
|
x := int64(v)
|
||||||
|
neg := x < 0
|
||||||
|
if neg {
|
||||||
|
x = -x
|
||||||
|
}
|
||||||
|
|
||||||
|
b[0] = byte(x)
|
||||||
|
b[1] = byte(x >> 8)
|
||||||
|
b[2] = byte(x >> 16)
|
||||||
|
b[3] = byte(x >> 24)
|
||||||
|
b[4] = byte(x >> 32)
|
||||||
|
b[5] = byte(x >> 40)
|
||||||
|
b[6] = byte(x >> 48)
|
||||||
|
b[7] = byte(x >> 56)
|
||||||
|
if neg {
|
||||||
|
b[7] |= 0x80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (signMagLittleEndian) String() string { return "signMagLittleEndian" }
|
109
vendor/github.com/inconshreveable/go-update/internal/binarydist/patch.go
generated
vendored
Normal file
109
vendor/github.com/inconshreveable/go-update/internal/binarydist/patch.go
generated
vendored
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package binarydist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/bzip2"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrCorrupt = errors.New("corrupt patch")
|
||||||
|
|
||||||
|
// Patch applies patch to old, according to the bspatch algorithm,
|
||||||
|
// and writes the result to new.
|
||||||
|
func Patch(old io.Reader, new io.Writer, patch io.Reader) error {
|
||||||
|
var hdr header
|
||||||
|
err := binary.Read(patch, signMagLittleEndian{}, &hdr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if hdr.Magic != magic {
|
||||||
|
return ErrCorrupt
|
||||||
|
}
|
||||||
|
if hdr.CtrlLen < 0 || hdr.DiffLen < 0 || hdr.NewSize < 0 {
|
||||||
|
return ErrCorrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrlbuf := make([]byte, hdr.CtrlLen)
|
||||||
|
_, err = io.ReadFull(patch, ctrlbuf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cpfbz2 := bzip2.NewReader(bytes.NewReader(ctrlbuf))
|
||||||
|
|
||||||
|
diffbuf := make([]byte, hdr.DiffLen)
|
||||||
|
_, err = io.ReadFull(patch, diffbuf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dpfbz2 := bzip2.NewReader(bytes.NewReader(diffbuf))
|
||||||
|
|
||||||
|
// The entire rest of the file is the extra block.
|
||||||
|
epfbz2 := bzip2.NewReader(patch)
|
||||||
|
|
||||||
|
obuf, err := ioutil.ReadAll(old)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nbuf := make([]byte, hdr.NewSize)
|
||||||
|
|
||||||
|
var oldpos, newpos int64
|
||||||
|
for newpos < hdr.NewSize {
|
||||||
|
var ctrl struct{ Add, Copy, Seek int64 }
|
||||||
|
err = binary.Read(cpfbz2, signMagLittleEndian{}, &ctrl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity-check
|
||||||
|
if newpos+ctrl.Add > hdr.NewSize {
|
||||||
|
return ErrCorrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read diff string
|
||||||
|
_, err = io.ReadFull(dpfbz2, nbuf[newpos:newpos+ctrl.Add])
|
||||||
|
if err != nil {
|
||||||
|
return ErrCorrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add old data to diff string
|
||||||
|
for i := int64(0); i < ctrl.Add; i++ {
|
||||||
|
if oldpos+i >= 0 && oldpos+i < int64(len(obuf)) {
|
||||||
|
nbuf[newpos+i] += obuf[oldpos+i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust pointers
|
||||||
|
newpos += ctrl.Add
|
||||||
|
oldpos += ctrl.Add
|
||||||
|
|
||||||
|
// Sanity-check
|
||||||
|
if newpos+ctrl.Copy > hdr.NewSize {
|
||||||
|
return ErrCorrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read extra string
|
||||||
|
_, err = io.ReadFull(epfbz2, nbuf[newpos:newpos+ctrl.Copy])
|
||||||
|
if err != nil {
|
||||||
|
return ErrCorrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust pointers
|
||||||
|
newpos += ctrl.Copy
|
||||||
|
oldpos += ctrl.Seek
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the new file
|
||||||
|
for len(nbuf) > 0 {
|
||||||
|
n, err := new.Write(nbuf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nbuf = nbuf[n:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
43
vendor/github.com/inconshreveable/go-update/internal/binarydist/seek.go
generated
vendored
Normal file
43
vendor/github.com/inconshreveable/go-update/internal/binarydist/seek.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package binarydist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type seekBuffer struct {
|
||||||
|
buf []byte
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *seekBuffer) Write(p []byte) (n int, err error) {
|
||||||
|
n = copy(b.buf[b.pos:], p)
|
||||||
|
if n == len(p) {
|
||||||
|
b.pos += n
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
b.buf = append(b.buf, p[n:]...)
|
||||||
|
b.pos += len(p)
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *seekBuffer) Seek(offset int64, whence int) (ret int64, err error) {
|
||||||
|
var abs int64
|
||||||
|
switch whence {
|
||||||
|
case 0:
|
||||||
|
abs = offset
|
||||||
|
case 1:
|
||||||
|
abs = int64(b.pos) + offset
|
||||||
|
case 2:
|
||||||
|
abs = int64(len(b.buf)) + offset
|
||||||
|
default:
|
||||||
|
return 0, errors.New("binarydist: invalid whence")
|
||||||
|
}
|
||||||
|
if abs < 0 {
|
||||||
|
return 0, errors.New("binarydist: negative position")
|
||||||
|
}
|
||||||
|
if abs >= 1<<31 {
|
||||||
|
return 0, errors.New("binarydist: position out of range")
|
||||||
|
}
|
||||||
|
b.pos = int(abs)
|
||||||
|
return abs, nil
|
||||||
|
}
|
27
vendor/github.com/inconshreveable/go-update/internal/osext/LICENSE
generated
vendored
Normal file
27
vendor/github.com/inconshreveable/go-update/internal/osext/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
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/inconshreveable/go-update/internal/osext/README.md
generated
vendored
Normal file
16
vendor/github.com/inconshreveable/go-update/internal/osext/README.md
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
### 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/inconshreveable/go-update/internal/osext/osext.go
generated
vendored
Normal file
27
vendor/github.com/inconshreveable/go-update/internal/osext/osext.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// 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/inconshreveable/go-update/internal/osext/osext_plan9.go
generated
vendored
Normal file
20
vendor/github.com/inconshreveable/go-update/internal/osext/osext_plan9.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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/inconshreveable/go-update/internal/osext/osext_procfs.go
generated
vendored
Normal file
36
vendor/github.com/inconshreveable/go-update/internal/osext/osext_procfs.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// 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/inconshreveable/go-update/internal/osext/osext_sysctl.go
generated
vendored
Normal file
79
vendor/github.com/inconshreveable/go-update/internal/osext/osext_sysctl.go
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build darwin freebsd
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var initCwd, initCwdErr = os.Getwd()
|
||||||
|
|
||||||
|
func executable() (string, error) {
|
||||||
|
var mib [4]int32
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "freebsd":
|
||||||
|
mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1}
|
||||||
|
case "darwin":
|
||||||
|
mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1}
|
||||||
|
}
|
||||||
|
|
||||||
|
n := uintptr(0)
|
||||||
|
// Get length.
|
||||||
|
_, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0)
|
||||||
|
if errNum != 0 {
|
||||||
|
return "", errNum
|
||||||
|
}
|
||||||
|
if n == 0 { // This shouldn't happen.
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
buf := make([]byte, n)
|
||||||
|
_, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0)
|
||||||
|
if errNum != 0 {
|
||||||
|
return "", errNum
|
||||||
|
}
|
||||||
|
if n == 0 { // This shouldn't happen.
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
for i, v := range buf {
|
||||||
|
if v == 0 {
|
||||||
|
buf = buf[:i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
execPath := string(buf)
|
||||||
|
// execPath will not be empty due to above checks.
|
||||||
|
// Try to get the absolute path if the execPath is not rooted.
|
||||||
|
if execPath[0] != '/' {
|
||||||
|
execPath, err = getAbs(execPath)
|
||||||
|
if err != nil {
|
||||||
|
return execPath, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// For darwin KERN_PROCARGS may return the path to a symlink rather than the
|
||||||
|
// actual executable.
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
if execPath, err = filepath.EvalSymlinks(execPath); err != nil {
|
||||||
|
return execPath, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return execPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAbs(execPath string) (string, error) {
|
||||||
|
if initCwdErr != nil {
|
||||||
|
return execPath, initCwdErr
|
||||||
|
}
|
||||||
|
// The execPath may begin with a "../" or a "./" so clean it first.
|
||||||
|
// Join the two paths, trailing and starting slashes undetermined, so use
|
||||||
|
// the generic Join function.
|
||||||
|
return filepath.Join(initCwd, filepath.Clean(execPath)), nil
|
||||||
|
}
|
34
vendor/github.com/inconshreveable/go-update/internal/osext/osext_windows.go
generated
vendored
Normal file
34
vendor/github.com/inconshreveable/go-update/internal/osext/osext_windows.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unicode/utf16"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
kernel = syscall.MustLoadDLL("kernel32.dll")
|
||||||
|
getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW")
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetModuleFileName() with hModule = NULL
|
||||||
|
func executable() (exePath string, err error) {
|
||||||
|
return getModuleFileName()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getModuleFileName() (string, error) {
|
||||||
|
var n uint32
|
||||||
|
b := make([]uint16, syscall.MAX_PATH)
|
||||||
|
size := uint32(len(b))
|
||||||
|
|
||||||
|
r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size))
|
||||||
|
n = uint32(r0)
|
||||||
|
if n == 0 {
|
||||||
|
return "", e1
|
||||||
|
}
|
||||||
|
return string(utf16.Decode(b[0:n])), nil
|
||||||
|
}
|
24
vendor/github.com/inconshreveable/go-update/patcher.go
generated
vendored
Normal file
24
vendor/github.com/inconshreveable/go-update/patcher.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package update
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/inconshreveable/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)
|
||||||
|
}
|
74
vendor/github.com/inconshreveable/go-update/verifier.go
generated
vendored
Normal file
74
vendor/github.com/inconshreveable/go-update/verifier.go
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
13
vendor/github.com/segmentio/go-prompt/History.md
generated
vendored
Normal file
13
vendor/github.com/segmentio/go-prompt/History.md
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
v1.2.0 / 2014-12-10
|
||||||
|
===================
|
||||||
|
|
||||||
|
* remove appending of ? to Confirm()
|
||||||
|
|
||||||
|
v1.1.0 / 2014-10-22
|
||||||
|
==================
|
||||||
|
|
||||||
|
* add passwords example
|
||||||
|
* add password docs
|
||||||
|
* Merge pull request #2 from nrmitchi/add/gopass
|
||||||
|
* Adding convenience wrappers around howeyc/gopass
|
33
vendor/github.com/segmentio/go-prompt/Readme.md
generated
vendored
Normal file
33
vendor/github.com/segmentio/go-prompt/Readme.md
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
# go-prompt
|
||||||
|
|
||||||
|
Terminal prompts for Go.
|
||||||
|
|
||||||
|
View the [docs](http://godoc.org/pkg/github.com/segmentio/go-prompt).
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/segmentio/go-prompt"
|
||||||
|
|
||||||
|
var langs = []string{
|
||||||
|
"c",
|
||||||
|
"c++",
|
||||||
|
"lua",
|
||||||
|
"go",
|
||||||
|
"js",
|
||||||
|
"ruby",
|
||||||
|
"python",
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
i := prompt.Choose("What's your favorite language?", langs)
|
||||||
|
println("picked: " + langs[i])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
94
vendor/github.com/segmentio/go-prompt/prompt.go
generated
vendored
Normal file
94
vendor/github.com/segmentio/go-prompt/prompt.go
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package prompt
|
||||||
|
|
||||||
|
import "github.com/howeyc/gopass"
|
||||||
|
import "strings"
|
||||||
|
import "strconv"
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// String prompt.
|
||||||
|
func String(prompt string, args ...interface{}) string {
|
||||||
|
var s string
|
||||||
|
fmt.Printf(prompt+": ", args...)
|
||||||
|
fmt.Scanln(&s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// String prompt (required).
|
||||||
|
func StringRequired(prompt string, args ...interface{}) (s string) {
|
||||||
|
for strings.Trim(s, " ") == "" {
|
||||||
|
s = String(prompt, args...)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm continues prompting until the input is boolean-ish.
|
||||||
|
func Confirm(prompt string, args ...interface{}) bool {
|
||||||
|
for {
|
||||||
|
switch String(prompt, args...) {
|
||||||
|
case "Yes", "yes", "y", "Y":
|
||||||
|
return true
|
||||||
|
case "No", "no", "n", "N":
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose prompts for a single selection from `list`, returning in the index.
|
||||||
|
func Choose(prompt string, list []string) int {
|
||||||
|
fmt.Println()
|
||||||
|
for i, val := range list {
|
||||||
|
fmt.Printf(" %d) %s\n", i+1, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
i := -1
|
||||||
|
|
||||||
|
for {
|
||||||
|
s := String(prompt)
|
||||||
|
|
||||||
|
// index
|
||||||
|
n, err := strconv.Atoi(s)
|
||||||
|
if err == nil {
|
||||||
|
if n > 0 && n <= len(list) {
|
||||||
|
i = n - 1
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// value
|
||||||
|
i = indexOf(s, list)
|
||||||
|
if i != -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Password prompt.
|
||||||
|
func Password(prompt string, args ...interface{}) string {
|
||||||
|
fmt.Printf(prompt+": ", args...)
|
||||||
|
password, _ := gopass.GetPasswd()
|
||||||
|
s := string(password[0:])
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Password prompt with mask.
|
||||||
|
func PasswordMasked(prompt string, args ...interface{}) string {
|
||||||
|
fmt.Printf(prompt+": ", args...)
|
||||||
|
password, _ := gopass.GetPasswdMasked()
|
||||||
|
s := string(password[0:])
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// index of `s` in `list`.
|
||||||
|
func indexOf(s string, list []string) int {
|
||||||
|
for i, val := range list {
|
||||||
|
if val == s {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
951
vendor/golang.org/x/crypto/ssh/terminal/terminal.go
generated
vendored
Normal file
951
vendor/golang.org/x/crypto/ssh/terminal/terminal.go
generated
vendored
Normal file
@ -0,0 +1,951 @@
|
|||||||
|
// Copyright 2011 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 terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EscapeCodes contains escape sequences that can be written to the terminal in
|
||||||
|
// order to achieve different styles of text.
|
||||||
|
type EscapeCodes struct {
|
||||||
|
// Foreground colors
|
||||||
|
Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte
|
||||||
|
|
||||||
|
// Reset all attributes
|
||||||
|
Reset []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var vt100EscapeCodes = EscapeCodes{
|
||||||
|
Black: []byte{keyEscape, '[', '3', '0', 'm'},
|
||||||
|
Red: []byte{keyEscape, '[', '3', '1', 'm'},
|
||||||
|
Green: []byte{keyEscape, '[', '3', '2', 'm'},
|
||||||
|
Yellow: []byte{keyEscape, '[', '3', '3', 'm'},
|
||||||
|
Blue: []byte{keyEscape, '[', '3', '4', 'm'},
|
||||||
|
Magenta: []byte{keyEscape, '[', '3', '5', 'm'},
|
||||||
|
Cyan: []byte{keyEscape, '[', '3', '6', 'm'},
|
||||||
|
White: []byte{keyEscape, '[', '3', '7', 'm'},
|
||||||
|
|
||||||
|
Reset: []byte{keyEscape, '[', '0', 'm'},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminal contains the state for running a VT100 terminal that is capable of
|
||||||
|
// reading lines of input.
|
||||||
|
type Terminal struct {
|
||||||
|
// AutoCompleteCallback, if non-null, is called for each keypress with
|
||||||
|
// the full input line and the current position of the cursor (in
|
||||||
|
// bytes, as an index into |line|). If it returns ok=false, the key
|
||||||
|
// press is processed normally. Otherwise it returns a replacement line
|
||||||
|
// and the new cursor position.
|
||||||
|
AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool)
|
||||||
|
|
||||||
|
// Escape contains a pointer to the escape codes for this terminal.
|
||||||
|
// It's always a valid pointer, although the escape codes themselves
|
||||||
|
// may be empty if the terminal doesn't support them.
|
||||||
|
Escape *EscapeCodes
|
||||||
|
|
||||||
|
// lock protects the terminal and the state in this object from
|
||||||
|
// concurrent processing of a key press and a Write() call.
|
||||||
|
lock sync.Mutex
|
||||||
|
|
||||||
|
c io.ReadWriter
|
||||||
|
prompt []rune
|
||||||
|
|
||||||
|
// line is the current line being entered.
|
||||||
|
line []rune
|
||||||
|
// pos is the logical position of the cursor in line
|
||||||
|
pos int
|
||||||
|
// echo is true if local echo is enabled
|
||||||
|
echo bool
|
||||||
|
// pasteActive is true iff there is a bracketed paste operation in
|
||||||
|
// progress.
|
||||||
|
pasteActive bool
|
||||||
|
|
||||||
|
// cursorX contains the current X value of the cursor where the left
|
||||||
|
// edge is 0. cursorY contains the row number where the first row of
|
||||||
|
// the current line is 0.
|
||||||
|
cursorX, cursorY int
|
||||||
|
// maxLine is the greatest value of cursorY so far.
|
||||||
|
maxLine int
|
||||||
|
|
||||||
|
termWidth, termHeight int
|
||||||
|
|
||||||
|
// outBuf contains the terminal data to be sent.
|
||||||
|
outBuf []byte
|
||||||
|
// remainder contains the remainder of any partial key sequences after
|
||||||
|
// a read. It aliases into inBuf.
|
||||||
|
remainder []byte
|
||||||
|
inBuf [256]byte
|
||||||
|
|
||||||
|
// history contains previously entered commands so that they can be
|
||||||
|
// accessed with the up and down keys.
|
||||||
|
history stRingBuffer
|
||||||
|
// historyIndex stores the currently accessed history entry, where zero
|
||||||
|
// means the immediately previous entry.
|
||||||
|
historyIndex int
|
||||||
|
// When navigating up and down the history it's possible to return to
|
||||||
|
// the incomplete, initial line. That value is stored in
|
||||||
|
// historyPending.
|
||||||
|
historyPending string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
|
||||||
|
// a local terminal, that terminal must first have been put into raw mode.
|
||||||
|
// prompt is a string that is written at the start of each input line (i.e.
|
||||||
|
// "> ").
|
||||||
|
func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
|
||||||
|
return &Terminal{
|
||||||
|
Escape: &vt100EscapeCodes,
|
||||||
|
c: c,
|
||||||
|
prompt: []rune(prompt),
|
||||||
|
termWidth: 80,
|
||||||
|
termHeight: 24,
|
||||||
|
echo: true,
|
||||||
|
historyIndex: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
keyCtrlD = 4
|
||||||
|
keyCtrlU = 21
|
||||||
|
keyEnter = '\r'
|
||||||
|
keyEscape = 27
|
||||||
|
keyBackspace = 127
|
||||||
|
keyUnknown = 0xd800 /* UTF-16 surrogate area */ + iota
|
||||||
|
keyUp
|
||||||
|
keyDown
|
||||||
|
keyLeft
|
||||||
|
keyRight
|
||||||
|
keyAltLeft
|
||||||
|
keyAltRight
|
||||||
|
keyHome
|
||||||
|
keyEnd
|
||||||
|
keyDeleteWord
|
||||||
|
keyDeleteLine
|
||||||
|
keyClearScreen
|
||||||
|
keyPasteStart
|
||||||
|
keyPasteEnd
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
crlf = []byte{'\r', '\n'}
|
||||||
|
pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'}
|
||||||
|
pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'}
|
||||||
|
)
|
||||||
|
|
||||||
|
// bytesToKey tries to parse a key sequence from b. If successful, it returns
|
||||||
|
// the key and the remainder of the input. Otherwise it returns utf8.RuneError.
|
||||||
|
func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return utf8.RuneError, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pasteActive {
|
||||||
|
switch b[0] {
|
||||||
|
case 1: // ^A
|
||||||
|
return keyHome, b[1:]
|
||||||
|
case 5: // ^E
|
||||||
|
return keyEnd, b[1:]
|
||||||
|
case 8: // ^H
|
||||||
|
return keyBackspace, b[1:]
|
||||||
|
case 11: // ^K
|
||||||
|
return keyDeleteLine, b[1:]
|
||||||
|
case 12: // ^L
|
||||||
|
return keyClearScreen, b[1:]
|
||||||
|
case 23: // ^W
|
||||||
|
return keyDeleteWord, b[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b[0] != keyEscape {
|
||||||
|
if !utf8.FullRune(b) {
|
||||||
|
return utf8.RuneError, b
|
||||||
|
}
|
||||||
|
r, l := utf8.DecodeRune(b)
|
||||||
|
return r, b[l:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' {
|
||||||
|
switch b[2] {
|
||||||
|
case 'A':
|
||||||
|
return keyUp, b[3:]
|
||||||
|
case 'B':
|
||||||
|
return keyDown, b[3:]
|
||||||
|
case 'C':
|
||||||
|
return keyRight, b[3:]
|
||||||
|
case 'D':
|
||||||
|
return keyLeft, b[3:]
|
||||||
|
case 'H':
|
||||||
|
return keyHome, b[3:]
|
||||||
|
case 'F':
|
||||||
|
return keyEnd, b[3:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
|
||||||
|
switch b[5] {
|
||||||
|
case 'C':
|
||||||
|
return keyAltRight, b[6:]
|
||||||
|
case 'D':
|
||||||
|
return keyAltLeft, b[6:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) {
|
||||||
|
return keyPasteStart, b[6:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) {
|
||||||
|
return keyPasteEnd, b[6:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here then we have a key that we don't recognise, or a
|
||||||
|
// partial sequence. It's not clear how one should find the end of a
|
||||||
|
// sequence without knowing them all, but it seems that [a-zA-Z~] only
|
||||||
|
// appears at the end of a sequence.
|
||||||
|
for i, c := range b[0:] {
|
||||||
|
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' {
|
||||||
|
return keyUnknown, b[i+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return utf8.RuneError, b
|
||||||
|
}
|
||||||
|
|
||||||
|
// queue appends data to the end of t.outBuf
|
||||||
|
func (t *Terminal) queue(data []rune) {
|
||||||
|
t.outBuf = append(t.outBuf, []byte(string(data))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var eraseUnderCursor = []rune{' ', keyEscape, '[', 'D'}
|
||||||
|
var space = []rune{' '}
|
||||||
|
|
||||||
|
func isPrintable(key rune) bool {
|
||||||
|
isInSurrogateArea := key >= 0xd800 && key <= 0xdbff
|
||||||
|
return key >= 32 && !isInSurrogateArea
|
||||||
|
}
|
||||||
|
|
||||||
|
// moveCursorToPos appends data to t.outBuf which will move the cursor to the
|
||||||
|
// given, logical position in the text.
|
||||||
|
func (t *Terminal) moveCursorToPos(pos int) {
|
||||||
|
if !t.echo {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
x := visualLength(t.prompt) + pos
|
||||||
|
y := x / t.termWidth
|
||||||
|
x = x % t.termWidth
|
||||||
|
|
||||||
|
up := 0
|
||||||
|
if y < t.cursorY {
|
||||||
|
up = t.cursorY - y
|
||||||
|
}
|
||||||
|
|
||||||
|
down := 0
|
||||||
|
if y > t.cursorY {
|
||||||
|
down = y - t.cursorY
|
||||||
|
}
|
||||||
|
|
||||||
|
left := 0
|
||||||
|
if x < t.cursorX {
|
||||||
|
left = t.cursorX - x
|
||||||
|
}
|
||||||
|
|
||||||
|
right := 0
|
||||||
|
if x > t.cursorX {
|
||||||
|
right = x - t.cursorX
|
||||||
|
}
|
||||||
|
|
||||||
|
t.cursorX = x
|
||||||
|
t.cursorY = y
|
||||||
|
t.move(up, down, left, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) move(up, down, left, right int) {
|
||||||
|
movement := make([]rune, 3*(up+down+left+right))
|
||||||
|
m := movement
|
||||||
|
for i := 0; i < up; i++ {
|
||||||
|
m[0] = keyEscape
|
||||||
|
m[1] = '['
|
||||||
|
m[2] = 'A'
|
||||||
|
m = m[3:]
|
||||||
|
}
|
||||||
|
for i := 0; i < down; i++ {
|
||||||
|
m[0] = keyEscape
|
||||||
|
m[1] = '['
|
||||||
|
m[2] = 'B'
|
||||||
|
m = m[3:]
|
||||||
|
}
|
||||||
|
for i := 0; i < left; i++ {
|
||||||
|
m[0] = keyEscape
|
||||||
|
m[1] = '['
|
||||||
|
m[2] = 'D'
|
||||||
|
m = m[3:]
|
||||||
|
}
|
||||||
|
for i := 0; i < right; i++ {
|
||||||
|
m[0] = keyEscape
|
||||||
|
m[1] = '['
|
||||||
|
m[2] = 'C'
|
||||||
|
m = m[3:]
|
||||||
|
}
|
||||||
|
|
||||||
|
t.queue(movement)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) clearLineToRight() {
|
||||||
|
op := []rune{keyEscape, '[', 'K'}
|
||||||
|
t.queue(op)
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxLineLength = 4096
|
||||||
|
|
||||||
|
func (t *Terminal) setLine(newLine []rune, newPos int) {
|
||||||
|
if t.echo {
|
||||||
|
t.moveCursorToPos(0)
|
||||||
|
t.writeLine(newLine)
|
||||||
|
for i := len(newLine); i < len(t.line); i++ {
|
||||||
|
t.writeLine(space)
|
||||||
|
}
|
||||||
|
t.moveCursorToPos(newPos)
|
||||||
|
}
|
||||||
|
t.line = newLine
|
||||||
|
t.pos = newPos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) advanceCursor(places int) {
|
||||||
|
t.cursorX += places
|
||||||
|
t.cursorY += t.cursorX / t.termWidth
|
||||||
|
if t.cursorY > t.maxLine {
|
||||||
|
t.maxLine = t.cursorY
|
||||||
|
}
|
||||||
|
t.cursorX = t.cursorX % t.termWidth
|
||||||
|
|
||||||
|
if places > 0 && t.cursorX == 0 {
|
||||||
|
// Normally terminals will advance the current position
|
||||||
|
// when writing a character. But that doesn't happen
|
||||||
|
// for the last character in a line. However, when
|
||||||
|
// writing a character (except a new line) that causes
|
||||||
|
// a line wrap, the position will be advanced two
|
||||||
|
// places.
|
||||||
|
//
|
||||||
|
// So, if we are stopping at the end of a line, we
|
||||||
|
// need to write a newline so that our cursor can be
|
||||||
|
// advanced to the next line.
|
||||||
|
t.outBuf = append(t.outBuf, '\r', '\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) eraseNPreviousChars(n int) {
|
||||||
|
if n == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.pos < n {
|
||||||
|
n = t.pos
|
||||||
|
}
|
||||||
|
t.pos -= n
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
|
||||||
|
copy(t.line[t.pos:], t.line[n+t.pos:])
|
||||||
|
t.line = t.line[:len(t.line)-n]
|
||||||
|
if t.echo {
|
||||||
|
t.writeLine(t.line[t.pos:])
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
t.queue(space)
|
||||||
|
}
|
||||||
|
t.advanceCursor(n)
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// countToLeftWord returns then number of characters from the cursor to the
|
||||||
|
// start of the previous word.
|
||||||
|
func (t *Terminal) countToLeftWord() int {
|
||||||
|
if t.pos == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := t.pos - 1
|
||||||
|
for pos > 0 {
|
||||||
|
if t.line[pos] != ' ' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pos--
|
||||||
|
}
|
||||||
|
for pos > 0 {
|
||||||
|
if t.line[pos] == ' ' {
|
||||||
|
pos++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pos--
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.pos - pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// countToRightWord returns then number of characters from the cursor to the
|
||||||
|
// start of the next word.
|
||||||
|
func (t *Terminal) countToRightWord() int {
|
||||||
|
pos := t.pos
|
||||||
|
for pos < len(t.line) {
|
||||||
|
if t.line[pos] == ' ' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
for pos < len(t.line) {
|
||||||
|
if t.line[pos] != ' ' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
return pos - t.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// visualLength returns the number of visible glyphs in s.
|
||||||
|
func visualLength(runes []rune) int {
|
||||||
|
inEscapeSeq := false
|
||||||
|
length := 0
|
||||||
|
|
||||||
|
for _, r := range runes {
|
||||||
|
switch {
|
||||||
|
case inEscapeSeq:
|
||||||
|
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
|
||||||
|
inEscapeSeq = false
|
||||||
|
}
|
||||||
|
case r == '\x1b':
|
||||||
|
inEscapeSeq = true
|
||||||
|
default:
|
||||||
|
length++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return length
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleKey processes the given key and, optionally, returns a line of text
|
||||||
|
// that the user has entered.
|
||||||
|
func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
||||||
|
if t.pasteActive && key != keyEnter {
|
||||||
|
t.addKeyToLine(key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case keyBackspace:
|
||||||
|
if t.pos == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.eraseNPreviousChars(1)
|
||||||
|
case keyAltLeft:
|
||||||
|
// move left by a word.
|
||||||
|
t.pos -= t.countToLeftWord()
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyAltRight:
|
||||||
|
// move right by a word.
|
||||||
|
t.pos += t.countToRightWord()
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyLeft:
|
||||||
|
if t.pos == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.pos--
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyRight:
|
||||||
|
if t.pos == len(t.line) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.pos++
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyHome:
|
||||||
|
if t.pos == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.pos = 0
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyEnd:
|
||||||
|
if t.pos == len(t.line) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.pos = len(t.line)
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyUp:
|
||||||
|
entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1)
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
if t.historyIndex == -1 {
|
||||||
|
t.historyPending = string(t.line)
|
||||||
|
}
|
||||||
|
t.historyIndex++
|
||||||
|
runes := []rune(entry)
|
||||||
|
t.setLine(runes, len(runes))
|
||||||
|
case keyDown:
|
||||||
|
switch t.historyIndex {
|
||||||
|
case -1:
|
||||||
|
return
|
||||||
|
case 0:
|
||||||
|
runes := []rune(t.historyPending)
|
||||||
|
t.setLine(runes, len(runes))
|
||||||
|
t.historyIndex--
|
||||||
|
default:
|
||||||
|
entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1)
|
||||||
|
if ok {
|
||||||
|
t.historyIndex--
|
||||||
|
runes := []rune(entry)
|
||||||
|
t.setLine(runes, len(runes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case keyEnter:
|
||||||
|
t.moveCursorToPos(len(t.line))
|
||||||
|
t.queue([]rune("\r\n"))
|
||||||
|
line = string(t.line)
|
||||||
|
ok = true
|
||||||
|
t.line = t.line[:0]
|
||||||
|
t.pos = 0
|
||||||
|
t.cursorX = 0
|
||||||
|
t.cursorY = 0
|
||||||
|
t.maxLine = 0
|
||||||
|
case keyDeleteWord:
|
||||||
|
// Delete zero or more spaces and then one or more characters.
|
||||||
|
t.eraseNPreviousChars(t.countToLeftWord())
|
||||||
|
case keyDeleteLine:
|
||||||
|
// Delete everything from the current cursor position to the
|
||||||
|
// end of line.
|
||||||
|
for i := t.pos; i < len(t.line); i++ {
|
||||||
|
t.queue(space)
|
||||||
|
t.advanceCursor(1)
|
||||||
|
}
|
||||||
|
t.line = t.line[:t.pos]
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyCtrlD:
|
||||||
|
// Erase the character under the current position.
|
||||||
|
// The EOF case when the line is empty is handled in
|
||||||
|
// readLine().
|
||||||
|
if t.pos < len(t.line) {
|
||||||
|
t.pos++
|
||||||
|
t.eraseNPreviousChars(1)
|
||||||
|
}
|
||||||
|
case keyCtrlU:
|
||||||
|
t.eraseNPreviousChars(t.pos)
|
||||||
|
case keyClearScreen:
|
||||||
|
// Erases the screen and moves the cursor to the home position.
|
||||||
|
t.queue([]rune("\x1b[2J\x1b[H"))
|
||||||
|
t.queue(t.prompt)
|
||||||
|
t.cursorX, t.cursorY = 0, 0
|
||||||
|
t.advanceCursor(visualLength(t.prompt))
|
||||||
|
t.setLine(t.line, t.pos)
|
||||||
|
default:
|
||||||
|
if t.AutoCompleteCallback != nil {
|
||||||
|
prefix := string(t.line[:t.pos])
|
||||||
|
suffix := string(t.line[t.pos:])
|
||||||
|
|
||||||
|
t.lock.Unlock()
|
||||||
|
newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key)
|
||||||
|
t.lock.Lock()
|
||||||
|
|
||||||
|
if completeOk {
|
||||||
|
t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isPrintable(key) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(t.line) == maxLineLength {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.addKeyToLine(key)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// addKeyToLine inserts the given key at the current position in the current
|
||||||
|
// line.
|
||||||
|
func (t *Terminal) addKeyToLine(key rune) {
|
||||||
|
if len(t.line) == cap(t.line) {
|
||||||
|
newLine := make([]rune, len(t.line), 2*(1+len(t.line)))
|
||||||
|
copy(newLine, t.line)
|
||||||
|
t.line = newLine
|
||||||
|
}
|
||||||
|
t.line = t.line[:len(t.line)+1]
|
||||||
|
copy(t.line[t.pos+1:], t.line[t.pos:])
|
||||||
|
t.line[t.pos] = key
|
||||||
|
if t.echo {
|
||||||
|
t.writeLine(t.line[t.pos:])
|
||||||
|
}
|
||||||
|
t.pos++
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) writeLine(line []rune) {
|
||||||
|
for len(line) != 0 {
|
||||||
|
remainingOnLine := t.termWidth - t.cursorX
|
||||||
|
todo := len(line)
|
||||||
|
if todo > remainingOnLine {
|
||||||
|
todo = remainingOnLine
|
||||||
|
}
|
||||||
|
t.queue(line[:todo])
|
||||||
|
t.advanceCursor(visualLength(line[:todo]))
|
||||||
|
line = line[todo:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeWithCRLF writes buf to w but replaces all occurrences of \n with \r\n.
|
||||||
|
func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) {
|
||||||
|
for len(buf) > 0 {
|
||||||
|
i := bytes.IndexByte(buf, '\n')
|
||||||
|
todo := len(buf)
|
||||||
|
if i >= 0 {
|
||||||
|
todo = i
|
||||||
|
}
|
||||||
|
|
||||||
|
var nn int
|
||||||
|
nn, err = w.Write(buf[:todo])
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
buf = buf[todo:]
|
||||||
|
|
||||||
|
if i >= 0 {
|
||||||
|
if _, err = w.Write(crlf); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
n += 1
|
||||||
|
buf = buf[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) Write(buf []byte) (n int, err error) {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
if t.cursorX == 0 && t.cursorY == 0 {
|
||||||
|
// This is the easy case: there's nothing on the screen that we
|
||||||
|
// have to move out of the way.
|
||||||
|
return writeWithCRLF(t.c, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have a prompt and possibly user input on the screen. We
|
||||||
|
// have to clear it first.
|
||||||
|
t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */)
|
||||||
|
t.cursorX = 0
|
||||||
|
t.clearLineToRight()
|
||||||
|
|
||||||
|
for t.cursorY > 0 {
|
||||||
|
t.move(1 /* up */, 0, 0, 0)
|
||||||
|
t.cursorY--
|
||||||
|
t.clearLineToRight()
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = t.c.Write(t.outBuf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.outBuf = t.outBuf[:0]
|
||||||
|
|
||||||
|
if n, err = writeWithCRLF(t.c, buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.writeLine(t.prompt)
|
||||||
|
if t.echo {
|
||||||
|
t.writeLine(t.line)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
|
||||||
|
if _, err = t.c.Write(t.outBuf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.outBuf = t.outBuf[:0]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPassword temporarily changes the prompt and reads a password, without
|
||||||
|
// echo, from the terminal.
|
||||||
|
func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
oldPrompt := t.prompt
|
||||||
|
t.prompt = []rune(prompt)
|
||||||
|
t.echo = false
|
||||||
|
|
||||||
|
line, err = t.readLine()
|
||||||
|
|
||||||
|
t.prompt = oldPrompt
|
||||||
|
t.echo = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadLine returns a line of input from the terminal.
|
||||||
|
func (t *Terminal) ReadLine() (line string, err error) {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
return t.readLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) readLine() (line string, err error) {
|
||||||
|
// t.lock must be held at this point
|
||||||
|
|
||||||
|
if t.cursorX == 0 && t.cursorY == 0 {
|
||||||
|
t.writeLine(t.prompt)
|
||||||
|
t.c.Write(t.outBuf)
|
||||||
|
t.outBuf = t.outBuf[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
lineIsPasted := t.pasteActive
|
||||||
|
|
||||||
|
for {
|
||||||
|
rest := t.remainder
|
||||||
|
lineOk := false
|
||||||
|
for !lineOk {
|
||||||
|
var key rune
|
||||||
|
key, rest = bytesToKey(rest, t.pasteActive)
|
||||||
|
if key == utf8.RuneError {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !t.pasteActive {
|
||||||
|
if key == keyCtrlD {
|
||||||
|
if len(t.line) == 0 {
|
||||||
|
return "", io.EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if key == keyPasteStart {
|
||||||
|
t.pasteActive = true
|
||||||
|
if len(t.line) == 0 {
|
||||||
|
lineIsPasted = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if key == keyPasteEnd {
|
||||||
|
t.pasteActive = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !t.pasteActive {
|
||||||
|
lineIsPasted = false
|
||||||
|
}
|
||||||
|
line, lineOk = t.handleKey(key)
|
||||||
|
}
|
||||||
|
if len(rest) > 0 {
|
||||||
|
n := copy(t.inBuf[:], rest)
|
||||||
|
t.remainder = t.inBuf[:n]
|
||||||
|
} else {
|
||||||
|
t.remainder = nil
|
||||||
|
}
|
||||||
|
t.c.Write(t.outBuf)
|
||||||
|
t.outBuf = t.outBuf[:0]
|
||||||
|
if lineOk {
|
||||||
|
if t.echo {
|
||||||
|
t.historyIndex = -1
|
||||||
|
t.history.Add(line)
|
||||||
|
}
|
||||||
|
if lineIsPasted {
|
||||||
|
err = ErrPasteIndicator
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// t.remainder is a slice at the beginning of t.inBuf
|
||||||
|
// containing a partial key sequence
|
||||||
|
readBuf := t.inBuf[len(t.remainder):]
|
||||||
|
var n int
|
||||||
|
|
||||||
|
t.lock.Unlock()
|
||||||
|
n, err = t.c.Read(readBuf)
|
||||||
|
t.lock.Lock()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.remainder = t.inBuf[:n+len(t.remainder)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPrompt sets the prompt to be used when reading subsequent lines.
|
||||||
|
func (t *Terminal) SetPrompt(prompt string) {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
t.prompt = []rune(prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) {
|
||||||
|
// Move cursor to column zero at the start of the line.
|
||||||
|
t.move(t.cursorY, 0, t.cursorX, 0)
|
||||||
|
t.cursorX, t.cursorY = 0, 0
|
||||||
|
t.clearLineToRight()
|
||||||
|
for t.cursorY < numPrevLines {
|
||||||
|
// Move down a line
|
||||||
|
t.move(0, 1, 0, 0)
|
||||||
|
t.cursorY++
|
||||||
|
t.clearLineToRight()
|
||||||
|
}
|
||||||
|
// Move back to beginning.
|
||||||
|
t.move(t.cursorY, 0, 0, 0)
|
||||||
|
t.cursorX, t.cursorY = 0, 0
|
||||||
|
|
||||||
|
t.queue(t.prompt)
|
||||||
|
t.advanceCursor(visualLength(t.prompt))
|
||||||
|
t.writeLine(t.line)
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) SetSize(width, height int) error {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
if width == 0 {
|
||||||
|
width = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
oldWidth := t.termWidth
|
||||||
|
t.termWidth, t.termHeight = width, height
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case width == oldWidth:
|
||||||
|
// If the width didn't change then nothing else needs to be
|
||||||
|
// done.
|
||||||
|
return nil
|
||||||
|
case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0:
|
||||||
|
// If there is nothing on current line and no prompt printed,
|
||||||
|
// just do nothing
|
||||||
|
return nil
|
||||||
|
case width < oldWidth:
|
||||||
|
// Some terminals (e.g. xterm) will truncate lines that were
|
||||||
|
// too long when shinking. Others, (e.g. gnome-terminal) will
|
||||||
|
// attempt to wrap them. For the former, repainting t.maxLine
|
||||||
|
// works great, but that behaviour goes badly wrong in the case
|
||||||
|
// of the latter because they have doubled every full line.
|
||||||
|
|
||||||
|
// We assume that we are working on a terminal that wraps lines
|
||||||
|
// and adjust the cursor position based on every previous line
|
||||||
|
// wrapping and turning into two. This causes the prompt on
|
||||||
|
// xterms to move upwards, which isn't great, but it avoids a
|
||||||
|
// huge mess with gnome-terminal.
|
||||||
|
if t.cursorX >= t.termWidth {
|
||||||
|
t.cursorX = t.termWidth - 1
|
||||||
|
}
|
||||||
|
t.cursorY *= 2
|
||||||
|
t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2)
|
||||||
|
case width > oldWidth:
|
||||||
|
// If the terminal expands then our position calculations will
|
||||||
|
// be wrong in the future because we think the cursor is
|
||||||
|
// |t.pos| chars into the string, but there will be a gap at
|
||||||
|
// the end of any wrapped line.
|
||||||
|
//
|
||||||
|
// But the position will actually be correct until we move, so
|
||||||
|
// we can move back to the beginning and repaint everything.
|
||||||
|
t.clearAndRepaintLinePlusNPrevious(t.maxLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := t.c.Write(t.outBuf)
|
||||||
|
t.outBuf = t.outBuf[:0]
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type pasteIndicatorError struct{}
|
||||||
|
|
||||||
|
func (pasteIndicatorError) Error() string {
|
||||||
|
return "terminal: ErrPasteIndicator not correctly handled"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrPasteIndicator may be returned from ReadLine as the error, in addition
|
||||||
|
// to valid line data. It indicates that bracketed paste mode is enabled and
|
||||||
|
// that the returned line consists only of pasted data. Programs may wish to
|
||||||
|
// interpret pasted data more literally than typed data.
|
||||||
|
var ErrPasteIndicator = pasteIndicatorError{}
|
||||||
|
|
||||||
|
// SetBracketedPasteMode requests that the terminal bracket paste operations
|
||||||
|
// with markers. Not all terminals support this but, if it is supported, then
|
||||||
|
// enabling this mode will stop any autocomplete callback from running due to
|
||||||
|
// pastes. Additionally, any lines that are completely pasted will be returned
|
||||||
|
// from ReadLine with the error set to ErrPasteIndicator.
|
||||||
|
func (t *Terminal) SetBracketedPasteMode(on bool) {
|
||||||
|
if on {
|
||||||
|
io.WriteString(t.c, "\x1b[?2004h")
|
||||||
|
} else {
|
||||||
|
io.WriteString(t.c, "\x1b[?2004l")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stRingBuffer is a ring buffer of strings.
|
||||||
|
type stRingBuffer struct {
|
||||||
|
// entries contains max elements.
|
||||||
|
entries []string
|
||||||
|
max int
|
||||||
|
// head contains the index of the element most recently added to the ring.
|
||||||
|
head int
|
||||||
|
// size contains the number of elements in the ring.
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stRingBuffer) Add(a string) {
|
||||||
|
if s.entries == nil {
|
||||||
|
const defaultNumEntries = 100
|
||||||
|
s.entries = make([]string, defaultNumEntries)
|
||||||
|
s.max = defaultNumEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
s.head = (s.head + 1) % s.max
|
||||||
|
s.entries[s.head] = a
|
||||||
|
if s.size < s.max {
|
||||||
|
s.size++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NthPreviousEntry returns the value passed to the nth previous call to Add.
|
||||||
|
// If n is zero then the immediately prior value is returned, if one, then the
|
||||||
|
// next most recent, and so on. If such an element doesn't exist then ok is
|
||||||
|
// false.
|
||||||
|
func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) {
|
||||||
|
if n >= s.size {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
index := s.head - n
|
||||||
|
if index < 0 {
|
||||||
|
index += s.max
|
||||||
|
}
|
||||||
|
return s.entries[index], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// readPasswordLine reads from reader until it finds \n or io.EOF.
|
||||||
|
// The slice returned does not include the \n.
|
||||||
|
// readPasswordLine also ignores any \r it finds.
|
||||||
|
func readPasswordLine(reader io.Reader) ([]byte, error) {
|
||||||
|
var buf [1]byte
|
||||||
|
var ret []byte
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, err := reader.Read(buf[:])
|
||||||
|
if n > 0 {
|
||||||
|
switch buf[0] {
|
||||||
|
case '\n':
|
||||||
|
return ret, nil
|
||||||
|
case '\r':
|
||||||
|
// remove \r from passwords on Windows
|
||||||
|
default:
|
||||||
|
ret = append(ret, buf[0])
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF && len(ret) > 0 {
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
119
vendor/golang.org/x/crypto/ssh/terminal/util.go
generated
vendored
Normal file
119
vendor/golang.org/x/crypto/ssh/terminal/util.go
generated
vendored
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
// Copyright 2011 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 dragonfly freebsd linux,!appengine netbsd openbsd
|
||||||
|
|
||||||
|
// Package terminal provides support functions for dealing with terminals, as
|
||||||
|
// commonly found on UNIX systems.
|
||||||
|
//
|
||||||
|
// Putting a terminal into raw mode is the most common requirement:
|
||||||
|
//
|
||||||
|
// oldState, err := terminal.MakeRaw(0)
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// defer terminal.Restore(0, oldState)
|
||||||
|
package terminal // import "golang.org/x/crypto/ssh/terminal"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// State contains the state of a terminal.
|
||||||
|
type State struct {
|
||||||
|
termios syscall.Termios
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd int) bool {
|
||||||
|
var termios syscall.Termios
|
||||||
|
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||||
|
return err == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd int) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState.termios
|
||||||
|
// This attempts to replicate the behaviour documented for cfmakeraw in
|
||||||
|
// the termios(3) manpage.
|
||||||
|
newState.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON
|
||||||
|
newState.Oflag &^= syscall.OPOST
|
||||||
|
newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN
|
||||||
|
newState.Cflag &^= syscall.CSIZE | syscall.PARENB
|
||||||
|
newState.Cflag |= syscall.CS8
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetState returns the current state of a terminal which may be useful to
|
||||||
|
// restore the terminal after a signal.
|
||||||
|
func GetState(fd int) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
|
// previous state.
|
||||||
|
func Restore(fd int, state *State) error {
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSize returns the dimensions of the given terminal.
|
||||||
|
func GetSize(fd int) (width, height int, err error) {
|
||||||
|
var dimensions [4]uint16
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0); err != 0 {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
return int(dimensions[1]), int(dimensions[0]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// passwordReader is an io.Reader that reads from a specific file descriptor.
|
||||||
|
type passwordReader int
|
||||||
|
|
||||||
|
func (r passwordReader) Read(buf []byte) (int, error) {
|
||||||
|
return syscall.Read(int(r), buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||||
|
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||||
|
// returned does not include the \n.
|
||||||
|
func ReadPassword(fd int) ([]byte, error) {
|
||||||
|
var oldState syscall.Termios
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState
|
||||||
|
newState.Lflag &^= syscall.ECHO
|
||||||
|
newState.Lflag |= syscall.ICANON | syscall.ISIG
|
||||||
|
newState.Iflag |= syscall.ICRNL
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return readPasswordLine(passwordReader(fd))
|
||||||
|
}
|
12
vendor/golang.org/x/crypto/ssh/terminal/util_bsd.go
generated
vendored
Normal file
12
vendor/golang.org/x/crypto/ssh/terminal/util_bsd.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright 2013 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 dragonfly freebsd netbsd openbsd
|
||||||
|
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const ioctlReadTermios = syscall.TIOCGETA
|
||||||
|
const ioctlWriteTermios = syscall.TIOCSETA
|
11
vendor/golang.org/x/crypto/ssh/terminal/util_linux.go
generated
vendored
Normal file
11
vendor/golang.org/x/crypto/ssh/terminal/util_linux.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// Copyright 2013 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 terminal
|
||||||
|
|
||||||
|
// These constants are declared here, rather than importing
|
||||||
|
// them from the syscall package as some syscall packages, even
|
||||||
|
// on linux, for example gccgo, do not declare them.
|
||||||
|
const ioctlReadTermios = 0x5401 // syscall.TCGETS
|
||||||
|
const ioctlWriteTermios = 0x5402 // syscall.TCSETS
|
58
vendor/golang.org/x/crypto/ssh/terminal/util_plan9.go
generated
vendored
Normal file
58
vendor/golang.org/x/crypto/ssh/terminal/util_plan9.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// Copyright 2016 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 terminal provides support functions for dealing with terminals, as
|
||||||
|
// commonly found on UNIX systems.
|
||||||
|
//
|
||||||
|
// Putting a terminal into raw mode is the most common requirement:
|
||||||
|
//
|
||||||
|
// oldState, err := terminal.MakeRaw(0)
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// defer terminal.Restore(0, oldState)
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type State struct{}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd int) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd int) (*State, error) {
|
||||||
|
return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetState returns the current state of a terminal which may be useful to
|
||||||
|
// restore the terminal after a signal.
|
||||||
|
func GetState(fd int) (*State, error) {
|
||||||
|
return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
|
// previous state.
|
||||||
|
func Restore(fd int, state *State) error {
|
||||||
|
return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSize returns the dimensions of the given terminal.
|
||||||
|
func GetSize(fd int) (width, height int, err error) {
|
||||||
|
return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||||
|
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||||
|
// returned does not include the \n.
|
||||||
|
func ReadPassword(fd int) ([]byte, error) {
|
||||||
|
return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
128
vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go
generated
vendored
Normal file
128
vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go
generated
vendored
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// Copyright 2015 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 solaris
|
||||||
|
|
||||||
|
package terminal // import "golang.org/x/crypto/ssh/terminal"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"io"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// State contains the state of a terminal.
|
||||||
|
type State struct {
|
||||||
|
state *unix.Termios
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd int) bool {
|
||||||
|
_, err := unix.IoctlGetTermio(fd, unix.TCGETA)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||||
|
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||||
|
// returned does not include the \n.
|
||||||
|
func ReadPassword(fd int) ([]byte, error) {
|
||||||
|
// see also: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libast/common/uwin/getpass.c
|
||||||
|
val, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
oldState := *val
|
||||||
|
|
||||||
|
newState := oldState
|
||||||
|
newState.Lflag &^= syscall.ECHO
|
||||||
|
newState.Lflag |= syscall.ICANON | syscall.ISIG
|
||||||
|
newState.Iflag |= syscall.ICRNL
|
||||||
|
err = unix.IoctlSetTermios(fd, unix.TCSETS, &newState)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer unix.IoctlSetTermios(fd, unix.TCSETS, &oldState)
|
||||||
|
|
||||||
|
var buf [16]byte
|
||||||
|
var ret []byte
|
||||||
|
for {
|
||||||
|
n, err := syscall.Read(fd, buf[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
if len(ret) == 0 {
|
||||||
|
return nil, io.EOF
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if buf[n-1] == '\n' {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
ret = append(ret, buf[:n]...)
|
||||||
|
if n < len(buf) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw puts the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
// see http://cr.illumos.org/~webrev/andy_js/1060/
|
||||||
|
func MakeRaw(fd int) (*State, error) {
|
||||||
|
oldTermiosPtr, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
oldTermios := *oldTermiosPtr
|
||||||
|
|
||||||
|
newTermios := oldTermios
|
||||||
|
newTermios.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON
|
||||||
|
newTermios.Oflag &^= syscall.OPOST
|
||||||
|
newTermios.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN
|
||||||
|
newTermios.Cflag &^= syscall.CSIZE | syscall.PARENB
|
||||||
|
newTermios.Cflag |= syscall.CS8
|
||||||
|
newTermios.Cc[unix.VMIN] = 1
|
||||||
|
newTermios.Cc[unix.VTIME] = 0
|
||||||
|
|
||||||
|
if err := unix.IoctlSetTermios(fd, unix.TCSETS, &newTermios); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &State{
|
||||||
|
state: oldTermiosPtr,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
|
// previous state.
|
||||||
|
func Restore(fd int, oldState *State) error {
|
||||||
|
return unix.IoctlSetTermios(fd, unix.TCSETS, oldState.state)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetState returns the current state of a terminal which may be useful to
|
||||||
|
// restore the terminal after a signal.
|
||||||
|
func GetState(fd int) (*State, error) {
|
||||||
|
oldTermiosPtr, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &State{
|
||||||
|
state: oldTermiosPtr,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSize returns the dimensions of the given terminal.
|
||||||
|
func GetSize(fd int) (width, height int, err error) {
|
||||||
|
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
return int(ws.Col), int(ws.Row), nil
|
||||||
|
}
|
155
vendor/golang.org/x/crypto/ssh/terminal/util_windows.go
generated
vendored
Normal file
155
vendor/golang.org/x/crypto/ssh/terminal/util_windows.go
generated
vendored
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
// Copyright 2011 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 windows
|
||||||
|
|
||||||
|
// Package terminal provides support functions for dealing with terminals, as
|
||||||
|
// commonly found on UNIX systems.
|
||||||
|
//
|
||||||
|
// Putting a terminal into raw mode is the most common requirement:
|
||||||
|
//
|
||||||
|
// oldState, err := terminal.MakeRaw(0)
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// defer terminal.Restore(0, oldState)
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
enableLineInput = 2
|
||||||
|
enableEchoInput = 4
|
||||||
|
enableProcessedInput = 1
|
||||||
|
enableWindowInput = 8
|
||||||
|
enableMouseInput = 16
|
||||||
|
enableInsertMode = 32
|
||||||
|
enableQuickEditMode = 64
|
||||||
|
enableExtendedFlags = 128
|
||||||
|
enableAutoPosition = 256
|
||||||
|
enableProcessedOutput = 1
|
||||||
|
enableWrapAtEolOutput = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
|
||||||
|
var (
|
||||||
|
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||||
|
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
|
||||||
|
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
short int16
|
||||||
|
word uint16
|
||||||
|
|
||||||
|
coord struct {
|
||||||
|
x short
|
||||||
|
y short
|
||||||
|
}
|
||||||
|
smallRect struct {
|
||||||
|
left short
|
||||||
|
top short
|
||||||
|
right short
|
||||||
|
bottom short
|
||||||
|
}
|
||||||
|
consoleScreenBufferInfo struct {
|
||||||
|
size coord
|
||||||
|
cursorPosition coord
|
||||||
|
attributes word
|
||||||
|
window smallRect
|
||||||
|
maximumWindowSize coord
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
mode uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd int) bool {
|
||||||
|
var st uint32
|
||||||
|
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||||
|
return r != 0 && e == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd int) (*State, error) {
|
||||||
|
var st uint32
|
||||||
|
_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||||
|
if e != 0 {
|
||||||
|
return nil, error(e)
|
||||||
|
}
|
||||||
|
raw := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput)
|
||||||
|
_, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(raw), 0)
|
||||||
|
if e != 0 {
|
||||||
|
return nil, error(e)
|
||||||
|
}
|
||||||
|
return &State{st}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetState returns the current state of a terminal which may be useful to
|
||||||
|
// restore the terminal after a signal.
|
||||||
|
func GetState(fd int) (*State, error) {
|
||||||
|
var st uint32
|
||||||
|
_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||||
|
if e != 0 {
|
||||||
|
return nil, error(e)
|
||||||
|
}
|
||||||
|
return &State{st}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
|
// previous state.
|
||||||
|
func Restore(fd int, state *State) error {
|
||||||
|
_, _, err := syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(state.mode), 0)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSize returns the dimensions of the given terminal.
|
||||||
|
func GetSize(fd int) (width, height int, err error) {
|
||||||
|
var info consoleScreenBufferInfo
|
||||||
|
_, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&info)), 0)
|
||||||
|
if e != 0 {
|
||||||
|
return 0, 0, error(e)
|
||||||
|
}
|
||||||
|
return int(info.size.x), int(info.size.y), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// passwordReader is an io.Reader that reads from a specific Windows HANDLE.
|
||||||
|
type passwordReader int
|
||||||
|
|
||||||
|
func (r passwordReader) Read(buf []byte) (int, error) {
|
||||||
|
return syscall.Read(syscall.Handle(r), buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||||
|
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||||
|
// returned does not include the \n.
|
||||||
|
func ReadPassword(fd int) ([]byte, error) {
|
||||||
|
var st uint32
|
||||||
|
_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||||
|
if e != 0 {
|
||||||
|
return nil, error(e)
|
||||||
|
}
|
||||||
|
old := st
|
||||||
|
|
||||||
|
st &^= (enableEchoInput)
|
||||||
|
st |= (enableProcessedInput | enableLineInput | enableProcessedOutput)
|
||||||
|
_, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0)
|
||||||
|
if e != 0 {
|
||||||
|
return nil, error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return readPasswordLine(passwordReader(fd))
|
||||||
|
}
|
30
vendor/vendor.json
vendored
30
vendor/vendor.json
vendored
@ -255,6 +255,30 @@
|
|||||||
"revision": "7e3c02b30806fa5779d3bdfc152ce4c6f40e7b38",
|
"revision": "7e3c02b30806fa5779d3bdfc152ce4c6f40e7b38",
|
||||||
"revisionTime": "2016-01-19T13:13:26-08:00"
|
"revisionTime": "2016-01-19T13:13:26-08:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "K6exl2ouL7d8cR2i378EzZOdRVI=",
|
||||||
|
"path": "github.com/howeyc/gopass",
|
||||||
|
"revision": "bf9dde6d0d2c004a008c27aaee91170c786f6db8",
|
||||||
|
"revisionTime": "2017-01-09T16:22:49Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "YjZiQ5N552dJ7GpJ2P4TTRu4PKI=",
|
||||||
|
"path": "github.com/inconshreveable/go-update",
|
||||||
|
"revision": "8152e7eb6ccf8679a64582a66b78519688d156ad",
|
||||||
|
"revisionTime": "2016-01-12T19:33:35Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "LeFW+ad/zlj30+Z0O4wwnXrVUSQ=",
|
||||||
|
"path": "github.com/inconshreveable/go-update/internal/binarydist",
|
||||||
|
"revision": "8152e7eb6ccf8679a64582a66b78519688d156ad",
|
||||||
|
"revisionTime": "2016-01-12T19:33:35Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "H63+DW/S2nASpHk59G1wEUZD1qk=",
|
||||||
|
"path": "github.com/inconshreveable/go-update/internal/osext",
|
||||||
|
"revision": "8152e7eb6ccf8679a64582a66b78519688d156ad",
|
||||||
|
"revisionTime": "2016-01-12T19:33:35Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "github.com/klauspost/cpuid",
|
"path": "github.com/klauspost/cpuid",
|
||||||
"revision": "349c675778172472f5e8f3a3e0fe187e302e5a10",
|
"revision": "349c675778172472f5e8f3a3e0fe187e302e5a10",
|
||||||
@ -464,6 +488,12 @@
|
|||||||
"revision": "5bf94b69c6b68ee1b541973bb8e1144db23a194b",
|
"revision": "5bf94b69c6b68ee1b541973bb8e1144db23a194b",
|
||||||
"revisionTime": "2017-03-21T23:07:31Z"
|
"revisionTime": "2017-03-21T23:07:31Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "llmzhtIUy63V3Pl65RuEn18ck5g=",
|
||||||
|
"path": "github.com/segmentio/go-prompt",
|
||||||
|
"revision": "f0d19b6901ade831d5a3204edc0d6a7d6457fbb2",
|
||||||
|
"revisionTime": "2016-10-17T23:32:05Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "u0hXGADM3JDza8YjgiyNJpAJk8g=",
|
"checksumSHA1": "u0hXGADM3JDza8YjgiyNJpAJk8g=",
|
||||||
"path": "github.com/skyrings/skyring-common/tools/uuid",
|
"path": "github.com/skyrings/skyring-common/tools/uuid",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user