Compare commits

..

8 Commits

Author SHA1 Message Date
Alexander Neumann
c1f047bcc6 doc: Add paragraph about mkdocs 2016-02-21 13:08:10 +01:00
Alexander Neumann
f2b54348c4 doc: Add paragraph about documentation version 2016-02-21 13:08:10 +01:00
Alexander Neumann
fea113da98 Manual: Correct headings, add section about debug 2016-02-21 13:08:09 +01:00
Alexander Neumann
cbd77a7f61 Update manual 2016-02-21 13:08:09 +01:00
Alexander Neumann
f0790c134c Remove obsolete structure image 2016-02-21 13:08:09 +01:00
Alexander Neumann
385c73697d Add something to the front page 2016-02-21 13:08:08 +01:00
Alexander Neumann
f4a0fd299e Manual: Fix shell blocks and ToC 2016-02-21 13:07:40 +01:00
Alexander Neumann
fb5b90f308 Add mkdocs for readthedocs.org 2016-02-21 13:07:39 +01:00
1271 changed files with 35801 additions and 246385 deletions

1
.envrc
View File

@@ -1 +0,0 @@
GOPATH=$PWD:$PWD/vendor

View File

@@ -1,25 +0,0 @@
<!--
NOTE: Not filling out the issue template needs a good reason, otherwise the
issue may be closed instantly! Please take the time to help us debugging the
problem by collecting information, even if it seems irrelevant to you. Thanks!
-->
## Output of `restic version`
## How did you start restic exactly? (Include the complete command line)
## What backend/server/service did you use?
## Expected behavior
## Actual behavior
## Steps to reproduce the behavior
## Do you have any idea what may have caused this?

10
.gitignore vendored
View File

@@ -1,6 +1,8 @@
/pkg
/bin
/.gopath
/restic
/restic.debug
/dirdiff
cmd/dirdiff/dirdiff
cmd/gentestdata/gentestdata
cmd/restic/restic
/.vagrant
/vendor/pkg
/doc/_build

View File

@@ -1,2 +0,0 @@
go:
enabled: true

View File

