Compare commits

..

8 Commits

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

View File

@@ -1,63 +0,0 @@
<!--
NOTE: Not filling out the issue template needs a good reason, otherwise it may
take a lot longer to find the problem! Please take the time to help us
debugging the problem by collecting information, even if it seems irrelevant to
you. Thanks!
If you have a question, the forum at https://discourse.restic.net is a better place.
Please do not create issues for usage or documentation questions! We're using
the GitHub issue tracker mainly for tracking bugs and feature requests.
-->
## Output of `restic version`
## How did you run restic exactly?
<!--
This section should include at least:
* The complete command line and any environment variables you used to
configure restic's backend access. Make sure to replace sensitive values!
* The output of the commands, what restic prints gives may give us much
information to diagnose the problem!
-->
## What backend/server/service did you use to store the repository?
## Expected behavior
<!--
Describe what you'd like restic to do differently.
-->
## Actual behavior
<!--
In this section, please try to concentrate on observations, so only describe
what you observed directly.
-->
## Steps to reproduce the behavior
<!--
The more time you spend describing an easy way to reproduce the behavior (if
this is possible), the easier it is for the project developers to fix it!
-->
## Do you have any idea what may have caused this?
## Do you have an idea how to solve the issue?
## Did restic help you or made you happy in any way?
<!--
Answering this question is not required, but if you have anything positive to share, please do so here!
Sometimes we get tired of reading bug reports all day and a little positive end note does wonders.
Idea by Joey Hess, https://joeyh.name/blog/entry/two_holiday_stories/
-->

View File

