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 #4781master
parent
8c08571cd9
commit
eb7c690ea9
@ -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. |
@ -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. |
@ -0,0 +1,27 @@ |
||||
# getpasswd in Go [![GoDoc](https://godoc.org/github.com/howeyc/gopass?status.svg)](https://godoc.org/github.com/howeyc/gopass) [![Build Status](https://secure.travis-ci.org/howeyc/gopass.png?branch=master)](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! |
@ -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) |
||||
} |
@ -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) |
||||
} |
@ -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) |
||||
} |
@ -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. |
@ -0,0 +1,65 @@ |
||||
# go-update: Build self-updating Go programs [![godoc reference](https://godoc.org/github.com/inconshreveable/go-update?status.png)](https://godoc.org/github.com/inconshreveable/go-update) |
||||
|
||||
Package update provides functionality to implement secure, self-updating Go programs (or other single-file targets) |
||||
A program can update itself by replacing its executable file with a new version. |
||||
|
||||
It provides the flexibility to implement different updating user experiences |
||||
like auto-updating, or manual user-initiated updates. It also boasts |
||||
advanced features like binary patching and code signing verification. |
||||
|
||||
Example of updating from a URL: |
||||
|
||||
```go |
||||
import ( |
||||
"fmt" |
||||
"net/http" |
||||
|
||||
"github.com/inconshreveable/go-update" |
||||
) |
||||
|
||||
func doUpdate(url string) error { |
||||
resp, err := http.Get(url) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer resp.Body.Close() |
||||
err := update.Apply(resp.Body, update.Options{}) |
||||
if err != nil { |
||||
// error handling |
||||
} |
||||
return err |
||||
} |
||||
``` |
||||
|
||||
## Features |
||||
|
||||
- Cross platform support (Windows too!) |
||||
- Binary patch application |
||||
- Checksum verification |
||||
- Code signing verification |
||||
- Support for updating arbitrary files |
||||
|
||||
## [equinox.io](https://equinox.io) |
||||
[equinox.io](https://equinox.io) is a complete ready-to-go updating solution built on top of go-update that provides: |
||||
|
||||
- Hosted updates |
||||
- Update channels (stable, beta, nightly, ...) |
||||
- Dynamically computed binary diffs |
||||
- Automatic key generation and code |
||||
- Release tooling with proper code signing |
||||
- Update/download metrics |
||||
|
||||
## API Compatibility Promises |
||||
The master branch of `go-update` is *not* guaranteed to have a stable API over time. For any production application, you should vendor |
||||
your dependency on `go-update` with a tool like git submodules, [gb](http://getgb.io/) or [govendor](https://github.com/kardianos/govendor). |
||||
|
||||
The `go-update` package makes the following promises about API compatibility: |
||||
1. A list of all API-breaking changes will be documented in this README. |
||||
1. `go-update` will strive for as few API-breaking changes as possible. |
||||
|
||||
## API Breaking Changes |
||||
- **Sept 3, 2015**: The `Options` struct passed to `Apply` was changed to be passed by value instead of passed by pointer. Old API at `28de026`. |
||||
- **Aug 9, 2015**: 2.0 API. Old API at `221d034` or `gopkg.in/inconshreveable/go-update.v0`. |
||||
|
||||
## License |
||||
Apache |
@ -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 |
||||
} |
@ -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 |
@ -0,0 +1,7 @@ |
||||
// +build !windows
|
||||
|
||||
package update |
||||
|
||||
func hideFile(path string) error { |
||||
return nil |
||||
} |
@ -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 |
||||
} |
||||
} |
@ -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. |
@ -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>. |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -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" } |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -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. |
@ -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. |
@ -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 |
||||
} |
@ -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())) |
||||
} |
@ -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) |
||||
} |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -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) |
||||
} |
@ -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 |
||||
}) |
||||
} |
@ -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 |
@ -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 |
@ -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 |
||||
} |
@ -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 |
||||
} |
||||
} |
||||
} |
@ -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)) |
||||
} |
@ -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 |
@ -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
|
@ -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) |
||||
} |
@ -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 |
||||
} |
@ -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)) |
||||
} |
Loading…
Reference in new issue