@@ -2,39 +2,14 @@ language: go
sudo: false
go:
- 1.7.6
- 1.8.3
- tip
- 1.3.3
- 1.4.2
- 1.5
os:
- linux
- osx
env:
matrix:
RESTIC_TEST_FUSE=0
matrix:
exclude:
- os: osx
go: 1.7.6
- os: osx
go: tip
- os: linux
go: 1.8.3
include:
- os: linux
go: 1.8.3
sudo: true
env:
RESTIC_TEST_FUSE=1
allow_failures:
- go: tip
branches:
only:
- master
notifications:
irc:
channels:
@@ -47,10 +22,11 @@ install:
- go version
- export GOBIN="$GOPATH/bin"
- export PATH="$PATH:$GOBIN"
- export GOPATH="$GOPATH:${TRAVIS_BUILD_DIR}/Godeps/_workspace"
- go env
script:
- go run run_integration_tests.go
after_success:
- bash <(curl -s https://codecov.io/bash) -f all.cov
- goveralls -coverprofile=all.cov -service=travis-ci -repotoken "$COVERALLS_TOKEN" || true

View File

@@ -1,126 +0,0 @@
This file describes changes relevant to all users that are made in each
released version of restic from the perspective of the user.
Important Changes in 0.X.Y
==========================
* New "swift" backend: A new backend for the OpenStack Swift cloud storage
protocol has been added, https://wiki.openstack.org/wiki/Swift
https://github.com/restic/restic/pull/975
https://github.com/restic/restic/pull/648
* New "b2" backend: A new backend for Backblaze B2 cloud storage
service has been added, https://www.backblaze.com
https://github.com/restic/restic/issues/512
https://github.com/restic/restic/pull/978
* Improved performance for the `find` command: Restic recognizes paths it has
already checked for the files in question, so the number of backend requests
is reduced a lot.
https://github.com/restic/restic/issues/989
https://github.com/restic/restic/pull/993
* Improved performance for the fuse mount: Listing directories which contain
large files now is significantly faster.
https://github.com/restic/restic/pull/998
* The default layout for the s3 backend is now `default` (instead of
`s3legacy`). Also, there's a new `migrate` command to convert an existing
repo, it can be run like this: `restic migrate s3_layout`
https://github.com/restic/restic/issues/965
https://github.com/restic/restic/pull/1004
* The fuse mount now has two more directories: `tags` contains a subdir for
each tag, which in turn contains only the snapshots that have this tag. The
subdir `hosts` contains a subdir for each host that has a snapshot, and the
subdir contains the snapshots for that host.
https://github.com/restic/restic/issues/636
https://github.com/restic/restic/pull/1050
Small changes
-------------
* For the s3 backend we're back to using the high-level API the s3 client
library for uploading data, a few users reported dropped connections (which
the library will automatically retry now).
https://github.com/restic/restic/issues/1013
https://github.com/restic/restic/issues/1023
https://github.com/restic/restic/pull/1025
* The `prune` command has been improved and will now remove invalid pack
files, for example files that have not been uploaded completely because a
backup was interrupted.
https://github.com/restic/restic/issues/1029
https://github.com/restic/restic/pull/1036
* restic now tries to detect when an invalid/unknown backend is used and
returns an error message.
https://github.com/restic/restic/issues/1021
https://github.com/restic/restic/pull/1070
Important Changes in 0.6.1
==========================
This is mostly a bugfix release and only contains small changes:
* We've fixed a bug where `rebuild-index` would corrupt the index when used
with the s3 backend together with the `default` layout. This is not the
default setting.
* Backends based on HTTP now allow several idle connections in parallel. This
is especially important for the REST backend, which (when used with a local
server) may create a lot connections and exhaust available ports quickly.
https://github.com/restic/restic/issues/985
https://github.com/restic/restic/pull/986
* Regular status report: We've removed the status report that was printed
every 10 seconds when restic is run non-interactively. You can still force
reporting the current status by sending a `USR1` signal to the process.
https://github.com/restic/restic/pull/974
* The `build.go` now strips the temporary directory used for compilation from
the binary. This is the first step in enabling reproducible builds.
https://github.com/restic/restic/pull/981
Important Changes in 0.6.0
==========================
Consistent forget policy
------------------------
The `forget` command was corrected to be more consistent in which snapshots are
to be forgotten. It is possible that the new code removes more snapshots than
before, so please review what would be deleted by using the `--dry-run` option.
https://github.com/restic/restic/pull/957
https://github.com/restic/restic/issues/953
Unified repository layout
-------------------------
Up to now the s3 backend used a special repository layout. We've decided to
unify the repository layout and implemented the default layout also for the s3
backend. For creating a new repository on s3 with the default layout, use
`restic -o s3.layout=default init`. For further commands the option is not
necessary any more, restic will automatically detect the correct layout to use.
A future version will switch to the default layout for new repositories.
https://github.com/restic/restic/pull/966
https://github.com/restic/restic/issues/965
Memory and time improvements for the s3 backend
-----------------------------------------------
We've updated the library used for accessing s3, switched to using a lower
level API and added caching for some requests. This lead to a decrease in
memory usage and a great speedup. In addition, we added benchmark functions for
all backends, so we can track improvements over time. The Continuous
Integration test service we're using (Travis) now runs the s3 backend tests not
only against a Minio server, but also against the Amazon s3 live service, so we
should be notified of any regressions much sooner.
https://github.com/restic/restic/pull/962
https://github.com/restic/restic/pull/960
https://github.com/restic/restic/pull/946
https://github.com/restic/restic/pull/938
https://github.com/restic/restic/pull/883

View File

@@ -3,10 +3,7 @@ This document describes the way you can contribute to the restic project.
Ways to Help Out
================
Thank you for your contribution! Please **open an issue first** (or add a
comment to an existing issue) if you plan to work on any code or add a new
feature. This way, duplicate work is prevented and we can discuss your ideas
and design first.
Thank you for your contribution!
There are several ways you can help us out. First of all code contributions and
bug fixes are most welcome. However even "minor" details as fixing spelling
@@ -28,65 +25,43 @@ those tagged
[minor complexity](https://github.com/restic/restic/labels/minor%20complexity).
Reporting Bugs
==============
You've found a bug? Thanks for letting us know so we can fix it! It is a good
idea to describe in detail how to reproduce the bug (when you know how), what
environment was used and so on. Please tell us at least the following things:
* What's the version of restic you used? Please include the output of
`restic version` in your bug report.
* What commands did you execute to get to where the bug occurred?
* What did you expect?
* What happened instead?
* Are you aware of a way to reproduce the bug?
Remember, the easier it is for us to reproduce the bug, the earlier it will be
corrected!
In addition, you can compile restic with debug support by running
`go run build.go -tags debug` and instructing it to create a debug log by
setting the environment variable `DEBUG_LOG` to a file, e.g. like this:
$ export DEBUG_LOG=/tmp/restic-debug.log
$ restic backup ~/work
Please be aware that the debug log file will contain potentially sensitive
things like file and directory names, so please either redact it before
uploading it somewhere or post only the parts that are really relevant.
Development Environment
=======================
For development you need the build tool [`gb`](https://getgb.io), it can be
installed by running the following command:
For development, it is recommended to check out the restic repository within a
`GOPATH`, an introductory text is
["How to Write Go Code"](https://golang.org/doc/code.html). It is recommended
to have a working directory, we're using `~/work/restic` in the following. This
directory mainly contains the directory `src`, where the source code is stored.
$ go get github.com/constabulary/gb/...
The repository contains two directories with code: `src/` contains the code
written for restic, whereas `vendor/` contains copies of libraries restic
depends on. The libraries are managed with the `gb vendor` command.
Just clone the repository, `cd` to it and run `gb build` to build the binary:
First, create the necessary directory structure and clone the restic repository
to the correct location:
$ mkdir --parents ~/work/restic/src/github.com/restic
$ cd ~/work/restic/src/github.com/restic
$ git clone https://github.com/restic/restic
$ cd restic
$ gb build
[...]
$ bin/restic version
restic compiled manually
compiled at unknown time with go1.7
Now we're in the main directory of the restic repository. The last step is to
set the environment variable `$GOPATH` to the correct value:
$ export GOPATH=~/work/restic:~/work/restic/src/github.com/restic/restic/Godeps/_workspace
The following commands can be used to run all the tests:
$ gb test
$ go test ./...
ok github.com/restic/restic 8.174s
[...]
If you want to run your tests on Linux, OpenBSD or FreeBSD, you can use
[vagrant](https://www.vagrantup.com/) with the provided `Vagrantfile` to
The restic binary can be built from the directory `cmd/restic` this way:
$ cd cmd/restic
$ go build
$ ./restic version
restic compiled manually on go1.4.2
if you want to run your tests on Linux, OpenBSD or FreeBSD, you can use
[vagrant](https://www.vagrantup.com/) with the proveded `Vagrantfile` to
quickly set up VMs and run the tests, e.g.:
$ vagrant up freebsd
@@ -95,16 +70,6 @@ quickly set up VMs and run the tests, e.g.:
$ vagrant ssh freebsd -c 'cd restic/restic; go test -v ./...'
[...]
The default `go` tool can also be used by setting the environment variable
`GOPATH` to the following value while being in the top level directory in the
git repository:
$ export GOPATH=$PWD:$PWD/vendor
The file `.envrc` allows automatic `GOPATH` configuration with
[direnv](https://direnv.net/), inspect the file and then allow automatic
configuration by running `direnv allow`.
Providing Patches
=================
@@ -113,38 +78,23 @@ get it into the project! The workflow we're using is also described on the
[GitHub Flow](https://guides.github.com/introduction/flow/) website, it boils
down to the following steps:
0. If you want to work on something, please add a comment to the issue on
GitHub. For a new feature, please add an issue before starting to work on
it, so that duplicate work is prevented.
1. First we would kindly ask you to fork our project on GitHub if you haven't
done so already.
2. Clone the repository locally and create a new branch. If you are working on
the code itself, please set up the development environment as described in
the previous section.
the previous section and instead of cloning add your fork on GitHub as a
remote to the clone of the restic repository.
3. Then commit your changes as fine grained as possible, as smaller patches,
that handle one and only one issue are easier to discuss and merge.
4. Push the new branch with your changes to your fork of the repository.
5. Create a pull request by visiting the GitHub website, it will guide you
through the process.
6. You will receive comments on your code and the feature or bug that they
address. Maybe you need to rework some minor things, in this case push new
commits to the branch you created for the pull request, they will be
automatically added to the pull request.
7. If your pull request changes anything that users should be aware of (a
bugfix, a new feature, ...) please add an entry to the file
['CHANGELOG.md'](CHANGELOG.md). It will be used in the announcement of the
next stable release. While writing, ask yourself: If I were the user, what
would I need to be aware of with this change.
8. Once your code looks good and passes all the tests, we'll merge it. Thanks
a low for your contribution!
7. Once your code looks good, we'll merge it. Thanks a low for your
contribution!
Please provide the patches for each bug or feature in a separate branch and
open up a pull request for each.
@@ -159,14 +109,6 @@ in the project root directory before committing. Installing the script
pre-commit hook checks formatting before committing automatically, just copy
this script to `.git/hooks/pre-commit`.
For each pull request, several different systems run the integration tests on
Linux, OS X and Windows. We won't merge any code that does not pass all tests
for all systems, so when a tests fails, try to find out what's wrong and fix
it. If you need help on this, please leave a comment in the pull request, and
we'll be glad to assist. Having a PR with failing integration tests is nothing
to be ashamed of. In contrast, that happens regularly for all of us. That's
what the tests are there for.
Git Commits
-----------

View File

@@ -1,57 +0,0 @@
# This Dockerfiles configures a container that is similar to the Travis CI
# environment and can be used to run tests locally.
#
# build the image:
# docker build -t restic/test .
#
# run all tests and cross-compile restic:
# docker run --rm -v $PWD:/home/travis/restic restic/test go run run_integration_tests.go -minio minio
#
# run interactively:
# docker run --interactive --tty --rm -v $PWD:/home/travis/restic restic/test /bin/bash
#
# run a subset of tests:
# docker run --rm -v $PWD:/home/travis/restic restic/test gb test -v ./backend
#
# build the image for an older version of Go:
# docker build --build-arg GOVERSION=1.6.4 -t restic/test:go1.6.4 .
FROM ubuntu:14.04
ARG GOVERSION=1.8.3
ARG GOARCH=amd64
# install dependencies
RUN apt-get update
RUN apt-get install -y --no-install-recommends ca-certificates wget git build-essential openssh-server
# add and configure user
ENV HOME /home/travis
RUN useradd -m -d $HOME -s /bin/bash travis
# run everything below as user travis
USER travis
WORKDIR $HOME
# download and install Go
RUN wget -q -O /tmp/go.tar.gz https://storage.googleapis.com/golang/go${GOVERSION}.linux-${GOARCH}.tar.gz
RUN tar xf /tmp/go.tar.gz && rm -f /tmp/go.tar.gz
ENV GOROOT $HOME/go
ENV GOPATH $HOME/gopath
ENV PATH $PATH:$GOROOT/bin:$GOPATH/bin:$HOME/bin
RUN mkdir -p $HOME/restic
# pre-install tools, this speeds up running the tests itself
RUN go get github.com/constabulary/gb/...
RUN go get golang.org/x/tools/cmd/cover
RUN go get github.com/mitchellh/gox
RUN go get github.com/pierrre/gotestcover
RUN mkdir $HOME/bin \
&& wget -q -O $HOME/bin/minio https://dl.minio.io/server/minio/release/linux-${GOARCH}/minio \
&& chmod +x $HOME/bin/minio
# set TRAVIS_BUILD_DIR for integration script
ENV TRAVIS_BUILD_DIR $HOME/restic
WORKDIR $HOME/restic

62
Godeps/Godeps.json generated Normal file
View File

@@ -0,0 +1,62 @@
{
"ImportPath": "github.com/restic/restic",
"GoVersion": "go1.4.2",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "bazil.org/fuse",
"Rev": "18419ee53958df28fcfc9490fe6123bd59e237bb"
},
{
"ImportPath": "github.com/jessevdk/go-flags",
"Comment": "v1-297-g1b89bf7",
"Rev": "1b89bf73cd2c3a911d7b2a279ab085c4a18cf539"
},
{
"ImportPath": "github.com/juju/errors",
"Rev": "4567a5e69fd3130ca0d89f69478e7ac025b67452"
},
{
"ImportPath": "github.com/kr/fs",
"Rev": "2788f0dbd16903de03cb8186e5c7d97b69ad387b"
},
{
"ImportPath": "github.com/mitchellh/goamz/aws",
"Rev": "caaaea8b30ee15616494ee68abd5d8ebbbef05cf"
},
{
"ImportPath": "github.com/mitchellh/goamz/s3",
"Rev": "caaaea8b30ee15616494ee68abd5d8ebbbef05cf"
},
{
"ImportPath": "github.com/pkg/sftp",
"Rev": "518aed2757a65cfa64d4b1b2baf08410f8b7a6bc"
},
{
"ImportPath": "github.com/restic/chunker",
"Rev": "e795b80f4c927ebcf2687ce18bcf1a39fee740b1"
},
{
"ImportPath": "github.com/vaughan0/go-ini",
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
},
{
"ImportPath": "golang.org/x/crypto/pbkdf2",
"Rev": "cc04154d65fb9296747569b107cfd05380b1ea3e"
},
{
"ImportPath": "golang.org/x/crypto/poly1305",
"Rev": "cc04154d65fb9296747569b107cfd05380b1ea3e"
},
{
"ImportPath": "golang.org/x/crypto/scrypt",
"Rev": "cc04154d65fb9296747569b107cfd05380b1ea3e"
},
{
"ImportPath": "golang.org/x/crypto/ssh",
"Rev": "cc04154d65fb9296747569b107cfd05380b1ea3e"
}
]
}

5
Godeps/Readme generated Normal file
View File

@@ -0,0 +1,5 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

2
Godeps/_workspace/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,2 @@
/pkg
/bin

2
Godeps/_workspace/src/bazil.org/fuse/.gitattributes generated vendored Normal file
View File

@@ -0,0 +1,2 @@
*.go filter=gofmt
*.cgo filter=gofmt

11
Godeps/_workspace/src/bazil.org/fuse/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,11 @@
*~
.#*
## the next line needs to start with a backslash to avoid looking like
## a comment
\#*#
.*.swp
*.test
/clockfs
/hellofs

4
Godeps/_workspace/src/bazil.org/fuse/doc/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,4 @@
/*.seq.svg
# not ignoring *.seq.png; we want those committed to the repo
# for embedding on Github

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -24,7 +24,16 @@ func usage() {
flag.PrintDefaults()
}
func run(mountpoint string) error {
func main() {
flag.Usage = usage
flag.Parse()
if flag.NArg() != 1 {
usage()
os.Exit(2)
}
mountpoint := flag.Arg(0)
c, err := fuse.Mount(
mountpoint,
fuse.FSName("clock"),
@@ -33,14 +42,10 @@ func run(mountpoint string) error {
fuse.VolumeName("Clock filesystem"),
)
if err != nil {
return err
log.Fatal(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
@@ -56,28 +61,12 @@ func run(mountpoint string) error {
// This goroutine never exits. That's fine for this example.
go filesys.clockFile.update()
if err := srv.Serve(filesys); err != nil {
return err
log.Fatal(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"
)
func usage() {
var Usage = func() {
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 @@
package fstestutil

View File

@@ -50,15 +50,13 @@ func (mnt *Mount) Close() {
os.Remove(mnt.Dir)
}
// 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.
// 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 MountedFunc(fn func(*Mount) fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
func Mounted(filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
dir, err := ioutil.TempDir("", "fusetest")
if err != nil {
return nil, err
@@ -77,7 +75,6 @@ func MountedFunc(fn func(*Mount) fs.FS, conf *fs.Config, options ...fuse.MountOp
Error: serveErr,
done: done,
}
filesys := fn(mnt)
go func() {
defer close(done)
serveErr <- server.Serve(filesys)
@@ -98,36 +95,6 @@ func MountedFunc(fn func(*Mount) fs.FS, conf *fs.Config, options ...fuse.MountOp
}
}
// 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 MountedFunc for usage.
//
// The debug log is not enabled by default. Use `-fuse.debug` or call
// DebugByDefault to enable.
func MountedFuncT(t testing.TB, fn func(*Mount) fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
if conf == nil {
conf = &fs.Config{}
}
if debug && conf.Debug == nil {
conf.Debug = func(msg interface{}) {
t.Logf("FUSE: %s", msg)
}
}
return MountedFunc(fn, conf, options...)
}
// MountedT mounts the filesystem at a temporary directory,
// directing it's debug log to the testing logger.
//
@@ -136,6 +103,13 @@ func MountedFuncT(t testing.TB, fn func(*Mount) fs.FS, conf *fs.Config, options
// 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...)
if conf == nil {
conf = &fs.Config{}
}
if debug && conf.Debug == nil {
conf.Debug = func(msg interface{}) {
t.Logf("FUSE: %s", msg)
}
}
return Mounted(filesys, conf, options...)
}

View File

@@ -1,4 +1,4 @@
package record // import "bazil.org/fuse/fs/fstestutil/record"
package record
import (
"sync"
@@ -382,28 +382,3 @@ 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

@@ -1,6 +1,6 @@
// FUSE service loop, for servers that wish to use it.
package fs // import "bazil.org/fuse/fs"
package fs
import (
"encoding/binary"
@@ -18,8 +18,6 @@ import (
)
import (
"bytes"
"bazil.org/fuse"
"bazil.org/fuse/fuseutil"
)
@@ -91,13 +89,6 @@ 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
}
@@ -114,7 +105,9 @@ 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, outside of Writes.
// the file. Not implementing Setattr causes writes to be unable
// to grow the file (except with OpenDirectIO, which bypasses that
// mechanism).
//
// req.Valid is a bitmask of what fields are actually being set.
// For example, the method should not change the mode of the file
@@ -304,17 +297,16 @@ type HandleReader interface {
}
type HandleWriter interface {
// Write requests to write data into the handle at the given offset.
// Store the amount of data written in resp.Size.
// Write requests to write data into the handle.
//
// 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.
//
// 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.
// 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).
Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error
}
@@ -330,13 +322,11 @@ type Config struct {
// See fuse.Debug for the rules that log functions must follow.
Debug func(msg interface{})
// Function to put things into context for processing the request.
// The returned context must have ctx as its parent.
// Function to create new contexts. If nil, use
// context.Background.
//
// Note that changing this may not affect existing calls to Serve.
//
// Must not retain req.
WithContext func(ctx context.Context, req fuse.Request) context.Context
GetContext func() context.Context
}
// New returns a new FUSE server ready to serve this kernel FUSE
@@ -352,11 +342,14 @@ func New(conn *fuse.Conn, config *Config) *Server {
}
if config != nil {
s.debug = config.Debug
s.context = config.WithContext
s.context = config.GetContext
}
if s.debug == nil {
s.debug = fuse.Debug
}
if s.context == nil {
s.context = context.Background
}
return s
}
@@ -364,7 +357,7 @@ type Server struct {
// set in New
conn *fuse.Conn
debug func(msg interface{})
context func(ctx context.Context, req fuse.Request) context.Context
context func() context.Context
// set once at Serve time
fs FS
@@ -501,7 +494,7 @@ func (c *Server) saveNode(inode uint64, node Node) (id fuse.NodeID, gen uint64)
}
sn.generation = c.nodeGen
c.nodeRef[node] = id
return id, sn.generation
return
}
func (c *Server) saveHandle(handle Handle, nodeID fuse.NodeID) (id fuse.HandleID) {
@@ -608,7 +601,7 @@ type logResponseHeader struct {
}
func (m logResponseHeader) String() string {
return fmt.Sprintf("ID=%v", m.ID)
return fmt.Sprintf("ID=%#x", m.ID)
}
type response struct {
@@ -633,21 +626,21 @@ func (r response) errstr() string {
func (r response) String() string {
switch {
case r.Errno != "" && r.Out != nil:
return fmt.Sprintf("-> [%v] %v error=%s", r.Request, r.Out, r.errstr())
return fmt.Sprintf("-> %s error=%s %s", r.Request, r.errstr(), r.Out)
case r.Errno != "":
return fmt.Sprintf("-> [%v] %s error=%s", r.Request, r.Op, r.errstr())
return fmt.Sprintf("-> %s error=%s", r.Request, r.errstr())
case r.Out != nil:
// make sure (seemingly) empty values are readable
switch r.Out.(type) {
case string:
return fmt.Sprintf("-> [%v] %s %q", r.Request, r.Op, r.Out)
return fmt.Sprintf("-> %s %q", r.Request, r.Out)
case []byte:
return fmt.Sprintf("-> [%v] %s [% x]", r.Request, r.Op, r.Out)
return fmt.Sprintf("-> %s [% x]", r.Request, r.Out)
default:
return fmt.Sprintf("-> [%v] %v", r.Request, r.Out)
return fmt.Sprintf("-> %s %s", r.Request, r.Out)
}
default:
return fmt.Sprintf("-> [%v] %s", r.Request, r.Op)
return fmt.Sprintf("-> %s", r.Request)
}
}
@@ -659,23 +652,20 @@ type notification struct {
}
func (n notification) String() string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "=> %s %v", n.Op, n.Node)
if n.Out != nil {
switch {
case n.Out != nil:
// make sure (seemingly) empty values are readable
switch n.Out.(type) {
case string:
fmt.Fprintf(&buf, " %q", n.Out)
return fmt.Sprintf("=> %s %d %q Err:%v", n.Op, n.Node, n.Out, n.Err)
case []byte:
fmt.Fprintf(&buf, " [% x]", n.Out)
return fmt.Sprintf("=> %s %d [% x] Err:%v", n.Op, n.Node, n.Out, n.Err)
default:
fmt.Fprintf(&buf, " %s", n.Out)
return fmt.Sprintf("=> %s %d %s Err:%v", n.Op, n.Node, n.Out, n.Err)
}
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 {
@@ -695,7 +685,7 @@ type logLinkRequestOldNodeNotFound struct {
}
func (m *logLinkRequestOldNodeNotFound) String() string {
return fmt.Sprintf("In LinkRequest (request %v), node %d not found", m.Request.Hdr().ID, m.In.OldNode)
return fmt.Sprintf("In LinkRequest (request %#x), node %d not found", m.Request.Hdr().ID, m.In.OldNode)
}
type renameNewDirNodeNotFound struct {
@@ -704,7 +694,7 @@ type renameNewDirNodeNotFound struct {
}
func (m *renameNewDirNodeNotFound) String() string {
return fmt.Sprintf("In RenameRequest (request %v), node %d not found", m.Request.Hdr().ID, m.In.NewDir)
return fmt.Sprintf("In RenameRequest (request %#x), node %d not found", m.Request.Hdr().ID, m.In.NewDir)
}
type handlerPanickedError struct {
@@ -727,52 +717,13 @@ 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(context.Background())
ctx, cancel := context.WithCancel(c.context())
defer cancel()
parentCtx := ctx
if c.context != nil {
ctx = c.context(ctx, r)
}
req := &serveRequest{Request: r, cancel: cancel}
@@ -849,7 +800,6 @@ func (c *Server) serve(r fuse.Request) {
c.meta.Unlock()
}
var responded bool
defer func() {
if rec := recover(); rec != nil {
const size = 1 << 16
@@ -863,132 +813,114 @@ 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.
return fuse.ENOSYS
done(fuse.ENOSYS)
r.RespondError(fuse.ENOSYS)
case *fuse.StatfsRequest:
s := &fuse.StatfsResponse{}
if fs, ok := c.fs.(FSStatfser); ok {
if err := fs.Statfs(ctx, r, s); err != nil {
return err
done(err)
r.RespondError(err)
break
}
}
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 {
return err
done(err)
r.RespondError(err)
break
}
} else {
if err := snode.attr(ctx, &s.Attr); err != nil {
return err
done(err)
r.RespondError(err)
break
}
}
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 {
return err
done(err)
r.RespondError(err)
break
}
done(s)
r.Respond(s)
break
}
if err := snode.attr(ctx, &s.Attr); err != nil {
return err
done(err)
r.RespondError(err)
break
}
done(s)
r.Respond(s)
return nil
case *fuse.SymlinkRequest:
s := &fuse.SymlinkResponse{}
initLookupResponse(&s.LookupResponse)
n, ok := node.(NodeSymlinker)
if !ok {
return fuse.EIO // XXX or EPERM like Mkdir?
done(fuse.EIO) // XXX or EPERM like Mkdir?
r.RespondError(fuse.EIO)
break
}
n2, err := n.Symlink(ctx, r)
if err != nil {
return err
done(err)
r.RespondError(err)
break
}
if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.NewName, n2); err != nil {
return err
done(err)
r.RespondError(err)
break
}
done(s)
r.Respond(s)
return nil
case *fuse.ReadlinkRequest:
n, ok := node.(NodeReadlinker)
if !ok {
return fuse.EIO /// XXX or EPERM?
done(fuse.EIO) /// XXX or EPERM?
r.RespondError(fuse.EIO)
break
}
target, err := n.Readlink(ctx, r)
if err != nil {
return err
done(err)
r.RespondError(err)
break
}
done(target)
r.Respond(target)
return nil
case *fuse.LinkRequest:
n, ok := node.(NodeLinker)
if !ok {
return fuse.EIO /// XXX or EPERM?
done(fuse.EIO) /// XXX or EPERM?
r.RespondError(fuse.EIO)
break
}
c.meta.Lock()
var oldNode *serveNode
@@ -1001,43 +933,52 @@ func (c *Server) handleRequest(ctx context.Context, node Node, snode *serveNode,
Request: r.Hdr(),
In: r,
})
return fuse.EIO
done(fuse.EIO)
r.RespondError(fuse.EIO)
break
}
n2, err := n.Link(ctx, r, oldNode.node)
if err != nil {
return err
done(err)
r.RespondError(err)
break
}
s := &fuse.LookupResponse{}
initLookupResponse(s)
if err := c.saveLookup(ctx, s, snode, r.NewName, n2); err != nil {
return err
done(err)
r.RespondError(err)
break
}
done(s)
r.Respond(s)
return nil
case *fuse.RemoveRequest:
n, ok := node.(NodeRemover)
if !ok {
return fuse.EIO /// XXX or EPERM?
done(fuse.EIO) /// XXX or EPERM?
r.RespondError(fuse.EIO)
break
}
err := n.Remove(ctx, r)
if err != nil {
return err
done(err)
r.RespondError(err)
break
}
done(nil)
r.Respond()
return nil
case *fuse.AccessRequest:
if n, ok := node.(NodeAccesser); ok {
if err := n.Access(ctx, r); err != nil {
return err
done(err)
r.RespondError(err)
break
}
}
done(nil)
r.Respond()
return nil
case *fuse.LookupRequest:
var n2 Node
@@ -1049,35 +990,45 @@ func (c *Server) handleRequest(ctx context.Context, node Node, snode *serveNode,
} else if n, ok := node.(NodeRequestLookuper); ok {
n2, err = n.Lookup(ctx, r, s)
} else {
return fuse.ENOENT
done(fuse.ENOENT)
r.RespondError(fuse.ENOENT)
break
}
if err != nil {
return err
done(err)
r.RespondError(err)
break
}
if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil {
return err
done(err)
r.RespondError(err)
break
}
done(s)
r.Respond(s)
return nil
case *fuse.MkdirRequest:
s := &fuse.MkdirResponse{}
initLookupResponse(&s.LookupResponse)
n, ok := node.(NodeMkdirer)
if !ok {
return fuse.EPERM
done(fuse.EPERM)
r.RespondError(fuse.EPERM)
break
}
n2, err := n.Mkdir(ctx, r)
if err != nil {
return err
done(err)
r.RespondError(err)
break
}
if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil {
return err
done(err)
r.RespondError(err)
break
}
done(s)
r.Respond(s)
return nil
case *fuse.OpenRequest:
s := &fuse.OpenResponse{}
@@ -1085,99 +1036,121 @@ func (c *Server) handleRequest(ctx context.Context, node Node, snode *serveNode,
if n, ok := node.(NodeOpener); ok {
hh, err := n.Open(ctx, r, s)
if err != nil {
return err
done(err)
r.RespondError(err)
break
}
h2 = hh
} else {
h2 = node
}
s.Handle = c.saveHandle(h2, r.Hdr().Node)
s.Handle = c.saveHandle(h2, 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.
return fuse.EPERM
done(fuse.EPERM)
r.RespondError(fuse.EPERM)
break
}
s := &fuse.CreateResponse{OpenResponse: fuse.OpenResponse{}}
initLookupResponse(&s.LookupResponse)
n2, h2, err := n.Create(ctx, r, s)
if err != nil {
return err
done(err)
r.RespondError(err)
break
}
if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil {
return err
done(err)
r.RespondError(err)
break
}
s.Handle = c.saveHandle(h2, r.Hdr().Node)
s.Handle = c.saveHandle(h2, hdr.Node)
done(s)
r.Respond(s)
return nil
case *fuse.GetxattrRequest:
n, ok := node.(NodeGetxattrer)
if !ok {
return fuse.ENOTSUP
done(fuse.ENOTSUP)
r.RespondError(fuse.ENOTSUP)
break
}
s := &fuse.GetxattrResponse{}
err := n.Getxattr(ctx, r, s)
if err != nil {
return err
done(err)
r.RespondError(err)
break
}
if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) {
return fuse.ERANGE
done(fuse.ERANGE)
r.RespondError(fuse.ERANGE)
break
}
done(s)
r.Respond(s)
return nil
case *fuse.ListxattrRequest:
n, ok := node.(NodeListxattrer)
if !ok {
return fuse.ENOTSUP
done(fuse.ENOTSUP)
r.RespondError(fuse.ENOTSUP)
break
}
s := &fuse.ListxattrResponse{}
err := n.Listxattr(ctx, r, s)
if err != nil {
return err
done(err)
r.RespondError(err)
break
}
if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) {
return fuse.ERANGE
done(fuse.ERANGE)
r.RespondError(fuse.ERANGE)
break
}
done(s)
r.Respond(s)
return nil
case *fuse.SetxattrRequest:
n, ok := node.(NodeSetxattrer)
if !ok {
return fuse.ENOTSUP
done(fuse.ENOTSUP)
r.RespondError(fuse.ENOTSUP)
break
}
err := n.Setxattr(ctx, r)
if err != nil {
return err
done(err)
r.RespondError(err)
break
}
done(nil)
r.Respond()
return nil
case *fuse.RemovexattrRequest:
n, ok := node.(NodeRemovexattrer)
if !ok {
return fuse.ENOTSUP
done(fuse.ENOTSUP)
r.RespondError(fuse.ENOTSUP)
break
}
err := n.Removexattr(ctx, r)
if err != nil {
return err
done(err)
r.RespondError(err)
break
}
done(nil)
r.Respond()
return nil
case *fuse.ForgetRequest:
forget := c.dropNode(r.Hdr().Node, r.N)
forget := c.dropNode(hdr.Node, r.N)
if forget {
n, ok := node.(NodeForgetter)
if ok {
@@ -1186,29 +1159,26 @@ func (c *Server) handleRequest(ctx context.Context, node Node, snode *serveNode,
}
done(nil)
r.Respond()
return nil
// Handle operations.
case *fuse.ReadRequest:
shandle := c.getHandle(r.Handle)
if shandle == nil {
return fuse.ESTALE
done(fuse.ESTALE)
r.RespondError(fuse.ESTALE)
return
}
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 {
return err
done(err)
r.RespondError(err)
break
}
var data []byte
for _, dir := range dirs {
@@ -1222,14 +1192,16 @@ func (c *Server) handleRequest(ctx context.Context, node Node, snode *serveNode,
fuseutil.HandleRead(r, s, shandle.readData)
done(s)
r.Respond(s)
return nil
break
}
} else {
if h, ok := handle.(HandleReadAller); ok {
if shandle.readData == nil {
data, err := h.ReadAll(ctx)
if err != nil {
return err
done(err)
r.RespondError(err)
break
}
if data == nil {
data = []byte{}
@@ -1239,58 +1211,71 @@ func (c *Server) handleRequest(ctx context.Context, node Node, snode *serveNode,
fuseutil.HandleRead(r, s, shandle.readData)
done(s)
r.Respond(s)
return nil
break
}
h, ok := handle.(HandleReader)
if !ok {
err := handleNotReaderError{handle: handle}
return err
fmt.Printf("NO READ FOR %T\n", handle)
done(fuse.EIO)
r.RespondError(fuse.EIO)
break
}
if err := h.Read(ctx, r, s); err != nil {
return err
done(err)
r.RespondError(err)
break
}
}
done(s)
r.Respond(s)
return nil
case *fuse.WriteRequest:
shandle := c.getHandle(r.Handle)
if shandle == nil {
return fuse.ESTALE
done(fuse.ESTALE)
r.RespondError(fuse.ESTALE)
return
}
s := &fuse.WriteResponse{}
if h, ok := shandle.handle.(HandleWriter); ok {
if err := h.Write(ctx, r, s); err != nil {
return err
done(err)
r.RespondError(err)
break
}
done(s)
r.Respond(s)
return nil
break
}
return fuse.EIO
done(fuse.EIO)
r.RespondError(fuse.EIO)
case *fuse.FlushRequest:
shandle := c.getHandle(r.Handle)
if shandle == nil {
return fuse.ESTALE
done(fuse.ESTALE)
r.RespondError(fuse.ESTALE)
return
}
handle := shandle.handle
if h, ok := handle.(HandleFlusher); ok {
if err := h.Flush(ctx, r); err != nil {
return err
done(err)
r.RespondError(err)
break
}
}
done(nil)
r.Respond()
return nil
case *fuse.ReleaseRequest:
shandle := c.getHandle(r.Handle)
if shandle == nil {
return fuse.ESTALE
done(fuse.ESTALE)
r.RespondError(fuse.ESTALE)
return
}
handle := shandle.handle
@@ -1299,12 +1284,13 @@ func (c *Server) handleRequest(ctx context.Context, node Node, snode *serveNode,
if h, ok := handle.(HandleReleaser); ok {
if err := h.Release(ctx, r); err != nil {
return err
done(err)
r.RespondError(err)
break
}
}
done(nil)
r.Respond()
return nil
case *fuse.DestroyRequest:
if fs, ok := c.fs.(FSDestroyer); ok {
@@ -1312,7 +1298,6 @@ func (c *Server) handleRequest(ctx context.Context, node Node, snode *serveNode,
}
done(nil)
r.Respond()
return nil
case *fuse.RenameRequest:
c.meta.Lock()
@@ -1326,50 +1311,63 @@ func (c *Server) handleRequest(ctx context.Context, node Node, snode *serveNode,
Request: r.Hdr(),
In: r,
})
return fuse.EIO
done(fuse.EIO)
r.RespondError(fuse.EIO)
break
}
n, ok := node.(NodeRenamer)
if !ok {
return fuse.EIO // XXX or EPERM like Mkdir?
done(fuse.EIO) // XXX or EPERM like Mkdir?
r.RespondError(fuse.EIO)
break
}
err := n.Rename(ctx, r, newDirNode.node)
if err != nil {
return err
done(err)
r.RespondError(err)
break
}
done(nil)
r.Respond()
return nil
case *fuse.MknodRequest:
n, ok := node.(NodeMknoder)
if !ok {
return fuse.EIO
done(fuse.EIO)
r.RespondError(fuse.EIO)
break
}
n2, err := n.Mknod(ctx, r)
if err != nil {
return err
done(err)
r.RespondError(err)
break
}
s := &fuse.LookupResponse{}
initLookupResponse(s)
if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil {
return err
done(err)
r.RespondError(err)
break
}
done(s)
r.Respond(s)
return nil
case *fuse.FsyncRequest:
n, ok := node.(NodeFsyncer)
if !ok {
return fuse.EIO
done(fuse.EIO)
r.RespondError(fuse.EIO)
break
}
err := n.Fsync(ctx, r)
if err != nil {
return err
done(err)
r.RespondError(err)
break
}
done(nil)
r.Respond()
return nil
case *fuse.InterruptRequest:
c.meta.Lock()
@@ -1381,23 +1379,24 @@ func (c *Server) handleRequest(ctx context.Context, node Node, snode *serveNode,
c.meta.Unlock()
done(nil)
r.Respond()
return nil
/* case *FsyncdirRequest:
return ENOSYS
done(ENOSYS)
r.RespondError(ENOSYS)
case *GetlkRequest, *SetlkRequest, *SetlkwRequest:
return ENOSYS
done(ENOSYS)
r.RespondError(ENOSYS)
case *BmapRequest:
return ENOSYS
done(ENOSYS)
r.RespondError(ENOSYS)
case *SetvolnameRequest, *GetxtimesRequest, *ExchangeRequest:
return ENOSYS
done(ENOSYS)
r.RespondError(ENOSYS)
*/
}
panic("not reached")
}
func (c *Server) saveLookup(ctx context.Context, s *fuse.LookupResponse, snode *serveNode, elem string, n2 Node) error {

View File

@@ -7,11 +7,10 @@ import (
"io/ioutil"
"log"
"os"
"path"
"os/exec"
"runtime"
"strings"
"sync"
"sync/atomic"
"syscall"
"testing"
"time"
@@ -57,25 +56,6 @@ 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) {
@@ -297,18 +277,12 @@ func (readAll) ReadAll(ctx context.Context) ([]byte, error) {
}
func testReadAll(t *testing.T, path string) {
f, err := os.Open(path)
data, err := ioutil.ReadFile(path)
if err != nil {
t.Fatal(err)
t.Fatalf("readAll: %v", err)
}
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)
if string(data) != hi {
t.Errorf("readAll = %q, want %q", data, hi)
}
}
@@ -392,15 +366,6 @@ 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)
}
@@ -417,13 +382,6 @@ 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)
@@ -454,15 +412,6 @@ 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)
}
@@ -633,6 +582,7 @@ 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 {
@@ -651,10 +601,6 @@ 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)
}
@@ -664,7 +610,6 @@ func TestMkdir(t *testing.T) {
type create1file struct {
fstestutil.File
record.Creates
record.Fsyncs
}
@@ -678,12 +623,31 @@ 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
_, _, _ = f.f.Creates.Create(ctx, req, resp)
// 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
}
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 {
@@ -694,38 +658,12 @@ 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.OpenFile(mnt.Dir+"/foo", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0640)
ff, err := os.Create(mnt.Dir + "/foo")
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)
@@ -957,6 +895,7 @@ 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")
}
@@ -968,15 +907,15 @@ func TestMknod(t *testing.T) {
}
defer mnt.Close()
defer syscall.Umask(syscall.Umask(0022))
err = syscall.Mknod(mnt.Dir+"/node", syscall.S_IFIFO|0660, 123)
defer syscall.Umask(syscall.Umask(0))
err = syscall.Mknod(mnt.Dir+"/node", syscall.S_IFIFO|0666, 123)
if err != nil {
t.Fatalf("mknod: %v", err)
t.Fatalf("Mknod: %v", err)
}
want := fuse.MknodRequest{
Name: "node",
Mode: os.FileMode(os.ModeNamedPipe | 0640),
Mode: os.FileMode(os.ModeNamedPipe | 0666),
Rdev: uint32(123),
}
if runtime.GOOS == "linux" {
@@ -985,13 +924,6 @@ 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)
}
@@ -1053,38 +985,7 @@ func (it *interrupt) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse
default:
}
<-ctx.Done()
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
return fuse.EINTR
}
func TestInterrupt(t *testing.T) {
@@ -1098,71 +999,46 @@ func TestInterrupt(t *testing.T) {
defer mnt.Close()
// start a subprocess that can hang until signaled
child, err := childCmd("interrupt")
if err != nil {
t.Fatal(err)
}
child.Dir = mnt.Dir
cmd := exec.Command("cat", mnt.Dir+"/child")
if err := child.Start(); err != nil {
t.Errorf("cannot start child: %v", err)
err = cmd.Start()
if err != nil {
t.Errorf("interrupt: cannot start cat: %v", err)
return
}
// try to clean up if child is still alive when returning
defer child.Process.Kill()
defer cmd.Process.Kill()
// wait till we're sure it's hanging in read
<-f.hanging
// 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)
err = cmd.Process.Signal(os.Interrupt)
if err != nil {
t.Errorf("cannot interrupt child: %v", err)
t.Errorf("interrupt: cannot interrupt cat: %v", err)
return
}
p, err := child.Process.Wait()
p, err := cmd.Process.Wait()
if err != nil {
t.Errorf("child failed: %v", err)
t.Errorf("interrupt: cat bork: %v", err)
return
}
switch ws := p.Sys().(type) {
case syscall.WaitStatus:
if ws.CoreDump() {
t.Fatalf("interrupt: didn't expect child to dump core: %v", ws)
t.Errorf("interrupt: didn't expect cat to dump core: %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)
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)
}
}
default:
@@ -1170,51 +1046,6 @@ 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 {
@@ -1393,58 +1224,6 @@ 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 {
@@ -1476,73 +1255,6 @@ 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 {
@@ -1652,10 +1364,6 @@ 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)
@@ -2401,20 +2109,8 @@ func TestInvalidateNodeAttr(t *testing.T) {
t.Fatalf("stat error: %v", err)
}
}
// 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)
if g, e := a.attr.Count(), uint32(1); g != e {
t.Errorf("wrong Attr call count: %d != %d", g, e)
}
t.Logf("invalidating...")
@@ -2427,7 +2123,7 @@ func TestInvalidateNodeAttr(t *testing.T) {
t.Fatalf("stat error: %v", err)
}
}
if g, e := a.attr.Count(), before+1; g != e {
if g, e := a.attr.Count(), uint32(2); g != e {
t.Errorf("wrong Attr call count: %d != %d", g, e)
}
}
@@ -2437,13 +2133,9 @@ type invalidateData struct {
t testing.TB
attr record.Counter
read record.Counter
data atomic.Value
}
const (
invalidateDataContent1 = "hello, world\n"
invalidateDataContent2 = "so long!\n"
)
const invalidateDataContent = "hello, world\n"
var _ fs.Node = (*invalidateData)(nil)
@@ -2451,7 +2143,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(i.data.Load().(string)))
a.Size = uint64(len(invalidateDataContent))
return nil
}
@@ -2460,18 +2152,17 @@ 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(i.data.Load().(string)))
fuseutil.HandleRead(req, resp, []byte(invalidateDataContent))
return nil
}
func TestInvalidateNodeDataInvalidatesAttr(t *testing.T) {
func TestInvalidateNodeData(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)
@@ -2488,94 +2179,32 @@ func TestInvalidateNodeDataInvalidatesAttr(t *testing.T) {
}
defer f.Close()
attrBefore := a.attr.Count()
if g, min := attrBefore, uint32(1); g < min {
t.Errorf("wrong Attr call count: %d < %d", g, min)
}
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)
}
buf := make([]byte, 4)
for i := 0; i < 10; i++ {
if _, err := f.ReadAt(buf, 0); err != nil {
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...")
a.data.Store(invalidateDataContent2)
if err := mnt.Server.InvalidateNodeData(a); err != nil {
t.Fatalf("invalidate error: %v", err)
}
if g, e := a.read.Count(), uint32(1); g != e {
t.Errorf("wrong Read 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)
}
for i := 0; i < 10; i++ {
if _, err := f.ReadAt(buf, 0); err != nil {
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(2); g != e {
t.Errorf("wrong Read call count: %d != %d", g, e)
}
@@ -2609,7 +2238,7 @@ func (i *invalidateDataPartial) Read(ctx context.Context, req *fuse.ReadRequest,
return nil
}
func TestInvalidateNodeDataRangeMiss(t *testing.T) {
func TestInvalidateNodeDataRange(t *testing.T) {
// This test may see false positive failures when run under
// extreme memory pressure.
t.Parallel()
@@ -2638,11 +2267,14 @@ func TestInvalidateNodeDataRangeMiss(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 an uninteresting block...")
t.Logf("invalidating...")
if err := mnt.Server.InvalidateNodeDataRange(a, 4096, 4096); err != nil {
t.Fatalf("invalidate error: %v", err)
}
@@ -2652,6 +2284,9 @@ func TestInvalidateNodeDataRangeMiss(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 {
@@ -2659,56 +2294,6 @@ func TestInvalidateNodeDataRangeMiss(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
@@ -2795,13 +2380,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{
WithContext: func(ctx context.Context, req fuse.Request) context.Context {
return context.WithValue(ctx, &contextFileSentinel, input)
},
GetContext: func() context.Context { return ctx },
})
if err != nil {
t.Fatal(err)
@@ -2816,28 +2401,3 @@ 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

@@ -98,7 +98,7 @@
// Behavior and metadata of the mounted file system can be changed by
// passing MountOption values to Mount.
//
package fuse // import "bazil.org/fuse"
package fuse
import (
"bytes"
@@ -132,18 +132,6 @@ 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.
//
@@ -176,13 +164,6 @@ 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
}
@@ -198,15 +179,11 @@ 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 ErrClosedWithoutInit
return fmt.Errorf("missing init, got EOF")
}
return err
}
@@ -235,7 +212,7 @@ func initMount(c *Conn, conf *mountConfig) error {
s := &InitResponse{
Library: proto,
MaxReadahead: conf.maxReadahead,
MaxWrite: maxWrite,
MaxWrite: 128 * 1024,
Flags: InitBigWrites | conf.initFlags,
}
r.Respond(s)
@@ -258,27 +235,15 @@ 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
@@ -296,7 +261,7 @@ type Header struct {
}
func (h *Header) String() string {
return fmt.Sprintf("ID=%v Node=%v Uid=%d Gid=%d Pid=%d", h.ID, h.Node, h.Uid, h.Gid, h.Pid)
return fmt.Sprintf("ID=%#x Node=%#x Uid=%d Gid=%d Pid=%d", h.ID, h.Node, h.Uid, h.Gid, h.Pid)
}
func (h *Header) Hdr() *Header {
@@ -403,6 +368,9 @@ 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()
@@ -1017,33 +985,7 @@ loop:
case opGetxtimes:
panic("opGetxtimes")
case 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
}
panic("opExchange")
}
return req, nil
@@ -1205,7 +1147,7 @@ type InitRequest struct {
var _ = Request(&InitRequest{})
func (r *InitRequest) String() string {
return fmt.Sprintf("Init [%v] %v ra=%d fl=%v", &r.Header, r.Kernel, r.MaxReadahead, r.Flags)
return fmt.Sprintf("Init [%s] %v ra=%d fl=%v", &r.Header, r.Kernel, r.MaxReadahead, r.Flags)
}
// An InitResponse is the response to an InitRequest.
@@ -1221,7 +1163,7 @@ type InitResponse struct {
}
func (r *InitResponse) String() string {
return fmt.Sprintf("Init %v ra=%d fl=%v w=%d", r.Library, r.MaxReadahead, r.Flags, r.MaxWrite)
return fmt.Sprintf("Init %+v", *r)
}
// Respond replies to the request with the given response.
@@ -1282,13 +1224,7 @@ type StatfsResponse struct {
}
func (r *StatfsResponse) String() string {
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,
)
return fmt.Sprintf("Statfs %+v", *r)
}
// An AccessRequest asks whether the file can be accessed
@@ -1323,7 +1259,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 (usually 1)
Nlink uint32 // number of links
Uid uint32 // owner uid
Gid uint32 // group gid
Rdev uint32 // device numbers
@@ -1331,10 +1267,6 @@ 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)
@@ -1397,7 +1329,7 @@ type GetattrRequest struct {
var _ = Request(&GetattrRequest{})
func (r *GetattrRequest) String() string {
return fmt.Sprintf("Getattr [%s] %v fl=%v", &r.Header, r.Handle, r.Flags)
return fmt.Sprintf("Getattr [%s] %#x fl=%v", &r.Header, r.Handle, r.Flags)
}
// Respond replies to the request with the given response.
@@ -1417,7 +1349,7 @@ type GetattrResponse struct {
}
func (r *GetattrResponse) String() string {
return fmt.Sprintf("Getattr %v", r.Attr)
return fmt.Sprintf("Getattr %+v", *r)
}
// A GetxattrRequest asks for the extended attributes associated with r.Node.
@@ -1608,12 +1540,8 @@ 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 %s", r.string())
return fmt.Sprintf("Lookup %+v", *r)
}
// An OpenRequest asks to open a file or directory
@@ -1644,12 +1572,8 @@ 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 %s", r.string())
return fmt.Sprintf("Open %+v", *r)
}
// A CreateRequest asks to create and open a file (not a directory).
@@ -1658,8 +1582,7 @@ type CreateRequest struct {
Name string
Flags OpenFlags
Mode os.FileMode
// Umask of the request. Not supported on OS X.
Umask os.FileMode
Umask os.FileMode
}
var _ = Request(&CreateRequest{})
@@ -1697,7 +1620,7 @@ type CreateResponse struct {
}
func (r *CreateResponse) String() string {
return fmt.Sprintf("Create {%s} {%s}", r.LookupResponse.string(), r.OpenResponse.string())
return fmt.Sprintf("Create %+v", *r)
}
// A MkdirRequest asks to create (but not open) a directory.
@@ -1705,8 +1628,7 @@ type MkdirRequest struct {
Header `json:"-"`
Name string
Mode os.FileMode
// Umask of the request. Not supported on OS X.
Umask os.FileMode
Umask os.FileMode
}
var _ = Request(&MkdirRequest{})
@@ -1736,7 +1658,7 @@ type MkdirResponse struct {
}
func (r *MkdirResponse) String() string {
return fmt.Sprintf("Mkdir %v", r.LookupResponse.string())
return fmt.Sprintf("Mkdir %+v", *r)
}
// A ReadRequest asks to read from an open file.
@@ -1754,7 +1676,7 @@ type ReadRequest struct {
var _ = Request(&ReadRequest{})
func (r *ReadRequest) String() string {
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)
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)
}
// Respond replies to the request with the given response.
@@ -1797,7 +1719,7 @@ type ReleaseRequest struct {
var _ = Request(&ReleaseRequest{})
func (r *ReleaseRequest) String() string {
return fmt.Sprintf("Release [%s] %v fl=%v rfl=%v owner=%#x", &r.Header, r.Handle, r.Flags, r.ReleaseFlags, r.LockOwner)
return fmt.Sprintf("Release [%s] %#x 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.
@@ -1939,7 +1861,7 @@ type WriteRequest struct {
var _ = Request(&WriteRequest{})
func (r *WriteRequest) String() string {
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)
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)
}
type jsonWriteRequest struct {
@@ -1973,7 +1895,7 @@ type WriteResponse struct {
}
func (r *WriteResponse) String() string {
return fmt.Sprintf("Write %d", r.Size)
return fmt.Sprintf("Write %+v", *r)
}
// A SetattrRequest asks to change one or more attributes associated with a file,
@@ -2026,9 +1948,9 @@ func (r *SetattrRequest) String() string {
fmt.Fprintf(&buf, " mtime=now")
}
if r.Valid.Handle() {
fmt.Fprintf(&buf, " handle=%v", r.Handle)
fmt.Fprintf(&buf, " handle=%#x", r.Handle)
} else {
fmt.Fprintf(&buf, " handle=INVALID-%v", r.Handle)
fmt.Fprintf(&buf, " handle=INVALID-%#x", r.Handle)
}
if r.Valid.LockOwner() {
fmt.Fprintf(&buf, " lockowner")
@@ -2043,7 +1965,7 @@ func (r *SetattrRequest) String() string {
fmt.Fprintf(&buf, " bkuptime=%v", r.Bkuptime)
}
if r.Valid.Flags() {
fmt.Fprintf(&buf, " flags=%v", r.Flags)
fmt.Fprintf(&buf, " flags=%#x", r.Flags)
}
return buf.String()
}
@@ -2066,7 +1988,7 @@ type SetattrResponse struct {
}
func (r *SetattrResponse) String() string {
return fmt.Sprintf("Setattr %v", r.Attr)
return fmt.Sprintf("Setattr %+v", *r)
}
// A FlushRequest asks for the current state of an open file to be flushed
@@ -2082,7 +2004,7 @@ type FlushRequest struct {
var _ = Request(&FlushRequest{})
func (r *FlushRequest) String() string {
return fmt.Sprintf("Flush [%s] %v fl=%#x lk=%#x", &r.Header, r.Handle, r.Flags, r.LockOwner)
return fmt.Sprintf("Flush [%s] %#x fl=%#x lk=%#x", &r.Header, r.Handle, r.Flags, r.LockOwner)
}
// Respond replies to the request, indicating that the flush succeeded.
@@ -2143,10 +2065,6 @@ 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:"-"`
@@ -2201,7 +2119,7 @@ type RenameRequest struct {
var _ = Request(&RenameRequest{})
func (r *RenameRequest) String() string {
return fmt.Sprintf("Rename [%s] from %q to dirnode %v %q", &r.Header, r.OldName, r.NewDir, r.NewName)
return fmt.Sprintf("Rename [%s] from %q to dirnode %d %q", &r.Header, r.OldName, r.NewDir, r.NewName)
}
func (r *RenameRequest) Respond() {
@@ -2214,8 +2132,7 @@ type MknodRequest struct {
Name string
Mode os.FileMode
Rdev uint32
// Umask of the request. Not supported on OS X.
Umask os.FileMode
Umask os.FileMode
}
var _ = Request(&MknodRequest{})
@@ -2274,30 +2191,3 @@ 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

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

View File

@@ -0,0 +1,31 @@
package fuse_test
import (
"os"
"testing"
"bazil.org/fuse"
)
func TestOpenFlagsAccmodeMask(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)
}
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 {
t.Fatalf("OpenFlags.String: %q != %q", g, e)
}
}

View File

@@ -1,4 +1,4 @@
package fuseutil // import "bazil.org/fuse/fuseutil"
package fuseutil
import (
"bazil.org/fuse"

126
Godeps/_workspace/src/bazil.org/fuse/mount_darwin.go generated vendored Normal file
View File

@@ -0,0 +1,126 @@
package fuse
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"syscall"
)
var errNoAvail = errors.New("no available fuse devices")
var errNotLoaded = errors.New("osxfusefs is not loaded")
func loadOSXFUSE() error {
cmd := exec.Command("/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs")
cmd.Dir = "/"
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
return err
}
func openOSXFUSEDev() (*os.File, error) {
var f *os.File
var err error
for i := uint64(0); ; i++ {
path := "/dev/osxfuse" + strconv.FormatUint(i, 10)
f, err = os.OpenFile(path, os.O_RDWR, 0000)
if os.IsNotExist(err) {
if i == 0 {
// not even the first device was found -> fuse is not loaded
return nil, errNotLoaded
}
// we've run out of kernel-provided devices
return nil, errNoAvail
}
if err2, ok := err.(*os.PathError); ok && err2.Err == syscall.EBUSY {
// try the next one
continue
}
if err != nil {
return nil, err
}
return f, nil
}
}
func callMount(dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, errp *error) error {
bin := "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs"
for k, v := range conf.options {
if strings.Contains(k, ",") || strings.Contains(v, ",") {
// Silly limitation but the mount helper does not
// understand any escaping. See TestMountOptionCommaError.
return fmt.Errorf("mount options cannot contain commas on darwin: %q=%q", k, v)
}
}
cmd := exec.Command(
bin,
"-o", conf.getOptions(),
// Tell osxfuse-kext how large our buffer is. It must split
// writes larger than this into multiple writes.
//
// OSXFUSE seems to ignore InitResponse.MaxWrite, and uses
// this instead.
"-o", "iosize="+strconv.FormatUint(maxWrite, 10),
// refers to fd passed in cmd.ExtraFiles
"3",
dir,
)
cmd.ExtraFiles = []*os.File{f}
cmd.Env = os.Environ()
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
err := cmd.Start()
if err != nil {
return err
}
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)
}
}
*errp = err
close(ready)
}()
return err
}
func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) {
f, err := openOSXFUSEDev()
if err == errNotLoaded {
err = loadOSXFUSE()
if err != nil {
return nil, err
}
// try again
f, err = openOSXFUSEDev()
}
if err != nil {
return nil, err
}
err = callMount(dir, conf, f, ready, errp)
if err != nil {
f.Close()
return nil, err
}
return f, nil
}

41
Godeps/_workspace/src/bazil.org/fuse/mount_freebsd.go generated vendored Normal file
View File

@@ -0,0 +1,41 @@
package fuse
import (
"fmt"
"os"
"os/exec"
"strings"
)
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, ",") {
// Silly limitation but the mount helper does not
// understand any escaping. See TestMountOptionCommaError.
return nil, fmt.Errorf("mount options cannot contain commas on FreeBSD: %q=%q", k, v)
}
}
f, err := os.OpenFile("/dev/fuse", os.O_RDWR, 0000)
if err != nil {
*errp = err
return nil, err
}
cmd := exec.Command(
"/sbin/mount_fusefs",
"--safe",
"-o", conf.getOptions(),
"3",
dir,
)
cmd.ExtraFiles = []*os.File{f}
out, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("mount_fusefs: %q, %v", out, err)
}
close(ready)
return f, nil
}

View File

@@ -1,58 +1,35 @@
package fuse
import (
"bufio"
"fmt"
"io"
"log"
"net"
"os"
"os/exec"
"strings"
"sync"
"syscall"
)
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` {
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`:
// Silence this particular message, it occurs way too
// commonly and isn't very relevant to whether the mount
// succeeds or not.
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
continue
default:
log.Printf("%s: %s", prefix, line)
}
}
return false
if err := scanner.Err(); err != nil {
log.Printf("%s, error reading: %v", prefix, err)
}
}
func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) {
@@ -93,26 +70,11 @@ 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", neverIgnoreLine, stdout)
go lineLogger(&wg, "mount helper error", handleFusermountStderr(helperErrCh), stderr)
go lineLogger(&wg, "mount helper output", stdout)
go lineLogger(&wg, "mount helper error", 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)
}

170
Godeps/_workspace/src/bazil.org/fuse/options.go generated vendored Normal file
View File

@@ -0,0 +1,170 @@
package fuse
import (
"errors"
"strings"
)
func dummyOption(conf *mountConfig) error {
return nil
}
// 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
}
func escapeComma(s string) string {
s = strings.Replace(s, `\`, `\\`, -1)
s = strings.Replace(s, `,`, `\,`, -1)
return s
}
// getOptions makes a string of options suitable for passing to FUSE
// mount flag `-o`. Returns an empty string if no options were set.
// Any platform specific adjustments should happen before the call.
func (m *mountConfig) getOptions() string {
var opts []string
for k, v := range m.options {
k = escapeComma(k)
if v != "" {
k += "=" + escapeComma(v)
}
opts = append(opts, k)
}
return strings.Join(opts, ",")
}
type mountOption func(*mountConfig) error
// MountOption is passed to Mount to change the behavior of the mount.
type MountOption mountOption
// FSName sets the file system name (also called source) that is
// visible in the list of mounted file systems.
//
// FreeBSD ignores this option.
func FSName(name string) MountOption {
return func(conf *mountConfig) error {
conf.options["fsname"] = name
return nil
}
}
// Subtype sets the subtype of the mount. The main type is always
// `fuse`. The type in a list of mounted file systems will look like
// `fuse.foo`.
//
// OS X ignores this option.
// FreeBSD ignores this option.
func Subtype(fstype string) MountOption {
return func(conf *mountConfig) error {
conf.options["subtype"] = fstype
return nil
}
}
// LocalVolume sets the volume to be local (instead of network),
// changing the behavior of Finder, Spotlight, and such.
//
// OS X only. Others ignore this option.
func LocalVolume() MountOption {
return localVolume
}
// VolumeName sets the volume name shown in Finder.
//
// OS X only. Others ignore this option.
func VolumeName(name string) MountOption {
return volumeName(name)
}
var ErrCannotCombineAllowOtherAndAllowRoot = errors.New("cannot combine AllowOther and AllowRoot")
// AllowOther allows other users to access the file system.
//
// Only one of AllowOther or AllowRoot can be used.
func AllowOther() MountOption {
return func(conf *mountConfig) error {
if _, ok := conf.options["allow_root"]; ok {
return ErrCannotCombineAllowOtherAndAllowRoot
}
conf.options["allow_other"] = ""
return nil
}
}
// AllowRoot allows other users to access the file system.
//
// Only one of AllowOther or AllowRoot can be used.
//
// FreeBSD ignores this option.
func AllowRoot() MountOption {
return func(conf *mountConfig) error {
if _, ok := conf.options["allow_other"]; ok {
return ErrCannotCombineAllowOtherAndAllowRoot
}
conf.options["allow_root"] = ""
return nil
}
}
// DefaultPermissions makes the kernel enforce access control based on
// the file mode (as in chmod).
//
// Without this option, the Node itself decides what is and is not
// allowed. This is normally ok because FUSE file systems cannot be
// accessed by other users without AllowOther/AllowRoot.
//
// FreeBSD ignores this option.
func DefaultPermissions() MountOption {
return func(conf *mountConfig) error {
conf.options["default_permissions"] = ""
return nil
}
}
// ReadOnly makes the mount read-only.
func ReadOnly() MountOption {
return func(conf *mountConfig) error {
conf.options["ro"] = ""
return nil
}
}
// MaxReadahead sets the number of bytes that can be prefetched for
// sequential reads. The kernel can enforce a maximum value lower than
// this.
//
// This setting makes the kernel perform speculative reads that do not
// originate from any client process. This usually tremendously
// improves read performance.
func MaxReadahead(n uint32) MountOption {
return func(conf *mountConfig) error {
conf.maxReadahead = n
return nil
}
}
// AsyncRead enables multiple outstanding read requests for the same
// handle. Without this, there is at most one request in flight at a
// time.
func AsyncRead() MountOption {
return func(conf *mountConfig) error {
conf.initFlags |= InitAsyncRead
return nil
}
}
// WritebackCache enables the kernel to buffer writes before sending
// them to the FUSE server. Without this, writethrough caching is
// used.
func WritebackCache() MountOption {
return func(conf *mountConfig) error {
conf.initFlags |= InitWritebackCache
return nil
}
}

13
Godeps/_workspace/src/bazil.org/fuse/options_darwin.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
package fuse
func localVolume(conf *mountConfig) error {
conf.options["local"] = ""
return nil
}
func volumeName(name string) MountOption {
return func(conf *mountConfig) error {
conf.options["volname"] = name
return nil
}
}

View File

@@ -0,0 +1,9 @@
package fuse
func localVolume(conf *mountConfig) error {
return nil
}
func volumeName(name string) MountOption {
return dummyOption
}

View File

@@ -0,0 +1,9 @@
package fuse
func localVolume(conf *mountConfig) error {
return nil
}
func volumeName(name string) MountOption {
return dummyOption
}

View File

@@ -165,7 +165,6 @@ 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{}},
@@ -173,6 +172,7 @@ 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

@@ -10,4 +10,4 @@
//
// Options can be implemented with separate wrappers, in the style of
// Linux getxattr/lgetxattr/fgetxattr.
package syscallx // import "bazil.org/fuse/syscallx"
package syscallx

View File

@@ -0,0 +1,35 @@
language: go
install:
# go-flags
- go get -d -v ./...
- go build -v ./...
# linting
- go get golang.org/x/tools/cmd/vet
- go get github.com/golang/lint
- go install github.com/golang/lint/golint
# code coverage
- go get golang.org/x/tools/cmd/cover
- go get github.com/onsi/ginkgo/ginkgo
- go get github.com/modocache/gover
- if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then go get github.com/mattn/goveralls; fi
script:
# go-flags
- $(exit $(gofmt -l . | wc -l))
- go test -v ./...
# linting
- go tool vet -all=true -v=true . || true
- $(go env GOPATH | awk 'BEGIN{FS=":"} {print $1}')/bin/golint ./...
# code coverage
- $(go env GOPATH | awk 'BEGIN{FS=":"} {print $1}')/bin/ginkgo -r -cover
- $(go env GOPATH | awk 'BEGIN{FS=":"} {print $1}')/bin/gover
- if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then $(go env GOPATH | awk 'BEGIN{FS=":"} {print $1}')/bin/goveralls -coverprofile=gover.coverprofile -service=travis-ci -repotoken $COVERALLS_TOKEN; fi
env:
# coveralls.io
secure: "RCYbiB4P0RjQRIoUx/vG/AjP3mmYCbzOmr86DCww1Z88yNcy3hYr3Cq8rpPtYU5v0g7wTpu4adaKIcqRE9xknYGbqj3YWZiCoBP1/n4Z+9sHW3Dsd9D/GRGeHUus0laJUGARjWoCTvoEtOgTdGQDoX7mH+pUUY0FBltNYUdOiiU="

View File

@@ -1,19 +1,17 @@
Copyright (c) 2012 Alex Ogier. All rights reserved.
Copyright (c) 2012 The Go Authors. All rights reserved.
Copyright (c) 2012 Jesse van den Kieboom. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT

View File

@@ -0,0 +1,131 @@
go-flags: a go library for parsing command line arguments
=========================================================
[![GoDoc](https://godoc.org/github.com/jessevdk/go-flags?status.png)](https://godoc.org/github.com/jessevdk/go-flags) [![Build Status](https://travis-ci.org/jessevdk/go-flags.svg?branch=master)](https://travis-ci.org/jessevdk/go-flags) [![Coverage Status](https://img.shields.io/coveralls/jessevdk/go-flags.svg)](https://coveralls.io/r/jessevdk/go-flags?branch=master)
This library provides similar functionality to the builtin flag library of
go, but provides much more functionality and nicer formatting. From the
documentation:
Package flags provides an extensive command line option parser.
The flags package is similar in functionality to the go builtin flag package
but provides more options and uses reflection to provide a convenient and
succinct way of specifying command line options.
Supported features:
* Options with short names (-v)
* Options with long names (--verbose)
* Options with and without arguments (bool v.s. other type)
* Options with optional arguments and default values
* Multiple option groups each containing a set of options
* Generate and print well-formatted help message
* Passing remaining command line arguments after -- (optional)
* Ignoring unknown command line options (optional)
* Supports -I/usr/include -I=/usr/include -I /usr/include option argument specification
* Supports multiple short options -aux
* Supports all primitive go types (string, int{8..64}, uint{8..64}, float)
* Supports same option multiple times (can store in slice or last option counts)
* Supports maps
* Supports function callbacks
* Supports namespaces for (nested) option groups
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"`
}
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
will be appended to the Verbose field. e.g. when specifying -vvv, the
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"`
// 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 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 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 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"`
}
// 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",
}
// 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)
}
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
More information can be found in the godocs: <http://godoc.org/github.com/jessevdk/go-flags>

View File

@@ -0,0 +1,21 @@
package flags
import (
"reflect"
)
// Arg represents a positional argument on the command line.
type Arg struct {
// The name of the positional argument (used in the help)
Name string
// A description of the positional argument (used in the help)
Description string
value reflect.Value
tag multiTag
}
func (a *Arg) isRemaining() bool {
return a.value.Type().Kind() == reflect.Slice
}

View File

@@ -0,0 +1,53 @@
package flags
import (
"testing"
)
func TestPositional(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Positional struct {
Command int
Filename string
Rest []string
} `positional-args:"yes" required:"yes"`
}{}
p := NewParser(&opts, Default)
ret, err := p.ParseArgs([]string{"10", "arg_test.go", "a", "b"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
return
}
if opts.Positional.Command != 10 {
t.Fatalf("Expected opts.Positional.Command to be 10, but got %v", opts.Positional.Command)
}
if opts.Positional.Filename != "arg_test.go" {
t.Fatalf("Expected opts.Positional.Filename to be \"arg_test.go\", but got %v", opts.Positional.Filename)
}
assertStringArray(t, opts.Positional.Rest, []string{"a", "b"})
assertStringArray(t, ret, []string{})
}
func TestPositionalRequired(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Positional struct {
Command int
Filename string
Rest []string
} `positional-args:"yes" required:"yes"`
}{}
p := NewParser(&opts, None)
_, err := p.ParseArgs([]string{"10"})
assertError(t, err, ErrRequired, "the required argument `Filename` was not provided")
}

View File

@@ -0,0 +1,177 @@
package flags
import (
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"runtime"
"testing"
)
func assertCallerInfo() (string, int) {
ptr := make([]uintptr, 15)
n := runtime.Callers(1, ptr)
if n == 0 {
return "", 0
}
mef := runtime.FuncForPC(ptr[0])
mefile, meline := mef.FileLine(ptr[0])
for i := 2; i < n; i++ {
f := runtime.FuncForPC(ptr[i])
file, line := f.FileLine(ptr[i])
if file != mefile {
return file, line
}
}
return mefile, meline
}
func assertErrorf(t *testing.T, format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
file, line := assertCallerInfo()
t.Errorf("%s:%d: %s", path.Base(file), line, msg)
}
func assertFatalf(t *testing.T, format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
file, line := assertCallerInfo()
t.Fatalf("%s:%d: %s", path.Base(file), line, msg)
}
func assertString(t *testing.T, a string, b string) {
if a != b {
assertErrorf(t, "Expected %#v, but got %#v", b, a)
}
}
func assertStringArray(t *testing.T, a []string, b []string) {
if len(a) != len(b) {
assertErrorf(t, "Expected %#v, but got %#v", b, a)
return
}
for i, v := range a {
if b[i] != v {
assertErrorf(t, "Expected %#v, but got %#v", b, a)
return
}
}
}
func assertBoolArray(t *testing.T, a []bool, b []bool) {
if len(a) != len(b) {
assertErrorf(t, "Expected %#v, but got %#v", b, a)
return
}
for i, v := range a {
if b[i] != v {
assertErrorf(t, "Expected %#v, but got %#v", b, a)
return
}
}
}
func assertParserSuccess(t *testing.T, data interface{}, args ...string) (*Parser, []string) {
parser := NewParser(data, Default&^PrintErrors)
ret, err := parser.ParseArgs(args)
if err != nil {
t.Fatalf("Unexpected parse error: %s", err)
return nil, nil
}
return parser, ret
}
func assertParseSuccess(t *testing.T, data interface{}, args ...string) []string {
_, ret := assertParserSuccess(t, data, args...)
return ret
}
func assertError(t *testing.T, err error, typ ErrorType, msg string) {
if err == nil {
assertFatalf(t, "Expected error: %s", msg)
return
}
if e, ok := err.(*Error); !ok {
assertFatalf(t, "Expected Error type, but got %#v", err)
} else {
if e.Type != typ {
assertErrorf(t, "Expected error type {%s}, but got {%s}", typ, e.Type)
}
if e.Message != msg {
assertErrorf(t, "Expected error message %#v, but got %#v", msg, e.Message)
}
}
}
func assertParseFail(t *testing.T, typ ErrorType, msg string, data interface{}, args ...string) []string {
parser := NewParser(data, Default&^PrintErrors)
ret, err := parser.ParseArgs(args)
assertError(t, err, typ, msg)
return ret
}
func diff(a, b string) (string, error) {
atmp, err := ioutil.TempFile("", "help-diff")
if err != nil {
return "", err
}
btmp, err := ioutil.TempFile("", "help-diff")
if err != nil {
return "", err
}
if _, err := io.WriteString(atmp, a); err != nil {
return "", err
}
if _, err := io.WriteString(btmp, b); err != nil {
return "", err
}
ret, err := exec.Command("diff", "-u", "-d", "--label", "got", atmp.Name(), "--label", "expected", btmp.Name()).Output()
os.Remove(atmp.Name())
os.Remove(btmp.Name())
if err.Error() == "exit status 1" {
return string(ret), nil
}
return string(ret), err
}
func assertDiff(t *testing.T, actual, expected, msg string) {
if actual == expected {
return
}
ret, err := diff(actual, expected)
if err != nil {
assertErrorf(t, "Unexpected diff error: %s", err)
assertErrorf(t, "Unexpected %s, expected:\n\n%s\n\nbut got\n\n%s", msg, expected, actual)
} else {
assertErrorf(t, "Unexpected %s:\n\n%s", msg, ret)
}
}

View File

@@ -0,0 +1,16 @@
#!/bin/bash
set -e
echo '# linux arm7'
GOARM=7 GOARCH=arm GOOS=linux go build
echo '# linux arm5'
GOARM=5 GOARCH=arm GOOS=linux go build
echo '# windows 386'
GOARCH=386 GOOS=windows go build
echo '# windows amd64'
GOARCH=amd64 GOOS=windows go build
echo '# darwin'
GOARCH=amd64 GOOS=darwin go build
echo '# freebsd'
GOARCH=amd64 GOOS=freebsd go build

View File

@@ -0,0 +1,59 @@
package flags
func levenshtein(s string, t string) int {
if len(s) == 0 {
return len(t)
}
if len(t) == 0 {
return len(s)
}
dists := make([][]int, len(s)+1)
for i := range dists {
dists[i] = make([]int, len(t)+1)
dists[i][0] = i
}
for j := range t {
dists[0][j] = j
}
for i, sc := range s {
for j, tc := range t {
if sc == tc {
dists[i+1][j+1] = dists[i][j]
} else {
dists[i+1][j+1] = dists[i][j] + 1
if dists[i+1][j] < dists[i+1][j+1] {
dists[i+1][j+1] = dists[i+1][j] + 1
}
if dists[i][j+1] < dists[i+1][j+1] {
dists[i+1][j+1] = dists[i][j+1] + 1
}
}
}
}
return dists[len(s)][len(t)]
}
func closestChoice(cmd string, choices []string) (string, int) {
if len(choices) == 0 {
return "", 0
}
mincmd := -1
mindist := -1
for i, c := range choices {
l := levenshtein(cmd, c)
if mincmd < 0 || l < mindist {
mindist = l
mincmd = i
}
}
return choices[mincmd], mindist
}

View File

@@ -0,0 +1,106 @@
package flags
// 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
// therefore also carries a set of command specific options.
type Command struct {
// Embedded, see Group for more information
*Group
// The name by which the command can be invoked
Name string
// The active sub command (set by parsing) or nil
Active *Command
// Whether subcommands are optional
SubcommandsOptional bool
// Aliases for the command
Aliases []string
// Whether positional arguments are required
ArgsRequired bool
commands []*Command
hasBuiltinHelpGroup bool
args []*Arg
}
// Commander is an interface which can be implemented by any command added in
// the options. When implemented, the Execute method will be called for the last
// specified (sub)command providing the remaining command line arguments.
type Commander interface {
// Execute will be called for the last active (sub)command. The
// args argument contains the remaining command line arguments. The
// error that Execute returns will be eventually passed out of the
// Parse method of the Parser.
Execute(args []string) error
}
// Usage is an interface which can be implemented to show a custom usage string
// in the help message shown for a command.
type Usage interface {
// Usage is called for commands to allow customized printing of command
// usage in the generated help message.
Usage() string
}
// 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
// Usage interfaces.
func (c *Command) AddCommand(command string, shortDescription string, longDescription string, data interface{}) (*Command, error) {
cmd := newCommand(command, shortDescription, longDescription, data)
cmd.parent = c
if err := cmd.scan(); err != nil {
return nil, err
}
c.commands = append(c.commands, cmd)
return cmd, nil
}
// 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.
func (c *Command) AddGroup(shortDescription string, longDescription string, data interface{}) (*Group, error) {
group := newGroup(shortDescription, longDescription, data)
group.parent = c
if err := group.scanType(c.scanSubcommandHandler(group)); err != nil {
return nil, err
}
c.groups = append(c.groups, group)
return group, nil
}
// Commands returns a list of subcommands of this command.
func (c *Command) Commands() []*Command {
return c.commands
}
// Find locates the subcommand with the given name and returns it. If no such
// command can be found Find will return nil.
func (c *Command) Find(name string) *Command {
for _, cc := range c.commands {
if cc.match(name) {
return cc
}
}
return nil
}
// Args returns a list of positional arguments associated with this command.
func (c *Command) Args() []*Arg {
ret := make([]*Arg, len(c.args))
copy(ret, c.args)
return ret
}

View File

@@ -0,0 +1,271 @@
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

@@ -0,0 +1,402 @@
package flags
import (
"fmt"
"testing"
)
func TestCommandInline(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Command struct {
G bool `short:"g"`
} `command:"cmd"`
}{}
p, ret := assertParserSuccess(t, &opts, "-v", "cmd", "-g")
assertStringArray(t, ret, []string{})
if p.Active == nil {
t.Errorf("Expected active command")
}
if !opts.Value {
t.Errorf("Expected Value to be true")
}
if !opts.Command.G {
t.Errorf("Expected Command.G to be true")
}
if p.Command.Find("cmd") != p.Active {
t.Errorf("Expected to find command `cmd' to be active")
}
}
func TestCommandInlineMulti(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
C1 struct {
} `command:"c1"`
C2 struct {
G bool `short:"g"`
} `command:"c2"`
}{}
p, ret := assertParserSuccess(t, &opts, "-v", "c2", "-g")
assertStringArray(t, ret, []string{})
if p.Active == nil {
t.Errorf("Expected active command")
}
if !opts.Value {
t.Errorf("Expected Value to be true")
}
if !opts.C2.G {
t.Errorf("Expected C2.G to be true")
}
if p.Command.Find("c1") == nil {
t.Errorf("Expected to find command `c1'")
}
if c2 := p.Command.Find("c2"); c2 == nil {
t.Errorf("Expected to find command `c2'")
} else if c2 != p.Active {
t.Errorf("Expected to find command `c2' to be active")
}
}
func TestCommandFlagOrder1(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Command struct {
G bool `short:"g"`
} `command:"cmd"`
}{}
assertParseFail(t, ErrUnknownFlag, "unknown flag `g'", &opts, "-v", "-g", "cmd")
}
func TestCommandFlagOrder2(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Command struct {
G bool `short:"g"`
} `command:"cmd"`
}{}
assertParseSuccess(t, &opts, "cmd", "-v", "-g")
if !opts.Value {
t.Errorf("Expected Value to be true")
}
if !opts.Command.G {
t.Errorf("Expected Command.G to be true")
}
}
func TestCommandFlagOverride1(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Command struct {
Value bool `short:"v"`
} `command:"cmd"`
}{}
assertParseSuccess(t, &opts, "-v", "cmd")
if !opts.Value {
t.Errorf("Expected Value to be true")
}
if opts.Command.Value {
t.Errorf("Expected Command.Value to be false")
}
}
func TestCommandFlagOverride2(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Command struct {
Value bool `short:"v"`
} `command:"cmd"`
}{}
assertParseSuccess(t, &opts, "cmd", "-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"`
Cmd1 struct {
} `command:"remove"`
Cmd2 struct {
} `command:"add"`
}{}
p := NewParser(&opts, None)
_, err := p.ParseArgs([]string{})
assertError(t, err, ErrCommandRequired, "Please specify one command of: add or remove")
}
func TestCommandEstimate2(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Cmd1 struct {
} `command:"remove"`
Cmd2 struct {
} `command:"add"`
}{}
p := NewParser(&opts, None)
_, err := p.ParseArgs([]string{"rmive"})
assertError(t, err, ErrUnknownCommand, "Unknown command `rmive', did you mean `remove'?")
}
type testCommand struct {
G bool `short:"g"`
Executed bool
EArgs []string
}
func (c *testCommand) Execute(args []string) error {
c.Executed = true
c.EArgs = args
return nil
}
func TestCommandExecute(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Command testCommand `command:"cmd"`
}{}
assertParseSuccess(t, &opts, "-v", "cmd", "-g", "a", "b")
if !opts.Value {
t.Errorf("Expected Value to be true")
}
if !opts.Command.Executed {
t.Errorf("Did not execute command")
}
if !opts.Command.G {
t.Errorf("Expected Command.C to be true")
}
assertStringArray(t, opts.Command.EArgs, []string{"a", "b"})
}
func TestCommandClosest(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Cmd1 struct {
} `command:"remove"`
Cmd2 struct {
} `command:"add"`
}{}
args := assertParseFail(t, ErrUnknownCommand, "Unknown command `addd', did you mean `add'?", &opts, "-v", "addd")
assertStringArray(t, args, []string{"addd"})
}
func TestCommandAdd(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
}{}
var cmd = struct {
G bool `short:"g"`
}{}
p := NewParser(&opts, Default)
c, err := p.AddCommand("cmd", "", "", &cmd)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
return
}
ret, err := p.ParseArgs([]string{"-v", "cmd", "-g", "rest"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
return
}
assertStringArray(t, ret, []string{"rest"})
if !opts.Value {
t.Errorf("Expected Value to be true")
}
if !cmd.G {
t.Errorf("Expected Command.G to be true")
}
if p.Command.Find("cmd") != c {
t.Errorf("Expected to find command `cmd'")
}
if p.Commands()[0] != c {
t.Errorf("Expected command %#v, but got %#v", c, p.Commands()[0])
}
if c.Options()[0].ShortName != 'g' {
t.Errorf("Expected short name `g' but got %v", c.Options()[0].ShortName)
}
}
func TestCommandNestedInline(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Command struct {
G bool `short:"g"`
Nested struct {
N string `long:"n"`
} `command:"nested"`
} `command:"cmd"`
}{}
p, ret := assertParserSuccess(t, &opts, "-v", "cmd", "-g", "nested", "--n", "n", "rest")
assertStringArray(t, ret, []string{"rest"})
if !opts.Value {
t.Errorf("Expected Value to be true")
}
if !opts.Command.G {
t.Errorf("Expected Command.G to be true")
}
assertString(t, opts.Command.Nested.N, "n")
if c := p.Command.Find("cmd"); c == nil {
t.Errorf("Expected to find command `cmd'")
} else {
if c != p.Active {
t.Errorf("Expected `cmd' to be the active parser command")
}
if nested := c.Find("nested"); nested == nil {
t.Errorf("Expected to find command `nested'")
} else if nested != c.Active {
t.Errorf("Expected to find command `nested' to be the active `cmd' command")
}
}
}
func TestRequiredOnCommand(t *testing.T) {
var opts = struct {
Value bool `short:"v" required:"true"`
Command struct {
G bool `short:"g"`
} `command:"cmd"`
}{}
assertParseFail(t, ErrRequired, fmt.Sprintf("the required flag `%cv' was not specified", defaultShortOptDelimiter), &opts, "cmd")
}
func TestRequiredAllOnCommand(t *testing.T) {
var opts = struct {
Value bool `short:"v" required:"true"`
Missing bool `long:"missing" required:"true"`
Command struct {
G bool `short:"g"`
} `command:"cmd"`
}{}
assertParseFail(t, ErrRequired, fmt.Sprintf("the required flags `%smissing' and `%cv' were not specified", defaultLongOptDelimiter, defaultShortOptDelimiter), &opts, "cmd")
}
func TestDefaultOnCommand(t *testing.T) {
var opts = struct {
Command struct {
G bool `short:"g" default:"true"`
} `command:"cmd"`
}{}
assertParseSuccess(t, &opts, "cmd")
if !opts.Command.G {
t.Errorf("Expected G to be true")
}
}
func TestSubcommandsOptional(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
_, err := p.ParseArgs([]string{"-v"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
return
}
if !opts.Value {
t.Errorf("Expected Value to be true")
}
}
func TestCommandAlias(t *testing.T) {
var opts = struct {
Command struct {
G bool `short:"g" default:"true"`
} `command:"cmd" alias:"cm"`
}{}
assertParseSuccess(t, &opts, "cm")
if !opts.Command.G {
t.Errorf("Expected G to be true")
}
}

View File

@@ -0,0 +1,304 @@
package flags
import (
"fmt"
"path/filepath"
"reflect"
"sort"
"strings"
"unicode/utf8"
)
// Completion is a type containing information of a completion.
type Completion struct {
// The completed item
Item string
// A description of the completed item (optional)
Description string
}
type completions []Completion
func (c completions) Len() int {
return len(c)
}
func (c completions) Less(i, j int) bool {
return c[i].Item < c[j].Item
}
func (c completions) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}
// Completer is an interface which can be implemented by types
// to provide custom command line argument completion.
type Completer interface {
// Complete receives a prefix representing a (partial) value
// for its type and should provide a list of possible valid
// completions.
Complete(match string) []Completion
}
type completion struct {
parser *Parser
ShowDescriptions bool
}
// Filename is a string alias which provides filename completion.
type Filename string
func completionsWithoutDescriptions(items []string) []Completion {
ret := make([]Completion, len(items))
for i, v := range items {
ret[i].Item = v
}
return ret
}
// Complete returns a list of existing files with the given
// prefix.
func (f *Filename) Complete(match string) []Completion {
ret, _ := filepath.Glob(match + "*")
return completionsWithoutDescriptions(ret)
}
func (c *completion) skipPositional(s *parseState, n int) {
if n >= len(s.positional) {
s.positional = nil
} else {
s.positional = s.positional[n:]
}
}
func (c *completion) completeOptionNames(names map[string]*Option, prefix string, match string) []Completion {
n := make([]Completion, 0, len(names))
for k, opt := range names {
if strings.HasPrefix(k, match) {
n = append(n, Completion{
Item: prefix + k,
Description: opt.Description,
})
}
}
return n
}
func (c *completion) completeLongNames(s *parseState, prefix string, match string) []Completion {
return c.completeOptionNames(s.lookup.longNames, prefix, match)
}
func (c *completion) completeShortNames(s *parseState, prefix string, match string) []Completion {
if len(match) != 0 {
return []Completion{
Completion{
Item: prefix + match,
},
}
}
return c.completeOptionNames(s.lookup.shortNames, prefix, match)
}
func (c *completion) completeCommands(s *parseState, match string) []Completion {
n := make([]Completion, 0, len(s.command.commands))
for _, cmd := range s.command.commands {
if cmd.data != c && strings.HasPrefix(cmd.Name, match) {
n = append(n, Completion{
Item: cmd.Name,
Description: cmd.ShortDescription,
})
}
}
return n
}
func (c *completion) completeValue(value reflect.Value, prefix string, match string) []Completion {
i := value.Interface()
var ret []Completion
if cmp, ok := i.(Completer); ok {
ret = cmp.Complete(match)
} else if value.CanAddr() {
if cmp, ok = value.Addr().Interface().(Completer); ok {
ret = cmp.Complete(match)
}
}
for i, v := range ret {
ret[i].Item = prefix + v.Item
}
return ret
}
func (c *completion) completeArg(arg *Arg, prefix string, match string) []Completion {
if arg.isRemaining() {
// For remaining positional args (that are parsed into a slice), complete
// based on the element type.
return c.completeValue(reflect.New(arg.value.Type().Elem()), prefix, match)
}
return c.completeValue(arg.value, prefix, match)
}
func (c *completion) complete(args []string) []Completion {
if len(args) == 0 {
args = []string{""}
}
s := &parseState{
args: args,
}
c.parser.fillParseState(s)
var opt *Option
for len(s.args) > 1 {
arg := s.pop()
if (c.parser.Options&PassDoubleDash) != None && arg == "--" {
opt = nil
c.skipPositional(s, len(s.args)-1)
break
}
if argumentIsOption(arg) {
prefix, optname, islong := stripOptionPrefix(arg)
optname, _, argument := splitOption(prefix, optname, islong)
if argument == nil {
var o *Option
canarg := true
if islong {
o = s.lookup.longNames[optname]
} else {
for i, r := range optname {
sname := string(r)
o = s.lookup.shortNames[sname]
if o == nil {
break
}
if i == 0 && o.canArgument() && len(optname) != len(sname) {
canarg = false
break
}
}
}
if o == nil && (c.parser.Options&PassAfterNonOption) != None {
opt = nil
c.skipPositional(s, len(s.args)-1)
break
} else if o != nil && o.canArgument() && !o.OptionalArgument && canarg {
if len(s.args) > 1 {
s.pop()
} else {
opt = o
}
}
}
} else {
if len(s.positional) > 0 {
if !s.positional[0].isRemaining() {
// Don't advance beyond a remaining positional arg (because
// it consumes all subsequent args).
s.positional = s.positional[1:]
}
} else if cmd, ok := s.lookup.commands[arg]; ok {
cmd.fillParseState(s)
}
opt = nil
}
}
lastarg := s.args[len(s.args)-1]
var ret []Completion
if opt != nil {
// Completion for the argument of 'opt'
ret = c.completeValue(opt.value, "", lastarg)
} else if argumentStartsOption(lastarg) {
// Complete the option
prefix, optname, islong := stripOptionPrefix(lastarg)
optname, split, argument := splitOption(prefix, optname, islong)
if argument == nil && !islong {
rname, n := utf8.DecodeRuneInString(optname)
sname := string(rname)
if opt := s.lookup.shortNames[sname]; opt != nil && opt.canArgument() {
ret = c.completeValue(opt.value, prefix+sname, optname[n:])
} else {
ret = c.completeShortNames(s, prefix, optname)
}
} else if argument != nil {
if islong {
opt = s.lookup.longNames[optname]
} else {
opt = s.lookup.shortNames[optname]
}
if opt != nil {
ret = c.completeValue(opt.value, prefix+optname+split, *argument)
}
} else if islong {
ret = c.completeLongNames(s, prefix, optname)
} else {
ret = c.completeShortNames(s, prefix, optname)
}
} else if len(s.positional) > 0 {
// Complete for positional argument
ret = c.completeArg(s.positional[0], "", lastarg)
} else if len(s.command.commands) > 0 {
// Complete for command
ret = c.completeCommands(s, lastarg)
}
sort.Sort(completions(ret))
return ret
}
func (c *completion) execute(args []string) {
ret := c.complete(args)
if c.ShowDescriptions && len(ret) > 1 {
maxl := 0
for _, v := range ret {
if len(v.Item) > maxl {
maxl = len(v.Item)
}
}
for _, v := range ret {
fmt.Printf("%s", v.Item)
if len(v.Description) > 0 {
fmt.Printf("%s # %s", strings.Repeat(" ", maxl-len(v.Item)), v.Description)
}
fmt.Printf("\n")
}
} else {
for _, v := range ret {
fmt.Println(v.Item)
}
}
}

