Merge pull request #619 from restic/update-deps

Update all vendored dependencies
This commit is contained in:
Alexander Neumann 2016-09-15 22:50:44 +02:00
commit 309dca8179
135 changed files with 14670 additions and 3186 deletions

View file

@ -13,8 +13,8 @@ import (
)
type CmdCheck struct {
ReadData bool `long:"read-data" default:"false" description:"Read data blobs"`
CheckUnused bool `long:"check-unused" default:"false" description:"Check for unused blobs"`
ReadData bool `long:"read-data" description:"Read data blobs"`
CheckUnused bool `long:"check-unused" description:"Check for unused blobs"`
global *GlobalOptions
}

View file

@ -17,7 +17,7 @@ import (
)
type CmdMount struct {
Root bool `long:"owner-root" description:"use 'root' as the owner of files and dirs" default:"false"`
Root bool `long:"owner-root" description:"use 'root' as the owner of files and dirs"`
global *GlobalOptions
}

View file

@ -32,8 +32,8 @@ type GlobalOptions struct {
Repo string `short:"r" long:"repo" description:"Repository directory to backup to/restore from"`
PasswordFile string `short:"p" long:"password-file" description:"Read the repository password from a file"`
CacheDir string ` long:"cache-dir" description:"Directory to use as a local cache"`
Quiet bool `short:"q" long:"quiet" default:"false" description:"Do not output comprehensive progress report"`
NoLock bool ` long:"no-lock" default:"false" description:"Do not lock the repo, this allows some operations on read-only repos."`
Quiet bool `short:"q" long:"quiet" description:"Do not output comprehensive progress report"`
NoLock bool ` long:"no-lock" description:"Do not lock the repo, this allows some operations on read-only repos."`
Options []string `short:"o" long:"option" description:"Specify options in the form 'foo.key=value'"`
password string

46
vendor/manifest vendored
View file

@ -4,8 +4,8 @@
{
"importpath": "bazil.org/fuse",
"repository": "https://github.com/bazil/fuse",
"revision": "18419ee53958df28fcfc9490fe6123bd59e237bb",
"branch": "HEAD"
"revision": "371fbbdaa8987b715bdd21d6adc4c9b20155f748",
"branch": "master"
},
{
"importpath": "github.com/elithrar/simple-scrypt",
@ -16,14 +16,14 @@
{
"importpath": "github.com/jessevdk/go-flags",
"repository": "https://github.com/jessevdk/go-flags",
"revision": "1b89bf73cd2c3a911d7b2a279ab085c4a18cf539",
"branch": "HEAD"
"revision": "4cc2832a6e6d1d3b815e2b9d544b2a4dfb3ce8fa",
"branch": "master"
},
{
"importpath": "github.com/kr/fs",
"repository": "https://github.com/kr/fs",
"revision": "2788f0dbd16903de03cb8186e5c7d97b69ad387b",
"branch": "HEAD"
"branch": "master"
},
{
"importpath": "github.com/minio/minio-go",
@ -40,7 +40,7 @@
{
"importpath": "github.com/pkg/sftp",
"repository": "https://github.com/pkg/sftp",
"revision": "a71e8f580e3b622ebff585309160b1cc549ef4d2",
"revision": "8197a2e580736b78d704be0fc47b2324c0591a32",
"branch": "master"
},
{
@ -49,45 +49,59 @@
"revision": "49e9b5212b022a1ab373faf981ed4f2fc807502a",
"branch": "master"
},
{
"importpath": "golang.org/x/crypto/curve25519",
"repository": "https://go.googlesource.com/crypto",
"revision": "81372b2fc2f10bef2a7f338da115c315a56b2726",
"branch": "master",
"path": "/curve25519"
},
{
"importpath": "golang.org/x/crypto/ed25519",
"repository": "https://go.googlesource.com/crypto",
"revision": "81372b2fc2f10bef2a7f338da115c315a56b2726",
"branch": "master",
"path": "/ed25519"
},
{
"importpath": "golang.org/x/crypto/pbkdf2",
"repository": "https://go.googlesource.com/crypto",
"revision": "cc04154d65fb9296747569b107cfd05380b1ea3e",
"branch": "HEAD",
"revision": "81372b2fc2f10bef2a7f338da115c315a56b2726",
"branch": "master",
"path": "/pbkdf2"
},
{
"importpath": "golang.org/x/crypto/poly1305",
"repository": "https://go.googlesource.com/crypto",
"revision": "1777f3ba8c1fed80fcaec3317e3aaa4f627764d2",
"revision": "81372b2fc2f10bef2a7f338da115c315a56b2726",
"branch": "master",
"path": "/poly1305"
},
{
"importpath": "golang.org/x/crypto/scrypt",
"repository": "https://go.googlesource.com/crypto",
"revision": "cc04154d65fb9296747569b107cfd05380b1ea3e",
"branch": "HEAD",
"revision": "81372b2fc2f10bef2a7f338da115c315a56b2726",
"branch": "master",
"path": "/scrypt"
},
{
"importpath": "golang.org/x/crypto/ssh",
"repository": "https://go.googlesource.com/crypto",
"revision": "cc04154d65fb9296747569b107cfd05380b1ea3e",
"branch": "HEAD",
"revision": "81372b2fc2f10bef2a7f338da115c315a56b2726",
"branch": "master",
"path": "/ssh"
},
{
"importpath": "golang.org/x/net/context",
"repository": "https://go.googlesource.com/net",
"revision": "7654728e381988afd88e58cabfd6363a5ea91810",
"branch": "HEAD",
"revision": "de35ec43e7a9aabd6a9c54d2898220ea7e44de7d",
"branch": "master",
"path": "/context"
},
{
"importpath": "golang.org/x/sys/unix",
"repository": "https://go.googlesource.com/sys",
"revision": "a646d33e2ee3172a661fc09bca23bb4889a41bc8",
"revision": "30de6d19a3bd89a5f38ae4028e23aaa5582648af",
"branch": "master",
"path": "/unix"
}

View file

@ -24,16 +24,7 @@ func usage() {
flag.PrintDefaults()
}
func main() {
flag.Usage = usage
flag.Parse()
if flag.NArg() != 1 {
usage()
os.Exit(2)
}
mountpoint := flag.Arg(0)
func run(mountpoint string) error {
c, err := fuse.Mount(
mountpoint,
fuse.FSName("clock"),
@ -42,10 +33,14 @@ func main() {
fuse.VolumeName("Clock filesystem"),
)
if err != nil {
log.Fatal(err)
return err
}
defer c.Close()
if p := c.Protocol(); !p.HasInvalidate() {
return fmt.Errorf("kernel FUSE support is too old to have invalidations: version %v", p)
}
srv := fs.New(c, nil)
filesys := &FS{
// We pre-create the clock node so that it's always the same
@ -61,12 +56,28 @@ func main() {
// This goroutine never exits. That's fine for this example.
go filesys.clockFile.update()
if err := srv.Serve(filesys); err != nil {
log.Fatal(err)
return err
}
// Check if the mount process has an error to report.
<-c.Ready
if err := c.MountError; err != nil {
return err
}
return nil
}
func main() {
flag.Usage = usage
flag.Parse()
if flag.NArg() != 1 {
usage()
os.Exit(2)
}
mountpoint := flag.Arg(0)
if err := run(mountpoint); err != nil {
log.Fatal(err)
}
}

View file

@ -13,18 +13,18 @@ import (
"golang.org/x/net/context"
)
var Usage = func() {
func usage() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
fmt.Fprintf(os.Stderr, " %s MOUNTPOINT\n", os.Args[0])
flag.PrintDefaults()
}
func main() {
flag.Usage = Usage
flag.Usage = usage
flag.Parse()
if flag.NArg() != 1 {
Usage()
usage()
os.Exit(2)
}
mountpoint := flag.Arg(0)

View file

@ -0,0 +1,54 @@
package bench_test
import (
"fmt"
"os"
"testing"
"bazil.org/fuse"
"bazil.org/fuse/fs"
"bazil.org/fuse/fs/fstestutil"
"golang.org/x/net/context"
)
type dummyFile struct {
fstestutil.File
}
type benchCreateDir struct {
fstestutil.Dir
}
var _ fs.NodeCreater = (*benchCreateDir)(nil)
func (f *benchCreateDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
child := &dummyFile{}
return child, child, nil
}
func BenchmarkCreate(b *testing.B) {
f := &benchCreateDir{}
mnt, err := fstestutil.MountedT(b, fstestutil.SimpleFS{f}, nil)
if err != nil {
b.Fatal(err)
}
defer mnt.Close()
// prepare file names to decrease test overhead
names := make([]string, 0, b.N)
for i := 0; i < b.N; i++ {
// zero-padded so cost stays the same on every iteration
names = append(names, mnt.Dir+"/"+fmt.Sprintf("%08x", i))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
f, err := os.Create(names[i])
if err != nil {
b.Fatalf("WriteFile: %v", err)
}
f.Close()
}
b.StopTimer()
}

View file

@ -0,0 +1,42 @@
package bench_test
import (
"os"
"testing"
"golang.org/x/net/context"
"bazil.org/fuse"
"bazil.org/fuse/fs"
"bazil.org/fuse/fs/fstestutil"
)
type benchLookupDir struct {
fstestutil.Dir
}
var _ fs.NodeRequestLookuper = (*benchLookupDir)(nil)
func (f *benchLookupDir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (fs.Node, error) {
return nil, fuse.ENOENT
}
func BenchmarkLookup(b *testing.B) {
f := &benchLookupDir{}
mnt, err := fstestutil.MountedT(b, fstestutil.SimpleFS{f}, nil)
if err != nil {
b.Fatal(err)
}
defer mnt.Close()
name := mnt.Dir + "/does-not-exist"
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err := os.Stat(name); !os.IsNotExist(err) {
b.Fatalf("Stat: wrong error: %v", err)
}
}
b.StopTimer()
}

View file

@ -0,0 +1,70 @@
package fstestutil
import (
"fmt"
"io/ioutil"
"os"
)
// FileInfoCheck is a function that validates an os.FileInfo according
// to some criteria.
type FileInfoCheck func(fi os.FileInfo) error
type checkDirError struct {
missing map[string]struct{}
extra map[string]os.FileMode
}
func (e *checkDirError) Error() string {
return fmt.Sprintf("wrong directory contents: missing %v, extra %v", e.missing, e.extra)
}
// CheckDir checks the contents of the directory at path, making sure
// every directory entry listed in want is present. If the check is
// not nil, it must also pass.
//
// If want contains the impossible filename "", unexpected files are
// checked with that. If the key is not in want, unexpected files are
// an error.
//
// Missing entries, that are listed in want but not seen, are an
// error.
func CheckDir(path string, want map[string]FileInfoCheck) error {
problems := &checkDirError{
missing: make(map[string]struct{}, len(want)),
extra: make(map[string]os.FileMode),
}
for k := range want {
if k == "" {
continue
}
problems.missing[k] = struct{}{}
}
fis, err := ioutil.ReadDir(path)
if err != nil {
return fmt.Errorf("cannot read directory: %v", err)
}
for _, fi := range fis {
check, ok := want[fi.Name()]
if !ok {
check, ok = want[""]
}
if !ok {
problems.extra[fi.Name()] = fi.Mode()
continue
}
delete(problems.missing, fi.Name())
if check != nil {
if err := check(fi); err != nil {
return fmt.Errorf("check failed: %v: %v", fi.Name(), err)
}
}
}
if len(problems.missing) > 0 || len(problems.extra) > 0 {
return problems
}
return nil
}

View file

@ -50,13 +50,15 @@ func (mnt *Mount) Close() {
os.Remove(mnt.Dir)
}
// Mounted mounts the fuse.Server at a temporary directory.
// MountedFunc mounts a filesystem at a temporary directory. The
// filesystem used is constructed by calling a function, to allow
// storing fuse.Conn and fs.Server in the FS.
//
// It also waits until the filesystem is known to be visible (OS X
// workaround).
//
// After successful return, caller must clean up by calling Close.
func Mounted(filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
func MountedFunc(fn func(*Mount) fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
dir, err := ioutil.TempDir("", "fusetest")
if err != nil {
return nil, err
@ -75,6 +77,7 @@ func Mounted(filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Moun
Error: serveErr,
done: done,
}
filesys := fn(mnt)
go func() {
defer close(done)
serveErr <- server.Serve(filesys)
@ -95,14 +98,25 @@ func Mounted(filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Moun
}
}
// MountedT mounts the filesystem at a temporary directory,
// Mounted mounts the fuse.Server at a temporary directory.
//
// It also waits until the filesystem is known to be visible (OS X
// workaround).
//
// After successful return, caller must clean up by calling Close.
func Mounted(filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
fn := func(*Mount) fs.FS { return filesys }
return MountedFunc(fn, conf, options...)
}
// MountedFuncT mounts a filesystem at a temporary directory,
// directing it's debug log to the testing logger.
//
// See Mounted for usage.
// See MountedFunc for usage.
//
// The debug log is not enabled by default. Use `-fuse.debug` or call
// DebugByDefault to enable.
func MountedT(t testing.TB, filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
func MountedFuncT(t testing.TB, fn func(*Mount) fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
if conf == nil {
conf = &fs.Config{}
}
@ -111,5 +125,17 @@ func MountedT(t testing.TB, filesys fs.FS, conf *fs.Config, options ...fuse.Moun
t.Logf("FUSE: %s", msg)
}
}
return Mounted(filesys, conf, options...)
return MountedFunc(fn, conf, options...)
}
// MountedT mounts the filesystem at a temporary directory,
// directing it's debug log to the testing logger.
//
// See Mounted for usage.
//
// The debug log is not enabled by default. Use `-fuse.debug` or call
// DebugByDefault to enable.
func MountedT(t testing.TB, filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
fn := func(*Mount) fs.FS { return filesys }
return MountedFuncT(t, fn, conf, options...)
}

View file

@ -382,3 +382,28 @@ func (r *Removexattrs) RecordedRemovexattr() fuse.RemovexattrRequest {
}
return *(val.(*fuse.RemovexattrRequest))
}
// Creates records a Create request and its fields.
type Creates struct {
rec RequestRecorder
}
var _ = fs.NodeCreater(&Creates{})
// Create records the request and returns an error. Most callers should
// wrap this call in a function that returns a more useful result.
func (r *Creates) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
tmp := *req
r.rec.RecordRequest(&tmp)
return nil, nil, fuse.EIO
}
// RecordedCreate returns information about the Create request.
// If no request was seen, returns a zero value.
func (r *Creates) RecordedCreate() fuse.CreateRequest {
val := r.rec.Recorded()
if val == nil {
return fuse.CreateRequest{}
}
return *(val.(*fuse.CreateRequest))
}

View file

@ -18,6 +18,8 @@ import (
)
import (
"bytes"
"bazil.org/fuse"
"bazil.org/fuse/fuseutil"
)
@ -89,6 +91,13 @@ type FSInodeGenerator interface {
// simple, read-only filesystem.
type Node interface {
// Attr fills attr with the standard metadata for the node.
//
// Fields with reasonable defaults are prepopulated. For example,
// all times are set to a fixed moment when the program started.
//
// If Inode is left as 0, a dynamic inode number is chosen.
//
// The result may be cached for the duration set in Valid.
Attr(ctx context.Context, attr *fuse.Attr) error
}
@ -105,9 +114,7 @@ type NodeSetattrer interface {
// Setattr sets the standard metadata for the receiver.
//
// Note, this is also used to communicate changes in the size of
// the file. Not implementing Setattr causes writes to be unable
// to grow the file (except with OpenDirectIO, which bypasses that
// mechanism).
// the file, outside of Writes.
//
// req.Valid is a bitmask of what fields are actually being set.
// For example, the method should not change the mode of the file
@ -297,16 +304,17 @@ type HandleReader interface {
}
type HandleWriter interface {
// Write requests to write data into the handle.
// Write requests to write data into the handle at the given offset.
// Store the amount of data written in resp.Size.
//
// There is a writeback page cache in the kernel that normally submits
// only page-aligned writes spanning one or more pages. However,
// you should not rely on this. To see individual requests as
// submitted by the file system clients, set OpenDirectIO.
//
// Note that file size changes are communicated through Setattr.
// Writes beyond the size of the file as reported by Attr are not
// even attempted (except in OpenDirectIO mode).
// Writes that grow the file are expected to update the file size
// (as seen through Attr). Note that file size changes are
// communicated also through Setattr.
Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error
}
@ -322,11 +330,13 @@ type Config struct {
// See fuse.Debug for the rules that log functions must follow.
Debug func(msg interface{})
// Function to create new contexts. If nil, use
// context.Background.
// Function to put things into context for processing the request.
// The returned context must have ctx as its parent.
//
// Note that changing this may not affect existing calls to Serve.
GetContext func() context.Context
//
// Must not retain req.
WithContext func(ctx context.Context, req fuse.Request) context.Context
}
// New returns a new FUSE server ready to serve this kernel FUSE
@ -342,14 +352,11 @@ func New(conn *fuse.Conn, config *Config) *Server {
}
if config != nil {
s.debug = config.Debug
s.context = config.GetContext
s.context = config.WithContext
}
if s.debug == nil {
s.debug = fuse.Debug
}
if s.context == nil {
s.context = context.Background
}
return s
}
@ -357,7 +364,7 @@ type Server struct {
// set in New
conn *fuse.Conn
debug func(msg interface{})
context func() context.Context
context func(ctx context.Context, req fuse.Request) context.Context
// set once at Serve time
fs FS
@ -494,7 +501,7 @@ func (c *Server) saveNode(inode uint64, node Node) (id fuse.NodeID, gen uint64)
}
sn.generation = c.nodeGen
c.nodeRef[node] = id
return
return id, sn.generation
}
func (c *Server) saveHandle(handle Handle, nodeID fuse.NodeID) (id fuse.HandleID) {
@ -601,7 +608,7 @@ type logResponseHeader struct {
}
func (m logResponseHeader) String() string {
return fmt.Sprintf("ID=%#x", m.ID)
return fmt.Sprintf("ID=%v", m.ID)
}
type response struct {
@ -626,21 +633,21 @@ func (r response) errstr() string {
func (r response) String() string {
switch {
case r.Errno != "" && r.Out != nil:
return fmt.Sprintf("-> %s error=%s %s", r.Request, r.errstr(), r.Out)
return fmt.Sprintf("-> [%v] %v error=%s", r.Request, r.Out, r.errstr())
case r.Errno != "":
return fmt.Sprintf("-> %s error=%s", r.Request, r.errstr())
return fmt.Sprintf("-> [%v] %s error=%s", r.Request, r.Op, r.errstr())
case r.Out != nil:
// make sure (seemingly) empty values are readable
switch r.Out.(type) {
case string:
return fmt.Sprintf("-> %s %q", r.Request, r.Out)
return fmt.Sprintf("-> [%v] %s %q", r.Request, r.Op, r.Out)
case []byte:
return fmt.Sprintf("-> %s [% x]", r.Request, r.Out)
return fmt.Sprintf("-> [%v] %s [% x]", r.Request, r.Op, r.Out)
default:
return fmt.Sprintf("-> %s %s", r.Request, r.Out)
return fmt.Sprintf("-> [%v] %v", r.Request, r.Out)
}
default:
return fmt.Sprintf("-> %s", r.Request)
return fmt.Sprintf("-> [%v] %s", r.Request, r.Op)
}
}
@ -652,20 +659,23 @@ type notification struct {
}
func (n notification) String() string {
switch {
case n.Out != nil:
var buf bytes.Buffer
fmt.Fprintf(&buf, "=> %s %v", n.Op, n.Node)
if n.Out != nil {
// make sure (seemingly) empty values are readable
switch n.Out.(type) {
case string:
return fmt.Sprintf("=> %s %d %q Err:%v", n.Op, n.Node, n.Out, n.Err)
fmt.Fprintf(&buf, " %q", n.Out)
case []byte:
return fmt.Sprintf("=> %s %d [% x] Err:%v", n.Op, n.Node, n.Out, n.Err)
fmt.Fprintf(&buf, " [% x]", n.Out)
default:
return fmt.Sprintf("=> %s %d %s Err:%v", n.Op, n.Node, n.Out, n.Err)
fmt.Fprintf(&buf, " %s", n.Out)
}
default:
return fmt.Sprintf("=> %s %d Err:%v", n.Op, n.Node, n.Err)
}
if n.Err != "" {
fmt.Fprintf(&buf, " Err:%v", n.Err)
}
return buf.String()
}
type logMissingNode struct {
@ -685,7 +695,7 @@ type logLinkRequestOldNodeNotFound struct {
}
func (m *logLinkRequestOldNodeNotFound) String() string {
return fmt.Sprintf("In LinkRequest (request %#x), node %d not found", m.Request.Hdr().ID, m.In.OldNode)
return fmt.Sprintf("In LinkRequest (request %v), node %d not found", m.Request.Hdr().ID, m.In.OldNode)
}
type renameNewDirNodeNotFound struct {
@ -694,7 +704,7 @@ type renameNewDirNodeNotFound struct {
}
func (m *renameNewDirNodeNotFound) String() string {
return fmt.Sprintf("In RenameRequest (request %#x), node %d not found", m.Request.Hdr().ID, m.In.NewDir)
return fmt.Sprintf("In RenameRequest (request %v), node %d not found", m.Request.Hdr().ID, m.In.NewDir)
}
type handlerPanickedError struct {
@ -717,13 +727,52 @@ func (h handlerPanickedError) Errno() fuse.Errno {
return fuse.DefaultErrno
}
// handlerTerminatedError happens when a handler terminates itself
// with runtime.Goexit. This is most commonly because of incorrect use
// of testing.TB.FailNow, typically via t.Fatal.
type handlerTerminatedError struct {
Request interface{}
}
var _ error = handlerTerminatedError{}
func (h handlerTerminatedError) Error() string {
return fmt.Sprintf("handler terminated (called runtime.Goexit)")
}
var _ fuse.ErrorNumber = handlerTerminatedError{}
func (h handlerTerminatedError) Errno() fuse.Errno {
return fuse.DefaultErrno
}
type handleNotReaderError struct {
handle Handle
}
var _ error = handleNotReaderError{}
func (e handleNotReaderError) Error() string {
return fmt.Sprintf("handle has no Read: %T", e.handle)
}
var _ fuse.ErrorNumber = handleNotReaderError{}
func (e handleNotReaderError) Errno() fuse.Errno {
return fuse.ENOTSUP
}
func initLookupResponse(s *fuse.LookupResponse) {
s.EntryValid = entryValidTime
}
func (c *Server) serve(r fuse.Request) {
ctx, cancel := context.WithCancel(c.context())
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
parentCtx := ctx
if c.context != nil {
ctx = c.context(ctx, r)
}
req := &serveRequest{Request: r, cancel: cancel}
@ -800,6 +849,7 @@ func (c *Server) serve(r fuse.Request) {
c.meta.Unlock()
}
var responded bool
defer func() {
if rec := recover(); rec != nil {
const size = 1 << 16
@ -813,114 +863,132 @@ func (c *Server) serve(r fuse.Request) {
}
done(err)
r.RespondError(err)
return
}
if !responded {
err := handlerTerminatedError{
Request: r,
}
done(err)
r.RespondError(err)
}
}()
if err := c.handleRequest(ctx, node, snode, r, done); err != nil {
if err == context.Canceled {
select {
case <-parentCtx.Done():
// We canceled the parent context because of an
// incoming interrupt request, so return EINTR
// to trigger the right behavior in the client app.
//
// Only do this when it's the parent context that was
// canceled, not a context controlled by the program
// using this library, so we don't return EINTR too
// eagerly -- it might cause busy loops.
//
// Decent write-up on role of EINTR:
// http://250bpm.com/blog:12
err = fuse.EINTR
default:
// nothing
}
}
done(err)
r.RespondError(err)
}
// disarm runtime.Goexit protection
responded = true
}
// handleRequest will either a) call done(s) and r.Respond(s) OR b) return an error.
func (c *Server) handleRequest(ctx context.Context, node Node, snode *serveNode, r fuse.Request, done func(resp interface{})) error {
switch r := r.(type) {
default:
// Note: To FUSE, ENOSYS means "this server never implements this request."
// It would be inappropriate to return ENOSYS for other operations in this
// switch that might only be unavailable in some contexts, not all.
done(fuse.ENOSYS)
r.RespondError(fuse.ENOSYS)
return fuse.ENOSYS
case *fuse.StatfsRequest:
s := &fuse.StatfsResponse{}
if fs, ok := c.fs.(FSStatfser); ok {
if err := fs.Statfs(ctx, r, s); err != nil {
done(err)
r.RespondError(err)
break
return err
}
}
done(s)
r.Respond(s)
return nil
// Node operations.
case *fuse.GetattrRequest:
s := &fuse.GetattrResponse{}
if n, ok := node.(NodeGetattrer); ok {
if err := n.Getattr(ctx, r, s); err != nil {
done(err)
r.RespondError(err)
break
return err
}
} else {
if err := snode.attr(ctx, &s.Attr); err != nil {
done(err)
r.RespondError(err)
break
return err
}
}
done(s)
r.Respond(s)
return nil
case *fuse.SetattrRequest:
s := &fuse.SetattrResponse{}
if n, ok := node.(NodeSetattrer); ok {
if err := n.Setattr(ctx, r, s); err != nil {
done(err)
r.RespondError(err)
break
return err
}
done(s)
r.Respond(s)
break
}
if err := snode.attr(ctx, &s.Attr); err != nil {
done(err)
r.RespondError(err)
break
return err
}
done(s)
r.Respond(s)
return nil
case *fuse.SymlinkRequest:
s := &fuse.SymlinkResponse{}
initLookupResponse(&s.LookupResponse)
n, ok := node.(NodeSymlinker)
if !ok {
done(fuse.EIO) // XXX or EPERM like Mkdir?
r.RespondError(fuse.EIO)
break
return fuse.EIO // XXX or EPERM like Mkdir?
}
n2, err := n.Symlink(ctx, r)
if err != nil {
done(err)
r.RespondError(err)
break
return err
}
if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.NewName, n2); err != nil {
done(err)
r.RespondError(err)
break
return err
}
done(s)
r.Respond(s)
return nil
case *fuse.ReadlinkRequest:
n, ok := node.(NodeReadlinker)
if !ok {
done(fuse.EIO) /// XXX or EPERM?
r.RespondError(fuse.EIO)
break
return fuse.EIO /// XXX or EPERM?
}
target, err := n.Readlink(ctx, r)
if err != nil {
done(err)
r.RespondError(err)
break
return err
}
done(target)
r.Respond(target)
return nil
case *fuse.LinkRequest:
n, ok := node.(NodeLinker)
if !ok {
done(fuse.EIO) /// XXX or EPERM?
r.RespondError(fuse.EIO)
break
return fuse.EIO /// XXX or EPERM?
}
c.meta.Lock()
var oldNode *serveNode
@ -933,52 +1001,43 @@ func (c *Server) serve(r fuse.Request) {
Request: r.Hdr(),
In: r,
})
done(fuse.EIO)
r.RespondError(fuse.EIO)
break
return fuse.EIO
}
n2, err := n.Link(ctx, r, oldNode.node)
if err != nil {
done(err)
r.RespondError(err)
break
return err
}
s := &fuse.LookupResponse{}
initLookupResponse(s)
if err := c.saveLookup(ctx, s, snode, r.NewName, n2); err != nil {
done(err)
r.RespondError(err)
break
return err
}
done(s)
r.Respond(s)
return nil
case *fuse.RemoveRequest:
n, ok := node.(NodeRemover)
if !ok {
done(fuse.EIO) /// XXX or EPERM?
r.RespondError(fuse.EIO)
break
return fuse.EIO /// XXX or EPERM?
}
err := n.Remove(ctx, r)
if err != nil {
done(err)
r.RespondError(err)
break
return err
}
done(nil)
r.Respond()
return nil
case *fuse.AccessRequest:
if n, ok := node.(NodeAccesser); ok {
if err := n.Access(ctx, r); err != nil {
done(err)
r.RespondError(err)
break
return err
}
}
done(nil)
r.Respond()
return nil
case *fuse.LookupRequest:
var n2 Node
@ -990,45 +1049,35 @@ func (c *Server) serve(r fuse.Request) {
} else if n, ok := node.(NodeRequestLookuper); ok {
n2, err = n.Lookup(ctx, r, s)
} else {
done(fuse.ENOENT)
r.RespondError(fuse.ENOENT)
break
return fuse.ENOENT
}
if err != nil {
done(err)
r.RespondError(err)
break
return err
}
if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil {
done(err)
r.RespondError(err)
break
return err
}
done(s)
r.Respond(s)
return nil
case *fuse.MkdirRequest:
s := &fuse.MkdirResponse{}
initLookupResponse(&s.LookupResponse)
n, ok := node.(NodeMkdirer)
if !ok {
done(fuse.EPERM)
r.RespondError(fuse.EPERM)
break
return fuse.EPERM
}
n2, err := n.Mkdir(ctx, r)
if err != nil {
done(err)
r.RespondError(err)
break
return err
}
if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil {
done(err)
r.RespondError(err)
break
return err
}
done(s)
r.Respond(s)
return nil
case *fuse.OpenRequest:
s := &fuse.OpenResponse{}
@ -1036,121 +1085,99 @@ func (c *Server) serve(r fuse.Request) {
if n, ok := node.(NodeOpener); ok {
hh, err := n.Open(ctx, r, s)
if err != nil {
done(err)
r.RespondError(err)
break
return err
}
h2 = hh
} else {
h2 = node
}
s.Handle = c.saveHandle(h2, hdr.Node)
s.Handle = c.saveHandle(h2, r.Hdr().Node)
done(s)
r.Respond(s)
return nil
case *fuse.CreateRequest:
n, ok := node.(NodeCreater)
if !ok {
// If we send back ENOSYS, FUSE will try mknod+open.
done(fuse.EPERM)
r.RespondError(fuse.EPERM)
break
return fuse.EPERM
}
s := &fuse.CreateResponse{OpenResponse: fuse.OpenResponse{}}
initLookupResponse(&s.LookupResponse)
n2, h2, err := n.Create(ctx, r, s)
if err != nil {
done(err)
r.RespondError(err)
break
return err
}
if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil {
done(err)
r.RespondError(err)
break
return err
}
s.Handle = c.saveHandle(h2, hdr.Node)
s.Handle = c.saveHandle(h2, r.Hdr().Node)
done(s)
r.Respond(s)
return nil
case *fuse.GetxattrRequest:
n, ok := node.(NodeGetxattrer)
if !ok {
done(fuse.ENOTSUP)
r.RespondError(fuse.ENOTSUP)
break
return fuse.ENOTSUP
}
s := &fuse.GetxattrResponse{}
err := n.Getxattr(ctx, r, s)
if err != nil {
done(err)
r.RespondError(err)
break
return err
}
if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) {
done(fuse.ERANGE)
r.RespondError(fuse.ERANGE)
break
return fuse.ERANGE
}
done(s)
r.Respond(s)
return nil
case *fuse.ListxattrRequest:
n, ok := node.(NodeListxattrer)
if !ok {
done(fuse.ENOTSUP)
r.RespondError(fuse.ENOTSUP)
break
return fuse.ENOTSUP
}
s := &fuse.ListxattrResponse{}
err := n.Listxattr(ctx, r, s)
if err != nil {
done(err)
r.RespondError(err)
break
return err
}
if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) {
done(fuse.ERANGE)
r.RespondError(fuse.ERANGE)
break
return fuse.ERANGE
}
done(s)
r.Respond(s)
return nil
case *fuse.SetxattrRequest:
n, ok := node.(NodeSetxattrer)
if !ok {
done(fuse.ENOTSUP)
r.RespondError(fuse.ENOTSUP)
break
return fuse.ENOTSUP
}
err := n.Setxattr(ctx, r)
if err != nil {
done(err)
r.RespondError(err)
break
return err
}
done(nil)
r.Respond()
return nil
case *fuse.RemovexattrRequest:
n, ok := node.(NodeRemovexattrer)
if !ok {
done(fuse.ENOTSUP)
r.RespondError(fuse.ENOTSUP)
break
return fuse.ENOTSUP
}
err := n.Removexattr(ctx, r)
if err != nil {
done(err)
r.RespondError(err)
break
return err
}
done(nil)
r.Respond()
return nil
case *fuse.ForgetRequest:
forget := c.dropNode(hdr.Node, r.N)
forget := c.dropNode(r.Hdr().Node, r.N)
if forget {
n, ok := node.(NodeForgetter)
if ok {
@ -1159,26 +1186,29 @@ func (c *Server) serve(r fuse.Request) {
}
done(nil)
r.Respond()
return nil
// Handle operations.
case *fuse.ReadRequest:
shandle := c.getHandle(r.Handle)
if shandle == nil {
done(fuse.ESTALE)
r.RespondError(fuse.ESTALE)
return
return fuse.ESTALE
}
handle := shandle.handle
s := &fuse.ReadResponse{Data: make([]byte, 0, r.Size)}
if r.Dir {
if h, ok := handle.(HandleReadDirAller); ok {
// detect rewinddir(3) or similar seek and refresh
// contents
if r.Offset == 0 {
shandle.readData = nil
}
if shandle.readData == nil {
dirs, err := h.ReadDirAll(ctx)
if err != nil {
done(err)
r.RespondError(err)
break
return err
}
var data []byte
for _, dir := range dirs {
@ -1192,16 +1222,14 @@ func (c *Server) serve(r fuse.Request) {
fuseutil.HandleRead(r, s, shandle.readData)
done(s)
r.Respond(s)
break
return nil
}
} else {
if h, ok := handle.(HandleReadAller); ok {
if shandle.readData == nil {
data, err := h.ReadAll(ctx)
if err != nil {
done(err)
r.RespondError(err)
break
return err
}
if data == nil {
data = []byte{}
@ -1211,71 +1239,58 @@ func (c *Server) serve(r fuse.Request) {
fuseutil.HandleRead(r, s, shandle.readData)
done(s)
r.Respond(s)
break
return nil
}
h, ok := handle.(HandleReader)
if !ok {
fmt.Printf("NO READ FOR %T\n", handle)
done(fuse.EIO)
r.RespondError(fuse.EIO)
break
err := handleNotReaderError{handle: handle}
return err
}
if err := h.Read(ctx, r, s); err != nil {
done(err)
r.RespondError(err)
break
return err
}
}
done(s)
r.Respond(s)
return nil
case *fuse.WriteRequest:
shandle := c.getHandle(r.Handle)
if shandle == nil {
done(fuse.ESTALE)
r.RespondError(fuse.ESTALE)
return
return fuse.ESTALE
}
s := &fuse.WriteResponse{}
if h, ok := shandle.handle.(HandleWriter); ok {
if err := h.Write(ctx, r, s); err != nil {
done(err)
r.RespondError(err)
break
return err
}
done(s)
r.Respond(s)
break
return nil
}
done(fuse.EIO)
r.RespondError(fuse.EIO)
return fuse.EIO
case *fuse.FlushRequest:
shandle := c.getHandle(r.Handle)
if shandle == nil {
done(fuse.ESTALE)
r.RespondError(fuse.ESTALE)
return
return fuse.ESTALE
}
handle := shandle.handle
if h, ok := handle.(HandleFlusher); ok {
if err := h.Flush(ctx, r); err != nil {
done(err)
r.RespondError(err)
break
return err
}
}
done(nil)
r.Respond()
return nil
case *fuse.ReleaseRequest:
shandle := c.getHandle(r.Handle)
if shandle == nil {
done(fuse.ESTALE)
r.RespondError(fuse.ESTALE)
return
return fuse.ESTALE
}
handle := shandle.handle
@ -1284,13 +1299,12 @@ func (c *Server) serve(r fuse.Request) {
if h, ok := handle.(HandleReleaser); ok {
if err := h.Release(ctx, r); err != nil {
done(err)
r.RespondError(err)
break
return err
}
}
done(nil)
r.Respond()
return nil
case *fuse.DestroyRequest:
if fs, ok := c.fs.(FSDestroyer); ok {
@ -1298,6 +1312,7 @@ func (c *Server) serve(r fuse.Request) {
}
done(nil)
r.Respond()
return nil
case *fuse.RenameRequest:
c.meta.Lock()
@ -1311,63 +1326,50 @@ func (c *Server) serve(r fuse.Request) {
Request: r.Hdr(),
In: r,
})
done(fuse.EIO)
r.RespondError(fuse.EIO)
break
return fuse.EIO
}
n, ok := node.(NodeRenamer)
if !ok {
done(fuse.EIO) // XXX or EPERM like Mkdir?
r.RespondError(fuse.EIO)
break
return fuse.EIO // XXX or EPERM like Mkdir?
}
err := n.Rename(ctx, r, newDirNode.node)
if err != nil {
done(err)
r.RespondError(err)
break
return err
}
done(nil)
r.Respond()
return nil
case *fuse.MknodRequest:
n, ok := node.(NodeMknoder)
if !ok {
done(fuse.EIO)
r.RespondError(fuse.EIO)
break
return fuse.EIO
}
n2, err := n.Mknod(ctx, r)
if err != nil {
done(err)
r.RespondError(err)
break
return err
}
s := &fuse.LookupResponse{}
initLookupResponse(s)
if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil {
done(err)
r.RespondError(err)
break
return err
}
done(s)
r.Respond(s)
return nil
case *fuse.FsyncRequest:
n, ok := node.(NodeFsyncer)
if !ok {
done(fuse.EIO)
r.RespondError(fuse.EIO)
break
return fuse.EIO
}
err := n.Fsync(ctx, r)
if err != nil {
done(err)
r.RespondError(err)
break
return err
}
done(nil)
r.Respond()
return nil
case *fuse.InterruptRequest:
c.meta.Lock()
@ -1379,24 +1381,23 @@ func (c *Server) serve(r fuse.Request) {
c.meta.Unlock()
done(nil)
r.Respond()
return nil
/* case *FsyncdirRequest:
done(ENOSYS)
r.RespondError(ENOSYS)
return ENOSYS
case *GetlkRequest, *SetlkRequest, *SetlkwRequest:
done(ENOSYS)
r.RespondError(ENOSYS)
return ENOSYS
case *BmapRequest:
done(ENOSYS)
r.RespondError(ENOSYS)
return ENOSYS
case *SetvolnameRequest, *GetxtimesRequest, *ExchangeRequest:
done(ENOSYS)
r.RespondError(ENOSYS)
return ENOSYS
*/
}
panic("not reached")
}
func (c *Server) saveLookup(ctx context.Context, s *fuse.LookupResponse, snode *serveNode, elem string, n2 Node) error {

View file

@ -0,0 +1,30 @@
package fs_test
import (
"testing"
"bazil.org/fuse/fs/fstestutil"
"golang.org/x/sys/unix"
)
type exchangeData struct {
fstestutil.File
// this struct cannot be zero size or multiple instances may look identical
_ int
}
func TestExchangeDataNotSupported(t *testing.T) {
t.Parallel()
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{
"one": &exchangeData{},
"two": &exchangeData{},
}}, nil)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
if err := unix.Exchangedata(mnt.Dir+"/one", mnt.Dir+"/two", 0); err != unix.ENOTSUP {
t.Fatalf("expected ENOTSUP from exchangedata: %v", err)
}
}

View file

@ -7,10 +7,11 @@ import (
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"runtime"
"strings"
"sync"
"sync/atomic"
"syscall"
"testing"
"time"
@ -56,6 +57,25 @@ func (f fifo) Attr(ctx context.Context, a *fuse.Attr) error {
return nil
}
func TestMountpointDoesNotExist(t *testing.T) {
t.Parallel()
tmp, err := ioutil.TempDir("", "fusetest")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmp)
mountpoint := path.Join(tmp, "does-not-exist")
conn, err := fuse.Mount(mountpoint)
if err == nil {
conn.Close()
t.Fatalf("expected error with non-existent mountpoint")
}
if _, ok := err.(*fuse.MountpointDoesNotExistError); !ok {
t.Fatalf("wrong error from mount: %T: %v", err, err)
}
}
type badRootFS struct{}
func (badRootFS) Root() (fs.Node, error) {
@ -277,12 +297,18 @@ func (readAll) ReadAll(ctx context.Context) ([]byte, error) {
}
func testReadAll(t *testing.T, path string) {
data, err := ioutil.ReadFile(path)
f, err := os.Open(path)
if err != nil {
t.Fatalf("readAll: %v", err)
t.Fatal(err)
}
if string(data) != hi {
t.Errorf("readAll = %q, want %q", data, hi)
defer f.Close()
data := make([]byte, 4096)
n, err := f.Read(data)
if err != nil {
t.Fatal(err)
}
if g, e := string(data[:n]), hi; g != e {
t.Errorf("readAll = %q, want %q", g, e)
}
}
@ -366,6 +392,15 @@ func TestReadFileFlags(t *testing.T) {
_ = f.Close()
want := fuse.OpenReadWrite | fuse.OpenAppend
if runtime.GOOS == "darwin" {
// OSXFUSE shares one read and one write handle for all
// clients, so it uses a OpenReadOnly handle for performing
// our read.
//
// If this test starts failing in the future, that probably
// means they added the feature, and we want to notice that!
want = fuse.OpenReadOnly
}
if g, e := r.fileFlags.Recorded().(fuse.OpenFlags), want; g != e {
t.Errorf("read saw file flags %+v, want %+v", g, e)
}
@ -382,6 +417,13 @@ func (r *writeFlags) Attr(ctx context.Context, a *fuse.Attr) error {
return nil
}
func (r *writeFlags) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
// OSXFUSE 3.0.4 does a read-modify-write cycle even when the
// write was for 4096 bytes.
fuseutil.HandleRead(req, resp, []byte(hi))
return nil
}
func (r *writeFlags) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
r.fileFlags.Record(req.FileFlags)
resp.Size = len(req.Data)
@ -412,6 +454,15 @@ func TestWriteFileFlags(t *testing.T) {
_ = f.Close()
want := fuse.OpenReadWrite | fuse.OpenAppend
if runtime.GOOS == "darwin" {
// OSXFUSE shares one read and one write handle for all
// clients, so it uses a OpenWriteOnly handle for performing
// our read.
//
// If this test starts failing in the future, that probably
// means they added the feature, and we want to notice that!
want = fuse.OpenWriteOnly
}
if g, e := r.fileFlags.Recorded().(fuse.OpenFlags), want; g != e {
t.Errorf("write saw file flags %+v, want %+v", g, e)
}
@ -582,7 +633,6 @@ func (f *mkdir1) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, er
}
func TestMkdir(t *testing.T) {
t.Parallel()
f := &mkdir1{}
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
if err != nil {
@ -601,6 +651,10 @@ func TestMkdir(t *testing.T) {
if mnt.Conn.Protocol().HasUmask() {
want.Umask = 0022
}
if runtime.GOOS == "darwin" {
// https://github.com/osxfuse/osxfuse/issues/225
want.Umask = 0
}
if g, e := f.RecordedMkdir(), want; g != e {
t.Errorf("mkdir saw %+v, want %+v", g, e)
}
@ -610,6 +664,7 @@ func TestMkdir(t *testing.T) {
type create1file struct {
fstestutil.File
record.Creates
record.Fsyncs
}
@ -623,31 +678,12 @@ func (f *create1) Create(ctx context.Context, req *fuse.CreateRequest, resp *fus
log.Printf("ERROR create1.Create unexpected name: %q\n", req.Name)
return nil, nil, fuse.EPERM
}
flags := req.Flags
// OS X does not pass O_TRUNC here, Linux does; as this is a
// Create, that's acceptable
flags &^= fuse.OpenTruncate
if runtime.GOOS == "linux" {
// Linux <3.7 accidentally leaks O_CLOEXEC through to FUSE;
// avoid spurious test failures
flags &^= fuse.OpenFlags(syscall.O_CLOEXEC)
}
if g, e := flags, fuse.OpenReadWrite|fuse.OpenCreate; g != e {
log.Printf("ERROR create1.Create unexpected flags: %v != %v\n", g, e)
return nil, nil, fuse.EPERM
}
if g, e := req.Mode, os.FileMode(0644); g != e {
log.Printf("ERROR create1.Create unexpected mode: %v != %v\n", g, e)
return nil, nil, fuse.EPERM
}
_, _, _ = f.f.Creates.Create(ctx, req, resp)
return &f.f, &f.f, nil
}
func TestCreate(t *testing.T) {
t.Parallel()
f := &create1{}
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
if err != nil {
@ -658,12 +694,38 @@ func TestCreate(t *testing.T) {
// uniform umask needed to make os.Create's 0666 into something
// reproducible
defer syscall.Umask(syscall.Umask(0022))
ff, err := os.Create(mnt.Dir + "/foo")
ff, err := os.OpenFile(mnt.Dir+"/foo", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0640)
if err != nil {
t.Fatalf("create1 WriteFile: %v", err)
}
defer ff.Close()
want := fuse.CreateRequest{
Name: "foo",
Flags: fuse.OpenReadWrite | fuse.OpenCreate | fuse.OpenTruncate,
Mode: 0640,
}
if mnt.Conn.Protocol().HasUmask() {
want.Umask = 0022
}
if runtime.GOOS == "darwin" {
// OS X does not pass O_TRUNC here, Linux does; as this is a
// Create, that's acceptable
want.Flags &^= fuse.OpenTruncate
// https://github.com/osxfuse/osxfuse/issues/225
want.Umask = 0
}
got := f.f.RecordedCreate()
if runtime.GOOS == "linux" {
// Linux <3.7 accidentally leaks O_CLOEXEC through to FUSE;
// avoid spurious test failures
got.Flags &^= fuse.OpenFlags(syscall.O_CLOEXEC)
}
if g, e := got, want; g != e {
t.Fatalf("create saw %+v, want %+v", g, e)
}
err = syscall.Fsync(int(ff.Fd()))
if err != nil {
t.Fatalf("Fsync = %v", err)
@ -895,7 +957,6 @@ func (f *mknod1) Mknod(ctx context.Context, r *fuse.MknodRequest) (fs.Node, erro
}
func TestMknod(t *testing.T) {
t.Parallel()
if os.Getuid() != 0 {
t.Skip("skipping unless root")
}
@ -907,15 +968,15 @@ func TestMknod(t *testing.T) {
}
defer mnt.Close()
defer syscall.Umask(syscall.Umask(0))
err = syscall.Mknod(mnt.Dir+"/node", syscall.S_IFIFO|0666, 123)
defer syscall.Umask(syscall.Umask(0022))
err = syscall.Mknod(mnt.Dir+"/node", syscall.S_IFIFO|0660, 123)
if err != nil {
t.Fatalf("Mknod: %v", err)
t.Fatalf("mknod: %v", err)
}
want := fuse.MknodRequest{
Name: "node",
Mode: os.FileMode(os.ModeNamedPipe | 0666),
Mode: os.FileMode(os.ModeNamedPipe | 0640),
Rdev: uint32(123),
}
if runtime.GOOS == "linux" {
@ -924,6 +985,13 @@ func TestMknod(t *testing.T) {
// bit is portable.)
want.Rdev = 0
}
if mnt.Conn.Protocol().HasUmask() {
want.Umask = 0022
}
if runtime.GOOS == "darwin" {
// https://github.com/osxfuse/osxfuse/issues/225
want.Umask = 0
}
if g, e := f.RecordedMknod(), want; g != e {
t.Fatalf("mknod saw %+v, want %+v", g, e)
}
@ -985,7 +1053,38 @@ func (it *interrupt) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse
default:
}
<-ctx.Done()
return fuse.EINTR
return ctx.Err()
}
func helperInterrupt() {
log.SetPrefix("interrupt child: ")
log.SetFlags(0)
log.Printf("starting...")
f, err := os.Open("child")
if err != nil {
log.Fatalf("cannot open file: %v", err)
}
defer f.Close()
log.Printf("reading...")
buf := make([]byte, 4096)
n, err := syscall.Read(int(f.Fd()), buf)
switch err {
case nil:
log.Fatalf("read: expected error, got data: %q", buf[:n])
case syscall.EINTR:
log.Printf("read: saw EINTR, all good")
default:
log.Fatalf("read: wrong error: %v", err)
}
log.Printf("exiting...")
}
func init() {
childHelpers["interrupt"] = helperInterrupt
}
func TestInterrupt(t *testing.T) {
@ -999,46 +1098,71 @@ func TestInterrupt(t *testing.T) {
defer mnt.Close()
// start a subprocess that can hang until signaled
cmd := exec.Command("cat", mnt.Dir+"/child")
err = cmd.Start()
child, err := childCmd("interrupt")
if err != nil {
t.Errorf("interrupt: cannot start cat: %v", err)
t.Fatal(err)
}
child.Dir = mnt.Dir
if err := child.Start(); err != nil {
t.Errorf("cannot start child: %v", err)
return
}
// try to clean up if child is still alive when returning
defer cmd.Process.Kill()
defer child.Process.Kill()
// wait till we're sure it's hanging in read
<-f.hanging
err = cmd.Process.Signal(os.Interrupt)
// err = child.Process.Signal(os.Interrupt)
var sig os.Signal = syscall.SIGIO
if runtime.GOOS == "darwin" {
// I can't get OSXFUSE 3.2.0 to trigger EINTR return from
// read(2), at least in a Go application. Works on Linux. So,
// on OS X, we just check that the signal at least kills the
// child, aborting the read, so operations on hanging FUSE
// filesystems can be aborted.
sig = os.Interrupt
}
err = child.Process.Signal(sig)
if err != nil {
t.Errorf("interrupt: cannot interrupt cat: %v", err)
t.Errorf("cannot interrupt child: %v", err)
return
}
p, err := cmd.Process.Wait()
p, err := child.Process.Wait()
if err != nil {
t.Errorf("interrupt: cat bork: %v", err)
t.Errorf("child failed: %v", err)
return
}
switch ws := p.Sys().(type) {
case syscall.WaitStatus:
if ws.CoreDump() {
t.Errorf("interrupt: didn't expect cat to dump core: %v", ws)
t.Fatalf("interrupt: didn't expect child to dump core: %v", ws)
}
if ws.Exited() {
t.Errorf("interrupt: didn't expect cat to exit normally: %v", ws)
}
if !ws.Signaled() {
t.Errorf("interrupt: expected cat to get a signal: %v", ws)
} else {
if ws.Signal() != os.Interrupt {
t.Errorf("interrupt: cat got wrong signal: %v", ws)
switch runtime.GOOS {
case "darwin":
// see comment above about EINTR on OS X
if ws.Exited() {
t.Fatalf("interrupt: expected child to die from signal, got exit status: %v", ws.ExitStatus())
}
if !ws.Signaled() {
t.Fatalf("interrupt: expected child to die from signal: %v", ws)
}
if got := ws.Signal(); got != sig {
t.Errorf("interrupt: child failed: signal %d", got)
}
default:
if ws.Signaled() {
t.Fatalf("interrupt: didn't expect child to exit with a signal: %v", ws)
}
if !ws.Exited() {
t.Fatalf("interrupt: expected child to exit normally: %v", ws)
}
if status := ws.ExitStatus(); status != 0 {
t.Errorf("interrupt: child failed: exit status %d", status)
}
}
default:
@ -1046,6 +1170,51 @@ func TestInterrupt(t *testing.T) {
}
}
// Test deadline
type deadline struct {
fstestutil.File
}
var _ fs.NodeOpener = (*deadline)(nil)
func (it *deadline) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
<-ctx.Done()
return nil, ctx.Err()
}
func TestDeadline(t *testing.T) {
t.Parallel()
child := &deadline{}
config := &fs.Config{
WithContext: func(ctx context.Context, req fuse.Request) context.Context {
// return a context that has already deadlined
// Server.serve will cancel the parent context, which will
// cancel this one, so discarding cancel here should be
// safe.
ctx, _ = context.WithDeadline(ctx, time.Unix(0, 0))
return ctx
},
}
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": child}}, config)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
f, err := os.Open(mnt.Dir + "/child")
if err == nil {
f.Close()
}
// not caused by signal -> should not get EINTR;
// context.DeadlineExceeded will be translated into EIO
if nerr, ok := err.(*os.PathError); !ok || nerr.Err != syscall.EIO {
t.Fatalf("wrong error from deadline open: %T: %v", err, err)
}
}
// Test truncate
type truncate struct {
@ -1224,6 +1393,58 @@ func TestReadDirAll(t *testing.T) {
}
}
type readDirAllBad struct {
fstestutil.Dir
}
func (d *readDirAllBad) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
r := []fuse.Dirent{
{Name: "one", Inode: 11, Type: fuse.DT_Dir},
{Name: "three", Inode: 13},
{Name: "two", Inode: 12, Type: fuse.DT_File},
}
// pick a really distinct error, to identify it later
return r, fuse.Errno(syscall.ENAMETOOLONG)
}
func TestReadDirAllBad(t *testing.T) {
t.Parallel()
f := &readDirAllBad{}
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
fil, err := os.Open(mnt.Dir)
if err != nil {
t.Error(err)
return
}
defer fil.Close()
var names []string
for {
n, err := fil.Readdirnames(1)
if err != nil {
if nerr, ok := err.(*os.SyscallError); !ok || nerr.Err != syscall.ENAMETOOLONG {
t.Fatalf("wrong error: %v", err)
}
break
}
names = append(names, n...)
}
t.Logf("Got readdir: %q", names)
// TODO could serve partial results from ReadDirAll but the
// shandle.readData mechanism makes that awkward.
if len(names) != 0 {
t.Errorf(`expected 0 entries, got: %q`, names)
return
}
}
// Test readdir without any ReadDir methods implemented.
type readDirNotImplemented struct {
@ -1255,6 +1476,73 @@ func TestReadDirNotImplemented(t *testing.T) {
}
}
type readDirAllRewind struct {
fstestutil.Dir
entries atomic.Value
}
func (d *readDirAllRewind) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
entries := d.entries.Load().([]fuse.Dirent)
return entries, nil
}
func TestReadDirAllRewind(t *testing.T) {
t.Parallel()
f := &readDirAllRewind{}
f.entries.Store([]fuse.Dirent{
{Name: "one", Inode: 11, Type: fuse.DT_Dir},
})
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
fil, err := os.Open(mnt.Dir)
if err != nil {
t.Error(err)
return
}
defer fil.Close()
{
names, err := fil.Readdirnames(100)
if err != nil {
t.Error(err)
return
}
t.Logf("Got readdir: %q", names)
if len(names) != 1 ||
names[0] != "one" {
t.Errorf(`expected entry of "one", got: %q`, names)
return
}
}
f.entries.Store([]fuse.Dirent{
{Name: "two", Inode: 12, Type: fuse.DT_File},
{Name: "one", Inode: 11, Type: fuse.DT_Dir},
})
if _, err := fil.Seek(0, os.SEEK_SET); err != nil {
t.Fatal(err)
}
{
names, err := fil.Readdirnames(100)
if err != nil {
t.Error(err)
return
}
t.Logf("Got readdir: %q", names)
if len(names) != 2 ||
names[0] != "two" ||
names[1] != "one" {
t.Errorf(`expected 2 entries of "two", "one", got: %q`, names)
return
}
}
}
// Test Chmod.
type chmod struct {
@ -1364,6 +1652,10 @@ func (f *openNonSeekable) Open(ctx context.Context, req *fuse.OpenRequest, resp
}
func TestOpenNonSeekable(t *testing.T) {
if runtime.GOOS == "darwin" {
t.Skip("OSXFUSE shares one read and one write handle for all clients, does not support open modes")
}
t.Parallel()
f := &openNonSeekable{}
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil)
@ -2109,8 +2401,20 @@ func TestInvalidateNodeAttr(t *testing.T) {
t.Fatalf("stat error: %v", err)
}
}
if g, e := a.attr.Count(), uint32(1); g != e {
t.Errorf("wrong Attr call count: %d != %d", g, e)
// With OSXFUSE 3.0.4, we seem to see typically two Attr calls by
// this point; something not populating the in-kernel cache
// properly? Cope with it; we care more about seeing a new Attr
// call after the invalidation.
//
// We still enforce a max number here so that we know that the
// invalidate actually did something, and it's not just that every
// Stat results in an Attr.
before := a.attr.Count()
if before == 0 {
t.Error("no Attr call seen")
}
if g, e := before, uint32(3); g > e {
t.Errorf("too many Attr calls seen: %d > %d", g, e)
}
t.Logf("invalidating...")
@ -2123,7 +2427,7 @@ func TestInvalidateNodeAttr(t *testing.T) {
t.Fatalf("stat error: %v", err)
}
}
if g, e := a.attr.Count(), uint32(2); g != e {
if g, e := a.attr.Count(), before+1; g != e {
t.Errorf("wrong Attr call count: %d != %d", g, e)
}
}
@ -2133,9 +2437,13 @@ type invalidateData struct {
t testing.TB
attr record.Counter
read record.Counter
data atomic.Value
}
const invalidateDataContent = "hello, world\n"
const (
invalidateDataContent1 = "hello, world\n"
invalidateDataContent2 = "so long!\n"
)
var _ fs.Node = (*invalidateData)(nil)
@ -2143,7 +2451,7 @@ func (i *invalidateData) Attr(ctx context.Context, a *fuse.Attr) error {
i.attr.Inc()
i.t.Logf("Attr called, #%d", i.attr.Count())
a.Mode = 0600
a.Size = uint64(len(invalidateDataContent))
a.Size = uint64(len(i.data.Load().(string)))
return nil
}
@ -2152,17 +2460,18 @@ var _ fs.HandleReader = (*invalidateData)(nil)
func (i *invalidateData) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
i.read.Inc()
i.t.Logf("Read called, #%d", i.read.Count())
fuseutil.HandleRead(req, resp, []byte(invalidateDataContent))
fuseutil.HandleRead(req, resp, []byte(i.data.Load().(string)))
return nil
}
func TestInvalidateNodeData(t *testing.T) {
func TestInvalidateNodeDataInvalidatesAttr(t *testing.T) {
// This test may see false positive failures when run under
// extreme memory pressure.
t.Parallel()
a := &invalidateData{
t: t,
}
a.data.Store(invalidateDataContent1)
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": a}}, nil)
if err != nil {
t.Fatal(err)
@ -2179,31 +2488,93 @@ func TestInvalidateNodeData(t *testing.T) {
}
defer f.Close()
buf := make([]byte, 4)
for i := 0; i < 10; i++ {
if _, err := f.ReadAt(buf, 0); err != nil {
t.Fatalf("readat error: %v", err)
}
attrBefore := a.attr.Count()
if g, min := attrBefore, uint32(1); g < min {
t.Errorf("wrong Attr call count: %d < %d", g, min)
}
if g, e := a.attr.Count(), uint32(1); g != e {
t.Errorf("wrong Attr call count: %d != %d", g, e)
t.Logf("invalidating...")
a.data.Store(invalidateDataContent2)
if err := mnt.Server.InvalidateNodeData(a); err != nil {
t.Fatalf("invalidate error: %v", err)
}
// on OSXFUSE 3.0.6, the Attr has already triggered here, so don't
// check the count at this point
if _, err := f.Stat(); err != nil {
t.Errorf("stat error: %v", err)
}
if g, prev := a.attr.Count(), attrBefore; g <= prev {
t.Errorf("did not see Attr call after invalidate: %d <= %d", g, prev)
}
}
func TestInvalidateNodeDataInvalidatesData(t *testing.T) {
// This test may see false positive failures when run under
// extreme memory pressure.
t.Parallel()
a := &invalidateData{
t: t,
}
a.data.Store(invalidateDataContent1)
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": a}}, nil)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
if !mnt.Conn.Protocol().HasInvalidate() {
t.Skip("Old FUSE protocol")
}
f, err := os.Open(mnt.Dir + "/child")
if err != nil {
t.Fatal(err)
}
defer f.Close()
{
buf := make([]byte, 100)
for i := 0; i < 10; i++ {
n, err := f.ReadAt(buf, 0)
if err != nil && err != io.EOF {
t.Fatalf("readat error: %v", err)
}
if g, e := string(buf[:n]), invalidateDataContent1; g != e {
t.Errorf("wrong content: %q != %q", g, e)
}
}
}
if g, e := a.read.Count(), uint32(1); g != e {
t.Errorf("wrong Read call count: %d != %d", g, e)
}
t.Logf("invalidating...")
a.data.Store(invalidateDataContent2)
if err := mnt.Server.InvalidateNodeData(a); err != nil {
t.Fatalf("invalidate error: %v", err)
}
for i := 0; i < 10; i++ {
if _, err := f.ReadAt(buf, 0); err != nil {
t.Fatalf("readat error: %v", err)
}
if g, e := a.read.Count(), uint32(1); g != e {
t.Errorf("wrong Read call count: %d != %d", g, e)
}
if g, e := a.attr.Count(), uint32(1); g != e {
t.Errorf("wrong Attr call count: %d != %d", g, e)
{
// explicitly don't cross the EOF, to trigger more edge cases
// (Linux will always do Getattr if you cross what it believes
// the EOF to be)
const bufSize = len(invalidateDataContent2) - 3
buf := make([]byte, bufSize)
for i := 0; i < 10; i++ {
n, err := f.ReadAt(buf, 0)
if err != nil && err != io.EOF {
t.Fatalf("readat error: %v", err)
}
if g, e := string(buf[:n]), invalidateDataContent2[:bufSize]; g != e {
t.Errorf("wrong content: %q != %q", g, e)
}
}
}
if g, e := a.read.Count(), uint32(2); g != e {
t.Errorf("wrong Read call count: %d != %d", g, e)
@ -2238,7 +2609,7 @@ func (i *invalidateDataPartial) Read(ctx context.Context, req *fuse.ReadRequest,
return nil
}
func TestInvalidateNodeDataRange(t *testing.T) {
func TestInvalidateNodeDataRangeMiss(t *testing.T) {
// This test may see false positive failures when run under
// extreme memory pressure.
t.Parallel()
@ -2267,14 +2638,11 @@ func TestInvalidateNodeDataRange(t *testing.T) {
t.Fatalf("readat error: %v", err)
}
}
if g, e := a.attr.Count(), uint32(1); g != e {
t.Errorf("wrong Attr call count: %d != %d", g, e)
}
if g, e := a.read.Count(), uint32(1); g != e {
t.Errorf("wrong Read call count: %d != %d", g, e)
}
t.Logf("invalidating...")
t.Logf("invalidating an uninteresting block...")
if err := mnt.Server.InvalidateNodeDataRange(a, 4096, 4096); err != nil {
t.Fatalf("invalidate error: %v", err)
}
@ -2284,9 +2652,6 @@ func TestInvalidateNodeDataRange(t *testing.T) {
t.Fatalf("readat error: %v", err)
}
}
if g, e := a.attr.Count(), uint32(1); g != e {
t.Errorf("wrong Attr call count: %d != %d", g, e)
}
// The page invalidated is not the page we're reading, so it
// should stay in cache.
if g, e := a.read.Count(), uint32(1); g != e {
@ -2294,6 +2659,56 @@ func TestInvalidateNodeDataRange(t *testing.T) {
}
}
func TestInvalidateNodeDataRangeHit(t *testing.T) {
// This test may see false positive failures when run under
// extreme memory pressure.
t.Parallel()
a := &invalidateDataPartial{
t: t,
}
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": a}}, nil)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
if !mnt.Conn.Protocol().HasInvalidate() {
t.Skip("Old FUSE protocol")
}
f, err := os.Open(mnt.Dir + "/child")
if err != nil {
t.Fatal(err)
}
defer f.Close()
const offset = 4096
buf := make([]byte, 4)
for i := 0; i < 10; i++ {
if _, err := f.ReadAt(buf, offset); err != nil {
t.Fatalf("readat error: %v", err)
}
}
if g, e := a.read.Count(), uint32(1); g != e {
t.Errorf("wrong Read call count: %d != %d", g, e)
}
t.Logf("invalidating where the reads are...")
if err := mnt.Server.InvalidateNodeDataRange(a, offset, 4096); err != nil {
t.Fatalf("invalidate error: %v", err)
}
for i := 0; i < 10; i++ {
if _, err := f.ReadAt(buf, offset); err != nil {
t.Fatalf("readat error: %v", err)
}
}
// One new read
if g, e := a.read.Count(), uint32(2); g != e {
t.Errorf("wrong Read call count: %d != %d", g, e)
}
}
type invalidateEntryRoot struct {
fs.NodeRef
t testing.TB
@ -2380,13 +2795,13 @@ func (contextFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.O
func TestContext(t *testing.T) {
t.Parallel()
ctx := context.Background()
const input = "kilroy was here"
ctx = context.WithValue(ctx, &contextFileSentinel, input)
mnt, err := fstestutil.MountedT(t,
fstestutil.SimpleFS{&fstestutil.ChildMap{"child": contextFile{}}},
&fs.Config{
GetContext: func() context.Context { return ctx },
WithContext: func(ctx context.Context, req fuse.Request) context.Context {
return context.WithValue(ctx, &contextFileSentinel, input)
},
})
if err != nil {
t.Fatal(err)
@ -2401,3 +2816,28 @@ func TestContext(t *testing.T) {
t.Errorf("read wrong data: %q != %q", g, e)
}
}
type goexitFile struct {
fstestutil.File
}
func (goexitFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
log.Println("calling runtime.Goexit...")
runtime.Goexit()
panic("not reached")
}
func TestGoexit(t *testing.T) {
t.Parallel()
mnt, err := fstestutil.MountedT(t,
fstestutil.SimpleFS{&fstestutil.ChildMap{"child": goexitFile{}}}, nil)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
_, err = ioutil.ReadFile(mnt.Dir + "/child")
if nerr, ok := err.(*os.PathError); !ok || nerr.Err != syscall.EIO {
t.Fatalf("wrong error from exiting handler: %T: %v", err, err)
}
}

View file

@ -132,6 +132,18 @@ type Conn struct {
proto Protocol
}
// MountpointDoesNotExistError is an error returned when the
// mountpoint does not exist.
type MountpointDoesNotExistError struct {
Path string
}
var _ error = (*MountpointDoesNotExistError)(nil)
func (e *MountpointDoesNotExistError) Error() string {
return fmt.Sprintf("mountpoint does not exist: %v", e.Path)
}
// Mount mounts a new FUSE connection on the named directory
// and returns a connection for reading and writing FUSE messages.
//
@ -164,6 +176,13 @@ func Mount(dir string, options ...MountOption) (*Conn, error) {
if err := initMount(c, &conf); err != nil {
c.Close()
if err == ErrClosedWithoutInit {
// see if we can provide a better error
<-c.Ready
if err := c.MountError; err != nil {
return nil, err
}
}
return nil, err
}
@ -179,11 +198,15 @@ func (e *OldVersionError) Error() string {
return fmt.Sprintf("kernel FUSE version is too old: %v < %v", e.Kernel, e.LibraryMin)
}
var (
ErrClosedWithoutInit = errors.New("fuse connection closed without init")
)
func initMount(c *Conn, conf *mountConfig) error {
req, err := c.ReadRequest()
if err != nil {
if err == io.EOF {
return fmt.Errorf("missing init, got EOF")
return ErrClosedWithoutInit
}
return err
}
@ -212,7 +235,7 @@ func initMount(c *Conn, conf *mountConfig) error {
s := &InitResponse{
Library: proto,
MaxReadahead: conf.maxReadahead,
MaxWrite: 128 * 1024,
MaxWrite: maxWrite,
Flags: InitBigWrites | conf.initFlags,
}
r.Respond(s)
@ -235,15 +258,27 @@ type Request interface {
// A RequestID identifies an active FUSE request.
type RequestID uint64
func (r RequestID) String() string {
return fmt.Sprintf("%#x", uint64(r))
}
// A NodeID is a number identifying a directory or file.
// It must be unique among IDs returned in LookupResponses
// that have not yet been forgotten by ForgetRequests.
type NodeID uint64
func (n NodeID) String() string {
return fmt.Sprintf("%#x", uint64(n))
}
// A HandleID is a number identifying an open directory or file.
// It only needs to be unique while the directory or file is open.
type HandleID uint64
func (h HandleID) String() string {
return fmt.Sprintf("%#x", uint64(h))
}
// The RootID identifies the root directory of a FUSE file system.
const RootID NodeID = rootID
@ -261,7 +296,7 @@ type Header struct {
}
func (h *Header) String() string {
return fmt.Sprintf("ID=%#x Node=%#x Uid=%d Gid=%d Pid=%d", h.ID, h.Node, h.Uid, h.Gid, h.Pid)
return fmt.Sprintf("ID=%v Node=%v Uid=%d Gid=%d Pid=%d", h.ID, h.Node, h.Uid, h.Gid, h.Pid)
}
func (h *Header) Hdr() *Header {
@ -368,9 +403,6 @@ func (h *Header) RespondError(err error) {
h.respond(buf)
}
// Maximum file write size we are prepared to receive from the kernel.
const maxWrite = 16 * 1024 * 1024
// All requests read from the kernel, without data, are shorter than
// this.
var maxRequestSize = syscall.Getpagesize()
@ -985,7 +1017,33 @@ loop:
case opGetxtimes:
panic("opGetxtimes")
case opExchange:
panic("opExchange")
in := (*exchangeIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
oldDirNodeID := NodeID(in.Olddir)
newDirNodeID := NodeID(in.Newdir)
oldNew := m.bytes()[unsafe.Sizeof(*in):]
// oldNew should be "oldname\x00newname\x00"
if len(oldNew) < 4 {
goto corrupt
}
if oldNew[len(oldNew)-1] != '\x00' {
goto corrupt
}
i := bytes.IndexByte(oldNew, '\x00')
if i < 0 {
goto corrupt
}
oldName, newName := string(oldNew[:i]), string(oldNew[i+1:len(oldNew)-1])
req = &ExchangeDataRequest{
Header: m.Header(),
OldDir: oldDirNodeID,
NewDir: newDirNodeID,
OldName: oldName,
NewName: newName,
// TODO options
}
}
return req, nil
@ -1147,7 +1205,7 @@ type InitRequest struct {
var _ = Request(&InitRequest{})
func (r *InitRequest) String() string {
return fmt.Sprintf("Init [%s] %v ra=%d fl=%v", &r.Header, r.Kernel, r.MaxReadahead, r.Flags)
return fmt.Sprintf("Init [%v] %v ra=%d fl=%v", &r.Header, r.Kernel, r.MaxReadahead, r.Flags)
}
// An InitResponse is the response to an InitRequest.
@ -1163,7 +1221,7 @@ type InitResponse struct {
}
func (r *InitResponse) String() string {
return fmt.Sprintf("Init %+v", *r)
return fmt.Sprintf("Init %v ra=%d fl=%v w=%d", r.Library, r.MaxReadahead, r.Flags, r.MaxWrite)
}
// Respond replies to the request with the given response.
@ -1224,7 +1282,13 @@ type StatfsResponse struct {
}
func (r *StatfsResponse) String() string {
return fmt.Sprintf("Statfs %+v", *r)
return fmt.Sprintf("Statfs blocks=%d/%d/%d files=%d/%d bsize=%d frsize=%d namelen=%d",
r.Bavail, r.Bfree, r.Blocks,
r.Ffree, r.Files,
r.Bsize,
r.Frsize,
r.Namelen,
)
}
// An AccessRequest asks whether the file can be accessed
@ -1259,7 +1323,7 @@ type Attr struct {
Ctime time.Time // time of last inode change
Crtime time.Time // time of creation (OS X only)
Mode os.FileMode // file mode
Nlink uint32 // number of links
Nlink uint32 // number of links (usually 1)
Uid uint32 // owner uid
Gid uint32 // group gid
Rdev uint32 // device numbers
@ -1267,6 +1331,10 @@ type Attr struct {
BlockSize uint32 // preferred blocksize for filesystem I/O
}
func (a Attr) String() string {
return fmt.Sprintf("valid=%v ino=%v size=%d mode=%v", a.Valid, a.Inode, a.Size, a.Mode)
}
func unix(t time.Time) (sec uint64, nsec uint32) {
nano := t.UnixNano()
sec = uint64(nano / 1e9)
@ -1329,7 +1397,7 @@ type GetattrRequest struct {
var _ = Request(&GetattrRequest{})
func (r *GetattrRequest) String() string {
return fmt.Sprintf("Getattr [%s] %#x fl=%v", &r.Header, r.Handle, r.Flags)
return fmt.Sprintf("Getattr [%s] %v fl=%v", &r.Header, r.Handle, r.Flags)
}
// Respond replies to the request with the given response.
@ -1349,7 +1417,7 @@ type GetattrResponse struct {
}
func (r *GetattrResponse) String() string {
return fmt.Sprintf("Getattr %+v", *r)
return fmt.Sprintf("Getattr %v", r.Attr)
}
// A GetxattrRequest asks for the extended attributes associated with r.Node.
@ -1540,8 +1608,12 @@ type LookupResponse struct {
Attr Attr
}
func (r *LookupResponse) string() string {
return fmt.Sprintf("%v gen=%d valid=%v attr={%v}", r.Node, r.Generation, r.EntryValid, r.Attr)
}
func (r *LookupResponse) String() string {
return fmt.Sprintf("Lookup %+v", *r)
return fmt.Sprintf("Lookup %s", r.string())
}
// An OpenRequest asks to open a file or directory
@ -1572,8 +1644,12 @@ type OpenResponse struct {
Flags OpenResponseFlags
}
func (r *OpenResponse) string() string {
return fmt.Sprintf("%v fl=%v", r.Handle, r.Flags)
}
func (r *OpenResponse) String() string {
return fmt.Sprintf("Open %+v", *r)
return fmt.Sprintf("Open %s", r.string())
}
// A CreateRequest asks to create and open a file (not a directory).
@ -1582,7 +1658,8 @@ type CreateRequest struct {
Name string
Flags OpenFlags
Mode os.FileMode
Umask os.FileMode
// Umask of the request. Not supported on OS X.
Umask os.FileMode
}
var _ = Request(&CreateRequest{})
@ -1620,7 +1697,7 @@ type CreateResponse struct {
}
func (r *CreateResponse) String() string {
return fmt.Sprintf("Create %+v", *r)
return fmt.Sprintf("Create {%s} {%s}", r.LookupResponse.string(), r.OpenResponse.string())
}
// A MkdirRequest asks to create (but not open) a directory.
@ -1628,7 +1705,8 @@ type MkdirRequest struct {
Header `json:"-"`
Name string
Mode os.FileMode
Umask os.FileMode
// Umask of the request. Not supported on OS X.
Umask os.FileMode
}
var _ = Request(&MkdirRequest{})
@ -1658,7 +1736,7 @@ type MkdirResponse struct {
}
func (r *MkdirResponse) String() string {
return fmt.Sprintf("Mkdir %+v", *r)
return fmt.Sprintf("Mkdir %v", r.LookupResponse.string())
}
// A ReadRequest asks to read from an open file.
@ -1676,7 +1754,7 @@ type ReadRequest struct {
var _ = Request(&ReadRequest{})
func (r *ReadRequest) String() string {
return fmt.Sprintf("Read [%s] %#x %d @%#x dir=%v fl=%v lock=%d ffl=%v", &r.Header, r.Handle, r.Size, r.Offset, r.Dir, r.Flags, r.LockOwner, r.FileFlags)
return fmt.Sprintf("Read [%s] %v %d @%#x dir=%v fl=%v lock=%d ffl=%v", &r.Header, r.Handle, r.Size, r.Offset, r.Dir, r.Flags, r.LockOwner, r.FileFlags)
}
// Respond replies to the request with the given response.
@ -1719,7 +1797,7 @@ type ReleaseRequest struct {
var _ = Request(&ReleaseRequest{})
func (r *ReleaseRequest) String() string {
return fmt.Sprintf("Release [%s] %#x fl=%v rfl=%v owner=%#x", &r.Header, r.Handle, r.Flags, r.ReleaseFlags, r.LockOwner)
return fmt.Sprintf("Release [%s] %v fl=%v rfl=%v owner=%#x", &r.Header, r.Handle, r.Flags, r.ReleaseFlags, r.LockOwner)
}
// Respond replies to the request, indicating that the handle has been released.
@ -1861,7 +1939,7 @@ type WriteRequest struct {
var _ = Request(&WriteRequest{})
func (r *WriteRequest) String() string {
return fmt.Sprintf("Write [%s] %#x %d @%d fl=%v lock=%d ffl=%v", &r.Header, r.Handle, len(r.Data), r.Offset, r.Flags, r.LockOwner, r.FileFlags)
return fmt.Sprintf("Write [%s] %v %d @%d fl=%v lock=%d ffl=%v", &r.Header, r.Handle, len(r.Data), r.Offset, r.Flags, r.LockOwner, r.FileFlags)
}
type jsonWriteRequest struct {
@ -1895,7 +1973,7 @@ type WriteResponse struct {
}
func (r *WriteResponse) String() string {
return fmt.Sprintf("Write %+v", *r)
return fmt.Sprintf("Write %d", r.Size)
}
// A SetattrRequest asks to change one or more attributes associated with a file,
@ -1948,9 +2026,9 @@ func (r *SetattrRequest) String() string {
fmt.Fprintf(&buf, " mtime=now")
}
if r.Valid.Handle() {
fmt.Fprintf(&buf, " handle=%#x", r.Handle)
fmt.Fprintf(&buf, " handle=%v", r.Handle)
} else {
fmt.Fprintf(&buf, " handle=INVALID-%#x", r.Handle)
fmt.Fprintf(&buf, " handle=INVALID-%v", r.Handle)
}
if r.Valid.LockOwner() {
fmt.Fprintf(&buf, " lockowner")
@ -1965,7 +2043,7 @@ func (r *SetattrRequest) String() string {
fmt.Fprintf(&buf, " bkuptime=%v", r.Bkuptime)
}
if r.Valid.Flags() {
fmt.Fprintf(&buf, " flags=%#x", r.Flags)
fmt.Fprintf(&buf, " flags=%v", r.Flags)
}
return buf.String()
}
@ -1988,7 +2066,7 @@ type SetattrResponse struct {
}
func (r *SetattrResponse) String() string {
return fmt.Sprintf("Setattr %+v", *r)
return fmt.Sprintf("Setattr %v", r.Attr)
}
// A FlushRequest asks for the current state of an open file to be flushed
@ -2004,7 +2082,7 @@ type FlushRequest struct {
var _ = Request(&FlushRequest{})
func (r *FlushRequest) String() string {
return fmt.Sprintf("Flush [%s] %#x fl=%#x lk=%#x", &r.Header, r.Handle, r.Flags, r.LockOwner)
return fmt.Sprintf("Flush [%s] %v fl=%#x lk=%#x", &r.Header, r.Handle, r.Flags, r.LockOwner)
}
// Respond replies to the request, indicating that the flush succeeded.
@ -2065,6 +2143,10 @@ type SymlinkResponse struct {
LookupResponse
}
func (r *SymlinkResponse) String() string {
return fmt.Sprintf("Symlink %v", r.LookupResponse.string())
}
// A ReadlinkRequest is a request to read a symlink's target.
type ReadlinkRequest struct {
Header `json:"-"`
@ -2119,7 +2201,7 @@ type RenameRequest struct {
var _ = Request(&RenameRequest{})
func (r *RenameRequest) String() string {
return fmt.Sprintf("Rename [%s] from %q to dirnode %d %q", &r.Header, r.OldName, r.NewDir, r.NewName)
return fmt.Sprintf("Rename [%s] from %q to dirnode %v %q", &r.Header, r.OldName, r.NewDir, r.NewName)
}
func (r *RenameRequest) Respond() {
@ -2132,7 +2214,8 @@ type MknodRequest struct {
Name string
Mode os.FileMode
Rdev uint32
Umask os.FileMode
// Umask of the request. Not supported on OS X.
Umask os.FileMode
}
var _ = Request(&MknodRequest{})
@ -2191,3 +2274,30 @@ func (r *InterruptRequest) Respond() {
func (r *InterruptRequest) String() string {
return fmt.Sprintf("Interrupt [%s] ID %v", &r.Header, r.IntrID)
}
// An ExchangeDataRequest is a request to exchange the contents of two
// files, while leaving most metadata untouched.
//
// This request comes from OS X exchangedata(2) and represents its
// specific semantics. Crucially, it is very different from Linux
// renameat(2) RENAME_EXCHANGE.
//
// https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/exchangedata.2.html
type ExchangeDataRequest struct {
Header `json:"-"`
OldDir, NewDir NodeID
OldName, NewName string
// TODO options
}
var _ = Request(&ExchangeDataRequest{})
func (r *ExchangeDataRequest) String() string {
// TODO options
return fmt.Sprintf("ExchangeData [%s] %v %q and %v %q", &r.Header, r.OldDir, r.OldName, r.NewDir, r.NewName)
}
func (r *ExchangeDataRequest) Respond() {
buf := newBuffer(0)
r.respond(buf)
}

View file

@ -0,0 +1,9 @@
package fuse
// Maximum file write size we are prepared to receive from the kernel.
//
// This value has to be >=16MB or OSXFUSE (3.4.0 observed) will
// forcibly close the /dev/fuse file descriptor on a Setxattr with a
// 16MB value. See TestSetxattr16MB and
// https://github.com/bazil/fuse/issues/42
const maxWrite = 16 * 1024 * 1024

View file

@ -0,0 +1,6 @@
package fuse
// Maximum file write size we are prepared to receive from the kernel.
//
// This number is just a guess.
const maxWrite = 128 * 1024

View file

@ -62,7 +62,7 @@ type kstatfs struct {
Bsize uint32
Namelen uint32
Frsize uint32
Padding uint32
_ uint32
Spare [6]uint32
}
@ -159,9 +159,13 @@ const (
OpenWriteOnly OpenFlags = syscall.O_WRONLY
OpenReadWrite OpenFlags = syscall.O_RDWR
// File was opened in append-only mode, all writes will go to end
// of file. OS X does not provide this information.
OpenAppend OpenFlags = syscall.O_APPEND
OpenCreate OpenFlags = syscall.O_CREAT
OpenDirectory OpenFlags = syscall.O_DIRECTORY
OpenExclusive OpenFlags = syscall.O_EXCL
OpenNonblock OpenFlags = syscall.O_NONBLOCK
OpenSync OpenFlags = syscall.O_SYNC
OpenTruncate OpenFlags = syscall.O_TRUNC
)
@ -213,11 +217,13 @@ func accModeName(flags OpenFlags) string {
}
var openFlagNames = []flagName{
{uint32(OpenCreate), "OpenCreate"},
{uint32(OpenExclusive), "OpenExclusive"},
{uint32(OpenTruncate), "OpenTruncate"},
{uint32(OpenAppend), "OpenAppend"},
{uint32(OpenCreate), "OpenCreate"},
{uint32(OpenDirectory), "OpenDirectory"},
{uint32(OpenExclusive), "OpenExclusive"},
{uint32(OpenNonblock), "OpenNonblock"},
{uint32(OpenSync), "OpenSync"},
{uint32(OpenTruncate), "OpenTruncate"},
}
// The OpenResponseFlags are returned in the OpenResponse.
@ -248,12 +254,13 @@ var openResponseFlagNames = []flagName{
type InitFlags uint32
const (
InitAsyncRead InitFlags = 1 << 0
InitPosixLocks InitFlags = 1 << 1
InitFileOps InitFlags = 1 << 2
InitAtomicTrunc InitFlags = 1 << 3
InitExportSupport InitFlags = 1 << 4
InitBigWrites InitFlags = 1 << 5
InitAsyncRead InitFlags = 1 << 0
InitPosixLocks InitFlags = 1 << 1
InitFileOps InitFlags = 1 << 2
InitAtomicTrunc InitFlags = 1 << 3
InitExportSupport InitFlags = 1 << 4
InitBigWrites InitFlags = 1 << 5
// Do not mask file access modes with umask. Not supported on OS X.
InitDontMask InitFlags = 1 << 6
InitSpliceWrite InitFlags = 1 << 7
InitSpliceMove InitFlags = 1 << 8
@ -412,14 +419,14 @@ type forgetIn struct {
type getattrIn struct {
GetattrFlags uint32
dummy uint32
_ uint32
Fh uint64
}
type attrOut struct {
AttrValid uint64 // Cache timeout for the attributes
AttrValidNsec uint32
Dummy uint32
_ uint32
Attr attr
}
@ -441,10 +448,10 @@ type getxtimesOut struct {
}
type mknodIn struct {
Mode uint32
Rdev uint32
Umask uint32
padding uint32
Mode uint32
Rdev uint32
Umask uint32
_ uint32
// "filename\x00" follows.
}
@ -482,6 +489,7 @@ type exchangeIn struct {
Olddir uint64
Newdir uint64
Options uint64
// "oldname\x00newname\x00" follows
}
type linkIn struct {
@ -490,7 +498,7 @@ type linkIn struct {
type setattrInCommon struct {
Valid uint32
Padding uint32
_ uint32
Fh uint64
Size uint64
LockOwner uint64 // unused on OS X?
@ -515,14 +523,14 @@ type openIn struct {
type openOut struct {
Fh uint64
OpenFlags uint32
Padding uint32
_ uint32
}
type createIn struct {
Flags uint32
Mode uint32
Umask uint32
padding uint32
Flags uint32
Mode uint32
Umask uint32
_ uint32
}
func createInSize(p Protocol) uintptr {
@ -544,7 +552,7 @@ type releaseIn struct {
type flushIn struct {
Fh uint64
FlushFlags uint32
Padding uint32
_ uint32
LockOwner uint64
}
@ -555,7 +563,7 @@ type readIn struct {
ReadFlags uint32
LockOwner uint64
Flags uint32
padding uint32
_ uint32
}
func readInSize(p Protocol) uintptr {
@ -590,7 +598,7 @@ type writeIn struct {
WriteFlags uint32
LockOwner uint64
Flags uint32
padding uint32
_ uint32
}
func writeInSize(p Protocol) uintptr {
@ -603,8 +611,8 @@ func writeInSize(p Protocol) uintptr {
}
type writeOut struct {
Size uint32
Padding uint32
Size uint32
_ uint32
}
// The WriteFlags are passed in WriteRequest.
@ -634,7 +642,7 @@ type statfsOut struct {
type fsyncIn struct {
Fh uint64
FsyncFlags uint32
Padding uint32
_ uint32
}
type setxattrInCommon struct {
@ -647,8 +655,8 @@ func (setxattrInCommon) position() uint32 {
}
type getxattrInCommon struct {
Size uint32
Padding uint32
Size uint32
_ uint32
}
func (getxattrInCommon) position() uint32 {
@ -656,8 +664,8 @@ func (getxattrInCommon) position() uint32 {
}
type getxattrOut struct {
Size uint32
Padding uint32
Size uint32
_ uint32
}
type lkIn struct {
@ -665,7 +673,7 @@ type lkIn struct {
Owner uint64
Lk fileLock
LkFlags uint32
padding uint32
_ uint32
}
func lkInSize(p Protocol) uintptr {
@ -682,8 +690,8 @@ type lkOut struct {
}
type accessIn struct {
Mask uint32
Padding uint32
Mask uint32
_ uint32
}
type initIn struct {
@ -711,7 +719,7 @@ type interruptIn struct {
type bmapIn struct {
Block uint64
BlockSize uint32
Padding uint32
_ uint32
}
type bmapOut struct {
@ -719,14 +727,14 @@ type bmapOut struct {
}
type inHeader struct {
Len uint32
Opcode uint32
Unique uint64
Nodeid uint64
Uid uint32
Gid uint32
Pid uint32
Padding uint32
Len uint32
Opcode uint32
Unique uint64
Nodeid uint64
Uid uint32
Gid uint32
Pid uint32
_ uint32
}
const inHeaderSize = int(unsafe.Sizeof(inHeader{}))
@ -762,5 +770,5 @@ type notifyInvalInodeOut struct {
type notifyInvalEntryOut struct {
Parent uint64
Namelen uint32
padding uint32
_ uint32
}

View file

@ -7,7 +7,7 @@ import (
"bazil.org/fuse"
)
func TestOpenFlagsAccmodeMask(t *testing.T) {
func TestOpenFlagsAccmodeMaskReadWrite(t *testing.T) {
var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC)
if g, e := f&fuse.OpenAccessModeMask, fuse.OpenReadWrite; g != e {
t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e)
@ -23,6 +23,38 @@ func TestOpenFlagsAccmodeMask(t *testing.T) {
}
}
func TestOpenFlagsAccmodeMaskReadOnly(t *testing.T) {
var f = fuse.OpenFlags(os.O_RDONLY | os.O_SYNC)
if g, e := f&fuse.OpenAccessModeMask, fuse.OpenReadOnly; g != e {
t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e)
}
if !f.IsReadOnly() {
t.Fatalf("IsReadOnly is wrong: %v", f)
}
if f.IsWriteOnly() {
t.Fatalf("IsWriteOnly is wrong: %v", f)
}
if f.IsReadWrite() {
t.Fatalf("IsReadWrite is wrong: %v", f)
}
}
func TestOpenFlagsAccmodeMaskWriteOnly(t *testing.T) {
var f = fuse.OpenFlags(os.O_WRONLY | os.O_SYNC)
if g, e := f&fuse.OpenAccessModeMask, fuse.OpenWriteOnly; g != e {
t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e)
}
if f.IsReadOnly() {
t.Fatalf("IsReadOnly is wrong: %v", f)
}
if !f.IsWriteOnly() {
t.Fatalf("IsWriteOnly is wrong: %v", f)
}
if f.IsReadWrite() {
t.Fatalf("IsReadWrite is wrong: %v", f)
}
}
func TestOpenFlagsString(t *testing.T) {
var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC | os.O_APPEND)
if g, e := f.String(), "OpenReadWrite+OpenAppend+OpenSync"; g != e {

View file

@ -0,0 +1,7 @@
package fuse
// Maximum file write size we are prepared to receive from the kernel.
//
// Linux 4.2.0 has been observed to cap this value at 128kB
// (FUSE_MAX_PAGES_PER_REQ=32, 4kB pages).
const maxWrite = 128 * 1024

38
vendor/src/bazil.org/fuse/mount.go vendored Normal file
View file

@ -0,0 +1,38 @@
package fuse
import (
"bufio"
"errors"
"io"
"log"
"sync"
)
var (
// ErrOSXFUSENotFound is returned from Mount when the OSXFUSE
// installation is not detected.
//
// Only happens on OS X. Make sure OSXFUSE is installed, or see
// OSXFUSELocations for customization.
ErrOSXFUSENotFound = errors.New("cannot locate OSXFUSE")
)
func neverIgnoreLine(line string) bool {
return false
}
func lineLogger(wg *sync.WaitGroup, prefix string, ignore func(line string) bool, r io.ReadCloser) {
defer wg.Done()
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
if ignore(line) {
continue
}
log.Printf("%s: %s", prefix, line)
}
if err := scanner.Err(); err != nil {
log.Printf("%s, error reading: %v", prefix, err)
}
}

View file

@ -1,22 +1,25 @@
package fuse
import (
"bytes"
"errors"
"fmt"
"log"
"os"
"os/exec"
"path"
"strconv"
"strings"
"sync"
"syscall"
)
var errNoAvail = errors.New("no available fuse devices")
var (
errNoAvail = errors.New("no available fuse devices")
errNotLoaded = errors.New("osxfuse is not loaded")
)
var errNotLoaded = errors.New("osxfusefs is not loaded")
func loadOSXFUSE() error {
cmd := exec.Command("/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs")
func loadOSXFUSE(bin string) error {
cmd := exec.Command(bin)
cmd.Dir = "/"
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@ -24,11 +27,11 @@ func loadOSXFUSE() error {
return err
}
func openOSXFUSEDev() (*os.File, error) {
func openOSXFUSEDev(devPrefix string) (*os.File, error) {
var f *os.File
var err error
for i := uint64(0); ; i++ {
path := "/dev/osxfuse" + strconv.FormatUint(i, 10)
path := devPrefix + strconv.FormatUint(i, 10)
f, err = os.OpenFile(path, os.O_RDWR, 0000)
if os.IsNotExist(err) {
if i == 0 {
@ -52,9 +55,42 @@ func openOSXFUSEDev() (*os.File, error) {
}
}
func callMount(dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, errp *error) error {
bin := "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs"
func handleMountOSXFUSE(helperName string, errCh chan<- error) func(line string) (ignore bool) {
var noMountpointPrefix = helperName + `: `
const noMountpointSuffix = `: No such file or directory`
return func(line string) (ignore bool) {
if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) {
// re-extract it from the error message in case some layer
// changed the path
mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)]
err := &MountpointDoesNotExistError{
Path: mountpoint,
}
select {
case errCh <- err:
return true
default:
// not the first error; fall back to logging it
return false
}
}
return false
}
}
// isBoringMountOSXFUSEError returns whether the Wait error is
// uninteresting; exit status 64 is.
func isBoringMountOSXFUSEError(err error) bool {
if err, ok := err.(*exec.ExitError); ok && err.Exited() {
if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 64 {
return true
}
}
return false
}
func callMount(bin string, daemonVar string, dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, errp *error) error {
for k, v := range conf.options {
if strings.Contains(k, ",") || strings.Contains(v, ",") {
// Silly limitation but the mount helper does not
@ -77,50 +113,96 @@ func callMount(dir string, conf *mountConfig, f *os.File, ready chan<- struct{},
)
cmd.ExtraFiles = []*os.File{f}
cmd.Env = os.Environ()
// OSXFUSE <3.3.0
cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=")
// TODO this is used for fs typenames etc, let app influence it
cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_DAEMON_PATH="+bin)
var buf bytes.Buffer
cmd.Stdout = &buf
cmd.Stderr = &buf
// OSXFUSE >=3.3.0
cmd.Env = append(cmd.Env, "MOUNT_OSXFUSE_CALL_BY_LIB=")
err := cmd.Start()
if err != nil {
return err
daemon := os.Args[0]
if daemonVar != "" {
cmd.Env = append(cmd.Env, daemonVar+"="+daemon)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("setting up mount_osxfusefs stderr: %v", err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
return fmt.Errorf("setting up mount_osxfusefs stderr: %v", err)
}
if err := cmd.Start(); err != nil {
return fmt.Errorf("mount_osxfusefs: %v", err)
}
helperErrCh := make(chan error, 1)
go func() {
err := cmd.Wait()
if err != nil {
if buf.Len() > 0 {
output := buf.Bytes()
output = bytes.TrimRight(output, "\n")
msg := err.Error() + ": " + string(output)
err = errors.New(msg)
var wg sync.WaitGroup
wg.Add(2)
go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout)
helperName := path.Base(bin)
go lineLogger(&wg, "mount helper error", handleMountOSXFUSE(helperName, helperErrCh), stderr)
wg.Wait()
if err := cmd.Wait(); err != nil {
// see if we have a better error to report
select {
case helperErr := <-helperErrCh:
// log the Wait error if it's not what we expected
if !isBoringMountOSXFUSEError(err) {
log.Printf("mount helper failed: %v", err)
}
// and now return what we grabbed from stderr as the real
// error
*errp = helperErr
close(ready)
return
default:
// nope, fall back to generic message
}
*errp = fmt.Errorf("mount_osxfusefs: %v", err)
close(ready)
return
}
*errp = err
*errp = nil
close(ready)
}()
return err
return nil
}
func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) {
f, err := openOSXFUSEDev()
if err == errNotLoaded {
err = loadOSXFUSE()
locations := conf.osxfuseLocations
if locations == nil {
locations = []OSXFUSEPaths{
OSXFUSELocationV3,
OSXFUSELocationV2,
}
}
for _, loc := range locations {
if _, err := os.Stat(loc.Mount); os.IsNotExist(err) {
// try the other locations
continue
}
f, err := openOSXFUSEDev(loc.DevicePrefix)
if err == errNotLoaded {
err = loadOSXFUSE(loc.Load)
if err != nil {
return nil, err
}
// try again
f, err = openOSXFUSEDev(loc.DevicePrefix)
}
if err != nil {
return nil, err
}
// try again
f, err = openOSXFUSEDev()
err = callMount(loc.Mount, loc.DaemonVar, dir, conf, f, ready, errp)
if err != nil {
f.Close()
return nil, err
}
return f, nil
}
if err != nil {
return nil, err
}
err = callMount(dir, conf, f, ready, errp)
if err != nil {
f.Close()
return nil, err
}
return f, nil
return nil, ErrOSXFUSENotFound
}

View file

@ -2,11 +2,51 @@ package fuse
import (
"fmt"
"log"
"os"
"os/exec"
"strings"
"sync"
"syscall"
)
func handleMountFusefsStderr(errCh chan<- error) func(line string) (ignore bool) {
return func(line string) (ignore bool) {
const (
noMountpointPrefix = `mount_fusefs: `
noMountpointSuffix = `: No such file or directory`
)
if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) {
// re-extract it from the error message in case some layer
// changed the path
mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)]
err := &MountpointDoesNotExistError{
Path: mountpoint,
}
select {
case errCh <- err:
return true
default:
// not the first error; fall back to logging it
return false
}
}
return false
}
}
// isBoringMountFusefsError returns whether the Wait error is
// uninteresting; exit status 1 is.
func isBoringMountFusefsError(err error) bool {
if err, ok := err.(*exec.ExitError); ok && err.Exited() {
if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 1 {
return true
}
}
return false
}
func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) {
for k, v := range conf.options {
if strings.Contains(k, ",") || strings.Contains(v, ",") {
@ -31,9 +71,39 @@ func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*
)
cmd.ExtraFiles = []*os.File{f}
out, err := cmd.CombinedOutput()
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("mount_fusefs: %q, %v", out, err)
return nil, fmt.Errorf("setting up mount_fusefs stderr: %v", err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
return nil, fmt.Errorf("setting up mount_fusefs stderr: %v", err)
}
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("mount_fusefs: %v", err)
}
helperErrCh := make(chan error, 1)
var wg sync.WaitGroup
wg.Add(2)
go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout)
go lineLogger(&wg, "mount helper error", handleMountFusefsStderr(helperErrCh), stderr)
wg.Wait()
if err := cmd.Wait(); err != nil {
// see if we have a better error to report
select {
case helperErr := <-helperErrCh:
// log the Wait error if it's not what we expected
if !isBoringMountFusefsError(err) {
log.Printf("mount helper failed: %v", err)
}
// and now return what we grabbed from stderr as the real
// error
return nil, helperErr
default:
// nope, fall back to generic message
}
return nil, fmt.Errorf("mount_fusefs: %v", err)
}
close(ready)

View file

@ -1,35 +1,58 @@
package fuse
import (
"bufio"
"fmt"
"io"
"log"
"net"
"os"
"os/exec"
"strings"
"sync"
"syscall"
)
func lineLogger(wg *sync.WaitGroup, prefix string, r io.ReadCloser) {
defer wg.Done()
scanner := bufio.NewScanner(r)
for scanner.Scan() {
switch line := scanner.Text(); line {
case `fusermount: failed to open /etc/fuse.conf: Permission denied`:
func handleFusermountStderr(errCh chan<- error) func(line string) (ignore bool) {
return func(line string) (ignore bool) {
if line == `fusermount: failed to open /etc/fuse.conf: Permission denied` {
// Silence this particular message, it occurs way too
// commonly and isn't very relevant to whether the mount
// succeeds or not.
continue
default:
log.Printf("%s: %s", prefix, line)
return true
}
const (
noMountpointPrefix = `fusermount: failed to access mountpoint `
noMountpointSuffix = `: No such file or directory`
)
if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) {
// re-extract it from the error message in case some layer
// changed the path
mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)]
err := &MountpointDoesNotExistError{
Path: mountpoint,
}
select {
case errCh <- err:
return true
default:
// not the first error; fall back to logging it
return false
}
}
return false
}
}
// isBoringFusermountError returns whether the Wait error is
// uninteresting; exit status 1 is.
func isBoringFusermountError(err error) bool {
if err, ok := err.(*exec.ExitError); ok && err.Exited() {
if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 1 {
return true
}
}
if err := scanner.Err(); err != nil {
log.Printf("%s, error reading: %v", prefix, err)
}
return false
}
func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) {
@ -70,11 +93,26 @@ func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (f
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("fusermount: %v", err)
}
helperErrCh := make(chan error, 1)
wg.Add(2)
go lineLogger(&wg, "mount helper output", stdout)
go lineLogger(&wg, "mount helper error", stderr)
go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout)
go lineLogger(&wg, "mount helper error", handleFusermountStderr(helperErrCh), stderr)
wg.Wait()
if err := cmd.Wait(); err != nil {
// see if we have a better error to report
select {
case helperErr := <-helperErrCh:
// log the Wait error if it's not what we expected
if !isBoringFusermountError(err) {
log.Printf("mount helper failed: %v", err)
}
// and now return what we grabbed from stderr as the real
// error
return nil, helperErr
default:
// nope, fall back to generic message
}
return nil, fmt.Errorf("fusermount: %v", err)
}

View file

@ -12,9 +12,10 @@ func dummyOption(conf *mountConfig) error {
// mountConfig holds the configuration for a mount operation.
// Use it by passing MountOption values to Mount.
type mountConfig struct {
options map[string]string
maxReadahead uint32
initFlags InitFlags
options map[string]string
maxReadahead uint32
initFlags InitFlags
osxfuseLocations []OSXFUSEPaths
}
func escapeComma(s string) string {
@ -82,6 +83,63 @@ func VolumeName(name string) MountOption {
return volumeName(name)
}
// NoAppleDouble makes OSXFUSE disallow files with names used by OS X
// to store extended attributes on file systems that do not support
// them natively.
//
// Such file names are:
//
// ._*
// .DS_Store
//
// OS X only. Others ignore this option.
func NoAppleDouble() MountOption {
return noAppleDouble
}
// NoAppleXattr makes OSXFUSE disallow extended attributes with the
// prefix "com.apple.". This disables persistent Finder state and
// other such information.
//
// OS X only. Others ignore this option.
func NoAppleXattr() MountOption {
return noAppleXattr
}
// ExclCreate causes O_EXCL flag to be set for only "truly" exclusive creates,
// i.e. create calls for which the initiator explicitly set the O_EXCL flag.
//
// OSXFUSE expects all create calls to return EEXIST in case the file
// already exists, regardless of whether O_EXCL was specified or not.
// To ensure this behavior, it normally sets OpenExclusive for all
// Create calls, regardless of whether the original call had it set.
// For distributed filesystems, that may force every file create to be
// a distributed consensus action, causing undesirable delays.
//
// This option makes the FUSE filesystem see the original flag value,
// and better decide when to ensure global consensus.
//
// Note that returning EEXIST on existing file create is still
// expected with OSXFUSE, regardless of the presence of the
// OpenExclusive flag.
//
// For more information, see
// https://github.com/osxfuse/osxfuse/issues/209
//
// OS X only. Others ignore this options.
// Requires OSXFUSE 3.4.1 or newer.
func ExclCreate() MountOption {
return exclCreate
}
// DaemonTimeout sets the time in seconds between a request and a reply before
// the FUSE mount is declared dead.
//
// OS X and FreeBSD only. Others ignore this option.
func DaemonTimeout(name string) MountOption {
return daemonTimeout(name)
}
var ErrCannotCombineAllowOtherAndAllowRoot = errors.New("cannot combine AllowOther and AllowRoot")
// AllowOther allows other users to access the file system.
@ -112,6 +170,24 @@ func AllowRoot() MountOption {
}
}
// AllowDev enables interpreting character or block special devices on the
// filesystem.
func AllowDev() MountOption {
return func(conf *mountConfig) error {
conf.options["dev"] = ""
return nil
}
}
// AllowSUID allows set-user-identifier or set-group-identifier bits to take
// effect.
func AllowSUID() MountOption {
return func(conf *mountConfig) error {
conf.options["suid"] = ""
return nil
}
}
// DefaultPermissions makes the kernel enforce access control based on
// the file mode (as in chmod).
//
@ -168,3 +244,67 @@ func WritebackCache() MountOption {
return nil
}
}
// OSXFUSEPaths describes the paths used by an installed OSXFUSE
// version. See OSXFUSELocationV3 for typical values.
type OSXFUSEPaths struct {
// Prefix for the device file. At mount time, an incrementing
// number is suffixed until a free FUSE device is found.
DevicePrefix string
// Path of the load helper, used to load the kernel extension if
// no device files are found.
Load string
// Path of the mount helper, used for the actual mount operation.
Mount string
// Environment variable used to pass the path to the executable
// calling the mount helper.
DaemonVar string
}
// Default paths for OSXFUSE. See OSXFUSELocations.
var (
OSXFUSELocationV3 = OSXFUSEPaths{
DevicePrefix: "/dev/osxfuse",
Load: "/Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse",
Mount: "/Library/Filesystems/osxfuse.fs/Contents/Resources/mount_osxfuse",
DaemonVar: "MOUNT_OSXFUSE_DAEMON_PATH",
}
OSXFUSELocationV2 = OSXFUSEPaths{
DevicePrefix: "/dev/osxfuse",
Load: "/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs",
Mount: "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs",
DaemonVar: "MOUNT_FUSEFS_DAEMON_PATH",
}
)
// OSXFUSELocations sets where to look for OSXFUSE files. The
// arguments are all the possible locations. The previous locations
// are replaced.
//
// Without this option, OSXFUSELocationV3 and OSXFUSELocationV2 are
// used.
//
// OS X only. Others ignore this option.
func OSXFUSELocations(paths ...OSXFUSEPaths) MountOption {
return func(conf *mountConfig) error {
if len(paths) == 0 {
return errors.New("must specify at least one location for OSXFUSELocations")
}
// replace previous values, but make a copy so there's no
// worries about caller mutating their slice
conf.osxfuseLocations = append(conf.osxfuseLocations[:0], paths...)
return nil
}
}
// AllowNonEmptyMount allows the mounting over a non-empty directory.
//
// The files in it will be shadowed by the freshly created mount. By
// default these mounts are rejected to prevent accidental covering up
// of data, which could for example prevent automatic backup.
func AllowNonEmptyMount() MountOption {
return func(conf *mountConfig) error {
conf.options["nonempty"] = ""
return nil
}
}

View file

@ -0,0 +1,64 @@
// Test for adjustable timeout between a FUSE request and the daemon's response.
//
// +build darwin freebsd
package fuse_test
import (
"os"
"runtime"
"syscall"
"testing"
"time"
"bazil.org/fuse"
"bazil.org/fuse/fs"
"bazil.org/fuse/fs/fstestutil"
"golang.org/x/net/context"
)
type slowCreaterDir struct {
fstestutil.Dir
}
var _ fs.NodeCreater = slowCreaterDir{}
func (c slowCreaterDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
time.Sleep(10 * time.Second)
// pick a really distinct error, to identify it later
return nil, nil, fuse.Errno(syscall.ENAMETOOLONG)
}
func TestMountOptionDaemonTimeout(t *testing.T) {
if runtime.GOOS != "darwin" && runtime.GOOS != "freebsd" {
return
}
if testing.Short() {
t.Skip("skipping time-based test in short mode")
}
t.Parallel()
mnt, err := fstestutil.MountedT(t,
fstestutil.SimpleFS{slowCreaterDir{}},
nil,
fuse.DaemonTimeout("2"),
)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
// This should fail by the kernel timing out the request.
f, err := os.Create(mnt.Dir + "/child")
if err == nil {
f.Close()
t.Fatal("expected an error")
}
perr, ok := err.(*os.PathError)
if !ok {
t.Fatalf("expected PathError, got %T: %v", err, err)
}
if perr.Err == syscall.ENAMETOOLONG {
t.Fatalf("expected other than ENAMETOOLONG, got %T: %v", err, err)
}
}

View file

@ -11,3 +11,25 @@ func volumeName(name string) MountOption {
return nil
}
}
func daemonTimeout(name string) MountOption {
return func(conf *mountConfig) error {
conf.options["daemon_timeout"] = name
return nil
}
}
func noAppleXattr(conf *mountConfig) error {
conf.options["noapplexattr"] = ""
return nil
}
func noAppleDouble(conf *mountConfig) error {
conf.options["noappledouble"] = ""
return nil
}
func exclCreate(conf *mountConfig) error {
conf.options["excl_create"] = ""
return nil
}

View file

@ -7,3 +7,22 @@ func localVolume(conf *mountConfig) error {
func volumeName(name string) MountOption {
return dummyOption
}
func daemonTimeout(name string) MountOption {
return func(conf *mountConfig) error {
conf.options["timeout"] = name
return nil
}
}
func noAppleXattr(conf *mountConfig) error {
return nil
}
func noAppleDouble(conf *mountConfig) error {
return nil
}
func exclCreate(conf *mountConfig) error {
return nil
}

View file

@ -7,3 +7,19 @@ func localVolume(conf *mountConfig) error {
func volumeName(name string) MountOption {
return dummyOption
}
func daemonTimeout(name string) MountOption {
return dummyOption
}
func noAppleXattr(conf *mountConfig) error {
return nil
}
func noAppleDouble(conf *mountConfig) error {
return nil
}
func exclCreate(conf *mountConfig) error {
return nil
}

View file

@ -165,6 +165,7 @@ func TestMountOptionDefaultPermissions(t *testing.T) {
t.Skip("FreeBSD does not support DefaultPermissions")
}
t.Parallel()
mnt, err := fstestutil.MountedT(t,
fstestutil.SimpleFS{
&fstestutil.ChildMap{"child": unwritableFile{}},
@ -172,7 +173,6 @@ func TestMountOptionDefaultPermissions(t *testing.T) {
nil,
fuse.DefaultPermissions(),
)
if err != nil {
t.Fatal(err)
}
@ -203,12 +203,12 @@ func (createrDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fus
func TestMountOptionReadOnly(t *testing.T) {
t.Parallel()
mnt, err := fstestutil.MountedT(t,
fstestutil.SimpleFS{createrDir{}},
nil,
fuse.ReadOnly(),
)
if err != nil {
t.Fatal(err)
}

View file

@ -33,9 +33,11 @@ The flags package uses structs, reflection and struct field tags
to allow users to specify command line options. This results in very simple
and concise specification of your application options. For example:
type Options struct {
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
}
```go
type Options struct {
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
}
```
This specifies one option with a short name -v and a long name --verbose.
When either -v or --verbose is found on the command line, a 'true' value
@ -44,88 +46,90 @@ resulting value of Verbose will be {[true, true, true]}.
Example:
--------
var opts struct {
// Slice of bool will append 'true' each time the option
// is encountered (can be set multiple times, like -vvv)
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
```go
var opts struct {
// Slice of bool will append 'true' each time the option
// is encountered (can be set multiple times, like -vvv)
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
// Example of automatic marshalling to desired type (uint)
Offset uint `long:"offset" description:"Offset"`
// Example of automatic marshalling to desired type (uint)
Offset uint `long:"offset" description:"Offset"`
// Example of a callback, called each time the option is found.
Call func(string) `short:"c" description:"Call phone number"`
// Example of a callback, called each time the option is found.
Call func(string) `short:"c" description:"Call phone number"`
// Example of a required flag
Name string `short:"n" long:"name" description:"A name" required:"true"`
// Example of a required flag
Name string `short:"n" long:"name" description:"A name" required:"true"`
// Example of a value name
File string `short:"f" long:"file" description:"A file" value-name:"FILE"`
// Example of a value name
File string `short:"f" long:"file" description:"A file" value-name:"FILE"`
// Example of a pointer
Ptr *int `short:"p" description:"A pointer to an integer"`
// Example of a pointer
Ptr *int `short:"p" description:"A pointer to an integer"`
// Example of a slice of strings
StringSlice []string `short:"s" description:"A slice of strings"`
// Example of a slice of strings
StringSlice []string `short:"s" description:"A slice of strings"`
// Example of a slice of pointers
PtrSlice []*string `long:"ptrslice" description:"A slice of pointers to string"`
// Example of a slice of pointers
PtrSlice []*string `long:"ptrslice" description:"A slice of pointers to string"`
// Example of a map
IntMap map[string]int `long:"intmap" description:"A map from string to int"`
}
// Example of a map
IntMap map[string]int `long:"intmap" description:"A map from string to int"`
}
// Callback which will invoke callto:<argument> to call a number.
// Note that this works just on OS X (and probably only with
// Skype) but it shows the idea.
opts.Call = func(num string) {
cmd := exec.Command("open", "callto:"+num)
cmd.Start()
cmd.Process.Release()
}
// Callback which will invoke callto:<argument> to call a number.
// Note that this works just on OS X (and probably only with
// Skype) but it shows the idea.
opts.Call = func(num string) {
cmd := exec.Command("open", "callto:"+num)
cmd.Start()
cmd.Process.Release()
}
// Make some fake arguments to parse.
args := []string{
"-vv",
"--offset=5",
"-n", "Me",
"-p", "3",
"-s", "hello",
"-s", "world",
"--ptrslice", "hello",
"--ptrslice", "world",
"--intmap", "a:1",
"--intmap", "b:5",
"arg1",
"arg2",
"arg3",
}
// Make some fake arguments to parse.
args := []string{
"-vv",
"--offset=5",
"-n", "Me",
"-p", "3",
"-s", "hello",
"-s", "world",
"--ptrslice", "hello",
"--ptrslice", "world",
"--intmap", "a:1",
"--intmap", "b:5",
"arg1",
"arg2",
"arg3",
}
// Parse flags from `args'. Note that here we use flags.ParseArgs for
// the sake of making a working example. Normally, you would simply use
// flags.Parse(&opts) which uses os.Args
args, err := flags.ParseArgs(&opts, args)
// Parse flags from `args'. Note that here we use flags.ParseArgs for
// the sake of making a working example. Normally, you would simply use
// flags.Parse(&opts) which uses os.Args
args, err := flags.ParseArgs(&opts, args)
if err != nil {
panic(err)
os.Exit(1)
}
if err != nil {
panic(err)
os.Exit(1)
}
fmt.Printf("Verbosity: %v\n", opts.Verbose)
fmt.Printf("Offset: %d\n", opts.Offset)
fmt.Printf("Name: %s\n", opts.Name)
fmt.Printf("Ptr: %d\n", *opts.Ptr)
fmt.Printf("StringSlice: %v\n", opts.StringSlice)
fmt.Printf("PtrSlice: [%v %v]\n", *opts.PtrSlice[0], *opts.PtrSlice[1])
fmt.Printf("IntMap: [a:%v b:%v]\n", opts.IntMap["a"], opts.IntMap["b"])
fmt.Printf("Remaining args: %s\n", strings.Join(args, " "))
fmt.Printf("Verbosity: %v\n", opts.Verbose)
fmt.Printf("Offset: %d\n", opts.Offset)
fmt.Printf("Name: %s\n", opts.Name)
fmt.Printf("Ptr: %d\n", *opts.Ptr)
fmt.Printf("StringSlice: %v\n", opts.StringSlice)
fmt.Printf("PtrSlice: [%v %v]\n", *opts.PtrSlice[0], *opts.PtrSlice[1])
fmt.Printf("IntMap: [a:%v b:%v]\n", opts.IntMap["a"], opts.IntMap["b"])
fmt.Printf("Remaining args: %s\n", strings.Join(args, " "))
// Output: Verbosity: [true true]
// Offset: 5
// Name: Me
// Ptr: 3
// StringSlice: [hello world]
// PtrSlice: [hello world]
// IntMap: [a:1 b:5]
// Remaining args: arg1 arg2 arg3
// Output: Verbosity: [true true]
// Offset: 5
// Name: Me
// Ptr: 3
// StringSlice: [hello world]
// PtrSlice: [hello world]
// IntMap: [a:1 b:5]
// Remaining args: arg1 arg2 arg3
```
More information can be found in the godocs: <http://godoc.org/github.com/jessevdk/go-flags>

View file

@ -12,6 +12,12 @@ type Arg struct {
// A description of the positional argument (used in the help)
Description string
// The minimal number of required positional arguments
Required int
// The maximum number of required positional arguments
RequiredMaximum int
value reflect.Value
tag multiTag
}

View file

@ -51,3 +51,113 @@ func TestPositionalRequired(t *testing.T) {
assertError(t, err, ErrRequired, "the required argument `Filename` was not provided")
}
func TestPositionalRequiredRest1Fail(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Positional struct {
Rest []string `required:"yes"`
} `positional-args:"yes"`
}{}
p := NewParser(&opts, None)
_, err := p.ParseArgs([]string{})
assertError(t, err, ErrRequired, "the required argument `Rest (at least 1 argument)` was not provided")
}
func TestPositionalRequiredRest1Pass(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Positional struct {
Rest []string `required:"yes"`
} `positional-args:"yes"`
}{}
p := NewParser(&opts, None)
_, err := p.ParseArgs([]string{"rest1"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
return
}
if len(opts.Positional.Rest) != 1 {
t.Fatalf("Expected 1 positional rest argument")
}
assertString(t, opts.Positional.Rest[0], "rest1")
}
func TestPositionalRequiredRest2Fail(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Positional struct {
Rest []string `required:"2"`
} `positional-args:"yes"`
}{}
p := NewParser(&opts, None)
_, err := p.ParseArgs([]string{"rest1"})
assertError(t, err, ErrRequired, "the required argument `Rest (at least 2 arguments, but got only 1)` was not provided")
}
func TestPositionalRequiredRest2Pass(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Positional struct {
Rest []string `required:"2"`
} `positional-args:"yes"`
}{}
p := NewParser(&opts, None)
_, err := p.ParseArgs([]string{"rest1", "rest2", "rest3"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
return
}
if len(opts.Positional.Rest) != 3 {
t.Fatalf("Expected 3 positional rest argument")
}
assertString(t, opts.Positional.Rest[0], "rest1")
assertString(t, opts.Positional.Rest[1], "rest2")
assertString(t, opts.Positional.Rest[2], "rest3")
}
func TestPositionalRequiredRestRangeFail(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Positional struct {
Rest []string `required:"1-2"`
} `positional-args:"yes"`
}{}
p := NewParser(&opts, None)
_, err := p.ParseArgs([]string{"rest1", "rest2", "rest3"})
assertError(t, err, ErrRequired, "the required argument `Rest (at most 2 arguments, but got 3)` was not provided")
}
func TestPositionalRequiredRestRangeEmptyFail(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Positional struct {
Rest []string `required:"0-0"`
} `positional-args:"yes"`
}{}
p := NewParser(&opts, None)
_, err := p.ParseArgs([]string{"some", "thing"})
assertError(t, err, ErrRequired, "the required argument `Rest (zero arguments)` was not provided")
}

View file

@ -1,5 +1,13 @@
package flags
import (
"reflect"
"sort"
"strconv"
"strings"
"unsafe"
)
// Command represents an application command. Commands can be added to the
// parser (which itself is a command) and are selected/executed when its name
// is specified on the command line. The Command type embeds a Group and
@ -47,6 +55,13 @@ type Usage interface {
Usage() string
}
type lookup struct {
shortNames map[string]*Option
longNames map[string]*Option
commands map[string]*Command
}
// AddCommand adds a new command to the parser with the given name and data. The
// data needs to be a pointer to a struct from which the fields indicate which
// options are in the command. The provided data can implement the Command and
@ -97,6 +112,32 @@ func (c *Command) Find(name string) *Command {
return nil
}
// FindOptionByLongName finds an option that is part of the command, or any of
// its parent commands, by matching its long name (including the option
// namespace).
func (c *Command) FindOptionByLongName(longName string) (option *Option) {
for option == nil && c != nil {
option = c.Group.FindOptionByLongName(longName)
c, _ = c.parent.(*Command)
}
return option
}
// FindOptionByShortName finds an option that is part of the command, or any of
// its parent commands, by matching its long name (including the option
// namespace).
func (c *Command) FindOptionByShortName(shortName rune) (option *Option) {
for option == nil && c != nil {
option = c.Group.FindOptionByShortName(shortName)
c, _ = c.parent.(*Command)
}
return option
}
// Args returns a list of positional arguments associated with this command.
func (c *Command) Args() []*Arg {
ret := make([]*Arg, len(c.args))
@ -104,3 +145,311 @@ func (c *Command) Args() []*Arg {
return ret
}
func newCommand(name string, shortDescription string, longDescription string, data interface{}) *Command {
return &Command{
Group: newGroup(shortDescription, longDescription, data),
Name: name,
}
}
func (c *Command) scanSubcommandHandler(parentg *Group) scanHandler {
f := func(realval reflect.Value, sfield *reflect.StructField) (bool, error) {
mtag := newMultiTag(string(sfield.Tag))
if err := mtag.Parse(); err != nil {
return true, err
}
positional := mtag.Get("positional-args")
if len(positional) != 0 {
stype := realval.Type()
for i := 0; i < stype.NumField(); i++ {
field := stype.Field(i)
m := newMultiTag((string(field.Tag)))
if err := m.Parse(); err != nil {
return true, err
}
name := m.Get("positional-arg-name")
if len(name) == 0 {
name = field.Name
}
required := -1
requiredMaximum := -1
sreq := m.Get("required")
if sreq != "" {
required = 1
rng := strings.SplitN(sreq, "-", 2)
if len(rng) > 1 {
if preq, err := strconv.ParseInt(rng[0], 10, 32); err == nil {
required = int(preq)
}
if preq, err := strconv.ParseInt(rng[1], 10, 32); err == nil {
requiredMaximum = int(preq)
}
} else {
if preq, err := strconv.ParseInt(sreq, 10, 32); err == nil {
required = int(preq)
}
}
}
arg := &Arg{
Name: name,
Description: m.Get("description"),
Required: required,
RequiredMaximum: requiredMaximum,
value: realval.Field(i),
tag: m,
}
c.args = append(c.args, arg)
if len(mtag.Get("required")) != 0 {
c.ArgsRequired = true
}
}
return true, nil
}
subcommand := mtag.Get("command")
if len(subcommand) != 0 {
ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr()))
shortDescription := mtag.Get("description")
longDescription := mtag.Get("long-description")
subcommandsOptional := mtag.Get("subcommands-optional")
aliases := mtag.GetMany("alias")
subc, err := c.AddCommand(subcommand, shortDescription, longDescription, ptrval.Interface())
if err != nil {
return true, err
}
subc.Hidden = mtag.Get("hidden") != ""
if len(subcommandsOptional) > 0 {
subc.SubcommandsOptional = true
}
if len(aliases) > 0 {
subc.Aliases = aliases
}
return true, nil
}
return parentg.scanSubGroupHandler(realval, sfield)
}
return f
}
func (c *Command) scan() error {
return c.scanType(c.scanSubcommandHandler(c.Group))
}
func (c *Command) eachOption(f func(*Command, *Group, *Option)) {
c.eachCommand(func(c *Command) {
c.eachGroup(func(g *Group) {
for _, option := range g.options {
f(c, g, option)
}
})
}, true)
}
func (c *Command) eachCommand(f func(*Command), recurse bool) {
f(c)
for _, cc := range c.commands {
if recurse {
cc.eachCommand(f, true)
} else {
f(cc)
}
}
}
func (c *Command) eachActiveGroup(f func(cc *Command, g *Group)) {
c.eachGroup(func(g *Group) {
f(c, g)
})
if c.Active != nil {
c.Active.eachActiveGroup(f)
}
}
func (c *Command) addHelpGroups(showHelp func() error) {
if !c.hasBuiltinHelpGroup {
c.addHelpGroup(showHelp)
c.hasBuiltinHelpGroup = true
}
for _, cc := range c.commands {
cc.addHelpGroups(showHelp)
}
}
func (c *Command) makeLookup() lookup {
ret := lookup{
shortNames: make(map[string]*Option),
longNames: make(map[string]*Option),
commands: make(map[string]*Command),
}
parent := c.parent
var parents []*Command
for parent != nil {
if cmd, ok := parent.(*Command); ok {
parents = append(parents, cmd)
parent = cmd.parent
} else {
parent = nil
}
}
for i := len(parents) - 1; i >= 0; i-- {
parents[i].fillLookup(&ret, true)
}
c.fillLookup(&ret, false)
return ret
}
func (c *Command) fillLookup(ret *lookup, onlyOptions bool) {
c.eachGroup(func(g *Group) {
for _, option := range g.options {
if option.ShortName != 0 {
ret.shortNames[string(option.ShortName)] = option
}
if len(option.LongName) > 0 {
ret.longNames[option.LongNameWithNamespace()] = option
}
}
})
if onlyOptions {
return
}
for _, subcommand := range c.commands {
ret.commands[subcommand.Name] = subcommand
for _, a := range subcommand.Aliases {
ret.commands[a] = subcommand
}
}
}
func (c *Command) groupByName(name string) *Group {
if grp := c.Group.groupByName(name); grp != nil {
return grp
}
for _, subc := range c.commands {
prefix := subc.Name + "."
if strings.HasPrefix(name, prefix) {
if grp := subc.groupByName(name[len(prefix):]); grp != nil {
return grp
}
} else if name == subc.Name {
return subc.Group
}
}
return nil
}
type commandList []*Command
func (c commandList) Less(i, j int) bool {
return c[i].Name < c[j].Name
}
func (c commandList) Len() int {
return len(c)
}
func (c commandList) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}
func (c *Command) sortedVisibleCommands() []*Command {
ret := commandList(c.visibleCommands())
sort.Sort(ret)
return []*Command(ret)
}
func (c *Command) visibleCommands() []*Command {
ret := make([]*Command, 0, len(c.commands))
for _, cmd := range c.commands {
if !cmd.Hidden {
ret = append(ret, cmd)
}
}
return ret
}
func (c *Command) match(name string) bool {
if c.Name == name {
return true
}
for _, v := range c.Aliases {
if v == name {
return true
}
}
return false
}
func (c *Command) hasCliOptions() bool {
ret := false
c.eachGroup(func(g *Group) {
if g.isBuiltinHelp {
return
}
for _, opt := range g.options {
if opt.canCli() {
ret = true
}
}
})
return ret
}
func (c *Command) fillParseState(s *parseState) {
s.positional = make([]*Arg, len(c.args))
copy(s.positional, c.args)
s.lookup = c.makeLookup()
s.command = c
}

View file

@ -1,271 +0,0 @@
package flags
import (
"reflect"
"sort"
"strings"
"unsafe"
)
type lookup struct {
shortNames map[string]*Option
longNames map[string]*Option
commands map[string]*Command
}
func newCommand(name string, shortDescription string, longDescription string, data interface{}) *Command {
return &Command{
Group: newGroup(shortDescription, longDescription, data),
Name: name,
}
}
func (c *Command) scanSubcommandHandler(parentg *Group) scanHandler {
f := func(realval reflect.Value, sfield *reflect.StructField) (bool, error) {
mtag := newMultiTag(string(sfield.Tag))
if err := mtag.Parse(); err != nil {
return true, err
}
positional := mtag.Get("positional-args")
if len(positional) != 0 {
stype := realval.Type()
for i := 0; i < stype.NumField(); i++ {
field := stype.Field(i)
m := newMultiTag((string(field.Tag)))
if err := m.Parse(); err != nil {
return true, err
}
name := m.Get("positional-arg-name")
if len(name) == 0 {
name = field.Name
}
arg := &Arg{
Name: name,
Description: m.Get("description"),
value: realval.Field(i),
tag: m,
}
c.args = append(c.args, arg)
if len(mtag.Get("required")) != 0 {
c.ArgsRequired = true
}
}
return true, nil
}
subcommand := mtag.Get("command")
if len(subcommand) != 0 {
ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr()))
shortDescription := mtag.Get("description")
longDescription := mtag.Get("long-description")
subcommandsOptional := mtag.Get("subcommands-optional")
aliases := mtag.GetMany("alias")
subc, err := c.AddCommand(subcommand, shortDescription, longDescription, ptrval.Interface())
if err != nil {
return true, err
}
if len(subcommandsOptional) > 0 {
subc.SubcommandsOptional = true
}
if len(aliases) > 0 {
subc.Aliases = aliases
}
return true, nil
}
return parentg.scanSubGroupHandler(realval, sfield)
}
return f
}
func (c *Command) scan() error {
return c.scanType(c.scanSubcommandHandler(c.Group))
}
func (c *Command) eachCommand(f func(*Command), recurse bool) {
f(c)
for _, cc := range c.commands {
if recurse {
cc.eachCommand(f, true)
} else {
f(cc)
}
}
}
func (c *Command) eachActiveGroup(f func(cc *Command, g *Group)) {
c.eachGroup(func(g *Group) {
f(c, g)
})
if c.Active != nil {
c.Active.eachActiveGroup(f)
}
}
func (c *Command) addHelpGroups(showHelp func() error) {
if !c.hasBuiltinHelpGroup {
c.addHelpGroup(showHelp)
c.hasBuiltinHelpGroup = true
}
for _, cc := range c.commands {
cc.addHelpGroups(showHelp)
}
}
func (c *Command) makeLookup() lookup {
ret := lookup{
shortNames: make(map[string]*Option),
longNames: make(map[string]*Option),
commands: make(map[string]*Command),
}
parent := c.parent
for parent != nil {
if cmd, ok := parent.(*Command); ok {
cmd.fillLookup(&ret, true)
}
if grp, ok := parent.(*Group); ok {
parent = grp
} else {
parent = nil
}
}
c.fillLookup(&ret, false)
return ret
}
func (c *Command) fillLookup(ret *lookup, onlyOptions bool) {
c.eachGroup(func(g *Group) {
for _, option := range g.options {
if option.ShortName != 0 {
ret.shortNames[string(option.ShortName)] = option
}
if len(option.LongName) > 0 {
ret.longNames[option.LongNameWithNamespace()] = option
}
}
})
if onlyOptions {
return
}
for _, subcommand := range c.commands {
ret.commands[subcommand.Name] = subcommand
for _, a := range subcommand.Aliases {
ret.commands[a] = subcommand
}
}
}
func (c *Command) groupByName(name string) *Group {
if grp := c.Group.groupByName(name); grp != nil {
return grp
}
for _, subc := range c.commands {
prefix := subc.Name + "."
if strings.HasPrefix(name, prefix) {
if grp := subc.groupByName(name[len(prefix):]); grp != nil {
return grp
}
} else if name == subc.Name {
return subc.Group
}
}
return nil
}
type commandList []*Command
func (c commandList) Less(i, j int) bool {
return c[i].Name < c[j].Name
}
func (c commandList) Len() int {
return len(c)
}
func (c commandList) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}
func (c *Command) sortedCommands() []*Command {
ret := make(commandList, len(c.commands))
copy(ret, c.commands)
sort.Sort(ret)
return []*Command(ret)
}
func (c *Command) match(name string) bool {
if c.Name == name {
return true
}
for _, v := range c.Aliases {
if v == name {
return true
}
}
return false
}
func (c *Command) hasCliOptions() bool {
ret := false
c.eachGroup(func(g *Group) {
if g.isBuiltinHelp {
return
}
for _, opt := range g.options {
if opt.canCli() {
ret = true
}
}
})
return ret
}
func (c *Command) fillParseState(s *parseState) {
s.positional = make([]*Arg, len(c.args))
copy(s.positional, c.args)
s.lookup = c.makeLookup()
s.command = c
}

View file

@ -106,6 +106,34 @@ func TestCommandFlagOrder2(t *testing.T) {
}
}
func TestCommandFlagOrderSub(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Command struct {
G bool `short:"g"`
SubCommand struct {
B bool `short:"b"`
} `command:"sub"`
} `command:"cmd"`
}{}
assertParseSuccess(t, &opts, "cmd", "sub", "-v", "-g", "-b")
if !opts.Value {
t.Errorf("Expected Value to be true")
}
if !opts.Command.G {
t.Errorf("Expected Command.G to be true")
}
if !opts.Command.SubCommand.B {
t.Errorf("Expected Command.SubCommand.B to be true")
}
}
func TestCommandFlagOverride1(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
@ -146,6 +174,58 @@ func TestCommandFlagOverride2(t *testing.T) {
}
}
func TestCommandFlagOverrideSub(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Command struct {
Value bool `short:"v"`
SubCommand struct {
Value bool `short:"v"`
} `command:"sub"`
} `command:"cmd"`
}{}
assertParseSuccess(t, &opts, "cmd", "sub", "-v")
if opts.Value {
t.Errorf("Expected Value to be false")
}
if opts.Command.Value {
t.Errorf("Expected Command.Value to be false")
}
if !opts.Command.SubCommand.Value {
t.Errorf("Expected Command.Value to be true")
}
}
func TestCommandFlagOverrideSub2(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Command struct {
Value bool `short:"v"`
SubCommand struct {
G bool `short:"g"`
} `command:"sub"`
} `command:"cmd"`
}{}
assertParseSuccess(t, &opts, "cmd", "sub", "-v")
if opts.Value {
t.Errorf("Expected Value to be false")
}
if !opts.Command.Value {
t.Errorf("Expected Command.Value to be true")
}
}
func TestCommandEstimate(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
@ -350,17 +430,31 @@ func TestRequiredAllOnCommand(t *testing.T) {
func TestDefaultOnCommand(t *testing.T) {
var opts = struct {
Command struct {
G bool `short:"g" default:"true"`
G string `short:"g" default:"value"`
} `command:"cmd"`
}{}
assertParseSuccess(t, &opts, "cmd")
if !opts.Command.G {
t.Errorf("Expected G to be true")
if opts.Command.G != "value" {
t.Errorf("Expected G to be \"value\"")
}
}
func TestAfterNonCommand(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Cmd1 struct {
} `command:"remove"`
Cmd2 struct {
} `command:"add"`
}{}
assertParseFail(t, ErrUnknownCommand, "Unknown command `nocmd'. Please specify one command of: add or remove", &opts, "nocmd", "remove")
}
func TestSubcommandsOptional(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
@ -387,16 +481,102 @@ func TestSubcommandsOptional(t *testing.T) {
}
}
func TestSubcommandsOptionalAfterNonCommand(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Cmd1 struct {
} `command:"remove"`
Cmd2 struct {
} `command:"add"`
}{}
p := NewParser(&opts, None)
p.SubcommandsOptional = true
retargs, err := p.ParseArgs([]string{"nocmd", "remove"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
return
}
assertStringArray(t, retargs, []string{"nocmd", "remove"})
}
func TestCommandAlias(t *testing.T) {
var opts = struct {
Command struct {
G bool `short:"g" default:"true"`
G string `short:"g" default:"value"`
} `command:"cmd" alias:"cm"`
}{}
assertParseSuccess(t, &opts, "cm")
if !opts.Command.G {
t.Errorf("Expected G to be true")
if opts.Command.G != "value" {
t.Errorf("Expected G to be \"value\"")
}
}
func TestSubCommandFindOptionByLongFlag(t *testing.T) {
var opts struct {
Testing bool `long:"testing" description:"Testing"`
}
var cmd struct {
Other bool `long:"other" description:"Other"`
}
p := NewParser(&opts, Default)
c, _ := p.AddCommand("command", "Short", "Long", &cmd)
opt := c.FindOptionByLongName("other")
if opt == nil {
t.Errorf("Expected option, but found none")
}
assertString(t, opt.LongName, "other")
opt = c.FindOptionByLongName("testing")
if opt == nil {
t.Errorf("Expected option, but found none")
}
assertString(t, opt.LongName, "testing")
}
func TestSubCommandFindOptionByShortFlag(t *testing.T) {
var opts struct {
Testing bool `short:"t" description:"Testing"`
}
var cmd struct {
Other bool `short:"o" description:"Other"`
}
p := NewParser(&opts, Default)
c, _ := p.AddCommand("command", "Short", "Long", &cmd)
opt := c.FindOptionByShortName('o')
if opt == nil {
t.Errorf("Expected option, but found none")
}
if opt.ShortName != 'o' {
t.Errorf("Expected 'o', but got %v", opt.ShortName)
}
opt = c.FindOptionByShortName('t')
if opt == nil {
t.Errorf("Expected option, but found none")
}
if opt.ShortName != 't' {
t.Errorf("Expected 'o', but got %v", opt.ShortName)
}
}

View file

@ -43,8 +43,6 @@ type Completer interface {
type completion struct {
parser *Parser
ShowDescriptions bool
}
// Filename is a string alias which provides filename completion.
@ -79,7 +77,7 @@ func (c *completion) completeOptionNames(names map[string]*Option, prefix string
n := make([]Completion, 0, len(names))
for k, opt := range names {
if strings.HasPrefix(k, match) {
if strings.HasPrefix(k, match) && !opt.Hidden {
n = append(n, Completion{
Item: prefix + k,
Description: opt.Description,
@ -275,19 +273,17 @@ func (c *completion) complete(args []string) []Completion {
return ret
}
func (c *completion) execute(args []string) {
ret := c.complete(args)
if c.ShowDescriptions && len(ret) > 1 {
func (c *completion) print(items []Completion, showDescriptions bool) {
if showDescriptions && len(items) > 1 {
maxl := 0
for _, v := range ret {
for _, v := range items {
if len(v.Item) > maxl {
maxl = len(v.Item)
}
}
for _, v := range ret {
for _, v := range items {
fmt.Printf("%s", v.Item)
if len(v.Description) > 0 {
@ -297,7 +293,7 @@ func (c *completion) execute(args []string) {
fmt.Printf("\n")
}
} else {
for _, v := range ret {
for _, v := range items {
fmt.Println(v.Item)
}
}

View file

@ -40,6 +40,7 @@ var completionTestOptions struct {
Debug bool `short:"d" long:"debug" description:"Enable debug"`
Version bool `long:"version" description:"Show version"`
Required bool `long:"required" required:"true" description:"This is required"`
Hidden bool `long:"hidden" hidden:"true" description:"This is hidden"`
AddCommand struct {
Positional struct {
@ -268,6 +269,11 @@ func TestParserCompletion(t *testing.T) {
p := NewParser(&completionTestOptions, None)
p.CompletionHandler = func(items []Completion) {
comp := &completion{parser: p}
comp.print(items, test.ShowDescriptions)
}
_, err := p.ParseArgs(test.Args)
w.Close()

View file

@ -339,39 +339,3 @@ func unquoteIfPossible(s string) (string, error) {
return strconv.Unquote(s)
}
func wrapText(s string, l int, prefix string) string {
// Basic text wrapping of s at spaces to fit in l
var ret string
s = strings.TrimSpace(s)
for len(s) > l {
// Try to split on space
suffix := ""
pos := strings.LastIndex(s[:l], " ")
if pos < 0 {
pos = l - 1
suffix = "-\n"
}
if len(ret) != 0 {
ret += "\n" + prefix
}
ret += strings.TrimSpace(s[:pos]) + suffix
s = strings.TrimSpace(s[pos:])
}
if len(s) > 0 {
if len(ret) != 0 {
ret += "\n" + prefix
}
return ret + s
}
return ret
}

View file

@ -157,19 +157,3 @@ func TestConvertToStringInvalidUintBase(t *testing.T) {
assertError(t, err, ErrMarshal, "strconv.ParseInt: parsing \"no\": invalid syntax")
}
func TestWrapText(t *testing.T) {
s := "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
got := wrapText(s, 60, " ")
expected := `Lorem ipsum dolor sit amet, consectetur adipisicing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.`
assertDiff(t, got, expected, "wrapped text")
}

View file

@ -51,6 +51,13 @@ const (
// ErrUnknownCommand indicates that an unknown command was specified.
ErrUnknownCommand
// ErrInvalidChoice indicates an invalid option value which only allows
// a certain number of choices.
ErrInvalidChoice
// ErrInvalidTag indicates an invalid tag or invalid use of an existing tag
ErrInvalidTag
)
func (e ErrorType) String() string {
@ -81,6 +88,10 @@ func (e ErrorType) String() string {
return "command required"
case ErrUnknownCommand:
return "unknown command"
case ErrInvalidChoice:
return "invalid choice"
case ErrInvalidTag:
return "invalid tag"
}
return "unrecognized error type"

View file

@ -41,7 +41,7 @@ func Example() {
// Example of positional arguments
Args struct {
Id string
ID string
Num int
Rest []string
} `positional-args:"yes" required:"yes"`
@ -92,7 +92,7 @@ func Example() {
fmt.Printf("PtrSlice: [%v %v]\n", *opts.PtrSlice[0], *opts.PtrSlice[1])
fmt.Printf("IntMap: [a:%v b:%v]\n", opts.IntMap["a"], opts.IntMap["b"])
fmt.Printf("Filename: %v\n", opts.Filename)
fmt.Printf("Args.Id: %s\n", opts.Args.Id)
fmt.Printf("Args.ID: %s\n", opts.Args.ID)
fmt.Printf("Args.Num: %d\n", opts.Args.Num)
fmt.Printf("Args.Rest: %v\n", opts.Args.Rest)
@ -104,7 +104,7 @@ func Example() {
// PtrSlice: [hello world]
// IntMap: [a:1 b:5]
// Filename: hello.go
// Args.Id: id
// Args.ID: id
// Args.Num: 10
// Args.Rest: [remaining1 remaining2]
}

View file

@ -35,6 +35,8 @@ Additional features specific to Windows:
Options with long names (/verbose)
Windows-style options with arguments use a colon as the delimiter
Modify generated help message with Windows-style / options
Windows style options can be disabled at build time using the "forceposix"
build tag
Basic usage
@ -84,7 +86,9 @@ The following is a list of tags for struct fields supported by go-flags:
displayed in generated man pages (optional)
no-flag: if non-empty this field is ignored as an option (optional)
optional: whether an argument of the option is optional (optional)
optional: whether an argument of the option is optional. When an
argument is optional it can only be specified using
--option=argument (optional)
optional-value: the value of an optional option when the option occurs
without an argument. This tag can be specified multiple
times in the case of maps or slices (optional)
@ -104,6 +108,9 @@ The following is a list of tags for struct fields supported by go-flags:
slices and maps (optional)
value-name: the name of the argument value (to be shown in the help)
(optional)
choice: limits the values for an option to a set of values.
This tag can be specified mltiple times (optional)
hidden: the option is not visible in the help or man page.
base: a base (radix) used to convert strings to integer values, the
default base is 10 (i.e. decimal) (optional)
@ -133,12 +140,17 @@ The following is a list of tags for struct fields supported by go-flags:
then all remaining arguments will be added to it.
Positional arguments are optional by default,
unless the "required" tag is specified together
with the "positional-args" tag (optional)
with the "positional-args" tag. The "required" tag
can also be set on the individual rest argument
fields, to require only the first N positional
arguments. If the "required" tag is set on the
rest arguments slice, then its value determines
the minimum amount of rest arguments that needs to
be provided (e.g. `required:"2"`) (optional)
positional-arg-name: used on a field in a positional argument struct; name
of the positional argument placeholder to be shown in
the help (optional)
Either the `short:` tag or the `long:` must be specified to make the field eligible as an
option.

View file

@ -6,7 +6,10 @@ package flags
import (
"errors"
"reflect"
"strings"
"unicode/utf8"
"unsafe"
)
// ErrNotPointerToStruct indicates that a provided data container is not
@ -32,6 +35,9 @@ type Group struct {
// The namespace of the group
Namespace string
// If true, the group is not displayed in the help or man page
Hidden bool
// The parent of the group or nil if it has no parent
parent interface{}
@ -47,6 +53,8 @@ type Group struct {
data interface{}
}
type scanHandler func(reflect.Value, *reflect.StructField) (bool, error)
// AddGroup adds a new group to the command with the given name and data. The
// data needs to be a pointer to a struct from which the fields indicate which
// options are in the group.
@ -89,3 +97,289 @@ func (g *Group) Find(shortDescription string) *Group {
return ret
}
func (g *Group) findOption(matcher func(*Option) bool) (option *Option) {
g.eachGroup(func(g *Group) {
for _, opt := range g.options {
if option == nil && matcher(opt) {
option = opt
}
}
})
return option
}
// FindOptionByLongName finds an option that is part of the group, or any of its
// subgroups, by matching its long name (including the option namespace).
func (g *Group) FindOptionByLongName(longName string) *Option {
return g.findOption(func(option *Option) bool {
return option.LongNameWithNamespace() == longName
})
}
// FindOptionByShortName finds an option that is part of the group, or any of
// its subgroups, by matching its short name.
func (g *Group) FindOptionByShortName(shortName rune) *Option {
return g.findOption(func(option *Option) bool {
return option.ShortName == shortName
})
}
func newGroup(shortDescription string, longDescription string, data interface{}) *Group {
return &Group{
ShortDescription: shortDescription,
LongDescription: longDescription,
data: data,
}
}
func (g *Group) optionByName(name string, namematch func(*Option, string) bool) *Option {
prio := 0
var retopt *Option
g.eachGroup(func(g *Group) {
for _, opt := range g.options {
if namematch != nil && namematch(opt, name) && prio < 4 {
retopt = opt
prio = 4
}
if name == opt.field.Name && prio < 3 {
retopt = opt
prio = 3
}
if name == opt.LongNameWithNamespace() && prio < 2 {
retopt = opt
prio = 2
}
if opt.ShortName != 0 && name == string(opt.ShortName) && prio < 1 {
retopt = opt
prio = 1
}
}
})
return retopt
}
func (g *Group) eachGroup(f func(*Group)) {
f(g)
for _, gg := range g.groups {
gg.eachGroup(f)
}
}
func (g *Group) scanStruct(realval reflect.Value, sfield *reflect.StructField, handler scanHandler) error {
stype := realval.Type()
if sfield != nil {
if ok, err := handler(realval, sfield); err != nil {
return err
} else if ok {
return nil
}
}
for i := 0; i < stype.NumField(); i++ {
field := stype.Field(i)
// PkgName is set only for non-exported fields, which we ignore
if field.PkgPath != "" && !field.Anonymous {
continue
}
mtag := newMultiTag(string(field.Tag))
if err := mtag.Parse(); err != nil {
return err
}
// Skip fields with the no-flag tag
if mtag.Get("no-flag") != "" {
continue
}
// Dive deep into structs or pointers to structs
kind := field.Type.Kind()
fld := realval.Field(i)
if kind == reflect.Struct {
if err := g.scanStruct(fld, &field, handler); err != nil {
return err
}
} else if kind == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct {
if fld.IsNil() {
fld.Set(reflect.New(fld.Type().Elem()))
}
if err := g.scanStruct(reflect.Indirect(fld), &field, handler); err != nil {
return err
}
}
longname := mtag.Get("long")
shortname := mtag.Get("short")
// Need at least either a short or long name
if longname == "" && shortname == "" && mtag.Get("ini-name") == "" {
continue
}
short := rune(0)
rc := utf8.RuneCountInString(shortname)
if rc > 1 {
return newErrorf(ErrShortNameTooLong,
"short names can only be 1 character long, not `%s'",
shortname)
} else if rc == 1 {
short, _ = utf8.DecodeRuneInString(shortname)
}
description := mtag.Get("description")
def := mtag.GetMany("default")
optionalValue := mtag.GetMany("optional-value")
valueName := mtag.Get("value-name")
defaultMask := mtag.Get("default-mask")
optional := (mtag.Get("optional") != "")
required := (mtag.Get("required") != "")
choices := mtag.GetMany("choice")
hidden := (mtag.Get("hidden") != "")
option := &Option{
Description: description,
ShortName: short,
LongName: longname,
Default: def,
EnvDefaultKey: mtag.Get("env"),
EnvDefaultDelim: mtag.Get("env-delim"),
OptionalArgument: optional,
OptionalValue: optionalValue,
Required: required,
ValueName: valueName,
DefaultMask: defaultMask,
Choices: choices,
Hidden: hidden,
group: g,
field: field,
value: realval.Field(i),
tag: mtag,
}
if option.isBool() && option.Default != nil {
return newErrorf(ErrInvalidTag,
"boolean flag `%s' may not have default values, they always default to `false' and can only be turned on",
option.shortAndLongName())
}
g.options = append(g.options, option)
}
return nil
}
func (g *Group) checkForDuplicateFlags() *Error {
shortNames := make(map[rune]*Option)
longNames := make(map[string]*Option)
var duplicateError *Error
g.eachGroup(func(g *Group) {
for _, option := range g.options {
if option.LongName != "" {
longName := option.LongNameWithNamespace()
if otherOption, ok := longNames[longName]; ok {
duplicateError = newErrorf(ErrDuplicatedFlag, "option `%s' uses the same long name as option `%s'", option, otherOption)
return
}
longNames[longName] = option
}
if option.ShortName != 0 {
if otherOption, ok := shortNames[option.ShortName]; ok {
duplicateError = newErrorf(ErrDuplicatedFlag, "option `%s' uses the same short name as option `%s'", option, otherOption)
return
}
shortNames[option.ShortName] = option
}
}
})
return duplicateError
}
func (g *Group) scanSubGroupHandler(realval reflect.Value, sfield *reflect.StructField) (bool, error) {
mtag := newMultiTag(string(sfield.Tag))
if err := mtag.Parse(); err != nil {
return true, err
}
subgroup := mtag.Get("group")
if len(subgroup) != 0 {
ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr()))
description := mtag.Get("description")
group, err := g.AddGroup(subgroup, description, ptrval.Interface())
if err != nil {
return true, err
}
group.Namespace = mtag.Get("namespace")
group.Hidden = mtag.Get("hidden") != ""
return true, nil
}
return false, nil
}
func (g *Group) scanType(handler scanHandler) error {
// Get all the public fields in the data struct
ptrval := reflect.ValueOf(g.data)
if ptrval.Type().Kind() != reflect.Ptr {
panic(ErrNotPointerToStruct)
}
stype := ptrval.Type().Elem()
if stype.Kind() != reflect.Struct {
panic(ErrNotPointerToStruct)
}
realval := reflect.Indirect(ptrval)
if err := g.scanStruct(realval, nil, handler); err != nil {
return err
}
if err := g.checkForDuplicateFlags(); err != nil {
return err
}
return nil
}
func (g *Group) scan() error {
return g.scanType(g.scanSubGroupHandler)
}
func (g *Group) groupByName(name string) *Group {
if len(name) == 0 {
return g
}
return g.Find(name)
}

View file

@ -1,254 +0,0 @@
package flags
import (
"reflect"
"unicode/utf8"
"unsafe"
)
type scanHandler func(reflect.Value, *reflect.StructField) (bool, error)
func newGroup(shortDescription string, longDescription string, data interface{}) *Group {
return &Group{
ShortDescription: shortDescription,
LongDescription: longDescription,
data: data,
}
}
func (g *Group) optionByName(name string, namematch func(*Option, string) bool) *Option {
prio := 0
var retopt *Option
for _, opt := range g.options {
if namematch != nil && namematch(opt, name) && prio < 4 {
retopt = opt
prio = 4
}
if name == opt.field.Name && prio < 3 {
retopt = opt
prio = 3
}
if name == opt.LongNameWithNamespace() && prio < 2 {
retopt = opt
prio = 2
}
if opt.ShortName != 0 && name == string(opt.ShortName) && prio < 1 {
retopt = opt
prio = 1
}
}
return retopt
}
func (g *Group) eachGroup(f func(*Group)) {
f(g)
for _, gg := range g.groups {
gg.eachGroup(f)
}
}
func (g *Group) scanStruct(realval reflect.Value, sfield *reflect.StructField, handler scanHandler) error {
stype := realval.Type()
if sfield != nil {
if ok, err := handler(realval, sfield); err != nil {
return err
} else if ok {
return nil
}
}
for i := 0; i < stype.NumField(); i++ {
field := stype.Field(i)
// PkgName is set only for non-exported fields, which we ignore
if field.PkgPath != "" {
continue
}
mtag := newMultiTag(string(field.Tag))
if err := mtag.Parse(); err != nil {
return err
}
// Skip fields with the no-flag tag
if mtag.Get("no-flag") != "" {
continue
}
// Dive deep into structs or pointers to structs
kind := field.Type.Kind()
fld := realval.Field(i)
if kind == reflect.Struct {
if err := g.scanStruct(fld, &field, handler); err != nil {
return err
}
} else if kind == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct {
if fld.IsNil() {
fld.Set(reflect.New(fld.Type().Elem()))
}
if err := g.scanStruct(reflect.Indirect(fld), &field, handler); err != nil {
return err
}
}
longname := mtag.Get("long")
shortname := mtag.Get("short")
// Need at least either a short or long name
if longname == "" && shortname == "" && mtag.Get("ini-name") == "" {
continue
}
short := rune(0)
rc := utf8.RuneCountInString(shortname)
if rc > 1 {
return newErrorf(ErrShortNameTooLong,
"short names can only be 1 character long, not `%s'",
shortname)
} else if rc == 1 {
short, _ = utf8.DecodeRuneInString(shortname)
}
description := mtag.Get("description")
def := mtag.GetMany("default")
optionalValue := mtag.GetMany("optional-value")
valueName := mtag.Get("value-name")
defaultMask := mtag.Get("default-mask")
optional := (mtag.Get("optional") != "")
required := (mtag.Get("required") != "")
option := &Option{
Description: description,
ShortName: short,
LongName: longname,
Default: def,
EnvDefaultKey: mtag.Get("env"),
EnvDefaultDelim: mtag.Get("env-delim"),
OptionalArgument: optional,
OptionalValue: optionalValue,
Required: required,
ValueName: valueName,
DefaultMask: defaultMask,
group: g,
field: field,
value: realval.Field(i),
tag: mtag,
}
g.options = append(g.options, option)
}
return nil
}
func (g *Group) checkForDuplicateFlags() *Error {
shortNames := make(map[rune]*Option)
longNames := make(map[string]*Option)
var duplicateError *Error
g.eachGroup(func(g *Group) {
for _, option := range g.options {
if option.LongName != "" {
longName := option.LongNameWithNamespace()
if otherOption, ok := longNames[longName]; ok {
duplicateError = newErrorf(ErrDuplicatedFlag, "option `%s' uses the same long name as option `%s'", option, otherOption)
return
}
longNames[longName] = option
}
if option.ShortName != 0 {
if otherOption, ok := shortNames[option.ShortName]; ok {
duplicateError = newErrorf(ErrDuplicatedFlag, "option `%s' uses the same short name as option `%s'", option, otherOption)
return
}
shortNames[option.ShortName] = option
}
}
})
return duplicateError
}
func (g *Group) scanSubGroupHandler(realval reflect.Value, sfield *reflect.StructField) (bool, error) {
mtag := newMultiTag(string(sfield.Tag))
if err := mtag.Parse(); err != nil {
return true, err
}
subgroup := mtag.Get("group")
if len(subgroup) != 0 {
ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr()))
description := mtag.Get("description")
group, err := g.AddGroup(subgroup, description, ptrval.Interface())
if err != nil {
return true, err
}
group.Namespace = mtag.Get("namespace")
return true, nil
}
return false, nil
}
func (g *Group) scanType(handler scanHandler) error {
// Get all the public fields in the data struct
ptrval := reflect.ValueOf(g.data)
if ptrval.Type().Kind() != reflect.Ptr {
panic(ErrNotPointerToStruct)
}
stype := ptrval.Type().Elem()
if stype.Kind() != reflect.Struct {
panic(ErrNotPointerToStruct)
}
realval := reflect.Indirect(ptrval)
if err := g.scanStruct(realval, nil, handler); err != nil {
return err
}
if err := g.checkForDuplicateFlags(); err != nil {
return err
}
return nil
}
func (g *Group) scan() error {
return g.scanType(g.scanSubGroupHandler)
}
func (g *Group) groupByName(name string) *Group {
if len(name) == 0 {
return g
}
return g.Find(name)
}

View file

@ -185,3 +185,71 @@ func TestDuplicateLongFlags(t *testing.T) {
}
}
}
func TestFindOptionByLongFlag(t *testing.T) {
var opts struct {
Testing bool `long:"testing" description:"Testing"`
}
p := NewParser(&opts, Default)
opt := p.FindOptionByLongName("testing")
if opt == nil {
t.Errorf("Expected option, but found none")
}
assertString(t, opt.LongName, "testing")
}
func TestFindOptionByShortFlag(t *testing.T) {
var opts struct {
Testing bool `short:"t" description:"Testing"`
}
p := NewParser(&opts, Default)
opt := p.FindOptionByShortName('t')
if opt == nil {
t.Errorf("Expected option, but found none")
}
if opt.ShortName != 't' {
t.Errorf("Expected 't', but got %v", opt.ShortName)
}
}
func TestFindOptionByLongFlagInSubGroup(t *testing.T) {
var opts struct {
Group struct {
Testing bool `long:"testing" description:"Testing"`
} `group:"sub-group"`
}
p := NewParser(&opts, Default)
opt := p.FindOptionByLongName("testing")
if opt == nil {
t.Errorf("Expected option, but found none")
}
assertString(t, opt.LongName, "testing")
}
func TestFindOptionByShortFlagInSubGroup(t *testing.T) {
var opts struct {
Group struct {
Testing bool `short:"t" description:"Testing"`
} `group:"sub-group"`
}
p := NewParser(&opts, Default)
opt := p.FindOptionByShortName('t')
if opt == nil {
t.Errorf("Expected option, but found none")
}
if opt.ShortName != 't' {
t.Errorf("Expected 't', but got %v", opt.ShortName)
}
}

View file

@ -9,7 +9,6 @@ import (
"bytes"
"fmt"
"io"
"reflect"
"runtime"
"strings"
"unicode/utf8"
@ -92,13 +91,71 @@ func (p *Parser) getAlignmentInfo() alignmentInfo {
ret.hasValueName = true
}
ret.updateLen(info.LongNameWithNamespace()+info.ValueName, c != p.Command)
l := info.LongNameWithNamespace() + info.ValueName
if len(info.Choices) != 0 {
l += "[" + strings.Join(info.Choices, "|") + "]"
}
ret.updateLen(l, c != p.Command)
}
})
return ret
}
func wrapText(s string, l int, prefix string) string {
var ret string
// Basic text wrapping of s at spaces to fit in l
lines := strings.Split(s, "\n")
for _, line := range lines {
var retline string
line = strings.TrimSpace(line)
for len(line) > l {
// Try to split on space
suffix := ""
pos := strings.LastIndex(line[:l], " ")
if pos < 0 {
pos = l - 1
suffix = "-\n"
}
if len(retline) != 0 {
retline += "\n" + prefix
}
retline += strings.TrimSpace(line[:pos]) + suffix
line = strings.TrimSpace(line[pos:])
}
if len(line) > 0 {
if len(retline) != 0 {
retline += "\n" + prefix
}
retline += line
}
if len(ret) > 0 {
ret += "\n"
if len(retline) > 0 {
ret += prefix
}
}
ret += retline
}
return ret
}
func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alignmentInfo) {
line := &bytes.Buffer{}
@ -108,6 +165,10 @@ func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alig
prefix += 4
}
if option.Hidden {
return
}
line.WriteString(strings.Repeat(" ", prefix))
if option.ShortName != 0 {
@ -136,6 +197,10 @@ func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alig
if len(option.ValueName) > 0 {
line.WriteString(option.ValueName)
}
if len(option.Choices) > 0 {
line.WriteString("[" + strings.Join(option.Choices, "|") + "]")
}
}
written := line.Len()
@ -145,39 +210,12 @@ func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alig
dw := descstart - written
writer.WriteString(strings.Repeat(" ", dw))
def := ""
defs := option.Default
var def string
if len(option.DefaultMask) != 0 {
if option.DefaultMask != "-" {
def = option.DefaultMask
}
} else if len(defs) == 0 && option.canArgument() {
var showdef bool
switch option.field.Type.Kind() {
case reflect.Func, reflect.Ptr:
showdef = !option.value.IsNil()
case reflect.Slice, reflect.String, reflect.Array:
showdef = option.value.Len() > 0
case reflect.Map:
showdef = !option.value.IsNil() && option.value.Len() > 0
default:
zeroval := reflect.Zero(option.field.Type)
showdef = !reflect.DeepEqual(zeroval.Interface(), option.value.Interface())
}
if showdef {
def, _ = convertToString(option.value, option.tag)
}
} else if len(defs) != 0 {
l := len(defs) - 1
for i := 0; i < l; i++ {
def += quoteIfNeeded(defs[i]) + ", "
}
def += quoteIfNeeded(defs[l])
if len(option.DefaultMask) != 0 && option.DefaultMask != "-" {
def = option.DefaultMask
} else {
def = option.defaultLiteral
}
var envDef string
@ -194,7 +232,7 @@ func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alig
var desc string
if def != "" {
desc = fmt.Sprintf("%s (%v)%s", option.Description, def, envDef)
desc = fmt.Sprintf("%s (default: %v)%s", option.Description, def, envDef)
} else {
desc = option.Description + envDef
}
@ -302,10 +340,12 @@ func (p *Parser) WriteHelp(writer io.Writer) {
co, cc = "<", ">"
}
if len(allcmd.commands) > 3 {
visibleCommands := allcmd.visibleCommands()
if len(visibleCommands) > 3 {
fmt.Fprintf(wr, " %scommand%s", co, cc)
} else {
subcommands := allcmd.sortedCommands()
subcommands := allcmd.sortedVisibleCommands()
names := make([]string, len(subcommands))
for i, subc := range subcommands {
@ -342,12 +382,12 @@ func (p *Parser) WriteHelp(writer io.Writer) {
// Skip built-in help group for all commands except the top-level
// parser
if grp.isBuiltinHelp && c != p.Command {
if grp.Hidden || (grp.isBuiltinHelp && c != p.Command) {
return
}
for _, info := range grp.options {
if !info.canCli() {
if !info.canCli() || info.Hidden {
continue
}
@ -372,22 +412,41 @@ func (p *Parser) WriteHelp(writer io.Writer) {
}
})
if len(c.args) > 0 {
var args []*Arg
for _, arg := range c.args {
if arg.Description != "" {
args = append(args, arg)
}
}
if len(args) > 0 {
if c == p.Command {
fmt.Fprintf(wr, "\nArguments:\n")
} else {
fmt.Fprintf(wr, "\n[%s command arguments]\n", c.Name)
}
maxlen := aligninfo.descriptionStart()
descStart := aligninfo.descriptionStart() + paddingBeforeOption
for _, arg := range c.args {
prefix := strings.Repeat(" ", paddingBeforeOption)
fmt.Fprintf(wr, "%s%s", prefix, arg.Name)
for _, arg := range args {
argPrefix := strings.Repeat(" ", paddingBeforeOption)
argPrefix += arg.Name
if len(arg.Description) > 0 {
align := strings.Repeat(" ", maxlen-len(arg.Name)-1)
fmt.Fprintf(wr, ":%s%s", align, arg.Description)
argPrefix += ":"
wr.WriteString(argPrefix)
// Space between "arg:" and the description start
descPadding := strings.Repeat(" ", descStart-len(argPrefix))
// How much space the description gets before wrapping
descWidth := aligninfo.terminalColumns - 1 - descStart
// Whitespace to which we can indent new description lines
descPrefix := strings.Repeat(" ", descStart)
wr.WriteString(descPadding)
wr.WriteString(wrapText(arg.Description, descWidth, descPrefix))
} else {
wr.WriteString(argPrefix)
}
fmt.Fprintln(wr)
@ -397,7 +456,7 @@ func (p *Parser) WriteHelp(writer io.Writer) {
c = c.Active
}
scommands := cmd.sortedCommands()
scommands := cmd.sortedVisibleCommands()
if len(scommands) > 0 {
maxnamelen := maxCommandLength(scommands)

View file

@ -21,6 +21,8 @@ type helpOptions struct {
EnvDefault1 string `long:"env-default1" default:"Some value" env:"ENV_DEFAULT" description:"Test env-default1 value"`
EnvDefault2 string `long:"env-default2" env:"ENV_DEFAULT" description:"Test env-default2 value"`
OptionWithArgName string `long:"opt-with-arg-name" value-name:"something" description:"Option with named argument"`
OptionWithChoices string `long:"opt-with-choices" value-name:"choice" choice:"dog" choice:"cat" description:"Option with choices"`
Hidden string `long:"hidden" description:"Hidden option" hidden:"yes"`
OnlyIni string `ini-name:"only-ini" description:"Option only available in ini"`
@ -29,8 +31,13 @@ type helpOptions struct {
IntMap map[string]int `long:"intmap" default:"a:1" description:"A map from string to int" ini-name:"int-map"`
} `group:"Other Options"`
HiddenGroup struct {
InsideHiddenGroup string `long:"inside-hidden-group" description:"Inside hidden group"`
} `group:"Hidden group" hidden:"yes"`
Group struct {
Opt string `long:"opt" description:"This is a subgroup option"`
Opt string `long:"opt" description:"This is a subgroup option"`
HiddenInsideGroup string `long:"hidden-inside-group" description:"Hidden inside group" hidden:"yes"`
Group struct {
Opt string `long:"opt" description:"This is a subsubgroup option"`
@ -41,9 +48,14 @@ type helpOptions struct {
ExtraVerbose []bool `long:"extra-verbose" description:"Use for extra verbosity"`
} `command:"command" alias:"cm" alias:"cmd" description:"A command"`
HiddenCommand struct {
ExtraVerbose []bool `long:"extra-verbose" description:"Use for extra verbosity"`
} `command:"hidden-command" description:"A hidden command" hidden:"yes"`
Args struct {
Filename string `positional-arg-name:"filename" description:"A filename"`
Number int `positional-arg-name:"num" description:"A number"`
Filename string `positional-arg-name:"filename" description:"A filename with a long description to trigger line wrapping"`
Number int `positional-arg-name:"num" description:"A number"`
HiddenInHelp float32 `positional-arg-name:"hidden-in-help" required:"yes"`
} `positional-args:"yes"`
}
@ -73,76 +85,91 @@ func TestHelp(t *testing.T) {
if runtime.GOOS == "windows" {
expected = `Usage:
TestHelp [OPTIONS] [filename] [num] <command>
TestHelp [OPTIONS] [filename] [num] [hidden-in-help] <command>
Application Options:
/v, /verbose Show verbose debug information
/c: Call phone number
/ptrslice: A slice of pointers to string
/v, /verbose Show verbose debug information
/c: Call phone number
/ptrslice: A slice of pointers to string
/empty-description
/default: Test default value ("Some\nvalue")
/default-array: Test default array value (Some value, "Other\tvalue")
/default-map: Testdefault map value (some:value, another:value)
/env-default1: Test env-default1 value (Some value) [%ENV_DEFAULT%]
/env-default2: Test env-default2 value [%ENV_DEFAULT%]
/opt-with-arg-name:something Option with named argument
/default: Test default value (default:
"Some\nvalue")
/default-array: Test default array value (default:
Some value, "Other\tvalue")
/default-map: Testdefault map value (default:
some:value, another:value)
/env-default1: Test env-default1 value (default:
Some value) [%ENV_DEFAULT%]
/env-default2: Test env-default2 value
[%ENV_DEFAULT%]
/opt-with-arg-name:something Option with named argument
/opt-with-choices:choice[dog|cat] Option with choices
Other Options:
/s: A slice of strings (some, value)
/intmap: A map from string to int (a:1)
/s: A slice of strings (default: some,
value)
/intmap: A map from string to int (default:
a:1)
Subgroup:
/sip.opt: This is a subgroup option
/sip.opt: This is a subgroup option
Subsubgroup:
/sip.sap.opt: This is a subsubgroup option
/sip.sap.opt: This is a subsubgroup option
Help Options:
/? Show this help message
/h, /help Show this help message
/? Show this help message
/h, /help Show this help message
Arguments:
filename: A filename
num: A number
filename: A filename
num: A number
Available commands:
command A command (aliases: cm, cmd)
`
} else {
expected = `Usage:
TestHelp [OPTIONS] [filename] [num] <command>
TestHelp [OPTIONS] [filename] [num] [hidden-in-help] <command>
Application Options:
-v, --verbose Show verbose debug information
-c= Call phone number
--ptrslice= A slice of pointers to string
-v, --verbose Show verbose debug information
-c= Call phone number
--ptrslice= A slice of pointers to string
--empty-description
--default= Test default value ("Some\nvalue")
--default-array= Test default array value (Some value,
"Other\tvalue")
--default-map= Testdefault map value (some:value,
another:value)
--env-default1= Test env-default1 value (Some value)
[$ENV_DEFAULT]
--env-default2= Test env-default2 value [$ENV_DEFAULT]
--opt-with-arg-name=something Option with named argument
--default= Test default value (default:
"Some\nvalue")
--default-array= Test default array value (default:
Some value, "Other\tvalue")
--default-map= Testdefault map value (default:
some:value, another:value)
--env-default1= Test env-default1 value (default:
Some value) [$ENV_DEFAULT]
--env-default2= Test env-default2 value
[$ENV_DEFAULT]
--opt-with-arg-name=something Option with named argument
--opt-with-choices=choice[dog|cat] Option with choices
Other Options:
-s= A slice of strings (some, value)
--intmap= A map from string to int (a:1)
-s= A slice of strings (default: some,
value)
--intmap= A map from string to int (default:
a:1)
Subgroup:
--sip.opt= This is a subgroup option
--sip.opt= This is a subgroup option
Subsubgroup:
--sip.sap.opt= This is a subsubgroup option
--sip.sap.opt= This is a subsubgroup option
Help Options:
-h, --help Show this help message
-h, --help Show this help message
Arguments:
filename: A filename
num: A number
filename: A filename with a long description
to trigger line wrapping
num: A number
Available commands:
command A command (aliases: cm, cmd)
@ -189,6 +216,8 @@ TestMan \- Test manpage generation
.SH DESCRIPTION
This is a somewhat \fBlonger\fP description of what this does
.SH OPTIONS
.SS Application Options
The application options
.TP
\fB\fB\-v\fR, \fB\-\-verbose\fR\fP
Show verbose debug information
@ -219,14 +248,20 @@ Test env-default2 value
\fB\fB\-\-opt-with-arg-name\fR \fIsomething\fR\fP
Option with named argument
.TP
\fB\fB\-\-opt-with-choices\fR \fIchoice\fR\fP
Option with choices
.SS Other Options
.TP
\fB\fB\-s\fR <default: \fI"some", "value"\fR>\fP
A slice of strings
.TP
\fB\fB\-\-intmap\fR <default: \fI"a:1"\fR>\fP
A map from string to int
.SS Subgroup
.TP
\fB\fB\-\-sip.opt\fR\fP
This is a subgroup option
.SS Subsubgroup
.TP
\fB\fB\-\-sip.sap.opt\fR\fP
This is a subsubgroup option
@ -237,7 +272,7 @@ A command
Longer \fBcommand\fP description
\fBUsage\fP: TestMan [OPTIONS] command [command-OPTIONS]
.TP
\fBAliases\fP: cm, cmd
@ -298,3 +333,136 @@ Help Options:
assertDiff(t, e.Message, expected, "help message")
}
}
func TestHelpDefaults(t *testing.T) {
var expected string
if runtime.GOOS == "windows" {
expected = `Usage:
TestHelpDefaults [OPTIONS]
Application Options:
/with-default: With default (default: default-value)
/without-default: Without default
/with-programmatic-default: With programmatic default (default:
default-value)
Help Options:
/? Show this help message
/h, /help Show this help message
`
} else {
expected = `Usage:
TestHelpDefaults [OPTIONS]
Application Options:
--with-default= With default (default: default-value)
--without-default= Without default
--with-programmatic-default= With programmatic default (default:
default-value)
Help Options:
-h, --help Show this help message
`
}
tests := []struct {
Args []string
Output string
}{
{
Args: []string{"-h"},
Output: expected,
},
{
Args: []string{"--with-default", "other-value", "--with-programmatic-default", "other-value", "-h"},
Output: expected,
},
}
for _, test := range tests {
var opts struct {
WithDefault string `long:"with-default" default:"default-value" description:"With default"`
WithoutDefault string `long:"without-default" description:"Without default"`
WithProgrammaticDefault string `long:"with-programmatic-default" description:"With programmatic default"`
}
opts.WithProgrammaticDefault = "default-value"
p := NewNamedParser("TestHelpDefaults", HelpFlag)
p.AddGroup("Application Options", "The application options", &opts)
_, err := p.ParseArgs(test.Args)
if err == nil {
t.Fatalf("Expected help error")
}
if e, ok := err.(*Error); !ok {
t.Fatalf("Expected flags.Error, but got %T", err)
} else {
if e.Type != ErrHelp {
t.Errorf("Expected flags.ErrHelp type, but got %s", e.Type)
}
assertDiff(t, e.Message, test.Output, "help message")
}
}
}
func TestHelpRestArgs(t *testing.T) {
opts := struct {
Verbose bool `short:"v"`
}{}
p := NewNamedParser("TestHelpDefaults", HelpFlag)
p.AddGroup("Application Options", "The application options", &opts)
retargs, err := p.ParseArgs([]string{"-h", "-v", "rest"})
if err == nil {
t.Fatalf("Expected help error")
}
assertStringArray(t, retargs, []string{"-v", "rest"})
}
func TestWrapText(t *testing.T) {
s := "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
got := wrapText(s, 60, " ")
expected := `Lorem ipsum dolor sit amet, consectetur adipisicing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.`
assertDiff(t, got, expected, "wrapped text")
}
func TestWrapParagraph(t *testing.T) {
s := "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n\n"
s += "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n\n"
s += "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n\n"
s += "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n"
got := wrapText(s, 60, " ")
expected := `Lorem ipsum dolor sit amet, consectetur adipisicing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.
`
assertDiff(t, got, expected, "wrapped paragraph")
}

View file

@ -1,8 +1,14 @@
package flags
import (
"bufio"
"fmt"
"io"
"os"
"reflect"
"sort"
"strconv"
"strings"
)
// IniError contains location information on where an error occured.
@ -52,9 +58,25 @@ const (
// IniParser is a utility to read and write flags options from and to ini
// formatted strings.
type IniParser struct {
ParseAsDefaults bool // override default flags
parser *Parser
}
type iniValue struct {
Name string
Value string
Quoted bool
LineNumber uint
}
type iniSection []iniValue
type ini struct {
File string
Sections map[string]iniSection
}
// NewIniParser creates a new ini parser for a given Parser.
func NewIniParser(p *Parser) *IniParser {
return &IniParser{
@ -123,7 +145,7 @@ func (i *IniParser) Parse(reader io.Reader) error {
return i.parse(ini)
}
// WriteFile writes the flags as ini format into a file. See WriteIni
// WriteFile writes the flags as ini format into a file. See Write
// for more information. The returned error occurs when the specified file
// could not be opened for writing.
func (i *IniParser) WriteFile(filename string, options IniOptions) error {
@ -138,3 +160,442 @@ func (i *IniParser) WriteFile(filename string, options IniOptions) error {
func (i *IniParser) Write(writer io.Writer, options IniOptions) {
writeIni(i, writer, options)
}
func readFullLine(reader *bufio.Reader) (string, error) {
var line []byte
for {
l, more, err := reader.ReadLine()
if err != nil {
return "", err
}
if line == nil && !more {
return string(l), nil
}
line = append(line, l...)
if !more {
break
}
}
return string(line), nil
}
func optionIniName(option *Option) string {
name := option.tag.Get("_read-ini-name")
if len(name) != 0 {
return name
}
name = option.tag.Get("ini-name")
if len(name) != 0 {
return name
}
return option.field.Name
}
func writeGroupIni(cmd *Command, group *Group, namespace string, writer io.Writer, options IniOptions) {
var sname string
if len(namespace) != 0 {
sname = namespace
}
if cmd.Group != group && len(group.ShortDescription) != 0 {
if len(sname) != 0 {
sname += "."
}
sname += group.ShortDescription
}
sectionwritten := false
comments := (options & IniIncludeComments) != IniNone
for _, option := range group.options {
if option.isFunc() || option.Hidden {
continue
}
if len(option.tag.Get("no-ini")) != 0 {
continue
}
val := option.value
if (options&IniIncludeDefaults) == IniNone && option.valueIsDefault() {
continue
}
if !sectionwritten {
fmt.Fprintf(writer, "[%s]\n", sname)
sectionwritten = true
}
if comments && len(option.Description) != 0 {
fmt.Fprintf(writer, "; %s\n", option.Description)
}
oname := optionIniName(option)
commentOption := (options&(IniIncludeDefaults|IniCommentDefaults)) == IniIncludeDefaults|IniCommentDefaults && option.valueIsDefault()
kind := val.Type().Kind()
switch kind {
case reflect.Slice:
kind = val.Type().Elem().Kind()
if val.Len() == 0 {
writeOption(writer, oname, kind, "", "", true, option.iniQuote)
} else {
for idx := 0; idx < val.Len(); idx++ {
v, _ := convertToString(val.Index(idx), option.tag)
writeOption(writer, oname, kind, "", v, commentOption, option.iniQuote)
}
}
case reflect.Map:
kind = val.Type().Elem().Kind()
if val.Len() == 0 {
writeOption(writer, oname, kind, "", "", true, option.iniQuote)
} else {
mkeys := val.MapKeys()
keys := make([]string, len(val.MapKeys()))
kkmap := make(map[string]reflect.Value)
for i, k := range mkeys {
keys[i], _ = convertToString(k, option.tag)
kkmap[keys[i]] = k
}
sort.Strings(keys)
for _, k := range keys {
v, _ := convertToString(val.MapIndex(kkmap[k]), option.tag)
writeOption(writer, oname, kind, k, v, commentOption, option.iniQuote)
}
}
default:
v, _ := convertToString(val, option.tag)
writeOption(writer, oname, kind, "", v, commentOption, option.iniQuote)
}
if comments {
fmt.Fprintln(writer)
}
}
if sectionwritten && !comments {
fmt.Fprintln(writer)
}
}
func writeOption(writer io.Writer, optionName string, optionType reflect.Kind, optionKey string, optionValue string, commentOption bool, forceQuote bool) {
if forceQuote || (optionType == reflect.String && !isPrint(optionValue)) {
optionValue = strconv.Quote(optionValue)
}
comment := ""
if commentOption {
comment = "; "
}
fmt.Fprintf(writer, "%s%s =", comment, optionName)
if optionKey != "" {
fmt.Fprintf(writer, " %s:%s", optionKey, optionValue)
} else if optionValue != "" {
fmt.Fprintf(writer, " %s", optionValue)
}
fmt.Fprintln(writer)
}
func writeCommandIni(command *Command, namespace string, writer io.Writer, options IniOptions) {
command.eachGroup(func(group *Group) {
if !group.Hidden {
writeGroupIni(command, group, namespace, writer, options)
}
})
for _, c := range command.commands {
var nns string
if c.Hidden {
continue
}
if len(namespace) != 0 {
nns = c.Name + "." + nns
} else {
nns = c.Name
}
writeCommandIni(c, nns, writer, options)
}
}
func writeIni(parser *IniParser, writer io.Writer, options IniOptions) {
writeCommandIni(parser.parser.Command, "", writer, options)
}
func writeIniToFile(parser *IniParser, filename string, options IniOptions) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
writeIni(parser, file, options)
return nil
}
func readIniFromFile(filename string) (*ini, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
return readIni(file, filename)
}
func readIni(contents io.Reader, filename string) (*ini, error) {
ret := &ini{
File: filename,
Sections: make(map[string]iniSection),
}
reader := bufio.NewReader(contents)
// Empty global section
section := make(iniSection, 0, 10)
sectionname := ""
ret.Sections[sectionname] = section
var lineno uint
for {
line, err := readFullLine(reader)
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
lineno++
line = strings.TrimSpace(line)
// Skip empty lines and lines starting with ; (comments)
if len(line) == 0 || line[0] == ';' || line[0] == '#' {
continue
}
if line[0] == '[' {
if line[0] != '[' || line[len(line)-1] != ']' {
return nil, &IniError{
Message: "malformed section header",
File: filename,
LineNumber: lineno,
}
}
name := strings.TrimSpace(line[1 : len(line)-1])
if len(name) == 0 {
return nil, &IniError{
Message: "empty section name",
File: filename,
LineNumber: lineno,
}
}
sectionname = name
section = ret.Sections[name]
if section == nil {
section = make(iniSection, 0, 10)
ret.Sections[name] = section
}
continue
}
// Parse option here
keyval := strings.SplitN(line, "=", 2)
if len(keyval) != 2 {
return nil, &IniError{
Message: fmt.Sprintf("malformed key=value (%s)", line),
File: filename,
LineNumber: lineno,
}
}
name := strings.TrimSpace(keyval[0])
value := strings.TrimSpace(keyval[1])
quoted := false
if len(value) != 0 && value[0] == '"' {
if v, err := strconv.Unquote(value); err == nil {
value = v
quoted = true
} else {
return nil, &IniError{
Message: err.Error(),
File: filename,
LineNumber: lineno,
}
}
}
section = append(section, iniValue{
Name: name,
Value: value,
Quoted: quoted,
LineNumber: lineno,
})
ret.Sections[sectionname] = section
}
return ret, nil
}
func (i *IniParser) matchingGroups(name string) []*Group {
if len(name) == 0 {
var ret []*Group
i.parser.eachGroup(func(g *Group) {
ret = append(ret, g)
})
return ret
}
g := i.parser.groupByName(name)
if g != nil {
return []*Group{g}
}
return nil
}
func (i *IniParser) parse(ini *ini) error {
p := i.parser
var quotesLookup = make(map[*Option]bool)
for name, section := range ini.Sections {
groups := i.matchingGroups(name)
if len(groups) == 0 {
return newErrorf(ErrUnknownGroup, "could not find option group `%s'", name)
}
for _, inival := range section {
var opt *Option
for _, group := range groups {
opt = group.optionByName(inival.Name, func(o *Option, n string) bool {
return strings.ToLower(o.tag.Get("ini-name")) == strings.ToLower(n)
})
if opt != nil && len(opt.tag.Get("no-ini")) != 0 {
opt = nil
}
if opt != nil {
break
}
}
if opt == nil {
if (p.Options & IgnoreUnknown) == None {
return &IniError{
Message: fmt.Sprintf("unknown option: %s", inival.Name),
File: ini.File,
LineNumber: inival.LineNumber,
}
}
continue
}
// ini value is ignored if override is set and
// value was previously set from non default
if i.ParseAsDefaults && !opt.isSetDefault {
continue
}
pval := &inival.Value
if !opt.canArgument() && len(inival.Value) == 0 {
pval = nil
} else {
if opt.value.Type().Kind() == reflect.Map {
parts := strings.SplitN(inival.Value, ":", 2)
// only handle unquoting
if len(parts) == 2 && parts[1][0] == '"' {
if v, err := strconv.Unquote(parts[1]); err == nil {
parts[1] = v
inival.Quoted = true
} else {
return &IniError{
Message: err.Error(),
File: ini.File,
LineNumber: inival.LineNumber,
}
}
s := parts[0] + ":" + parts[1]
pval = &s
}
}
}
if err := opt.set(pval); err != nil {
return &IniError{
Message: err.Error(),
File: ini.File,
LineNumber: inival.LineNumber,
}
}
// either all INI values are quoted or only values who need quoting
if _, ok := quotesLookup[opt]; !inival.Quoted || !ok {
quotesLookup[opt] = inival.Quoted
}
opt.tag.Set("_read-ini-name", inival.Name)
}
}
for opt, quoted := range quotesLookup {
opt.iniQuote = quoted
}
return nil
}

View file

@ -1,452 +0,0 @@
package flags
import (
"bufio"
"fmt"
"io"
"os"
"reflect"
"sort"
"strconv"
"strings"
)
type iniValue struct {
Name string
Value string
Quoted bool
LineNumber uint
}
type iniSection []iniValue
type ini struct {
File string
Sections map[string]iniSection
}
func readFullLine(reader *bufio.Reader) (string, error) {
var line []byte
for {
l, more, err := reader.ReadLine()
if err != nil {
return "", err
}
if line == nil && !more {
return string(l), nil
}
line = append(line, l...)
if !more {
break
}
}
return string(line), nil
}
func optionIniName(option *Option) string {
name := option.tag.Get("_read-ini-name")
if len(name) != 0 {
return name
}
name = option.tag.Get("ini-name")
if len(name) != 0 {
return name
}
return option.field.Name
}
func writeGroupIni(cmd *Command, group *Group, namespace string, writer io.Writer, options IniOptions) {
var sname string
if len(namespace) != 0 {
sname = namespace
}
if cmd.Group != group && len(group.ShortDescription) != 0 {
if len(sname) != 0 {
sname += "."
}
sname += group.ShortDescription
}
sectionwritten := false
comments := (options & IniIncludeComments) != IniNone
for _, option := range group.options {
if option.isFunc() {
continue
}
if len(option.tag.Get("no-ini")) != 0 {
continue
}
val := option.value
if (options&IniIncludeDefaults) == IniNone && option.valueIsDefault() {
continue
}
if !sectionwritten {
fmt.Fprintf(writer, "[%s]\n", sname)
sectionwritten = true
}
if comments && len(option.Description) != 0 {
fmt.Fprintf(writer, "; %s\n", option.Description)
}
oname := optionIniName(option)
commentOption := (options&(IniIncludeDefaults|IniCommentDefaults)) == IniIncludeDefaults|IniCommentDefaults && option.valueIsDefault()
kind := val.Type().Kind()
switch kind {
case reflect.Slice:
kind = val.Type().Elem().Kind()
if val.Len() == 0 {
writeOption(writer, oname, kind, "", "", true, option.iniQuote)
} else {
for idx := 0; idx < val.Len(); idx++ {
v, _ := convertToString(val.Index(idx), option.tag)
writeOption(writer, oname, kind, "", v, commentOption, option.iniQuote)
}
}
case reflect.Map:
kind = val.Type().Elem().Kind()
if val.Len() == 0 {
writeOption(writer, oname, kind, "", "", true, option.iniQuote)
} else {
mkeys := val.MapKeys()
keys := make([]string, len(val.MapKeys()))
kkmap := make(map[string]reflect.Value)
for i, k := range mkeys {
keys[i], _ = convertToString(k, option.tag)
kkmap[keys[i]] = k
}
sort.Strings(keys)
for _, k := range keys {
v, _ := convertToString(val.MapIndex(kkmap[k]), option.tag)
writeOption(writer, oname, kind, k, v, commentOption, option.iniQuote)
}
}
default:
v, _ := convertToString(val, option.tag)
writeOption(writer, oname, kind, "", v, commentOption, option.iniQuote)
}
if comments {
fmt.Fprintln(writer)
}
}
if sectionwritten && !comments {
fmt.Fprintln(writer)
}
}
func writeOption(writer io.Writer, optionName string, optionType reflect.Kind, optionKey string, optionValue string, commentOption bool, forceQuote bool) {
if forceQuote || (optionType == reflect.String && !isPrint(optionValue)) {
optionValue = strconv.Quote(optionValue)
}
comment := ""
if commentOption {
comment = "; "
}
fmt.Fprintf(writer, "%s%s =", comment, optionName)
if optionKey != "" {
fmt.Fprintf(writer, " %s:%s", optionKey, optionValue)
} else if optionValue != "" {
fmt.Fprintf(writer, " %s", optionValue)
}
fmt.Fprintln(writer)
}
func writeCommandIni(command *Command, namespace string, writer io.Writer, options IniOptions) {
command.eachGroup(func(group *Group) {
writeGroupIni(command, group, namespace, writer, options)
})
for _, c := range command.commands {
var nns string
if len(namespace) != 0 {
nns = c.Name + "." + nns
} else {
nns = c.Name
}
writeCommandIni(c, nns, writer, options)
}
}
func writeIni(parser *IniParser, writer io.Writer, options IniOptions) {
writeCommandIni(parser.parser.Command, "", writer, options)
}
func writeIniToFile(parser *IniParser, filename string, options IniOptions) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
writeIni(parser, file, options)
return nil
}
func readIniFromFile(filename string) (*ini, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
return readIni(file, filename)
}
func readIni(contents io.Reader, filename string) (*ini, error) {
ret := &ini{
File: filename,
Sections: make(map[string]iniSection),
}
reader := bufio.NewReader(contents)
// Empty global section
section := make(iniSection, 0, 10)
sectionname := ""
ret.Sections[sectionname] = section
var lineno uint
for {
line, err := readFullLine(reader)
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
lineno++
line = strings.TrimSpace(line)
// Skip empty lines and lines starting with ; (comments)
if len(line) == 0 || line[0] == ';' || line[0] == '#' {
continue
}
if line[0] == '[' {
if line[0] != '[' || line[len(line)-1] != ']' {
return nil, &IniError{
Message: "malformed section header",
File: filename,
LineNumber: lineno,
}
}
name := strings.TrimSpace(line[1 : len(line)-1])
if len(name) == 0 {
return nil, &IniError{
Message: "empty section name",
File: filename,
LineNumber: lineno,
}
}
sectionname = name
section = ret.Sections[name]
if section == nil {
section = make(iniSection, 0, 10)
ret.Sections[name] = section
}
continue
}
// Parse option here
keyval := strings.SplitN(line, "=", 2)
if len(keyval) != 2 {
return nil, &IniError{
Message: fmt.Sprintf("malformed key=value (%s)", line),
File: filename,
LineNumber: lineno,
}
}
name := strings.TrimSpace(keyval[0])
value := strings.TrimSpace(keyval[1])
quoted := false
if len(value) != 0 && value[0] == '"' {
if v, err := strconv.Unquote(value); err == nil {
value = v
quoted = true
} else {
return nil, &IniError{
Message: err.Error(),
File: filename,
LineNumber: lineno,
}
}
}
section = append(section, iniValue{
Name: name,
Value: value,
Quoted: quoted,
LineNumber: lineno,
})
ret.Sections[sectionname] = section
}
return ret, nil
}
func (i *IniParser) matchingGroups(name string) []*Group {
if len(name) == 0 {
var ret []*Group
i.parser.eachGroup(func(g *Group) {
ret = append(ret, g)
})
return ret
}
g := i.parser.groupByName(name)
if g != nil {
return []*Group{g}
}
return nil
}
func (i *IniParser) parse(ini *ini) error {
p := i.parser
var quotesLookup = make(map[*Option]bool)
for name, section := range ini.Sections {
groups := i.matchingGroups(name)
if len(groups) == 0 {
return newErrorf(ErrUnknownGroup, "could not find option group `%s'", name)
}
for _, inival := range section {
var opt *Option
for _, group := range groups {
opt = group.optionByName(inival.Name, func(o *Option, n string) bool {
return strings.ToLower(o.tag.Get("ini-name")) == strings.ToLower(n)
})
if opt != nil && len(opt.tag.Get("no-ini")) != 0 {
opt = nil
}
if opt != nil {
break
}
}
if opt == nil {
if (p.Options & IgnoreUnknown) == None {
return &IniError{
Message: fmt.Sprintf("unknown option: %s", inival.Name),
File: ini.File,
LineNumber: inival.LineNumber,
}
}
continue
}
pval := &inival.Value
if !opt.canArgument() && len(inival.Value) == 0 {
pval = nil
} else {
if opt.value.Type().Kind() == reflect.Map {
parts := strings.SplitN(inival.Value, ":", 2)
// only handle unquoting
if len(parts) == 2 && parts[1][0] == '"' {
if v, err := strconv.Unquote(parts[1]); err == nil {
parts[1] = v
inival.Quoted = true
} else {
return &IniError{
Message: err.Error(),
File: ini.File,
LineNumber: inival.LineNumber,
}
}
s := parts[0] + ":" + parts[1]
pval = &s
}
}
}
if err := opt.set(pval); err != nil {
return &IniError{
Message: err.Error(),
File: ini.File,
LineNumber: inival.LineNumber,
}
}
// either all INI values are quoted or only values who need quoting
if _, ok := quotesLookup[opt]; !inival.Quoted || !ok {
quotesLookup[opt] = inival.Quoted
}
opt.tag.Set("_read-ini-name", inival.Name)
}
}
for opt, quoted := range quotesLookup {
opt.iniQuote = quoted
}
return nil
}

View file

@ -21,7 +21,7 @@ func TestWriteIni(t *testing.T) {
expected string
}{
{
[]string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "command"},
[]string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "3.14", "command"},
IniDefault,
`[Application Options]
; Show verbose debug information
@ -42,7 +42,7 @@ int-map = b:3
`,
},
{
[]string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "command"},
[]string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "3.14", "command"},
IniDefault | IniIncludeDefaults,
`[Application Options]
; Show verbose debug information
@ -74,6 +74,9 @@ EnvDefault2 = env-def
; Option with named argument
OptionWithArgName =
; Option with choices
OptionWithChoices =
; Option only available in ini
only-ini =
@ -101,7 +104,7 @@ Opt =
`,
},
{
[]string{"filename", "0", "command"},
[]string{"filename", "0", "3.14", "command"},
IniDefault | IniIncludeDefaults | IniCommentDefaults,
`[Application Options]
; Show verbose debug information
@ -132,6 +135,9 @@ EnvDefault2 = env-def
; Option with named argument
; OptionWithArgName =
; Option with choices
; OptionWithChoices =
; Option only available in ini
; only-ini =
@ -158,7 +164,7 @@ EnvDefault2 = env-def
`,
},
{
[]string{"--default=New value", "--default-array=New value", "--default-map=new:value", "filename", "0", "command"},
[]string{"--default=New value", "--default-array=New value", "--default-map=new:value", "filename", "0", "3.14", "command"},
IniDefault | IniIncludeDefaults | IniCommentDefaults,
`[Application Options]
; Show verbose debug information
@ -187,6 +193,9 @@ EnvDefault2 = env-def
; Option with named argument
; OptionWithArgName =
; Option with choices
; OptionWithChoices =
; Option only available in ini
; only-ini =
@ -239,6 +248,92 @@ EnvDefault2 = env-def
}
}
func TestReadIni_flagEquivalent(t *testing.T) {
type options struct {
Opt1 bool `long:"opt1"`
Group1 struct {
Opt2 bool `long:"opt2"`
} `group:"group1"`
Group2 struct {
Opt3 bool `long:"opt3"`
} `group:"group2" namespace:"ns1"`
Cmd1 struct {
Opt4 bool `long:"opt4"`
Opt5 bool `long:"foo.opt5"`
Group1 struct {
Opt6 bool `long:"opt6"`
Opt7 bool `long:"foo.opt7"`
} `group:"group1"`
Group2 struct {
Opt8 bool `long:"opt8"`
} `group:"group2" namespace:"ns1"`
} `command:"cmd1"`
}
a := `
opt1=true
[group1]
opt2=true
[group2]
ns1.opt3=true
[cmd1]
opt4=true
foo.opt5=true
[cmd1.group1]
opt6=true
foo.opt7=true
[cmd1.group2]
ns1.opt8=true
`
b := `
opt1=true
opt2=true
ns1.opt3=true
[cmd1]
opt4=true
foo.opt5=true
opt6=true
foo.opt7=true
ns1.opt8=true
`
parse := func(readIni string) (opts options, writeIni string) {
p := NewNamedParser("TestIni", Default)
p.AddGroup("Application Options", "The application options", &opts)
inip := NewIniParser(p)
err := inip.Parse(strings.NewReader(readIni))
if err != nil {
t.Fatalf("Unexpected error: %s\n\nFile:\n%s", err, readIni)
}
var b bytes.Buffer
inip.Write(&b, Default)
return opts, b.String()
}
aOpt, aIni := parse(a)
bOpt, bIni := parse(b)
assertDiff(t, aIni, bIni, "")
if !reflect.DeepEqual(aOpt, bOpt) {
t.Errorf("not equal")
}
}
func TestReadIni(t *testing.T) {
var opts helpOptions
@ -663,6 +758,94 @@ func TestIniParse(t *testing.T) {
}
}
func TestIniCliOverrides(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("Cannot create temporary file: %s", err)
}
defer os.Remove(file.Name())
_, err = file.WriteString("values = 123\n")
_, err = file.WriteString("values = 456\n")
if err != nil {
t.Fatalf("Cannot write to temporary file: %s", err)
}
file.Close()
var opts struct {
Values []int `long:"values"`
}
p := NewParser(&opts, Default)
err = NewIniParser(p).ParseFile(file.Name())
if err != nil {
t.Fatalf("Could not parse ini: %s", err)
}
_, err = p.ParseArgs([]string{"--values", "111", "--values", "222"})
if err != nil {
t.Fatalf("Failed to parse arguments: %s", err)
}
if len(opts.Values) != 2 {
t.Fatalf("Expected Values to contain two elements, but got %d", len(opts.Values))
}
if opts.Values[0] != 111 {
t.Fatalf("Expected Values[0] to be 111, but got '%d'", opts.Values[0])
}
if opts.Values[1] != 222 {
t.Fatalf("Expected Values[1] to be 222, but got '%d'", opts.Values[1])
}
}
func TestIniOverrides(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("Cannot create temporary file: %s", err)
}
defer os.Remove(file.Name())
_, err = file.WriteString("value-with-default = \"ini-value\"\n")
_, err = file.WriteString("value-with-default-override-cli = \"ini-value\"\n")
if err != nil {
t.Fatalf("Cannot write to temporary file: %s", err)
}
file.Close()
var opts struct {
ValueWithDefault string `long:"value-with-default" default:"value"`
ValueWithDefaultOverrideCli string `long:"value-with-default-override-cli" default:"value"`
}
p := NewParser(&opts, Default)
err = NewIniParser(p).ParseFile(file.Name())
if err != nil {
t.Fatalf("Could not parse ini: %s", err)
}
_, err = p.ParseArgs([]string{"--value-with-default-override-cli", "cli-value"})
if err != nil {
t.Fatalf("Failed to parse arguments: %s", err)
}
assertString(t, opts.ValueWithDefault, "ini-value")
assertString(t, opts.ValueWithDefaultOverrideCli, "cli-value")
}
func TestWriteFile(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
@ -765,3 +948,74 @@ func TestOverwriteRequiredOptions(t *testing.T) {
}
}
}
func TestIniOverwriteOptions(t *testing.T) {
var tests = []struct {
args []string
expected string
toggled bool
}{
{
args: []string{},
expected: "from default",
},
{
args: []string{"--value", "from CLI"},
expected: "from CLI",
},
{
args: []string{"--config", "no file name"},
expected: "from INI",
toggled: true,
},
{
args: []string{"--value", "from CLI before", "--config", "no file name"},
expected: "from CLI before",
toggled: true,
},
{
args: []string{"--config", "no file name", "--value", "from CLI after"},
expected: "from CLI after",
toggled: true,
},
{
args: []string{"--toggle"},
toggled: true,
expected: "from default",
},
}
for _, test := range tests {
var opts struct {
Config string `long:"config" no-ini:"true"`
Value string `long:"value" default:"from default"`
Toggle bool `long:"toggle"`
}
p := NewParser(&opts, Default)
_, err := p.ParseArgs(test.args)
if err != nil {
t.Fatalf("Unexpected error %s with args %+v", err, test.args)
}
if opts.Config != "" {
inip := NewIniParser(p)
inip.ParseAsDefaults = true
err = inip.Parse(bytes.NewBufferString("value = from INI\ntoggle = true"))
if err != nil {
t.Fatalf("Unexpected error %s with args %+v", err, test.args)
}
}
if opts.Value != test.expected {
t.Fatalf("Expected Value to be \"%s\" but was \"%s\" with args %+v", test.expected, opts.Value, test.args)
}
if opts.Toggle != test.toggled {
t.Fatalf("Expected Toggle to be \"%v\" but was \"%v\" with args %+v", test.toggled, opts.Toggle, test.args)
}
}
}

View file

@ -38,8 +38,23 @@ func formatForMan(wr io.Writer, s string) {
func writeManPageOptions(wr io.Writer, grp *Group) {
grp.eachGroup(func(group *Group) {
if group.Hidden || len(group.options) == 0 {
return
}
// If the parent (grp) has any subgroups, display their descriptions as
// subsection headers similar to the output of --help.
if group.ShortDescription != "" && len(grp.groups) > 0 {
fmt.Fprintf(wr, ".SS %s\n", group.ShortDescription)
if group.LongDescription != "" {
formatForMan(wr, group.LongDescription)
fmt.Fprintln(wr, "")
}
}
for _, opt := range group.options {
if !opt.canCli() {
if !opt.canCli() || opt.Hidden {
continue
}
@ -91,11 +106,15 @@ func writeManPageOptions(wr io.Writer, grp *Group) {
}
func writeManPageSubcommands(wr io.Writer, name string, root *Command) {
commands := root.sortedCommands()
commands := root.sortedVisibleCommands()
for _, c := range commands {
var nn string
if c.Hidden {
continue
}
if len(name) != 0 {
nn = name + " " + c.Name
} else {
@ -141,7 +160,7 @@ func writeManPageCommand(wr io.Writer, name string, root *Command, command *Comm
}
if len(usage) > 0 {
fmt.Fprintf(wr, "\n\\fBUsage\\fP: %s %s\n\n", manQuote(pre), manQuote(usage))
fmt.Fprintf(wr, "\n\\fBUsage\\fP: %s %s\n.TP\n", manQuote(pre), manQuote(usage))
}
if len(command.Aliases) > 0 {
@ -178,7 +197,7 @@ func (p *Parser) WriteManPage(wr io.Writer) {
writeManPageOptions(wr, p.Command.Group)
if len(p.commands) > 0 {
if len(p.visibleCommands()) > 0 {
fmt.Fprintln(wr, ".SH COMMANDS")
writeManPageSubcommands(wr, "", p.Command)

View file

@ -5,13 +5,13 @@ import (
"testing"
)
type marshalled bool
type marshalled string
func (m *marshalled) UnmarshalFlag(value string) error {
if value == "yes" {
*m = true
*m = "true"
} else if value == "no" {
*m = false
*m = "false"
} else {
return fmt.Errorf("`%s' is not a valid value, please specify `yes' or `no'", value)
}
@ -20,7 +20,7 @@ func (m *marshalled) UnmarshalFlag(value string) error {
}
func (m marshalled) MarshalFlag() (string, error) {
if m {
if m == "true" {
return "yes", nil
}
@ -42,8 +42,8 @@ func TestUnmarshal(t *testing.T) {
assertStringArray(t, ret, []string{})
if !opts.Value {
t.Errorf("Expected Value to be true")
if opts.Value != "true" {
t.Errorf("Expected Value to be \"true\"")
}
}
@ -56,8 +56,8 @@ func TestUnmarshalDefault(t *testing.T) {
assertStringArray(t, ret, []string{})
if !opts.Value {
t.Errorf("Expected Value to be true")
if opts.Value != "true" {
t.Errorf("Expected Value to be \"true\"")
}
}
@ -70,8 +70,8 @@ func TestUnmarshalOptional(t *testing.T) {
assertStringArray(t, ret, []string{})
if !opts.Value {
t.Errorf("Expected Value to be true")
if opts.Value != "true" {
t.Errorf("Expected Value to be \"true\"")
}
}
@ -83,6 +83,28 @@ func TestUnmarshalError(t *testing.T) {
assertParseFail(t, ErrMarshal, fmt.Sprintf("invalid argument for flag `%cv' (expected flags.marshalled): `invalid' is not a valid value, please specify `yes' or `no'", defaultShortOptDelimiter), &opts, "-vinvalid")
}
func TestUnmarshalPositionalError(t *testing.T) {
var opts = struct {
Args struct {
Value marshalled
} `positional-args:"yes"`
}{}
parser := NewParser(&opts, Default&^PrintErrors)
_, err := parser.ParseArgs([]string{"invalid"})
msg := "`invalid' is not a valid value, please specify `yes' or `no'"
if err == nil {
assertFatalf(t, "Expected error: %s", msg)
return
}
if err.Error() != msg {
assertErrorf(t, "Expected error message %#v, but got %#v", msg, err.Error())
}
}
func TestMarshalError(t *testing.T) {
var opts = struct {
Value marshalledError `short:"v"`

View file

@ -1,8 +1,11 @@
package flags
import (
"bytes"
"fmt"
"reflect"
"strings"
"syscall"
"unicode/utf8"
)
@ -59,6 +62,12 @@ type Option struct {
// passwords.
DefaultMask string
// If non empty, only a certain set of values is allowed for an option.
Choices []string
// If true, the option is not displayed in the help or man page
Hidden bool
// The group which the option belongs to
group *Group
@ -71,8 +80,12 @@ type Option struct {
// Determines if the option will be always quoted in the INI output
iniQuote bool
tag multiTag
isSet bool
tag multiTag
isSet bool
isSetDefault bool
preventDefault bool
defaultLiteral string
}
// LongNameWithNamespace returns the option's long name with the group namespaces
@ -156,7 +169,288 @@ func (option *Option) Value() interface{} {
return option.value.Interface()
}
// Field returns the reflect struct field of the option.
func (option *Option) Field() reflect.StructField {
return option.field
}
// IsSet returns true if option has been set
func (option *Option) IsSet() bool {
return option.isSet
}
// Set the value of an option to the specified value. An error will be returned
// if the specified value could not be converted to the corresponding option
// value type.
func (option *Option) set(value *string) error {
kind := option.value.Type().Kind()
if (kind == reflect.Map || kind == reflect.Slice) && !option.isSet {
option.empty()
}
option.isSet = true
option.preventDefault = true
if len(option.Choices) != 0 {
found := false
for _, choice := range option.Choices {
if choice == *value {
found = true
break
}
}
if !found {
allowed := strings.Join(option.Choices[0:len(option.Choices)-1], ", ")
if len(option.Choices) > 1 {
allowed += " or " + option.Choices[len(option.Choices)-1]
}
return newErrorf(ErrInvalidChoice,
"Invalid value `%s' for option `%s'. Allowed values are: %s",
*value, option, allowed)
}
}
if option.isFunc() {
return option.call(value)
} else if value != nil {
return convert(*value, option.value, option.tag)
}
return convert("", option.value, option.tag)
}
func (option *Option) canCli() bool {
return option.ShortName != 0 || len(option.LongName) != 0
}
func (option *Option) canArgument() bool {
if u := option.isUnmarshaler(); u != nil {
return true
}
return !option.isBool()
}
func (option *Option) emptyValue() reflect.Value {
tp := option.value.Type()
if tp.Kind() == reflect.Map {
return reflect.MakeMap(tp)
}
return reflect.Zero(tp)
}
func (option *Option) empty() {
if !option.isFunc() {
option.value.Set(option.emptyValue())
}
}
func (option *Option) clearDefault() {
usedDefault := option.Default
if envKey := option.EnvDefaultKey; envKey != "" {
// os.Getenv() makes no distinction between undefined and
// empty values, so we use syscall.Getenv()
if value, ok := syscall.Getenv(envKey); ok {
if option.EnvDefaultDelim != "" {
usedDefault = strings.Split(value,
option.EnvDefaultDelim)
} else {
usedDefault = []string{value}
}
}
}
option.isSetDefault = true
if len(usedDefault) > 0 {
option.empty()
for _, d := range usedDefault {
option.set(&d)
option.isSetDefault = true
}
} else {
tp := option.value.Type()
switch tp.Kind() {
case reflect.Map:
if option.value.IsNil() {
option.empty()
}
case reflect.Slice:
if option.value.IsNil() {
option.empty()
}
}
}
}
func (option *Option) valueIsDefault() bool {
// Check if the value of the option corresponds to its
// default value
emptyval := option.emptyValue()
checkvalptr := reflect.New(emptyval.Type())
checkval := reflect.Indirect(checkvalptr)
checkval.Set(emptyval)
if len(option.Default) != 0 {
for _, v := range option.Default {
convert(v, checkval, option.tag)
}
}
return reflect.DeepEqual(option.value.Interface(), checkval.Interface())
}
func (option *Option) isUnmarshaler() Unmarshaler {
v := option.value
for {
if !v.CanInterface() {
break
}
i := v.Interface()
if u, ok := i.(Unmarshaler); ok {
return u
}
if !v.CanAddr() {
break
}
v = v.Addr()
}
return nil
}
func (option *Option) isBool() bool {
tp := option.value.Type()
for {
switch tp.Kind() {
case reflect.Slice, reflect.Ptr:
tp = tp.Elem()
case reflect.Bool:
return true
case reflect.Func:
return tp.NumIn() == 0
default:
return false
}
}
}
func (option *Option) isSignedNumber() bool {
tp := option.value.Type()
for {
switch tp.Kind() {
case reflect.Slice, reflect.Ptr:
tp = tp.Elem()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64:
return true
default:
return false
}
}
}
func (option *Option) isFunc() bool {
return option.value.Type().Kind() == reflect.Func
}
func (option *Option) call(value *string) error {
var retval []reflect.Value
if value == nil {
retval = option.value.Call(nil)
} else {
tp := option.value.Type().In(0)
val := reflect.New(tp)
val = reflect.Indirect(val)
if err := convert(*value, val, option.tag); err != nil {
return err
}
retval = option.value.Call([]reflect.Value{val})
}
if len(retval) == 1 && retval[0].Type() == reflect.TypeOf((*error)(nil)).Elem() {
if retval[0].Interface() == nil {
return nil
}
return retval[0].Interface().(error)
}
return nil
}
func (option *Option) updateDefaultLiteral() {
defs := option.Default
def := ""
if len(defs) == 0 && option.canArgument() {
var showdef bool
switch option.field.Type.Kind() {
case reflect.Func, reflect.Ptr:
showdef = !option.value.IsNil()
case reflect.Slice, reflect.String, reflect.Array:
showdef = option.value.Len() > 0
case reflect.Map:
showdef = !option.value.IsNil() && option.value.Len() > 0
default:
zeroval := reflect.Zero(option.field.Type)
showdef = !reflect.DeepEqual(zeroval.Interface(), option.value.Interface())
}
if showdef {
def, _ = convertToString(option.value, option.tag)
}
} else if len(defs) != 0 {
l := len(defs) - 1
for i := 0; i < l; i++ {
def += quoteIfNeeded(defs[i]) + ", "
}
def += quoteIfNeeded(defs[l])
}
option.defaultLiteral = def
}
func (option *Option) shortAndLongName() string {
ret := &bytes.Buffer{}
if option.ShortName != 0 {
ret.WriteRune(defaultShortOptDelimiter)
ret.WriteRune(option.ShortName)
}
if len(option.LongName) != 0 {
if option.ShortName != 0 {
ret.WriteRune('/')
}
ret.WriteString(option.LongName)
}
return ret.String()
}

View file

@ -1,182 +0,0 @@
package flags
import (
"reflect"
"strings"
"syscall"
)
// Set the value of an option to the specified value. An error will be returned
// if the specified value could not be converted to the corresponding option
// value type.
func (option *Option) set(value *string) error {
option.isSet = true
if option.isFunc() {
return option.call(value)
} else if value != nil {
return convert(*value, option.value, option.tag)
}
return convert("", option.value, option.tag)
}
func (option *Option) canCli() bool {
return option.ShortName != 0 || len(option.LongName) != 0
}
func (option *Option) canArgument() bool {
if u := option.isUnmarshaler(); u != nil {
return true
}
return !option.isBool()
}
func (option *Option) emptyValue() reflect.Value {
tp := option.value.Type()
if tp.Kind() == reflect.Map {
return reflect.MakeMap(tp)
}
return reflect.Zero(tp)
}
func (option *Option) empty() {
if !option.isFunc() {
option.value.Set(option.emptyValue())
}
}
func (option *Option) clearDefault() {
usedDefault := option.Default
if envKey := option.EnvDefaultKey; envKey != "" {
// os.Getenv() makes no distinction between undefined and
// empty values, so we use syscall.Getenv()
if value, ok := syscall.Getenv(envKey); ok {
if option.EnvDefaultDelim != "" {
usedDefault = strings.Split(value,
option.EnvDefaultDelim)
} else {
usedDefault = []string{value}
}
}
}
if len(usedDefault) > 0 {
option.empty()
for _, d := range usedDefault {
option.set(&d)
}
} else {
tp := option.value.Type()
switch tp.Kind() {
case reflect.Map:
if option.value.IsNil() {
option.empty()
}
case reflect.Slice:
if option.value.IsNil() {
option.empty()
}
}
}
}
func (option *Option) valueIsDefault() bool {
// Check if the value of the option corresponds to its
// default value
emptyval := option.emptyValue()
checkvalptr := reflect.New(emptyval.Type())
checkval := reflect.Indirect(checkvalptr)
checkval.Set(emptyval)
if len(option.Default) != 0 {
for _, v := range option.Default {
convert(v, checkval, option.tag)
}
}
return reflect.DeepEqual(option.value.Interface(), checkval.Interface())
}
func (option *Option) isUnmarshaler() Unmarshaler {
v := option.value
for {
if !v.CanInterface() {
break
}
i := v.Interface()
if u, ok := i.(Unmarshaler); ok {
return u
}
if !v.CanAddr() {
break
}
v = v.Addr()
}
return nil
}
func (option *Option) isBool() bool {
tp := option.value.Type()
for {
switch tp.Kind() {
case reflect.Bool:
return true
case reflect.Slice:
return (tp.Elem().Kind() == reflect.Bool)
case reflect.Func:
return tp.NumIn() == 0
case reflect.Ptr:
tp = tp.Elem()
default:
return false
}
}
}
func (option *Option) isFunc() bool {
return option.value.Type().Kind() == reflect.Func
}
func (option *Option) call(value *string) error {
var retval []reflect.Value
if value == nil {
retval = option.value.Call(nil)
} else {
tp := option.value.Type().In(0)
val := reflect.New(tp)
val = reflect.Indirect(val)
if err := convert(*value, val, option.tag); err != nil {
return err
}
retval = option.value.Call([]reflect.Value{val})
}
if len(retval) == 1 && retval[0].Type() == reflect.TypeOf((*error)(nil)).Elem() {
if retval[0].Interface() == nil {
return nil
}
return retval[0].Interface().(error)
}
return nil
}

View file

@ -1,4 +1,4 @@
// +build !windows
// +build !windows forceposix
package flags

View file

@ -1,3 +1,5 @@
// +build !forceposix
package flags
import (

View file

@ -5,8 +5,13 @@
package flags
import (
"bytes"
"fmt"
"os"
"path"
"sort"
"strings"
"unicode/utf8"
)
// A Parser provides command line option parsing. It can contain several
@ -32,6 +37,21 @@ type Parser struct {
// or an error to indicate a parse failure.
UnknownOptionHandler func(option string, arg SplitArgument, args []string) ([]string, error)
// CompletionHandler is a function gets called to handle the completion of
// items. By default, the items are printed and the application is exited.
// You can override this default behavior by specifying a custom CompletionHandler.
CompletionHandler func(items []Completion)
// CommandHandler is a function that gets called to handle execution of a
// command. By default, the command will simply be executed. This can be
// overridden to perform certain actions (such as applying global flags)
// just before the command is executed. Note that if you override the
// handler it is your responsibility to call the command.Execute function.
//
// The command passed into CommandHandler may be nil in case there is no
// command to be executed when parsing has finished.
CommandHandler func(command Commander, args []string) error
internalError error
}
@ -93,6 +113,17 @@ const (
Default = HelpFlag | PrintErrors | PassDoubleDash
)
type parseState struct {
arg string
args []string
retargs []string
positional []*Arg
err error
command *Command
lookup lookup
}
// Parse is a convenience function to parse command line options with default
// settings. The provided data is a pointer to a struct representing the
// default option group (named "Application Options"). For more control, use
@ -162,14 +193,19 @@ func (p *Parser) Parse() ([]string, error) {
//
// When the common help group has been added (AddHelp) and either -h or --help
// was specified in the command line arguments, a help message will be
// automatically printed. Furthermore, the special error type ErrHelp is returned.
// automatically printed if the PrintErrors option is enabled.
// Furthermore, the special error type ErrHelp is returned.
// It is up to the caller to exit the program if so desired.
func (p *Parser) ParseArgs(args []string) ([]string, error) {
if p.internalError != nil {
return nil, p.internalError
}
p.clearIsSet()
p.eachOption(func(c *Command, g *Group, option *Option) {
option.isSet = false
option.isSetDefault = false
option.updateDefaultLiteral()
})
// Add built-in help group to all commands if necessary
if (p.Options & HelpFlag) != None {
@ -180,13 +216,15 @@ func (p *Parser) ParseArgs(args []string) ([]string, error) {
if len(compval) != 0 {
comp := &completion{parser: p}
items := comp.complete(args)
if compval == "verbose" {
comp.ShowDescriptions = true
if p.CompletionHandler != nil {
p.CompletionHandler(items)
} else {
comp.print(items, compval == "verbose")
os.Exit(0)
}
comp.execute(args)
return nil, nil
}
@ -253,17 +291,13 @@ func (p *Parser) ParseArgs(args []string) ([]string, error) {
}
if s.err == nil {
p.eachCommand(func(c *Command) {
c.eachGroup(func(g *Group) {
for _, option := range g.options {
if option.isSet {
continue
}
p.eachOption(func(c *Command, g *Group, option *Option) {
if option.preventDefault {
return
}
option.clearDefault()
}
})
}, true)
option.clearDefault()
})
s.checkRequired(p)
}
@ -275,12 +309,385 @@ func (p *Parser) ParseArgs(args []string) ([]string, error) {
} else if len(s.command.commands) != 0 && !s.command.SubcommandsOptional {
reterr = s.estimateCommand()
} else if cmd, ok := s.command.data.(Commander); ok {
reterr = cmd.Execute(s.retargs)
if p.CommandHandler != nil {
reterr = p.CommandHandler(cmd, s.retargs)
} else {
reterr = cmd.Execute(s.retargs)
}
} else if p.CommandHandler != nil {
reterr = p.CommandHandler(nil, s.retargs)
}
if reterr != nil {
return append([]string{s.arg}, s.args...), p.printError(reterr)
var retargs []string
if ourErr, ok := reterr.(*Error); !ok || ourErr.Type != ErrHelp {
retargs = append([]string{s.arg}, s.args...)
} else {
retargs = s.args
}
return retargs, p.printError(reterr)
}
return s.retargs, nil
}
func (p *parseState) eof() bool {
return len(p.args) == 0
}
func (p *parseState) pop() string {
if p.eof() {
return ""
}
p.arg = p.args[0]
p.args = p.args[1:]
return p.arg
}
func (p *parseState) peek() string {
if p.eof() {
return ""
}
return p.args[0]
}
func (p *parseState) checkRequired(parser *Parser) error {
c := parser.Command
var required []*Option
for c != nil {
c.eachGroup(func(g *Group) {
for _, option := range g.options {
if !option.isSet && option.Required {
required = append(required, option)
}
}
})
c = c.Active
}
if len(required) == 0 {
if len(p.positional) > 0 {
var reqnames []string
for _, arg := range p.positional {
argRequired := (!arg.isRemaining() && p.command.ArgsRequired) || arg.Required != -1 || arg.RequiredMaximum != -1
if !argRequired {
continue
}
if arg.isRemaining() {
if arg.value.Len() < arg.Required {
var arguments string
if arg.Required > 1 {
arguments = "arguments, but got only " + fmt.Sprintf("%d", arg.value.Len())
} else {
arguments = "argument"
}
reqnames = append(reqnames, "`"+arg.Name+" (at least "+fmt.Sprintf("%d", arg.Required)+" "+arguments+")`")
} else if arg.RequiredMaximum != -1 && arg.value.Len() > arg.RequiredMaximum {
if arg.RequiredMaximum == 0 {
reqnames = append(reqnames, "`"+arg.Name+" (zero arguments)`")
} else {
var arguments string
if arg.RequiredMaximum > 1 {
arguments = "arguments, but got " + fmt.Sprintf("%d", arg.value.Len())
} else {
arguments = "argument"
}
reqnames = append(reqnames, "`"+arg.Name+" (at most "+fmt.Sprintf("%d", arg.RequiredMaximum)+" "+arguments+")`")
}
}
} else {
reqnames = append(reqnames, "`"+arg.Name+"`")
}
}
if len(reqnames) == 0 {
return nil
}
var msg string
if len(reqnames) == 1 {
msg = fmt.Sprintf("the required argument %s was not provided", reqnames[0])
} else {
msg = fmt.Sprintf("the required arguments %s and %s were not provided",
strings.Join(reqnames[:len(reqnames)-1], ", "), reqnames[len(reqnames)-1])
}
p.err = newError(ErrRequired, msg)
return p.err
}
return nil
}
names := make([]string, 0, len(required))
for _, k := range required {
names = append(names, "`"+k.String()+"'")
}
sort.Strings(names)
var msg string
if len(names) == 1 {
msg = fmt.Sprintf("the required flag %s was not specified", names[0])
} else {
msg = fmt.Sprintf("the required flags %s and %s were not specified",
strings.Join(names[:len(names)-1], ", "), names[len(names)-1])
}
p.err = newError(ErrRequired, msg)
return p.err
}
func (p *parseState) estimateCommand() error {
commands := p.command.sortedVisibleCommands()
cmdnames := make([]string, len(commands))
for i, v := range commands {
cmdnames[i] = v.Name
}
var msg string
var errtype ErrorType
if len(p.retargs) != 0 {
c, l := closestChoice(p.retargs[0], cmdnames)
msg = fmt.Sprintf("Unknown command `%s'", p.retargs[0])
errtype = ErrUnknownCommand
if float32(l)/float32(len(c)) < 0.5 {
msg = fmt.Sprintf("%s, did you mean `%s'?", msg, c)
} else if len(cmdnames) == 1 {
msg = fmt.Sprintf("%s. You should use the %s command",
msg,
cmdnames[0])
} else {
msg = fmt.Sprintf("%s. Please specify one command of: %s or %s",
msg,
strings.Join(cmdnames[:len(cmdnames)-1], ", "),
cmdnames[len(cmdnames)-1])
}
} else {
errtype = ErrCommandRequired
if len(cmdnames) == 1 {
msg = fmt.Sprintf("Please specify the %s command", cmdnames[0])
} else {
msg = fmt.Sprintf("Please specify one command of: %s or %s",
strings.Join(cmdnames[:len(cmdnames)-1], ", "),
cmdnames[len(cmdnames)-1])
}
}
return newError(errtype, msg)
}
func (p *Parser) parseOption(s *parseState, name string, option *Option, canarg bool, argument *string) (err error) {
if !option.canArgument() {
if argument != nil {
return newErrorf(ErrNoArgumentForBool, "bool flag `%s' cannot have an argument", option)
}
err = option.set(nil)
} else if argument != nil || (canarg && !s.eof()) {
var arg string
if argument != nil {
arg = *argument
} else {
arg = s.pop()
if argumentIsOption(arg) && !(option.isSignedNumber() && len(arg) > 1 && arg[0] == '-' && arg[1] >= '0' && arg[1] <= '9') {
return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got option `%s'", option, arg)
} else if p.Options&PassDoubleDash != 0 && arg == "--" {
return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got double dash `--'", option)
}
}
if option.tag.Get("unquote") != "false" {
arg, err = unquoteIfPossible(arg)
}
if err == nil {
err = option.set(&arg)
}
} else if option.OptionalArgument {
option.empty()
for _, v := range option.OptionalValue {
err = option.set(&v)
if err != nil {
break
}
}
} else {
err = newErrorf(ErrExpectedArgument, "expected argument for flag `%s'", option)
}
if err != nil {
if _, ok := err.(*Error); !ok {
err = newErrorf(ErrMarshal, "invalid argument for flag `%s' (expected %s): %s",
option,
option.value.Type(),
err.Error())
}
}
return err
}
func (p *Parser) parseLong(s *parseState, name string, argument *string) error {
if option := s.lookup.longNames[name]; option != nil {
// Only long options that are required can consume an argument
// from the argument list
canarg := !option.OptionalArgument
return p.parseOption(s, name, option, canarg, argument)
}
return newErrorf(ErrUnknownFlag, "unknown flag `%s'", name)
}
func (p *Parser) splitShortConcatArg(s *parseState, optname string) (string, *string) {
c, n := utf8.DecodeRuneInString(optname)
if n == len(optname) {
return optname, nil
}
first := string(c)
if option := s.lookup.shortNames[first]; option != nil && option.canArgument() {
arg := optname[n:]
return first, &arg
}
return optname, nil
}
func (p *Parser) parseShort(s *parseState, optname string, argument *string) error {
if argument == nil {
optname, argument = p.splitShortConcatArg(s, optname)
}
for i, c := range optname {
shortname := string(c)
if option := s.lookup.shortNames[shortname]; option != nil {
// Only the last short argument can consume an argument from
// the arguments list, and only if it's non optional
canarg := (i+utf8.RuneLen(c) == len(optname)) && !option.OptionalArgument
if err := p.parseOption(s, shortname, option, canarg, argument); err != nil {
return err
}
} else {
return newErrorf(ErrUnknownFlag, "unknown flag `%s'", shortname)
}
// Only the first option can have a concatted argument, so just
// clear argument here
argument = nil
}
return nil
}
func (p *parseState) addArgs(args ...string) error {
for len(p.positional) > 0 && len(args) > 0 {
arg := p.positional[0]
if err := convert(args[0], arg.value, arg.tag); err != nil {
p.err = err
return err
}
if !arg.isRemaining() {
p.positional = p.positional[1:]
}
args = args[1:]
}
p.retargs = append(p.retargs, args...)
return nil
}
func (p *Parser) parseNonOption(s *parseState) error {
if len(s.positional) > 0 {
return s.addArgs(s.arg)
}
if len(s.command.commands) > 0 && len(s.retargs) == 0 {
if cmd := s.lookup.commands[s.arg]; cmd != nil {
s.command.Active = cmd
cmd.fillParseState(s)
return nil
} else if !s.command.SubcommandsOptional {
s.addArgs(s.arg)
return newErrorf(ErrUnknownCommand, "Unknown command `%s'", s.arg)
}
}
if (p.Options & PassAfterNonOption) != None {
// If PassAfterNonOption is set then all remaining arguments
// are considered positional
if err := s.addArgs(s.arg); err != nil {
return err
}
if err := s.addArgs(s.args...); err != nil {
return err
}
s.args = []string{}
} else {
return s.addArgs(s.arg)
}
return nil
}
func (p *Parser) showBuiltinHelp() error {
var b bytes.Buffer
p.WriteHelp(&b)
return newError(ErrHelp, b.String())
}
func (p *Parser) printError(err error) error {
if err != nil && (p.Options&PrintErrors) != None {
fmt.Fprintln(os.Stderr, err)
}
return err
}
func (p *Parser) clearIsSet() {
p.eachCommand(func(c *Command) {
c.eachGroup(func(g *Group) {
for _, option := range g.options {
option.isSet = false
}
})
}, true)
}

View file

@ -1,340 +0,0 @@
package flags
import (
"bytes"
"fmt"
"os"
"sort"
"strings"
"unicode/utf8"
)
type parseState struct {
arg string
args []string
retargs []string
positional []*Arg
err error
command *Command
lookup lookup
}
func (p *parseState) eof() bool {
return len(p.args) == 0
}
func (p *parseState) pop() string {
if p.eof() {
return ""
}
p.arg = p.args[0]
p.args = p.args[1:]
return p.arg
}
func (p *parseState) peek() string {
if p.eof() {
return ""
}
return p.args[0]
}
func (p *parseState) checkRequired(parser *Parser) error {
c := parser.Command
var required []*Option
for c != nil {
c.eachGroup(func(g *Group) {
for _, option := range g.options {
if !option.isSet && option.Required {
required = append(required, option)
}
}
})
c = c.Active
}
if len(required) == 0 {
if len(p.positional) > 0 && p.command.ArgsRequired {
var reqnames []string
for _, arg := range p.positional {
if arg.isRemaining() {
break
}
reqnames = append(reqnames, "`"+arg.Name+"`")
}
if len(reqnames) == 0 {
return nil
}
var msg string
if len(reqnames) == 1 {
msg = fmt.Sprintf("the required argument %s was not provided", reqnames[0])
} else {
msg = fmt.Sprintf("the required arguments %s and %s were not provided",
strings.Join(reqnames[:len(reqnames)-1], ", "), reqnames[len(reqnames)-1])
}
p.err = newError(ErrRequired, msg)
return p.err
}
return nil
}
names := make([]string, 0, len(required))
for _, k := range required {
names = append(names, "`"+k.String()+"'")
}
sort.Strings(names)
var msg string
if len(names) == 1 {
msg = fmt.Sprintf("the required flag %s was not specified", names[0])
} else {
msg = fmt.Sprintf("the required flags %s and %s were not specified",
strings.Join(names[:len(names)-1], ", "), names[len(names)-1])
}
p.err = newError(ErrRequired, msg)
return p.err
}
func (p *parseState) estimateCommand() error {
commands := p.command.sortedCommands()
cmdnames := make([]string, len(commands))
for i, v := range commands {
cmdnames[i] = v.Name
}
var msg string
var errtype ErrorType
if len(p.retargs) != 0 {
c, l := closestChoice(p.retargs[0], cmdnames)
msg = fmt.Sprintf("Unknown command `%s'", p.retargs[0])
errtype = ErrUnknownCommand
if float32(l)/float32(len(c)) < 0.5 {
msg = fmt.Sprintf("%s, did you mean `%s'?", msg, c)
} else if len(cmdnames) == 1 {
msg = fmt.Sprintf("%s. You should use the %s command",
msg,
cmdnames[0])
} else {
msg = fmt.Sprintf("%s. Please specify one command of: %s or %s",
msg,
strings.Join(cmdnames[:len(cmdnames)-1], ", "),
cmdnames[len(cmdnames)-1])
}
} else {
errtype = ErrCommandRequired
if len(cmdnames) == 1 {
msg = fmt.Sprintf("Please specify the %s command", cmdnames[0])
} else {
msg = fmt.Sprintf("Please specify one command of: %s or %s",
strings.Join(cmdnames[:len(cmdnames)-1], ", "),
cmdnames[len(cmdnames)-1])
}
}
return newError(errtype, msg)
}
func (p *Parser) parseOption(s *parseState, name string, option *Option, canarg bool, argument *string) (err error) {
if !option.canArgument() {
if argument != nil {
return newErrorf(ErrNoArgumentForBool, "bool flag `%s' cannot have an argument", option)
}
err = option.set(nil)
} else if argument != nil || (canarg && !s.eof()) {
var arg string
if argument != nil {
arg = *argument
} else {
arg = s.pop()
if argumentIsOption(arg) {
return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got option `%s'", option, arg)
} else if p.Options&PassDoubleDash != 0 && arg == "--" {
return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got double dash `--'", option)
}
}
if option.tag.Get("unquote") != "false" {
arg, err = unquoteIfPossible(arg)
}
if err == nil {
err = option.set(&arg)
}
} else if option.OptionalArgument {
option.empty()
for _, v := range option.OptionalValue {
err = option.set(&v)
if err != nil {
break
}
}
} else {
err = newErrorf(ErrExpectedArgument, "expected argument for flag `%s'", option)
}
if err != nil {
if _, ok := err.(*Error); !ok {
err = newErrorf(ErrMarshal, "invalid argument for flag `%s' (expected %s): %s",
option,
option.value.Type(),
err.Error())
}
}
return err
}
func (p *Parser) parseLong(s *parseState, name string, argument *string) error {
if option := s.lookup.longNames[name]; option != nil {
// Only long options that are required can consume an argument
// from the argument list
canarg := !option.OptionalArgument
return p.parseOption(s, name, option, canarg, argument)
}
return newErrorf(ErrUnknownFlag, "unknown flag `%s'", name)
}
func (p *Parser) splitShortConcatArg(s *parseState, optname string) (string, *string) {
c, n := utf8.DecodeRuneInString(optname)
if n == len(optname) {
return optname, nil
}
first := string(c)
if option := s.lookup.shortNames[first]; option != nil && option.canArgument() {
arg := optname[n:]
return first, &arg
}
return optname, nil
}
func (p *Parser) parseShort(s *parseState, optname string, argument *string) error {
if argument == nil {
optname, argument = p.splitShortConcatArg(s, optname)
}
for i, c := range optname {
shortname := string(c)
if option := s.lookup.shortNames[shortname]; option != nil {
// Only the last short argument can consume an argument from
// the arguments list, and only if it's non optional
canarg := (i+utf8.RuneLen(c) == len(optname)) && !option.OptionalArgument
if err := p.parseOption(s, shortname, option, canarg, argument); err != nil {
return err
}
} else {
return newErrorf(ErrUnknownFlag, "unknown flag `%s'", shortname)
}
// Only the first option can have a concatted argument, so just
// clear argument here
argument = nil
}
return nil
}
func (p *parseState) addArgs(args ...string) error {
for len(p.positional) > 0 && len(args) > 0 {
arg := p.positional[0]
if err := convert(args[0], arg.value, arg.tag); err != nil {
return err
}
if !arg.isRemaining() {
p.positional = p.positional[1:]
}
args = args[1:]
}
p.retargs = append(p.retargs, args...)
return nil
}
func (p *Parser) parseNonOption(s *parseState) error {
if len(s.positional) > 0 {
return s.addArgs(s.arg)
}
if cmd := s.lookup.commands[s.arg]; cmd != nil {
s.command.Active = cmd
cmd.fillParseState(s)
} else if (p.Options & PassAfterNonOption) != None {
// If PassAfterNonOption is set then all remaining arguments
// are considered positional
if err := s.addArgs(s.arg); err != nil {
return err
}
if err := s.addArgs(s.args...); err != nil {
return err
}
s.args = []string{}
} else {
return s.addArgs(s.arg)
}
return nil
}
func (p *Parser) showBuiltinHelp() error {
var b bytes.Buffer
p.WriteHelp(&b)
return newError(ErrHelp, b.String())
}
func (p *Parser) printError(err error) error {
if err != nil && (p.Options&PrintErrors) != None {
fmt.Fprintln(os.Stderr, err)
}
return err
}
func (p *Parser) clearIsSet() {
p.eachCommand(func(c *Command) {
c.eachGroup(func(g *Group) {
for _, option := range g.options {
option.isSet = false
}
})
}, true)
}

View file

@ -4,6 +4,7 @@ import (
"fmt"
"os"
"reflect"
"runtime"
"strconv"
"strings"
"testing"
@ -14,6 +15,11 @@ type defaultOptions struct {
Int int `long:"i"`
IntDefault int `long:"id" default:"1"`
Float64 float64 `long:"f"`
Float64Default float64 `long:"fd" default:"-3.14"`
NumericFlag bool `short:"3"`
String string `long:"str"`
StringDefault string `long:"strd" default:"abc"`
StringNotUnquoted string `long:"strnot" unquote:"false"`
@ -41,6 +47,11 @@ func TestDefaults(t *testing.T) {
Int: 0,
IntDefault: 1,
Float64: 0.0,
Float64Default: -3.14,
NumericFlag: false,
String: "",
StringDefault: "abc",
@ -56,11 +67,16 @@ func TestDefaults(t *testing.T) {
},
{
msg: "non-zero value arguments, expecting overwritten arguments",
args: []string{"--i=3", "--id=3", "--str=def", "--strd=def", "--t=3ms", "--td=3ms", "--m=c:3", "--md=c:3", "--s=3", "--sd=3"},
args: []string{"--i=3", "--id=3", "--f=-2.71", "--fd=2.71", "-3", "--str=def", "--strd=def", "--t=3ms", "--td=3ms", "--m=c:3", "--md=c:3", "--s=3", "--sd=3"},
expected: defaultOptions{
Int: 3,
IntDefault: 3,
Float64: -2.71,
Float64Default: 2.71,
NumericFlag: true,
String: "def",
StringDefault: "def",
@ -76,11 +92,14 @@ func TestDefaults(t *testing.T) {
},
{
msg: "zero value arguments, expecting overwritten arguments",
args: []string{"--i=0", "--id=0", "--str", "", "--strd=\"\"", "--t=0ms", "--td=0s", "--m=:0", "--md=:0", "--s=0", "--sd=0"},
args: []string{"--i=0", "--id=0", "--f=0", "--fd=0", "--str", "", "--strd=\"\"", "--t=0ms", "--td=0s", "--m=:0", "--md=:0", "--s=0", "--sd=0"},
expected: defaultOptions{
Int: 0,
IntDefault: 0,
Float64: 0,
Float64Default: 0,
String: "",
StringDefault: "",
@ -114,6 +133,18 @@ func TestDefaults(t *testing.T) {
}
}
func TestNoDefaultsForBools(t *testing.T) {
var opts struct {
DefaultBool bool `short:"d" default:"true"`
}
if runtime.GOOS == "windows" {
assertParseFail(t, ErrInvalidTag, "boolean flag `/d' may not have default values, they always default to `false' and can only be turned on", &opts)
} else {
assertParseFail(t, ErrInvalidTag, "boolean flag `-d' may not have default values, they always default to `false' and can only be turned on", &opts)
}
}
func TestUnquoting(t *testing.T) {
var tests = []struct {
arg string
@ -184,28 +215,33 @@ func TestUnquoting(t *testing.T) {
}
}
// envRestorer keeps a copy of a set of env variables and can restore the env from them
type envRestorer struct {
// EnvRestorer keeps a copy of a set of env variables and can restore the env from them
type EnvRestorer struct {
env map[string]string
}
func (r *envRestorer) Restore() {
func (r *EnvRestorer) Restore() {
os.Clearenv()
for k, v := range r.env {
os.Setenv(k, v)
}
}
// EnvSnapshot returns a snapshot of the currently set env variables
func EnvSnapshot() *envRestorer {
r := envRestorer{make(map[string]string)}
func EnvSnapshot() *EnvRestorer {
r := EnvRestorer{make(map[string]string)}
for _, kv := range os.Environ() {
parts := strings.SplitN(kv, "=", 2)
if len(parts) != 2 {
panic("got a weird env variable: " + kv)
}
r.env[parts[0]] = parts[1]
}
return &r
}
@ -320,21 +356,21 @@ func TestOptionAsArgument(t *testing.T) {
args: []string{"--string-slice", "foobar", "--string-slice", "-o"},
expectError: true,
errType: ErrExpectedArgument,
errMsg: "expected argument for flag `--string-slice', but got option `-o'",
errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `-o'",
},
{
// long option must not be accepted as argument
args: []string{"--string-slice", "foobar", "--string-slice", "--other-option"},
expectError: true,
errType: ErrExpectedArgument,
errMsg: "expected argument for flag `--string-slice', but got option `--other-option'",
errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `--other-option'",
},
{
// long option must not be accepted as argument
args: []string{"--string-slice", "--"},
expectError: true,
errType: ErrExpectedArgument,
errMsg: "expected argument for flag `--string-slice', but got double dash `--'",
errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got double dash `--'",
},
{
// quoted and appended option should be accepted as argument (even if it looks like an option)
@ -344,13 +380,48 @@ func TestOptionAsArgument(t *testing.T) {
// Accept any single character arguments including '-'
args: []string{"--string-slice", "-"},
},
{
// Do not accept arguments which start with '-' even if the next character is a digit
args: []string{"--string-slice", "-3.14"},
expectError: true,
errType: ErrExpectedArgument,
errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `-3.14'",
},
{
// Do not accept arguments which start with '-' if the next character is not a digit
args: []string{"--string-slice", "-character"},
expectError: true,
errType: ErrExpectedArgument,
errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `-character'",
},
{
args: []string{"-o", "-", "-"},
rest: []string{"-", "-"},
},
{
// Accept arguments which start with '-' if the next character is a digit, for number options only
args: []string{"--int-slice", "-3"},
},
{
// Accept arguments which start with '-' if the next character is a digit, for number options only
args: []string{"--int16", "-3"},
},
{
// Accept arguments which start with '-' if the next character is a digit, for number options only
args: []string{"--float32", "-3.2"},
},
{
// Accept arguments which start with '-' if the next character is a digit, for number options only
args: []string{"--float32ptr", "-3.2"},
},
}
var opts struct {
StringSlice []string `long:"string-slice"`
IntSlice []int `long:"int-slice"`
Int16 int16 `long:"int16"`
Float32 float32 `long:"float32"`
Float32Ptr *float32 `long:"float32ptr"`
OtherOption bool `long:"other-option" short:"o"`
}
@ -429,3 +500,113 @@ func TestUnknownFlagHandler(t *testing.T) {
assertErrorf(t, "Parser should have returned error, but returned nil")
}
}
func TestChoices(t *testing.T) {
var opts struct {
Choice string `long:"choose" choice:"v1" choice:"v2"`
}
assertParseFail(t, ErrInvalidChoice, "Invalid value `invalid' for option `"+defaultLongOptDelimiter+"choose'. Allowed values are: v1 or v2", &opts, "--choose", "invalid")
assertParseSuccess(t, &opts, "--choose", "v2")
assertString(t, opts.Choice, "v2")
}
func TestEmbedded(t *testing.T) {
type embedded struct {
V bool `short:"v"`
}
var opts struct {
embedded
}
assertParseSuccess(t, &opts, "-v")
if !opts.V {
t.Errorf("Expected V to be true")
}
}
type command struct {
}
func (c *command) Execute(args []string) error {
return nil
}
func TestCommandHandlerNoCommand(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
}{}
parser := NewParser(&opts, Default&^PrintErrors)
var executedCommand Commander
var executedArgs []string
executed := false
parser.CommandHandler = func(command Commander, args []string) error {
executed = true
executedCommand = command
executedArgs = args
return nil
}
_, err := parser.ParseArgs([]string{"arg1", "arg2"})
if err != nil {
t.Fatalf("Unexpected parse error: %s", err)
}
if !executed {
t.Errorf("Expected command handler to be executed")
}
if executedCommand != nil {
t.Errorf("Did not exect an executed command")
}
assertStringArray(t, executedArgs, []string{"arg1", "arg2"})
}
func TestCommandHandler(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Command command `command:"cmd"`
}{}
parser := NewParser(&opts, Default&^PrintErrors)
var executedCommand Commander
var executedArgs []string
executed := false
parser.CommandHandler = func(command Commander, args []string) error {
executed = true
executedCommand = command
executedArgs = args
return nil
}
_, err := parser.ParseArgs([]string{"cmd", "arg1", "arg2"})
if err != nil {
t.Fatalf("Unexpected parse error: %s", err)
}
if !executed {
t.Errorf("Expected command handler to be executed")
}
if executedCommand == nil {
t.Errorf("Expected command handler to be executed")
}
assertStringArray(t, executedArgs, []string{"arg1", "arg2"})
}

View file

@ -490,18 +490,13 @@ func (c *Client) Join(elem ...string) string { return path.Join(elem...) }
// is not empty.
func (c *Client) Remove(path string) error {
err := c.removeFile(path)
switch err := err.(type) {
case *StatusError:
if err, ok := err.(*StatusError); ok {
switch err.Code {
// some servers, *cough* osx *cough*, return EPERM, not ENODIR.
// serv-u returns ssh_FX_FILE_IS_A_DIRECTORY
case ssh_FX_PERMISSION_DENIED, ssh_FX_FAILURE, ssh_FX_FILE_IS_A_DIRECTORY:
return c.removeDirectory(path)
default:
return err
}
default:
return err
}
return err
}

View file

@ -0,0 +1,20 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This code was translated into a form compatible with 6a from the public
// domain sources in SUPERCOP: http://bench.cr.yp.to/supercop.html
// +build amd64,!gccgo,!appengine
DATA ·REDMASK51(SB)/8, $0x0007FFFFFFFFFFFF
GLOBL ·REDMASK51(SB), 8, $8
DATA ·_121666_213(SB)/8, $996687872
GLOBL ·_121666_213(SB), 8, $8
DATA ·_2P0(SB)/8, $0xFFFFFFFFFFFDA
GLOBL ·_2P0(SB), 8, $8
DATA ·_2P1234(SB)/8, $0xFFFFFFFFFFFFE
GLOBL ·_2P1234(SB), 8, $8

View file

@ -0,0 +1,88 @@
// 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.
// This code was translated into a form compatible with 6a from the public
// domain sources in SUPERCOP: http://bench.cr.yp.to/supercop.html
// +build amd64,!gccgo,!appengine
// func cswap(inout *[5]uint64, v uint64)
TEXT ·cswap(SB),7,$0
MOVQ inout+0(FP),DI
MOVQ v+8(FP),SI
CMPQ SI,$1
MOVQ 0(DI),SI
MOVQ 80(DI),DX
MOVQ 8(DI),CX
MOVQ 88(DI),R8
MOVQ SI,R9
CMOVQEQ DX,SI
CMOVQEQ R9,DX
MOVQ CX,R9
CMOVQEQ R8,CX
CMOVQEQ R9,R8
MOVQ SI,0(DI)
MOVQ DX,80(DI)
MOVQ CX,8(DI)
MOVQ R8,88(DI)
MOVQ 16(DI),SI
MOVQ 96(DI),DX
MOVQ 24(DI),CX
MOVQ 104(DI),R8
MOVQ SI,R9
CMOVQEQ DX,SI
CMOVQEQ R9,DX
MOVQ CX,R9
CMOVQEQ R8,CX
CMOVQEQ R9,R8
MOVQ SI,16(DI)
MOVQ DX,96(DI)
MOVQ CX,24(DI)
MOVQ R8,104(DI)
MOVQ 32(DI),SI
MOVQ 112(DI),DX
MOVQ 40(DI),CX
MOVQ 120(DI),R8
MOVQ SI,R9
CMOVQEQ DX,SI
CMOVQEQ R9,DX
MOVQ CX,R9
CMOVQEQ R8,CX
CMOVQEQ R9,R8
MOVQ SI,32(DI)
MOVQ DX,112(DI)
MOVQ CX,40(DI)
MOVQ R8,120(DI)
MOVQ 48(DI),SI
MOVQ 128(DI),DX
MOVQ 56(DI),CX
MOVQ 136(DI),R8
MOVQ SI,R9
CMOVQEQ DX,SI
CMOVQEQ R9,DX
MOVQ CX,R9
CMOVQEQ R8,CX
CMOVQEQ R9,R8
MOVQ SI,48(DI)
MOVQ DX,128(DI)
MOVQ CX,56(DI)
MOVQ R8,136(DI)
MOVQ 64(DI),SI
MOVQ 144(DI),DX
MOVQ 72(DI),CX
MOVQ 152(DI),R8
MOVQ SI,R9
CMOVQEQ DX,SI
CMOVQEQ R9,DX
MOVQ CX,R9
CMOVQEQ R8,CX
CMOVQEQ R9,R8
MOVQ SI,64(DI)
MOVQ DX,144(DI)
MOVQ CX,72(DI)
MOVQ R8,152(DI)
MOVQ DI,AX
MOVQ SI,DX
RET

View file

@ -0,0 +1,841 @@
// 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.
// We have a implementation in amd64 assembly so this code is only run on
// non-amd64 platforms. The amd64 assembly does not support gccgo.
// +build !amd64 gccgo appengine
package curve25519
// This code is a port of the public domain, "ref10" implementation of
// curve25519 from SUPERCOP 20130419 by D. J. Bernstein.
// fieldElement represents an element of the field GF(2^255 - 19). An element
// t, entries t[0]...t[9], represents the integer t[0]+2^26 t[1]+2^51 t[2]+2^77
// t[3]+2^102 t[4]+...+2^230 t[9]. Bounds on each t[i] vary depending on
// context.
type fieldElement [10]int32
func feZero(fe *fieldElement) {
for i := range fe {
fe[i] = 0
}
}
func feOne(fe *fieldElement) {
feZero(fe)
fe[0] = 1
}
func feAdd(dst, a, b *fieldElement) {
for i := range dst {
dst[i] = a[i] + b[i]
}
}
func feSub(dst, a, b *fieldElement) {
for i := range dst {
dst[i] = a[i] - b[i]
}
}
func feCopy(dst, src *fieldElement) {
for i := range dst {
dst[i] = src[i]
}
}
// feCSwap replaces (f,g) with (g,f) if b == 1; replaces (f,g) with (f,g) if b == 0.
//
// Preconditions: b in {0,1}.
func feCSwap(f, g *fieldElement, b int32) {
var x fieldElement
b = -b
for i := range x {
x[i] = b & (f[i] ^ g[i])
}
for i := range f {
f[i] ^= x[i]
}
for i := range g {
g[i] ^= x[i]
}
}
// load3 reads a 24-bit, little-endian value from in.
func load3(in []byte) int64 {
var r int64
r = int64(in[0])
r |= int64(in[1]) << 8
r |= int64(in[2]) << 16
return r
}
// load4 reads a 32-bit, little-endian value from in.
func load4(in []byte) int64 {
var r int64
r = int64(in[0])
r |= int64(in[1]) << 8
r |= int64(in[2]) << 16
r |= int64(in[3]) << 24
return r
}
func feFromBytes(dst *fieldElement, src *[32]byte) {
h0 := load4(src[:])
h1 := load3(src[4:]) << 6
h2 := load3(src[7:]) << 5
h3 := load3(src[10:]) << 3
h4 := load3(src[13:]) << 2
h5 := load4(src[16:])
h6 := load3(src[20:]) << 7
h7 := load3(src[23:]) << 5
h8 := load3(src[26:]) << 4
h9 := load3(src[29:]) << 2
var carry [10]int64
carry[9] = (h9 + 1<<24) >> 25
h0 += carry[9] * 19
h9 -= carry[9] << 25
carry[1] = (h1 + 1<<24) >> 25
h2 += carry[1]
h1 -= carry[1] << 25
carry[3] = (h3 + 1<<24) >> 25
h4 += carry[3]
h3 -= carry[3] << 25
carry[5] = (h5 + 1<<24) >> 25
h6 += carry[5]
h5 -= carry[5] << 25
carry[7] = (h7 + 1<<24) >> 25
h8 += carry[7]
h7 -= carry[7] << 25
carry[0] = (h0 + 1<<25) >> 26
h1 += carry[0]
h0 -= carry[0] << 26
carry[2] = (h2 + 1<<25) >> 26
h3 += carry[2]
h2 -= carry[2] << 26
carry[4] = (h4 + 1<<25) >> 26
h5 += carry[4]
h4 -= carry[4] << 26
carry[6] = (h6 + 1<<25) >> 26
h7 += carry[6]
h6 -= carry[6] << 26
carry[8] = (h8 + 1<<25) >> 26
h9 += carry[8]
h8 -= carry[8] << 26
dst[0] = int32(h0)
dst[1] = int32(h1)
dst[2] = int32(h2)
dst[3] = int32(h3)
dst[4] = int32(h4)
dst[5] = int32(h5)
dst[6] = int32(h6)
dst[7] = int32(h7)
dst[8] = int32(h8)
dst[9] = int32(h9)
}
// feToBytes marshals h to s.
// Preconditions:
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
//
// Write p=2^255-19; q=floor(h/p).
// Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))).
//
// Proof:
// Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4.
// Also have |h-2^230 h9|<2^230 so |19 2^(-255)(h-2^230 h9)|<1/4.
//
// Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9).
// Then 0<y<1.
//
// Write r=h-pq.
// Have 0<=r<=p-1=2^255-20.
// Thus 0<=r+19(2^-255)r<r+19(2^-255)2^255<=2^255-1.
//
// Write x=r+19(2^-255)r+y.
// Then 0<x<2^255 so floor(2^(-255)x) = 0 so floor(q+2^(-255)x) = q.
//
// Have q+2^(-255)x = 2^(-255)(h + 19 2^(-25) h9 + 2^(-1))
// so floor(2^(-255)(h + 19 2^(-25) h9 + 2^(-1))) = q.
func feToBytes(s *[32]byte, h *fieldElement) {
var carry [10]int32
q := (19*h[9] + (1 << 24)) >> 25
q = (h[0] + q) >> 26
q = (h[1] + q) >> 25
q = (h[2] + q) >> 26
q = (h[3] + q) >> 25
q = (h[4] + q) >> 26
q = (h[5] + q) >> 25
q = (h[6] + q) >> 26
q = (h[7] + q) >> 25
q = (h[8] + q) >> 26
q = (h[9] + q) >> 25
// Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20.
h[0] += 19 * q
// Goal: Output h-2^255 q, which is between 0 and 2^255-20.
carry[0] = h[0] >> 26
h[1] += carry[0]
h[0] -= carry[0] << 26
carry[1] = h[1] >> 25
h[2] += carry[1]
h[1] -= carry[1] << 25
carry[2] = h[2] >> 26
h[3] += carry[2]
h[2] -= carry[2] << 26
carry[3] = h[3] >> 25
h[4] += carry[3]
h[3] -= carry[3] << 25
carry[4] = h[4] >> 26
h[5] += carry[4]
h[4] -= carry[4] << 26
carry[5] = h[5] >> 25
h[6] += carry[5]
h[5] -= carry[5] << 25
carry[6] = h[6] >> 26
h[7] += carry[6]
h[6] -= carry[6] << 26
carry[7] = h[7] >> 25
h[8] += carry[7]
h[7] -= carry[7] << 25
carry[8] = h[8] >> 26
h[9] += carry[8]
h[8] -= carry[8] << 26
carry[9] = h[9] >> 25
h[9] -= carry[9] << 25
// h10 = carry9
// Goal: Output h[0]+...+2^255 h10-2^255 q, which is between 0 and 2^255-20.
// Have h[0]+...+2^230 h[9] between 0 and 2^255-1;
// evidently 2^255 h10-2^255 q = 0.
// Goal: Output h[0]+...+2^230 h[9].
s[0] = byte(h[0] >> 0)
s[1] = byte(h[0] >> 8)
s[2] = byte(h[0] >> 16)
s[3] = byte((h[0] >> 24) | (h[1] << 2))
s[4] = byte(h[1] >> 6)
s[5] = byte(h[1] >> 14)
s[6] = byte((h[1] >> 22) | (h[2] << 3))
s[7] = byte(h[2] >> 5)
s[8] = byte(h[2] >> 13)
s[9] = byte((h[2] >> 21) | (h[3] << 5))
s[10] = byte(h[3] >> 3)
s[11] = byte(h[3] >> 11)
s[12] = byte((h[3] >> 19) | (h[4] << 6))
s[13] = byte(h[4] >> 2)
s[14] = byte(h[4] >> 10)
s[15] = byte(h[4] >> 18)
s[16] = byte(h[5] >> 0)
s[17] = byte(h[5] >> 8)
s[18] = byte(h[5] >> 16)
s[19] = byte((h[5] >> 24) | (h[6] << 1))
s[20] = byte(h[6] >> 7)
s[21] = byte(h[6] >> 15)
s[22] = byte((h[6] >> 23) | (h[7] << 3))
s[23] = byte(h[7] >> 5)
s[24] = byte(h[7] >> 13)
s[25] = byte((h[7] >> 21) | (h[8] << 4))
s[26] = byte(h[8] >> 4)
s[27] = byte(h[8] >> 12)
s[28] = byte((h[8] >> 20) | (h[9] << 6))
s[29] = byte(h[9] >> 2)
s[30] = byte(h[9] >> 10)
s[31] = byte(h[9] >> 18)
}
// feMul calculates h = f * g
// Can overlap h with f or g.
//
// Preconditions:
// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
// |g| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
//
// Postconditions:
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
//
// Notes on implementation strategy:
//
// Using schoolbook multiplication.
// Karatsuba would save a little in some cost models.
//
// Most multiplications by 2 and 19 are 32-bit precomputations;
// cheaper than 64-bit postcomputations.
//
// There is one remaining multiplication by 19 in the carry chain;
// one *19 precomputation can be merged into this,
// but the resulting data flow is considerably less clean.
//
// There are 12 carries below.
// 10 of them are 2-way parallelizable and vectorizable.
// Can get away with 11 carries, but then data flow is much deeper.
//
// With tighter constraints on inputs can squeeze carries into int32.
func feMul(h, f, g *fieldElement) {
f0 := f[0]
f1 := f[1]
f2 := f[2]
f3 := f[3]
f4 := f[4]
f5 := f[5]
f6 := f[6]
f7 := f[7]
f8 := f[8]
f9 := f[9]
g0 := g[0]
g1 := g[1]
g2 := g[2]
g3 := g[3]
g4 := g[4]
g5 := g[5]
g6 := g[6]
g7 := g[7]
g8 := g[8]
g9 := g[9]
g1_19 := 19 * g1 // 1.4*2^29
g2_19 := 19 * g2 // 1.4*2^30; still ok
g3_19 := 19 * g3
g4_19 := 19 * g4
g5_19 := 19 * g5
g6_19 := 19 * g6
g7_19 := 19 * g7
g8_19 := 19 * g8
g9_19 := 19 * g9
f1_2 := 2 * f1
f3_2 := 2 * f3
f5_2 := 2 * f5
f7_2 := 2 * f7
f9_2 := 2 * f9
f0g0 := int64(f0) * int64(g0)
f0g1 := int64(f0) * int64(g1)
f0g2 := int64(f0) * int64(g2)
f0g3 := int64(f0) * int64(g3)
f0g4 := int64(f0) * int64(g4)
f0g5 := int64(f0) * int64(g5)
f0g6 := int64(f0) * int64(g6)
f0g7 := int64(f0) * int64(g7)
f0g8 := int64(f0) * int64(g8)
f0g9 := int64(f0) * int64(g9)
f1g0 := int64(f1) * int64(g0)
f1g1_2 := int64(f1_2) * int64(g1)
f1g2 := int64(f1) * int64(g2)
f1g3_2 := int64(f1_2) * int64(g3)
f1g4 := int64(f1) * int64(g4)
f1g5_2 := int64(f1_2) * int64(g5)
f1g6 := int64(f1) * int64(g6)
f1g7_2 := int64(f1_2) * int64(g7)
f1g8 := int64(f1) * int64(g8)
f1g9_38 := int64(f1_2) * int64(g9_19)
f2g0 := int64(f2) * int64(g0)
f2g1 := int64(f2) * int64(g1)
f2g2 := int64(f2) * int64(g2)
f2g3 := int64(f2) * int64(g3)
f2g4 := int64(f2) * int64(g4)
f2g5 := int64(f2) * int64(g5)
f2g6 := int64(f2) * int64(g6)
f2g7 := int64(f2) * int64(g7)
f2g8_19 := int64(f2) * int64(g8_19)
f2g9_19 := int64(f2) * int64(g9_19)
f3g0 := int64(f3) * int64(g0)
f3g1_2 := int64(f3_2) * int64(g1)
f3g2 := int64(f3) * int64(g2)
f3g3_2 := int64(f3_2) * int64(g3)
f3g4 := int64(f3) * int64(g4)
f3g5_2 := int64(f3_2) * int64(g5)
f3g6 := int64(f3) * int64(g6)
f3g7_38 := int64(f3_2) * int64(g7_19)
f3g8_19 := int64(f3) * int64(g8_19)
f3g9_38 := int64(f3_2) * int64(g9_19)
f4g0 := int64(f4) * int64(g0)
f4g1 := int64(f4) * int64(g1)
f4g2 := int64(f4) * int64(g2)
f4g3 := int64(f4) * int64(g3)
f4g4 := int64(f4) * int64(g4)
f4g5 := int64(f4) * int64(g5)
f4g6_19 := int64(f4) * int64(g6_19)
f4g7_19 := int64(f4) * int64(g7_19)
f4g8_19 := int64(f4) * int64(g8_19)
f4g9_19 := int64(f4) * int64(g9_19)
f5g0 := int64(f5) * int64(g0)
f5g1_2 := int64(f5_2) * int64(g1)
f5g2 := int64(f5) * int64(g2)
f5g3_2 := int64(f5_2) * int64(g3)
f5g4 := int64(f5) * int64(g4)
f5g5_38 := int64(f5_2) * int64(g5_19)
f5g6_19 := int64(f5) * int64(g6_19)
f5g7_38 := int64(f5_2) * int64(g7_19)
f5g8_19 := int64(f5) * int64(g8_19)
f5g9_38 := int64(f5_2) * int64(g9_19)
f6g0 := int64(f6) * int64(g0)
f6g1 := int64(f6) * int64(g1)
f6g2 := int64(f6) * int64(g2)
f6g3 := int64(f6) * int64(g3)
f6g4_19 := int64(f6) * int64(g4_19)
f6g5_19 := int64(f6) * int64(g5_19)
f6g6_19 := int64(f6) * int64(g6_19)
f6g7_19 := int64(f6) * int64(g7_19)
f6g8_19 := int64(f6) * int64(g8_19)
f6g9_19 := int64(f6) * int64(g9_19)
f7g0 := int64(f7) * int64(g0)
f7g1_2 := int64(f7_2) * int64(g1)
f7g2 := int64(f7) * int64(g2)
f7g3_38 := int64(f7_2) * int64(g3_19)
f7g4_19 := int64(f7) * int64(g4_19)
f7g5_38 := int64(f7_2) * int64(g5_19)
f7g6_19 := int64(f7) * int64(g6_19)
f7g7_38 := int64(f7_2) * int64(g7_19)
f7g8_19 := int64(f7) * int64(g8_19)
f7g9_38 := int64(f7_2) * int64(g9_19)
f8g0 := int64(f8) * int64(g0)
f8g1 := int64(f8) * int64(g1)
f8g2_19 := int64(f8) * int64(g2_19)
f8g3_19 := int64(f8) * int64(g3_19)
f8g4_19 := int64(f8) * int64(g4_19)
f8g5_19 := int64(f8) * int64(g5_19)
f8g6_19 := int64(f8) * int64(g6_19)
f8g7_19 := int64(f8) * int64(g7_19)
f8g8_19 := int64(f8) * int64(g8_19)
f8g9_19 := int64(f8) * int64(g9_19)
f9g0 := int64(f9) * int64(g0)
f9g1_38 := int64(f9_2) * int64(g1_19)
f9g2_19 := int64(f9) * int64(g2_19)
f9g3_38 := int64(f9_2) * int64(g3_19)
f9g4_19 := int64(f9) * int64(g4_19)
f9g5_38 := int64(f9_2) * int64(g5_19)
f9g6_19 := int64(f9) * int64(g6_19)
f9g7_38 := int64(f9_2) * int64(g7_19)
f9g8_19 := int64(f9) * int64(g8_19)
f9g9_38 := int64(f9_2) * int64(g9_19)
h0 := f0g0 + f1g9_38 + f2g8_19 + f3g7_38 + f4g6_19 + f5g5_38 + f6g4_19 + f7g3_38 + f8g2_19 + f9g1_38
h1 := f0g1 + f1g0 + f2g9_19 + f3g8_19 + f4g7_19 + f5g6_19 + f6g5_19 + f7g4_19 + f8g3_19 + f9g2_19
h2 := f0g2 + f1g1_2 + f2g0 + f3g9_38 + f4g8_19 + f5g7_38 + f6g6_19 + f7g5_38 + f8g4_19 + f9g3_38
h3 := f0g3 + f1g2 + f2g1 + f3g0 + f4g9_19 + f5g8_19 + f6g7_19 + f7g6_19 + f8g5_19 + f9g4_19
h4 := f0g4 + f1g3_2 + f2g2 + f3g1_2 + f4g0 + f5g9_38 + f6g8_19 + f7g7_38 + f8g6_19 + f9g5_38
h5 := f0g5 + f1g4 + f2g3 + f3g2 + f4g1 + f5g0 + f6g9_19 + f7g8_19 + f8g7_19 + f9g6_19
h6 := f0g6 + f1g5_2 + f2g4 + f3g3_2 + f4g2 + f5g1_2 + f6g0 + f7g9_38 + f8g8_19 + f9g7_38
h7 := f0g7 + f1g6 + f2g5 + f3g4 + f4g3 + f5g2 + f6g1 + f7g0 + f8g9_19 + f9g8_19
h8 := f0g8 + f1g7_2 + f2g6 + f3g5_2 + f4g4 + f5g3_2 + f6g2 + f7g1_2 + f8g0 + f9g9_38
h9 := f0g9 + f1g8 + f2g7 + f3g6 + f4g5 + f5g4 + f6g3 + f7g2 + f8g1 + f9g0
var carry [10]int64
// |h0| <= (1.1*1.1*2^52*(1+19+19+19+19)+1.1*1.1*2^50*(38+38+38+38+38))
// i.e. |h0| <= 1.2*2^59; narrower ranges for h2, h4, h6, h8
// |h1| <= (1.1*1.1*2^51*(1+1+19+19+19+19+19+19+19+19))
// i.e. |h1| <= 1.5*2^58; narrower ranges for h3, h5, h7, h9
carry[0] = (h0 + (1 << 25)) >> 26
h1 += carry[0]
h0 -= carry[0] << 26
carry[4] = (h4 + (1 << 25)) >> 26
h5 += carry[4]
h4 -= carry[4] << 26
// |h0| <= 2^25
// |h4| <= 2^25
// |h1| <= 1.51*2^58
// |h5| <= 1.51*2^58
carry[1] = (h1 + (1 << 24)) >> 25
h2 += carry[1]
h1 -= carry[1] << 25
carry[5] = (h5 + (1 << 24)) >> 25
h6 += carry[5]
h5 -= carry[5] << 25
// |h1| <= 2^24; from now on fits into int32
// |h5| <= 2^24; from now on fits into int32
// |h2| <= 1.21*2^59
// |h6| <= 1.21*2^59
carry[2] = (h2 + (1 << 25)) >> 26
h3 += carry[2]
h2 -= carry[2] << 26
carry[6] = (h6 + (1 << 25)) >> 26
h7 += carry[6]
h6 -= carry[6] << 26
// |h2| <= 2^25; from now on fits into int32 unchanged
// |h6| <= 2^25; from now on fits into int32 unchanged
// |h3| <= 1.51*2^58
// |h7| <= 1.51*2^58
carry[3] = (h3 + (1 << 24)) >> 25
h4 += carry[3]
h3 -= carry[3] << 25
carry[7] = (h7 + (1 << 24)) >> 25
h8 += carry[7]
h7 -= carry[7] << 25
// |h3| <= 2^24; from now on fits into int32 unchanged
// |h7| <= 2^24; from now on fits into int32 unchanged
// |h4| <= 1.52*2^33
// |h8| <= 1.52*2^33
carry[4] = (h4 + (1 << 25)) >> 26
h5 += carry[4]
h4 -= carry[4] << 26
carry[8] = (h8 + (1 << 25)) >> 26
h9 += carry[8]
h8 -= carry[8] << 26
// |h4| <= 2^25; from now on fits into int32 unchanged
// |h8| <= 2^25; from now on fits into int32 unchanged
// |h5| <= 1.01*2^24
// |h9| <= 1.51*2^58
carry[9] = (h9 + (1 << 24)) >> 25
h0 += carry[9] * 19
h9 -= carry[9] << 25
// |h9| <= 2^24; from now on fits into int32 unchanged
// |h0| <= 1.8*2^37
carry[0] = (h0 + (1 << 25)) >> 26
h1 += carry[0]
h0 -= carry[0] << 26
// |h0| <= 2^25; from now on fits into int32 unchanged
// |h1| <= 1.01*2^24
h[0] = int32(h0)
h[1] = int32(h1)
h[2] = int32(h2)
h[3] = int32(h3)
h[4] = int32(h4)
h[5] = int32(h5)
h[6] = int32(h6)
h[7] = int32(h7)
h[8] = int32(h8)
h[9] = int32(h9)
}
// feSquare calculates h = f*f. Can overlap h with f.
//
// Preconditions:
// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
//
// Postconditions:
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
func feSquare(h, f *fieldElement) {
f0 := f[0]
f1 := f[1]
f2 := f[2]
f3 := f[3]
f4 := f[4]
f5 := f[5]
f6 := f[6]
f7 := f[7]
f8 := f[8]
f9 := f[9]
f0_2 := 2 * f0
f1_2 := 2 * f1
f2_2 := 2 * f2
f3_2 := 2 * f3
f4_2 := 2 * f4
f5_2 := 2 * f5
f6_2 := 2 * f6
f7_2 := 2 * f7
f5_38 := 38 * f5 // 1.31*2^30
f6_19 := 19 * f6 // 1.31*2^30
f7_38 := 38 * f7 // 1.31*2^30
f8_19 := 19 * f8 // 1.31*2^30
f9_38 := 38 * f9 // 1.31*2^30
f0f0 := int64(f0) * int64(f0)
f0f1_2 := int64(f0_2) * int64(f1)
f0f2_2 := int64(f0_2) * int64(f2)
f0f3_2 := int64(f0_2) * int64(f3)
f0f4_2 := int64(f0_2) * int64(f4)
f0f5_2 := int64(f0_2) * int64(f5)
f0f6_2 := int64(f0_2) * int64(f6)
f0f7_2 := int64(f0_2) * int64(f7)
f0f8_2 := int64(f0_2) * int64(f8)
f0f9_2 := int64(f0_2) * int64(f9)
f1f1_2 := int64(f1_2) * int64(f1)
f1f2_2 := int64(f1_2) * int64(f2)
f1f3_4 := int64(f1_2) * int64(f3_2)
f1f4_2 := int64(f1_2) * int64(f4)
f1f5_4 := int64(f1_2) * int64(f5_2)
f1f6_2 := int64(f1_2) * int64(f6)
f1f7_4 := int64(f1_2) * int64(f7_2)
f1f8_2 := int64(f1_2) * int64(f8)
f1f9_76 := int64(f1_2) * int64(f9_38)
f2f2 := int64(f2) * int64(f2)
f2f3_2 := int64(f2_2) * int64(f3)
f2f4_2 := int64(f2_2) * int64(f4)
f2f5_2 := int64(f2_2) * int64(f5)
f2f6_2 := int64(f2_2) * int64(f6)
f2f7_2 := int64(f2_2) * int64(f7)
f2f8_38 := int64(f2_2) * int64(f8_19)
f2f9_38 := int64(f2) * int64(f9_38)
f3f3_2 := int64(f3_2) * int64(f3)
f3f4_2 := int64(f3_2) * int64(f4)
f3f5_4 := int64(f3_2) * int64(f5_2)
f3f6_2 := int64(f3_2) * int64(f6)
f3f7_76 := int64(f3_2) * int64(f7_38)
f3f8_38 := int64(f3_2) * int64(f8_19)
f3f9_76 := int64(f3_2) * int64(f9_38)
f4f4 := int64(f4) * int64(f4)
f4f5_2 := int64(f4_2) * int64(f5)
f4f6_38 := int64(f4_2) * int64(f6_19)
f4f7_38 := int64(f4) * int64(f7_38)
f4f8_38 := int64(f4_2) * int64(f8_19)
f4f9_38 := int64(f4) * int64(f9_38)
f5f5_38 := int64(f5) * int64(f5_38)
f5f6_38 := int64(f5_2) * int64(f6_19)
f5f7_76 := int64(f5_2) * int64(f7_38)
f5f8_38 := int64(f5_2) * int64(f8_19)
f5f9_76 := int64(f5_2) * int64(f9_38)
f6f6_19 := int64(f6) * int64(f6_19)
f6f7_38 := int64(f6) * int64(f7_38)
f6f8_38 := int64(f6_2) * int64(f8_19)
f6f9_38 := int64(f6) * int64(f9_38)
f7f7_38 := int64(f7) * int64(f7_38)
f7f8_38 := int64(f7_2) * int64(f8_19)
f7f9_76 := int64(f7_2) * int64(f9_38)
f8f8_19 := int64(f8) * int64(f8_19)
f8f9_38 := int64(f8) * int64(f9_38)
f9f9_38 := int64(f9) * int64(f9_38)
h0 := f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38
h1 := f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38
h2 := f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19
h3 := f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38
h4 := f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38
h5 := f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38
h6 := f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19
h7 := f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38
h8 := f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38
h9 := f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2
var carry [10]int64
carry[0] = (h0 + (1 << 25)) >> 26
h1 += carry[0]
h0 -= carry[0] << 26
carry[4] = (h4 + (1 << 25)) >> 26
h5 += carry[4]
h4 -= carry[4] << 26
carry[1] = (h1 + (1 << 24)) >> 25
h2 += carry[1]
h1 -= carry[1] << 25
carry[5] = (h5 + (1 << 24)) >> 25
h6 += carry[5]
h5 -= carry[5] << 25
carry[2] = (h2 + (1 << 25)) >> 26
h3 += carry[2]
h2 -= carry[2] << 26
carry[6] = (h6 + (1 << 25)) >> 26
h7 += carry[6]
h6 -= carry[6] << 26
carry[3] = (h3 + (1 << 24)) >> 25
h4 += carry[3]
h3 -= carry[3] << 25
carry[7] = (h7 + (1 << 24)) >> 25
h8 += carry[7]
h7 -= carry[7] << 25
carry[4] = (h4 + (1 << 25)) >> 26
h5 += carry[4]
h4 -= carry[4] << 26
carry[8] = (h8 + (1 << 25)) >> 26
h9 += carry[8]
h8 -= carry[8] << 26
carry[9] = (h9 + (1 << 24)) >> 25
h0 += carry[9] * 19
h9 -= carry[9] << 25
carry[0] = (h0 + (1 << 25)) >> 26
h1 += carry[0]
h0 -= carry[0] << 26
h[0] = int32(h0)
h[1] = int32(h1)
h[2] = int32(h2)
h[3] = int32(h3)
h[4] = int32(h4)
h[5] = int32(h5)
h[6] = int32(h6)
h[7] = int32(h7)
h[8] = int32(h8)
h[9] = int32(h9)
}
// feMul121666 calculates h = f * 121666. Can overlap h with f.
//
// Preconditions:
// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
//
// Postconditions:
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
func feMul121666(h, f *fieldElement) {
h0 := int64(f[0]) * 121666
h1 := int64(f[1]) * 121666
h2 := int64(f[2]) * 121666
h3 := int64(f[3]) * 121666
h4 := int64(f[4]) * 121666
h5 := int64(f[5]) * 121666
h6 := int64(f[6]) * 121666
h7 := int64(f[7]) * 121666
h8 := int64(f[8]) * 121666
h9 := int64(f[9]) * 121666
var carry [10]int64
carry[9] = (h9 + (1 << 24)) >> 25
h0 += carry[9] * 19
h9 -= carry[9] << 25
carry[1] = (h1 + (1 << 24)) >> 25
h2 += carry[1]
h1 -= carry[1] << 25
carry[3] = (h3 + (1 << 24)) >> 25
h4 += carry[3]
h3 -= carry[3] << 25
carry[5] = (h5 + (1 << 24)) >> 25
h6 += carry[5]
h5 -= carry[5] << 25
carry[7] = (h7 + (1 << 24)) >> 25
h8 += carry[7]
h7 -= carry[7] << 25
carry[0] = (h0 + (1 << 25)) >> 26
h1 += carry[0]
h0 -= carry[0] << 26
carry[2] = (h2 + (1 << 25)) >> 26
h3 += carry[2]
h2 -= carry[2] << 26
carry[4] = (h4 + (1 << 25)) >> 26
h5 += carry[4]
h4 -= carry[4] << 26
carry[6] = (h6 + (1 << 25)) >> 26
h7 += carry[6]
h6 -= carry[6] << 26
carry[8] = (h8 + (1 << 25)) >> 26
h9 += carry[8]
h8 -= carry[8] << 26
h[0] = int32(h0)
h[1] = int32(h1)
h[2] = int32(h2)
h[3] = int32(h3)
h[4] = int32(h4)
h[5] = int32(h5)
h[6] = int32(h6)
h[7] = int32(h7)
h[8] = int32(h8)
h[9] = int32(h9)
}
// feInvert sets out = z^-1.
func feInvert(out, z *fieldElement) {
var t0, t1, t2, t3 fieldElement
var i int
feSquare(&t0, z)
for i = 1; i < 1; i++ {
feSquare(&t0, &t0)
}
feSquare(&t1, &t0)
for i = 1; i < 2; i++ {
feSquare(&t1, &t1)
}
feMul(&t1, z, &t1)
feMul(&t0, &t0, &t1)
feSquare(&t2, &t0)
for i = 1; i < 1; i++ {
feSquare(&t2, &t2)
}
feMul(&t1, &t1, &t2)
feSquare(&t2, &t1)
for i = 1; i < 5; i++ {
feSquare(&t2, &t2)
}
feMul(&t1, &t2, &t1)
feSquare(&t2, &t1)
for i = 1; i < 10; i++ {
feSquare(&t2, &t2)
}
feMul(&t2, &t2, &t1)
feSquare(&t3, &t2)
for i = 1; i < 20; i++ {
feSquare(&t3, &t3)
}
feMul(&t2, &t3, &t2)
feSquare(&t2, &t2)
for i = 1; i < 10; i++ {
feSquare(&t2, &t2)
}
feMul(&t1, &t2, &t1)
feSquare(&t2, &t1)
for i = 1; i < 50; i++ {
feSquare(&t2, &t2)
}
feMul(&t2, &t2, &t1)
feSquare(&t3, &t2)
for i = 1; i < 100; i++ {
feSquare(&t3, &t3)
}
feMul(&t2, &t3, &t2)
feSquare(&t2, &t2)
for i = 1; i < 50; i++ {
feSquare(&t2, &t2)
}
feMul(&t1, &t2, &t1)
feSquare(&t1, &t1)
for i = 1; i < 5; i++ {
feSquare(&t1, &t1)
}
feMul(out, &t1, &t0)
}
func scalarMult(out, in, base *[32]byte) {
var e [32]byte
copy(e[:], in[:])
e[0] &= 248
e[31] &= 127
e[31] |= 64
var x1, x2, z2, x3, z3, tmp0, tmp1 fieldElement
feFromBytes(&x1, base)
feOne(&x2)
feCopy(&x3, &x1)
feOne(&z3)
swap := int32(0)
for pos := 254; pos >= 0; pos-- {
b := e[pos/8] >> uint(pos&7)
b &= 1
swap ^= int32(b)
feCSwap(&x2, &x3, swap)
feCSwap(&z2, &z3, swap)
swap = int32(b)
feSub(&tmp0, &x3, &z3)
feSub(&tmp1, &x2, &z2)
feAdd(&x2, &x2, &z2)
feAdd(&z2, &x3, &z3)
feMul(&z3, &tmp0, &x2)
feMul(&z2, &z2, &tmp1)
feSquare(&tmp0, &tmp1)
feSquare(&tmp1, &x2)
feAdd(&x3, &z3, &z2)
feSub(&z2, &z3, &z2)
feMul(&x2, &tmp1, &tmp0)
feSub(&tmp1, &tmp1, &tmp0)
feSquare(&z2, &z2)
feMul121666(&z3, &tmp1)
feSquare(&x3, &x3)
feAdd(&tmp0, &tmp0, &z3)
feMul(&z3, &x1, &z2)
feMul(&z2, &tmp1, &tmp0)
}
feCSwap(&x2, &x3, swap)
feCSwap(&z2, &z3, swap)
feInvert(&z2, &z2)
feMul(&x2, &x2, &z2)
feToBytes(out, &x2)
}

View file

@ -0,0 +1,29 @@
// 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 curve25519
import (
"fmt"
"testing"
)
const expectedHex = "89161fde887b2b53de549af483940106ecc114d6982daa98256de23bdf77661a"
func TestBaseScalarMult(t *testing.T) {
var a, b [32]byte
in := &a
out := &b
a[0] = 1
for i := 0; i < 200; i++ {
ScalarBaseMult(out, in)
in, out = out, in
}
result := fmt.Sprintf("%x", in[:])
if result != expectedHex {
t.Errorf("incorrect result: got %s, want %s", result, expectedHex)
}
}

View file

@ -0,0 +1,23 @@
// 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 curve25519 provides an implementation of scalar multiplication on
// the elliptic curve known as curve25519. See http://cr.yp.to/ecdh.html
package curve25519 // import "golang.org/x/crypto/curve25519"
// basePoint is the x coordinate of the generator of the curve.
var basePoint = [32]byte{9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
// ScalarMult sets dst to the product in*base where dst and base are the x
// coordinates of group points and all values are in little-endian form.
func ScalarMult(dst, in, base *[32]byte) {
scalarMult(dst, in, base)
}
// ScalarBaseMult sets dst to the product in*base where dst and base are the x
// coordinates of group points, base is the standard generator and all values
// are in little-endian form.
func ScalarBaseMult(dst, in *[32]byte) {
ScalarMult(dst, in, &basePoint)
}

View file

@ -0,0 +1,94 @@
// 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.
// This code was translated into a form compatible with 6a from the public
// domain sources in SUPERCOP: http://bench.cr.yp.to/supercop.html
// +build amd64,!gccgo,!appengine
// func freeze(inout *[5]uint64)
TEXT ·freeze(SB),7,$96-8
MOVQ inout+0(FP), DI
MOVQ SP,R11
MOVQ $31,CX
NOTQ CX
ANDQ CX,SP
ADDQ $32,SP
MOVQ R11,0(SP)
MOVQ R12,8(SP)
MOVQ R13,16(SP)
MOVQ R14,24(SP)
MOVQ R15,32(SP)
MOVQ BX,40(SP)
MOVQ BP,48(SP)
MOVQ 0(DI),SI
MOVQ 8(DI),DX
MOVQ 16(DI),CX
MOVQ 24(DI),R8
MOVQ 32(DI),R9
MOVQ ·REDMASK51(SB),AX
MOVQ AX,R10
SUBQ $18,R10
MOVQ $3,R11
REDUCELOOP:
MOVQ SI,R12
SHRQ $51,R12
ANDQ AX,SI
ADDQ R12,DX
MOVQ DX,R12
SHRQ $51,R12
ANDQ AX,DX
ADDQ R12,CX
MOVQ CX,R12
SHRQ $51,R12
ANDQ AX,CX
ADDQ R12,R8
MOVQ R8,R12
SHRQ $51,R12
ANDQ AX,R8
ADDQ R12,R9
MOVQ R9,R12
SHRQ $51,R12
ANDQ AX,R9
IMUL3Q $19,R12,R12
ADDQ R12,SI
SUBQ $1,R11
JA REDUCELOOP
MOVQ $1,R12
CMPQ R10,SI
CMOVQLT R11,R12
CMPQ AX,DX
CMOVQNE R11,R12
CMPQ AX,CX
CMOVQNE R11,R12
CMPQ AX,R8
CMOVQNE R11,R12
CMPQ AX,R9
CMOVQNE R11,R12
NEGQ R12
ANDQ R12,AX
ANDQ R12,R10
SUBQ R10,SI
SUBQ AX,DX
SUBQ AX,CX
SUBQ AX,R8
SUBQ AX,R9
MOVQ SI,0(DI)
MOVQ DX,8(DI)
MOVQ CX,16(DI)
MOVQ R8,24(DI)
MOVQ R9,32(DI)
MOVQ 0(SP),R11
MOVQ 8(SP),R12
MOVQ 16(SP),R13
MOVQ 24(SP),R14
MOVQ 32(SP),R15
MOVQ 40(SP),BX
MOVQ 48(SP),BP
MOVQ R11,SP
MOVQ DI,AX
MOVQ SI,DX
RET

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,240 @@
// 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 amd64,!gccgo,!appengine
package curve25519
// These functions are implemented in the .s files. The names of the functions
// in the rest of the file are also taken from the SUPERCOP sources to help
// people following along.
//go:noescape
func cswap(inout *[5]uint64, v uint64)
//go:noescape
func ladderstep(inout *[5][5]uint64)
//go:noescape
func freeze(inout *[5]uint64)
//go:noescape
func mul(dest, a, b *[5]uint64)
//go:noescape
func square(out, in *[5]uint64)
// mladder uses a Montgomery ladder to calculate (xr/zr) *= s.
func mladder(xr, zr *[5]uint64, s *[32]byte) {
var work [5][5]uint64
work[0] = *xr
setint(&work[1], 1)
setint(&work[2], 0)
work[3] = *xr
setint(&work[4], 1)
j := uint(6)
var prevbit byte
for i := 31; i >= 0; i-- {
for j < 8 {
bit := ((*s)[i] >> j) & 1
swap := bit ^ prevbit
prevbit = bit
cswap(&work[1], uint64(swap))
ladderstep(&work)
j--
}
j = 7
}
*xr = work[1]
*zr = work[2]
}
func scalarMult(out, in, base *[32]byte) {
var e [32]byte
copy(e[:], (*in)[:])
e[0] &= 248
e[31] &= 127
e[31] |= 64
var t, z [5]uint64
unpack(&t, base)
mladder(&t, &z, &e)
invert(&z, &z)
mul(&t, &t, &z)
pack(out, &t)
}
func setint(r *[5]uint64, v uint64) {
r[0] = v
r[1] = 0
r[2] = 0
r[3] = 0
r[4] = 0
}
// unpack sets r = x where r consists of 5, 51-bit limbs in little-endian
// order.
func unpack(r *[5]uint64, x *[32]byte) {
r[0] = uint64(x[0]) |
uint64(x[1])<<8 |
uint64(x[2])<<16 |
uint64(x[3])<<24 |
uint64(x[4])<<32 |
uint64(x[5])<<40 |
uint64(x[6]&7)<<48
r[1] = uint64(x[6])>>3 |
uint64(x[7])<<5 |
uint64(x[8])<<13 |
uint64(x[9])<<21 |
uint64(x[10])<<29 |
uint64(x[11])<<37 |
uint64(x[12]&63)<<45
r[2] = uint64(x[12])>>6 |
uint64(x[13])<<2 |
uint64(x[14])<<10 |
uint64(x[15])<<18 |
uint64(x[16])<<26 |
uint64(x[17])<<34 |
uint64(x[18])<<42 |
uint64(x[19]&1)<<50
r[3] = uint64(x[19])>>1 |
uint64(x[20])<<7 |
uint64(x[21])<<15 |
uint64(x[22])<<23 |
uint64(x[23])<<31 |
uint64(x[24])<<39 |
uint64(x[25]&15)<<47
r[4] = uint64(x[25])>>4 |
uint64(x[26])<<4 |
uint64(x[27])<<12 |
uint64(x[28])<<20 |
uint64(x[29])<<28 |
uint64(x[30])<<36 |
uint64(x[31]&127)<<44
}
// pack sets out = x where out is the usual, little-endian form of the 5,
// 51-bit limbs in x.
func pack(out *[32]byte, x *[5]uint64) {
t := *x
freeze(&t)
out[0] = byte(t[0])
out[1] = byte(t[0] >> 8)
out[2] = byte(t[0] >> 16)
out[3] = byte(t[0] >> 24)
out[4] = byte(t[0] >> 32)
out[5] = byte(t[0] >> 40)
out[6] = byte(t[0] >> 48)
out[6] ^= byte(t[1]<<3) & 0xf8
out[7] = byte(t[1] >> 5)
out[8] = byte(t[1] >> 13)
out[9] = byte(t[1] >> 21)
out[10] = byte(t[1] >> 29)
out[11] = byte(t[1] >> 37)
out[12] = byte(t[1] >> 45)
out[12] ^= byte(t[2]<<6) & 0xc0
out[13] = byte(t[2] >> 2)
out[14] = byte(t[2] >> 10)
out[15] = byte(t[2] >> 18)
out[16] = byte(t[2] >> 26)
out[17] = byte(t[2] >> 34)
out[18] = byte(t[2] >> 42)
out[19] = byte(t[2] >> 50)
out[19] ^= byte(t[3]<<1) & 0xfe
out[20] = byte(t[3] >> 7)
out[21] = byte(t[3] >> 15)
out[22] = byte(t[3] >> 23)
out[23] = byte(t[3] >> 31)
out[24] = byte(t[3] >> 39)
out[25] = byte(t[3] >> 47)
out[25] ^= byte(t[4]<<4) & 0xf0
out[26] = byte(t[4] >> 4)
out[27] = byte(t[4] >> 12)
out[28] = byte(t[4] >> 20)
out[29] = byte(t[4] >> 28)
out[30] = byte(t[4] >> 36)
out[31] = byte(t[4] >> 44)
}
// invert calculates r = x^-1 mod p using Fermat's little theorem.
func invert(r *[5]uint64, x *[5]uint64) {
var z2, z9, z11, z2_5_0, z2_10_0, z2_20_0, z2_50_0, z2_100_0, t [5]uint64
square(&z2, x) /* 2 */
square(&t, &z2) /* 4 */
square(&t, &t) /* 8 */
mul(&z9, &t, x) /* 9 */
mul(&z11, &z9, &z2) /* 11 */
square(&t, &z11) /* 22 */
mul(&z2_5_0, &t, &z9) /* 2^5 - 2^0 = 31 */
square(&t, &z2_5_0) /* 2^6 - 2^1 */
for i := 1; i < 5; i++ { /* 2^20 - 2^10 */
square(&t, &t)
}
mul(&z2_10_0, &t, &z2_5_0) /* 2^10 - 2^0 */
square(&t, &z2_10_0) /* 2^11 - 2^1 */
for i := 1; i < 10; i++ { /* 2^20 - 2^10 */
square(&t, &t)
}
mul(&z2_20_0, &t, &z2_10_0) /* 2^20 - 2^0 */
square(&t, &z2_20_0) /* 2^21 - 2^1 */
for i := 1; i < 20; i++ { /* 2^40 - 2^20 */
square(&t, &t)
}
mul(&t, &t, &z2_20_0) /* 2^40 - 2^0 */
square(&t, &t) /* 2^41 - 2^1 */
for i := 1; i < 10; i++ { /* 2^50 - 2^10 */
square(&t, &t)
}
mul(&z2_50_0, &t, &z2_10_0) /* 2^50 - 2^0 */
square(&t, &z2_50_0) /* 2^51 - 2^1 */
for i := 1; i < 50; i++ { /* 2^100 - 2^50 */
square(&t, &t)
}
mul(&z2_100_0, &t, &z2_50_0) /* 2^100 - 2^0 */
square(&t, &z2_100_0) /* 2^101 - 2^1 */
for i := 1; i < 100; i++ { /* 2^200 - 2^100 */
square(&t, &t)
}
mul(&t, &t, &z2_100_0) /* 2^200 - 2^0 */
square(&t, &t) /* 2^201 - 2^1 */
for i := 1; i < 50; i++ { /* 2^250 - 2^50 */
square(&t, &t)
}
mul(&t, &t, &z2_50_0) /* 2^250 - 2^0 */
square(&t, &t) /* 2^251 - 2^1 */
square(&t, &t) /* 2^252 - 2^2 */
square(&t, &t) /* 2^253 - 2^3 */
square(&t, &t) /* 2^254 - 2^4 */
square(&t, &t) /* 2^255 - 2^5 */
mul(r, &t, &z11) /* 2^255 - 21 */
}

View file

@ -0,0 +1,191 @@
// 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.
// This code was translated into a form compatible with 6a from the public
// domain sources in SUPERCOP: http://bench.cr.yp.to/supercop.html
// +build amd64,!gccgo,!appengine
// func mul(dest, a, b *[5]uint64)
TEXT ·mul(SB),0,$128-24
MOVQ dest+0(FP), DI
MOVQ a+8(FP), SI
MOVQ b+16(FP), DX
MOVQ SP,R11
MOVQ $31,CX
NOTQ CX
ANDQ CX,SP
ADDQ $32,SP
MOVQ R11,0(SP)
MOVQ R12,8(SP)
MOVQ R13,16(SP)
MOVQ R14,24(SP)
MOVQ R15,32(SP)
MOVQ BX,40(SP)
MOVQ BP,48(SP)
MOVQ DI,56(SP)
MOVQ DX,CX
MOVQ 24(SI),DX
IMUL3Q $19,DX,AX
MOVQ AX,64(SP)
MULQ 16(CX)
MOVQ AX,R8
MOVQ DX,R9
MOVQ 32(SI),DX
IMUL3Q $19,DX,AX
MOVQ AX,72(SP)
MULQ 8(CX)
ADDQ AX,R8
ADCQ DX,R9
MOVQ 0(SI),AX
MULQ 0(CX)
ADDQ AX,R8
ADCQ DX,R9
MOVQ 0(SI),AX
MULQ 8(CX)
MOVQ AX,R10
MOVQ DX,R11
MOVQ 0(SI),AX
MULQ 16(CX)
MOVQ AX,R12
MOVQ DX,R13
MOVQ 0(SI),AX
MULQ 24(CX)
MOVQ AX,R14
MOVQ DX,R15
MOVQ 0(SI),AX
MULQ 32(CX)
MOVQ AX,BX
MOVQ DX,BP
MOVQ 8(SI),AX
MULQ 0(CX)
ADDQ AX,R10
ADCQ DX,R11
MOVQ 8(SI),AX
MULQ 8(CX)
ADDQ AX,R12
ADCQ DX,R13
MOVQ 8(SI),AX
MULQ 16(CX)
ADDQ AX,R14
ADCQ DX,R15
MOVQ 8(SI),AX
MULQ 24(CX)
ADDQ AX,BX
ADCQ DX,BP
MOVQ 8(SI),DX
IMUL3Q $19,DX,AX
MULQ 32(CX)
ADDQ AX,R8
ADCQ DX,R9
MOVQ 16(SI),AX
MULQ 0(CX)
ADDQ AX,R12
ADCQ DX,R13
MOVQ 16(SI),AX
MULQ 8(CX)
ADDQ AX,R14
ADCQ DX,R15
MOVQ 16(SI),AX
MULQ 16(CX)
ADDQ AX,BX
ADCQ DX,BP
MOVQ 16(SI),DX
IMUL3Q $19,DX,AX
MULQ 24(CX)
ADDQ AX,R8
ADCQ DX,R9
MOVQ 16(SI),DX
IMUL3Q $19,DX,AX
MULQ 32(CX)
ADDQ AX,R10
ADCQ DX,R11
MOVQ 24(SI),AX
MULQ 0(CX)
ADDQ AX,R14
ADCQ DX,R15
MOVQ 24(SI),AX
MULQ 8(CX)
ADDQ AX,BX
ADCQ DX,BP
MOVQ 64(SP),AX
MULQ 24(CX)
ADDQ AX,R10
ADCQ DX,R11
MOVQ 64(SP),AX
MULQ 32(CX)
ADDQ AX,R12
ADCQ DX,R13
MOVQ 32(SI),AX
MULQ 0(CX)
ADDQ AX,BX
ADCQ DX,BP
MOVQ 72(SP),AX
MULQ 16(CX)
ADDQ AX,R10
ADCQ DX,R11
MOVQ 72(SP),AX
MULQ 24(CX)
ADDQ AX,R12
ADCQ DX,R13
MOVQ 72(SP),AX
MULQ 32(CX)
ADDQ AX,R14
ADCQ DX,R15
MOVQ ·REDMASK51(SB),SI
SHLQ $13,R9:R8
ANDQ SI,R8
SHLQ $13,R11:R10
ANDQ SI,R10
ADDQ R9,R10
SHLQ $13,R13:R12
ANDQ SI,R12
ADDQ R11,R12
SHLQ $13,R15:R14
ANDQ SI,R14
ADDQ R13,R14
SHLQ $13,BP:BX
ANDQ SI,BX
ADDQ R15,BX
IMUL3Q $19,BP,DX
ADDQ DX,R8
MOVQ R8,DX
SHRQ $51,DX
ADDQ R10,DX
MOVQ DX,CX
SHRQ $51,DX
ANDQ SI,R8
ADDQ R12,DX
MOVQ DX,R9
SHRQ $51,DX
ANDQ SI,CX
ADDQ R14,DX
MOVQ DX,AX
SHRQ $51,DX
ANDQ SI,R9
ADDQ BX,DX
MOVQ DX,R10
SHRQ $51,DX
ANDQ SI,AX
IMUL3Q $19,DX,DX
ADDQ DX,R8
ANDQ SI,R10
MOVQ R8,0(DI)
MOVQ CX,8(DI)
MOVQ R9,16(DI)
MOVQ AX,24(DI)
MOVQ R10,32(DI)
MOVQ 0(SP),R11
MOVQ 8(SP),R12
MOVQ 16(SP),R13
MOVQ 24(SP),R14
MOVQ 32(SP),R15
MOVQ 40(SP),BX
MOVQ 48(SP),BP
MOVQ R11,SP
MOVQ DI,AX
MOVQ SI,DX
RET

View file

@ -0,0 +1,153 @@
// 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.
// This code was translated into a form compatible with 6a from the public
// domain sources in SUPERCOP: http://bench.cr.yp.to/supercop.html
// +build amd64,!gccgo,!appengine
// func square(out, in *[5]uint64)
TEXT ·square(SB),7,$96-16
MOVQ out+0(FP), DI
MOVQ in+8(FP), SI
MOVQ SP,R11
MOVQ $31,CX
NOTQ CX
ANDQ CX,SP
ADDQ $32, SP
MOVQ R11,0(SP)
MOVQ R12,8(SP)
MOVQ R13,16(SP)
MOVQ R14,24(SP)
MOVQ R15,32(SP)
MOVQ BX,40(SP)
MOVQ BP,48(SP)
MOVQ 0(SI),AX
MULQ 0(SI)
MOVQ AX,CX
MOVQ DX,R8
MOVQ 0(SI),AX
SHLQ $1,AX
MULQ 8(SI)
MOVQ AX,R9
MOVQ DX,R10
MOVQ 0(SI),AX
SHLQ $1,AX
MULQ 16(SI)
MOVQ AX,R11
MOVQ DX,R12
MOVQ 0(SI),AX
SHLQ $1,AX
MULQ 24(SI)
MOVQ AX,R13
MOVQ DX,R14
MOVQ 0(SI),AX
SHLQ $1,AX
MULQ 32(SI)
MOVQ AX,R15
MOVQ DX,BX
MOVQ 8(SI),AX
MULQ 8(SI)
ADDQ AX,R11
ADCQ DX,R12
MOVQ 8(SI),AX
SHLQ $1,AX
MULQ 16(SI)
ADDQ AX,R13
ADCQ DX,R14
MOVQ 8(SI),AX
SHLQ $1,AX
MULQ 24(SI)
ADDQ AX,R15
ADCQ DX,BX
MOVQ 8(SI),DX
IMUL3Q $38,DX,AX
MULQ 32(SI)
ADDQ AX,CX
ADCQ DX,R8
MOVQ 16(SI),AX
MULQ 16(SI)
ADDQ AX,R15
ADCQ DX,BX
MOVQ 16(SI),DX
IMUL3Q $38,DX,AX
MULQ 24(SI)
ADDQ AX,CX
ADCQ DX,R8
MOVQ 16(SI),DX
IMUL3Q $38,DX,AX
MULQ 32(SI)
ADDQ AX,R9
ADCQ DX,R10
MOVQ 24(SI),DX
IMUL3Q $19,DX,AX
MULQ 24(SI)
ADDQ AX,R9
ADCQ DX,R10
MOVQ 24(SI),DX
IMUL3Q $38,DX,AX
MULQ 32(SI)
ADDQ AX,R11
ADCQ DX,R12
MOVQ 32(SI),DX
IMUL3Q $19,DX,AX
MULQ 32(SI)
ADDQ AX,R13
ADCQ DX,R14
MOVQ ·REDMASK51(SB),SI
SHLQ $13,R8:CX
ANDQ SI,CX
SHLQ $13,R10:R9
ANDQ SI,R9
ADDQ R8,R9
SHLQ $13,R12:R11
ANDQ SI,R11
ADDQ R10,R11
SHLQ $13,R14:R13
ANDQ SI,R13
ADDQ R12,R13
SHLQ $13,BX:R15
ANDQ SI,R15
ADDQ R14,R15
IMUL3Q $19,BX,DX
ADDQ DX,CX
MOVQ CX,DX
SHRQ $51,DX
ADDQ R9,DX
ANDQ SI,CX
MOVQ DX,R8
SHRQ $51,DX
ADDQ R11,DX
ANDQ SI,R8
MOVQ DX,R9
SHRQ $51,DX
ADDQ R13,DX
ANDQ SI,R9
MOVQ DX,AX
SHRQ $51,DX
ADDQ R15,DX
ANDQ SI,AX
MOVQ DX,R10
SHRQ $51,DX
IMUL3Q $19,DX,DX
ADDQ DX,CX
ANDQ SI,R10
MOVQ CX,0(DI)
MOVQ R8,8(DI)
MOVQ R9,16(DI)
MOVQ AX,24(DI)
MOVQ R10,32(DI)
MOVQ 0(SP),R11
MOVQ 8(SP),R12
MOVQ 16(SP),R13
MOVQ 24(SP),R14
MOVQ 32(SP),R15
MOVQ 40(SP),BX
MOVQ 48(SP),BP
MOVQ R11,SP
MOVQ DI,AX
MOVQ SI,DX
RET

View file

@ -0,0 +1,181 @@
// 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 ed25519 implements the Ed25519 signature algorithm. See
// http://ed25519.cr.yp.to/.
//
// These functions are also compatible with the “Ed25519” function defined in
// https://tools.ietf.org/html/draft-irtf-cfrg-eddsa-05.
package ed25519
// This code is a port of the public domain, “ref10” implementation of ed25519
// from SUPERCOP.
import (
"crypto"
cryptorand "crypto/rand"
"crypto/sha512"
"crypto/subtle"
"errors"
"io"
"strconv"
"golang.org/x/crypto/ed25519/internal/edwards25519"
)
const (
// PublicKeySize is the size, in bytes, of public keys as used in this package.
PublicKeySize = 32
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
PrivateKeySize = 64
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
SignatureSize = 64
)
// PublicKey is the type of Ed25519 public keys.
type PublicKey []byte
// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer.
type PrivateKey []byte
// Public returns the PublicKey corresponding to priv.
func (priv PrivateKey) Public() crypto.PublicKey {
publicKey := make([]byte, PublicKeySize)
copy(publicKey, priv[32:])
return PublicKey(publicKey)
}
// Sign signs the given message with priv.
// Ed25519 performs two passes over messages to be signed and therefore cannot
// handle pre-hashed messages. Thus opts.HashFunc() must return zero to
// indicate the message hasn't been hashed. This can be achieved by passing
// crypto.Hash(0) as the value for opts.
func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
if opts.HashFunc() != crypto.Hash(0) {
return nil, errors.New("ed25519: cannot sign hashed message")
}
return Sign(priv, message), nil
}
// GenerateKey generates a public/private key pair using entropy from rand.
// If rand is nil, crypto/rand.Reader will be used.
func GenerateKey(rand io.Reader) (publicKey PublicKey, privateKey PrivateKey, err error) {
if rand == nil {
rand = cryptorand.Reader
}
privateKey = make([]byte, PrivateKeySize)
publicKey = make([]byte, PublicKeySize)
_, err = io.ReadFull(rand, privateKey[:32])
if err != nil {
return nil, nil, err
}
digest := sha512.Sum512(privateKey[:32])
digest[0] &= 248
digest[31] &= 127
digest[31] |= 64
var A edwards25519.ExtendedGroupElement
var hBytes [32]byte
copy(hBytes[:], digest[:])
edwards25519.GeScalarMultBase(&A, &hBytes)
var publicKeyBytes [32]byte
A.ToBytes(&publicKeyBytes)
copy(privateKey[32:], publicKeyBytes[:])
copy(publicKey, publicKeyBytes[:])
return publicKey, privateKey, nil
}
// Sign signs the message with privateKey and returns a signature. It will
// panic if len(privateKey) is not PrivateKeySize.
func Sign(privateKey PrivateKey, message []byte) []byte {
if l := len(privateKey); l != PrivateKeySize {
panic("ed25519: bad private key length: " + strconv.Itoa(l))
}
h := sha512.New()
h.Write(privateKey[:32])
var digest1, messageDigest, hramDigest [64]byte
var expandedSecretKey [32]byte
h.Sum(digest1[:0])
copy(expandedSecretKey[:], digest1[:])
expandedSecretKey[0] &= 248
expandedSecretKey[31] &= 63
expandedSecretKey[31] |= 64
h.Reset()
h.Write(digest1[32:])
h.Write(message)
h.Sum(messageDigest[:0])
var messageDigestReduced [32]byte
edwards25519.ScReduce(&messageDigestReduced, &messageDigest)
var R edwards25519.ExtendedGroupElement
edwards25519.GeScalarMultBase(&R, &messageDigestReduced)
var encodedR [32]byte
R.ToBytes(&encodedR)
h.Reset()
h.Write(encodedR[:])
h.Write(privateKey[32:])
h.Write(message)
h.Sum(hramDigest[:0])
var hramDigestReduced [32]byte
edwards25519.ScReduce(&hramDigestReduced, &hramDigest)
var s [32]byte
edwards25519.ScMulAdd(&s, &hramDigestReduced, &expandedSecretKey, &messageDigestReduced)
signature := make([]byte, SignatureSize)
copy(signature[:], encodedR[:])
copy(signature[32:], s[:])
return signature
}
// Verify reports whether sig is a valid signature of message by publicKey. It
// will panic if len(publicKey) is not PublicKeySize.
func Verify(publicKey PublicKey, message, sig []byte) bool {
if l := len(publicKey); l != PublicKeySize {
panic("ed25519: bad public key length: " + strconv.Itoa(l))
}
if len(sig) != SignatureSize || sig[63]&224 != 0 {
return false
}
var A edwards25519.ExtendedGroupElement
var publicKeyBytes [32]byte
copy(publicKeyBytes[:], publicKey)
if !A.FromBytes(&publicKeyBytes) {
return false
}
edwards25519.FeNeg(&A.X, &A.X)
edwards25519.FeNeg(&A.T, &A.T)
h := sha512.New()
h.Write(sig[:32])
h.Write(publicKey[:])
h.Write(message)
var digest [64]byte
h.Sum(digest[:0])
var hReduced [32]byte
edwards25519.ScReduce(&hReduced, &digest)
var R edwards25519.ProjectiveGroupElement
var b [32]byte
copy(b[:], sig[32:])
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &b)
var checkR [32]byte
R.ToBytes(&checkR)
return subtle.ConstantTimeCompare(sig[:32], checkR[:]) == 1
}

View file

@ -0,0 +1,183 @@
// 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 ed25519
import (
"bufio"
"bytes"
"compress/gzip"
"crypto"
"crypto/rand"
"encoding/hex"
"os"
"strings"
"testing"
"golang.org/x/crypto/ed25519/internal/edwards25519"
)
type zeroReader struct{}
func (zeroReader) Read(buf []byte) (int, error) {
for i := range buf {
buf[i] = 0
}
return len(buf), nil
}
func TestUnmarshalMarshal(t *testing.T) {
pub, _, _ := GenerateKey(rand.Reader)
var A edwards25519.ExtendedGroupElement
var pubBytes [32]byte
copy(pubBytes[:], pub)
if !A.FromBytes(&pubBytes) {
t.Fatalf("ExtendedGroupElement.FromBytes failed")
}
var pub2 [32]byte
A.ToBytes(&pub2)
if pubBytes != pub2 {
t.Errorf("FromBytes(%v)->ToBytes does not round-trip, got %x\n", pubBytes, pub2)
}
}
func TestSignVerify(t *testing.T) {
var zero zeroReader
public, private, _ := GenerateKey(zero)
message := []byte("test message")
sig := Sign(private, message)
if !Verify(public, message, sig) {
t.Errorf("valid signature rejected")
}
wrongMessage := []byte("wrong message")
if Verify(public, wrongMessage, sig) {
t.Errorf("signature of different message accepted")
}
}
func TestCryptoSigner(t *testing.T) {
var zero zeroReader
public, private, _ := GenerateKey(zero)
signer := crypto.Signer(private)
publicInterface := signer.Public()
public2, ok := publicInterface.(PublicKey)
if !ok {
t.Fatalf("expected PublicKey from Public() but got %T", publicInterface)
}
if !bytes.Equal(public, public2) {
t.Errorf("public keys do not match: original:%x vs Public():%x", public, public2)
}
message := []byte("message")
var noHash crypto.Hash
signature, err := signer.Sign(zero, message, noHash)
if err != nil {
t.Fatalf("error from Sign(): %s", err)
}
if !Verify(public, message, signature) {
t.Errorf("Verify failed on signature from Sign()")
}
}
func TestGolden(t *testing.T) {
// sign.input.gz is a selection of test cases from
// http://ed25519.cr.yp.to/python/sign.input
testDataZ, err := os.Open("testdata/sign.input.gz")
if err != nil {
t.Fatal(err)
}
defer testDataZ.Close()
testData, err := gzip.NewReader(testDataZ)
if err != nil {
t.Fatal(err)
}
defer testData.Close()
scanner := bufio.NewScanner(testData)
lineNo := 0
for scanner.Scan() {
lineNo++
line := scanner.Text()
parts := strings.Split(line, ":")
if len(parts) != 5 {
t.Fatalf("bad number of parts on line %d", lineNo)
}
privBytes, _ := hex.DecodeString(parts[0])
pubKey, _ := hex.DecodeString(parts[1])
msg, _ := hex.DecodeString(parts[2])
sig, _ := hex.DecodeString(parts[3])
// The signatures in the test vectors also include the message
// at the end, but we just want R and S.
sig = sig[:SignatureSize]
if l := len(pubKey); l != PublicKeySize {
t.Fatalf("bad public key length on line %d: got %d bytes", lineNo, l)
}
var priv [PrivateKeySize]byte
copy(priv[:], privBytes)
copy(priv[32:], pubKey)
sig2 := Sign(priv[:], msg)
if !bytes.Equal(sig, sig2[:]) {
t.Errorf("different signature result on line %d: %x vs %x", lineNo, sig, sig2)
}
if !Verify(pubKey, msg, sig2) {
t.Errorf("signature failed to verify on line %d", lineNo)
}
}
if err := scanner.Err(); err != nil {
t.Fatalf("error reading test data: %s", err)
}
}
func BenchmarkKeyGeneration(b *testing.B) {
var zero zeroReader
for i := 0; i < b.N; i++ {
if _, _, err := GenerateKey(zero); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkSigning(b *testing.B) {
var zero zeroReader
_, priv, err := GenerateKey(zero)
if err != nil {
b.Fatal(err)
}
message := []byte("Hello, world!")
b.ResetTimer()
for i := 0; i < b.N; i++ {
Sign(priv, message)
}
}
func BenchmarkVerification(b *testing.B) {
var zero zeroReader
pub, priv, err := GenerateKey(zero)
if err != nil {
b.Fatal(err)
}
message := []byte("Hello, world!")
signature := Sign(priv, message)
b.ResetTimer()
for i := 0; i < b.N; i++ {
Verify(pub, message, signature)
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -218,7 +218,7 @@ func smix(b []byte, r, N int, v, xy []uint32) {
// For example, you can get a derived key for e.g. AES-256 (which needs a
// 32-byte key) by doing:
//
// dk := scrypt.Key([]byte("some password"), salt, 16384, 8, 1, 32)
// dk, err := scrypt.Key([]byte("some password"), salt, 16384, 8, 1, 32)
//
// The recommended parameters for interactive logins as of 2009 are N=16384,
// r=8, p=1. They should be increased as memory latency and CPU parallelism

View file

@ -2,12 +2,13 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package agent implements a client to an ssh-agent daemon.
References:
[PROTOCOL.agent]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent?rev=HEAD
*/
// Package agent implements the ssh-agent protocol, and provides both
// a client and a server. The client can talk to a standard ssh-agent
// that uses UNIX sockets, and one could implement an alternative
// ssh-agent process using the sample server.
//
// References:
// [PROTOCOL.agent]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent?rev=HEAD
package agent // import "golang.org/x/crypto/ssh/agent"
import (
@ -24,6 +25,7 @@ import (
"math/big"
"sync"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/ssh"
)
@ -36,9 +38,8 @@ type Agent interface {
// in [PROTOCOL.agent] section 2.6.2.
Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error)
// Insert adds a private key to the agent. If a certificate
// is given, that certificate is added as public key.
Add(s interface{}, cert *ssh.Certificate, comment string) error
// Add adds a private key to the agent.
Add(key AddedKey) error
// Remove removes all identities with the given public key.
Remove(key ssh.PublicKey) error
@ -56,9 +57,28 @@ type Agent interface {
Signers() ([]ssh.Signer, error)
}
// AddedKey describes an SSH key to be added to an Agent.
type AddedKey struct {
// PrivateKey must be a *rsa.PrivateKey, *dsa.PrivateKey or
// *ecdsa.PrivateKey, which will be inserted into the agent.
PrivateKey interface{}
// Certificate, if not nil, is communicated to the agent and will be
// stored with the key.
Certificate *ssh.Certificate
// Comment is an optional, free-form string.
Comment string
// LifetimeSecs, if not zero, is the number of seconds that the
// agent will store the key for.
LifetimeSecs uint32
// ConfirmBeforeUse, if true, requests that the agent confirm with the
// user before each use of this key.
ConfirmBeforeUse bool
}
// See [PROTOCOL.agent], section 3.
const (
agentRequestV1Identities = 1
agentRequestV1Identities = 1
agentRemoveAllV1Identities = 9
// 3.2 Requests from client to agent for protocol 2 key operations
agentAddIdentity = 17
@ -165,10 +185,13 @@ func (k *Key) Marshal() []byte {
return k.Blob
}
// Verify satisfies the ssh.PublicKey interface, but is not
// implemented for agent keys.
// Verify satisfies the ssh.PublicKey interface.
func (k *Key) Verify(data []byte, sig *ssh.Signature) error {
return errors.New("agent: agent key does not know how to verify")
pubKey, err := ssh.ParsePublicKey(k.Blob)
if err != nil {
return fmt.Errorf("agent: bad public key: %v", err)
}
return pubKey.Verify(data, sig)
}
type wireKey struct {
@ -358,6 +381,8 @@ func unmarshal(packet []byte) (interface{}, error) {
msg = new(identitiesAnswerAgentMsg)
case agentSignResponse:
msg = new(signResponseAgentMsg)
case agentV1IdentitiesAnswer:
msg = new(agentV1IdentityMsg)
default:
return nil, fmt.Errorf("agent: unknown type tag %d", packet[0])
}
@ -368,36 +393,47 @@ func unmarshal(packet []byte) (interface{}, error) {
}
type rsaKeyMsg struct {
Type string `sshtype:"17"`
N *big.Int
E *big.Int
D *big.Int
Iqmp *big.Int // IQMP = Inverse Q Mod P
P *big.Int
Q *big.Int
Comments string
Type string `sshtype:"17|25"`
N *big.Int
E *big.Int
D *big.Int
Iqmp *big.Int // IQMP = Inverse Q Mod P
P *big.Int
Q *big.Int
Comments string
Constraints []byte `ssh:"rest"`
}
type dsaKeyMsg struct {
Type string `sshtype:"17"`
P *big.Int
Q *big.Int
G *big.Int
Y *big.Int
X *big.Int
Comments string
Type string `sshtype:"17|25"`
P *big.Int
Q *big.Int
G *big.Int
Y *big.Int
X *big.Int
Comments string
Constraints []byte `ssh:"rest"`
}
type ecdsaKeyMsg struct {
Type string `sshtype:"17"`
Curve string
KeyBytes []byte
D *big.Int
Comments string
Type string `sshtype:"17|25"`
Curve string
KeyBytes []byte
D *big.Int
Comments string
Constraints []byte `ssh:"rest"`
}
type ed25519KeyMsg struct {
Type string `sshtype:"17|25"`
Pub []byte
Priv []byte
Comments string
Constraints []byte `ssh:"rest"`
}
// Insert adds a private key to the agent.
func (c *client) insertKey(s interface{}, comment string) error {
func (c *client) insertKey(s interface{}, comment string, constraints []byte) error {
var req []byte
switch k := s.(type) {
case *rsa.PrivateKey:
@ -406,37 +442,54 @@ func (c *client) insertKey(s interface{}, comment string) error {
}
k.Precompute()
req = ssh.Marshal(rsaKeyMsg{
Type: ssh.KeyAlgoRSA,
N: k.N,
E: big.NewInt(int64(k.E)),
D: k.D,
Iqmp: k.Precomputed.Qinv,
P: k.Primes[0],
Q: k.Primes[1],
Comments: comment,
Type: ssh.KeyAlgoRSA,
N: k.N,
E: big.NewInt(int64(k.E)),
D: k.D,
Iqmp: k.Precomputed.Qinv,
P: k.Primes[0],
Q: k.Primes[1],
Comments: comment,
Constraints: constraints,
})
case *dsa.PrivateKey:
req = ssh.Marshal(dsaKeyMsg{
Type: ssh.KeyAlgoDSA,
P: k.P,
Q: k.Q,
G: k.G,
Y: k.Y,
X: k.X,
Comments: comment,
Type: ssh.KeyAlgoDSA,
P: k.P,
Q: k.Q,
G: k.G,
Y: k.Y,
X: k.X,
Comments: comment,
Constraints: constraints,
})
case *ecdsa.PrivateKey:
nistID := fmt.Sprintf("nistp%d", k.Params().BitSize)
req = ssh.Marshal(ecdsaKeyMsg{
Type: "ecdsa-sha2-" + nistID,
Curve: nistID,
KeyBytes: elliptic.Marshal(k.Curve, k.X, k.Y),
D: k.D,
Comments: comment,
Type: "ecdsa-sha2-" + nistID,
Curve: nistID,
KeyBytes: elliptic.Marshal(k.Curve, k.X, k.Y),
D: k.D,
Comments: comment,
Constraints: constraints,
})
case *ed25519.PrivateKey:
req = ssh.Marshal(ed25519KeyMsg{
Type: ssh.KeyAlgoED25519,
Pub: []byte(*k)[32:],
Priv: []byte(*k),
Comments: comment,
Constraints: constraints,
})
default:
return fmt.Errorf("agent: unsupported key type %T", s)
}
// if constraints are present then the message type needs to be changed.
if len(constraints) != 0 {
req[0] = agentAddIdConstrained
}
resp, err := c.call(req)
if err != nil {
return err
@ -448,40 +501,66 @@ func (c *client) insertKey(s interface{}, comment string) error {
}
type rsaCertMsg struct {
Type string `sshtype:"17"`
CertBytes []byte
D *big.Int
Iqmp *big.Int // IQMP = Inverse Q Mod P
P *big.Int
Q *big.Int
Comments string
Type string `sshtype:"17|25"`
CertBytes []byte
D *big.Int
Iqmp *big.Int // IQMP = Inverse Q Mod P
P *big.Int
Q *big.Int
Comments string
Constraints []byte `ssh:"rest"`
}
type dsaCertMsg struct {
Type string `sshtype:"17"`
CertBytes []byte
X *big.Int
Comments string
Type string `sshtype:"17|25"`
CertBytes []byte
X *big.Int
Comments string
Constraints []byte `ssh:"rest"`
}
type ecdsaCertMsg struct {
Type string `sshtype:"17"`
CertBytes []byte
D *big.Int
Comments string
Type string `sshtype:"17|25"`
CertBytes []byte
D *big.Int
Comments string
Constraints []byte `ssh:"rest"`
}
// Insert adds a private key to the agent. If a certificate is given,
type ed25519CertMsg struct {
Type string `sshtype:"17|25"`
CertBytes []byte
Pub []byte
Priv []byte
Comments string
Constraints []byte `ssh:"rest"`
}
// Add adds a private key to the agent. If a certificate is given,
// that certificate is added instead as public key.
func (c *client) Add(s interface{}, cert *ssh.Certificate, comment string) error {
if cert == nil {
return c.insertKey(s, comment)
func (c *client) Add(key AddedKey) error {
var constraints []byte
if secs := key.LifetimeSecs; secs != 0 {
constraints = append(constraints, agentConstrainLifetime)
var secsBytes [4]byte
binary.BigEndian.PutUint32(secsBytes[:], secs)
constraints = append(constraints, secsBytes[:]...)
}
if key.ConfirmBeforeUse {
constraints = append(constraints, agentConstrainConfirm)
}
if cert := key.Certificate; cert == nil {
return c.insertKey(key.PrivateKey, key.Comment, constraints)
} else {
return c.insertCert(s, cert, comment)
return c.insertCert(key.PrivateKey, cert, key.Comment, constraints)
}
}
func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string) error {
func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string, constraints []byte) error {
var req []byte
switch k := s.(type) {
case *rsa.PrivateKey:
@ -490,32 +569,49 @@ func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string
}
k.Precompute()
req = ssh.Marshal(rsaCertMsg{
Type: cert.Type(),
CertBytes: cert.Marshal(),
D: k.D,
Iqmp: k.Precomputed.Qinv,
P: k.Primes[0],
Q: k.Primes[1],
Comments: comment,
Type: cert.Type(),
CertBytes: cert.Marshal(),
D: k.D,
Iqmp: k.Precomputed.Qinv,
P: k.Primes[0],
Q: k.Primes[1],
Comments: comment,
Constraints: constraints,
})
case *dsa.PrivateKey:
req = ssh.Marshal(dsaCertMsg{
Type: cert.Type(),
CertBytes: cert.Marshal(),
X: k.X,
Comments: comment,
Type: cert.Type(),
CertBytes: cert.Marshal(),
X: k.X,
Comments: comment,
Constraints: constraints,
})
case *ecdsa.PrivateKey:
req = ssh.Marshal(ecdsaCertMsg{
Type: cert.Type(),
CertBytes: cert.Marshal(),
D: k.D,
Comments: comment,
Type: cert.Type(),
CertBytes: cert.Marshal(),
D: k.D,
Comments: comment,
Constraints: constraints,
})
case *ed25519.PrivateKey:
req = ssh.Marshal(ed25519CertMsg{
Type: cert.Type(),
CertBytes: cert.Marshal(),
Pub: []byte(*k)[32:],
Priv: []byte(*k),
Comments: comment,
Constraints: constraints,
})
default:
return fmt.Errorf("agent: unsupported key type %T", s)
}
// if constraints are present then the message type needs to be changed.
if len(constraints) != 0 {
req[0] = agentAddIdConstrained
}
signer, err := ssh.NewSignerFromKey(s)
if err != nil {
return err

View file

@ -14,6 +14,7 @@ import (
"path/filepath"
"strconv"
"testing"
"time"
"golang.org/x/crypto/ssh"
)
@ -78,14 +79,14 @@ func startAgent(t *testing.T) (client Agent, socket string, cleanup func()) {
}
}
func testAgent(t *testing.T, key interface{}, cert *ssh.Certificate) {
func testAgent(t *testing.T, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
agent, _, cleanup := startAgent(t)
defer cleanup()
testAgentInterface(t, agent, key, cert)
testAgentInterface(t, agent, key, cert, lifetimeSecs)
}
func testAgentInterface(t *testing.T, agent Agent, key interface{}, cert *ssh.Certificate) {
func testAgentInterface(t *testing.T, agent Agent, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
signer, err := ssh.NewSignerFromKey(key)
if err != nil {
t.Fatalf("NewSignerFromKey(%T): %v", key, err)
@ -100,10 +101,15 @@ func testAgentInterface(t *testing.T, agent Agent, key interface{}, cert *ssh.Ce
// Attempt to insert the key, with certificate if specified.
var pubKey ssh.PublicKey
if cert != nil {
err = agent.Add(key, cert, "comment")
err = agent.Add(AddedKey{
PrivateKey: key,
Certificate: cert,
Comment: "comment",
LifetimeSecs: lifetimeSecs,
})
pubKey = cert
} else {
err = agent.Add(key, nil, "comment")
err = agent.Add(AddedKey{PrivateKey: key, Comment: "comment", LifetimeSecs: lifetimeSecs})
pubKey = signer.PublicKey()
}
if err != nil {
@ -134,8 +140,8 @@ func testAgentInterface(t *testing.T, agent Agent, key interface{}, cert *ssh.Ce
}
func TestAgent(t *testing.T) {
for _, keyType := range []string{"rsa", "dsa", "ecdsa"} {
testAgent(t, testPrivateKeys[keyType], nil)
for _, keyType := range []string{"rsa", "dsa", "ecdsa", "ed25519"} {
testAgent(t, testPrivateKeys[keyType], nil, 0)
}
}
@ -147,7 +153,11 @@ func TestCert(t *testing.T) {
}
cert.SignCert(rand.Reader, testSigners["ecdsa"])
testAgent(t, testPrivateKeys["rsa"], cert)
testAgent(t, testPrivateKeys["rsa"], cert, 0)
}
func TestConstraints(t *testing.T) {
testAgent(t, testPrivateKeys["rsa"], nil, 3600 /* lifetime in seconds */)
}
// netPipe is analogous to net.Pipe, but it uses a real net.Conn, and
@ -185,7 +195,7 @@ func TestAuth(t *testing.T) {
agent, _, cleanup := startAgent(t)
defer cleanup()
if err := agent.Add(testPrivateKeys["rsa"], nil, "comment"); err != nil {
if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"], Comment: "comment"}); err != nil {
t.Errorf("Add: %v", err)
}
@ -223,10 +233,10 @@ func TestLockClient(t *testing.T) {
}
func testLockAgent(agent Agent, t *testing.T) {
if err := agent.Add(testPrivateKeys["rsa"], nil, "comment 1"); err != nil {
if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"], Comment: "comment 1"}); err != nil {
t.Errorf("Add: %v", err)
}
if err := agent.Add(testPrivateKeys["dsa"], nil, "comment dsa"); err != nil {
if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["dsa"], Comment: "comment dsa"}); err != nil {
t.Errorf("Add: %v", err)
}
if keys, err := agent.List(); err != nil {
@ -276,3 +286,42 @@ func testLockAgent(agent Agent, t *testing.T) {
t.Errorf("Want 1 keys, got %v", keys)
}
}
func TestAgentLifetime(t *testing.T) {
agent, _, cleanup := startAgent(t)
defer cleanup()
for _, keyType := range []string{"rsa", "dsa", "ecdsa"} {
// Add private keys to the agent.
err := agent.Add(AddedKey{
PrivateKey: testPrivateKeys[keyType],
Comment: "comment",
LifetimeSecs: 1,
})
if err != nil {
t.Fatalf("add: %v", err)
}
// Add certs to the agent.
cert := &ssh.Certificate{
Key: testPublicKeys[keyType],
ValidBefore: ssh.CertTimeInfinity,
CertType: ssh.UserCert,
}
cert.SignCert(rand.Reader, testSigners[keyType])
err = agent.Add(AddedKey{
PrivateKey: testPrivateKeys[keyType],
Certificate: cert,
Comment: "comment",
LifetimeSecs: 1,
})
if err != nil {
t.Fatalf("add: %v", err)
}
}
time.Sleep(1100 * time.Millisecond)
if keys, err := agent.List(); err != nil {
t.Errorf("List: %v", err)
} else if len(keys) != 0 {
t.Errorf("Want 0 keys, got %v", len(keys))
}
}

View file

@ -0,0 +1,40 @@
// 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 agent_test
import (
"log"
"os"
"net"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
func ExampleClientAgent() {
// ssh-agent has a UNIX socket under $SSH_AUTH_SOCK
socket := os.Getenv("SSH_AUTH_SOCK")
conn, err := net.Dial("unix", socket)
if err != nil {
log.Fatalf("net.Dial: %v", err)
}
agentClient := agent.NewClient(conn)
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
// Use a callback rather than PublicKeys
// so we only consult the agent once the remote server
// wants it.
ssh.PublicKeysCallback(agentClient.Signers),
},
}
sshc, err := ssh.Dial("tcp", "localhost:22", config)
if err != nil {
log.Fatalf("Dial: %v", err)
}
// .. use sshc
sshc.Close()
}

View file

@ -62,7 +62,7 @@ func (r *keyring) Remove(key ssh.PublicKey) error {
if bytes.Equal(r.keys[i].signer.PublicKey().Marshal(), want) {
found = true
r.keys[i] = r.keys[len(r.keys)-1]
r.keys = r.keys[len(r.keys)-1:]
r.keys = r.keys[:len(r.keys)-1]
continue
} else {
i++
@ -125,27 +125,28 @@ func (r *keyring) List() ([]*Key, error) {
}
// Insert adds a private key to the keyring. If a certificate
// is given, that certificate is added as public key.
func (r *keyring) Add(priv interface{}, cert *ssh.Certificate, comment string) error {
// is given, that certificate is added as public key. Note that
// any constraints given are ignored.
func (r *keyring) Add(key AddedKey) error {
r.mu.Lock()
defer r.mu.Unlock()
if r.locked {
return errLocked
}
signer, err := ssh.NewSignerFromKey(priv)
signer, err := ssh.NewSignerFromKey(key.PrivateKey)
if err != nil {
return err
}
if cert != nil {
if cert := key.Certificate; cert != nil {
signer, err = ssh.NewCertSigner(cert, signer)
if err != nil {
return err
}
}
r.keys = append(r.keys, privKey{signer, comment})
r.keys = append(r.keys, privKey{signer, key.Comment})
return nil
}

View file

@ -0,0 +1,78 @@
// 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.
package agent
import (
"testing"
)
func addTestKey(t *testing.T, a Agent, keyName string) {
err := a.Add(AddedKey{
PrivateKey: testPrivateKeys[keyName],
Comment: keyName,
})
if err != nil {
t.Fatalf("failed to add key %q: %v", keyName, err)
}
}
func removeTestKey(t *testing.T, a Agent, keyName string) {
err := a.Remove(testPublicKeys[keyName])
if err != nil {
t.Fatalf("failed to remove key %q: %v", keyName, err)
}
}
func validateListedKeys(t *testing.T, a Agent, expectedKeys []string) {
listedKeys, err := a.List()
if err != nil {
t.Fatalf("failed to list keys: %v", err)
return
}
actualKeys := make(map[string]bool)
for _, key := range listedKeys {
actualKeys[key.Comment] = true
}
matchedKeys := make(map[string]bool)
for _, expectedKey := range expectedKeys {
if !actualKeys[expectedKey] {
t.Fatalf("expected key %q, but was not found", expectedKey)
} else {
matchedKeys[expectedKey] = true
}
}
for actualKey := range actualKeys {
if !matchedKeys[actualKey] {
t.Fatalf("key %q was found, but was not expected", actualKey)
}
}
}
func TestKeyringAddingAndRemoving(t *testing.T) {
keyNames := []string{"dsa", "ecdsa", "rsa", "user"}
// add all test private keys
k := NewKeyring()
for _, keyName := range keyNames {
addTestKey(t, k, keyName)
}
validateListedKeys(t, k, keyNames)
// remove a key in the middle
keyToRemove := keyNames[1]
keyNames = append(keyNames[:1], keyNames[2:]...)
removeTestKey(t, k, keyToRemove)
validateListedKeys(t, k, keyNames)
// remove all keys
err := k.RemoveAll()
if err != nil {
t.Fatalf("failed to remove all keys: %v", err)
}
validateListedKeys(t, k, []string{})
}

View file

@ -5,13 +5,18 @@
package agent
import (
"crypto/dsa"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"encoding/binary"
"errors"
"fmt"
"io"
"log"
"math/big"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/ssh"
)
@ -49,6 +54,9 @@ func marshalKey(k *Key) []byte {
return ssh.Marshal(&record)
}
// See [PROTOCOL.agent], section 2.5.1.
const agentV1IdentitiesAnswer = 2
type agentV1IdentityMsg struct {
Numkeys uint32 `sshtype:"2"`
}
@ -69,6 +77,10 @@ func (s *server) processRequest(data []byte) (interface{}, error) {
switch data[0] {
case agentRequestV1Identities:
return &agentV1IdentityMsg{0}, nil
case agentRemoveAllV1Identities:
return nil, nil
case agentRemoveIdentity:
var req agentRemoveIdentityMsg
if err := ssh.Unmarshal(data, &req); err != nil {
@ -121,6 +133,7 @@ func (s *server) processRequest(data []byte) (interface{}, error) {
return nil, err
}
return &signResponseAgentMsg{SigBlob: ssh.Marshal(sig)}, nil
case agentRequestIdentities:
keys, err := s.agent.List()
if err != nil {
@ -134,42 +147,271 @@ func (s *server) processRequest(data []byte) (interface{}, error) {
rep.Keys = append(rep.Keys, marshalKey(k)...)
}
return rep, nil
case agentAddIdentity:
case agentAddIdConstrained, agentAddIdentity:
return nil, s.insertIdentity(data)
}
return nil, fmt.Errorf("unknown opcode %d", data[0])
}
func parseRSAKey(req []byte) (*AddedKey, error) {
var k rsaKeyMsg
if err := ssh.Unmarshal(req, &k); err != nil {
return nil, err
}
if k.E.BitLen() > 30 {
return nil, errors.New("agent: RSA public exponent too large")
}
priv := &rsa.PrivateKey{
PublicKey: rsa.PublicKey{
E: int(k.E.Int64()),
N: k.N,
},
D: k.D,
Primes: []*big.Int{k.P, k.Q},
}
priv.Precompute()
return &AddedKey{PrivateKey: priv, Comment: k.Comments}, nil
}
func parseEd25519Key(req []byte) (*AddedKey, error) {
var k ed25519KeyMsg
if err := ssh.Unmarshal(req, &k); err != nil {
return nil, err
}
priv := ed25519.PrivateKey(k.Priv)
return &AddedKey{PrivateKey: &priv, Comment: k.Comments}, nil
}
func parseDSAKey(req []byte) (*AddedKey, error) {
var k dsaKeyMsg
if err := ssh.Unmarshal(req, &k); err != nil {
return nil, err
}
priv := &dsa.PrivateKey{
PublicKey: dsa.PublicKey{
Parameters: dsa.Parameters{
P: k.P,
Q: k.Q,
G: k.G,
},
Y: k.Y,
},
X: k.X,
}
return &AddedKey{PrivateKey: priv, Comment: k.Comments}, nil
}
func unmarshalECDSA(curveName string, keyBytes []byte, privScalar *big.Int) (priv *ecdsa.PrivateKey, err error) {
priv = &ecdsa.PrivateKey{
D: privScalar,
}
switch curveName {
case "nistp256":
priv.Curve = elliptic.P256()
case "nistp384":
priv.Curve = elliptic.P384()
case "nistp521":
priv.Curve = elliptic.P521()
default:
return nil, fmt.Errorf("agent: unknown curve %q", curveName)
}
priv.X, priv.Y = elliptic.Unmarshal(priv.Curve, keyBytes)
if priv.X == nil || priv.Y == nil {
return nil, errors.New("agent: point not on curve")
}
return priv, nil
}
func parseEd25519Cert(req []byte) (*AddedKey, error) {
var k ed25519CertMsg
if err := ssh.Unmarshal(req, &k); err != nil {
return nil, err
}
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
if err != nil {
return nil, err
}
priv := ed25519.PrivateKey(k.Priv)
cert, ok := pubKey.(*ssh.Certificate)
if !ok {
return nil, errors.New("agent: bad ED25519 certificate")
}
return &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}, nil
}
func parseECDSAKey(req []byte) (*AddedKey, error) {
var k ecdsaKeyMsg
if err := ssh.Unmarshal(req, &k); err != nil {
return nil, err
}
priv, err := unmarshalECDSA(k.Curve, k.KeyBytes, k.D)
if err != nil {
return nil, err
}
return &AddedKey{PrivateKey: priv, Comment: k.Comments}, nil
}
func parseRSACert(req []byte) (*AddedKey, error) {
var k rsaCertMsg
if err := ssh.Unmarshal(req, &k); err != nil {
return nil, err
}
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
if err != nil {
return nil, err
}
cert, ok := pubKey.(*ssh.Certificate)
if !ok {
return nil, errors.New("agent: bad RSA certificate")
}
// An RSA publickey as marshaled by rsaPublicKey.Marshal() in keys.go
var rsaPub struct {
Name string
E *big.Int
N *big.Int
}
if err := ssh.Unmarshal(cert.Key.Marshal(), &rsaPub); err != nil {
return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err)
}
if rsaPub.E.BitLen() > 30 {
return nil, errors.New("agent: RSA public exponent too large")
}
priv := rsa.PrivateKey{
PublicKey: rsa.PublicKey{
E: int(rsaPub.E.Int64()),
N: rsaPub.N,
},
D: k.D,
Primes: []*big.Int{k.Q, k.P},
}
priv.Precompute()
return &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}, nil
}
func parseDSACert(req []byte) (*AddedKey, error) {
var k dsaCertMsg
if err := ssh.Unmarshal(req, &k); err != nil {
return nil, err
}
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
if err != nil {
return nil, err
}
cert, ok := pubKey.(*ssh.Certificate)
if !ok {
return nil, errors.New("agent: bad DSA certificate")
}
// A DSA publickey as marshaled by dsaPublicKey.Marshal() in keys.go
var w struct {
Name string
P, Q, G, Y *big.Int
}
if err := ssh.Unmarshal(cert.Key.Marshal(), &w); err != nil {
return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err)
}
priv := &dsa.PrivateKey{
PublicKey: dsa.PublicKey{
Parameters: dsa.Parameters{
P: w.P,
Q: w.Q,
G: w.G,
},
Y: w.Y,
},
X: k.X,
}
return &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}, nil
}
func parseECDSACert(req []byte) (*AddedKey, error) {
var k ecdsaCertMsg
if err := ssh.Unmarshal(req, &k); err != nil {
return nil, err
}
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
if err != nil {
return nil, err
}
cert, ok := pubKey.(*ssh.Certificate)
if !ok {
return nil, errors.New("agent: bad ECDSA certificate")
}
// An ECDSA publickey as marshaled by ecdsaPublicKey.Marshal() in keys.go
var ecdsaPub struct {
Name string
ID string
Key []byte
}
if err := ssh.Unmarshal(cert.Key.Marshal(), &ecdsaPub); err != nil {
return nil, err
}
priv, err := unmarshalECDSA(ecdsaPub.ID, ecdsaPub.Key, k.D)
if err != nil {
return nil, err
}
return &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}, nil
}
func (s *server) insertIdentity(req []byte) error {
var record struct {
Type string `sshtype:"17"`
Type string `sshtype:"17|25"`
Rest []byte `ssh:"rest"`
}
if err := ssh.Unmarshal(req, &record); err != nil {
return err
}
var addedKey *AddedKey
var err error
switch record.Type {
case ssh.KeyAlgoRSA:
var k rsaKeyMsg
if err := ssh.Unmarshal(req, &k); err != nil {
return err
}
priv := rsa.PrivateKey{
PublicKey: rsa.PublicKey{
E: int(k.E.Int64()),
N: k.N,
},
D: k.D,
Primes: []*big.Int{k.P, k.Q},
}
priv.Precompute()
return s.agent.Add(&priv, nil, k.Comments)
addedKey, err = parseRSAKey(req)
case ssh.KeyAlgoDSA:
addedKey, err = parseDSAKey(req)
case ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521:
addedKey, err = parseECDSAKey(req)
case ssh.KeyAlgoED25519:
addedKey, err = parseEd25519Key(req)
case ssh.CertAlgoRSAv01:
addedKey, err = parseRSACert(req)
case ssh.CertAlgoDSAv01:
addedKey, err = parseDSACert(req)
case ssh.CertAlgoECDSA256v01, ssh.CertAlgoECDSA384v01, ssh.CertAlgoECDSA521v01:
addedKey, err = parseECDSACert(req)
case ssh.CertAlgoED25519v01:
addedKey, err = parseEd25519Cert(req)
default:
return fmt.Errorf("agent: not implemented: %q", record.Type)
}
return fmt.Errorf("not implemented: %s", record.Type)
if err != nil {
return err
}
return s.agent.Add(*addedKey)
}
// ServeAgent serves the agent protocol on the given connection. It

View file

@ -5,6 +5,9 @@
package agent
import (
"crypto"
"crypto/rand"
"fmt"
"testing"
"golang.org/x/crypto/ssh"
@ -21,7 +24,7 @@ func TestServer(t *testing.T) {
go ServeAgent(NewKeyring(), c2)
testAgentInterface(t, client, testPrivateKeys["rsa"], nil)
testAgentInterface(t, client, testPrivateKeys["rsa"], nil, 0)
}
func TestLockServer(t *testing.T) {
@ -72,6 +75,133 @@ func TestSetupForwardAgent(t *testing.T) {
go ssh.DiscardRequests(reqs)
agentClient := NewClient(ch)
testAgentInterface(t, agentClient, testPrivateKeys["rsa"], nil)
testAgentInterface(t, agentClient, testPrivateKeys["rsa"], nil, 0)
conn.Close()
}
func TestV1ProtocolMessages(t *testing.T) {
c1, c2, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
defer c1.Close()
defer c2.Close()
c := NewClient(c1)
go ServeAgent(NewKeyring(), c2)
testV1ProtocolMessages(t, c.(*client))
}
func testV1ProtocolMessages(t *testing.T, c *client) {
reply, err := c.call([]byte{agentRequestV1Identities})
if err != nil {
t.Fatalf("v1 request all failed: %v", err)
}
if msg, ok := reply.(*agentV1IdentityMsg); !ok || msg.Numkeys != 0 {
t.Fatalf("invalid request all response: %#v", reply)
}
reply, err = c.call([]byte{agentRemoveAllV1Identities})
if err != nil {
t.Fatalf("v1 remove all failed: %v", err)
}
if _, ok := reply.(*successAgentMsg); !ok {
t.Fatalf("invalid remove all response: %#v", reply)
}
}
func verifyKey(sshAgent Agent) error {
keys, err := sshAgent.List()
if err != nil {
return fmt.Errorf("listing keys: %v", err)
}
if len(keys) != 1 {
return fmt.Errorf("bad number of keys found. expected 1, got %d", len(keys))
}
buf := make([]byte, 128)
if _, err := rand.Read(buf); err != nil {
return fmt.Errorf("rand: %v", err)
}
sig, err := sshAgent.Sign(keys[0], buf)
if err != nil {
return fmt.Errorf("sign: %v", err)
}
if err := keys[0].Verify(buf, sig); err != nil {
return fmt.Errorf("verify: %v", err)
}
return nil
}
func addKeyToAgent(key crypto.PrivateKey) error {
sshAgent := NewKeyring()
if err := sshAgent.Add(AddedKey{PrivateKey: key}); err != nil {
return fmt.Errorf("add: %v", err)
}
return verifyKey(sshAgent)
}
func TestKeyTypes(t *testing.T) {
for k, v := range testPrivateKeys {
if err := addKeyToAgent(v); err != nil {
t.Errorf("error adding key type %s, %v", k, err)
}
if err := addCertToAgentSock(v, nil); err != nil {
t.Errorf("error adding key type %s, %v", k, err)
}
}
}
func addCertToAgentSock(key crypto.PrivateKey, cert *ssh.Certificate) error {
a, b, err := netPipe()
if err != nil {
return err
}
agentServer := NewKeyring()
go ServeAgent(agentServer, a)
agentClient := NewClient(b)
if err := agentClient.Add(AddedKey{PrivateKey: key, Certificate: cert}); err != nil {
return fmt.Errorf("add: %v", err)
}
return verifyKey(agentClient)
}
func addCertToAgent(key crypto.PrivateKey, cert *ssh.Certificate) error {
sshAgent := NewKeyring()
if err := sshAgent.Add(AddedKey{PrivateKey: key, Certificate: cert}); err != nil {
return fmt.Errorf("add: %v", err)
}
return verifyKey(sshAgent)
}
func TestCertTypes(t *testing.T) {
for keyType, key := range testPublicKeys {
cert := &ssh.Certificate{
ValidPrincipals: []string{"gopher1"},
ValidAfter: 0,
ValidBefore: ssh.CertTimeInfinity,
Key: key,
Serial: 1,
CertType: ssh.UserCert,
SignatureKey: testPublicKeys["rsa"],
Permissions: ssh.Permissions{
CriticalOptions: map[string]string{},
Extensions: map[string]string{},
},
}
if err := cert.SignCert(rand.Reader, testSigners["rsa"]); err != nil {
t.Fatalf("signcert: %v", err)
}
if err := addCertToAgent(testPrivateKeys[keyType], cert); err != nil {
t.Fatalf("%v", err)
}
if err := addCertToAgentSock(testPrivateKeys[keyType], cert); err != nil {
t.Fatalf("%v", err)
}
}
}

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// IMPLEMENTOR NOTE: To avoid a package loop, this file is in three places:
// IMPLEMENTATION NOTE: To avoid a package loop, this file is in three places:
// ssh/, ssh/agent, and ssh/test/. It should be kept in sync across all three
// instances.

View file

@ -22,6 +22,7 @@ const (
CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com"
CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com"
CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com"
CertAlgoED25519v01 = "ssh-ed25519-cert-v01@openssh.com"
)
// Certificate types distinguish between host and user
@ -401,6 +402,7 @@ var certAlgoNames = map[string]string{
KeyAlgoECDSA256: CertAlgoECDSA256v01,
KeyAlgoECDSA384: CertAlgoECDSA384v01,
KeyAlgoECDSA521: CertAlgoECDSA521v01,
KeyAlgoED25519: CertAlgoED25519v01,
}
// certToPrivAlgo returns the underlying algorithm for a certificate algorithm.
@ -459,7 +461,7 @@ func (c *Certificate) Marshal() []byte {
func (c *Certificate) Type() string {
algo, ok := certAlgoNames[c.Key.Type()]
if !ok {
panic("unknown cert key type")
panic("unknown cert key type " + c.Key.Type())
}
return algo
}

View file

@ -186,15 +186,15 @@ func TestHostKeyCert(t *testing.T) {
defer c1.Close()
defer c2.Close()
errc := make(chan error)
go func() {
conf := ServerConfig{
NoClientAuth: true,
}
conf.AddHostKey(certSigner)
_, _, _, err := NewServerConn(c1, &conf)
if err != nil {
t.Fatalf("NewServerConn: %v", err)
}
errc <- err
}()
config := &ClientConfig{
@ -207,5 +207,10 @@ func TestHostKeyCert(t *testing.T) {
if (err == nil) != succeed {
t.Fatalf("NewClientConn(%q): %v", name, err)
}
err = <-errc
if (err == nil) != succeed {
t.Fatalf("NewServerConn(%q): %v", name, err)
}
}
}

View file

@ -67,6 +67,8 @@ type Channel interface {
// boolean, otherwise the return value will be false. Channel
// requests are out-of-band messages so they may be sent even
// if the data stream is closed or blocked by flow control.
// If the channel is closed before a reply is returned, io.EOF
// is returned.
SendRequest(name string, wantReply bool, payload []byte) (bool, error)
// Stderr returns an io.ReadWriter that writes to this channel
@ -217,7 +219,7 @@ func (c *channel) writePacket(packet []byte) error {
func (c *channel) sendMessage(msg interface{}) error {
if debugMux {
log.Printf("send %d: %#v", c.mux.chanList.offset, msg)
log.Printf("send(%d): %#v", c.mux.chanList.offset, msg)
}
p := Marshal(msg)
@ -371,7 +373,7 @@ func (c *channel) close() {
close(c.msg)
close(c.incomingRequests)
c.writeMu.Lock()
// This is not necesary for a normal channel teardown, but if
// This is not necessary for a normal channel teardown, but if
// there was another error, it is.
c.sentClose = true
c.writeMu.Unlock()

View file

@ -7,6 +7,7 @@ package ssh
import (
"crypto/aes"
"crypto/cipher"
"crypto/des"
"crypto/rc4"
"crypto/subtle"
"encoding/binary"
@ -115,9 +116,15 @@ var cipherModes = map[string]*streamCipherMode{
// should invest a cleaner way to do this.
gcmCipherID: {16, 12, 0, nil},
// insecure cipher, see http://www.isg.rhul.ac.uk/~kp/SandPfinal.pdf
// uncomment below to enable it.
// aes128cbcID: {16, aes.BlockSize, 0, nil},
// CBC mode is insecure and so is not included in the default config.
// (See http://www.isg.rhul.ac.uk/~kp/SandPfinal.pdf). If absolutely
// needed, it's possible to specify a custom Config to enable it.
// You should expect that an active attacker can recover plaintext if
// you do.
aes128cbcID: {16, aes.BlockSize, 0, nil},
// 3des-cbc is insecure and is disabled by default.
tripledescbcID: {24, des.BlockSize, 0, nil},
}
// prefixLen is the length of the packet prefix that contains the packet length
@ -365,12 +372,7 @@ type cbcCipher struct {
oracleCamouflage uint32
}
func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
func newCBCCipher(c cipher.Block, iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
cbc := &cbcCipher{
mac: macModes[algs.MAC].new(macKey),
decrypter: cipher.NewCBCDecrypter(c, iv),
@ -384,6 +386,34 @@ func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCi
return cbc, nil
}
func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
cbc, err := newCBCCipher(c, iv, key, macKey, algs)
if err != nil {
return nil, err
}
return cbc, nil
}
func newTripleDESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
c, err := des.NewTripleDESCipher(key)
if err != nil {
return nil, err
}
cbc, err := newCBCCipher(c, iv, key, macKey, algs)
if err != nil {
return nil, err
}
return cbc, nil
}
func maxUInt32(a, b int) uint32 {
if a > b {
return uint32(a)

View file

@ -21,7 +21,7 @@ func TestDefaultCiphersExist(t *testing.T) {
}
func TestPacketCiphers(t *testing.T) {
// Still test aes128cbc cipher althought it's commented out.
// Still test aes128cbc cipher although it's commented out.
cipherModes[aes128cbcID] = &streamCipherMode{16, aes.BlockSize, 0, nil}
defer delete(cipherModes, aes128cbcID)

View file

@ -9,6 +9,7 @@ import (
"fmt"
"net"
"sync"
"time"
)
// Client implements a traditional SSH client that supports shells,
@ -96,16 +97,10 @@ func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) e
c.transport = newClientTransport(
newTransport(c.sshConn.conn, config.Rand, true /* is client */),
c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr())
if err := c.transport.requestKeyChange(); err != nil {
if err := c.transport.requestInitialKeyChange(); err != nil {
return err
}
if packet, err := c.transport.readPacket(); err != nil {
return err
} else if packet[0] != msgNewKeys {
return unexpectedMessageError(msgNewKeys, packet[0])
}
// We just did the key change, so the session ID is established.
c.sessionID = c.transport.getSessionID()
@ -169,7 +164,7 @@ func (c *Client) handleChannelOpens(in <-chan NewChannel) {
// to incoming channels and requests, use net.Dial with NewClientConn
// instead.
func Dial(network, addr string, config *ClientConfig) (*Client, error) {
conn, err := net.Dial(network, addr)
conn, err := net.DialTimeout(network, addr, config.Timeout)
if err != nil {
return nil, err
}
@ -203,4 +198,16 @@ type ClientConfig struct {
// ClientVersion contains the version identification string that will
// be used for the connection. If empty, a reasonable default is used.
ClientVersion string
// HostKeyAlgorithms lists the key types that the client will
// accept from the server as host key, in order of
// preference. If empty, a reasonable default is used. Any
// string returned from PublicKey.Type method may be used, or
// any of the CertAlgoXxxx and KeyAlgoXxxx constants.
HostKeyAlgorithms []string
// Timeout is the maximum amount of time for the TCP connection to establish.
//
// A Timeout of zero means no timeout.
Timeout time.Duration
}

View file

@ -321,8 +321,6 @@ func handleAuthResponse(c packetConn) (bool, []string, error) {
return false, msg.Methods, nil
case msgUserAuthSuccess:
return true, nil, nil
case msgDisconnect:
return false, nil, io.EOF
default:
return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
}
@ -439,3 +437,37 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
}
}
}
type retryableAuthMethod struct {
authMethod AuthMethod
maxTries int
}
func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok bool, methods []string, err error) {
for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ {
ok, methods, err = r.authMethod.auth(session, user, c, rand)
if ok || err != nil { // either success or error terminate
return ok, methods, err
}
}
return ok, methods, err
}
func (r *retryableAuthMethod) method() string {
return r.authMethod.method()
}
// RetryableAuthMethod is a decorator for other auth methods enabling them to
// be retried up to maxTries before considering that AuthMethod itself failed.
// If maxTries is <= 0, will retry indefinitely
//
// This is useful for interactive clients using challenge/response type
// authentication (e.g. Keyboard-Interactive, Password, etc) where the user
// could mistype their response resulting in the server issuing a
// SSH_MSG_USERAUTH_FAILURE (rfc4252 #8 [password] and rfc4256 #3.4
// [keyboard-interactive]); Without this decorator, the non-retryable
// AuthMethod would be removed from future consideration, and never tried again
// (and so the user would never be able to retry their entry).
func RetryableAuthMethod(auth AuthMethod, maxTries int) AuthMethod {
return &retryableAuthMethod{authMethod: auth, maxTries: maxTries}
}

View file

@ -9,6 +9,7 @@ import (
"crypto/rand"
"errors"
"fmt"
"os"
"strings"
"testing"
)
@ -243,6 +244,9 @@ func TestClientUnsupportedCipher(t *testing.T) {
}
func TestClientUnsupportedKex(t *testing.T) {
if os.Getenv("GO_BUILDER_NAME") != "" {
t.Skip("skipping known-flaky test on the Go build dashboard; see golang.org/issue/15198")
}
config := &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
@ -252,8 +256,8 @@ func TestClientUnsupportedKex(t *testing.T) {
KeyExchanges: []string{"diffie-hellman-group-exchange-sha256"}, // not currently supported
},
}
if err := tryAuth(t, config); err == nil || !strings.Contains(err.Error(), "no common algorithms") {
t.Errorf("got %v, expected 'no common algorithms'", err)
if err := tryAuth(t, config); err == nil || !strings.Contains(err.Error(), "common algorithm") {
t.Errorf("got %v, expected 'common algorithm'", err)
}
}
@ -296,7 +300,7 @@ func TestClientLoginCert(t *testing.T) {
t.Log("sign with wrong key")
cert.SignCert(rand.Reader, testSigners["dsa"])
if err := tryAuth(t, clientConfig); err == nil {
t.Errorf("cert login passed with non-authoritive key")
t.Errorf("cert login passed with non-authoritative key")
}
t.Log("host cert")
@ -391,3 +395,78 @@ func TestPermissionsPassing(t *testing.T) {
func TestNoPermissionsPassing(t *testing.T) {
testPermissionsPassing(false, t)
}
func TestRetryableAuth(t *testing.T) {
n := 0
passwords := []string{"WRONG1", "WRONG2"}
config := &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
RetryableAuthMethod(PasswordCallback(func() (string, error) {
p := passwords[n]
n++
return p, nil
}), 2),
PublicKeys(testSigners["rsa"]),
},
}
if err := tryAuth(t, config); err != nil {
t.Fatalf("unable to dial remote side: %s", err)
}
if n != 2 {
t.Fatalf("Did not try all passwords")
}
}
func ExampleRetryableAuthMethod(t *testing.T) {
user := "testuser"
NumberOfPrompts := 3
// Normally this would be a callback that prompts the user to answer the
// provided questions
Cb := func(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
return []string{"answer1", "answer2"}, nil
}
config := &ClientConfig{
User: user,
Auth: []AuthMethod{
RetryableAuthMethod(KeyboardInteractiveChallenge(Cb), NumberOfPrompts),
},
}
if err := tryAuth(t, config); err != nil {
t.Fatalf("unable to dial remote side: %s", err)
}
}
// Test if username is received on server side when NoClientAuth is used
func TestClientAuthNone(t *testing.T) {
user := "testuser"
serverConfig := &ServerConfig{
NoClientAuth: true,
}
serverConfig.AddHostKey(testSigners["rsa"])
clientConfig := &ClientConfig{
User: user,
}
c1, c2, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
defer c1.Close()
defer c2.Close()
go NewClientConn(c2, "", clientConfig)
serverConn, err := newServer(c1, serverConfig)
if err != nil {
t.Fatal("newServer: %v", err)
}
if serverConn.User() != user {
t.Fatalf("server: got %q, want %q", serverConn.User(), user)
}
}

View file

@ -33,6 +33,7 @@ var supportedCiphers = []string{
// supportedKexAlgos specifies the supported key-exchange algorithms in
// preference order.
var supportedKexAlgos = []string{
kexAlgoCurve25519SHA256,
// P384 and P521 are not constant-time yet, but since we don't
// reuse ephemeral keys, using them for ECDH should be OK.
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
@ -43,10 +44,12 @@ var supportedKexAlgos = []string{
// of authenticating servers) in preference order.
var supportedHostKeyAlgos = []string{
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
CertAlgoECDSA384v01, CertAlgoECDSA521v01,
CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01,
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
KeyAlgoRSA, KeyAlgoDSA,
KeyAlgoED25519,
}
// supportedMACs specifies a default set of MAC algorithms in preference order.
@ -84,27 +87,15 @@ func parseError(tag uint8) error {
return fmt.Errorf("ssh: parse error in message type %d", tag)
}
func findCommonAlgorithm(clientAlgos []string, serverAlgos []string) (commonAlgo string, ok bool) {
for _, clientAlgo := range clientAlgos {
for _, serverAlgo := range serverAlgos {
if clientAlgo == serverAlgo {
return clientAlgo, true
func findCommon(what string, client []string, server []string) (common string, err error) {
for _, c := range client {
for _, s := range server {
if c == s {
return c, nil
}
}
}
return
}
func findCommonCipher(clientCiphers []string, serverCiphers []string) (commonCipher string, ok bool) {
for _, clientCipher := range clientCiphers {
for _, serverCipher := range serverCiphers {
// reject the cipher if we have no cipherModes definition
if clientCipher == serverCipher && cipherModes[clientCipher] != nil {
return clientCipher, true
}
}
}
return
return "", fmt.Errorf("ssh: no common algorithm for %s; client offered: %v, server offered: %v", what, client, server)
}
type directionAlgorithms struct {
@ -120,50 +111,50 @@ type algorithms struct {
r directionAlgorithms
}
func findAgreedAlgorithms(clientKexInit, serverKexInit *kexInitMsg) (algs *algorithms) {
var ok bool
func findAgreedAlgorithms(clientKexInit, serverKexInit *kexInitMsg) (algs *algorithms, err error) {
result := &algorithms{}
result.kex, ok = findCommonAlgorithm(clientKexInit.KexAlgos, serverKexInit.KexAlgos)
if !ok {
result.kex, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos)
if err != nil {
return
}
result.hostKey, ok = findCommonAlgorithm(clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos)
if !ok {
result.hostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos)
if err != nil {
return
}
result.w.Cipher, ok = findCommonCipher(clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
if !ok {
result.w.Cipher, err = findCommon("client to server cipher", clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
if err != nil {
return
}
result.r.Cipher, ok = findCommonCipher(clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient)
if !ok {
result.r.Cipher, err = findCommon("server to client cipher", clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient)
if err != nil {
return
}
result.w.MAC, ok = findCommonAlgorithm(clientKexInit.MACsClientServer, serverKexInit.MACsClientServer)
if !ok {
result.w.MAC, err = findCommon("client to server MAC", clientKexInit.MACsClientServer, serverKexInit.MACsClientServer)
if err != nil {
return
}
result.r.MAC, ok = findCommonAlgorithm(clientKexInit.MACsServerClient, serverKexInit.MACsServerClient)
if !ok {
result.r.MAC, err = findCommon("server to client MAC", clientKexInit.MACsServerClient, serverKexInit.MACsServerClient)
if err != nil {
return
}
result.w.Compression, ok = findCommonAlgorithm(clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
if !ok {
result.w.Compression, err = findCommon("client to server compression", clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
if err != nil {
return
}
result.r.Compression, ok = findCommonAlgorithm(clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient)
if !ok {
result.r.Compression, err = findCommon("server to client compression", clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient)
if err != nil {
return
}
return result
return result, nil
}
// If rekeythreshold is too small, we can't make any progress sending

View file

@ -23,7 +23,6 @@ func (e *OpenChannelError) Error() string {
// ConnMetadata holds metadata for the connection.
type ConnMetadata interface {
// User returns the user ID for this connection.
// It is empty if no authentication is used.
User() string
// SessionID returns the sesson hash, also denoted by H.
@ -33,7 +32,7 @@ type ConnMetadata interface {
// into the session ID.
ClientVersion() []byte
// ServerVersion returns the client's version string as hashed
// ServerVersion returns the server's version string as hashed
// into the session ID.
ServerVersion() []byte

View file

@ -113,8 +113,7 @@ func ExampleNewServerConn() {
}
func ExampleDial() {
// An SSH client is represented with a ClientConn. Currently only
// the "password" authentication method is supported.
// An SSH client is represented with a ClientConn.
//
// To authenticate with the remote server you must pass at least one
// implementation of AuthMethod via the Auth field in ClientConfig.
@ -147,6 +146,39 @@ func ExampleDial() {
fmt.Println(b.String())
}
func ExamplePublicKeys() {
// A public key may be used to authenticate against the remote
// server by using an unencrypted PEM-encoded private key file.
//
// If you have an encrypted private key, the crypto/x509 package
// can be used to decrypt it.
key, err := ioutil.ReadFile("/home/user/.ssh/id_rsa")
if err != nil {
log.Fatalf("unable to read private key: %v", err)
}
// Create the Signer for this private key.
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
log.Fatalf("unable to parse private key: %v", err)
}
config := &ssh.ClientConfig{
User: "user",
Auth: []ssh.AuthMethod{
// Use the PublicKeys method for remote authentication.
ssh.PublicKeys(signer),
},
}
// Connect to the remote server and perform the SSH handshake.
client, err := ssh.Dial("tcp", "host.com:22", config)
if err != nil {
log.Fatalf("unable to connect: %v", err)
}
defer client.Close()
}
func ExampleClient_Listen() {
config := &ssh.ClientConfig{
User: "username",

View file

@ -29,25 +29,6 @@ type keyingTransport interface {
// direction will be effected if a msgNewKeys message is sent
// or received.
prepareKeyChange(*algorithms, *kexResult) error
// getSessionID returns the session ID. prepareKeyChange must
// have been called once.
getSessionID() []byte
}
// rekeyingTransport is the interface of handshakeTransport that we
// (internally) expose to ClientConn and ServerConn.
type rekeyingTransport interface {
packetConn
// requestKeyChange asks the remote side to change keys. All
// writes are blocked until the key change succeeds, which is
// signaled by reading a msgNewKeys.
requestKeyChange() error
// getSessionID returns the session ID. This is only valid
// after the first key change has completed.
getSessionID() []byte
}
// handshakeTransport implements rekeying on top of a keyingTransport
@ -59,7 +40,14 @@ type handshakeTransport struct {
serverVersion []byte
clientVersion []byte
hostKeys []Signer // If hostKeys are given, we are the server.
// hostKeys is non-empty if we are the server. In that case,
// it contains all host keys that can be used to sign the
// connection.
hostKeys []Signer
// hostKeyAlgorithms is non-empty if we are the client. In that case,
// we accept these key types from the server as host key.
hostKeyAlgorithms []string
// On read error, incoming is closed, and readError is set.
incoming chan []byte
@ -79,6 +67,9 @@ type handshakeTransport struct {
sentInitMsg *kexInitMsg
writtenSinceKex uint64
writeError error
// The session ID or nil if first kex did not complete yet.
sessionID []byte
}
func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion, serverVersion []byte) *handshakeTransport {
@ -98,6 +89,11 @@ func newClientTransport(conn keyingTransport, clientVersion, serverVersion []byt
t.dialAddress = dialAddr
t.remoteAddr = addr
t.hostKeyCallback = config.HostKeyCallback
if config.HostKeyAlgorithms != nil {
t.hostKeyAlgorithms = config.HostKeyAlgorithms
} else {
t.hostKeyAlgorithms = supportedHostKeyAlgos
}
go t.readLoop()
return t
}
@ -110,7 +106,7 @@ func newServerTransport(conn keyingTransport, clientVersion, serverVersion []byt
}
func (t *handshakeTransport) getSessionID() []byte {
return t.conn.getSessionID()
return t.sessionID
}
func (t *handshakeTransport) id() string {
@ -141,6 +137,14 @@ func (t *handshakeTransport) readLoop() {
}
t.incoming <- p
}
// If we can't read, declare the writing part dead too.
t.mu.Lock()
defer t.mu.Unlock()
if t.writeError == nil {
t.writeError = t.readError
}
t.cond.Broadcast()
}
func (t *handshakeTransport) readOnePacket() ([]byte, error) {
@ -157,15 +161,22 @@ func (t *handshakeTransport) readOnePacket() ([]byte, error) {
t.readSinceKex += uint64(len(p))
if debugHandshake {
msg, err := decode(p)
log.Printf("%s got %T %v (%v)", t.id(), msg, msg, err)
if p[0] == msgChannelData || p[0] == msgChannelExtendedData {
log.Printf("%s got data (packet %d bytes)", t.id(), len(p))
} else {
msg, err := decode(p)
log.Printf("%s got %T %v (%v)", t.id(), msg, msg, err)
}
}
if p[0] != msgKexInit {
return p, nil
}
err = t.enterKeyExchange(p)
t.mu.Lock()
firstKex := t.sessionID == nil
err = t.enterKeyExchangeLocked(p)
if err != nil {
// drop connection
t.conn.Close()
@ -173,7 +184,7 @@ func (t *handshakeTransport) readOnePacket() ([]byte, error) {
}
if debugHandshake {
log.Printf("%s exited key exchange, err %v", t.id(), err)
log.Printf("%s exited key exchange (first %v), err %v", t.id(), firstKex, err)
}
// Unblock writers.
@ -188,28 +199,69 @@ func (t *handshakeTransport) readOnePacket() ([]byte, error) {
}
t.readSinceKex = 0
return []byte{msgNewKeys}, nil
// By default, a key exchange is hidden from higher layers by
// translating it into msgIgnore.
successPacket := []byte{msgIgnore}
if firstKex {
// sendKexInit() for the first kex waits for
// msgNewKeys so the authentication process is
// guaranteed to happen over an encrypted transport.
successPacket = []byte{msgNewKeys}
}
return successPacket, nil
}
// keyChangeCategory describes whether a key exchange is the first on a
// connection, or a subsequent one.
type keyChangeCategory bool
const (
firstKeyExchange keyChangeCategory = true
subsequentKeyExchange keyChangeCategory = false
)
// sendKexInit sends a key change message, and returns the message
// that was sent. After initiating the key change, all writes will be
// blocked until the change is done, and a failed key change will
// close the underlying transport. This function is safe for
// concurrent use by multiple goroutines.
func (t *handshakeTransport) sendKexInit() (*kexInitMsg, []byte, error) {
func (t *handshakeTransport) sendKexInit(isFirst keyChangeCategory) error {
var err error
t.mu.Lock()
defer t.mu.Unlock()
return t.sendKexInitLocked()
// If this is the initial key change, but we already have a sessionID,
// then do nothing because the key exchange has already completed
// asynchronously.
if !isFirst || t.sessionID == nil {
_, _, err = t.sendKexInitLocked(isFirst)
}
t.mu.Unlock()
if err != nil {
return err
}
if isFirst {
if packet, err := t.readPacket(); err != nil {
return err
} else if packet[0] != msgNewKeys {
return unexpectedMessageError(msgNewKeys, packet[0])
}
}
return nil
}
func (t *handshakeTransport) requestInitialKeyChange() error {
return t.sendKexInit(firstKeyExchange)
}
func (t *handshakeTransport) requestKeyChange() error {
_, _, err := t.sendKexInit()
return err
return t.sendKexInit(subsequentKeyExchange)
}
// sendKexInitLocked sends a key change message. t.mu must be locked
// while this happens.
func (t *handshakeTransport) sendKexInitLocked() (*kexInitMsg, []byte, error) {
func (t *handshakeTransport) sendKexInitLocked(isFirst keyChangeCategory) (*kexInitMsg, []byte, error) {
// kexInits may be sent either in response to the other side,
// or because our side wants to initiate a key change, so we
// may have already sent a kexInit. In that case, don't send a
@ -217,6 +269,7 @@ func (t *handshakeTransport) sendKexInitLocked() (*kexInitMsg, []byte, error) {
if t.sentInitMsg != nil {
return t.sentInitMsg, t.sentInitPacket, nil
}
msg := &kexInitMsg{
KexAlgos: t.config.KeyExchanges,
CiphersClientServer: t.config.Ciphers,
@ -234,7 +287,7 @@ func (t *handshakeTransport) sendKexInitLocked() (*kexInitMsg, []byte, error) {
msg.ServerHostKeyAlgos, k.PublicKey().Type())
}
} else {
msg.ServerHostKeyAlgos = supportedHostKeyAlgos
msg.ServerHostKeyAlgos = t.hostKeyAlgorithms
}
packet := Marshal(msg)
@ -253,10 +306,12 @@ func (t *handshakeTransport) sendKexInitLocked() (*kexInitMsg, []byte, error) {
func (t *handshakeTransport) writePacket(p []byte) error {
t.mu.Lock()
defer t.mu.Unlock()
if t.writtenSinceKex > t.config.RekeyThreshold {
t.sendKexInitLocked()
t.sendKexInitLocked(subsequentKeyExchange)
}
for t.sentInitMsg != nil {
for t.sentInitMsg != nil && t.writeError == nil {
t.cond.Wait()
}
if t.writeError != nil {
@ -264,29 +319,26 @@ func (t *handshakeTransport) writePacket(p []byte) error {
}
t.writtenSinceKex += uint64(len(p))
var err error
switch p[0] {
case msgKexInit:
err = errors.New("ssh: only handshakeTransport can send kexInit")
return errors.New("ssh: only handshakeTransport can send kexInit")
case msgNewKeys:
err = errors.New("ssh: only handshakeTransport can send newKeys")
return errors.New("ssh: only handshakeTransport can send newKeys")
default:
err = t.conn.writePacket(p)
return t.conn.writePacket(p)
}
t.mu.Unlock()
return err
}
func (t *handshakeTransport) Close() error {
return t.conn.Close()
}
// enterKeyExchange runs the key exchange.
func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
// enterKeyExchange runs the key exchange. t.mu must be held while running this.
func (t *handshakeTransport) enterKeyExchangeLocked(otherInitPacket []byte) error {
if debugHandshake {
log.Printf("%s entered key exchange", t.id())
}
myInit, myInitPacket, err := t.sendKexInit()
myInit, myInitPacket, err := t.sendKexInitLocked(subsequentKeyExchange)
if err != nil {
return err
}
@ -313,9 +365,9 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
magics.serverKexInit = otherInitPacket
}
algs := findAgreedAlgorithms(clientInit, serverInit)
if algs == nil {
return errors.New("ssh: no common algorithms")
algs, err := findAgreedAlgorithms(clientInit, serverInit)
if err != nil {
return err
}
// We don't send FirstKexFollows, but we handle receiving it.
@ -343,6 +395,11 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
return err
}
if t.sessionID == nil {
t.sessionID = result.H
}
result.SessionID = t.sessionID
t.conn.prepareKeyChange(algs, result)
if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil {
return err
@ -352,6 +409,7 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
} else if packet[0] != msgNewKeys {
return unexpectedMessageError(msgNewKeys, packet[0])
}
return nil
}

View file

@ -7,8 +7,13 @@ package ssh
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"net"
"reflect"
"runtime"
"strings"
"sync"
"testing"
)
@ -68,6 +73,7 @@ func handshakePair(clientConf *ClientConfig, addr string) (client *handshakeTran
serverConf := &ServerConfig{}
serverConf.AddHostKey(testSigners["ecdsa"])
serverConf.AddHostKey(testSigners["rsa"])
serverConf.SetDefaults()
server = newServerTransport(trS, v, v, serverConf)
@ -75,6 +81,9 @@ func handshakePair(clientConf *ClientConfig, addr string) (client *handshakeTran
}
func TestHandshakeBasic(t *testing.T) {
if runtime.GOOS == "plan9" {
t.Skip("see golang.org/issue/7237")
}
checker := &testChecker{}
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr")
if err != nil {
@ -95,7 +104,7 @@ func TestHandshakeBasic(t *testing.T) {
}
if i == 5 {
// halfway through, we request a key change.
_, _, err := trC.sendKexInit()
err := trC.sendKexInit(subsequentKeyExchange)
if err != nil {
t.Fatalf("sendKexInit: %v", err)
}
@ -152,7 +161,7 @@ func TestHandshakeError(t *testing.T) {
}
// Now request a key change.
_, _, err = trC.sendKexInit()
err = trC.sendKexInit(subsequentKeyExchange)
if err != nil {
t.Errorf("sendKexInit: %v", err)
}
@ -175,6 +184,28 @@ func TestHandshakeError(t *testing.T) {
}
}
func TestForceFirstKex(t *testing.T) {
checker := &testChecker{}
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr")
if err != nil {
t.Fatalf("handshakePair: %v", err)
}
defer trC.Close()
defer trS.Close()
trC.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth}))
// We setup the initial key exchange, but the remote side
// tries to send serviceRequestMsg in cleartext, which is
// disallowed.
err = trS.sendKexInit(firstKeyExchange)
if err == nil {
t.Errorf("server first kex init should reject unexpected packet")
}
}
func TestHandshakeTwice(t *testing.T) {
checker := &testChecker{}
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr")
@ -185,18 +216,25 @@ func TestHandshakeTwice(t *testing.T) {
defer trC.Close()
defer trS.Close()
// Both sides should ask for the first key exchange first.
err = trS.sendKexInit(firstKeyExchange)
if err != nil {
t.Errorf("server sendKexInit: %v", err)
}
err = trC.sendKexInit(firstKeyExchange)
if err != nil {
t.Errorf("client sendKexInit: %v", err)
}
sent := 0
// send a packet
packet := make([]byte, 5)
packet[0] = msgRequestSuccess
if err := trC.writePacket(packet); err != nil {
t.Errorf("writePacket: %v", err)
}
// Now request a key change.
_, _, err = trC.sendKexInit()
if err != nil {
t.Errorf("sendKexInit: %v", err)
}
sent++
// Send another packet. Use a fresh one, since writePacket destroys.
packet = make([]byte, 5)
@ -204,9 +242,10 @@ func TestHandshakeTwice(t *testing.T) {
if err := trC.writePacket(packet); err != nil {
t.Errorf("writePacket: %v", err)
}
sent++
// 2nd key change.
_, _, err = trC.sendKexInit()
err = trC.sendKexInit(subsequentKeyExchange)
if err != nil {
t.Errorf("sendKexInit: %v", err)
}
@ -216,17 +255,15 @@ func TestHandshakeTwice(t *testing.T) {
if err := trC.writePacket(packet); err != nil {
t.Errorf("writePacket: %v", err)
}
sent++
packet = make([]byte, 5)
packet[0] = msgRequestSuccess
for i := 0; i < 5; i++ {
for i := 0; i < sent; i++ {
msg, err := trS.readPacket()
if err != nil {
t.Fatalf("server closed too soon: %v", err)
}
if msg[0] == msgNewKeys {
continue
}
if bytes.Compare(msg, packet) != 0 {
t.Errorf("packet %d: got %q want %q", i, msg, packet)
@ -309,3 +346,141 @@ func TestHandshakeAutoRekeyRead(t *testing.T) {
<-sync.called
}
// errorKeyingTransport generates errors after a given number of
// read/write operations.
type errorKeyingTransport struct {
packetConn
readLeft, writeLeft int
}
func (n *errorKeyingTransport) prepareKeyChange(*algorithms, *kexResult) error {
return nil
}
func (n *errorKeyingTransport) getSessionID() []byte {
return nil
}
func (n *errorKeyingTransport) writePacket(packet []byte) error {
if n.writeLeft == 0 {
n.Close()
return errors.New("barf")
}
n.writeLeft--
return n.packetConn.writePacket(packet)
}
func (n *errorKeyingTransport) readPacket() ([]byte, error) {
if n.readLeft == 0 {
n.Close()
return nil, errors.New("barf")
}
n.readLeft--
return n.packetConn.readPacket()
}
func TestHandshakeErrorHandlingRead(t *testing.T) {
for i := 0; i < 20; i++ {
testHandshakeErrorHandlingN(t, i, -1)
}
}
func TestHandshakeErrorHandlingWrite(t *testing.T) {
for i := 0; i < 20; i++ {
testHandshakeErrorHandlingN(t, -1, i)
}
}
// testHandshakeErrorHandlingN runs handshakes, injecting errors. If
// handshakeTransport deadlocks, the go runtime will detect it and
// panic.
func testHandshakeErrorHandlingN(t *testing.T, readLimit, writeLimit int) {
msg := Marshal(&serviceRequestMsg{strings.Repeat("x", int(minRekeyThreshold)/4)})
a, b := memPipe()
defer a.Close()
defer b.Close()
key := testSigners["ecdsa"]
serverConf := Config{RekeyThreshold: minRekeyThreshold}
serverConf.SetDefaults()
serverConn := newHandshakeTransport(&errorKeyingTransport{a, readLimit, writeLimit}, &serverConf, []byte{'a'}, []byte{'b'})
serverConn.hostKeys = []Signer{key}
go serverConn.readLoop()
clientConf := Config{RekeyThreshold: 10 * minRekeyThreshold}
clientConf.SetDefaults()
clientConn := newHandshakeTransport(&errorKeyingTransport{b, -1, -1}, &clientConf, []byte{'a'}, []byte{'b'})
clientConn.hostKeyAlgorithms = []string{key.PublicKey().Type()}
go clientConn.readLoop()
var wg sync.WaitGroup
wg.Add(4)
for _, hs := range []packetConn{serverConn, clientConn} {
go func(c packetConn) {
for {
err := c.writePacket(msg)
if err != nil {
break
}
}
wg.Done()
}(hs)
go func(c packetConn) {
for {
_, err := c.readPacket()
if err != nil {
break
}
}
wg.Done()
}(hs)
}
wg.Wait()
}
func TestDisconnect(t *testing.T) {
if runtime.GOOS == "plan9" {
t.Skip("see golang.org/issue/7237")
}
checker := &testChecker{}
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr")
if err != nil {
t.Fatalf("handshakePair: %v", err)
}
defer trC.Close()
defer trS.Close()
trC.writePacket([]byte{msgRequestSuccess, 0, 0})
errMsg := &disconnectMsg{
Reason: 42,
Message: "such is life",
}
trC.writePacket(Marshal(errMsg))
trC.writePacket([]byte{msgRequestSuccess, 0, 0})
packet, err := trS.readPacket()
if err != nil {
t.Fatalf("readPacket 1: %v", err)
}
if packet[0] != msgRequestSuccess {
t.Errorf("got packet %v, want packet type %d", packet, msgRequestSuccess)
}
_, err = trS.readPacket()
if err == nil {
t.Errorf("readPacket 2 succeeded")
} else if !reflect.DeepEqual(err, errMsg) {
t.Errorf("got error %#v, want %#v", err, errMsg)
}
_, err = trS.readPacket()
if err == nil {
t.Errorf("readPacket 3 succeeded")
}
}

View file

@ -9,17 +9,21 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/subtle"
"errors"
"io"
"math/big"
"golang.org/x/crypto/curve25519"
)
const (
kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
kexAlgoECDH256 = "ecdh-sha2-nistp256"
kexAlgoECDH384 = "ecdh-sha2-nistp384"
kexAlgoECDH521 = "ecdh-sha2-nistp521"
kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
kexAlgoECDH256 = "ecdh-sha2-nistp256"
kexAlgoECDH384 = "ecdh-sha2-nistp384"
kexAlgoECDH521 = "ecdh-sha2-nistp521"
kexAlgoCurve25519SHA256 = "curve25519-sha256@libssh.org"
)
// kexResult captures the outcome of a key exchange.
@ -42,7 +46,7 @@ type kexResult struct {
Hash crypto.Hash
// The session ID, which is the first H computed. This is used
// to signal data inside transport.
// to derive key material inside the transport.
SessionID []byte
}
@ -383,4 +387,140 @@ func init() {
kexAlgoMap[kexAlgoECDH521] = &ecdh{elliptic.P521()}
kexAlgoMap[kexAlgoECDH384] = &ecdh{elliptic.P384()}
kexAlgoMap[kexAlgoECDH256] = &ecdh{elliptic.P256()}
kexAlgoMap[kexAlgoCurve25519SHA256] = &curve25519sha256{}
}
// curve25519sha256 implements the curve25519-sha256@libssh.org key
// agreement protocol, as described in
// https://git.libssh.org/projects/libssh.git/tree/doc/curve25519-sha256@libssh.org.txt
type curve25519sha256 struct{}
type curve25519KeyPair struct {
priv [32]byte
pub [32]byte
}
func (kp *curve25519KeyPair) generate(rand io.Reader) error {
if _, err := io.ReadFull(rand, kp.priv[:]); err != nil {
return err
}
curve25519.ScalarBaseMult(&kp.pub, &kp.priv)
return nil
}
// curve25519Zeros is just an array of 32 zero bytes so that we have something
// convenient to compare against in order to reject curve25519 points with the
// wrong order.
var curve25519Zeros [32]byte
func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) {
var kp curve25519KeyPair
if err := kp.generate(rand); err != nil {
return nil, err
}
if err := c.writePacket(Marshal(&kexECDHInitMsg{kp.pub[:]})); err != nil {
return nil, err
}
packet, err := c.readPacket()
if err != nil {
return nil, err
}
var reply kexECDHReplyMsg
if err = Unmarshal(packet, &reply); err != nil {
return nil, err
}
if len(reply.EphemeralPubKey) != 32 {
return nil, errors.New("ssh: peer's curve25519 public value has wrong length")
}
var servPub, secret [32]byte
copy(servPub[:], reply.EphemeralPubKey)
curve25519.ScalarMult(&secret, &kp.priv, &servPub)
if subtle.ConstantTimeCompare(secret[:], curve25519Zeros[:]) == 1 {
return nil, errors.New("ssh: peer's curve25519 public value has wrong order")
}
h := crypto.SHA256.New()
magics.write(h)
writeString(h, reply.HostKey)
writeString(h, kp.pub[:])
writeString(h, reply.EphemeralPubKey)
kInt := new(big.Int).SetBytes(secret[:])
K := make([]byte, intLength(kInt))
marshalInt(K, kInt)
h.Write(K)
return &kexResult{
H: h.Sum(nil),
K: K,
HostKey: reply.HostKey,
Signature: reply.Signature,
Hash: crypto.SHA256,
}, nil
}
func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
packet, err := c.readPacket()
if err != nil {
return
}
var kexInit kexECDHInitMsg
if err = Unmarshal(packet, &kexInit); err != nil {
return
}
if len(kexInit.ClientPubKey) != 32 {
return nil, errors.New("ssh: peer's curve25519 public value has wrong length")
}
var kp curve25519KeyPair
if err := kp.generate(rand); err != nil {
return nil, err
}
var clientPub, secret [32]byte
copy(clientPub[:], kexInit.ClientPubKey)
curve25519.ScalarMult(&secret, &kp.priv, &clientPub)
if subtle.ConstantTimeCompare(secret[:], curve25519Zeros[:]) == 1 {
return nil, errors.New("ssh: peer's curve25519 public value has wrong order")
}
hostKeyBytes := priv.PublicKey().Marshal()
h := crypto.SHA256.New()
magics.write(h)
writeString(h, hostKeyBytes)
writeString(h, kexInit.ClientPubKey)
writeString(h, kp.pub[:])
kInt := new(big.Int).SetBytes(secret[:])
K := make([]byte, intLength(kInt))
marshalInt(K, kInt)
h.Write(K)
H := h.Sum(nil)
sig, err := signAndMarshal(priv, rand, H)
if err != nil {
return nil, err
}
reply := kexECDHReplyMsg{
EphemeralPubKey: kp.pub[:],
HostKey: hostKeyBytes,
Signature: sig,
}
if err := c.writePacket(Marshal(&reply)); err != nil {
return nil, err
}
return &kexResult{
H: H,
K: K,
HostKey: hostKeyBytes,
Signature: sig,
Hash: crypto.SHA256,
}, nil
}

Some files were not shown because too many files have changed in this diff Show more