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