View File

@@ -0,0 +1,289 @@
package flags
import (
"bytes"
"io"
"os"
"path"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
)
type TestComplete struct {
}
func (t *TestComplete) Complete(match string) []Completion {
options := []string{
"hello world",
"hello universe",
"hello multiverse",
}
ret := make([]Completion, 0, len(options))
for _, o := range options {
if strings.HasPrefix(o, match) {
ret = append(ret, Completion{
Item: o,
})
}
}
return ret
}
var completionTestOptions struct {
Verbose bool `short:"v" long:"verbose" description:"Verbose messages"`
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"`
AddCommand struct {
Positional struct {
Filename Filename
} `positional-args:"yes"`
} `command:"add" description:"add an item"`
AddMultiCommand struct {
Positional struct {
Filename []Filename
} `positional-args:"yes"`
} `command:"add-multi" description:"add multiple items"`
RemoveCommand struct {
Other bool `short:"o"`
File Filename `short:"f" long:"filename"`
} `command:"rm" description:"remove an item"`
RenameCommand struct {
Completed TestComplete `short:"c" long:"completed"`
} `command:"rename" description:"rename an item"`
}
type completionTest struct {
Args []string
Completed []string
ShowDescriptions bool
}
var completionTests []completionTest
func init() {
_, sourcefile, _, _ := runtime.Caller(0)
completionTestSourcedir := filepath.Join(filepath.SplitList(path.Dir(sourcefile))...)
completionTestFilename := []string{filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion_test.go")}
completionTests = []completionTest{
{
// Short names
[]string{"-"},
[]string{"-d", "-v"},
false,
},
{
// Short names concatenated
[]string{"-dv"},
[]string{"-dv"},
false,
},
{
// Long names
[]string{"--"},
[]string{"--debug", "--required", "--verbose", "--version"},
false,
},
{
// Long names with descriptions
[]string{"--"},
[]string{
"--debug # Enable debug",
"--required # This is required",
"--verbose # Verbose messages",
"--version # Show version",
},
true,
},
{
// Long names partial
[]string{"--ver"},
[]string{"--verbose", "--version"},
false,
},
{
// Commands
[]string{""},
[]string{"add", "add-multi", "rename", "rm"},
false,
},
{
// Commands with descriptions
[]string{""},
[]string{
"add # add an item",
"add-multi # add multiple items",
"rename # rename an item",
"rm # remove an item",
},
true,
},
{
// Commands partial
[]string{"r"},
[]string{"rename", "rm"},
false,
},
{
// Positional filename
[]string{"add", filepath.Join(completionTestSourcedir, "completion")},
completionTestFilename,
false,
},
{
// Multiple positional filename (1 arg)
[]string{"add-multi", filepath.Join(completionTestSourcedir, "completion")},
completionTestFilename,
false,
},
{
// Multiple positional filename (2 args)
[]string{"add-multi", filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion")},
completionTestFilename,
false,
},
{
// Multiple positional filename (3 args)
[]string{"add-multi", filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion")},
completionTestFilename,
false,
},
{
// Flag filename
[]string{"rm", "-f", path.Join(completionTestSourcedir, "completion")},
completionTestFilename,
false,
},
{
// Flag short concat last filename
[]string{"rm", "-of", path.Join(completionTestSourcedir, "completion")},
completionTestFilename,
false,
},
{
// Flag concat filename
[]string{"rm", "-f" + path.Join(completionTestSourcedir, "completion")},
[]string{"-f" + completionTestFilename[0], "-f" + completionTestFilename[1]},
false,
},
{
// Flag equal concat filename
[]string{"rm", "-f=" + path.Join(completionTestSourcedir, "completion")},
[]string{"-f=" + completionTestFilename[0], "-f=" + completionTestFilename[1]},
false,
},
{
// Flag concat long filename
[]string{"rm", "--filename=" + path.Join(completionTestSourcedir, "completion")},
[]string{"--filename=" + completionTestFilename[0], "--filename=" + completionTestFilename[1]},
false,
},
{
// Flag long filename
[]string{"rm", "--filename", path.Join(completionTestSourcedir, "completion")},
completionTestFilename,
false,
},
{
// Custom completed
[]string{"rename", "-c", "hello un"},
[]string{"hello universe"},
false,
},
}
}
func TestCompletion(t *testing.T) {
p := NewParser(&completionTestOptions, Default)
c := &completion{parser: p}
for _, test := range completionTests {
if test.ShowDescriptions {
continue
}
ret := c.complete(test.Args)
items := make([]string, len(ret))
for i, v := range ret {
items[i] = v.Item
}
if !reflect.DeepEqual(items, test.Completed) {
t.Errorf("Args: %#v, %#v\n Expected: %#v\n Got: %#v", test.Args, test.ShowDescriptions, test.Completed, items)
}
}
}
func TestParserCompletion(t *testing.T) {
for _, test := range completionTests {
if test.ShowDescriptions {
os.Setenv("GO_FLAGS_COMPLETION", "verbose")
} else {
os.Setenv("GO_FLAGS_COMPLETION", "1")
}
tmp := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
out := make(chan string)
go func() {
var buf bytes.Buffer
io.Copy(&buf, r)
out <- buf.String()
}()
p := NewParser(&completionTestOptions, None)
_, err := p.ParseArgs(test.Args)
w.Close()
os.Stdout = tmp
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
got := strings.Split(strings.Trim(<-out, "\n"), "\n")
if !reflect.DeepEqual(got, test.Completed) {
t.Errorf("Expected: %#v\nGot: %#v", test.Completed, got)
}
}
os.Setenv("GO_FLAGS_COMPLETION", "")
}

View File

@@ -0,0 +1,377 @@
// Copyright 2012 Jesse van den Kieboom. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package flags
import (
"fmt"
"reflect"
"strconv"
"strings"
"time"
)
// Marshaler is the interface implemented by types that can marshal themselves
// to a string representation of the flag.
type Marshaler interface {
// MarshalFlag marshals a flag value to its string representation.
MarshalFlag() (string, error)
}
// Unmarshaler is the interface implemented by types that can unmarshal a flag
// argument to themselves. The provided value is directly passed from the
// command line.
type Unmarshaler interface {
// UnmarshalFlag unmarshals a string value representation to the flag
// value (which therefore needs to be a pointer receiver).
UnmarshalFlag(value string) error
}
func getBase(options multiTag, base int) (int, error) {
sbase := options.Get("base")
var err error
var ivbase int64
if sbase != "" {
ivbase, err = strconv.ParseInt(sbase, 10, 32)
base = int(ivbase)
}
return base, err
}
func convertMarshal(val reflect.Value) (bool, string, error) {
// Check first for the Marshaler interface
if val.Type().NumMethod() > 0 && val.CanInterface() {
if marshaler, ok := val.Interface().(Marshaler); ok {
ret, err := marshaler.MarshalFlag()
return true, ret, err
}
}
return false, "", nil
}
func convertToString(val reflect.Value, options multiTag) (string, error) {
if ok, ret, err := convertMarshal(val); ok {
return ret, err
}
tp := val.Type()
// Support for time.Duration
if tp == reflect.TypeOf((*time.Duration)(nil)).Elem() {
stringer := val.Interface().(fmt.Stringer)
return stringer.String(), nil
}
switch tp.Kind() {
case reflect.String:
return val.String(), nil
case reflect.Bool:
if val.Bool() {
return "true", nil
}
return "false", nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
base, err := getBase(options, 10)
if err != nil {
return "", err
}
return strconv.FormatInt(val.Int(), base), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
base, err := getBase(options, 10)
if err != nil {
return "", err
}
return strconv.FormatUint(val.Uint(), base), nil
case reflect.Float32, reflect.Float64:
return strconv.FormatFloat(val.Float(), 'g', -1, tp.Bits()), nil
case reflect.Slice:
if val.Len() == 0 {
return "", nil
}
ret := "["
for i := 0; i < val.Len(); i++ {
if i != 0 {
ret += ", "
}
item, err := convertToString(val.Index(i), options)
if err != nil {
return "", err
}
ret += item
}
return ret + "]", nil
case reflect.Map:
ret := "{"
for i, key := range val.MapKeys() {
if i != 0 {
ret += ", "
}
keyitem, err := convertToString(key, options)
if err != nil {
return "", err
}
item, err := convertToString(val.MapIndex(key), options)
if err != nil {
return "", err
}
ret += keyitem + ":" + item
}
return ret + "}", nil
case reflect.Ptr:
return convertToString(reflect.Indirect(val), options)
case reflect.Interface:
if !val.IsNil() {
return convertToString(val.Elem(), options)
}
}
return "", nil
}
func convertUnmarshal(val string, retval reflect.Value) (bool, error) {
if retval.Type().NumMethod() > 0 && retval.CanInterface() {
if unmarshaler, ok := retval.Interface().(Unmarshaler); ok {
return true, unmarshaler.UnmarshalFlag(val)
}
}
if retval.Type().Kind() != reflect.Ptr && retval.CanAddr() {
return convertUnmarshal(val, retval.Addr())
}
if retval.Type().Kind() == reflect.Interface && !retval.IsNil() {
return convertUnmarshal(val, retval.Elem())
}
return false, nil
}
func convert(val string, retval reflect.Value, options multiTag) error {
if ok, err := convertUnmarshal(val, retval); ok {
return err
}
tp := retval.Type()
// Support for time.Duration
if tp == reflect.TypeOf((*time.Duration)(nil)).Elem() {
parsed, err := time.ParseDuration(val)
if err != nil {
return err
}
retval.SetInt(int64(parsed))
return nil
}
switch tp.Kind() {
case reflect.String:
retval.SetString(val)
case reflect.Bool:
if val == "" {
retval.SetBool(true)
} else {
b, err := strconv.ParseBool(val)
if err != nil {
return err
}
retval.SetBool(b)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
base, err := getBase(options, 10)
if err != nil {
return err
}
parsed, err := strconv.ParseInt(val, base, tp.Bits())
if err != nil {
return err
}
retval.SetInt(parsed)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
base, err := getBase(options, 10)
if err != nil {
return err
}
parsed, err := strconv.ParseUint(val, base, tp.Bits())
if err != nil {
return err
}
retval.SetUint(parsed)
case reflect.Float32, reflect.Float64:
parsed, err := strconv.ParseFloat(val, tp.Bits())
if err != nil {
return err
}
retval.SetFloat(parsed)
case reflect.Slice:
elemtp := tp.Elem()
elemvalptr := reflect.New(elemtp)
elemval := reflect.Indirect(elemvalptr)
if err := convert(val, elemval, options); err != nil {
return err
}
retval.Set(reflect.Append(retval, elemval))
case reflect.Map:
parts := strings.SplitN(val, ":", 2)
key := parts[0]
var value string
if len(parts) == 2 {
value = parts[1]
}
keytp := tp.Key()
keyval := reflect.New(keytp)
if err := convert(key, keyval, options); err != nil {
return err
}
valuetp := tp.Elem()
valueval := reflect.New(valuetp)
if err := convert(value, valueval, options); err != nil {
return err
}
if retval.IsNil() {
retval.Set(reflect.MakeMap(tp))
}
retval.SetMapIndex(reflect.Indirect(keyval), reflect.Indirect(valueval))
case reflect.Ptr:
if retval.IsNil() {
retval.Set(reflect.New(retval.Type().Elem()))
}
return convert(val, reflect.Indirect(retval), options)
case reflect.Interface:
if !retval.IsNil() {
return convert(val, retval.Elem(), options)
}
}
return nil
}
func isPrint(s string) bool {
for _, c := range s {
if !strconv.IsPrint(c) {
return false
}
}
return true
}
func quoteIfNeeded(s string) string {
if !isPrint(s) {
return strconv.Quote(s)
}
return s
}
func quoteIfNeededV(s []string) []string {
ret := make([]string, len(s))
for i, v := range s {
ret[i] = quoteIfNeeded(v)
}
return ret
}
func quoteV(s []string) []string {
ret := make([]string, len(s))
for i, v := range s {
ret[i] = strconv.Quote(v)
}
return ret
}
func unquoteIfPossible(s string) (string, error) {
if len(s) == 0 || s[0] != '"' {
return s, nil
}
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

@@ -0,0 +1,175 @@
package flags
import (
"testing"
"time"
)
func expectConvert(t *testing.T, o *Option, expected string) {
s, err := convertToString(o.value, o.tag)
if err != nil {
t.Errorf("Unexpected error: %v", err)
return
}
assertString(t, s, expected)
}
func TestConvertToString(t *testing.T) {
d, _ := time.ParseDuration("1h2m4s")
var opts = struct {
String string `long:"string"`
Int int `long:"int"`
Int8 int8 `long:"int8"`
Int16 int16 `long:"int16"`
Int32 int32 `long:"int32"`
Int64 int64 `long:"int64"`
Uint uint `long:"uint"`
Uint8 uint8 `long:"uint8"`
Uint16 uint16 `long:"uint16"`
Uint32 uint32 `long:"uint32"`
Uint64 uint64 `long:"uint64"`
Float32 float32 `long:"float32"`
Float64 float64 `long:"float64"`
Duration time.Duration `long:"duration"`
Bool bool `long:"bool"`
IntSlice []int `long:"int-slice"`
IntFloatMap map[int]float64 `long:"int-float-map"`
PtrBool *bool `long:"ptr-bool"`
Interface interface{} `long:"interface"`
Int32Base int32 `long:"int32-base" base:"16"`
Uint32Base uint32 `long:"uint32-base" base:"16"`
}{
"string",
-2,
-1,
0,
1,
2,
1,
2,
3,
4,
5,
1.2,
-3.4,
d,
true,
[]int{-3, 4, -2},
map[int]float64{-2: 4.5},
new(bool),
float32(5.2),
-5823,
4232,
}
p := NewNamedParser("test", Default)
grp, _ := p.AddGroup("test group", "", &opts)
expects := []string{
"string",
"-2",
"-1",
"0",
"1",
"2",
"1",
"2",
"3",
"4",
"5",
"1.2",
"-3.4",
"1h2m4s",
"true",
"[-3, 4, -2]",
"{-2:4.5}",
"false",
"5.2",
"-16bf",
"1088",
}
for i, v := range grp.Options() {
expectConvert(t, v, expects[i])
}
}
func TestConvertToStringInvalidIntBase(t *testing.T) {
var opts = struct {
Int int `long:"int" base:"no"`
}{
2,
}
p := NewNamedParser("test", Default)
grp, _ := p.AddGroup("test group", "", &opts)
o := grp.Options()[0]
_, err := convertToString(o.value, o.tag)
if err != nil {
err = newErrorf(ErrMarshal, "%v", err)
}
assertError(t, err, ErrMarshal, "strconv.ParseInt: parsing \"no\": invalid syntax")
}
func TestConvertToStringInvalidUintBase(t *testing.T) {
var opts = struct {
Uint uint `long:"uint" base:"no"`
}{
2,
}
p := NewNamedParser("test", Default)
grp, _ := p.AddGroup("test group", "", &opts)
o := grp.Options()[0]
_, err := convertToString(o.value, o.tag)
if err != nil {
err = newErrorf(ErrMarshal, "%v", err)
}
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

@@ -0,0 +1,123 @@
package flags
import (
"fmt"
)
// ErrorType represents the type of error.
type ErrorType uint
const (
// ErrUnknown indicates a generic error.
ErrUnknown ErrorType = iota
// ErrExpectedArgument indicates that an argument was expected.
ErrExpectedArgument
// ErrUnknownFlag indicates an unknown flag.
ErrUnknownFlag
// ErrUnknownGroup indicates an unknown group.
ErrUnknownGroup
// ErrMarshal indicates a marshalling error while converting values.
ErrMarshal
// ErrHelp indicates that the built-in help was shown (the error
// contains the help message).
ErrHelp
// ErrNoArgumentForBool indicates that an argument was given for a
// boolean flag (which don't not take any arguments).
ErrNoArgumentForBool
// ErrRequired indicates that a required flag was not provided.
ErrRequired
// ErrShortNameTooLong indicates that a short flag name was specified,
// longer than one character.
ErrShortNameTooLong
// ErrDuplicatedFlag indicates that a short or long flag has been
// defined more than once
ErrDuplicatedFlag
// ErrTag indicates an error while parsing flag tags.
ErrTag
// ErrCommandRequired indicates that a command was required but not
// specified
ErrCommandRequired
// ErrUnknownCommand indicates that an unknown command was specified.
ErrUnknownCommand
)
func (e ErrorType) String() string {
switch e {
case ErrUnknown:
return "unknown"
case ErrExpectedArgument:
return "expected argument"
case ErrUnknownFlag:
return "unknown flag"
case ErrUnknownGroup:
return "unknown group"
case ErrMarshal:
return "marshal"
case ErrHelp:
return "help"
case ErrNoArgumentForBool:
return "no argument for bool"
case ErrRequired:
return "required"
case ErrShortNameTooLong:
return "short name too long"
case ErrDuplicatedFlag:
return "duplicated flag"
case ErrTag:
return "tag"
case ErrCommandRequired:
return "command required"
case ErrUnknownCommand:
return "unknown command"
}
return "unrecognized error type"
}
// Error represents a parser error. The error returned from Parse is of this
// type. The error contains both a Type and Message.
type Error struct {
// The type of error
Type ErrorType
// The error message
Message string
}
// Error returns the error's message
func (e *Error) Error() string {
return e.Message
}
func newError(tp ErrorType, message string) *Error {
return &Error{
Type: tp,
Message: message,
}
}
func newErrorf(tp ErrorType, format string, args ...interface{}) *Error {
return newError(tp, fmt.Sprintf(format, args...))
}
func wrapError(err error) *Error {
ret, ok := err.(*Error)
if !ok {
return newError(ErrUnknown, err.Error())
}
return ret
}

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