@@ -1,31 +0,0 @@
<!--
Thank you very much for contributing code or documentation to restic! Please
fill out the following questions to make it easier for us to review your
changes.
You do not need to check all the boxes below all at once, feel free to take
your time and add more commits. If you're done and ready for review, please
check the last box.
-->
### What is the purpose of this change? What does it change?
<!--
Describe the changes here, as detailed as needed.
-->
### Was the change discussed in an issue or in the forum before?
<!--
Link issues and relevant forum posts here.
-->
### Checklist
- [ ] I have read the [Contribution Guidelines](https://github.com/restic/restic/blob/master/CONTRIBUTING.md#providing-patches)
- [ ] I have added tests for all changes in this PR
- [ ] I have added documentation for the changes (in the manual)
- [ ] There's a new file in a subdir of `changelog/x.y.z` that describe the changes for our users (template [here](https://github.com/restic/restic/blob/master/changelog/changelog-entry.tmpl))
- [ ] I have run `gofmt` on the code in all commits
- [ ] All commit messages are formatted in the same style as [the other commits in the repo](https://github.com/restic/restic/blob/master/CONTRIBUTING.md#git-commits)
- [ ] I'm done, this Pull Request is ready for review

7
.gitignore vendored
View File

@@ -1,3 +1,8 @@
/.gopath
/restic
/restic.debug
/dirdiff
cmd/dirdiff/dirdiff
cmd/gentestdata/gentestdata
cmd/restic/restic
/.vagrant
/doc/_build

View File

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

View File

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

View File

@@ -1,700 +0,0 @@
Changelog for restic 0.8.1 (2017-12-27)
=======================================
The following sections list the changes in restic 0.8.1 relevant to
restic users. The changes are ordered by importance.
Summary
-------
* Fix #1457: Improve s3 backend with DigitalOcean Spaces
* Fix #1454: Correct cache dir location for Windows and Darwin
* Fix #1459: Disable handling SIGPIPE
* Chg #1452: Do not save atime by default
* Enh #1436: Add code to detect old cache directories
* Enh #1439: Improve cancellation logic
* Enh #11: Add the `diff` command
Details
-------
* Bugfix #1457: Improve s3 backend with DigitalOcean Spaces
https://github.com/restic/restic/issues/1457
https://github.com/restic/restic/pull/1459
* Bugfix #1454: Correct cache dir location for Windows and Darwin
The cache directory on Windows and Darwin was not correct, instead the directory `.cache` was
used.
https://github.com/restic/restic/pull/1454
* Bugfix #1459: Disable handling SIGPIPE
We've disabled handling SIGPIPE again. Turns out, writing to broken TCP connections also
raised SIGPIPE, so restic exits on the first write to a broken connection. Instead, restic
should retry the request.
https://github.com/restic/restic/issues/1457
https://github.com/restic/restic/issues/1466
https://github.com/restic/restic/pull/1459
* Change #1452: Do not save atime by default
By default, the access time for files and dirs is not saved any more. It is not possible to
reliably disable updating the access time during a backup, so for the next backup the access
time is different again. This means a lot of metadata is saved. If you want to save the access time
anyway, pass `--with-atime` to the `backup` command.
https://github.com/restic/restic/pull/1452
* Enhancement #1436: Add code to detect old cache directories
We've added code to detect old cache directories of repositories that haven't been used in a
long time, restic now prints a note when it detects that such dirs exist. Also, the option
`--cleanup-cache` was added to automatically remove such directories. That's not a problem
because the cache will be rebuild once a repo is accessed again.
https://github.com/restic/restic/pull/1436
* Enhancement #1439: Improve cancellation logic
The cancellation logic was improved, restic can now shut down cleanly when requested to do so
(e.g. via ctrl+c).
https://github.com/restic/restic/pull/1439
* Enhancement #11: Add the `diff` command
The command `diff` was added, it allows comparing two snapshots and listing all differences.
https://github.com/restic/restic/issues/11
https://github.com/restic/restic/issues/1460
https://github.com/restic/restic/pull/1462
Changelog for restic 0.8.0 (2017-11-26)
=======================================
The following sections list the changes in restic 0.8.0 relevant to
restic users. The changes are ordered by importance.
Summary
-------
* Sec #1445: Prevent writing outside the target directory during restore
* Fix #1256: Re-enable workaround for S3 backend
* Fix #1291: Reuse backend TCP connections to BackBlaze B2
* Fix #1317: Run prune when `forget --prune` is called with just snapshot IDs
* Fix #1437: Remove implicit path `/restic` for the s3 backend
* Enh #1102: Add subdirectory `ids` to fuse mount
* Enh #1114: Add `--cacert` to specify TLS certificates to check against
* Enh #1216: Add upload/download limiting
* Enh #1271: Cache results for excludes for `backup`
* Enh #1274: Add `generate` command, replaces `manpage` and `autocomplete`
* Enh #1367: Allow comments in files read from via `--file-from`
* Enh #448: Sftp backend prompts for password
* Enh #510: Add `dump` command
* Enh #1040: Add local metadata cache
* Enh #1249: Add `latest` symlink in fuse mount
* Enh #1269: Add `--compact` to `forget` command
* Enh #1281: Google Cloud Storage backend needs less permissions
* Enh #1319: Make `check` print `no errors found` explicitly
* Enh #1353: Retry failed backend requests
Details
-------
* Security #1445: Prevent writing outside the target directory during restore
A vulnerability was found in the restic restorer, which allowed attackers in special
circumstances to restore files to a location outside of the target directory. Due to the
circumstances we estimate this to be a low-risk vulnerability, but urge all users to upgrade to
the latest version of restic.
Exploiting the vulnerability requires a Linux/Unix system which saves backups via restic and
a Windows systems which restores files from the repo. In addition, the attackers need to be able
to create create files with arbitrary names which are then saved to the restic repo. For
example, by creating a file named "..\test.txt" (which is a perfectly legal filename on Linux)
and restoring a snapshot containing this file on Windows, it would be written to the parent of
the target directory.
We'd like to thank Tyler Spivey for reporting this responsibly!
https://github.com/restic/restic/pull/1445
* Bugfix #1256: Re-enable workaround for S3 backend
We've re-enabled a workaround for `minio-go` (the library we're using to access s3 backends),
this reduces memory usage.
https://github.com/restic/restic/issues/1256
https://github.com/restic/restic/pull/1267
* Bugfix #1291: Reuse backend TCP connections to BackBlaze B2
A bug was discovered in the library we're using to access Backblaze, it now reuses already
established TCP connections which should be a lot faster and not cause network failures any
more.
https://github.com/restic/restic/issues/1291
https://github.com/restic/restic/pull/1301
* Bugfix #1317: Run prune when `forget --prune` is called with just snapshot IDs
A bug in the `forget` command caused `prune` not to be run when `--prune` was specified without a
policy, e.g. when only snapshot IDs that should be forgotten are listed manually.
https://github.com/restic/restic/pull/1317
* Bugfix #1437: Remove implicit path `/restic` for the s3 backend
The s3 backend used the subdir `restic` within a bucket if no explicit path after the bucket name
was specified. Since this version, restic does not use this default path any more. If you
created a repo on s3 in a bucket without specifying a path within the bucket, you need to add
`/restic` at the end of the repository specification to access your repo:
`s3:s3.amazonaws.com/bucket/restic`
https://github.com/restic/restic/issues/1292
https://github.com/restic/restic/pull/1437
* Enhancement #1102: Add subdirectory `ids` to fuse mount
The fuse mount now has an `ids` subdirectory which contains the snapshots below their (short)
IDs.
https://github.com/restic/restic/issues/1102
https://github.com/restic/restic/pull/1299
https://github.com/restic/restic/pull/1320
* Enhancement #1114: Add `--cacert` to specify TLS certificates to check against
We've added the `--cacert` option which can be used to pass one (or more) CA certificates to
restic. These are used in addition to the system CA certificates to verify HTTPS certificates
(e.g. for the REST backend).
https://github.com/restic/restic/issues/1114
https://github.com/restic/restic/pull/1276
* Enhancement #1216: Add upload/download limiting
We've added support for rate limiting through `--limit-upload` and `--limit-download`
flags.
https://github.com/restic/restic/issues/1216
https://github.com/restic/restic/pull/1336
https://github.com/restic/restic/pull/1358
* Enhancement #1271: Cache results for excludes for `backup`
The `backup` command now caches the result of excludes for a directory.
https://github.com/restic/restic/issues/1271
https://github.com/restic/restic/pull/1326
* Enhancement #1274: Add `generate` command, replaces `manpage` and `autocomplete`
The `generate` command has been added, which replaces the now removed commands `manpage` and
`autocomplete`. This release of restic contains the most recent manpages in `doc/man` and the
auto-completion files for bash and zsh in `doc/bash-completion.sh` and
`doc/zsh-completion.zsh`
https://github.com/restic/restic/issues/1274
https://github.com/restic/restic/pull/1282
* Enhancement #1367: Allow comments in files read from via `--file-from`
When the list of files/dirs to be saved is read from a file with `--files-from`, comment lines
(starting with `#`) are now ignored.
https://github.com/restic/restic/issues/1367
https://github.com/restic/restic/pull/1368
* Enhancement #448: Sftp backend prompts for password
The sftp backend now prompts for the password if a password is necessary for login.
https://github.com/restic/restic/issues/448
https://github.com/restic/restic/pull/1270
* Enhancement #510: Add `dump` command
We've added the `dump` command which prints a file from a snapshot to stdout. This can e.g. be
used to restore files read with `backup --stdin`.
https://github.com/restic/restic/issues/510
https://github.com/restic/restic/pull/1346
* Enhancement #1040: Add local metadata cache
We've added a local cache for metadata so that restic doesn't need to load all metadata
(snapshots, indexes, ...) from the repo each time it starts. By default the cache is active, but
there's a new global option `--no-cache` that can be used to disable the cache. By deafult, the
cache a standard cache folder for the OS, which can be overridden with `--cache-dir`. The cache
will automatically populate, indexes and snapshots are saved as they are loaded. Cache
directories for repos that haven't been used recently can automatically be removed by restic
with the `--cleanup-cache` option.
A related change was to by default create pack files in the repo that contain either data or
metadata, not both mixed together. This allows easy caching of only the metadata files. The
next run of `restic prune` will untangle mixed files automatically.
https://github.com/restic/restic/issues/29
https://github.com/restic/restic/issues/738
https://github.com/restic/restic/issues/282
https://github.com/restic/restic/pull/1040
https://github.com/restic/restic/pull/1287
https://github.com/restic/restic/pull/1436
https://github.com/restic/restic/pull/1265
* Enhancement #1249: Add `latest` symlink in fuse mount
The directory structure in the fuse mount now exposes a symlink `latest` which points to the
latest snapshot in that particular directory.
https://github.com/restic/restic/pull/1249
* Enhancement #1269: Add `--compact` to `forget` command
The option `--compact` was added to the `forget` command to provide the same compact view as the
`snapshots` command.
https://github.com/restic/restic/pull/1269
* Enhancement #1281: Google Cloud Storage backend needs less permissions
The Google Cloud Storage backend no longer requires the service account to have the
`storage.buckets.get` permission ("Storage Admin" role) in `restic init` if the bucket
already exists.
https://github.com/restic/restic/pull/1281
* Enhancement #1319: Make `check` print `no errors found` explicitly
The `check` command now explicetly prints `No errors were found` when no errors could be found.
https://github.com/restic/restic/issues/1303
https://github.com/restic/restic/pull/1319
* Enhancement #1353: Retry failed backend requests
https://github.com/restic/restic/pull/1353
Changelog for restic 0.7.3 (2017-09-20)
=======================================
The following sections list the changes in restic 0.7.3 relevant to
restic users. The changes are ordered by importance.
Summary
-------
* Fix #1246: List all files stored in Google Cloud Storage
Details
-------
* Bugfix #1246: List all files stored in Google Cloud Storage
For large backups stored in Google Cloud Storage, the `prune` command fails because listing
only returns the first 1000 files. This has been corrected, no data is lost in the process. In
addition, a plausibility check was added to `prune`.
https://github.com/restic/restic/issues/1246
https://github.com/restic/restic/pull/1247
Changelog for restic 0.7.2 (2017-09-13)
=======================================
The following sections list the changes in restic 0.7.2 relevant to
restic users. The changes are ordered by importance.
Summary
-------
* Fix #1167: Do not create a local repo unless `init` is used
* Fix #1164: Make the `key remove` command behave as documented
* Fix #1191: Make sure to write profiling files on interrupt
* Enh #1132: Make `key` command always prompt for a password
* Enh #1179: Resolve name conflicts, append a counter
* Enh #1218: Add `--compact` to `snapshots` command
* Enh #317: Add `--exclude-caches` and `--exclude-if-present`
* Enh #697: Automatically generate man pages for all restic commands
* Enh #1044: Improve `restore`, do not traverse/load excluded directories
* Enh #1061: Add Dockerfile and official Docker image
* Enh #1126: Use the standard Go git repository layout, use `dep` for vendoring
* Enh #1134: Add support for storing backups on Google Cloud Storage
* Enh #1144: Properly report errors when reading files with exclude patterns.
* Enh #1149: Add support for storing backups on Microsoft Azure Blob Storage
* Enh #1196: Add `--group-by` to `forget` command for flexible grouping
* Enh #1203: Print stats on all BSD systems when SIGINFO (ctrl+t) is received
* Enh #1205: Allow specifying time/date for a backup with `--time`
Details
-------
* Bugfix #1167: Do not create a local repo unless `init` is used
When a restic command other than `init` is used with a local repository and the repository
directory does not exist, restic creates the directory structure. That's an error, only the
`init` command should create the dir.
https://github.com/restic/restic/issues/1167
https://github.com/restic/restic/pull/1182
* Bugfix #1164: Make the `key remove` command behave as documented
https://github.com/restic/restic/pull/1164
* Bugfix #1191: Make sure to write profiling files on interrupt
Since a few releases restic had the ability to write profiling files for memory and CPU usage
when `debug` is enabled. It was discovered that when restic is interrupted (ctrl+c is
pressed), the proper shutdown hook is not run. This is now corrected.
https://github.com/restic/restic/pull/1191
* Enhancement #1132: Make `key` command always prompt for a password
The `key` command now prompts for a password even if the original password to access a repo has
been specified via the `RESTIC_PASSWORD` environment variable or a password file.
https://github.com/restic/restic/issues/1132
https://github.com/restic/restic/pull/1133
* Enhancement #1179: Resolve name conflicts, append a counter
https://github.com/restic/restic/issues/1179
https://github.com/restic/restic/pull/1209
* Enhancement #1218: Add `--compact` to `snapshots` command
The option `--compact` was added to the `snapshots` command to get a better overview of the
snapshots in a repo. It limits each snapshot to a single line.
https://github.com/restic/restic/issues/1218
https://github.com/restic/restic/pull/1223
* Enhancement #317: Add `--exclude-caches` and `--exclude-if-present`
A new option `--exclude-caches` was added that allows excluding cache directories (that are
tagged as such). This is a special case of a more generic option `--exclude-if-present` which
excludes a directory if a file with a specific name (and contents) is present.
https://github.com/restic/restic/issues/317
https://github.com/restic/restic/pull/1170
https://github.com/restic/restic/pull/1224
* Enhancement #697: Automatically generate man pages for all restic commands
https://github.com/restic/restic/issues/697
https://github.com/restic/restic/pull/1147
* Enhancement #1044: Improve `restore`, do not traverse/load excluded directories
https://github.com/restic/restic/pull/1044
* Enhancement #1061: Add Dockerfile and official Docker image
https://github.com/restic/restic/pull/1061
* Enhancement #1126: Use the standard Go git repository layout, use `dep` for vendoring
The git repository layout was changed to resemble the layout typically used in Go projects,
we're not using `gb` for building restic any more and vendoring the dependencies is now taken
care of by `dep`.
https://github.com/restic/restic/pull/1126
* Enhancement #1134: Add support for storing backups on Google Cloud Storage
https://github.com/restic/restic/issues/211
https://github.com/restic/restic/pull/1134
https://github.com/restic/restic/pull/1052
* Enhancement #1144: Properly report errors when reading files with exclude patterns.
https://github.com/restic/restic/pull/1144
* Enhancement #1149: Add support for storing backups on Microsoft Azure Blob Storage
The library we're using to access the service requires Go 1.8, so restic now needs at least Go
1.8.
https://github.com/restic/restic/issues/609
https://github.com/restic/restic/pull/1149
https://github.com/restic/restic/pull/1059
* Enhancement #1196: Add `--group-by` to `forget` command for flexible grouping
https://github.com/restic/restic/pull/1196
* Enhancement #1203: Print stats on all BSD systems when SIGINFO (ctrl+t) is received
https://github.com/restic/restic/pull/1203
https://github.com/restic/restic/pull/1082
* Enhancement #1205: Allow specifying time/date for a backup with `--time`
https://github.com/restic/restic/pull/1205
Changelog for restic 0.7.1 (2017-07-22)
=======================================
The following sections list the changes in restic 0.7.1 relevant to
restic users. The changes are ordered by importance.
Summary
-------
* Fix #1115: Fix `prune`, only include existing files in indexes
* Enh #1055: Create subdirs below `data/` for local/sftp backends
* Enh #1067: Allow loading credentials for s3 from IAM
* Enh #1073: Add `migrate` cmd to migrate from `s3legacy` to `default` layout
* Enh #1081: Clarify semantic for `--tasg` for the `forget` command
* Enh #1080: Ignore chmod() errors on filesystems which do not support it
* Enh #1082: Print stats on SIGINFO on Darwin and FreeBSD (ctrl+t)
Details
-------
* Bugfix #1115: Fix `prune`, only include existing files in indexes
A bug was found (and corrected) in the index rebuilding after prune, which led to indexes which
include blobs that were not present in the repo any more. There were already checks in place
which detected this situation and aborted with an error message. A new run of either `prune` or
`rebuild-index` corrected the index files. This is now fixed and a test has been added to detect
this.
https://github.com/restic/restic/pull/1115
* Enhancement #1055: Create subdirs below `data/` for local/sftp backends
The local and sftp backends now create the subdirs below `data/` on open/init. This way, restic
makes sure that they always exist. This is connected to an issue for the sftp server:
https://github.com/restic/restic/issues/1055
https://github.com/restic/restic/pull/1077
https://github.com/restic/restic/pull/1105
https://github.com/restic/rest-server/pull/11#issuecomment-309879710
* Enhancement #1067: Allow loading credentials for s3 from IAM
When no S3 credentials are specified in the environment variables, restic now tries to load
credentials from an IAM instance profile when the s3 backend is used.
https://github.com/restic/restic/issues/1067
https://github.com/restic/restic/pull/1086
* Enhancement #1073: Add `migrate` cmd to migrate from `s3legacy` to `default` layout
The `migrate` command for chaning the `s3legacy` layout to the `default` layout for s3
backends has been improved: It can now be restarted with `restic migrate --force s3_layout`
and automatically retries operations on error.
https://github.com/restic/restic/issues/1073
https://github.com/restic/restic/pull/1075
* Enhancement #1081: Clarify semantic for `--tasg` for the `forget` command
https://github.com/restic/restic/issues/1081
https://github.com/restic/restic/pull/1090
* Enhancement #1080: Ignore chmod() errors on filesystems which do not support it
https://github.com/restic/restic/pull/1080
https://github.com/restic/restic/pull/1112
* Enhancement #1082: Print stats on SIGINFO on Darwin and FreeBSD (ctrl+t)
https://github.com/restic/restic/pull/1082
Changelog for restic 0.7.0 (2017-07-01)
=======================================
The following sections list the changes in restic 0.7.0 relevant to
restic users. The changes are ordered by importance.
Summary
-------
* Fix #1013: Switch back to using the high-level minio-go API for s3
* Fix #965: Switch to `default` repo layout for the s3 backend
* Enh #1021: Detect invalid backend name and print error
* Enh #1029: Remove invalid pack files when `prune` is run
* Enh #512: Add Backblaze B2 backend
* Enh #636: Add dirs `tags` and `hosts` to fuse mount
* Enh #989: Improve performance of the `find` command
* Enh #975: Add new backend for OpenStack Swift
* Enh #998: Improve performance of the fuse mount
Details
-------
* Bugfix #1013: Switch back to using the high-level minio-go API for s3
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
* Bugfix #965: Switch to `default` repo layout for the s3 backend
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
* Enhancement #1021: Detect invalid backend name and print error
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
* Enhancement #1029: Remove invalid pack files when `prune` is run
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
* Enhancement #512: Add Backblaze B2 backend
https://github.com/restic/restic/issues/512
https://github.com/restic/restic/pull/978
* Enhancement #636: Add dirs `tags` and `hosts` to fuse mount
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
* Enhancement #989: Improve performance of the `find` command
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
* Enhancement #975: Add new backend for OpenStack Swift
https://github.com/restic/restic/pull/975
https://github.com/restic/restic/pull/648
* Enhancement #998: Improve performance of the fuse mount
Listing directories which contain large files now is significantly faster.
https://github.com/restic/restic/pull/998
Changelog for restic 0.6.1 (2017-06-01)
=======================================
The following sections list the changes in restic 0.6.1 relevant to
restic users. The changes are ordered by importance.
Summary
-------
* Enh #985: Allow multiple parallel idle HTTP connections
* Enh #981: Remove temporary path from binary in `build.go`
* Enh #974: Remove regular status reports
Details
-------
* Enhancement #985: Allow multiple parallel idle HTTP connections
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
* Enhancement #981: Remove temporary path from binary in `build.go`
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
* Enhancement #974: Remove regular status reports
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
Changelog for restic 0.6.0 (2017-05-29)
=======================================
The following sections list the changes in restic 0.6.0 relevant to
restic users. The changes are ordered by importance.
Summary
-------
* Enh #957: Make `forget` consistent
* Enh #966: Unify repository layout for all backends
* Enh #962: Improve memory and runtime for the s3 backend
Details
-------
* Enhancement #957: Make `forget` consistent
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/issues/953
https://github.com/restic/restic/pull/957
* Enhancement #966: Unify repository layout for all backends
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/issues/965
https://github.com/restic/restic/pull/966
* Enhancement #962: Improve memory and runtime for the s3 backend
We've updated the library used for accessing s3, switched to using a lower level API and added
caching for some requests. This lead to a decrease in memory usage and a great speedup. In
addition, we added benchmark functions for all backends, so we can track improvements over
time. The Continuous Integration test service we're using (Travis) now runs the s3 backend
tests not only against a Minio server, but also against the Amazon s3 live service, so we should
be notified of any regressions much sooner.
https://github.com/restic/restic/pull/962
https://github.com/restic/restic/pull/960
https://github.com/restic/restic/pull/946
https://github.com/restic/restic/pull/938
https://github.com/restic/restic/pull/883

View File

@@ -3,10 +3,7 @@ This document describes the way you can contribute to the restic project.
Ways to Help Out
================
Thank you for your contribution! Please **open an issue first** (or add a
comment to an existing issue) if you plan to work on any code or add a new
feature. This way, duplicate work is prevented and we can discuss your ideas
and design first.
Thank you for your contribution!
There are several ways you can help us out. First of all code contributions and
bug fixes are most welcome. However even "minor" details as fixing spelling
@@ -28,67 +25,50 @@ 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
=======================
In order to compile restic with the `go` tool directly, it needs to be checked
out at the right path within a `GOPATH`. The concept of a `GOPATH` is explained
in ["How to write Go code"](https://golang.org/doc/code.html).
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.
If you do not have a directory with Go code yet, executing the following
instructions in your shell will create one for you and check out the restic
repo:
First, create the necessary directory structure and clone the restic repository
to the correct location:
$ export GOPATH="$HOME/go"
$ mkdir -p "$GOPATH/src/github.com/restic"
$ cd "$GOPATH/src/github.com/restic"
$ mkdir --parents ~/work/restic/src/github.com/restic
$ cd ~/work/restic/src/github.com/restic
$ git clone https://github.com/restic/restic
$ cd restic
You can then build restic as follows:
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:
$ go build ./cmd/restic
$ ./restic version
restic compiled manually
compiled with go1.8.3 on linux/amd64
$ export GOPATH=~/work/restic:~/work/restic/src/github.com/restic/restic/Godeps/_workspace
The following commands can be used to run all the tests:
$ go test ./cmd/... ./internal/...
$ go test ./...
ok github.com/restic/restic 8.174s
[...]
The repository contains two sets of directories with code: `cmd/` and
`internal/` contain the code written for restic, whereas `vendor/` contains
copies of libraries restic depends on. The libraries are managed with the
[`dep`](https://github.com/golang/dep) tool.
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
[...]
$ vagrant ssh freebsd -c 'cd restic/restic; go test -v ./...'
[...]
Providing Patches
=================
@@ -98,39 +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. Especially take care to place your forked repository
at the correct path (`src/github.com/restic/restic`) within your `GOPATH`.
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 lot 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.
@@ -145,18 +109,10 @@ 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
-----------
It would be good if you could follow the same general style regarding Git
I would be good if you could follow the same general style regarding Git
commits as the rest of the project, this makes reviewing code, browsing the
history and triaging bugs much easier.

62
Godeps/Godeps.json generated Normal file
View File

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

5
Godeps/Readme generated Normal file
View File

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

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

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

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

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

View File

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

View File

@@ -0,0 +1 @@
package fstestutil

View File

@@ -0,0 +1,115 @@
package fstestutil
import (
"errors"
"io/ioutil"
"log"
"os"
"testing"
"time"
"bazil.org/fuse"
"bazil.org/fuse/fs"
)
// Mount contains information about the mount for the test to use.
type Mount struct {
// Dir is the temporary directory where the filesystem is mounted.
Dir string
Conn *fuse.Conn
Server *fs.Server
// Error will receive the return value of Serve.
Error <-chan error
done <-chan struct{}
closed bool
}
// Close unmounts the filesystem and waits for fs.Serve to return. Any
// returned error will be stored in Err. It is safe to call Close
// multiple times.
func (mnt *Mount) Close() {
if mnt.closed {
return
}
mnt.closed = true
for tries := 0; tries < 1000; tries++ {
err := fuse.Unmount(mnt.Dir)
if err != nil {
// TODO do more than log?
log.Printf("unmount error: %v", err)
time.Sleep(10 * time.Millisecond)
continue
}
break
}
<-mnt.done
mnt.Conn.Close()
os.Remove(mnt.Dir)
}
// 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) {
dir, err := ioutil.TempDir("", "fusetest")
if err != nil {
return nil, err
}
c, err := fuse.Mount(dir, options...)
if err != nil {
return nil, err
}
server := fs.New(c, conf)
done := make(chan struct{})
serveErr := make(chan error, 1)
mnt := &Mount{
Dir: dir,
Conn: c,
Server: server,
Error: serveErr,
done: done,
}
go func() {
defer close(done)
serveErr <- server.Serve(filesys)
}()
select {
case <-mnt.Conn.Ready:
if err := mnt.Conn.MountError; err != nil {
return nil, err
}
return mnt, nil
case err = <-mnt.Error:
// Serve quit early
if err != nil {
return nil, err
}
return nil, errors.New("Serve exited early")
}
}
// MountedT mounts the filesystem at a temporary directory,
// directing it's debug log to the testing logger.
//
// See Mounted for usage.
//
// The debug log is not enabled by default. Use `-fuse.debug` or call
// DebugByDefault to enable.
func MountedT(t testing.TB, filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
if conf == nil {
conf = &fs.Config{}
}
if debug && conf.Debug == nil {
conf.Debug = func(msg interface{}) {
t.Logf("FUSE: %s", msg)
}
}
return Mounted(filesys, conf, options...)
}

View File

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

View File

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

View File

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

2193
Godeps/_workspace/src/bazil.org/fuse/fuse.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,31 @@
package fuse_test
import (
"os"
"testing"
"bazil.org/fuse"
)
func TestOpenFlagsAccmodeMask(t *testing.T) {
var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC)
if g, e := f&fuse.OpenAccessModeMask, fuse.OpenReadWrite; g != e {
t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e)
}
if f.IsReadOnly() {
t.Fatalf("IsReadOnly is wrong: %v", f)
}
if f.IsWriteOnly() {
t.Fatalf("IsWriteOnly is wrong: %v", f)
}
if !f.IsReadWrite() {
t.Fatalf("IsReadWrite is wrong: %v", f)
}
}
func TestOpenFlagsString(t *testing.T) {
var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC | os.O_APPEND)
if g, e := f.String(), "OpenReadWrite+OpenAppend+OpenSync"; g != e {
t.Fatalf("OpenFlags.String: %q != %q", g, e)
}
}

