Compare commits
8 Commits
v0.7.0
...
v0.1.0-doc
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c1f047bcc6 | ||
![]() |
f2b54348c4 | ||
![]() |
fea113da98 | ||
![]() |
cbd77a7f61 | ||
![]() |
f0790c134c | ||
![]() |
385c73697d | ||
![]() |
f4a0fd299e | ||
![]() |
fb5b90f308 |
25
.github/ISSUE_TEMPLATE.md
vendored
@@ -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
@@ -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
|
||||
|
@@ -1,2 +0,0 @@
|
||||
go:
|
||||
enabled: true
|
34
.travis.yml
@@ -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
|
||||
|
126
CHANGELOG.md
@@ -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
|
116
CONTRIBUTING.md
@@ -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
|
||||
-----------
|
||||
|
||||
|
57
Dockerfile
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,2 @@
|
||||
/pkg
|
||||
/bin
|
2
Godeps/_workspace/src/bazil.org/fuse/.gitattributes
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.go filter=gofmt
|
||||
*.cgo filter=gofmt
|
11
Godeps/_workspace/src/bazil.org/fuse/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
*~
|
||||
.#*
|
||||
## the next line needs to start with a backslash to avoid looking like
|
||||
## a comment
|
||||
\#*#
|
||||
.*.swp
|
||||
|
||||
*.test
|
||||
|
||||
/clockfs
|
||||
/hellofs
|
0
vendor/src/bazil.org/fuse/LICENSE → Godeps/_workspace/src/bazil.org/fuse/LICENSE
generated
vendored
0
vendor/src/bazil.org/fuse/README.md → Godeps/_workspace/src/bazil.org/fuse/README.md
generated
vendored
0
vendor/src/bazil.org/fuse/buffer.go → Godeps/_workspace/src/bazil.org/fuse/buffer.go
generated
vendored
0
vendor/src/bazil.org/fuse/debug.go → Godeps/_workspace/src/bazil.org/fuse/debug.go
generated
vendored
4
Godeps/_workspace/src/bazil.org/fuse/doc/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/*.seq.svg
|
||||
|
||||
# not ignoring *.seq.png; we want those committed to the repo
|
||||
# for embedding on Github
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
@@ -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)
|
||||
}
|
||||
}
|
@@ -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)
|
1
Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/doc.go
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
package fstestutil
|
@@ -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...)
|
||||
}
|
@@ -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))
|
||||
}
|
449
vendor/src/bazil.org/fuse/fs/serve.go → Godeps/_workspace/src/bazil.org/fuse/fs/serve.go
generated
vendored
@@ -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 {
|
@@ -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)
|
||||
}
|
||||
}
|
172
vendor/src/bazil.org/fuse/fuse.go → Godeps/_workspace/src/bazil.org/fuse/fuse.go
generated
vendored
@@ -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)
|
||||
}
|
@@ -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
|
||||
}
|
31
Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
@@ -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
@@ -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
@@ -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
|
||||
}
|
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
}
|
9
Godeps/_workspace/src/bazil.org/fuse/options_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package fuse
|
||||
|
||||
func localVolume(conf *mountConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func volumeName(name string) MountOption {
|
||||
return dummyOption
|
||||
}
|
9
Godeps/_workspace/src/bazil.org/fuse/options_linux.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package fuse
|
||||
|
||||
func localVolume(conf *mountConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func volumeName(name string) MountOption {
|
||||
return dummyOption
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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
|
35
Godeps/_workspace/src/github.com/jessevdk/go-flags/.travis.yml
generated
vendored
Normal 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="
|
@@ -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
|
131
Godeps/_workspace/src/github.com/jessevdk/go-flags/README.md
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
go-flags: a go library for parsing command line arguments
|
||||
=========================================================
|
||||
|
||||
[](https://godoc.org/github.com/jessevdk/go-flags) [](https://travis-ci.org/jessevdk/go-flags) [](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>
|
21
Godeps/_workspace/src/github.com/jessevdk/go-flags/arg.go
generated
vendored
Normal 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
|
||||
}
|
53
Godeps/_workspace/src/github.com/jessevdk/go-flags/arg_test.go
generated
vendored
Normal 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")
|
||||
}
|
177
Godeps/_workspace/src/github.com/jessevdk/go-flags/assert_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
16
Godeps/_workspace/src/github.com/jessevdk/go-flags/check_crosscompile.sh
generated
vendored
Normal 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
|
59
Godeps/_workspace/src/github.com/jessevdk/go-flags/closest.go
generated
vendored
Normal 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
|
||||
}
|
106
Godeps/_workspace/src/github.com/jessevdk/go-flags/command.go
generated
vendored
Normal 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
|
||||
}
|
271
Godeps/_workspace/src/github.com/jessevdk/go-flags/command_private.go
generated
vendored
Normal 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
|
||||
}
|
402
Godeps/_workspace/src/github.com/jessevdk/go-flags/command_test.go
generated
vendored
Normal 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")
|
||||
}
|
||||
}
|
304
Godeps/_workspace/src/github.com/jessevdk/go-flags/completion.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
289
Godeps/_workspace/src/github.com/jessevdk/go-flags/completion_test.go
generated
vendored
Normal 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", "")
|
||||
}
|
377
Godeps/_workspace/src/github.com/jessevdk/go-flags/convert.go
generated
vendored
Normal 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
|
||||
}
|
175
Godeps/_workspace/src/github.com/jessevdk/go-flags/convert_test.go
generated
vendored
Normal 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")
|
||||
}
|
123
Godeps/_workspace/src/github.com/jessevdk/go-flags/error.go
generated
vendored
Normal 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
|
||||
}
|