View File

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

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

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

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

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

112
Godeps/_workspace/src/bazil.org/fuse/mount_linux.go generated vendored Normal file
View File

@@ -0,0 +1,112 @@
package fuse
import (
"bufio"
"fmt"
"io"
"log"
"net"
"os"
"os/exec"
"sync"
"syscall"
)
func lineLogger(wg *sync.WaitGroup, prefix string, r io.ReadCloser) {
defer wg.Done()
scanner := bufio.NewScanner(r)
for scanner.Scan() {
switch line := scanner.Text(); line {
case `fusermount: failed to open /etc/fuse.conf: Permission denied`:
// Silence this particular message, it occurs way too
// commonly and isn't very relevant to whether the mount
// succeeds or not.
continue
default:
log.Printf("%s: %s", prefix, line)
}
}
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) {
// linux mount is never delayed
close(ready)
fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0)
if err != nil {
return nil, fmt.Errorf("socketpair error: %v", err)
}
writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes")
defer writeFile.Close()
readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads")
defer readFile.Close()
cmd := exec.Command(
"fusermount",
"-o", conf.getOptions(),
"--",
dir,
)
cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3")
cmd.ExtraFiles = []*os.File{writeFile}
var wg sync.WaitGroup
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("setting up fusermount stderr: %v", err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
return nil, fmt.Errorf("setting up fusermount stderr: %v", err)
}
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("fusermount: %v", err)
}
wg.Add(2)
go lineLogger(&wg, "mount helper output", stdout)
go lineLogger(&wg, "mount helper error", stderr)
wg.Wait()
if err := cmd.Wait(); err != nil {
return nil, fmt.Errorf("fusermount: %v", err)
}
c, err := net.FileConn(readFile)
if err != nil {
return nil, fmt.Errorf("FileConn from fusermount socket: %v", err)
}
defer c.Close()
uc, ok := c.(*net.UnixConn)
if !ok {
return nil, fmt.Errorf("unexpected FileConn type; expected UnixConn, got %T", c)
}
buf := make([]byte, 32) // expect 1 byte
oob := make([]byte, 32) // expect 24 bytes
_, oobn, _, _, err := uc.ReadMsgUnix(buf, oob)
scms, err := syscall.ParseSocketControlMessage(oob[:oobn])
if err != nil {
return nil, fmt.Errorf("ParseSocketControlMessage: %v", err)
}
if len(scms) != 1 {
return nil, fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms)
}
scm := scms[0]
gotFds, err := syscall.ParseUnixRights(&scm)
if err != nil {
return nil, fmt.Errorf("syscall.ParseUnixRights: %v", err)
}
if len(gotFds) != 1 {
return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds)
}
f := os.NewFile(uintptr(gotFds[0]), "/dev/fuse")
return f, nil
}

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

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

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

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

View File

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

View File

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

231
Godeps/_workspace/src/bazil.org/fuse/options_test.go generated vendored Normal file
View File

@@ -0,0 +1,231 @@
package fuse_test
import (
"os"
"runtime"
"syscall"
"testing"
"bazil.org/fuse"
"bazil.org/fuse/fs"
"bazil.org/fuse/fs/fstestutil"
"golang.org/x/net/context"
)
func init() {
fstestutil.DebugByDefault()
}
func TestMountOptionFSName(t *testing.T) {
if runtime.GOOS == "freebsd" {
t.Skip("FreeBSD does not support FSName")
}
t.Parallel()
const name = "FuseTestMarker"
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
fuse.FSName(name),
)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
info, err := fstestutil.GetMountInfo(mnt.Dir)
if err != nil {
t.Fatal(err)
}
if g, e := info.FSName, name; g != e {
t.Errorf("wrong FSName: %q != %q", g, e)
}
}
func testMountOptionFSNameEvil(t *testing.T, evil string) {
if runtime.GOOS == "freebsd" {
t.Skip("FreeBSD does not support FSName")
}
t.Parallel()
var name = "FuseTest" + evil + "Marker"
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
fuse.FSName(name),
)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
info, err := fstestutil.GetMountInfo(mnt.Dir)
if err != nil {
t.Fatal(err)
}
if g, e := info.FSName, name; g != e {
t.Errorf("wrong FSName: %q != %q", g, e)
}
}
func TestMountOptionFSNameEvilComma(t *testing.T) {
if runtime.GOOS == "darwin" {
// see TestMountOptionCommaError for a test that enforces we
// at least give a nice error, instead of corrupting the mount
// options
t.Skip("TODO: OS X gets this wrong, commas in mount options cannot be escaped at all")
}
testMountOptionFSNameEvil(t, ",")
}
func TestMountOptionFSNameEvilSpace(t *testing.T) {
testMountOptionFSNameEvil(t, " ")
}
func TestMountOptionFSNameEvilTab(t *testing.T) {
testMountOptionFSNameEvil(t, "\t")
}
func TestMountOptionFSNameEvilNewline(t *testing.T) {
testMountOptionFSNameEvil(t, "\n")
}
func TestMountOptionFSNameEvilBackslash(t *testing.T) {
testMountOptionFSNameEvil(t, `\`)
}
func TestMountOptionFSNameEvilBackslashDouble(t *testing.T) {
// catch double-unescaping, if it were to happen
testMountOptionFSNameEvil(t, `\\`)
}
func TestMountOptionSubtype(t *testing.T) {
if runtime.GOOS == "darwin" {
t.Skip("OS X does not support Subtype")
}
if runtime.GOOS == "freebsd" {
t.Skip("FreeBSD does not support Subtype")
}
t.Parallel()
const name = "FuseTestMarker"
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
fuse.Subtype(name),
)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
info, err := fstestutil.GetMountInfo(mnt.Dir)
if err != nil {
t.Fatal(err)
}
if g, e := info.Type, "fuse."+name; g != e {
t.Errorf("wrong Subtype: %q != %q", g, e)
}
}
// TODO test LocalVolume
// TODO test AllowOther; hard because needs system-level authorization
func TestMountOptionAllowOtherThenAllowRoot(t *testing.T) {
t.Parallel()
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
fuse.AllowOther(),
fuse.AllowRoot(),
)
if err == nil {
mnt.Close()
}
if g, e := err, fuse.ErrCannotCombineAllowOtherAndAllowRoot; g != e {
t.Fatalf("wrong error: %v != %v", g, e)
}
}
// TODO test AllowRoot; hard because needs system-level authorization
func TestMountOptionAllowRootThenAllowOther(t *testing.T) {
t.Parallel()
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
fuse.AllowRoot(),
fuse.AllowOther(),
)
if err == nil {
mnt.Close()
}
if g, e := err, fuse.ErrCannotCombineAllowOtherAndAllowRoot; g != e {
t.Fatalf("wrong error: %v != %v", g, e)
}
}
type unwritableFile struct{}
func (f unwritableFile) Attr(ctx context.Context, a *fuse.Attr) error {
a.Mode = 0000
return nil
}
func TestMountOptionDefaultPermissions(t *testing.T) {
if runtime.GOOS == "freebsd" {
t.Skip("FreeBSD does not support DefaultPermissions")
}
t.Parallel()
mnt, err := fstestutil.MountedT(t,
fstestutil.SimpleFS{
&fstestutil.ChildMap{"child": unwritableFile{}},
},
nil,
fuse.DefaultPermissions(),
)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
// This will be prevented by kernel-level access checking when
// DefaultPermissions is used.
f, err := os.OpenFile(mnt.Dir+"/child", os.O_WRONLY, 0000)
if err == nil {
f.Close()
t.Fatal("expected an error")
}
if !os.IsPermission(err) {
t.Fatalf("expected a permission error, got %T: %v", err, err)
}
}
type createrDir struct {
fstestutil.Dir
}
var _ fs.NodeCreater = createrDir{}
func (createrDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
// pick a really distinct error, to identify it later
return nil, nil, fuse.Errno(syscall.ENAMETOOLONG)
}
func TestMountOptionReadOnly(t *testing.T) {
t.Parallel()
mnt, err := fstestutil.MountedT(t,
fstestutil.SimpleFS{createrDir{}},
nil,
fuse.ReadOnly(),
)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
// This will be prevented by kernel-level access checking when
// ReadOnly is used.
f, err := os.Create(mnt.Dir + "/child")
if err == nil {
f.Close()
t.Fatal("expected an error")
}
perr, ok := err.(*os.PathError)
if !ok {
t.Fatalf("expected PathError, got %T: %v", err, err)
}
if perr.Err != syscall.EROFS {
t.Fatalf("expected EROFS, got %T: %v", err, err)
}
}

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

@@ -0,0 +1,13 @@
// Package syscallx provides wrappers that make syscalls on various
// platforms more interoperable.
//
// The API intentionally omits the OS X-specific position and option
// arguments for extended attribute calls.
//
// Not having position means it might not be useful for accessing the
// resource fork. If that's needed by code inside fuse, a function
// with a different name may be added on the side.
//
// Options can be implemented with separate wrappers, in the style of
// Linux getxattr/lgetxattr/fgetxattr.
package syscallx

View File

View File

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

View File

@@ -0,0 +1,26 @@
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.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,131 @@
go-flags: a go library for parsing command line arguments
=========================================================
[![GoDoc](https://godoc.org/github.com/jessevdk/go-flags?status.png)](https://godoc.org/github.com/jessevdk/go-flags) [![Build Status](https://travis-ci.org/jessevdk/go-flags.svg?branch=master)](https://travis-ci.org/jessevdk/go-flags) [![Coverage Status](https://img.shields.io/coveralls/jessevdk/go-flags.svg)](https://coveralls.io/r/jessevdk/go-flags?branch=master)
This library provides similar functionality to the builtin flag library of
go, but provides much more functionality and nicer formatting. From the
documentation:
Package flags provides an extensive command line option parser.
The flags package is similar in functionality to the go builtin flag package
but provides more options and uses reflection to provide a convenient and
succinct way of specifying command line options.
Supported features:
* Options with short names (-v)
* Options with long names (--verbose)
* Options with and without arguments (bool v.s. other type)
* Options with optional arguments and default values
* Multiple option groups each containing a set of options
* Generate and print well-formatted help message
* Passing remaining command line arguments after -- (optional)
* Ignoring unknown command line options (optional)
* Supports -I/usr/include -I=/usr/include -I /usr/include option argument specification
* Supports multiple short options -aux
* Supports all primitive go types (string, int{8..64}, uint{8..64}, float)
* Supports same option multiple times (can store in slice or last option counts)
* Supports maps
* Supports function callbacks
* Supports namespaces for (nested) option groups
The flags package uses structs, reflection and struct field tags
to allow users to specify command line options. This results in very simple
and concise specification of your application options. For example:
type Options struct {
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
}
This specifies one option with a short name -v and a long name --verbose.
When either -v or --verbose is found on the command line, a 'true' value
will be appended to the Verbose field. e.g. when specifying -vvv, the
resulting value of Verbose will be {[true, true, true]}.
Example:
--------
var opts struct {
// Slice of bool will append 'true' each time the option
// is encountered (can be set multiple times, like -vvv)
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
// Example of automatic marshalling to desired type (uint)
Offset uint `long:"offset" description:"Offset"`
// Example of a callback, called each time the option is found.
Call func(string) `short:"c" description:"Call phone number"`
// Example of a required flag
Name string `short:"n" long:"name" description:"A name" required:"true"`
// Example of a value name
File string `short:"f" long:"file" description:"A file" value-name:"FILE"`
// Example of a pointer
Ptr *int `short:"p" description:"A pointer to an integer"`
// Example of a slice of strings
StringSlice []string `short:"s" description:"A slice of strings"`
// Example of a slice of pointers
PtrSlice []*string `long:"ptrslice" description:"A slice of pointers to string"`
// Example of a map
IntMap map[string]int `long:"intmap" description:"A map from string to int"`
}
// Callback which will invoke callto:<argument> to call a number.
// Note that this works just on OS X (and probably only with
// Skype) but it shows the idea.
opts.Call = func(num string) {
cmd := exec.Command("open", "callto:"+num)
cmd.Start()
cmd.Process.Release()
}
// Make some fake arguments to parse.
args := []string{
"-vv",
"--offset=5",
"-n", "Me",
"-p", "3",
"-s", "hello",
"-s", "world",
"--ptrslice", "hello",
"--ptrslice", "world",
"--intmap", "a:1",
"--intmap", "b:5",
"arg1",
"arg2",
"arg3",
}
// Parse flags from `args'. Note that here we use flags.ParseArgs for
// the sake of making a working example. Normally, you would simply use
// flags.Parse(&opts) which uses os.Args
args, err := flags.ParseArgs(&opts, args)
if err != nil {
panic(err)
os.Exit(1)
}
fmt.Printf("Verbosity: %v\n", opts.Verbose)
fmt.Printf("Offset: %d\n", opts.Offset)
fmt.Printf("Name: %s\n", opts.Name)
fmt.Printf("Ptr: %d\n", *opts.Ptr)
fmt.Printf("StringSlice: %v\n", opts.StringSlice)
fmt.Printf("PtrSlice: [%v %v]\n", *opts.PtrSlice[0], *opts.PtrSlice[1])
fmt.Printf("IntMap: [a:%v b:%v]\n", opts.IntMap["a"], opts.IntMap["b"])
fmt.Printf("Remaining args: %s\n", strings.Join(args, " "))
// Output: Verbosity: [true true]
// Offset: 5
// Name: Me
// Ptr: 3
// StringSlice: [hello world]
// PtrSlice: [hello world]
// IntMap: [a:1 b:5]
// Remaining args: arg1 arg2 arg3
More information can be found in the godocs: <http://godoc.org/github.com/jessevdk/go-flags>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,175 @@
package flags
import (
"testing"
"time"
)
func expectConvert(t *testing.T, o *Option, expected string) {
s, err := convertToString(o.value, o.tag)
if err != nil {
t.Errorf("Unexpected error: %v", err)
return
}
assertString(t, s, expected)
}
func TestConvertToString(t *testing.T) {
d, _ := time.ParseDuration("1h2m4s")
var opts = struct {
String string `long:"string"`
Int int `long:"int"`
Int8 int8 `long:"int8"`
Int16 int16 `long:"int16"`
Int32 int32 `long:"int32"`
Int64 int64 `long:"int64"`
Uint uint `long:"uint"`
Uint8 uint8 `long:"uint8"`
Uint16 uint16 `long:"uint16"`
Uint32 uint32 `long:"uint32"`
Uint64 uint64 `long:"uint64"`
Float32 float32 `long:"float32"`
Float64 float64 `long:"float64"`
Duration time.Duration `long:"duration"`
Bool bool `long:"bool"`
IntSlice []int `long:"int-slice"`
IntFloatMap map[int]float64 `long:"int-float-map"`
PtrBool *bool `long:"ptr-bool"`
Interface interface{} `long:"interface"`
Int32Base int32 `long:"int32-base" base:"16"`
Uint32Base uint32 `long:"uint32-base" base:"16"`
}{
"string",
-2,
-1,
0,
1,
2,
1,
2,
3,
4,
5,
1.2,
-3.4,
d,
true,
[]int{-3, 4, -2},
map[int]float64{-2: 4.5},
new(bool),
float32(5.2),
-5823,
4232,
}
p := NewNamedParser("test", Default)
grp, _ := p.AddGroup("test group", "", &opts)
expects := []string{
"string",
"-2",
"-1",
"0",
"1",
"2",
"1",
"2",
"3",
"4",
"5",
"1.2",
"-3.4",
"1h2m4s",
"true",
"[-3, 4, -2]",
"{-2:4.5}",
"false",
"5.2",
"-16bf",
"1088",
}
for i, v := range grp.Options() {
expectConvert(t, v, expects[i])
}
}
func TestConvertToStringInvalidIntBase(t *testing.T) {
var opts = struct {
Int int `long:"int" base:"no"`
}{
2,
}
p := NewNamedParser("test", Default)
grp, _ := p.AddGroup("test group", "", &opts)
o := grp.Options()[0]
_, err := convertToString(o.value, o.tag)
if err != nil {
err = newErrorf(ErrMarshal, "%v", err)
}
assertError(t, err, ErrMarshal, "strconv.ParseInt: parsing \"no\": invalid syntax")
}
func TestConvertToStringInvalidUintBase(t *testing.T) {
var opts = struct {
Uint uint `long:"uint" base:"no"`
}{
2,
}
p := NewNamedParser("test", Default)
grp, _ := p.AddGroup("test group", "", &opts)
o := grp.Options()[0]
_, err := convertToString(o.value, o.tag)
if err != nil {
err = newErrorf(ErrMarshal, "%v", err)
}
assertError(t, err, ErrMarshal, "strconv.ParseInt: parsing \"no\": invalid syntax")
}
func TestWrapText(t *testing.T) {
s := "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
got := wrapText(s, 60, " ")
expected := `Lorem ipsum dolor sit amet, consectetur adipisicing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.`
assertDiff(t, got, expected, "wrapped text")
}

View File

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

View File

@@ -0,0 +1,110 @@
// Example of use of the flags package.
package flags
import (
"fmt"
"os/exec"
)
func 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"`
// Example of a filename (useful for completion)
Filename Filename `long:"filename" description:"A filename"`
// Example of positional arguments
Args struct {
Id string
Num int
Rest []string
} `positional-args:"yes" required:"yes"`
}
// 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",
"--filename", "hello.go",
"id",
"10",
"remaining1",
"remaining2",
}
// 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
_, err := ParseArgs(&opts, args)
if err != nil {
panic(err)
}
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("Filename: %v\n", opts.Filename)
fmt.Printf("Args.Id: %s\n", opts.Args.Id)
fmt.Printf("Args.Num: %d\n", opts.Args.Num)
fmt.Printf("Args.Rest: %v\n", opts.Args.Rest)
// Output: Verbosity: [true true]
// Offset: 5
// Name: Me
// Ptr: 3
// StringSlice: [hello world]
// PtrSlice: [hello world]
// IntMap: [a:1 b:5]
// Filename: hello.go
// Args.Id: id
// Args.Num: 10
// Args.Rest: [remaining1 remaining2]
}

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