forked from TrueCloudLab/restic
Remove vendor
This commit is contained in:
parent
6caeff2408
commit
d2ac35af26
786 changed files with 0 additions and 253060 deletions
157
vendor/manifest
vendored
157
vendor/manifest
vendored
|
@ -1,157 +0,0 @@
|
||||||
{
|
|
||||||
"version": 0,
|
|
||||||
"dependencies": [
|
|
||||||
{
|
|
||||||
"importpath": "bazil.org/fuse",
|
|
||||||
"repository": "https://github.com/bazil/fuse",
|
|
||||||
"revision": "371fbbdaa8987b715bdd21d6adc4c9b20155f748",
|
|
||||||
"branch": "master"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "github.com/elithrar/simple-scrypt",
|
|
||||||
"repository": "https://github.com/elithrar/simple-scrypt",
|
|
||||||
"revision": "6724715de445c2e70cdafb7a1a14c8cfe0984210",
|
|
||||||
"branch": "master"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "github.com/go-ini/ini",
|
|
||||||
"repository": "https://github.com/go-ini/ini",
|
|
||||||
"revision": "3d73f4b845efdf9989fffd4b4e562727744a34ba",
|
|
||||||
"branch": "master"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "github.com/inconshreveable/mousetrap",
|
|
||||||
"repository": "https://github.com/inconshreveable/mousetrap",
|
|
||||||
"revision": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75",
|
|
||||||
"branch": "master"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "github.com/kr/fs",
|
|
||||||
"repository": "https://github.com/kr/fs",
|
|
||||||
"revision": "2788f0dbd16903de03cb8186e5c7d97b69ad387b",
|
|
||||||
"branch": "master"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "github.com/kurin/blazer",
|
|
||||||
"repository": "https://github.com/kurin/blazer",
|
|
||||||
"revision": "612082ed2430716569f1ec816fc6ade849020816",
|
|
||||||
"branch": "master"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "github.com/minio/go-homedir",
|
|
||||||
"repository": "https://github.com/minio/go-homedir",
|
|
||||||
"revision": "21304a94172ae3a09dee2cd86a12fb6f842138c7",
|
|
||||||
"branch": "master"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "github.com/minio/minio-go",
|
|
||||||
"repository": "https://github.com/minio/minio-go",
|
|
||||||
"revision": "5ca66c9a35ba1cd674484be99dc97aa0973afe12",
|
|
||||||
"branch": "HEAD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "github.com/ncw/swift",
|
|
||||||
"repository": "https://github.com/ncw/swift",
|
|
||||||
"revision": "9e6fdb8957a022d5780a78b58d6181c3580bb01f",
|
|
||||||
"branch": "master"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "github.com/pkg/errors",
|
|
||||||
"repository": "https://github.com/pkg/errors",
|
|
||||||
"revision": "c605e284fe17294bda444b34710735b29d1a9d90",
|
|
||||||
"branch": "master"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "github.com/pkg/profile",
|
|
||||||
"repository": "https://github.com/pkg/profile",
|
|
||||||
"revision": "5b67d428864e92711fcbd2f8629456121a56d91f",
|
|
||||||
"branch": "master"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "github.com/pkg/sftp",
|
|
||||||
"repository": "https://github.com/pkg/sftp",
|
|
||||||
"revision": "314a5ccb89b21e053d7d96c3a706eacaf2b18231",
|
|
||||||
"branch": "master"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "github.com/pkg/xattr",
|
|
||||||
"repository": "https://github.com/pkg/xattr",
|
|
||||||
"revision": "2c7218aab2e9980561010ef420b53d948749deaf",
|
|
||||||
"branch": "master"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "github.com/restic/chunker",
|
|
||||||
"repository": "https://github.com/restic/chunker",
|
|
||||||
"revision": "1542d55ca53d2d8d7b38e890f7a4be90014356af",
|
|
||||||
"branch": "master"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "github.com/spf13/cobra",
|
|
||||||
"repository": "https://github.com/spf13/cobra",
|
|
||||||
"revision": "d994347edadc56d6a7f863775fb6887606685ae6",
|
|
||||||
"branch": "master"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "github.com/spf13/pflag",
|
|
||||||
"repository": "https://github.com/spf13/pflag",
|
|
||||||
"revision": "e57e3eeb33f795204c1ca35f56c44f83227c6e66",
|
|
||||||
"branch": "master"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "golang.org/x/crypto/curve25519",
|
|
||||||
"repository": "https://go.googlesource.com/crypto",
|
|
||||||
"revision": "7f7c0c2d75ebb4e32a21396ce36e87b6dadc91c9",
|
|
||||||
"branch": "master",
|
|
||||||
"path": "/curve25519"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "golang.org/x/crypto/ed25519",
|
|
||||||
"repository": "https://go.googlesource.com/crypto",
|
|
||||||
"revision": "7f7c0c2d75ebb4e32a21396ce36e87b6dadc91c9",
|
|
||||||
"branch": "master",
|
|
||||||
"path": "/ed25519"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "golang.org/x/crypto/pbkdf2",
|
|
||||||
"repository": "https://go.googlesource.com/crypto",
|
|
||||||
"revision": "7f7c0c2d75ebb4e32a21396ce36e87b6dadc91c9",
|
|
||||||
"branch": "master",
|
|
||||||
"path": "/pbkdf2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "golang.org/x/crypto/poly1305",
|
|
||||||
"repository": "https://go.googlesource.com/crypto",
|
|
||||||
"revision": "7f7c0c2d75ebb4e32a21396ce36e87b6dadc91c9",
|
|
||||||
"branch": "master",
|
|
||||||
"path": "/poly1305"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "golang.org/x/crypto/scrypt",
|
|
||||||
"repository": "https://go.googlesource.com/crypto",
|
|
||||||
"revision": "7f7c0c2d75ebb4e32a21396ce36e87b6dadc91c9",
|
|
||||||
"branch": "master",
|
|
||||||
"path": "/scrypt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "golang.org/x/crypto/ssh",
|
|
||||||
"repository": "https://go.googlesource.com/crypto",
|
|
||||||
"revision": "7f7c0c2d75ebb4e32a21396ce36e87b6dadc91c9",
|
|
||||||
"branch": "master",
|
|
||||||
"path": "/ssh"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "golang.org/x/net/context",
|
|
||||||
"repository": "https://go.googlesource.com/net",
|
|
||||||
"revision": "b3756b4b77d7b13260a0a2ec658753cf48922eac",
|
|
||||||
"branch": "master",
|
|
||||||
"path": "/context"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "golang.org/x/sys/unix",
|
|
||||||
"repository": "https://go.googlesource.com/sys",
|
|
||||||
"revision": "4cd6d1a821c7175768725b55ca82f14683a29ea4",
|
|
||||||
"branch": "master",
|
|
||||||
"path": "/unix"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
93
vendor/src/bazil.org/fuse/LICENSE
vendored
93
vendor/src/bazil.org/fuse/LICENSE
vendored
|
@ -1,93 +0,0 @@
|
||||||
Copyright (c) 2013-2015 Tommi Virtanen.
|
|
||||||
Copyright (c) 2009, 2011, 2012 The Go Authors.
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The following included software components have additional copyright
|
|
||||||
notices and license terms that may differ from the above.
|
|
||||||
|
|
||||||
|
|
||||||
File fuse.go:
|
|
||||||
|
|
||||||
// Adapted from Plan 9 from User Space's src/cmd/9pfuse/fuse.c,
|
|
||||||
// which carries this notice:
|
|
||||||
//
|
|
||||||
// The files in this directory are subject to the following license.
|
|
||||||
//
|
|
||||||
// The author of this software is Russ Cox.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2006 Russ Cox
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
|
||||||
// purpose without fee is hereby granted, provided that this entire notice
|
|
||||||
// is included in all copies of any software which is or includes a copy
|
|
||||||
// or modification of this software and in all copies of the supporting
|
|
||||||
// documentation for such software.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
|
||||||
// WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION OR WARRANTY
|
|
||||||
// OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS
|
|
||||||
// FITNESS FOR ANY PARTICULAR PURPOSE.
|
|
||||||
|
|
||||||
|
|
||||||
File fuse_kernel.go:
|
|
||||||
|
|
||||||
// Derived from FUSE's fuse_kernel.h
|
|
||||||
/*
|
|
||||||
This file defines the kernel interface of FUSE
|
|
||||||
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
|
|
||||||
|
|
||||||
|
|
||||||
This -- and only this -- header file may also be distributed under
|
|
||||||
the terms of the BSD Licence as follows:
|
|
||||||
|
|
||||||
Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions
|
|
||||||
are met:
|
|
||||||
1. Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
2. 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.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
|
|
||||||
*/
|
|
23
vendor/src/bazil.org/fuse/README.md
vendored
23
vendor/src/bazil.org/fuse/README.md
vendored
|
@ -1,23 +0,0 @@
|
||||||
bazil.org/fuse -- Filesystems in Go
|
|
||||||
===================================
|
|
||||||
|
|
||||||
`bazil.org/fuse` is a Go library for writing FUSE userspace
|
|
||||||
filesystems.
|
|
||||||
|
|
||||||
It is a from-scratch implementation of the kernel-userspace
|
|
||||||
communication protocol, and does not use the C library from the
|
|
||||||
project called FUSE. `bazil.org/fuse` embraces Go fully for safety and
|
|
||||||
ease of programming.
|
|
||||||
|
|
||||||
Here’s how to get going:
|
|
||||||
|
|
||||||
go get bazil.org/fuse
|
|
||||||
|
|
||||||
Website: http://bazil.org/fuse/
|
|
||||||
|
|
||||||
Github repository: https://github.com/bazil/fuse
|
|
||||||
|
|
||||||
API docs: http://godoc.org/bazil.org/fuse
|
|
||||||
|
|
||||||
Our thanks to Russ Cox for his fuse library, which this project is
|
|
||||||
based on.
|
|
35
vendor/src/bazil.org/fuse/buffer.go
vendored
35
vendor/src/bazil.org/fuse/buffer.go
vendored
|
@ -1,35 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
// buffer provides a mechanism for constructing a message from
|
|
||||||
// multiple segments.
|
|
||||||
type buffer []byte
|
|
||||||
|
|
||||||
// alloc allocates size bytes and returns a pointer to the new
|
|
||||||
// segment.
|
|
||||||
func (w *buffer) alloc(size uintptr) unsafe.Pointer {
|
|
||||||
s := int(size)
|
|
||||||
if len(*w)+s > cap(*w) {
|
|
||||||
old := *w
|
|
||||||
*w = make([]byte, len(*w), 2*cap(*w)+s)
|
|
||||||
copy(*w, old)
|
|
||||||
}
|
|
||||||
l := len(*w)
|
|
||||||
*w = (*w)[:l+s]
|
|
||||||
return unsafe.Pointer(&(*w)[l])
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset clears out the contents of the buffer.
|
|
||||||
func (w *buffer) reset() {
|
|
||||||
for i := range (*w)[:cap(*w)] {
|
|
||||||
(*w)[i] = 0
|
|
||||||
}
|
|
||||||
*w = (*w)[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBuffer(extra uintptr) buffer {
|
|
||||||
const hdrSize = unsafe.Sizeof(outHeader{})
|
|
||||||
buf := make(buffer, hdrSize, hdrSize+extra)
|
|
||||||
return buf
|
|
||||||
}
|
|
21
vendor/src/bazil.org/fuse/debug.go
vendored
21
vendor/src/bazil.org/fuse/debug.go
vendored
|
@ -1,21 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
func stack() string {
|
|
||||||
buf := make([]byte, 1024)
|
|
||||||
return string(buf[:runtime.Stack(buf, false)])
|
|
||||||
}
|
|
||||||
|
|
||||||
func nop(msg interface{}) {}
|
|
||||||
|
|
||||||
// Debug is called to output debug messages, including protocol
|
|
||||||
// traces. The default behavior is to do nothing.
|
|
||||||
//
|
|
||||||
// The messages have human-friendly string representations and are
|
|
||||||
// safe to marshal to JSON.
|
|
||||||
//
|
|
||||||
// Implementations must not retain msg.
|
|
||||||
var Debug func(msg interface{}) = nop
|
|
6
vendor/src/bazil.org/fuse/doc/README.md
vendored
6
vendor/src/bazil.org/fuse/doc/README.md
vendored
|
@ -1,6 +0,0 @@
|
||||||
# bazil.org/fuse documentation
|
|
||||||
|
|
||||||
See also API docs at http://godoc.org/bazil.org/fuse
|
|
||||||
|
|
||||||
- [The mount sequence](mount-sequence.md)
|
|
||||||
- [Writing documentation](writing-docs.md)
|
|
|
@ -1,32 +0,0 @@
|
||||||
seqdiag {
|
|
||||||
app;
|
|
||||||
fuse [label="bazil.org/fuse"];
|
|
||||||
fusermount;
|
|
||||||
kernel;
|
|
||||||
mounts;
|
|
||||||
|
|
||||||
app;
|
|
||||||
fuse [label="bazil.org/fuse"];
|
|
||||||
fusermount;
|
|
||||||
kernel;
|
|
||||||
mounts;
|
|
||||||
|
|
||||||
app -> fuse [label="Mount"];
|
|
||||||
fuse -> fusermount [label="spawn, pass socketpair fd"];
|
|
||||||
fusermount -> kernel [label="open /dev/fuse"];
|
|
||||||
fusermount -> kernel [label="mount(2)"];
|
|
||||||
kernel ->> mounts [label="mount is visible"];
|
|
||||||
fusermount <-- kernel [label="mount(2) returns"];
|
|
||||||
fuse <<-- fusermount [diagonal, label="exit, receive /dev/fuse fd", leftnote="on Linux, successful exit here\nmeans the mount has happened,\nthough InitRequest might not have yet"];
|
|
||||||
app <-- fuse [label="Mount returns\nConn.Ready is already closed"];
|
|
||||||
|
|
||||||
app -> fuse [label="fs.Serve"];
|
|
||||||
fuse => kernel [label="read /dev/fuse fd", note="starts with InitRequest"];
|
|
||||||
fuse -> app [label="Init"];
|
|
||||||
fuse <-- app [color=red];
|
|
||||||
fuse -> kernel [label="write /dev/fuse fd", color=red];
|
|
||||||
kernel -> kernel [label="set connection\nstate to error", color=red];
|
|
||||||
fuse <-- kernel;
|
|
||||||
... conn.MountError == nil, so it is still mounted ...
|
|
||||||
... call conn.Close to clean up ...
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 28 KiB |
41
vendor/src/bazil.org/fuse/doc/mount-linux.seq
vendored
41
vendor/src/bazil.org/fuse/doc/mount-linux.seq
vendored
|
@ -1,41 +0,0 @@
|
||||||
seqdiag {
|
|
||||||
// seqdiag -T svg -o doc/mount-osx.svg doc/mount-osx.seq
|
|
||||||
app;
|
|
||||||
fuse [label="bazil.org/fuse"];
|
|
||||||
fusermount;
|
|
||||||
kernel;
|
|
||||||
mounts;
|
|
||||||
|
|
||||||
app -> fuse [label="Mount"];
|
|
||||||
fuse -> fusermount [label="spawn, pass socketpair fd"];
|
|
||||||
fusermount -> kernel [label="open /dev/fuse"];
|
|
||||||
fusermount -> kernel [label="mount(2)"];
|
|
||||||
kernel ->> mounts [label="mount is visible"];
|
|
||||||
fusermount <-- kernel [label="mount(2) returns"];
|
|
||||||
fuse <<-- fusermount [diagonal, label="exit, receive /dev/fuse fd", leftnote="on Linux, successful exit here\nmeans the mount has happened,\nthough InitRequest might not have yet"];
|
|
||||||
app <-- fuse [label="Mount returns\nConn.Ready is already closed", rightnote="InitRequest and StatfsRequest\nmay or may not be seen\nbefore Conn.Ready,\ndepending on platform"];
|
|
||||||
|
|
||||||
app -> fuse [label="fs.Serve"];
|
|
||||||
fuse => kernel [label="read /dev/fuse fd", note="starts with InitRequest"];
|
|
||||||
fuse => app [label="FS/Node/Handle methods"];
|
|
||||||
fuse => kernel [label="write /dev/fuse fd"];
|
|
||||||
... repeat ...
|
|
||||||
|
|
||||||
... shutting down ...
|
|
||||||
app -> fuse [label="Unmount"];
|
|
||||||
fuse -> fusermount [label="fusermount -u"];
|
|
||||||
fusermount -> kernel;
|
|
||||||
kernel <<-- mounts;
|
|
||||||
fusermount <-- kernel;
|
|
||||||
fuse <<-- fusermount [diagonal];
|
|
||||||
app <-- fuse [label="Unmount returns"];
|
|
||||||
|
|
||||||
// actually triggers before above
|
|
||||||
fuse <<-- kernel [diagonal, label="/dev/fuse EOF"];
|
|
||||||
app <-- fuse [label="fs.Serve returns"];
|
|
||||||
|
|
||||||
app -> fuse [label="conn.Close"];
|
|
||||||
fuse -> kernel [label="close /dev/fuse fd"];
|
|
||||||
fuse <-- kernel;
|
|
||||||
app <-- fuse;
|
|
||||||
}
|
|
BIN
vendor/src/bazil.org/fuse/doc/mount-linux.seq.png
vendored
BIN
vendor/src/bazil.org/fuse/doc/mount-linux.seq.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 44 KiB |
|
@ -1,32 +0,0 @@
|
||||||
seqdiag {
|
|
||||||
app;
|
|
||||||
fuse [label="bazil.org/fuse"];
|
|
||||||
wait [label="callMount\nhelper goroutine"];
|
|
||||||
mount_osxfusefs;
|
|
||||||
kernel;
|
|
||||||
|
|
||||||
app -> fuse [label="Mount"];
|
|
||||||
fuse -> kernel [label="open /dev/osxfuseN"];
|
|
||||||
fuse -> mount_osxfusefs [label="spawn, pass fd"];
|
|
||||||
fuse -> wait [label="goroutine", note="blocks on cmd.Wait"];
|
|
||||||
app <-- fuse [label="Mount returns"];
|
|
||||||
|
|
||||||
mount_osxfusefs -> kernel [label="mount(2)"];
|
|
||||||
|
|
||||||
app -> fuse [label="fs.Serve"];
|
|
||||||
fuse => kernel [label="read /dev/osxfuseN fd", note="starts with InitRequest,\nalso seen before mount exits:\ntwo StatfsRequest calls"];
|
|
||||||
fuse -> app [label="Init"];
|
|
||||||
fuse <-- app [color=red];
|
|
||||||
fuse -> kernel [label="write /dev/osxfuseN fd", color=red];
|
|
||||||
fuse <-- kernel;
|
|
||||||
|
|
||||||
mount_osxfusefs <-- kernel [label="mount(2) returns", color=red];
|
|
||||||
wait <<-- mount_osxfusefs [diagonal, label="exit", color=red];
|
|
||||||
app <<-- wait [diagonal, label="mount has failed,\nclose Conn.Ready", color=red];
|
|
||||||
|
|
||||||
// actually triggers before above
|
|
||||||
fuse <<-- kernel [diagonal, label="/dev/osxfuseN EOF"];
|
|
||||||
app <-- fuse [label="fs.Serve returns"];
|
|
||||||
... conn.MountError != nil, so it was was never mounted ...
|
|
||||||
... call conn.Close to clean up ...
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 32 KiB |
45
vendor/src/bazil.org/fuse/doc/mount-osx.seq
vendored
45
vendor/src/bazil.org/fuse/doc/mount-osx.seq
vendored
|
@ -1,45 +0,0 @@
|
||||||
seqdiag {
|
|
||||||
// seqdiag -T svg -o doc/mount-osx.svg doc/mount-osx.seq
|
|
||||||
app;
|
|
||||||
fuse [label="bazil.org/fuse"];
|
|
||||||
wait [label="callMount\nhelper goroutine"];
|
|
||||||
mount_osxfusefs;
|
|
||||||
kernel;
|
|
||||||
mounts;
|
|
||||||
|
|
||||||
app -> fuse [label="Mount"];
|
|
||||||
fuse -> kernel [label="open /dev/osxfuseN"];
|
|
||||||
fuse -> mount_osxfusefs [label="spawn, pass fd"];
|
|
||||||
fuse -> wait [label="goroutine", note="blocks on cmd.Wait"];
|
|
||||||
app <-- fuse [label="Mount returns"];
|
|
||||||
|
|
||||||
mount_osxfusefs -> kernel [label="mount(2)"];
|
|
||||||
|
|
||||||
app -> fuse [label="fs.Serve"];
|
|
||||||
fuse => kernel [label="read /dev/osxfuseN fd", note="starts with InitRequest,\nalso seen before mount exits:\ntwo StatfsRequest calls"];
|
|
||||||
fuse => app [label="FS/Node/Handle methods"];
|
|
||||||
fuse => kernel [label="write /dev/osxfuseN fd"];
|
|
||||||
... repeat ...
|
|
||||||
|
|
||||||
kernel ->> mounts [label="mount is visible"];
|
|
||||||
mount_osxfusefs <-- kernel [label="mount(2) returns"];
|
|
||||||
wait <<-- mount_osxfusefs [diagonal, label="exit", leftnote="on OS X, successful exit\nhere means we finally know\nthe mount has happened\n(can't trust InitRequest,\nkernel might have timed out\nwaiting for InitResponse)"];
|
|
||||||
|
|
||||||
app <<-- wait [diagonal, label="mount is ready,\nclose Conn.Ready", rightnote="InitRequest and StatfsRequest\nmay or may not be seen\nbefore Conn.Ready,\ndepending on platform"];
|
|
||||||
|
|
||||||
... shutting down ...
|
|
||||||
app -> fuse [label="Unmount"];
|
|
||||||
fuse -> kernel [label="umount(2)"];
|
|
||||||
kernel <<-- mounts;
|
|
||||||
fuse <-- kernel;
|
|
||||||
app <-- fuse [label="Unmount returns"];
|
|
||||||
|
|
||||||
// actually triggers before above
|
|
||||||
fuse <<-- kernel [diagonal, label="/dev/osxfuseN EOF"];
|
|
||||||
app <-- fuse [label="fs.Serve returns"];
|
|
||||||
|
|
||||||
app -> fuse [label="conn.Close"];
|
|
||||||
fuse -> kernel [label="close /dev/osxfuseN"];
|
|
||||||
fuse <-- kernel;
|
|
||||||
app <-- fuse;
|
|
||||||
}
|
|
BIN
vendor/src/bazil.org/fuse/doc/mount-osx.seq.png
vendored
BIN
vendor/src/bazil.org/fuse/doc/mount-osx.seq.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 50 KiB |
30
vendor/src/bazil.org/fuse/doc/mount-sequence.md
vendored
30
vendor/src/bazil.org/fuse/doc/mount-sequence.md
vendored
|
@ -1,30 +0,0 @@
|
||||||
# The mount sequence
|
|
||||||
|
|
||||||
FUSE mounting is a little bit tricky. There's a userspace helper tool
|
|
||||||
that performs the handshake with the kernel, and then steps out of the
|
|
||||||
way. This helper behaves differently on different platforms, forcing a
|
|
||||||
more complex API on us.
|
|
||||||
|
|
||||||
## Successful runs
|
|
||||||
|
|
||||||
On Linux, the mount is immediate and file system accesses wait until
|
|
||||||
the requests are served.
|
|
||||||
|
|
||||||
![Diagram of Linux FUSE mount sequence](mount-linux.seq.png)
|
|
||||||
|
|
||||||
On OS X, the mount becomes visible only after `InitRequest` (and maybe
|
|
||||||
more) have been served.
|
|
||||||
|
|
||||||
![Diagram of OSXFUSE mount sequence](mount-osx.seq.png)
|
|
||||||
|
|
||||||
|
|
||||||
## Errors
|
|
||||||
|
|
||||||
Let's see what happens if `InitRequest` gets an error response. On
|
|
||||||
Linux, the mountpoint is there but all operations will fail:
|
|
||||||
|
|
||||||
![Diagram of Linux error handling](mount-linux-error-init.seq.png)
|
|
||||||
|
|
||||||
On OS X, the mount never happened:
|
|
||||||
|
|
||||||
![Diagram of OS X error handling](mount-osx-error-init.seq.png)
|
|
16
vendor/src/bazil.org/fuse/doc/writing-docs.md
vendored
16
vendor/src/bazil.org/fuse/doc/writing-docs.md
vendored
|
@ -1,16 +0,0 @@
|
||||||
# Writing documentation
|
|
||||||
|
|
||||||
## Sequence diagrams
|
|
||||||
|
|
||||||
The sequence diagrams are generated with `seqdiag`:
|
|
||||||
http://blockdiag.com/en/seqdiag/index.html
|
|
||||||
|
|
||||||
An easy way to work on them is to automatically update the generated
|
|
||||||
files with https://github.com/cespare/reflex :
|
|
||||||
|
|
||||||
reflex -g 'doc/[^.]*.seq' -- seqdiag -T svg -o '{}.svg' '{}' &
|
|
||||||
|
|
||||||
reflex -g 'doc/[^.]*.seq' -- seqdiag -T png -o '{}.png' '{}' &
|
|
||||||
|
|
||||||
The markdown files refer to PNG images because of Github limitations,
|
|
||||||
but the SVG is generally more pleasant to view.
|
|
17
vendor/src/bazil.org/fuse/error_darwin.go
vendored
17
vendor/src/bazil.org/fuse/error_darwin.go
vendored
|
@ -1,17 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ENOATTR = Errno(syscall.ENOATTR)
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
errNoXattr = ENOATTR
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
errnoNames[errNoXattr] = "ENOATTR"
|
|
||||||
}
|
|
15
vendor/src/bazil.org/fuse/error_freebsd.go
vendored
15
vendor/src/bazil.org/fuse/error_freebsd.go
vendored
|
@ -1,15 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
const (
|
|
||||||
ENOATTR = Errno(syscall.ENOATTR)
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
errNoXattr = ENOATTR
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
errnoNames[errNoXattr] = "ENOATTR"
|
|
||||||
}
|
|
17
vendor/src/bazil.org/fuse/error_linux.go
vendored
17
vendor/src/bazil.org/fuse/error_linux.go
vendored
|
@ -1,17 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ENODATA = Errno(syscall.ENODATA)
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
errNoXattr = ENODATA
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
errnoNames[errNoXattr] = "ENODATA"
|
|
||||||
}
|
|
31
vendor/src/bazil.org/fuse/error_std.go
vendored
31
vendor/src/bazil.org/fuse/error_std.go
vendored
|
@ -1,31 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
// There is very little commonality in extended attribute errors
|
|
||||||
// across platforms.
|
|
||||||
//
|
|
||||||
// getxattr return value for "extended attribute does not exist" is
|
|
||||||
// ENOATTR on OS X, and ENODATA on Linux and apparently at least
|
|
||||||
// NetBSD. There may be a #define ENOATTR on Linux too, but the value
|
|
||||||
// is ENODATA in the actual syscalls. FreeBSD and OpenBSD have no
|
|
||||||
// ENODATA, only ENOATTR. ENOATTR is not in any of the standards,
|
|
||||||
// ENODATA exists but is only used for STREAMs.
|
|
||||||
//
|
|
||||||
// Each platform will define it a errNoXattr constant, and this file
|
|
||||||
// will enforce that it implements the right interfaces and hide the
|
|
||||||
// implementation.
|
|
||||||
//
|
|
||||||
// https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/getxattr.2.html
|
|
||||||
// http://mail-index.netbsd.org/tech-kern/2012/04/30/msg013090.html
|
|
||||||
// http://mail-index.netbsd.org/tech-kern/2012/04/30/msg013097.html
|
|
||||||
// http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html
|
|
||||||
// http://www.freebsd.org/cgi/man.cgi?query=extattr_get_file&sektion=2
|
|
||||||
// http://nixdoc.net/man-pages/openbsd/man2/extattr_get_file.2.html
|
|
||||||
|
|
||||||
// ErrNoXattr is a platform-independent error value meaning the
|
|
||||||
// extended attribute was not found. It can be used to respond to
|
|
||||||
// GetxattrRequest and such.
|
|
||||||
const ErrNoXattr = errNoXattr
|
|
||||||
|
|
||||||
var _ error = ErrNoXattr
|
|
||||||
var _ Errno = ErrNoXattr
|
|
||||||
var _ ErrorNumber = ErrNoXattr
|
|
|
@ -1,184 +0,0 @@
|
||||||
// Clockfs implements a file system with the current time in a file.
|
|
||||||
// It was written to demonstrate kernel cache invalidation.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"sync/atomic"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"bazil.org/fuse"
|
|
||||||
"bazil.org/fuse/fs"
|
|
||||||
_ "bazil.org/fuse/fs/fstestutil"
|
|
||||||
"bazil.org/fuse/fuseutil"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func usage() {
|
|
||||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
|
||||||
fmt.Fprintf(os.Stderr, " %s MOUNTPOINT\n", os.Args[0])
|
|
||||||
flag.PrintDefaults()
|
|
||||||
}
|
|
||||||
|
|
||||||
func run(mountpoint string) error {
|
|
||||||
c, err := fuse.Mount(
|
|
||||||
mountpoint,
|
|
||||||
fuse.FSName("clock"),
|
|
||||||
fuse.Subtype("clockfsfs"),
|
|
||||||
fuse.LocalVolume(),
|
|
||||||
fuse.VolumeName("Clock filesystem"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
if p := c.Protocol(); !p.HasInvalidate() {
|
|
||||||
return fmt.Errorf("kernel FUSE support is too old to have invalidations: version %v", p)
|
|
||||||
}
|
|
||||||
|
|
||||||
srv := fs.New(c, nil)
|
|
||||||
filesys := &FS{
|
|
||||||
// We pre-create the clock node so that it's always the same
|
|
||||||
// object returned from all the Lookups. You could carefully
|
|
||||||
// track its lifetime between Lookup&Forget, and have the
|
|
||||||
// ticking & invalidation happen only when active, but let's
|
|
||||||
// keep this example simple.
|
|
||||||
clockFile: &File{
|
|
||||||
fuse: srv,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
filesys.clockFile.tick()
|
|
||||||
// This goroutine never exits. That's fine for this example.
|
|
||||||
go filesys.clockFile.update()
|
|
||||||
if err := srv.Serve(filesys); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the mount process has an error to report.
|
|
||||||
<-c.Ready
|
|
||||||
if err := c.MountError; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Usage = usage
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if flag.NArg() != 1 {
|
|
||||||
usage()
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
mountpoint := flag.Arg(0)
|
|
||||||
|
|
||||||
if err := run(mountpoint); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type FS struct {
|
|
||||||
clockFile *File
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.FS = (*FS)(nil)
|
|
||||||
|
|
||||||
func (f *FS) Root() (fs.Node, error) {
|
|
||||||
return &Dir{fs: f}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dir implements both Node and Handle for the root directory.
|
|
||||||
type Dir struct {
|
|
||||||
fs *FS
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.Node = (*Dir)(nil)
|
|
||||||
|
|
||||||
func (d *Dir) Attr(ctx context.Context, a *fuse.Attr) error {
|
|
||||||
a.Inode = 1
|
|
||||||
a.Mode = os.ModeDir | 0555
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.NodeStringLookuper = (*Dir)(nil)
|
|
||||||
|
|
||||||
func (d *Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
|
||||||
if name == "clock" {
|
|
||||||
return d.fs.clockFile, nil
|
|
||||||
}
|
|
||||||
return nil, fuse.ENOENT
|
|
||||||
}
|
|
||||||
|
|
||||||
var dirDirs = []fuse.Dirent{
|
|
||||||
{Inode: 2, Name: "clock", Type: fuse.DT_File},
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.HandleReadDirAller = (*Dir)(nil)
|
|
||||||
|
|
||||||
func (d *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
|
||||||
return dirDirs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
fs.NodeRef
|
|
||||||
fuse *fs.Server
|
|
||||||
content atomic.Value
|
|
||||||
count uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.Node = (*File)(nil)
|
|
||||||
|
|
||||||
func (f *File) Attr(ctx context.Context, a *fuse.Attr) error {
|
|
||||||
a.Inode = 2
|
|
||||||
a.Mode = 0444
|
|
||||||
t := f.content.Load().(string)
|
|
||||||
a.Size = uint64(len(t))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.NodeOpener = (*File)(nil)
|
|
||||||
|
|
||||||
func (f *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
|
|
||||||
if !req.Flags.IsReadOnly() {
|
|
||||||
return nil, fuse.Errno(syscall.EACCES)
|
|
||||||
}
|
|
||||||
resp.Flags |= fuse.OpenKeepCache
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.Handle = (*File)(nil)
|
|
||||||
|
|
||||||
var _ fs.HandleReader = (*File)(nil)
|
|
||||||
|
|
||||||
func (f *File) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
|
|
||||||
t := f.content.Load().(string)
|
|
||||||
fuseutil.HandleRead(req, resp, []byte(t))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) tick() {
|
|
||||||
// Intentionally a variable-length format, to demonstrate size changes.
|
|
||||||
f.count++
|
|
||||||
s := fmt.Sprintf("%d\t%s\n", f.count, time.Now())
|
|
||||||
f.content.Store(s)
|
|
||||||
|
|
||||||
// For simplicity, this example tries to send invalidate
|
|
||||||
// notifications even when the kernel does not hold a reference to
|
|
||||||
// the node, so be extra sure to ignore ErrNotCached.
|
|
||||||
if err := f.fuse.InvalidateNodeData(f); err != nil && err != fuse.ErrNotCached {
|
|
||||||
log.Printf("invalidate error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) update() {
|
|
||||||
tick := time.NewTicker(1 * time.Second)
|
|
||||||
defer tick.Stop()
|
|
||||||
for range tick.C {
|
|
||||||
f.tick()
|
|
||||||
}
|
|
||||||
}
|
|
101
vendor/src/bazil.org/fuse/examples/hellofs/hello.go
vendored
101
vendor/src/bazil.org/fuse/examples/hellofs/hello.go
vendored
|
@ -1,101 +0,0 @@
|
||||||
// Hellofs implements a simple "hello world" file system.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"bazil.org/fuse"
|
|
||||||
"bazil.org/fuse/fs"
|
|
||||||
_ "bazil.org/fuse/fs/fstestutil"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func usage() {
|
|
||||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
|
||||||
fmt.Fprintf(os.Stderr, " %s MOUNTPOINT\n", os.Args[0])
|
|
||||||
flag.PrintDefaults()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Usage = usage
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if flag.NArg() != 1 {
|
|
||||||
usage()
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
mountpoint := flag.Arg(0)
|
|
||||||
|
|
||||||
c, err := fuse.Mount(
|
|
||||||
mountpoint,
|
|
||||||
fuse.FSName("helloworld"),
|
|
||||||
fuse.Subtype("hellofs"),
|
|
||||||
fuse.LocalVolume(),
|
|
||||||
fuse.VolumeName("Hello world!"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
err = fs.Serve(c, FS{})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if the mount process has an error to report
|
|
||||||
<-c.Ready
|
|
||||||
if err := c.MountError; err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FS implements the hello world file system.
|
|
||||||
type FS struct{}
|
|
||||||
|
|
||||||
func (FS) Root() (fs.Node, error) {
|
|
||||||
return Dir{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dir implements both Node and Handle for the root directory.
|
|
||||||
type Dir struct{}
|
|
||||||
|
|
||||||
func (Dir) Attr(ctx context.Context, a *fuse.Attr) error {
|
|
||||||
a.Inode = 1
|
|
||||||
a.Mode = os.ModeDir | 0555
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
|
||||||
if name == "hello" {
|
|
||||||
return File{}, nil
|
|
||||||
}
|
|
||||||
return nil, fuse.ENOENT
|
|
||||||
}
|
|
||||||
|
|
||||||
var dirDirs = []fuse.Dirent{
|
|
||||||
{Inode: 2, Name: "hello", Type: fuse.DT_File},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
|
||||||
return dirDirs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// File implements both Node and Handle for the hello file.
|
|
||||||
type File struct{}
|
|
||||||
|
|
||||||
const greeting = "hello, world\n"
|
|
||||||
|
|
||||||
func (File) Attr(ctx context.Context, a *fuse.Attr) error {
|
|
||||||
a.Inode = 2
|
|
||||||
a.Mode = 0444
|
|
||||||
a.Size = uint64(len(greeting))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (File) ReadAll(ctx context.Context) ([]byte, error) {
|
|
||||||
return []byte(greeting), nil
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
package bench_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"bazil.org/fuse"
|
|
||||||
"bazil.org/fuse/fs"
|
|
||||||
"bazil.org/fuse/fs/fstestutil"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type dummyFile struct {
|
|
||||||
fstestutil.File
|
|
||||||
}
|
|
||||||
|
|
||||||
type benchCreateDir struct {
|
|
||||||
fstestutil.Dir
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.NodeCreater = (*benchCreateDir)(nil)
|
|
||||||
|
|
||||||
func (f *benchCreateDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
|
|
||||||
child := &dummyFile{}
|
|
||||||
return child, child, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkCreate(b *testing.B) {
|
|
||||||
f := &benchCreateDir{}
|
|
||||||
mnt, err := fstestutil.MountedT(b, fstestutil.SimpleFS{f}, nil)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
defer mnt.Close()
|
|
||||||
|
|
||||||
// prepare file names to decrease test overhead
|
|
||||||
names := make([]string, 0, b.N)
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
// zero-padded so cost stays the same on every iteration
|
|
||||||
names = append(names, mnt.Dir+"/"+fmt.Sprintf("%08x", i))
|
|
||||||
}
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
f, err := os.Create(names[i])
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("WriteFile: %v", err)
|
|
||||||
}
|
|
||||||
f.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
b.StopTimer()
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
package bench_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"bazil.org/fuse"
|
|
||||||
"bazil.org/fuse/fs"
|
|
||||||
"bazil.org/fuse/fs/fstestutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
type benchLookupDir struct {
|
|
||||||
fstestutil.Dir
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.NodeRequestLookuper = (*benchLookupDir)(nil)
|
|
||||||
|
|
||||||
func (f *benchLookupDir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (fs.Node, error) {
|
|
||||||
return nil, fuse.ENOENT
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLookup(b *testing.B) {
|
|
||||||
f := &benchLookupDir{}
|
|
||||||
mnt, err := fstestutil.MountedT(b, fstestutil.SimpleFS{f}, nil)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
defer mnt.Close()
|
|
||||||
|
|
||||||
name := mnt.Dir + "/does-not-exist"
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
if _, err := os.Stat(name); !os.IsNotExist(err) {
|
|
||||||
b.Fatalf("Stat: wrong error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.StopTimer()
|
|
||||||
}
|
|
|
@ -1,268 +0,0 @@
|
||||||
package bench_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"bazil.org/fuse"
|
|
||||||
"bazil.org/fuse/fs"
|
|
||||||
"bazil.org/fuse/fs/fstestutil"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type benchConfig struct {
|
|
||||||
directIO bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type benchFS struct {
|
|
||||||
conf *benchConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = fs.FS(benchFS{})
|
|
||||||
|
|
||||||
func (f benchFS) Root() (fs.Node, error) {
|
|
||||||
return benchDir{conf: f.conf}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type benchDir struct {
|
|
||||||
conf *benchConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = fs.Node(benchDir{})
|
|
||||||
var _ = fs.NodeStringLookuper(benchDir{})
|
|
||||||
var _ = fs.Handle(benchDir{})
|
|
||||||
var _ = fs.HandleReadDirAller(benchDir{})
|
|
||||||
|
|
||||||
func (benchDir) Attr(ctx context.Context, a *fuse.Attr) error {
|
|
||||||
a.Inode = 1
|
|
||||||
a.Mode = os.ModeDir | 0555
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d benchDir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
|
||||||
if name == "bench" {
|
|
||||||
return benchFile{conf: d.conf}, nil
|
|
||||||
}
|
|
||||||
return nil, fuse.ENOENT
|
|
||||||
}
|
|
||||||
|
|
||||||
func (benchDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
|
||||||
l := []fuse.Dirent{
|
|
||||||
{Inode: 2, Name: "bench", Type: fuse.DT_File},
|
|
||||||
}
|
|
||||||
return l, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type benchFile struct {
|
|
||||||
conf *benchConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = fs.Node(benchFile{})
|
|
||||||
var _ = fs.NodeOpener(benchFile{})
|
|
||||||
var _ = fs.NodeFsyncer(benchFile{})
|
|
||||||
var _ = fs.Handle(benchFile{})
|
|
||||||
var _ = fs.HandleReader(benchFile{})
|
|
||||||
var _ = fs.HandleWriter(benchFile{})
|
|
||||||
|
|
||||||
func (benchFile) Attr(ctx context.Context, a *fuse.Attr) error {
|
|
||||||
a.Inode = 2
|
|
||||||
a.Mode = 0644
|
|
||||||
a.Size = 9999999999999999
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f benchFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
|
|
||||||
if f.conf.directIO {
|
|
||||||
resp.Flags |= fuse.OpenDirectIO
|
|
||||||
}
|
|
||||||
// TODO configurable?
|
|
||||||
resp.Flags |= fuse.OpenKeepCache
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (benchFile) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
|
|
||||||
resp.Data = resp.Data[:cap(resp.Data)]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (benchFile) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
|
|
||||||
resp.Size = len(req.Data)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (benchFile) Fsync(ctx context.Context, req *fuse.FsyncRequest) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchmark(b *testing.B, fn func(b *testing.B, mnt string), conf *benchConfig) {
|
|
||||||
filesys := benchFS{
|
|
||||||
conf: conf,
|
|
||||||
}
|
|
||||||
mnt, err := fstestutil.Mounted(filesys, nil,
|
|
||||||
fuse.MaxReadahead(64*1024*1024),
|
|
||||||
fuse.AsyncRead(),
|
|
||||||
fuse.WritebackCache(),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
defer mnt.Close()
|
|
||||||
|
|
||||||
fn(b, mnt.Dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
type zero struct{}
|
|
||||||
|
|
||||||
func (zero) Read(p []byte) (n int, err error) {
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var Zero io.Reader = zero{}
|
|
||||||
|
|
||||||
func doWrites(size int64) func(b *testing.B, mnt string) {
|
|
||||||
return func(b *testing.B, mnt string) {
|
|
||||||
p := path.Join(mnt, "bench")
|
|
||||||
|
|
||||||
f, err := os.Create(p)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("create: %v", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
b.SetBytes(size)
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, err = io.CopyN(f, Zero, size)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("write: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkWrite100(b *testing.B) {
|
|
||||||
benchmark(b, doWrites(100), &benchConfig{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkWrite10MB(b *testing.B) {
|
|
||||||
benchmark(b, doWrites(10*1024*1024), &benchConfig{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkWrite100MB(b *testing.B) {
|
|
||||||
benchmark(b, doWrites(100*1024*1024), &benchConfig{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkDirectWrite100(b *testing.B) {
|
|
||||||
benchmark(b, doWrites(100), &benchConfig{
|
|
||||||
directIO: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkDirectWrite10MB(b *testing.B) {
|
|
||||||
benchmark(b, doWrites(10*1024*1024), &benchConfig{
|
|
||||||
directIO: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkDirectWrite100MB(b *testing.B) {
|
|
||||||
benchmark(b, doWrites(100*1024*1024), &benchConfig{
|
|
||||||
directIO: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func doWritesSync(size int64) func(b *testing.B, mnt string) {
|
|
||||||
return func(b *testing.B, mnt string) {
|
|
||||||
p := path.Join(mnt, "bench")
|
|
||||||
|
|
||||||
f, err := os.Create(p)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("create: %v", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
b.SetBytes(size)
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, err = io.CopyN(f, Zero, size)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("write: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := f.Sync(); err != nil {
|
|
||||||
b.Fatalf("sync: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkWriteSync100(b *testing.B) {
|
|
||||||
benchmark(b, doWritesSync(100), &benchConfig{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkWriteSync10MB(b *testing.B) {
|
|
||||||
benchmark(b, doWritesSync(10*1024*1024), &benchConfig{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkWriteSync100MB(b *testing.B) {
|
|
||||||
benchmark(b, doWritesSync(100*1024*1024), &benchConfig{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func doReads(size int64) func(b *testing.B, mnt string) {
|
|
||||||
return func(b *testing.B, mnt string) {
|
|
||||||
p := path.Join(mnt, "bench")
|
|
||||||
|
|
||||||
f, err := os.Open(p)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("close: %v", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
b.SetBytes(size)
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
n, err := io.CopyN(ioutil.Discard, f, size)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("read: %v", err)
|
|
||||||
}
|
|
||||||
if n != size {
|
|
||||||
b.Errorf("unexpected size: %d != %d", n, size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkRead100(b *testing.B) {
|
|
||||||
benchmark(b, doReads(100), &benchConfig{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkRead10MB(b *testing.B) {
|
|
||||||
benchmark(b, doReads(10*1024*1024), &benchConfig{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkRead100MB(b *testing.B) {
|
|
||||||
benchmark(b, doReads(100*1024*1024), &benchConfig{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkDirectRead100(b *testing.B) {
|
|
||||||
benchmark(b, doReads(100), &benchConfig{
|
|
||||||
directIO: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkDirectRead10MB(b *testing.B) {
|
|
||||||
benchmark(b, doReads(10*1024*1024), &benchConfig{
|
|
||||||
directIO: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkDirectRead100MB(b *testing.B) {
|
|
||||||
benchmark(b, doReads(100*1024*1024), &benchConfig{
|
|
||||||
directIO: true,
|
|
||||||
})
|
|
||||||
}
|
|
5
vendor/src/bazil.org/fuse/fs/bench/doc.go
vendored
5
vendor/src/bazil.org/fuse/fs/bench/doc.go
vendored
|
@ -1,5 +0,0 @@
|
||||||
// Package bench contains benchmarks.
|
|
||||||
//
|
|
||||||
// It is kept in a separate package to avoid conflicting with the
|
|
||||||
// debug-heavy defaults for the actual tests.
|
|
||||||
package bench
|
|
|
@ -1,70 +0,0 @@
|
||||||
package fstestutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileInfoCheck is a function that validates an os.FileInfo according
|
|
||||||
// to some criteria.
|
|
||||||
type FileInfoCheck func(fi os.FileInfo) error
|
|
||||||
|
|
||||||
type checkDirError struct {
|
|
||||||
missing map[string]struct{}
|
|
||||||
extra map[string]os.FileMode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *checkDirError) Error() string {
|
|
||||||
return fmt.Sprintf("wrong directory contents: missing %v, extra %v", e.missing, e.extra)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckDir checks the contents of the directory at path, making sure
|
|
||||||
// every directory entry listed in want is present. If the check is
|
|
||||||
// not nil, it must also pass.
|
|
||||||
//
|
|
||||||
// If want contains the impossible filename "", unexpected files are
|
|
||||||
// checked with that. If the key is not in want, unexpected files are
|
|
||||||
// an error.
|
|
||||||
//
|
|
||||||
// Missing entries, that are listed in want but not seen, are an
|
|
||||||
// error.
|
|
||||||
func CheckDir(path string, want map[string]FileInfoCheck) error {
|
|
||||||
problems := &checkDirError{
|
|
||||||
missing: make(map[string]struct{}, len(want)),
|
|
||||||
extra: make(map[string]os.FileMode),
|
|
||||||
}
|
|
||||||
for k := range want {
|
|
||||||
if k == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
problems.missing[k] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
fis, err := ioutil.ReadDir(path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot read directory: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fi := range fis {
|
|
||||||
check, ok := want[fi.Name()]
|
|
||||||
if !ok {
|
|
||||||
check, ok = want[""]
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
problems.extra[fi.Name()] = fi.Mode()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
delete(problems.missing, fi.Name())
|
|
||||||
if check != nil {
|
|
||||||
if err := check(fi); err != nil {
|
|
||||||
return fmt.Errorf("check failed: %v: %v", fi.Name(), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(problems.missing) > 0 || len(problems.extra) > 0 {
|
|
||||||
return problems
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
65
vendor/src/bazil.org/fuse/fs/fstestutil/debug.go
vendored
65
vendor/src/bazil.org/fuse/fs/fstestutil/debug.go
vendored
|
@ -1,65 +0,0 @@
|
||||||
package fstestutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"log"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"bazil.org/fuse"
|
|
||||||
)
|
|
||||||
|
|
||||||
type flagDebug bool
|
|
||||||
|
|
||||||
var debug flagDebug
|
|
||||||
|
|
||||||
var _ = flag.Value(&debug)
|
|
||||||
|
|
||||||
func (f *flagDebug) IsBoolFlag() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func nop(msg interface{}) {}
|
|
||||||
|
|
||||||
func (f *flagDebug) Set(s string) error {
|
|
||||||
v, err := strconv.ParseBool(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*f = flagDebug(v)
|
|
||||||
if v {
|
|
||||||
fuse.Debug = logMsg
|
|
||||||
} else {
|
|
||||||
fuse.Debug = nop
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *flagDebug) String() string {
|
|
||||||
return strconv.FormatBool(bool(*f))
|
|
||||||
}
|
|
||||||
|
|
||||||
func logMsg(msg interface{}) {
|
|
||||||
log.Printf("FUSE: %s\n", msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.Var(&debug, "fuse.debug", "log FUSE processing details")
|
|
||||||
}
|
|
||||||
|
|
||||||
// DebugByDefault changes the default of the `-fuse.debug` flag to
|
|
||||||
// true.
|
|
||||||
//
|
|
||||||
// This package registers a command line flag `-fuse.debug` and when
|
|
||||||
// run with that flag (and activated inside the tests), logs FUSE
|
|
||||||
// debug messages.
|
|
||||||
//
|
|
||||||
// This is disabled by default, as most callers probably won't care
|
|
||||||
// about FUSE details. Use DebugByDefault for tests where you'd
|
|
||||||
// normally be passing `-fuse.debug` all the time anyway.
|
|
||||||
//
|
|
||||||
// Call from an init function.
|
|
||||||
func DebugByDefault() {
|
|
||||||
f := flag.Lookup("fuse.debug")
|
|
||||||
f.DefValue = "true"
|
|
||||||
f.Value.Set(f.DefValue)
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
package fstestutil // import "bazil.org/fuse/fs/fstestutil"
|
|
141
vendor/src/bazil.org/fuse/fs/fstestutil/mounted.go
vendored
141
vendor/src/bazil.org/fuse/fs/fstestutil/mounted.go
vendored
|
@ -1,141 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MountedFunc mounts a filesystem at a temporary directory. The
|
|
||||||
// filesystem used is constructed by calling a function, to allow
|
|
||||||
// storing fuse.Conn and fs.Server in the FS.
|
|
||||||
//
|
|
||||||
// It also waits until the filesystem is known to be visible (OS X
|
|
||||||
// workaround).
|
|
||||||
//
|
|
||||||
// After successful return, caller must clean up by calling Close.
|
|
||||||
func MountedFunc(fn func(*Mount) fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
|
|
||||||
dir, err := ioutil.TempDir("", "fusetest")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
filesys := fn(mnt)
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mounted mounts the fuse.Server at a temporary directory.
|
|
||||||
//
|
|
||||||
// It also waits until the filesystem is known to be visible (OS X
|
|
||||||
// workaround).
|
|
||||||
//
|
|
||||||
// After successful return, caller must clean up by calling Close.
|
|
||||||
func Mounted(filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
|
|
||||||
fn := func(*Mount) fs.FS { return filesys }
|
|
||||||
return MountedFunc(fn, conf, options...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MountedFuncT mounts a filesystem at a temporary directory,
|
|
||||||
// directing it's debug log to the testing logger.
|
|
||||||
//
|
|
||||||
// See MountedFunc for usage.
|
|
||||||
//
|
|
||||||
// The debug log is not enabled by default. Use `-fuse.debug` or call
|
|
||||||
// DebugByDefault to enable.
|
|
||||||
func MountedFuncT(t testing.TB, fn func(*Mount) fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
|
|
||||||
if conf == nil {
|
|
||||||
conf = &fs.Config{}
|
|
||||||
}
|
|
||||||
if debug && conf.Debug == nil {
|
|
||||||
conf.Debug = func(msg interface{}) {
|
|
||||||
t.Logf("FUSE: %s", msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return MountedFunc(fn, conf, options...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MountedT mounts the filesystem at a temporary directory,
|
|
||||||
// directing it's debug log to the testing logger.
|
|
||||||
//
|
|
||||||
// See Mounted for usage.
|
|
||||||
//
|
|
||||||
// The debug log is not enabled by default. Use `-fuse.debug` or call
|
|
||||||
// DebugByDefault to enable.
|
|
||||||
func MountedT(t testing.TB, filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
|
|
||||||
fn := func(*Mount) fs.FS { return filesys }
|
|
||||||
return MountedFuncT(t, fn, conf, options...)
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package fstestutil
|
|
||||||
|
|
||||||
// MountInfo describes a mounted file system.
|
|
||||||
type MountInfo struct {
|
|
||||||
FSName string
|
|
||||||
Type string
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMountInfo finds information about the mount at mnt. It is
|
|
||||||
// intended for use by tests only, and only fetches information
|
|
||||||
// relevant to the current tests.
|
|
||||||
func GetMountInfo(mnt string) (*MountInfo, error) {
|
|
||||||
return getMountInfo(mnt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// cstr converts a nil-terminated C string into a Go string
|
|
||||||
func cstr(ca []int8) string {
|
|
||||||
s := make([]byte, 0, len(ca))
|
|
||||||
for _, c := range ca {
|
|
||||||
if c == 0x00 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
s = append(s, byte(c))
|
|
||||||
}
|
|
||||||
return string(s)
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
package fstestutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
var re = regexp.MustCompile(`\\(.)`)
|
|
||||||
|
|
||||||
// unescape removes backslash-escaping. The escaped characters are not
|
|
||||||
// mapped in any way; that is, unescape(`\n` ) == `n`.
|
|
||||||
func unescape(s string) string {
|
|
||||||
return re.ReplaceAllString(s, `$1`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMountInfo(mnt string) (*MountInfo, error) {
|
|
||||||
var st syscall.Statfs_t
|
|
||||||
err := syscall.Statfs(mnt, &st)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
i := &MountInfo{
|
|
||||||
// osx getmntent(3) fails to un-escape the data, so we do it..
|
|
||||||
// this might lead to double-unescaping in the future. fun.
|
|
||||||
// TestMountOptionFSNameEvilBackslashDouble checks for that.
|
|
||||||
FSName: unescape(cstr(st.Mntfromname[:])),
|
|
||||||
}
|
|
||||||
return i, nil
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package fstestutil
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
func getMountInfo(mnt string) (*MountInfo, error) {
|
|
||||||
return nil, errors.New("FreeBSD has no useful mount information")
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
package fstestutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Linux /proc/mounts shows current mounts.
|
|
||||||
// Same format as /etc/fstab. Quoting getmntent(3):
|
|
||||||
//
|
|
||||||
// Since fields in the mtab and fstab files are separated by whitespace,
|
|
||||||
// octal escapes are used to represent the four characters space (\040),
|
|
||||||
// tab (\011), newline (\012) and backslash (\134) in those files when
|
|
||||||
// they occur in one of the four strings in a mntent structure.
|
|
||||||
//
|
|
||||||
// http://linux.die.net/man/3/getmntent
|
|
||||||
|
|
||||||
var fstabUnescape = strings.NewReplacer(
|
|
||||||
`\040`, "\040",
|
|
||||||
`\011`, "\011",
|
|
||||||
`\012`, "\012",
|
|
||||||
`\134`, "\134",
|
|
||||||
)
|
|
||||||
|
|
||||||
var errNotFound = errors.New("mount not found")
|
|
||||||
|
|
||||||
func getMountInfo(mnt string) (*MountInfo, error) {
|
|
||||||
data, err := ioutil.ReadFile("/proc/mounts")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, line := range strings.Split(string(data), "\n") {
|
|
||||||
fields := strings.Fields(line)
|
|
||||||
if len(fields) < 3 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Fields are: fsname dir type opts freq passno
|
|
||||||
fsname := fstabUnescape.Replace(fields[0])
|
|
||||||
dir := fstabUnescape.Replace(fields[1])
|
|
||||||
fstype := fstabUnescape.Replace(fields[2])
|
|
||||||
if mnt == dir {
|
|
||||||
info := &MountInfo{
|
|
||||||
FSName: fsname,
|
|
||||||
Type: fstype,
|
|
||||||
}
|
|
||||||
return info, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errNotFound
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
package record
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Buffer is like bytes.Buffer but safe to access from multiple
|
|
||||||
// goroutines.
|
|
||||||
type Buffer struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
buf bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = io.Writer(&Buffer{})
|
|
||||||
|
|
||||||
func (b *Buffer) Write(p []byte) (n int, err error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
return b.buf.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Buffer) Bytes() []byte {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
return b.buf.Bytes()
|
|
||||||
}
|
|
|
@ -1,409 +0,0 @@
|
||||||
package record // import "bazil.org/fuse/fs/fstestutil/record"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"bazil.org/fuse"
|
|
||||||
"bazil.org/fuse/fs"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Writes gathers data from FUSE Write calls.
|
|
||||||
type Writes struct {
|
|
||||||
buf Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = fs.HandleWriter(&Writes{})
|
|
||||||
|
|
||||||
func (w *Writes) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
|
|
||||||
n, err := w.buf.Write(req.Data)
|
|
||||||
resp.Size = n
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writes) RecordedWriteData() []byte {
|
|
||||||
return w.buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Counter records number of times a thing has occurred.
|
|
||||||
type Counter struct {
|
|
||||||
count uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Counter) Inc() {
|
|
||||||
atomic.AddUint32(&r.count, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Counter) Count() uint32 {
|
|
||||||
return atomic.LoadUint32(&r.count)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkRecorder records whether a thing has occurred.
|
|
||||||
type MarkRecorder struct {
|
|
||||||
count Counter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *MarkRecorder) Mark() {
|
|
||||||
r.count.Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *MarkRecorder) Recorded() bool {
|
|
||||||
return r.count.Count() > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flushes notes whether a FUSE Flush call has been seen.
|
|
||||||
type Flushes struct {
|
|
||||||
rec MarkRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = fs.HandleFlusher(&Flushes{})
|
|
||||||
|
|
||||||
func (r *Flushes) Flush(ctx context.Context, req *fuse.FlushRequest) error {
|
|
||||||
r.rec.Mark()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Flushes) RecordedFlush() bool {
|
|
||||||
return r.rec.Recorded()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Recorder struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
val interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record that we've seen value. A nil value is indistinguishable from
|
|
||||||
// no value recorded.
|
|
||||||
func (r *Recorder) Record(value interface{}) {
|
|
||||||
r.mu.Lock()
|
|
||||||
r.val = value
|
|
||||||
r.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Recorder) Recorded() interface{} {
|
|
||||||
r.mu.Lock()
|
|
||||||
val := r.val
|
|
||||||
r.mu.Unlock()
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
type RequestRecorder struct {
|
|
||||||
rec Recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record a fuse.Request, after zeroing header fields that are hard to
|
|
||||||
// reproduce.
|
|
||||||
//
|
|
||||||
// Make sure to record a copy, not the original request.
|
|
||||||
func (r *RequestRecorder) RecordRequest(req fuse.Request) {
|
|
||||||
hdr := req.Hdr()
|
|
||||||
*hdr = fuse.Header{}
|
|
||||||
r.rec.Record(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RequestRecorder) Recorded() fuse.Request {
|
|
||||||
val := r.rec.Recorded()
|
|
||||||
if val == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return val.(fuse.Request)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setattrs records a Setattr request and its fields.
|
|
||||||
type Setattrs struct {
|
|
||||||
rec RequestRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = fs.NodeSetattrer(&Setattrs{})
|
|
||||||
|
|
||||||
func (r *Setattrs) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error {
|
|
||||||
tmp := *req
|
|
||||||
r.rec.RecordRequest(&tmp)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Setattrs) RecordedSetattr() fuse.SetattrRequest {
|
|
||||||
val := r.rec.Recorded()
|
|
||||||
if val == nil {
|
|
||||||
return fuse.SetattrRequest{}
|
|
||||||
}
|
|
||||||
return *(val.(*fuse.SetattrRequest))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fsyncs records an Fsync request and its fields.
|
|
||||||
type Fsyncs struct {
|
|
||||||
rec RequestRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = fs.NodeFsyncer(&Fsyncs{})
|
|
||||||
|
|
||||||
func (r *Fsyncs) Fsync(ctx context.Context, req *fuse.FsyncRequest) error {
|
|
||||||
tmp := *req
|
|
||||||
r.rec.RecordRequest(&tmp)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Fsyncs) RecordedFsync() fuse.FsyncRequest {
|
|
||||||
val := r.rec.Recorded()
|
|
||||||
if val == nil {
|
|
||||||
return fuse.FsyncRequest{}
|
|
||||||
}
|
|
||||||
return *(val.(*fuse.FsyncRequest))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mkdirs records a Mkdir request and its fields.
|
|
||||||
type Mkdirs struct {
|
|
||||||
rec RequestRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = fs.NodeMkdirer(&Mkdirs{})
|
|
||||||
|
|
||||||
// Mkdir records the request and returns an error. Most callers should
|
|
||||||
// wrap this call in a function that returns a more useful result.
|
|
||||||
func (r *Mkdirs) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
|
|
||||||
tmp := *req
|
|
||||||
r.rec.RecordRequest(&tmp)
|
|
||||||
return nil, fuse.EIO
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordedMkdir returns information about the Mkdir request.
|
|
||||||
// If no request was seen, returns a zero value.
|
|
||||||
func (r *Mkdirs) RecordedMkdir() fuse.MkdirRequest {
|
|
||||||
val := r.rec.Recorded()
|
|
||||||
if val == nil {
|
|
||||||
return fuse.MkdirRequest{}
|
|
||||||
}
|
|
||||||
return *(val.(*fuse.MkdirRequest))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Symlinks records a Symlink request and its fields.
|
|
||||||
type Symlinks struct {
|
|
||||||
rec RequestRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = fs.NodeSymlinker(&Symlinks{})
|
|
||||||
|
|
||||||
// Symlink records the request and returns an error. Most callers should
|
|
||||||
// wrap this call in a function that returns a more useful result.
|
|
||||||
func (r *Symlinks) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (fs.Node, error) {
|
|
||||||
tmp := *req
|
|
||||||
r.rec.RecordRequest(&tmp)
|
|
||||||
return nil, fuse.EIO
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordedSymlink returns information about the Symlink request.
|
|
||||||
// If no request was seen, returns a zero value.
|
|
||||||
func (r *Symlinks) RecordedSymlink() fuse.SymlinkRequest {
|
|
||||||
val := r.rec.Recorded()
|
|
||||||
if val == nil {
|
|
||||||
return fuse.SymlinkRequest{}
|
|
||||||
}
|
|
||||||
return *(val.(*fuse.SymlinkRequest))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Links records a Link request and its fields.
|
|
||||||
type Links struct {
|
|
||||||
rec RequestRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = fs.NodeLinker(&Links{})
|
|
||||||
|
|
||||||
// Link records the request and returns an error. Most callers should
|
|
||||||
// wrap this call in a function that returns a more useful result.
|
|
||||||
func (r *Links) Link(ctx context.Context, req *fuse.LinkRequest, old fs.Node) (fs.Node, error) {
|
|
||||||
tmp := *req
|
|
||||||
r.rec.RecordRequest(&tmp)
|
|
||||||
return nil, fuse.EIO
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordedLink returns information about the Link request.
|
|
||||||
// If no request was seen, returns a zero value.
|
|
||||||
func (r *Links) RecordedLink() fuse.LinkRequest {
|
|
||||||
val := r.rec.Recorded()
|
|
||||||
if val == nil {
|
|
||||||
return fuse.LinkRequest{}
|
|
||||||
}
|
|
||||||
return *(val.(*fuse.LinkRequest))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mknods records a Mknod request and its fields.
|
|
||||||
type Mknods struct {
|
|
||||||
rec RequestRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = fs.NodeMknoder(&Mknods{})
|
|
||||||
|
|
||||||
// Mknod records the request and returns an error. Most callers should
|
|
||||||
// wrap this call in a function that returns a more useful result.
|
|
||||||
func (r *Mknods) Mknod(ctx context.Context, req *fuse.MknodRequest) (fs.Node, error) {
|
|
||||||
tmp := *req
|
|
||||||
r.rec.RecordRequest(&tmp)
|
|
||||||
return nil, fuse.EIO
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordedMknod returns information about the Mknod request.
|
|
||||||
// If no request was seen, returns a zero value.
|
|
||||||
func (r *Mknods) RecordedMknod() fuse.MknodRequest {
|
|
||||||
val := r.rec.Recorded()
|
|
||||||
if val == nil {
|
|
||||||
return fuse.MknodRequest{}
|
|
||||||
}
|
|
||||||
return *(val.(*fuse.MknodRequest))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opens records a Open request and its fields.
|
|
||||||
type Opens struct {
|
|
||||||
rec RequestRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = fs.NodeOpener(&Opens{})
|
|
||||||
|
|
||||||
// Open records the request and returns an error. Most callers should
|
|
||||||
// wrap this call in a function that returns a more useful result.
|
|
||||||
func (r *Opens) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
|
|
||||||
tmp := *req
|
|
||||||
r.rec.RecordRequest(&tmp)
|
|
||||||
return nil, fuse.EIO
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordedOpen returns information about the Open request.
|
|
||||||
// If no request was seen, returns a zero value.
|
|
||||||
func (r *Opens) RecordedOpen() fuse.OpenRequest {
|
|
||||||
val := r.rec.Recorded()
|
|
||||||
if val == nil {
|
|
||||||
return fuse.OpenRequest{}
|
|
||||||
}
|
|
||||||
return *(val.(*fuse.OpenRequest))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getxattrs records a Getxattr request and its fields.
|
|
||||||
type Getxattrs struct {
|
|
||||||
rec RequestRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = fs.NodeGetxattrer(&Getxattrs{})
|
|
||||||
|
|
||||||
// Getxattr records the request and returns an error. Most callers should
|
|
||||||
// wrap this call in a function that returns a more useful result.
|
|
||||||
func (r *Getxattrs) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
|
|
||||||
tmp := *req
|
|
||||||
r.rec.RecordRequest(&tmp)
|
|
||||||
return fuse.ErrNoXattr
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordedGetxattr returns information about the Getxattr request.
|
|
||||||
// If no request was seen, returns a zero value.
|
|
||||||
func (r *Getxattrs) RecordedGetxattr() fuse.GetxattrRequest {
|
|
||||||
val := r.rec.Recorded()
|
|
||||||
if val == nil {
|
|
||||||
return fuse.GetxattrRequest{}
|
|
||||||
}
|
|
||||||
return *(val.(*fuse.GetxattrRequest))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listxattrs records a Listxattr request and its fields.
|
|
||||||
type Listxattrs struct {
|
|
||||||
rec RequestRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = fs.NodeListxattrer(&Listxattrs{})
|
|
||||||
|
|
||||||
// Listxattr records the request and returns an error. Most callers should
|
|
||||||
// wrap this call in a function that returns a more useful result.
|
|
||||||
func (r *Listxattrs) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {
|
|
||||||
tmp := *req
|
|
||||||
r.rec.RecordRequest(&tmp)
|
|
||||||
return fuse.ErrNoXattr
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordedListxattr returns information about the Listxattr request.
|
|
||||||
// If no request was seen, returns a zero value.
|
|
||||||
func (r *Listxattrs) RecordedListxattr() fuse.ListxattrRequest {
|
|
||||||
val := r.rec.Recorded()
|
|
||||||
if val == nil {
|
|
||||||
return fuse.ListxattrRequest{}
|
|
||||||
}
|
|
||||||
return *(val.(*fuse.ListxattrRequest))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setxattrs records a Setxattr request and its fields.
|
|
||||||
type Setxattrs struct {
|
|
||||||
rec RequestRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = fs.NodeSetxattrer(&Setxattrs{})
|
|
||||||
|
|
||||||
// Setxattr records the request and returns an error. Most callers should
|
|
||||||
// wrap this call in a function that returns a more useful result.
|
|
||||||
func (r *Setxattrs) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error {
|
|
||||||
tmp := *req
|
|
||||||
// The byte slice points to memory that will be reused, so make a
|
|
||||||
// deep copy.
|
|
||||||
tmp.Xattr = append([]byte(nil), req.Xattr...)
|
|
||||||
r.rec.RecordRequest(&tmp)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordedSetxattr returns information about the Setxattr request.
|
|
||||||
// If no request was seen, returns a zero value.
|
|
||||||
func (r *Setxattrs) RecordedSetxattr() fuse.SetxattrRequest {
|
|
||||||
val := r.rec.Recorded()
|
|
||||||
if val == nil {
|
|
||||||
return fuse.SetxattrRequest{}
|
|
||||||
}
|
|
||||||
return *(val.(*fuse.SetxattrRequest))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Removexattrs records a Removexattr request and its fields.
|
|
||||||
type Removexattrs struct {
|
|
||||||
rec RequestRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = fs.NodeRemovexattrer(&Removexattrs{})
|
|
||||||
|
|
||||||
// Removexattr records the request and returns an error. Most callers should
|
|
||||||
// wrap this call in a function that returns a more useful result.
|
|
||||||
func (r *Removexattrs) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error {
|
|
||||||
tmp := *req
|
|
||||||
r.rec.RecordRequest(&tmp)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordedRemovexattr returns information about the Removexattr request.
|
|
||||||
// If no request was seen, returns a zero value.
|
|
||||||
func (r *Removexattrs) RecordedRemovexattr() fuse.RemovexattrRequest {
|
|
||||||
val := r.rec.Recorded()
|
|
||||||
if val == nil {
|
|
||||||
return 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))
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
package record
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"bazil.org/fuse"
|
|
||||||
"bazil.org/fuse/fs"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type nothing struct{}
|
|
||||||
|
|
||||||
// ReleaseWaiter notes whether a FUSE Release call has been seen.
|
|
||||||
//
|
|
||||||
// Releases are not guaranteed to happen synchronously with any client
|
|
||||||
// call, so they must be waited for.
|
|
||||||
type ReleaseWaiter struct {
|
|
||||||
once sync.Once
|
|
||||||
seen chan nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = fs.HandleReleaser(&ReleaseWaiter{})
|
|
||||||
|
|
||||||
func (r *ReleaseWaiter) init() {
|
|
||||||
r.once.Do(func() {
|
|
||||||
r.seen = make(chan nothing, 1)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ReleaseWaiter) Release(ctx context.Context, req *fuse.ReleaseRequest) error {
|
|
||||||
r.init()
|
|
||||||
close(r.seen)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForRelease waits for Release to be called.
|
|
||||||
//
|
|
||||||
// With zero duration, wait forever. Otherwise, timeout early
|
|
||||||
// in a more controller way than `-test.timeout`.
|
|
||||||
//
|
|
||||||
// Returns whether a Release was seen. Always true if dur==0.
|
|
||||||
func (r *ReleaseWaiter) WaitForRelease(dur time.Duration) bool {
|
|
||||||
r.init()
|
|
||||||
var timeout <-chan time.Time
|
|
||||||
if dur > 0 {
|
|
||||||
timeout = time.After(dur)
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-r.seen:
|
|
||||||
return true
|
|
||||||
case <-timeout:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
package fstestutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"bazil.org/fuse"
|
|
||||||
"bazil.org/fuse/fs"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SimpleFS is a trivial FS that just implements the Root method.
|
|
||||||
type SimpleFS struct {
|
|
||||||
Node fs.Node
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = fs.FS(SimpleFS{})
|
|
||||||
|
|
||||||
func (f SimpleFS) Root() (fs.Node, error) {
|
|
||||||
return f.Node, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// File can be embedded in a struct to make it look like a file.
|
|
||||||
type File struct{}
|
|
||||||
|
|
||||||
func (f File) Attr(ctx context.Context, a *fuse.Attr) error {
|
|
||||||
a.Mode = 0666
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dir can be embedded in a struct to make it look like a directory.
|
|
||||||
type Dir struct{}
|
|
||||||
|
|
||||||
func (f Dir) Attr(ctx context.Context, a *fuse.Attr) error {
|
|
||||||
a.Mode = os.ModeDir | 0777
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChildMap is a directory with child nodes looked up from a map.
|
|
||||||
type ChildMap map[string]fs.Node
|
|
||||||
|
|
||||||
var _ = fs.Node(&ChildMap{})
|
|
||||||
var _ = fs.NodeStringLookuper(&ChildMap{})
|
|
||||||
|
|
||||||
func (f *ChildMap) Attr(ctx context.Context, a *fuse.Attr) error {
|
|
||||||
a.Mode = os.ModeDir | 0777
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *ChildMap) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
|
||||||
child, ok := (*f)[name]
|
|
||||||
if !ok {
|
|
||||||
return nil, fuse.ENOENT
|
|
||||||
}
|
|
||||||
return child, nil
|
|
||||||
}
|
|
67
vendor/src/bazil.org/fuse/fs/helpers_test.go
vendored
67
vendor/src/bazil.org/fuse/fs/helpers_test.go
vendored
|
@ -1,67 +0,0 @@
|
||||||
package fs_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"flag"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var childHelpers = map[string]func(){}
|
|
||||||
|
|
||||||
type childProcess struct {
|
|
||||||
name string
|
|
||||||
fn func()
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ flag.Value = (*childProcess)(nil)
|
|
||||||
|
|
||||||
func (c *childProcess) String() string {
|
|
||||||
return c.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *childProcess) Set(s string) error {
|
|
||||||
fn, ok := childHelpers[s]
|
|
||||||
if !ok {
|
|
||||||
return errors.New("helper not found")
|
|
||||||
}
|
|
||||||
c.name = s
|
|
||||||
c.fn = fn
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var childMode childProcess
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.Var(&childMode, "fuse.internal.child", "internal use only")
|
|
||||||
}
|
|
||||||
|
|
||||||
// childCmd prepares a test function to be run in a subprocess, with
|
|
||||||
// childMode set to true. Caller must still call Run or Start.
|
|
||||||
//
|
|
||||||
// Re-using the test executable as the subprocess is useful because
|
|
||||||
// now test executables can e.g. be cross-compiled, transferred
|
|
||||||
// between hosts, and run in settings where the whole Go development
|
|
||||||
// environment is not installed.
|
|
||||||
func childCmd(childName string) (*exec.Cmd, error) {
|
|
||||||
// caller may set cwd, so we can't rely on relative paths
|
|
||||||
executable, err := filepath.Abs(os.Args[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cmd := exec.Command(executable, "-fuse.internal.child="+childName)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
flag.Parse()
|
|
||||||
if childMode.fn != nil {
|
|
||||||
childMode.fn()
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
os.Exit(m.Run())
|
|
||||||
}
|
|
1568
vendor/src/bazil.org/fuse/fs/serve.go
vendored
1568
vendor/src/bazil.org/fuse/fs/serve.go
vendored
File diff suppressed because it is too large
Load diff
|
@ -1,30 +0,0 @@
|
||||||
package fs_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"bazil.org/fuse/fs/fstestutil"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
type exchangeData struct {
|
|
||||||
fstestutil.File
|
|
||||||
// this struct cannot be zero size or multiple instances may look identical
|
|
||||||
_ int
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExchangeDataNotSupported(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{
|
|
||||||
"one": &exchangeData{},
|
|
||||||
"two": &exchangeData{},
|
|
||||||
}}, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer mnt.Close()
|
|
||||||
|
|
||||||
if err := unix.Exchangedata(mnt.Dir+"/one", mnt.Dir+"/two", 0); err != unix.ENOTSUP {
|
|
||||||
t.Fatalf("expected ENOTSUP from exchangedata: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
2843
vendor/src/bazil.org/fuse/fs/serve_test.go
vendored
2843
vendor/src/bazil.org/fuse/fs/serve_test.go
vendored
File diff suppressed because it is too large
Load diff
99
vendor/src/bazil.org/fuse/fs/tree.go
vendored
99
vendor/src/bazil.org/fuse/fs/tree.go
vendored
|
@ -1,99 +0,0 @@
|
||||||
// FUSE directory tree, for servers that wish to use it with the service loop.
|
|
||||||
|
|
||||||
package fs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
pathpkg "path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bazil.org/fuse"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Tree implements a basic read-only directory tree for FUSE.
|
|
||||||
// The Nodes contained in it may still be writable.
|
|
||||||
type Tree struct {
|
|
||||||
tree
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) Root() (Node, error) {
|
|
||||||
return &t.tree, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds the path to the tree, resolving to the given node.
|
|
||||||
// If path or a prefix of path has already been added to the tree,
|
|
||||||
// Add panics.
|
|
||||||
//
|
|
||||||
// Add is only safe to call before starting to serve requests.
|
|
||||||
func (t *Tree) Add(path string, node Node) {
|
|
||||||
path = pathpkg.Clean("/" + path)[1:]
|
|
||||||
elems := strings.Split(path, "/")
|
|
||||||
dir := Node(&t.tree)
|
|
||||||
for i, elem := range elems {
|
|
||||||
dt, ok := dir.(*tree)
|
|
||||||
if !ok {
|
|
||||||
panic("fuse: Tree.Add for " + strings.Join(elems[:i], "/") + " and " + path)
|
|
||||||
}
|
|
||||||
n := dt.lookup(elem)
|
|
||||||
if n != nil {
|
|
||||||
if i+1 == len(elems) {
|
|
||||||
panic("fuse: Tree.Add for " + path + " conflicts with " + elem)
|
|
||||||
}
|
|
||||||
dir = n
|
|
||||||
} else {
|
|
||||||
if i+1 == len(elems) {
|
|
||||||
dt.add(elem, node)
|
|
||||||
} else {
|
|
||||||
dir = &tree{}
|
|
||||||
dt.add(elem, dir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type treeDir struct {
|
|
||||||
name string
|
|
||||||
node Node
|
|
||||||
}
|
|
||||||
|
|
||||||
type tree struct {
|
|
||||||
dir []treeDir
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tree) lookup(name string) Node {
|
|
||||||
for _, d := range t.dir {
|
|
||||||
if d.name == name {
|
|
||||||
return d.node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tree) add(name string, n Node) {
|
|
||||||
t.dir = append(t.dir, treeDir{name, n})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tree) Attr(ctx context.Context, a *fuse.Attr) error {
|
|
||||||
a.Mode = os.ModeDir | 0555
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tree) Lookup(ctx context.Context, name string) (Node, error) {
|
|
||||||
n := t.lookup(name)
|
|
||||||
if n != nil {
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
return nil, fuse.ENOENT
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tree) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
|
||||||
var out []fuse.Dirent
|
|
||||||
for _, d := range t.dir {
|
|
||||||
out = append(out, fuse.Dirent{Name: d.name})
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
2303
vendor/src/bazil.org/fuse/fuse.go
vendored
2303
vendor/src/bazil.org/fuse/fuse.go
vendored
File diff suppressed because it is too large
Load diff
9
vendor/src/bazil.org/fuse/fuse_darwin.go
vendored
9
vendor/src/bazil.org/fuse/fuse_darwin.go
vendored
|
@ -1,9 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
// Maximum file write size we are prepared to receive from the kernel.
|
|
||||||
//
|
|
||||||
// This value has to be >=16MB or OSXFUSE (3.4.0 observed) will
|
|
||||||
// forcibly close the /dev/fuse file descriptor on a Setxattr with a
|
|
||||||
// 16MB value. See TestSetxattr16MB and
|
|
||||||
// https://github.com/bazil/fuse/issues/42
|
|
||||||
const maxWrite = 16 * 1024 * 1024
|
|
6
vendor/src/bazil.org/fuse/fuse_freebsd.go
vendored
6
vendor/src/bazil.org/fuse/fuse_freebsd.go
vendored
|
@ -1,6 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
// Maximum file write size we are prepared to receive from the kernel.
|
|
||||||
//
|
|
||||||
// This number is just a guess.
|
|
||||||
const maxWrite = 128 * 1024
|
|
774
vendor/src/bazil.org/fuse/fuse_kernel.go
vendored
774
vendor/src/bazil.org/fuse/fuse_kernel.go
vendored
|
@ -1,774 +0,0 @@
|
||||||
// See the file LICENSE for copyright and licensing information.
|
|
||||||
|
|
||||||
// Derived from FUSE's fuse_kernel.h, which carries this notice:
|
|
||||||
/*
|
|
||||||
This file defines the kernel interface of FUSE
|
|
||||||
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
|
|
||||||
|
|
||||||
|
|
||||||
This -- and only this -- header file may also be distributed under
|
|
||||||
the terms of the BSD Licence as follows:
|
|
||||||
|
|
||||||
Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions
|
|
||||||
are met:
|
|
||||||
1. Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
2. 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.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package fuse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The FUSE version implemented by the package.
|
|
||||||
const (
|
|
||||||
protoVersionMinMajor = 7
|
|
||||||
protoVersionMinMinor = 8
|
|
||||||
protoVersionMaxMajor = 7
|
|
||||||
protoVersionMaxMinor = 12
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
rootID = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
type kstatfs struct {
|
|
||||||
Blocks uint64
|
|
||||||
Bfree uint64
|
|
||||||
Bavail uint64
|
|
||||||
Files uint64
|
|
||||||
Ffree uint64
|
|
||||||
Bsize uint32
|
|
||||||
Namelen uint32
|
|
||||||
Frsize uint32
|
|
||||||
_ uint32
|
|
||||||
Spare [6]uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type fileLock struct {
|
|
||||||
Start uint64
|
|
||||||
End uint64
|
|
||||||
Type uint32
|
|
||||||
Pid uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetattrFlags are bit flags that can be seen in GetattrRequest.
|
|
||||||
type GetattrFlags uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Indicates the handle is valid.
|
|
||||||
GetattrFh GetattrFlags = 1 << 0
|
|
||||||
)
|
|
||||||
|
|
||||||
var getattrFlagsNames = []flagName{
|
|
||||||
{uint32(GetattrFh), "GetattrFh"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fl GetattrFlags) String() string {
|
|
||||||
return flagString(uint32(fl), getattrFlagsNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The SetattrValid are bit flags describing which fields in the SetattrRequest
|
|
||||||
// are included in the change.
|
|
||||||
type SetattrValid uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
SetattrMode SetattrValid = 1 << 0
|
|
||||||
SetattrUid SetattrValid = 1 << 1
|
|
||||||
SetattrGid SetattrValid = 1 << 2
|
|
||||||
SetattrSize SetattrValid = 1 << 3
|
|
||||||
SetattrAtime SetattrValid = 1 << 4
|
|
||||||
SetattrMtime SetattrValid = 1 << 5
|
|
||||||
SetattrHandle SetattrValid = 1 << 6
|
|
||||||
|
|
||||||
// Linux only(?)
|
|
||||||
SetattrAtimeNow SetattrValid = 1 << 7
|
|
||||||
SetattrMtimeNow SetattrValid = 1 << 8
|
|
||||||
SetattrLockOwner SetattrValid = 1 << 9 // http://www.mail-archive.com/git-commits-head@vger.kernel.org/msg27852.html
|
|
||||||
|
|
||||||
// OS X only
|
|
||||||
SetattrCrtime SetattrValid = 1 << 28
|
|
||||||
SetattrChgtime SetattrValid = 1 << 29
|
|
||||||
SetattrBkuptime SetattrValid = 1 << 30
|
|
||||||
SetattrFlags SetattrValid = 1 << 31
|
|
||||||
)
|
|
||||||
|
|
||||||
func (fl SetattrValid) Mode() bool { return fl&SetattrMode != 0 }
|
|
||||||
func (fl SetattrValid) Uid() bool { return fl&SetattrUid != 0 }
|
|
||||||
func (fl SetattrValid) Gid() bool { return fl&SetattrGid != 0 }
|
|
||||||
func (fl SetattrValid) Size() bool { return fl&SetattrSize != 0 }
|
|
||||||
func (fl SetattrValid) Atime() bool { return fl&SetattrAtime != 0 }
|
|
||||||
func (fl SetattrValid) Mtime() bool { return fl&SetattrMtime != 0 }
|
|
||||||
func (fl SetattrValid) Handle() bool { return fl&SetattrHandle != 0 }
|
|
||||||
func (fl SetattrValid) AtimeNow() bool { return fl&SetattrAtimeNow != 0 }
|
|
||||||
func (fl SetattrValid) MtimeNow() bool { return fl&SetattrMtimeNow != 0 }
|
|
||||||
func (fl SetattrValid) LockOwner() bool { return fl&SetattrLockOwner != 0 }
|
|
||||||
func (fl SetattrValid) Crtime() bool { return fl&SetattrCrtime != 0 }
|
|
||||||
func (fl SetattrValid) Chgtime() bool { return fl&SetattrChgtime != 0 }
|
|
||||||
func (fl SetattrValid) Bkuptime() bool { return fl&SetattrBkuptime != 0 }
|
|
||||||
func (fl SetattrValid) Flags() bool { return fl&SetattrFlags != 0 }
|
|
||||||
|
|
||||||
func (fl SetattrValid) String() string {
|
|
||||||
return flagString(uint32(fl), setattrValidNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
var setattrValidNames = []flagName{
|
|
||||||
{uint32(SetattrMode), "SetattrMode"},
|
|
||||||
{uint32(SetattrUid), "SetattrUid"},
|
|
||||||
{uint32(SetattrGid), "SetattrGid"},
|
|
||||||
{uint32(SetattrSize), "SetattrSize"},
|
|
||||||
{uint32(SetattrAtime), "SetattrAtime"},
|
|
||||||
{uint32(SetattrMtime), "SetattrMtime"},
|
|
||||||
{uint32(SetattrHandle), "SetattrHandle"},
|
|
||||||
{uint32(SetattrAtimeNow), "SetattrAtimeNow"},
|
|
||||||
{uint32(SetattrMtimeNow), "SetattrMtimeNow"},
|
|
||||||
{uint32(SetattrLockOwner), "SetattrLockOwner"},
|
|
||||||
{uint32(SetattrCrtime), "SetattrCrtime"},
|
|
||||||
{uint32(SetattrChgtime), "SetattrChgtime"},
|
|
||||||
{uint32(SetattrBkuptime), "SetattrBkuptime"},
|
|
||||||
{uint32(SetattrFlags), "SetattrFlags"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flags that can be seen in OpenRequest.Flags.
|
|
||||||
const (
|
|
||||||
// Access modes. These are not 1-bit flags, but alternatives where
|
|
||||||
// only one can be chosen. See the IsReadOnly etc convenience
|
|
||||||
// methods.
|
|
||||||
OpenReadOnly OpenFlags = syscall.O_RDONLY
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
// OpenAccessModeMask is a bitmask that separates the access mode
|
|
||||||
// from the other flags in OpenFlags.
|
|
||||||
const OpenAccessModeMask OpenFlags = syscall.O_ACCMODE
|
|
||||||
|
|
||||||
// OpenFlags are the O_FOO flags passed to open/create/etc calls. For
|
|
||||||
// example, os.O_WRONLY | os.O_APPEND.
|
|
||||||
type OpenFlags uint32
|
|
||||||
|
|
||||||
func (fl OpenFlags) String() string {
|
|
||||||
// O_RDONLY, O_RWONLY, O_RDWR are not flags
|
|
||||||
s := accModeName(fl & OpenAccessModeMask)
|
|
||||||
flags := uint32(fl &^ OpenAccessModeMask)
|
|
||||||
if flags != 0 {
|
|
||||||
s = s + "+" + flagString(flags, openFlagNames)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true if OpenReadOnly is set.
|
|
||||||
func (fl OpenFlags) IsReadOnly() bool {
|
|
||||||
return fl&OpenAccessModeMask == OpenReadOnly
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true if OpenWriteOnly is set.
|
|
||||||
func (fl OpenFlags) IsWriteOnly() bool {
|
|
||||||
return fl&OpenAccessModeMask == OpenWriteOnly
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true if OpenReadWrite is set.
|
|
||||||
func (fl OpenFlags) IsReadWrite() bool {
|
|
||||||
return fl&OpenAccessModeMask == OpenReadWrite
|
|
||||||
}
|
|
||||||
|
|
||||||
func accModeName(flags OpenFlags) string {
|
|
||||||
switch flags {
|
|
||||||
case OpenReadOnly:
|
|
||||||
return "OpenReadOnly"
|
|
||||||
case OpenWriteOnly:
|
|
||||||
return "OpenWriteOnly"
|
|
||||||
case OpenReadWrite:
|
|
||||||
return "OpenReadWrite"
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var openFlagNames = []flagName{
|
|
||||||
{uint32(OpenAppend), "OpenAppend"},
|
|
||||||
{uint32(OpenCreate), "OpenCreate"},
|
|
||||||
{uint32(OpenDirectory), "OpenDirectory"},
|
|
||||||
{uint32(OpenExclusive), "OpenExclusive"},
|
|
||||||
{uint32(OpenNonblock), "OpenNonblock"},
|
|
||||||
{uint32(OpenSync), "OpenSync"},
|
|
||||||
{uint32(OpenTruncate), "OpenTruncate"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// The OpenResponseFlags are returned in the OpenResponse.
|
|
||||||
type OpenResponseFlags uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
OpenDirectIO OpenResponseFlags = 1 << 0 // bypass page cache for this open file
|
|
||||||
OpenKeepCache OpenResponseFlags = 1 << 1 // don't invalidate the data cache on open
|
|
||||||
OpenNonSeekable OpenResponseFlags = 1 << 2 // mark the file as non-seekable (not supported on OS X)
|
|
||||||
|
|
||||||
OpenPurgeAttr OpenResponseFlags = 1 << 30 // OS X
|
|
||||||
OpenPurgeUBC OpenResponseFlags = 1 << 31 // OS X
|
|
||||||
)
|
|
||||||
|
|
||||||
func (fl OpenResponseFlags) String() string {
|
|
||||||
return flagString(uint32(fl), openResponseFlagNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
var openResponseFlagNames = []flagName{
|
|
||||||
{uint32(OpenDirectIO), "OpenDirectIO"},
|
|
||||||
{uint32(OpenKeepCache), "OpenKeepCache"},
|
|
||||||
{uint32(OpenNonSeekable), "OpenNonSeekable"},
|
|
||||||
{uint32(OpenPurgeAttr), "OpenPurgeAttr"},
|
|
||||||
{uint32(OpenPurgeUBC), "OpenPurgeUBC"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// The InitFlags are used in the Init exchange.
|
|
||||||
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.
|
|
||||||
InitDontMask InitFlags = 1 << 6
|
|
||||||
InitSpliceWrite InitFlags = 1 << 7
|
|
||||||
InitSpliceMove InitFlags = 1 << 8
|
|
||||||
InitSpliceRead InitFlags = 1 << 9
|
|
||||||
InitFlockLocks InitFlags = 1 << 10
|
|
||||||
InitHasIoctlDir InitFlags = 1 << 11
|
|
||||||
InitAutoInvalData InitFlags = 1 << 12
|
|
||||||
InitDoReaddirplus InitFlags = 1 << 13
|
|
||||||
InitReaddirplusAuto InitFlags = 1 << 14
|
|
||||||
InitAsyncDIO InitFlags = 1 << 15
|
|
||||||
InitWritebackCache InitFlags = 1 << 16
|
|
||||||
InitNoOpenSupport InitFlags = 1 << 17
|
|
||||||
|
|
||||||
InitCaseSensitive InitFlags = 1 << 29 // OS X only
|
|
||||||
InitVolRename InitFlags = 1 << 30 // OS X only
|
|
||||||
InitXtimes InitFlags = 1 << 31 // OS X only
|
|
||||||
)
|
|
||||||
|
|
||||||
type flagName struct {
|
|
||||||
bit uint32
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
var initFlagNames = []flagName{
|
|
||||||
{uint32(InitAsyncRead), "InitAsyncRead"},
|
|
||||||
{uint32(InitPosixLocks), "InitPosixLocks"},
|
|
||||||
{uint32(InitFileOps), "InitFileOps"},
|
|
||||||
{uint32(InitAtomicTrunc), "InitAtomicTrunc"},
|
|
||||||
{uint32(InitExportSupport), "InitExportSupport"},
|
|
||||||
{uint32(InitBigWrites), "InitBigWrites"},
|
|
||||||
{uint32(InitDontMask), "InitDontMask"},
|
|
||||||
{uint32(InitSpliceWrite), "InitSpliceWrite"},
|
|
||||||
{uint32(InitSpliceMove), "InitSpliceMove"},
|
|
||||||
{uint32(InitSpliceRead), "InitSpliceRead"},
|
|
||||||
{uint32(InitFlockLocks), "InitFlockLocks"},
|
|
||||||
{uint32(InitHasIoctlDir), "InitHasIoctlDir"},
|
|
||||||
{uint32(InitAutoInvalData), "InitAutoInvalData"},
|
|
||||||
{uint32(InitDoReaddirplus), "InitDoReaddirplus"},
|
|
||||||
{uint32(InitReaddirplusAuto), "InitReaddirplusAuto"},
|
|
||||||
{uint32(InitAsyncDIO), "InitAsyncDIO"},
|
|
||||||
{uint32(InitWritebackCache), "InitWritebackCache"},
|
|
||||||
{uint32(InitNoOpenSupport), "InitNoOpenSupport"},
|
|
||||||
|
|
||||||
{uint32(InitCaseSensitive), "InitCaseSensitive"},
|
|
||||||
{uint32(InitVolRename), "InitVolRename"},
|
|
||||||
{uint32(InitXtimes), "InitXtimes"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fl InitFlags) String() string {
|
|
||||||
return flagString(uint32(fl), initFlagNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func flagString(f uint32, names []flagName) string {
|
|
||||||
var s string
|
|
||||||
|
|
||||||
if f == 0 {
|
|
||||||
return "0"
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, n := range names {
|
|
||||||
if f&n.bit != 0 {
|
|
||||||
s += "+" + n.name
|
|
||||||
f &^= n.bit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if f != 0 {
|
|
||||||
s += fmt.Sprintf("%+#x", f)
|
|
||||||
}
|
|
||||||
return s[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// The ReleaseFlags are used in the Release exchange.
|
|
||||||
type ReleaseFlags uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
ReleaseFlush ReleaseFlags = 1 << 0
|
|
||||||
)
|
|
||||||
|
|
||||||
func (fl ReleaseFlags) String() string {
|
|
||||||
return flagString(uint32(fl), releaseFlagNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
var releaseFlagNames = []flagName{
|
|
||||||
{uint32(ReleaseFlush), "ReleaseFlush"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opcodes
|
|
||||||
const (
|
|
||||||
opLookup = 1
|
|
||||||
opForget = 2 // no reply
|
|
||||||
opGetattr = 3
|
|
||||||
opSetattr = 4
|
|
||||||
opReadlink = 5
|
|
||||||
opSymlink = 6
|
|
||||||
opMknod = 8
|
|
||||||
opMkdir = 9
|
|
||||||
opUnlink = 10
|
|
||||||
opRmdir = 11
|
|
||||||
opRename = 12
|
|
||||||
opLink = 13
|
|
||||||
opOpen = 14
|
|
||||||
opRead = 15
|
|
||||||
opWrite = 16
|
|
||||||
opStatfs = 17
|
|
||||||
opRelease = 18
|
|
||||||
opFsync = 20
|
|
||||||
opSetxattr = 21
|
|
||||||
opGetxattr = 22
|
|
||||||
opListxattr = 23
|
|
||||||
opRemovexattr = 24
|
|
||||||
opFlush = 25
|
|
||||||
opInit = 26
|
|
||||||
opOpendir = 27
|
|
||||||
opReaddir = 28
|
|
||||||
opReleasedir = 29
|
|
||||||
opFsyncdir = 30
|
|
||||||
opGetlk = 31
|
|
||||||
opSetlk = 32
|
|
||||||
opSetlkw = 33
|
|
||||||
opAccess = 34
|
|
||||||
opCreate = 35
|
|
||||||
opInterrupt = 36
|
|
||||||
opBmap = 37
|
|
||||||
opDestroy = 38
|
|
||||||
opIoctl = 39 // Linux?
|
|
||||||
opPoll = 40 // Linux?
|
|
||||||
|
|
||||||
// OS X
|
|
||||||
opSetvolname = 61
|
|
||||||
opGetxtimes = 62
|
|
||||||
opExchange = 63
|
|
||||||
)
|
|
||||||
|
|
||||||
type entryOut struct {
|
|
||||||
Nodeid uint64 // Inode ID
|
|
||||||
Generation uint64 // Inode generation
|
|
||||||
EntryValid uint64 // Cache timeout for the name
|
|
||||||
AttrValid uint64 // Cache timeout for the attributes
|
|
||||||
EntryValidNsec uint32
|
|
||||||
AttrValidNsec uint32
|
|
||||||
Attr attr
|
|
||||||
}
|
|
||||||
|
|
||||||
func entryOutSize(p Protocol) uintptr {
|
|
||||||
switch {
|
|
||||||
case p.LT(Protocol{7, 9}):
|
|
||||||
return unsafe.Offsetof(entryOut{}.Attr) + unsafe.Offsetof(entryOut{}.Attr.Blksize)
|
|
||||||
default:
|
|
||||||
return unsafe.Sizeof(entryOut{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type forgetIn struct {
|
|
||||||
Nlookup uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type getattrIn struct {
|
|
||||||
GetattrFlags uint32
|
|
||||||
_ uint32
|
|
||||||
Fh uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type attrOut struct {
|
|
||||||
AttrValid uint64 // Cache timeout for the attributes
|
|
||||||
AttrValidNsec uint32
|
|
||||||
_ uint32
|
|
||||||
Attr attr
|
|
||||||
}
|
|
||||||
|
|
||||||
func attrOutSize(p Protocol) uintptr {
|
|
||||||
switch {
|
|
||||||
case p.LT(Protocol{7, 9}):
|
|
||||||
return unsafe.Offsetof(attrOut{}.Attr) + unsafe.Offsetof(attrOut{}.Attr.Blksize)
|
|
||||||
default:
|
|
||||||
return unsafe.Sizeof(attrOut{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OS X
|
|
||||||
type getxtimesOut struct {
|
|
||||||
Bkuptime uint64
|
|
||||||
Crtime uint64
|
|
||||||
BkuptimeNsec uint32
|
|
||||||
CrtimeNsec uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type mknodIn struct {
|
|
||||||
Mode uint32
|
|
||||||
Rdev uint32
|
|
||||||
Umask uint32
|
|
||||||
_ uint32
|
|
||||||
// "filename\x00" follows.
|
|
||||||
}
|
|
||||||
|
|
||||||
func mknodInSize(p Protocol) uintptr {
|
|
||||||
switch {
|
|
||||||
case p.LT(Protocol{7, 12}):
|
|
||||||
return unsafe.Offsetof(mknodIn{}.Umask)
|
|
||||||
default:
|
|
||||||
return unsafe.Sizeof(mknodIn{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type mkdirIn struct {
|
|
||||||
Mode uint32
|
|
||||||
Umask uint32
|
|
||||||
// filename follows
|
|
||||||
}
|
|
||||||
|
|
||||||
func mkdirInSize(p Protocol) uintptr {
|
|
||||||
switch {
|
|
||||||
case p.LT(Protocol{7, 12}):
|
|
||||||
return unsafe.Offsetof(mkdirIn{}.Umask) + 4
|
|
||||||
default:
|
|
||||||
return unsafe.Sizeof(mkdirIn{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type renameIn struct {
|
|
||||||
Newdir uint64
|
|
||||||
// "oldname\x00newname\x00" follows
|
|
||||||
}
|
|
||||||
|
|
||||||
// OS X
|
|
||||||
type exchangeIn struct {
|
|
||||||
Olddir uint64
|
|
||||||
Newdir uint64
|
|
||||||
Options uint64
|
|
||||||
// "oldname\x00newname\x00" follows
|
|
||||||
}
|
|
||||||
|
|
||||||
type linkIn struct {
|
|
||||||
Oldnodeid uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type setattrInCommon struct {
|
|
||||||
Valid uint32
|
|
||||||
_ uint32
|
|
||||||
Fh uint64
|
|
||||||
Size uint64
|
|
||||||
LockOwner uint64 // unused on OS X?
|
|
||||||
Atime uint64
|
|
||||||
Mtime uint64
|
|
||||||
Unused2 uint64
|
|
||||||
AtimeNsec uint32
|
|
||||||
MtimeNsec uint32
|
|
||||||
Unused3 uint32
|
|
||||||
Mode uint32
|
|
||||||
Unused4 uint32
|
|
||||||
Uid uint32
|
|
||||||
Gid uint32
|
|
||||||
Unused5 uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type openIn struct {
|
|
||||||
Flags uint32
|
|
||||||
Unused uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type openOut struct {
|
|
||||||
Fh uint64
|
|
||||||
OpenFlags uint32
|
|
||||||
_ uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type createIn struct {
|
|
||||||
Flags uint32
|
|
||||||
Mode uint32
|
|
||||||
Umask uint32
|
|
||||||
_ uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func createInSize(p Protocol) uintptr {
|
|
||||||
switch {
|
|
||||||
case p.LT(Protocol{7, 12}):
|
|
||||||
return unsafe.Offsetof(createIn{}.Umask)
|
|
||||||
default:
|
|
||||||
return unsafe.Sizeof(createIn{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type releaseIn struct {
|
|
||||||
Fh uint64
|
|
||||||
Flags uint32
|
|
||||||
ReleaseFlags uint32
|
|
||||||
LockOwner uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type flushIn struct {
|
|
||||||
Fh uint64
|
|
||||||
FlushFlags uint32
|
|
||||||
_ uint32
|
|
||||||
LockOwner uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type readIn struct {
|
|
||||||
Fh uint64
|
|
||||||
Offset uint64
|
|
||||||
Size uint32
|
|
||||||
ReadFlags uint32
|
|
||||||
LockOwner uint64
|
|
||||||
Flags uint32
|
|
||||||
_ uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func readInSize(p Protocol) uintptr {
|
|
||||||
switch {
|
|
||||||
case p.LT(Protocol{7, 9}):
|
|
||||||
return unsafe.Offsetof(readIn{}.ReadFlags) + 4
|
|
||||||
default:
|
|
||||||
return unsafe.Sizeof(readIn{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The ReadFlags are passed in ReadRequest.
|
|
||||||
type ReadFlags uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
// LockOwner field is valid.
|
|
||||||
ReadLockOwner ReadFlags = 1 << 1
|
|
||||||
)
|
|
||||||
|
|
||||||
var readFlagNames = []flagName{
|
|
||||||
{uint32(ReadLockOwner), "ReadLockOwner"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fl ReadFlags) String() string {
|
|
||||||
return flagString(uint32(fl), readFlagNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
type writeIn struct {
|
|
||||||
Fh uint64
|
|
||||||
Offset uint64
|
|
||||||
Size uint32
|
|
||||||
WriteFlags uint32
|
|
||||||
LockOwner uint64
|
|
||||||
Flags uint32
|
|
||||||
_ uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeInSize(p Protocol) uintptr {
|
|
||||||
switch {
|
|
||||||
case p.LT(Protocol{7, 9}):
|
|
||||||
return unsafe.Offsetof(writeIn{}.LockOwner)
|
|
||||||
default:
|
|
||||||
return unsafe.Sizeof(writeIn{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type writeOut struct {
|
|
||||||
Size uint32
|
|
||||||
_ uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// The WriteFlags are passed in WriteRequest.
|
|
||||||
type WriteFlags uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
WriteCache WriteFlags = 1 << 0
|
|
||||||
// LockOwner field is valid.
|
|
||||||
WriteLockOwner WriteFlags = 1 << 1
|
|
||||||
)
|
|
||||||
|
|
||||||
var writeFlagNames = []flagName{
|
|
||||||
{uint32(WriteCache), "WriteCache"},
|
|
||||||
{uint32(WriteLockOwner), "WriteLockOwner"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fl WriteFlags) String() string {
|
|
||||||
return flagString(uint32(fl), writeFlagNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
const compatStatfsSize = 48
|
|
||||||
|
|
||||||
type statfsOut struct {
|
|
||||||
St kstatfs
|
|
||||||
}
|
|
||||||
|
|
||||||
type fsyncIn struct {
|
|
||||||
Fh uint64
|
|
||||||
FsyncFlags uint32
|
|
||||||
_ uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type setxattrInCommon struct {
|
|
||||||
Size uint32
|
|
||||||
Flags uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (setxattrInCommon) position() uint32 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type getxattrInCommon struct {
|
|
||||||
Size uint32
|
|
||||||
_ uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (getxattrInCommon) position() uint32 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type getxattrOut struct {
|
|
||||||
Size uint32
|
|
||||||
_ uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type lkIn struct {
|
|
||||||
Fh uint64
|
|
||||||
Owner uint64
|
|
||||||
Lk fileLock
|
|
||||||
LkFlags uint32
|
|
||||||
_ uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func lkInSize(p Protocol) uintptr {
|
|
||||||
switch {
|
|
||||||
case p.LT(Protocol{7, 9}):
|
|
||||||
return unsafe.Offsetof(lkIn{}.LkFlags)
|
|
||||||
default:
|
|
||||||
return unsafe.Sizeof(lkIn{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type lkOut struct {
|
|
||||||
Lk fileLock
|
|
||||||
}
|
|
||||||
|
|
||||||
type accessIn struct {
|
|
||||||
Mask uint32
|
|
||||||
_ uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type initIn struct {
|
|
||||||
Major uint32
|
|
||||||
Minor uint32
|
|
||||||
MaxReadahead uint32
|
|
||||||
Flags uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
const initInSize = int(unsafe.Sizeof(initIn{}))
|
|
||||||
|
|
||||||
type initOut struct {
|
|
||||||
Major uint32
|
|
||||||
Minor uint32
|
|
||||||
MaxReadahead uint32
|
|
||||||
Flags uint32
|
|
||||||
Unused uint32
|
|
||||||
MaxWrite uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type interruptIn struct {
|
|
||||||
Unique uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type bmapIn struct {
|
|
||||||
Block uint64
|
|
||||||
BlockSize uint32
|
|
||||||
_ uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type bmapOut struct {
|
|
||||||
Block uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type inHeader struct {
|
|
||||||
Len uint32
|
|
||||||
Opcode uint32
|
|
||||||
Unique uint64
|
|
||||||
Nodeid uint64
|
|
||||||
Uid uint32
|
|
||||||
Gid uint32
|
|
||||||
Pid uint32
|
|
||||||
_ uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
const inHeaderSize = int(unsafe.Sizeof(inHeader{}))
|
|
||||||
|
|
||||||
type outHeader struct {
|
|
||||||
Len uint32
|
|
||||||
Error int32
|
|
||||||
Unique uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type dirent struct {
|
|
||||||
Ino uint64
|
|
||||||
Off uint64
|
|
||||||
Namelen uint32
|
|
||||||
Type uint32
|
|
||||||
Name [0]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
const direntSize = 8 + 8 + 4 + 4
|
|
||||||
|
|
||||||
const (
|
|
||||||
notifyCodePoll int32 = 1
|
|
||||||
notifyCodeInvalInode int32 = 2
|
|
||||||
notifyCodeInvalEntry int32 = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
type notifyInvalInodeOut struct {
|
|
||||||
Ino uint64
|
|
||||||
Off int64
|
|
||||||
Len int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type notifyInvalEntryOut struct {
|
|
||||||
Parent uint64
|
|
||||||
Namelen uint32
|
|
||||||
_ uint32
|
|
||||||
}
|
|
88
vendor/src/bazil.org/fuse/fuse_kernel_darwin.go
vendored
88
vendor/src/bazil.org/fuse/fuse_kernel_darwin.go
vendored
|
@ -1,88 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type attr struct {
|
|
||||||
Ino uint64
|
|
||||||
Size uint64
|
|
||||||
Blocks uint64
|
|
||||||
Atime uint64
|
|
||||||
Mtime uint64
|
|
||||||
Ctime uint64
|
|
||||||
Crtime_ uint64 // OS X only
|
|
||||||
AtimeNsec uint32
|
|
||||||
MtimeNsec uint32
|
|
||||||
CtimeNsec uint32
|
|
||||||
CrtimeNsec uint32 // OS X only
|
|
||||||
Mode uint32
|
|
||||||
Nlink uint32
|
|
||||||
Uid uint32
|
|
||||||
Gid uint32
|
|
||||||
Rdev uint32
|
|
||||||
Flags_ uint32 // OS X only; see chflags(2)
|
|
||||||
Blksize uint32
|
|
||||||
padding uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *attr) SetCrtime(s uint64, ns uint32) {
|
|
||||||
a.Crtime_, a.CrtimeNsec = s, ns
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *attr) SetFlags(f uint32) {
|
|
||||||
a.Flags_ = f
|
|
||||||
}
|
|
||||||
|
|
||||||
type setattrIn struct {
|
|
||||||
setattrInCommon
|
|
||||||
|
|
||||||
// OS X only
|
|
||||||
Bkuptime_ uint64
|
|
||||||
Chgtime_ uint64
|
|
||||||
Crtime uint64
|
|
||||||
BkuptimeNsec uint32
|
|
||||||
ChgtimeNsec uint32
|
|
||||||
CrtimeNsec uint32
|
|
||||||
Flags_ uint32 // see chflags(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (in *setattrIn) BkupTime() time.Time {
|
|
||||||
return time.Unix(int64(in.Bkuptime_), int64(in.BkuptimeNsec))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (in *setattrIn) Chgtime() time.Time {
|
|
||||||
return time.Unix(int64(in.Chgtime_), int64(in.ChgtimeNsec))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (in *setattrIn) Flags() uint32 {
|
|
||||||
return in.Flags_
|
|
||||||
}
|
|
||||||
|
|
||||||
func openFlags(flags uint32) OpenFlags {
|
|
||||||
return OpenFlags(flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
type getxattrIn struct {
|
|
||||||
getxattrInCommon
|
|
||||||
|
|
||||||
// OS X only
|
|
||||||
Position uint32
|
|
||||||
Padding uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *getxattrIn) position() uint32 {
|
|
||||||
return g.Position
|
|
||||||
}
|
|
||||||
|
|
||||||
type setxattrIn struct {
|
|
||||||
setxattrInCommon
|
|
||||||
|
|
||||||
// OS X only
|
|
||||||
Position uint32
|
|
||||||
Padding uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *setxattrIn) position() uint32 {
|
|
||||||
return s.Position
|
|
||||||
}
|
|
62
vendor/src/bazil.org/fuse/fuse_kernel_freebsd.go
vendored
62
vendor/src/bazil.org/fuse/fuse_kernel_freebsd.go
vendored
|
@ -1,62 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type attr struct {
|
|
||||||
Ino uint64
|
|
||||||
Size uint64
|
|
||||||
Blocks uint64
|
|
||||||
Atime uint64
|
|
||||||
Mtime uint64
|
|
||||||
Ctime uint64
|
|
||||||
AtimeNsec uint32
|
|
||||||
MtimeNsec uint32
|
|
||||||
CtimeNsec uint32
|
|
||||||
Mode uint32
|
|
||||||
Nlink uint32
|
|
||||||
Uid uint32
|
|
||||||
Gid uint32
|
|
||||||
Rdev uint32
|
|
||||||
Blksize uint32
|
|
||||||
padding uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *attr) Crtime() time.Time {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *attr) SetCrtime(s uint64, ns uint32) {
|
|
||||||
// ignored on freebsd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *attr) SetFlags(f uint32) {
|
|
||||||
// ignored on freebsd
|
|
||||||
}
|
|
||||||
|
|
||||||
type setattrIn struct {
|
|
||||||
setattrInCommon
|
|
||||||
}
|
|
||||||
|
|
||||||
func (in *setattrIn) BkupTime() time.Time {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (in *setattrIn) Chgtime() time.Time {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (in *setattrIn) Flags() uint32 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func openFlags(flags uint32) OpenFlags {
|
|
||||||
return OpenFlags(flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
type getxattrIn struct {
|
|
||||||
getxattrInCommon
|
|
||||||
}
|
|
||||||
|
|
||||||
type setxattrIn struct {
|
|
||||||
setxattrInCommon
|
|
||||||
}
|
|
70
vendor/src/bazil.org/fuse/fuse_kernel_linux.go
vendored
70
vendor/src/bazil.org/fuse/fuse_kernel_linux.go
vendored
|
@ -1,70 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type attr struct {
|
|
||||||
Ino uint64
|
|
||||||
Size uint64
|
|
||||||
Blocks uint64
|
|
||||||
Atime uint64
|
|
||||||
Mtime uint64
|
|
||||||
Ctime uint64
|
|
||||||
AtimeNsec uint32
|
|
||||||
MtimeNsec uint32
|
|
||||||
CtimeNsec uint32
|
|
||||||
Mode uint32
|
|
||||||
Nlink uint32
|
|
||||||
Uid uint32
|
|
||||||
Gid uint32
|
|
||||||
Rdev uint32
|
|
||||||
Blksize uint32
|
|
||||||
padding uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *attr) Crtime() time.Time {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *attr) SetCrtime(s uint64, ns uint32) {
|
|
||||||
// Ignored on Linux.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *attr) SetFlags(f uint32) {
|
|
||||||
// Ignored on Linux.
|
|
||||||
}
|
|
||||||
|
|
||||||
type setattrIn struct {
|
|
||||||
setattrInCommon
|
|
||||||
}
|
|
||||||
|
|
||||||
func (in *setattrIn) BkupTime() time.Time {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (in *setattrIn) Chgtime() time.Time {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (in *setattrIn) Flags() uint32 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func openFlags(flags uint32) OpenFlags {
|
|
||||||
// on amd64, the 32-bit O_LARGEFILE flag is always seen;
|
|
||||||
// on i386, the flag probably depends on the app
|
|
||||||
// requesting, but in any case should be utterly
|
|
||||||
// uninteresting to us here; our kernel protocol messages
|
|
||||||
// are not directly related to the client app's kernel
|
|
||||||
// API/ABI
|
|
||||||
flags &^= 0x8000
|
|
||||||
|
|
||||||
return OpenFlags(flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
type getxattrIn struct {
|
|
||||||
getxattrInCommon
|
|
||||||
}
|
|
||||||
|
|
||||||
type setxattrIn struct {
|
|
||||||
setxattrInCommon
|
|
||||||
}
|
|
1
vendor/src/bazil.org/fuse/fuse_kernel_std.go
vendored
1
vendor/src/bazil.org/fuse/fuse_kernel_std.go
vendored
|
@ -1 +0,0 @@
|
||||||
package fuse
|
|
63
vendor/src/bazil.org/fuse/fuse_kernel_test.go
vendored
63
vendor/src/bazil.org/fuse/fuse_kernel_test.go
vendored
|
@ -1,63 +0,0 @@
|
||||||
package fuse_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"bazil.org/fuse"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestOpenFlagsAccmodeMaskReadWrite(t *testing.T) {
|
|
||||||
var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC)
|
|
||||||
if g, e := f&fuse.OpenAccessModeMask, fuse.OpenReadWrite; g != e {
|
|
||||||
t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e)
|
|
||||||
}
|
|
||||||
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 TestOpenFlagsAccmodeMaskReadOnly(t *testing.T) {
|
|
||||||
var f = fuse.OpenFlags(os.O_RDONLY | os.O_SYNC)
|
|
||||||
if g, e := f&fuse.OpenAccessModeMask, fuse.OpenReadOnly; g != e {
|
|
||||||
t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e)
|
|
||||||
}
|
|
||||||
if !f.IsReadOnly() {
|
|
||||||
t.Fatalf("IsReadOnly is wrong: %v", f)
|
|
||||||
}
|
|
||||||
if f.IsWriteOnly() {
|
|
||||||
t.Fatalf("IsWriteOnly is wrong: %v", f)
|
|
||||||
}
|
|
||||||
if f.IsReadWrite() {
|
|
||||||
t.Fatalf("IsReadWrite is wrong: %v", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOpenFlagsAccmodeMaskWriteOnly(t *testing.T) {
|
|
||||||
var f = fuse.OpenFlags(os.O_WRONLY | os.O_SYNC)
|
|
||||||
if g, e := f&fuse.OpenAccessModeMask, fuse.OpenWriteOnly; g != e {
|
|
||||||
t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e)
|
|
||||||
}
|
|
||||||
if f.IsReadOnly() {
|
|
||||||
t.Fatalf("IsReadOnly is wrong: %v", f)
|
|
||||||
}
|
|
||||||
if !f.IsWriteOnly() {
|
|
||||||
t.Fatalf("IsWriteOnly is wrong: %v", f)
|
|
||||||
}
|
|
||||||
if f.IsReadWrite() {
|
|
||||||
t.Fatalf("IsReadWrite is wrong: %v", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOpenFlagsString(t *testing.T) {
|
|
||||||
var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC | os.O_APPEND)
|
|
||||||
if g, e := f.String(), "OpenReadWrite+OpenAppend+OpenSync"; g != e {
|
|
||||||
t.Fatalf("OpenFlags.String: %q != %q", g, e)
|
|
||||||
}
|
|
||||||
}
|
|
7
vendor/src/bazil.org/fuse/fuse_linux.go
vendored
7
vendor/src/bazil.org/fuse/fuse_linux.go
vendored
|
@ -1,7 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
// Maximum file write size we are prepared to receive from the kernel.
|
|
||||||
//
|
|
||||||
// Linux 4.2.0 has been observed to cap this value at 128kB
|
|
||||||
// (FUSE_MAX_PAGES_PER_REQ=32, 4kB pages).
|
|
||||||
const maxWrite = 128 * 1024
|
|
20
vendor/src/bazil.org/fuse/fuseutil/fuseutil.go
vendored
20
vendor/src/bazil.org/fuse/fuseutil/fuseutil.go
vendored
|
@ -1,20 +0,0 @@
|
||||||
package fuseutil // import "bazil.org/fuse/fuseutil"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bazil.org/fuse"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HandleRead handles a read request assuming that data is the entire file content.
|
|
||||||
// It adjusts the amount returned in resp according to req.Offset and req.Size.
|
|
||||||
func HandleRead(req *fuse.ReadRequest, resp *fuse.ReadResponse, data []byte) {
|
|
||||||
if req.Offset >= int64(len(data)) {
|
|
||||||
data = nil
|
|
||||||
} else {
|
|
||||||
data = data[req.Offset:]
|
|
||||||
}
|
|
||||||
if len(data) > req.Size {
|
|
||||||
data = data[:req.Size]
|
|
||||||
}
|
|
||||||
n := copy(resp.Data[:req.Size], data)
|
|
||||||
resp.Data = resp.Data[:n]
|
|
||||||
}
|
|
38
vendor/src/bazil.org/fuse/mount.go
vendored
38
vendor/src/bazil.org/fuse/mount.go
vendored
|
@ -1,38 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrOSXFUSENotFound is returned from Mount when the OSXFUSE
|
|
||||||
// installation is not detected.
|
|
||||||
//
|
|
||||||
// Only happens on OS X. Make sure OSXFUSE is installed, or see
|
|
||||||
// OSXFUSELocations for customization.
|
|
||||||
ErrOSXFUSENotFound = errors.New("cannot locate OSXFUSE")
|
|
||||||
)
|
|
||||||
|
|
||||||
func neverIgnoreLine(line string) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func lineLogger(wg *sync.WaitGroup, prefix string, ignore func(line string) bool, r io.ReadCloser) {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(r)
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
if ignore(line) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Printf("%s: %s", prefix, line)
|
|
||||||
}
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
log.Printf("%s, error reading: %v", prefix, err)
|
|
||||||
}
|
|
||||||
}
|
|
208
vendor/src/bazil.org/fuse/mount_darwin.go
vendored
208
vendor/src/bazil.org/fuse/mount_darwin.go
vendored
|
@ -1,208 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errNoAvail = errors.New("no available fuse devices")
|
|
||||||
errNotLoaded = errors.New("osxfuse is not loaded")
|
|
||||||
)
|
|
||||||
|
|
||||||
func loadOSXFUSE(bin string) error {
|
|
||||||
cmd := exec.Command(bin)
|
|
||||||
cmd.Dir = "/"
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
err := cmd.Run()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func openOSXFUSEDev(devPrefix string) (*os.File, error) {
|
|
||||||
var f *os.File
|
|
||||||
var err error
|
|
||||||
for i := uint64(0); ; i++ {
|
|
||||||
path := devPrefix + 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 handleMountOSXFUSE(helperName string, errCh chan<- error) func(line string) (ignore bool) {
|
|
||||||
var noMountpointPrefix = helperName + `: `
|
|
||||||
const noMountpointSuffix = `: No such file or directory`
|
|
||||||
return func(line string) (ignore bool) {
|
|
||||||
if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) {
|
|
||||||
// re-extract it from the error message in case some layer
|
|
||||||
// changed the path
|
|
||||||
mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)]
|
|
||||||
err := &MountpointDoesNotExistError{
|
|
||||||
Path: mountpoint,
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case errCh <- err:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
// not the first error; fall back to logging it
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// isBoringMountOSXFUSEError returns whether the Wait error is
|
|
||||||
// uninteresting; exit status 64 is.
|
|
||||||
func isBoringMountOSXFUSEError(err error) bool {
|
|
||||||
if err, ok := err.(*exec.ExitError); ok && err.Exited() {
|
|
||||||
if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 64 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func callMount(bin string, daemonVar string, dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, errp *error) error {
|
|
||||||
for k, v := range conf.options {
|
|
||||||
if strings.Contains(k, ",") || strings.Contains(v, ",") {
|
|
||||||
// Silly limitation but the mount helper does not
|
|
||||||
// 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()
|
|
||||||
// OSXFUSE <3.3.0
|
|
||||||
cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=")
|
|
||||||
// OSXFUSE >=3.3.0
|
|
||||||
cmd.Env = append(cmd.Env, "MOUNT_OSXFUSE_CALL_BY_LIB=")
|
|
||||||
|
|
||||||
daemon := os.Args[0]
|
|
||||||
if daemonVar != "" {
|
|
||||||
cmd.Env = append(cmd.Env, daemonVar+"="+daemon)
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("setting up mount_osxfusefs stderr: %v", err)
|
|
||||||
}
|
|
||||||
stderr, err := cmd.StderrPipe()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("setting up mount_osxfusefs stderr: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
return fmt.Errorf("mount_osxfusefs: %v", err)
|
|
||||||
}
|
|
||||||
helperErrCh := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(2)
|
|
||||||
go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout)
|
|
||||||
helperName := path.Base(bin)
|
|
||||||
go lineLogger(&wg, "mount helper error", handleMountOSXFUSE(helperName, helperErrCh), stderr)
|
|
||||||
wg.Wait()
|
|
||||||
if err := cmd.Wait(); err != nil {
|
|
||||||
// see if we have a better error to report
|
|
||||||
select {
|
|
||||||
case helperErr := <-helperErrCh:
|
|
||||||
// log the Wait error if it's not what we expected
|
|
||||||
if !isBoringMountOSXFUSEError(err) {
|
|
||||||
log.Printf("mount helper failed: %v", err)
|
|
||||||
}
|
|
||||||
// and now return what we grabbed from stderr as the real
|
|
||||||
// error
|
|
||||||
*errp = helperErr
|
|
||||||
close(ready)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
// nope, fall back to generic message
|
|
||||||
}
|
|
||||||
|
|
||||||
*errp = fmt.Errorf("mount_osxfusefs: %v", err)
|
|
||||||
close(ready)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
*errp = nil
|
|
||||||
close(ready)
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) {
|
|
||||||
locations := conf.osxfuseLocations
|
|
||||||
if locations == nil {
|
|
||||||
locations = []OSXFUSEPaths{
|
|
||||||
OSXFUSELocationV3,
|
|
||||||
OSXFUSELocationV2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, loc := range locations {
|
|
||||||
if _, err := os.Stat(loc.Mount); os.IsNotExist(err) {
|
|
||||||
// try the other locations
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := openOSXFUSEDev(loc.DevicePrefix)
|
|
||||||
if err == errNotLoaded {
|
|
||||||
err = loadOSXFUSE(loc.Load)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// try again
|
|
||||||
f, err = openOSXFUSEDev(loc.DevicePrefix)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = callMount(loc.Mount, loc.DaemonVar, dir, conf, f, ready, errp)
|
|
||||||
if err != nil {
|
|
||||||
f.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
return nil, ErrOSXFUSENotFound
|
|
||||||
}
|
|
111
vendor/src/bazil.org/fuse/mount_freebsd.go
vendored
111
vendor/src/bazil.org/fuse/mount_freebsd.go
vendored
|
@ -1,111 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
func handleMountFusefsStderr(errCh chan<- error) func(line string) (ignore bool) {
|
|
||||||
return func(line string) (ignore bool) {
|
|
||||||
const (
|
|
||||||
noMountpointPrefix = `mount_fusefs: `
|
|
||||||
noMountpointSuffix = `: No such file or directory`
|
|
||||||
)
|
|
||||||
if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) {
|
|
||||||
// re-extract it from the error message in case some layer
|
|
||||||
// changed the path
|
|
||||||
mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)]
|
|
||||||
err := &MountpointDoesNotExistError{
|
|
||||||
Path: mountpoint,
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case errCh <- err:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
// not the first error; fall back to logging it
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// isBoringMountFusefsError returns whether the Wait error is
|
|
||||||
// uninteresting; exit status 1 is.
|
|
||||||
func isBoringMountFusefsError(err error) bool {
|
|
||||||
if err, ok := err.(*exec.ExitError); ok && err.Exited() {
|
|
||||||
if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 1 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) {
|
|
||||||
for k, v := range conf.options {
|
|
||||||
if strings.Contains(k, ",") || strings.Contains(v, ",") {
|
|
||||||
// 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}
|
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("setting up mount_fusefs stderr: %v", err)
|
|
||||||
}
|
|
||||||
stderr, err := cmd.StderrPipe()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("setting up mount_fusefs stderr: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
return nil, fmt.Errorf("mount_fusefs: %v", err)
|
|
||||||
}
|
|
||||||
helperErrCh := make(chan error, 1)
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(2)
|
|
||||||
go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout)
|
|
||||||
go lineLogger(&wg, "mount helper error", handleMountFusefsStderr(helperErrCh), stderr)
|
|
||||||
wg.Wait()
|
|
||||||
if err := cmd.Wait(); err != nil {
|
|
||||||
// see if we have a better error to report
|
|
||||||
select {
|
|
||||||
case helperErr := <-helperErrCh:
|
|
||||||
// log the Wait error if it's not what we expected
|
|
||||||
if !isBoringMountFusefsError(err) {
|
|
||||||
log.Printf("mount helper failed: %v", err)
|
|
||||||
}
|
|
||||||
// and now return what we grabbed from stderr as the real
|
|
||||||
// error
|
|
||||||
return nil, helperErr
|
|
||||||
default:
|
|
||||||
// nope, fall back to generic message
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("mount_fusefs: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
close(ready)
|
|
||||||
return f, nil
|
|
||||||
}
|
|
150
vendor/src/bazil.org/fuse/mount_linux.go
vendored
150
vendor/src/bazil.org/fuse/mount_linux.go
vendored
|
@ -1,150 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
func handleFusermountStderr(errCh chan<- error) func(line string) (ignore bool) {
|
|
||||||
return func(line string) (ignore bool) {
|
|
||||||
if line == `fusermount: failed to open /etc/fuse.conf: Permission denied` {
|
|
||||||
// Silence this particular message, it occurs way too
|
|
||||||
// commonly and isn't very relevant to whether the mount
|
|
||||||
// succeeds or not.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
noMountpointPrefix = `fusermount: failed to access mountpoint `
|
|
||||||
noMountpointSuffix = `: No such file or directory`
|
|
||||||
)
|
|
||||||
if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) {
|
|
||||||
// re-extract it from the error message in case some layer
|
|
||||||
// changed the path
|
|
||||||
mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)]
|
|
||||||
err := &MountpointDoesNotExistError{
|
|
||||||
Path: mountpoint,
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case errCh <- err:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
// not the first error; fall back to logging it
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// isBoringFusermountError returns whether the Wait error is
|
|
||||||
// uninteresting; exit status 1 is.
|
|
||||||
func isBoringFusermountError(err error) bool {
|
|
||||||
if err, ok := err.(*exec.ExitError); ok && err.Exited() {
|
|
||||||
if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 1 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
helperErrCh := make(chan error, 1)
|
|
||||||
wg.Add(2)
|
|
||||||
go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout)
|
|
||||||
go lineLogger(&wg, "mount helper error", handleFusermountStderr(helperErrCh), stderr)
|
|
||||||
wg.Wait()
|
|
||||||
if err := cmd.Wait(); err != nil {
|
|
||||||
// see if we have a better error to report
|
|
||||||
select {
|
|
||||||
case helperErr := <-helperErrCh:
|
|
||||||
// log the Wait error if it's not what we expected
|
|
||||||
if !isBoringFusermountError(err) {
|
|
||||||
log.Printf("mount helper failed: %v", err)
|
|
||||||
}
|
|
||||||
// and now return what we grabbed from stderr as the real
|
|
||||||
// error
|
|
||||||
return nil, helperErr
|
|
||||||
default:
|
|
||||||
// nope, fall back to generic message
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("fusermount: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
310
vendor/src/bazil.org/fuse/options.go
vendored
310
vendor/src/bazil.org/fuse/options.go
vendored
|
@ -1,310 +0,0 @@
|
||||||
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
|
|
||||||
osxfuseLocations []OSXFUSEPaths
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NoAppleDouble makes OSXFUSE disallow files with names used by OS X
|
|
||||||
// to store extended attributes on file systems that do not support
|
|
||||||
// them natively.
|
|
||||||
//
|
|
||||||
// Such file names are:
|
|
||||||
//
|
|
||||||
// ._*
|
|
||||||
// .DS_Store
|
|
||||||
//
|
|
||||||
// OS X only. Others ignore this option.
|
|
||||||
func NoAppleDouble() MountOption {
|
|
||||||
return noAppleDouble
|
|
||||||
}
|
|
||||||
|
|
||||||
// NoAppleXattr makes OSXFUSE disallow extended attributes with the
|
|
||||||
// prefix "com.apple.". This disables persistent Finder state and
|
|
||||||
// other such information.
|
|
||||||
//
|
|
||||||
// OS X only. Others ignore this option.
|
|
||||||
func NoAppleXattr() MountOption {
|
|
||||||
return noAppleXattr
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExclCreate causes O_EXCL flag to be set for only "truly" exclusive creates,
|
|
||||||
// i.e. create calls for which the initiator explicitly set the O_EXCL flag.
|
|
||||||
//
|
|
||||||
// OSXFUSE expects all create calls to return EEXIST in case the file
|
|
||||||
// already exists, regardless of whether O_EXCL was specified or not.
|
|
||||||
// To ensure this behavior, it normally sets OpenExclusive for all
|
|
||||||
// Create calls, regardless of whether the original call had it set.
|
|
||||||
// For distributed filesystems, that may force every file create to be
|
|
||||||
// a distributed consensus action, causing undesirable delays.
|
|
||||||
//
|
|
||||||
// This option makes the FUSE filesystem see the original flag value,
|
|
||||||
// and better decide when to ensure global consensus.
|
|
||||||
//
|
|
||||||
// Note that returning EEXIST on existing file create is still
|
|
||||||
// expected with OSXFUSE, regardless of the presence of the
|
|
||||||
// OpenExclusive flag.
|
|
||||||
//
|
|
||||||
// For more information, see
|
|
||||||
// https://github.com/osxfuse/osxfuse/issues/209
|
|
||||||
//
|
|
||||||
// OS X only. Others ignore this options.
|
|
||||||
// Requires OSXFUSE 3.4.1 or newer.
|
|
||||||
func ExclCreate() MountOption {
|
|
||||||
return exclCreate
|
|
||||||
}
|
|
||||||
|
|
||||||
// DaemonTimeout sets the time in seconds between a request and a reply before
|
|
||||||
// the FUSE mount is declared dead.
|
|
||||||
//
|
|
||||||
// OS X and FreeBSD only. Others ignore this option.
|
|
||||||
func DaemonTimeout(name string) MountOption {
|
|
||||||
return daemonTimeout(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrCannotCombineAllowOtherAndAllowRoot = errors.New("cannot combine AllowOther and AllowRoot")
|
|
||||||
|
|
||||||
// AllowOther allows other users to access the file system.
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllowDev enables interpreting character or block special devices on the
|
|
||||||
// filesystem.
|
|
||||||
func AllowDev() MountOption {
|
|
||||||
return func(conf *mountConfig) error {
|
|
||||||
conf.options["dev"] = ""
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllowSUID allows set-user-identifier or set-group-identifier bits to take
|
|
||||||
// effect.
|
|
||||||
func AllowSUID() MountOption {
|
|
||||||
return func(conf *mountConfig) error {
|
|
||||||
conf.options["suid"] = ""
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultPermissions makes the kernel enforce access control based on
|
|
||||||
// the file mode (as in chmod).
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OSXFUSEPaths describes the paths used by an installed OSXFUSE
|
|
||||||
// version. See OSXFUSELocationV3 for typical values.
|
|
||||||
type OSXFUSEPaths struct {
|
|
||||||
// Prefix for the device file. At mount time, an incrementing
|
|
||||||
// number is suffixed until a free FUSE device is found.
|
|
||||||
DevicePrefix string
|
|
||||||
// Path of the load helper, used to load the kernel extension if
|
|
||||||
// no device files are found.
|
|
||||||
Load string
|
|
||||||
// Path of the mount helper, used for the actual mount operation.
|
|
||||||
Mount string
|
|
||||||
// Environment variable used to pass the path to the executable
|
|
||||||
// calling the mount helper.
|
|
||||||
DaemonVar string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default paths for OSXFUSE. See OSXFUSELocations.
|
|
||||||
var (
|
|
||||||
OSXFUSELocationV3 = OSXFUSEPaths{
|
|
||||||
DevicePrefix: "/dev/osxfuse",
|
|
||||||
Load: "/Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse",
|
|
||||||
Mount: "/Library/Filesystems/osxfuse.fs/Contents/Resources/mount_osxfuse",
|
|
||||||
DaemonVar: "MOUNT_OSXFUSE_DAEMON_PATH",
|
|
||||||
}
|
|
||||||
OSXFUSELocationV2 = OSXFUSEPaths{
|
|
||||||
DevicePrefix: "/dev/osxfuse",
|
|
||||||
Load: "/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs",
|
|
||||||
Mount: "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs",
|
|
||||||
DaemonVar: "MOUNT_FUSEFS_DAEMON_PATH",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// OSXFUSELocations sets where to look for OSXFUSE files. The
|
|
||||||
// arguments are all the possible locations. The previous locations
|
|
||||||
// are replaced.
|
|
||||||
//
|
|
||||||
// Without this option, OSXFUSELocationV3 and OSXFUSELocationV2 are
|
|
||||||
// used.
|
|
||||||
//
|
|
||||||
// OS X only. Others ignore this option.
|
|
||||||
func OSXFUSELocations(paths ...OSXFUSEPaths) MountOption {
|
|
||||||
return func(conf *mountConfig) error {
|
|
||||||
if len(paths) == 0 {
|
|
||||||
return errors.New("must specify at least one location for OSXFUSELocations")
|
|
||||||
}
|
|
||||||
// replace previous values, but make a copy so there's no
|
|
||||||
// worries about caller mutating their slice
|
|
||||||
conf.osxfuseLocations = append(conf.osxfuseLocations[:0], paths...)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllowNonEmptyMount allows the mounting over a non-empty directory.
|
|
||||||
//
|
|
||||||
// The files in it will be shadowed by the freshly created mount. By
|
|
||||||
// default these mounts are rejected to prevent accidental covering up
|
|
||||||
// of data, which could for example prevent automatic backup.
|
|
||||||
func AllowNonEmptyMount() MountOption {
|
|
||||||
return func(conf *mountConfig) error {
|
|
||||||
conf.options["nonempty"] = ""
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
// Test for adjustable timeout between a FUSE request and the daemon's response.
|
|
||||||
//
|
|
||||||
// +build darwin freebsd
|
|
||||||
|
|
||||||
package fuse_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"syscall"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"bazil.org/fuse"
|
|
||||||
"bazil.org/fuse/fs"
|
|
||||||
"bazil.org/fuse/fs/fstestutil"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type slowCreaterDir struct {
|
|
||||||
fstestutil.Dir
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.NodeCreater = slowCreaterDir{}
|
|
||||||
|
|
||||||
func (c slowCreaterDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
|
|
||||||
time.Sleep(10 * time.Second)
|
|
||||||
// pick a really distinct error, to identify it later
|
|
||||||
return nil, nil, fuse.Errno(syscall.ENAMETOOLONG)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMountOptionDaemonTimeout(t *testing.T) {
|
|
||||||
if runtime.GOOS != "darwin" && runtime.GOOS != "freebsd" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping time-based test in short mode")
|
|
||||||
}
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
mnt, err := fstestutil.MountedT(t,
|
|
||||||
fstestutil.SimpleFS{slowCreaterDir{}},
|
|
||||||
nil,
|
|
||||||
fuse.DaemonTimeout("2"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer mnt.Close()
|
|
||||||
|
|
||||||
// This should fail by the kernel timing out the request.
|
|
||||||
f, err := os.Create(mnt.Dir + "/child")
|
|
||||||
if err == nil {
|
|
||||||
f.Close()
|
|
||||||
t.Fatal("expected an error")
|
|
||||||
}
|
|
||||||
perr, ok := err.(*os.PathError)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("expected PathError, got %T: %v", err, err)
|
|
||||||
}
|
|
||||||
if perr.Err == syscall.ENAMETOOLONG {
|
|
||||||
t.Fatalf("expected other than ENAMETOOLONG, got %T: %v", err, err)
|
|
||||||
}
|
|
||||||
}
|
|
35
vendor/src/bazil.org/fuse/options_darwin.go
vendored
35
vendor/src/bazil.org/fuse/options_darwin.go
vendored
|
@ -1,35 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func daemonTimeout(name string) MountOption {
|
|
||||||
return func(conf *mountConfig) error {
|
|
||||||
conf.options["daemon_timeout"] = name
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func noAppleXattr(conf *mountConfig) error {
|
|
||||||
conf.options["noapplexattr"] = ""
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func noAppleDouble(conf *mountConfig) error {
|
|
||||||
conf.options["noappledouble"] = ""
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func exclCreate(conf *mountConfig) error {
|
|
||||||
conf.options["excl_create"] = ""
|
|
||||||
return nil
|
|
||||||
}
|
|
28
vendor/src/bazil.org/fuse/options_freebsd.go
vendored
28
vendor/src/bazil.org/fuse/options_freebsd.go
vendored
|
@ -1,28 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
func localVolume(conf *mountConfig) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func volumeName(name string) MountOption {
|
|
||||||
return dummyOption
|
|
||||||
}
|
|
||||||
|
|
||||||
func daemonTimeout(name string) MountOption {
|
|
||||||
return func(conf *mountConfig) error {
|
|
||||||
conf.options["timeout"] = name
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func noAppleXattr(conf *mountConfig) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func noAppleDouble(conf *mountConfig) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func exclCreate(conf *mountConfig) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
10
vendor/src/bazil.org/fuse/options_helper_test.go
vendored
10
vendor/src/bazil.org/fuse/options_helper_test.go
vendored
|
@ -1,10 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
// for TestMountOptionCommaError
|
|
||||||
func ForTestSetMountOption(k, v string) MountOption {
|
|
||||||
fn := func(conf *mountConfig) error {
|
|
||||||
conf.options[k] = v
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fn
|
|
||||||
}
|
|
25
vendor/src/bazil.org/fuse/options_linux.go
vendored
25
vendor/src/bazil.org/fuse/options_linux.go
vendored
|
@ -1,25 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
func localVolume(conf *mountConfig) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func volumeName(name string) MountOption {
|
|
||||||
return dummyOption
|
|
||||||
}
|
|
||||||
|
|
||||||
func daemonTimeout(name string) MountOption {
|
|
||||||
return dummyOption
|
|
||||||
}
|
|
||||||
|
|
||||||
func noAppleXattr(conf *mountConfig) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func noAppleDouble(conf *mountConfig) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func exclCreate(conf *mountConfig) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
// This file contains tests for platforms that have no escape
|
|
||||||
// mechanism for including commas in mount options.
|
|
||||||
//
|
|
||||||
// +build darwin
|
|
||||||
|
|
||||||
package fuse_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"bazil.org/fuse"
|
|
||||||
"bazil.org/fuse/fs/fstestutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMountOptionCommaError(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
// this test is not tied to any specific option, it just needs
|
|
||||||
// some string content
|
|
||||||
var evil = "FuseTest,Marker"
|
|
||||||
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
|
|
||||||
fuse.ForTestSetMountOption("fusetest", evil),
|
|
||||||
)
|
|
||||||
if err == nil {
|
|
||||||
mnt.Close()
|
|
||||||
t.Fatal("expected an error about commas")
|
|
||||||
}
|
|
||||||
if g, e := err.Error(), `mount options cannot contain commas on `+runtime.GOOS+`: "fusetest"="FuseTest,Marker"`; g != e {
|
|
||||||
t.Fatalf("wrong error: %q != %q", g, e)
|
|
||||||
}
|
|
||||||
}
|
|
231
vendor/src/bazil.org/fuse/options_test.go
vendored
231
vendor/src/bazil.org/fuse/options_test.go
vendored
|
@ -1,231 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
75
vendor/src/bazil.org/fuse/protocol.go
vendored
75
vendor/src/bazil.org/fuse/protocol.go
vendored
|
@ -1,75 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Protocol is a FUSE protocol version number.
|
|
||||||
type Protocol struct {
|
|
||||||
Major uint32
|
|
||||||
Minor uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Protocol) String() string {
|
|
||||||
return fmt.Sprintf("%d.%d", p.Major, p.Minor)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LT returns whether a is less than b.
|
|
||||||
func (a Protocol) LT(b Protocol) bool {
|
|
||||||
return a.Major < b.Major ||
|
|
||||||
(a.Major == b.Major && a.Minor < b.Minor)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GE returns whether a is greater than or equal to b.
|
|
||||||
func (a Protocol) GE(b Protocol) bool {
|
|
||||||
return a.Major > b.Major ||
|
|
||||||
(a.Major == b.Major && a.Minor >= b.Minor)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Protocol) is79() bool {
|
|
||||||
return a.GE(Protocol{7, 9})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasAttrBlockSize returns whether Attr.BlockSize is respected by the
|
|
||||||
// kernel.
|
|
||||||
func (a Protocol) HasAttrBlockSize() bool {
|
|
||||||
return a.is79()
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasReadWriteFlags returns whether ReadRequest/WriteRequest
|
|
||||||
// fields Flags and FileFlags are valid.
|
|
||||||
func (a Protocol) HasReadWriteFlags() bool {
|
|
||||||
return a.is79()
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasGetattrFlags returns whether GetattrRequest field Flags is
|
|
||||||
// valid.
|
|
||||||
func (a Protocol) HasGetattrFlags() bool {
|
|
||||||
return a.is79()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Protocol) is710() bool {
|
|
||||||
return a.GE(Protocol{7, 10})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasOpenNonSeekable returns whether OpenResponse field Flags flag
|
|
||||||
// OpenNonSeekable is supported.
|
|
||||||
func (a Protocol) HasOpenNonSeekable() bool {
|
|
||||||
return a.is710()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Protocol) is712() bool {
|
|
||||||
return a.GE(Protocol{7, 12})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasUmask returns whether CreateRequest/MkdirRequest/MknodRequest
|
|
||||||
// field Umask is valid.
|
|
||||||
func (a Protocol) HasUmask() bool {
|
|
||||||
return a.is712()
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasInvalidate returns whether InvalidateNode/InvalidateEntry are
|
|
||||||
// supported.
|
|
||||||
func (a Protocol) HasInvalidate() bool {
|
|
||||||
return a.is712()
|
|
||||||
}
|
|
13
vendor/src/bazil.org/fuse/syscallx/doc.go
vendored
13
vendor/src/bazil.org/fuse/syscallx/doc.go
vendored
|
@ -1,13 +0,0 @@
|
||||||
// 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 // import "bazil.org/fuse/syscallx"
|
|
34
vendor/src/bazil.org/fuse/syscallx/generate
vendored
34
vendor/src/bazil.org/fuse/syscallx/generate
vendored
|
@ -1,34 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
set -e
|
|
||||||
|
|
||||||
mksys="$(go env GOROOT)/src/pkg/syscall/mksyscall.pl"
|
|
||||||
|
|
||||||
fix() {
|
|
||||||
sed 's,^package syscall$,&x\nimport "syscall",' \
|
|
||||||
| gofmt -r='BytePtrFromString -> syscall.BytePtrFromString' \
|
|
||||||
| gofmt -r='Syscall6 -> syscall.Syscall6' \
|
|
||||||
| gofmt -r='Syscall -> syscall.Syscall' \
|
|
||||||
| gofmt -r='SYS_GETXATTR -> syscall.SYS_GETXATTR' \
|
|
||||||
| gofmt -r='SYS_LISTXATTR -> syscall.SYS_LISTXATTR' \
|
|
||||||
| gofmt -r='SYS_SETXATTR -> syscall.SYS_SETXATTR' \
|
|
||||||
| gofmt -r='SYS_REMOVEXATTR -> syscall.SYS_REMOVEXATTR' \
|
|
||||||
| gofmt -r='SYS_MSYNC -> syscall.SYS_MSYNC'
|
|
||||||
}
|
|
||||||
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
|
|
||||||
$mksys xattr_darwin.go \
|
|
||||||
| fix \
|
|
||||||
>xattr_darwin_amd64.go
|
|
||||||
|
|
||||||
$mksys -l32 xattr_darwin.go \
|
|
||||||
| fix \
|
|
||||||
>xattr_darwin_386.go
|
|
||||||
|
|
||||||
$mksys msync.go \
|
|
||||||
| fix \
|
|
||||||
>msync_amd64.go
|
|
||||||
|
|
||||||
$mksys -l32 msync.go \
|
|
||||||
| fix \
|
|
||||||
>msync_386.go
|
|
9
vendor/src/bazil.org/fuse/syscallx/msync.go
vendored
9
vendor/src/bazil.org/fuse/syscallx/msync.go
vendored
|
@ -1,9 +0,0 @@
|
||||||
package syscallx
|
|
||||||
|
|
||||||
/* This is the source file for msync_*.go, to regenerate run
|
|
||||||
|
|
||||||
./generate
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
//sys Msync(b []byte, flags int) (err error)
|
|
24
vendor/src/bazil.org/fuse/syscallx/msync_386.go
vendored
24
vendor/src/bazil.org/fuse/syscallx/msync_386.go
vendored
|
@ -1,24 +0,0 @@
|
||||||
// mksyscall.pl -l32 msync.go
|
|
||||||
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
|
||||||
|
|
||||||
package syscallx
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func Msync(b []byte, flags int) (err error) {
|
|
||||||
var _p0 unsafe.Pointer
|
|
||||||
if len(b) > 0 {
|
|
||||||
_p0 = unsafe.Pointer(&b[0])
|
|
||||||
} else {
|
|
||||||
_p0 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
_, _, e1 := syscall.Syscall(syscall.SYS_MSYNC, uintptr(_p0), uintptr(len(b)), uintptr(flags))
|
|
||||||
if e1 != 0 {
|
|
||||||
err = e1
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
// mksyscall.pl msync.go
|
|
||||||
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
|
||||||
|
|
||||||
package syscallx
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func Msync(b []byte, flags int) (err error) {
|
|
||||||
var _p0 unsafe.Pointer
|
|
||||||
if len(b) > 0 {
|
|
||||||
_p0 = unsafe.Pointer(&b[0])
|
|
||||||
} else {
|
|
||||||
_p0 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
_, _, e1 := syscall.Syscall(syscall.SYS_MSYNC, uintptr(_p0), uintptr(len(b)), uintptr(flags))
|
|
||||||
if e1 != 0 {
|
|
||||||
err = e1
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
package syscallx
|
|
||||||
|
|
||||||
// make us look more like package syscall, so mksyscall.pl output works
|
|
||||||
var _zero uintptr
|
|
|
@ -1,26 +0,0 @@
|
||||||
// +build !darwin
|
|
||||||
|
|
||||||
package syscallx
|
|
||||||
|
|
||||||
// This file just contains wrappers for platforms that already have
|
|
||||||
// the right stuff in golang.org/x/sys/unix.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Getxattr(path string, attr string, dest []byte) (sz int, err error) {
|
|
||||||
return unix.Getxattr(path, attr, dest)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Listxattr(path string, dest []byte) (sz int, err error) {
|
|
||||||
return unix.Listxattr(path, dest)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Setxattr(path string, attr string, data []byte, flags int) (err error) {
|
|
||||||
return unix.Setxattr(path, attr, data, flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Removexattr(path string, attr string) (err error) {
|
|
||||||
return unix.Removexattr(path, attr)
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package syscallx
|
|
||||||
|
|
||||||
/* This is the source file for syscallx_darwin_*.go, to regenerate run
|
|
||||||
|
|
||||||
./generate
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// cannot use dest []byte here because OS X getxattr really wants a
|
|
||||||
// NULL to trigger size probing, size==0 is not enough
|
|
||||||
//
|
|
||||||
//sys getxattr(path string, attr string, dest *byte, size int, position uint32, options int) (sz int, err error)
|
|
||||||
|
|
||||||
func Getxattr(path string, attr string, dest []byte) (sz int, err error) {
|
|
||||||
var destp *byte
|
|
||||||
if len(dest) > 0 {
|
|
||||||
destp = &dest[0]
|
|
||||||
}
|
|
||||||
return getxattr(path, attr, destp, len(dest), 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
//sys listxattr(path string, dest []byte, options int) (sz int, err error)
|
|
||||||
|
|
||||||
func Listxattr(path string, dest []byte) (sz int, err error) {
|
|
||||||
return listxattr(path, dest, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
//sys setxattr(path string, attr string, data []byte, position uint32, flags int) (err error)
|
|
||||||
|
|
||||||
func Setxattr(path string, attr string, data []byte, flags int) (err error) {
|
|
||||||
return setxattr(path, attr, data, 0, flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
//sys removexattr(path string, attr string, options int) (err error)
|
|
||||||
|
|
||||||
func Removexattr(path string, attr string) (err error) {
|
|
||||||
return removexattr(path, attr, 0)
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
// mksyscall.pl -l32 xattr_darwin.go
|
|
||||||
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
|
||||||
|
|
||||||
package syscallx
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func getxattr(path string, attr string, dest *byte, size int, position uint32, options int) (sz int, err error) {
|
|
||||||
var _p0 *byte
|
|
||||||
_p0, err = syscall.BytePtrFromString(path)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p1 *byte
|
|
||||||
_p1, err = syscall.BytePtrFromString(attr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(unsafe.Pointer(dest)), uintptr(size), uintptr(position), uintptr(options))
|
|
||||||
sz = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = e1
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func listxattr(path string, dest []byte, options int) (sz int, err error) {
|
|
||||||
var _p0 *byte
|
|
||||||
_p0, err = syscall.BytePtrFromString(path)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p1 unsafe.Pointer
|
|
||||||
if len(dest) > 0 {
|
|
||||||
_p1 = unsafe.Pointer(&dest[0])
|
|
||||||
} else {
|
|
||||||
_p1 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
r0, _, e1 := syscall.Syscall6(syscall.SYS_LISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(dest)), uintptr(options), 0, 0)
|
|
||||||
sz = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = e1
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func setxattr(path string, attr string, data []byte, position uint32, flags int) (err error) {
|
|
||||||
var _p0 *byte
|
|
||||||
_p0, err = syscall.BytePtrFromString(path)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p1 *byte
|
|
||||||
_p1, err = syscall.BytePtrFromString(attr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p2 unsafe.Pointer
|
|
||||||
if len(data) > 0 {
|
|
||||||
_p2 = unsafe.Pointer(&data[0])
|
|
||||||
} else {
|
|
||||||
_p2 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
_, _, e1 := syscall.Syscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(data)), uintptr(position), uintptr(flags))
|
|
||||||
if e1 != 0 {
|
|
||||||
err = e1
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func removexattr(path string, attr string, options int) (err error) {
|
|
||||||
var _p0 *byte
|
|
||||||
_p0, err = syscall.BytePtrFromString(path)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p1 *byte
|
|
||||||
_p1, err = syscall.BytePtrFromString(attr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, _, e1 := syscall.Syscall(syscall.SYS_REMOVEXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(options))
|
|
||||||
if e1 != 0 {
|
|
||||||
err = e1
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
// mksyscall.pl xattr_darwin.go
|
|
||||||
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
|
||||||
|
|
||||||
package syscallx
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func getxattr(path string, attr string, dest *byte, size int, position uint32, options int) (sz int, err error) {
|
|
||||||
var _p0 *byte
|
|
||||||
_p0, err = syscall.BytePtrFromString(path)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p1 *byte
|
|
||||||
_p1, err = syscall.BytePtrFromString(attr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(unsafe.Pointer(dest)), uintptr(size), uintptr(position), uintptr(options))
|
|
||||||
sz = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = e1
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func listxattr(path string, dest []byte, options int) (sz int, err error) {
|
|
||||||
var _p0 *byte
|
|
||||||
_p0, err = syscall.BytePtrFromString(path)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p1 unsafe.Pointer
|
|
||||||
if len(dest) > 0 {
|
|
||||||
_p1 = unsafe.Pointer(&dest[0])
|
|
||||||
} else {
|
|
||||||
_p1 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
r0, _, e1 := syscall.Syscall6(syscall.SYS_LISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(dest)), uintptr(options), 0, 0)
|
|
||||||
sz = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = e1
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func setxattr(path string, attr string, data []byte, position uint32, flags int) (err error) {
|
|
||||||
var _p0 *byte
|
|
||||||
_p0, err = syscall.BytePtrFromString(path)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p1 *byte
|
|
||||||
_p1, err = syscall.BytePtrFromString(attr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p2 unsafe.Pointer
|
|
||||||
if len(data) > 0 {
|
|
||||||
_p2 = unsafe.Pointer(&data[0])
|
|
||||||
} else {
|
|
||||||
_p2 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
_, _, e1 := syscall.Syscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(data)), uintptr(position), uintptr(flags))
|
|
||||||
if e1 != 0 {
|
|
||||||
err = e1
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func removexattr(path string, attr string, options int) (err error) {
|
|
||||||
var _p0 *byte
|
|
||||||
_p0, err = syscall.BytePtrFromString(path)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p1 *byte
|
|
||||||
_p1, err = syscall.BytePtrFromString(attr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, _, e1 := syscall.Syscall(syscall.SYS_REMOVEXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(options))
|
|
||||||
if e1 != 0 {
|
|
||||||
err = e1
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
6
vendor/src/bazil.org/fuse/unmount.go
vendored
6
vendor/src/bazil.org/fuse/unmount.go
vendored
|
@ -1,6 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
// Unmount tries to unmount the filesystem mounted at dir.
|
|
||||||
func Unmount(dir string) error {
|
|
||||||
return unmount(dir)
|
|
||||||
}
|
|
21
vendor/src/bazil.org/fuse/unmount_linux.go
vendored
21
vendor/src/bazil.org/fuse/unmount_linux.go
vendored
|
@ -1,21 +0,0 @@
|
||||||
package fuse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
func unmount(dir string) error {
|
|
||||||
cmd := exec.Command("fusermount", "-u", dir)
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
if len(output) > 0 {
|
|
||||||
output = bytes.TrimRight(output, "\n")
|
|
||||||
msg := err.Error() + ": " + string(output)
|
|
||||||
err = errors.New(msg)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
17
vendor/src/bazil.org/fuse/unmount_std.go
vendored
17
vendor/src/bazil.org/fuse/unmount_std.go
vendored
|
@ -1,17 +0,0 @@
|
||||||
// +build !linux
|
|
||||||
|
|
||||||
package fuse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
func unmount(dir string) error {
|
|
||||||
err := syscall.Unmount(dir, 0)
|
|
||||||
if err != nil {
|
|
||||||
err = &os.PathError{Op: "unmount", Path: dir, Err: err}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2015 Matthew Silverlock (matt@eatsleeprepeat.net)
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
|
@ -1,155 +0,0 @@
|
||||||
# simple-scrypt
|
|
||||||
[![GoDoc](https://godoc.org/github.com/elithrar/simple-scrypt?status.svg)](https://godoc.org/github.com/elithrar/simple-scrypt) [![Build Status](https://travis-ci.org/elithrar/simple-scrypt.svg?branch=master)](https://travis-ci.org/elithrar/simple-scrypt)
|
|
||||||
|
|
||||||
simple-scrypt provides a convenience wrapper around Go's existing
|
|
||||||
[scrypt](http://golang.org/x/crypto/scrypt) package that makes it easier to
|
|
||||||
securely derive strong keys ("hash user passwords"). This library allows you to:
|
|
||||||
|
|
||||||
* Generate a scrypt derived key with a crytographically secure salt and sane
|
|
||||||
default parameters for N, r and p.
|
|
||||||
* Upgrade the parameters used to generate keys as hardware improves by storing
|
|
||||||
them with the derived key (the scrypt spec. doesn't allow for this by
|
|
||||||
default).
|
|
||||||
* Provide your own parameters (if you wish to).
|
|
||||||
|
|
||||||
The API closely mirrors Go's [bcrypt](https://golang.org/x/crypto/bcrypt)
|
|
||||||
library in an effort to make it easy to migrate—and because it's an easy to grok
|
|
||||||
API.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
With a [working Go toolchain](https://golang.org/doc/code.html):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
go get -u github.com/elithrar/simple-scrypt
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
simple-scrypt doesn't try to re-invent the wheel or do anything "special". It
|
|
||||||
wraps the `scrypt.Key` function as thinly as possible, generates a
|
|
||||||
crytographically secure salt for you using Go's `crypto/rand` package, and
|
|
||||||
returns the derived key with the parameters prepended:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import(
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/elithrar/simple-scrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// e.g. r.PostFormValue("password")
|
|
||||||
passwordFromForm := "prew8fid9hick6c"
|
|
||||||
|
|
||||||
// Generates a derived key of the form "N$r$p$salt$dk" where N, r and p are defined as per
|
|
||||||
// Colin Percival's scrypt paper: http://www.tarsnap.com/scrypt/scrypt.pdf
|
|
||||||
// scrypt.Defaults (N=16384, r=8, p=1) makes it easy to provide these parameters, and
|
|
||||||
// (should you wish) provide your own values via the scrypt.Params type.
|
|
||||||
hash, err := scrypt.GenerateFromPassword([]byte(passwordFromForm), scrypt.DefaultParams)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print the derived key with its parameters prepended.
|
|
||||||
fmt.Printf("%s\n", hash)
|
|
||||||
|
|
||||||
// Uses the parameters from the existing derived key. Return an error if they don't match.
|
|
||||||
err := scrypt.CompareHashAndPassword(hash, []byte(passwordFromForm))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Upgrading Parameters
|
|
||||||
|
|
||||||
Upgrading derived keys from a set of parameters to a "stronger" set of parameters
|
|
||||||
as hardware improves, or as you scale (and move your auth process to separate
|
|
||||||
hardware), can be pretty useful. Here's how to do it with simple-scrypt:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func main() {
|
|
||||||
// SCENE: We've successfully authenticated a user, compared their submitted
|
|
||||||
// (cleartext) password against the derived key stored in our database, and
|
|
||||||
// now want to upgrade the parameters (more rounds, more parallelism) to
|
|
||||||
// reflect some shiny new hardware we just purchased. As the user is logging
|
|
||||||
// in, we can retrieve the parameters used to generate their key, and if
|
|
||||||
// they don't match our "new" parameters, we can re-generate the key while
|
|
||||||
// we still have the cleartext password in memory
|
|
||||||
// (e.g. before the HTTP request ends).
|
|
||||||
current, err := scrypt.Cost(hash)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now to check them against our own Params struct (e.g. using reflect.DeepEquals)
|
|
||||||
// and determine whether we want to generate a new key with our "upgraded" parameters.
|
|
||||||
slower := scrypt.Params{
|
|
||||||
N: 32768,
|
|
||||||
R: 8,
|
|
||||||
P: 2,
|
|
||||||
SaltLen: 16,
|
|
||||||
DKLen: 32,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(current, slower) {
|
|
||||||
// Re-generate the key with the slower parameters
|
|
||||||
// here using scrypt.GenerateFromPassword
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Automatically Determining Parameters
|
|
||||||
|
|
||||||
Thanks to the work by [tgulacsi](https://github.com/tgulacsi), you can have simple-scrypt
|
|
||||||
automatically determine the optimal parameters for you (time vs. memory). You should run this once
|
|
||||||
on program startup, as calibrating parameters can be an expensive operation.
|
|
||||||
|
|
||||||
```go
|
|
||||||
var params scrypt.Params
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var err error
|
|
||||||
// 500ms, 64MB of RAM per hash.
|
|
||||||
params, err = scrypt.Calibrate(500*time.Millisecond, 64, Params{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterUserHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
err := r.ParseForm()
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure you validate: not empty, not too long, etc.
|
|
||||||
email := r.PostFormValue("email")
|
|
||||||
pass := r.PostFormValue("password")
|
|
||||||
|
|
||||||
// Use our calibrated parameters
|
|
||||||
hash, err := scrypt.GenerateFromPassword([]byte(pass), params)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save to DB, etc.
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Be aware that increasing these, whilst making it harder to brute-force the resulting hash, also
|
|
||||||
increases the risk of a denial-of-service attack against your server. A surge in authenticate
|
|
||||||
attempts (even if legitimate!) could consume all available resources.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT Licensed. See LICENSE file for details.
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,295 +0,0 @@
|
||||||
// Package scrypt provides a convenience wrapper around Go's existing scrypt package
|
|
||||||
// that makes it easier to securely derive strong keys from weak
|
|
||||||
// inputs (i.e. user passwords).
|
|
||||||
// The package provides password generation, constant-time comparison and
|
|
||||||
// parameter upgrading for scrypt derived keys.
|
|
||||||
package scrypt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/subtle"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/scrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
const (
|
|
||||||
maxInt = 1<<31 - 1
|
|
||||||
minDKLen = 16 // the minimum derived key length in bytes.
|
|
||||||
minSaltLen = 8 // the minimum allowed salt length in bytes.
|
|
||||||
)
|
|
||||||
|
|
||||||
// Params describes the input parameters to the scrypt
|
|
||||||
// key derivation function as per Colin Percival's scrypt
|
|
||||||
// paper: http://www.tarsnap.com/scrypt/scrypt.pdf
|
|
||||||
type Params struct {
|
|
||||||
N int // CPU/memory cost parameter (logN)
|
|
||||||
R int // block size parameter (octets)
|
|
||||||
P int // parallelisation parameter (positive int)
|
|
||||||
SaltLen int // bytes to use as salt (octets)
|
|
||||||
DKLen int // length of the derived key (octets)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultParams provides sensible default inputs into the scrypt function
|
|
||||||
// for interactive use (i.e. web applications).
|
|
||||||
// These defaults will consume approxmiately 16MB of memory (128 * r * N).
|
|
||||||
// The default key length is 256 bits.
|
|
||||||
var DefaultParams = Params{N: 16384, R: 8, P: 1, SaltLen: 16, DKLen: 32}
|
|
||||||
|
|
||||||
// ErrInvalidHash is returned when failing to parse a provided scrypt
|
|
||||||
// hash and/or parameters.
|
|
||||||
var ErrInvalidHash = errors.New("scrypt: the provided hash is not in the correct format")
|
|
||||||
|
|
||||||
// ErrInvalidParams is returned when the cost parameters (N, r, p), salt length
|
|
||||||
// or derived key length are invalid.
|
|
||||||
var ErrInvalidParams = errors.New("scrypt: the parameters provided are invalid")
|
|
||||||
|
|
||||||
// ErrMismatchedHashAndPassword is returned when a password (hashed) and
|
|
||||||
// given hash do not match.
|
|
||||||
var ErrMismatchedHashAndPassword = errors.New("scrypt: the hashed password does not match the hash of the given password")
|
|
||||||
|
|
||||||
// GenerateRandomBytes returns securely generated random bytes.
|
|
||||||
// It will return an error if the system's secure random
|
|
||||||
// number generator fails to function correctly, in which
|
|
||||||
// case the caller should not continue.
|
|
||||||
func GenerateRandomBytes(n int) ([]byte, error) {
|
|
||||||
b := make([]byte, n)
|
|
||||||
_, err := rand.Read(b)
|
|
||||||
// err == nil only if len(b) == n
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateFromPassword returns the derived key of the password using the
|
|
||||||
// parameters provided. The parameters are prepended to the derived key and
|
|
||||||
// separated by the "$" character (0x24).
|
|
||||||
// If the parameters provided are less than the minimum acceptable values,
|
|
||||||
// an error will be returned.
|
|
||||||
func GenerateFromPassword(password []byte, params Params) ([]byte, error) {
|
|
||||||
salt, err := GenerateRandomBytes(params.SaltLen)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := params.Check(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// scrypt.Key returns the raw scrypt derived key.
|
|
||||||
dk, err := scrypt.Key(password, salt, params.N, params.R, params.P, params.DKLen)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepend the params and the salt to the derived key, each separated
|
|
||||||
// by a "$" character. The salt and the derived key are hex encoded.
|
|
||||||
return []byte(fmt.Sprintf("%d$%d$%d$%x$%x", params.N, params.R, params.P, salt, dk)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompareHashAndPassword compares a derived key with the possible cleartext
|
|
||||||
// equivalent. The parameters used in the provided derived key are used.
|
|
||||||
// The comparison performed by this function is constant-time. It returns nil
|
|
||||||
// on success, and an error if the derived keys do not match.
|
|
||||||
func CompareHashAndPassword(hash []byte, password []byte) error {
|
|
||||||
// Decode existing hash, retrieve params and salt.
|
|
||||||
params, salt, dk, err := decodeHash(hash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// scrypt the cleartext password with the same parameters and salt
|
|
||||||
other, err := scrypt.Key(password, salt, params.N, params.R, params.P, params.DKLen)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constant time comparison
|
|
||||||
if subtle.ConstantTimeCompare(dk, other) == 1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrMismatchedHashAndPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check checks that the parameters are valid for input into the
|
|
||||||
// scrypt key derivation function.
|
|
||||||
func (p *Params) Check() error {
|
|
||||||
// Validate N
|
|
||||||
if p.N > maxInt || p.N <= 1 || p.N%2 != 0 {
|
|
||||||
return ErrInvalidParams
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate r
|
|
||||||
if p.R < 1 || p.R > maxInt {
|
|
||||||
return ErrInvalidParams
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate p
|
|
||||||
if p.P < 1 || p.P > maxInt {
|
|
||||||
return ErrInvalidParams
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that r & p don't exceed 2^30 and that N, r, p values don't
|
|
||||||
// exceed the limits defined by the scrypt algorithm.
|
|
||||||
if uint64(p.R)*uint64(p.P) >= 1<<30 || p.R > maxInt/128/p.P || p.R > maxInt/256 || p.N > maxInt/128/p.R {
|
|
||||||
return ErrInvalidParams
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the salt length
|
|
||||||
if p.SaltLen < minSaltLen || p.SaltLen > maxInt {
|
|
||||||
return ErrInvalidParams
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the derived key length
|
|
||||||
if p.DKLen < minDKLen || p.DKLen > maxInt {
|
|
||||||
return ErrInvalidParams
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeHash extracts the parameters, salt and derived key from the
|
|
||||||
// provided hash. It returns an error if the hash format is invalid and/or
|
|
||||||
// the parameters are invalid.
|
|
||||||
func decodeHash(hash []byte) (Params, []byte, []byte, error) {
|
|
||||||
vals := strings.Split(string(hash), "$")
|
|
||||||
|
|
||||||
// P, N, R, salt, scrypt derived key
|
|
||||||
if len(vals) != 5 {
|
|
||||||
return Params{}, nil, nil, ErrInvalidHash
|
|
||||||
}
|
|
||||||
|
|
||||||
var params Params
|
|
||||||
var err error
|
|
||||||
|
|
||||||
params.N, err = strconv.Atoi(vals[0])
|
|
||||||
if err != nil {
|
|
||||||
return params, nil, nil, ErrInvalidHash
|
|
||||||
}
|
|
||||||
|
|
||||||
params.R, err = strconv.Atoi(vals[1])
|
|
||||||
if err != nil {
|
|
||||||
return params, nil, nil, ErrInvalidHash
|
|
||||||
}
|
|
||||||
|
|
||||||
params.P, err = strconv.Atoi(vals[2])
|
|
||||||
if err != nil {
|
|
||||||
return params, nil, nil, ErrInvalidHash
|
|
||||||
}
|
|
||||||
|
|
||||||
salt, err := hex.DecodeString(vals[3])
|
|
||||||
if err != nil {
|
|
||||||
return params, nil, nil, ErrInvalidHash
|
|
||||||
}
|
|
||||||
params.SaltLen = len(salt)
|
|
||||||
|
|
||||||
dk, err := hex.DecodeString(vals[4])
|
|
||||||
if err != nil {
|
|
||||||
return params, nil, nil, ErrInvalidHash
|
|
||||||
}
|
|
||||||
params.DKLen = len(dk)
|
|
||||||
|
|
||||||
if err := params.Check(); err != nil {
|
|
||||||
return params, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return params, salt, dk, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cost returns the scrypt parameters used to generate the derived key. This
|
|
||||||
// allows a package user to increase the cost (in time & resources) used as
|
|
||||||
// computational performance increases over time.
|
|
||||||
func Cost(hash []byte) (Params, error) {
|
|
||||||
params, _, _, err := decodeHash(hash)
|
|
||||||
|
|
||||||
return params, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calibrate returns the hardest parameters (not weaker than the given params),
|
|
||||||
// allowed by the given limits.
|
|
||||||
// The returned params will not use more memory than the given (MiB);
|
|
||||||
// will not take more time than the given timeout, but more than timeout/2.
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// The default timeout (when the timeout arg is zero) is 200ms.
|
|
||||||
// The default memMiBytes (when memMiBytes is zero) is 16MiB.
|
|
||||||
// The default parameters (when params == Params{}) is DefaultParams.
|
|
||||||
func Calibrate(timeout time.Duration, memMiBytes int, params Params) (Params, error) {
|
|
||||||
p := params
|
|
||||||
if p.N == 0 || p.R == 0 || p.P == 0 || p.SaltLen == 0 || p.DKLen == 0 {
|
|
||||||
p = DefaultParams
|
|
||||||
} else if err := p.Check(); err != nil {
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
if timeout == 0 {
|
|
||||||
timeout = 200 * time.Millisecond
|
|
||||||
}
|
|
||||||
if memMiBytes == 0 {
|
|
||||||
memMiBytes = 16
|
|
||||||
}
|
|
||||||
salt, err := GenerateRandomBytes(p.SaltLen)
|
|
||||||
if err != nil {
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
password := []byte("weakpassword")
|
|
||||||
|
|
||||||
// First, we calculate the minimal required time.
|
|
||||||
start := time.Now()
|
|
||||||
if _, err := scrypt.Key(password, salt, p.N, p.R, p.P, p.DKLen); err != nil {
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
dur := time.Since(start)
|
|
||||||
|
|
||||||
for dur < timeout && p.N < maxInt>>1 {
|
|
||||||
p.N <<= 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Memory usage is at least 128 * r * N, see
|
|
||||||
// http://blog.ircmaxell.com/2014/03/why-i-dont-recommend-scrypt.html
|
|
||||||
// or https://drupal.org/comment/4675994#comment-4675994
|
|
||||||
|
|
||||||
var again bool
|
|
||||||
memBytes := memMiBytes << 20
|
|
||||||
// If we'd use more memory then the allowed, we can tune the memory usage
|
|
||||||
for 128*int64(p.R)*int64(p.N) > int64(memBytes) {
|
|
||||||
if p.R > 1 {
|
|
||||||
// by lowering r
|
|
||||||
p.R--
|
|
||||||
} else if p.N > 16 {
|
|
||||||
again = true
|
|
||||||
p.N >>= 1
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !again {
|
|
||||||
return p, p.Check()
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to compensate the lowering of N, by increasing p.
|
|
||||||
for i := 0; i < 10 && p.P > 0; i++ {
|
|
||||||
start := time.Now()
|
|
||||||
if _, err := scrypt.Key(password, salt, p.N, p.R, p.P, p.DKLen); err != nil {
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
dur := time.Since(start)
|
|
||||||
if dur < timeout/2 {
|
|
||||||
p.P = int(float64(p.P)*float64(timeout/dur) + 1)
|
|
||||||
} else if dur > timeout && p.P > 1 {
|
|
||||||
p.P--
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, p.Check()
|
|
||||||
}
|
|
|
@ -1,156 +0,0 @@
|
||||||
package scrypt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test cases
|
|
||||||
var (
|
|
||||||
testLengths = []int{1, 8, 16, 32, 100, 500, 2500}
|
|
||||||
password = "super-secret-password"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testParams = []struct {
|
|
||||||
pass bool
|
|
||||||
params Params
|
|
||||||
}{
|
|
||||||
{true, Params{16384, 8, 1, 32, 64}},
|
|
||||||
{true, Params{16384, 8, 1, 16, 32}},
|
|
||||||
{true, Params{65536, 8, 1, 16, 64}},
|
|
||||||
{true, Params{1048576, 8, 2, 64, 128}},
|
|
||||||
{false, Params{-1, 8, 1, 16, 32}}, // invalid N
|
|
||||||
{false, Params{0, 8, 1, 16, 32}}, // invalid N
|
|
||||||
{false, Params{1<<31 - 1, 8, 1, 16, 32}}, // invalid N
|
|
||||||
{false, Params{16384, 0, 12, 16, 32}}, // invalid R
|
|
||||||
{false, Params{16384, 8, 0, 16, 32}}, // invalid R > maxInt/128/P
|
|
||||||
{false, Params{16384, 1 << 24, 1, 16, 32}}, // invalid R > maxInt/256
|
|
||||||
{false, Params{1<<31 - 1, 8, 0, 16, 32}}, // invalid p < 0
|
|
||||||
{false, Params{4096, 8, 1, 5, 32}}, // invalid SaltLen
|
|
||||||
{false, Params{4096, 8, 1, 16, 2}}, // invalid DKLen
|
|
||||||
}
|
|
||||||
|
|
||||||
var testHashes = []struct {
|
|
||||||
pass bool
|
|
||||||
hash string
|
|
||||||
}{
|
|
||||||
{false, "1$8$1$9003d0e8e69482843e6bd560c2c9cd94$1976f233124e0ee32bb2678eb1b0ed668eb66cff6fa43279d1e33f6e81af893b"}, // N too small
|
|
||||||
{false, "$9003d0e8e69482843e6bd560c2c9cd94$1976f233124e0ee32bb2678eb1b0ed668eb66cff6fa43279d1e33f6e81af893b"}, // too short
|
|
||||||
{false, "16384#8#1#18fbc325efa37402d27c3c2172900cbf$d4e5e1b9eedc1a6a14aad6624ab57b7b42ae75b9c9845fde32de765835f2aaf9"}, // incorrect separators
|
|
||||||
{false, "16384$nogood$1$18fbc325efa37402d27c3c2172900cbf$d4e5e1b9eedc1a6a14aad6624ab57b7b42ae75b9c9845fde32de765835f2aaf9"}, // invalid R
|
|
||||||
{false, "16384$8$abc1$18fbc325efa37402d27c3c2172900cbf$d4e5e1b9eedc1a6a14aad6624ab57b7b42ae75b9c9845fde32de765835f2aaf9"}, // invalid P
|
|
||||||
{false, "16384$8$1$Tk9QRQ==$d4e5e1b9eedc1a6a14aad6624ab57b7b42ae75b9c9845fde32de765835f2aaf9"}, // invalid salt (not hex)
|
|
||||||
{false, "16384$8$1$18fbc325efa37402d27c3c2172900cbf$42ae====/75b9c9845fde32de765835f2aaf9"}, // invalid dk (not hex)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerateRandomBytes(t *testing.T) {
|
|
||||||
for _, v := range testLengths {
|
|
||||||
_, err := GenerateRandomBytes(v)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to generate random bytes")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerateFromPassword(t *testing.T) {
|
|
||||||
for _, v := range testParams {
|
|
||||||
_, err := GenerateFromPassword([]byte(password), v.params)
|
|
||||||
if err != nil && v.pass == true {
|
|
||||||
t.Fatalf("no error was returned when expected for params: %+v", v.params)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompareHashAndPassword(t *testing.T) {
|
|
||||||
hash, err := GenerateFromPassword([]byte(password), DefaultParams)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := CompareHashAndPassword(hash, []byte(password)); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := CompareHashAndPassword(hash, []byte("invalid-password")); err == nil {
|
|
||||||
t.Fatalf("mismatched passwords did not produce an error")
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidHash := []byte("$166$$11$a2ad56a415af5")
|
|
||||||
if err := CompareHashAndPassword(invalidHash, []byte(password)); err == nil {
|
|
||||||
t.Fatalf("did not identify an invalid hash")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCost(t *testing.T) {
|
|
||||||
hash, err := GenerateFromPassword([]byte(password), DefaultParams)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
params, err := Cost(hash)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(params, DefaultParams) {
|
|
||||||
t.Fatal("cost mismatch: parameters used did not match those retrieved")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecodeHash(t *testing.T) {
|
|
||||||
for _, v := range testHashes {
|
|
||||||
_, err := Cost([]byte(v.hash))
|
|
||||||
if err == nil && v.pass == false {
|
|
||||||
t.Fatal("invalid hash: did not correctly detect invalid password hash")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCalibrate(t *testing.T) {
|
|
||||||
timeout := 500 * time.Millisecond
|
|
||||||
for testNum, tc := range []struct {
|
|
||||||
MemMiB int
|
|
||||||
}{
|
|
||||||
{64},
|
|
||||||
{32},
|
|
||||||
{16},
|
|
||||||
{8},
|
|
||||||
{1},
|
|
||||||
} {
|
|
||||||
var (
|
|
||||||
p Params
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
p, err = Calibrate(timeout, tc.MemMiB, p)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%d. %#v: %v", testNum, p, err)
|
|
||||||
}
|
|
||||||
if (128*p.R*p.N)>>20 > tc.MemMiB {
|
|
||||||
t.Errorf("%d. wanted memory limit %d, got %d.", testNum, tc.MemMiB, (128*p.R*p.N)>>20)
|
|
||||||
}
|
|
||||||
start := time.Now()
|
|
||||||
_, err = GenerateFromPassword([]byte(password), p)
|
|
||||||
dur := time.Since(start)
|
|
||||||
t.Logf("GenerateFromPassword with %#v took %s (%v)", p, dur, err)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%d. GenerateFromPassword with %#v: %v", testNum, p, err)
|
|
||||||
}
|
|
||||||
if dur < timeout/2 {
|
|
||||||
t.Errorf("%d. GenerateFromPassword was too fast (wanted around %s, got %s) with %#v.", testNum, timeout, dur, p)
|
|
||||||
} else if timeout*2 < dur {
|
|
||||||
t.Errorf("%d. GenerateFromPassword took too long (wanted around %s, got %s) with %#v.", testNum, timeout, dur, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleCalibrate() {
|
|
||||||
p, err := Calibrate(1*time.Second, 128, Params{})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
dk, err := GenerateFromPassword([]byte("super-secret-password"), p)
|
|
||||||
fmt.Printf("generated password is %q (%v)", dk, err)
|
|
||||||
}
|
|
191
vendor/src/github.com/go-ini/ini/LICENSE
vendored
191
vendor/src/github.com/go-ini/ini/LICENSE
vendored
|
@ -1,191 +0,0 @@
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction, and
|
|
||||||
distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
|
||||||
owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
|
||||||
that control, are controlled by, or are under common control with that entity.
|
|
||||||
For the purposes of this definition, "control" means (i) the power, direct or
|
|
||||||
indirect, to cause the direction or management of such entity, whether by
|
|
||||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
|
||||||
permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications, including
|
|
||||||
but not limited to software source code, documentation source, and configuration
|
|
||||||
files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical transformation or
|
|
||||||
translation of a Source form, including but not limited to compiled object code,
|
|
||||||
generated documentation, and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
|
||||||
available under the License, as indicated by a copyright notice that is included
|
|
||||||
in or attached to the work (an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
|
||||||
is based on (or derived from) the Work and for which the editorial revisions,
|
|
||||||
annotations, elaborations, or other modifications represent, as a whole, an
|
|
||||||
original work of authorship. For the purposes of this License, Derivative Works
|
|
||||||
shall not include works that remain separable from, or merely link (or bind by
|
|
||||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including the original version
|
|
||||||
of the Work and any modifications or additions to that Work or Derivative Works
|
|
||||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
|
||||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
|
||||||
on behalf of the copyright owner. For the purposes of this definition,
|
|
||||||
"submitted" means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems, and
|
|
||||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
|
||||||
the purpose of discussing and improving the Work, but excluding communication
|
|
||||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
|
||||||
owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
|
||||||
of whom a Contribution has been received by Licensor and subsequently
|
|
||||||
incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License.
|
|
||||||
|
|
||||||
Subject to the terms and conditions of this License, each Contributor hereby
|
|
||||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
|
||||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
|
||||||
Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License.
|
|
||||||
|
|
||||||
Subject to the terms and conditions of this License, each Contributor hereby
|
|
||||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
|
||||||
irrevocable (except as stated in this section) patent license to make, have
|
|
||||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
|
||||||
such license applies only to those patent claims licensable by such Contributor
|
|
||||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
|
||||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
|
||||||
submitted. If You institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
|
||||||
Contribution incorporated within the Work constitutes direct or contributory
|
|
||||||
patent infringement, then any patent licenses granted to You under this License
|
|
||||||
for that Work shall terminate as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution.
|
|
||||||
|
|
||||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
|
||||||
in any medium, with or without modifications, and in Source or Object form,
|
|
||||||
provided that You meet the following conditions:
|
|
||||||
|
|
||||||
You must give any other recipients of the Work or Derivative Works a copy of
|
|
||||||
this License; and
|
|
||||||
You must cause any modified files to carry prominent notices stating that You
|
|
||||||
changed the files; and
|
|
||||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
|
||||||
all copyright, patent, trademark, and attribution notices from the Source form
|
|
||||||
of the Work, excluding those notices that do not pertain to any part of the
|
|
||||||
Derivative Works; and
|
|
||||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
|
||||||
Derivative Works that You distribute must include a readable copy of the
|
|
||||||
attribution notices contained within such NOTICE file, excluding those notices
|
|
||||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
|
||||||
following places: within a NOTICE text file distributed as part of the
|
|
||||||
Derivative Works; within the Source form or documentation, if provided along
|
|
||||||
with the Derivative Works; or, within a display generated by the Derivative
|
|
||||||
Works, if and wherever such third-party notices normally appear. The contents of
|
|
||||||
the NOTICE file are for informational purposes only and do not modify the
|
|
||||||
License. You may add Your own attribution notices within Derivative Works that
|
|
||||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
|
||||||
provided that such additional attribution notices cannot be construed as
|
|
||||||
modifying the License.
|
|
||||||
You may add Your own copyright statement to Your modifications and may provide
|
|
||||||
additional or different license terms and conditions for use, reproduction, or
|
|
||||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
|
||||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
|
||||||
with the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions.
|
|
||||||
|
|
||||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
|
||||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
|
||||||
conditions of this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
|
||||||
any separate license agreement you may have executed with Licensor regarding
|
|
||||||
such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks.
|
|
||||||
|
|
||||||
This License does not grant permission to use the trade names, trademarks,
|
|
||||||
service marks, or product names of the Licensor, except as required for
|
|
||||||
reasonable and customary use in describing the origin of the Work and
|
|
||||||
reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
|
||||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
|
||||||
including, without limitation, any warranties or conditions of TITLE,
|
|
||||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
|
||||||
solely responsible for determining the appropriateness of using or
|
|
||||||
redistributing the Work and assume any risks associated with Your exercise of
|
|
||||||
permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability.
|
|
||||||
|
|
||||||
In no event and under no legal theory, whether in tort (including negligence),
|
|
||||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
|
||||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special, incidental,
|
|
||||||
or consequential damages of any character arising as a result of this License or
|
|
||||||
out of the use or inability to use the Work (including but not limited to
|
|
||||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
|
||||||
any and all other commercial damages or losses), even if such Contributor has
|
|
||||||
been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability.
|
|
||||||
|
|
||||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
|
||||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
|
||||||
other liability obligations and/or rights consistent with this License. However,
|
|
||||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
|
||||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
|
||||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason of your
|
|
||||||
accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following boilerplate
|
|
||||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
|
||||||
identifying information. (Don't include the brackets!) The text should be
|
|
||||||
enclosed in the appropriate comment syntax for the file format. We also
|
|
||||||
recommend that a file or class name and description of purpose be included on
|
|
||||||
the same "printed page" as the copyright notice for easier identification within
|
|
||||||
third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
12
vendor/src/github.com/go-ini/ini/Makefile
vendored
12
vendor/src/github.com/go-ini/ini/Makefile
vendored
|
@ -1,12 +0,0 @@
|
||||||
.PHONY: build test bench vet
|
|
||||||
|
|
||||||
build: vet bench
|
|
||||||
|
|
||||||
test:
|
|
||||||
go test -v -cover -race
|
|
||||||
|
|
||||||
bench:
|
|
||||||
go test -v -cover -race -test.bench=. -test.benchmem
|
|
||||||
|
|
||||||
vet:
|
|
||||||
go vet
|
|
746
vendor/src/github.com/go-ini/ini/README.md
vendored
746
vendor/src/github.com/go-ini/ini/README.md
vendored
|
@ -1,746 +0,0 @@
|
||||||
INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini) [![Sourcegraph](https://sourcegraph.com/github.com/go-ini/ini/-/badge.svg)](https://sourcegraph.com/github.com/go-ini/ini?badge)
|
|
||||||
===
|
|
||||||
|
|
||||||
![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
|
|
||||||
|
|
||||||
Package ini provides INI file read and write functionality in Go.
|
|
||||||
|
|
||||||
[简体中文](README_ZH.md)
|
|
||||||
|
|
||||||
## Feature
|
|
||||||
|
|
||||||
- Load multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites.
|
|
||||||
- Read with recursion values.
|
|
||||||
- Read with parent-child sections.
|
|
||||||
- Read with auto-increment key names.
|
|
||||||
- Read with multiple-line values.
|
|
||||||
- Read with tons of helper methods.
|
|
||||||
- Read and convert values to Go types.
|
|
||||||
- Read and **WRITE** comments of sections and keys.
|
|
||||||
- Manipulate sections, keys and comments with ease.
|
|
||||||
- Keep sections and keys in order as you parse and save.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
To use a tagged revision:
|
|
||||||
|
|
||||||
go get gopkg.in/ini.v1
|
|
||||||
|
|
||||||
To use with latest changes:
|
|
||||||
|
|
||||||
go get github.com/go-ini/ini
|
|
||||||
|
|
||||||
Please add `-u` flag to update in the future.
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
If you want to test on your machine, please apply `-t` flag:
|
|
||||||
|
|
||||||
go get -t gopkg.in/ini.v1
|
|
||||||
|
|
||||||
Please add `-u` flag to update in the future.
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
### Loading from data sources
|
|
||||||
|
|
||||||
A **Data Source** is either raw data in type `[]byte`, a file name with type `string` or `io.ReadCloser`. You can load **as many data sources as you want**. Passing other types will simply return an error.
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
|
|
||||||
```
|
|
||||||
|
|
||||||
Or start with an empty object:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg := ini.Empty()
|
|
||||||
```
|
|
||||||
|
|
||||||
When you cannot decide how many data sources to load at the beginning, you will still be able to **Append()** them later.
|
|
||||||
|
|
||||||
```go
|
|
||||||
err := cfg.Append("other file", []byte("other raw data"))
|
|
||||||
```
|
|
||||||
|
|
||||||
If you have a list of files with possibilities that some of them may not available at the time, and you don't know exactly which ones, you can use `LooseLoad` to ignore nonexistent files without returning error.
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := ini.LooseLoad("filename", "filename_404")
|
|
||||||
```
|
|
||||||
|
|
||||||
The cool thing is, whenever the file is available to load while you're calling `Reload` method, it will be counted as usual.
|
|
||||||
|
|
||||||
#### Ignore cases of key name
|
|
||||||
|
|
||||||
When you do not care about cases of section and key names, you can use `InsensitiveLoad` to force all names to be lowercased while parsing.
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := ini.InsensitiveLoad("filename")
|
|
||||||
//...
|
|
||||||
|
|
||||||
// sec1 and sec2 are the exactly same section object
|
|
||||||
sec1, err := cfg.GetSection("Section")
|
|
||||||
sec2, err := cfg.GetSection("SecTIOn")
|
|
||||||
|
|
||||||
// key1 and key2 are the exactly same key object
|
|
||||||
key1, err := sec1.GetKey("Key")
|
|
||||||
key2, err := sec2.GetKey("KeY")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### MySQL-like boolean key
|
|
||||||
|
|
||||||
MySQL's configuration allows a key without value as follows:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[mysqld]
|
|
||||||
...
|
|
||||||
skip-host-cache
|
|
||||||
skip-name-resolve
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, this is considered as missing value. But if you know you're going to deal with those cases, you can assign advanced load options:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
|
|
||||||
```
|
|
||||||
|
|
||||||
The value of those keys are always `true`, and when you save to a file, it will keep in the same foramt as you read.
|
|
||||||
|
|
||||||
To generate such keys in your program, you could use `NewBooleanKey`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
key, err := sec.NewBooleanKey("skip-host-cache")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Comment
|
|
||||||
|
|
||||||
Take care that following format will be treated as comment:
|
|
||||||
|
|
||||||
1. Line begins with `#` or `;`
|
|
||||||
2. Words after `#` or `;`
|
|
||||||
3. Words after section name (i.e words after `[some section name]`)
|
|
||||||
|
|
||||||
If you want to save a value with `#` or `;`, please quote them with ``` ` ``` or ``` """ ```.
|
|
||||||
|
|
||||||
Alternatively, you can use following `LoadOptions` to completely ignore inline comments:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := LoadSources(LoadOptions{IgnoreInlineComment: true}, "app.ini"))
|
|
||||||
```
|
|
||||||
|
|
||||||
### Working with sections
|
|
||||||
|
|
||||||
To get a section, you would need to:
|
|
||||||
|
|
||||||
```go
|
|
||||||
section, err := cfg.GetSection("section name")
|
|
||||||
```
|
|
||||||
|
|
||||||
For a shortcut for default section, just give an empty string as name:
|
|
||||||
|
|
||||||
```go
|
|
||||||
section, err := cfg.GetSection("")
|
|
||||||
```
|
|
||||||
|
|
||||||
When you're pretty sure the section exists, following code could make your life easier:
|
|
||||||
|
|
||||||
```go
|
|
||||||
section := cfg.Section("section name")
|
|
||||||
```
|
|
||||||
|
|
||||||
What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you.
|
|
||||||
|
|
||||||
To create a new section:
|
|
||||||
|
|
||||||
```go
|
|
||||||
err := cfg.NewSection("new section")
|
|
||||||
```
|
|
||||||
|
|
||||||
To get a list of sections or section names:
|
|
||||||
|
|
||||||
```go
|
|
||||||
sections := cfg.Sections()
|
|
||||||
names := cfg.SectionStrings()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Working with keys
|
|
||||||
|
|
||||||
To get a key under a section:
|
|
||||||
|
|
||||||
```go
|
|
||||||
key, err := cfg.Section("").GetKey("key name")
|
|
||||||
```
|
|
||||||
|
|
||||||
Same rule applies to key operations:
|
|
||||||
|
|
||||||
```go
|
|
||||||
key := cfg.Section("").Key("key name")
|
|
||||||
```
|
|
||||||
|
|
||||||
To check if a key exists:
|
|
||||||
|
|
||||||
```go
|
|
||||||
yes := cfg.Section("").HasKey("key name")
|
|
||||||
```
|
|
||||||
|
|
||||||
To create a new key:
|
|
||||||
|
|
||||||
```go
|
|
||||||
err := cfg.Section("").NewKey("name", "value")
|
|
||||||
```
|
|
||||||
|
|
||||||
To get a list of keys or key names:
|
|
||||||
|
|
||||||
```go
|
|
||||||
keys := cfg.Section("").Keys()
|
|
||||||
names := cfg.Section("").KeyStrings()
|
|
||||||
```
|
|
||||||
|
|
||||||
To get a clone hash of keys and corresponding values:
|
|
||||||
|
|
||||||
```go
|
|
||||||
hash := cfg.Section("").KeysHash()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Working with values
|
|
||||||
|
|
||||||
To get a string value:
|
|
||||||
|
|
||||||
```go
|
|
||||||
val := cfg.Section("").Key("key name").String()
|
|
||||||
```
|
|
||||||
|
|
||||||
To validate key value on the fly:
|
|
||||||
|
|
||||||
```go
|
|
||||||
val := cfg.Section("").Key("key name").Validate(func(in string) string {
|
|
||||||
if len(in) == 0 {
|
|
||||||
return "default"
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
If you do not want any auto-transformation (such as recursive read) for the values, you can get raw value directly (this way you get much better performance):
|
|
||||||
|
|
||||||
```go
|
|
||||||
val := cfg.Section("").Key("key name").Value()
|
|
||||||
```
|
|
||||||
|
|
||||||
To check if raw value exists:
|
|
||||||
|
|
||||||
```go
|
|
||||||
yes := cfg.Section("").HasValue("test value")
|
|
||||||
```
|
|
||||||
|
|
||||||
To get value with types:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// For boolean values:
|
|
||||||
// true when value is: 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
|
|
||||||
// false when value is: 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
|
|
||||||
v, err = cfg.Section("").Key("BOOL").Bool()
|
|
||||||
v, err = cfg.Section("").Key("FLOAT64").Float64()
|
|
||||||
v, err = cfg.Section("").Key("INT").Int()
|
|
||||||
v, err = cfg.Section("").Key("INT64").Int64()
|
|
||||||
v, err = cfg.Section("").Key("UINT").Uint()
|
|
||||||
v, err = cfg.Section("").Key("UINT64").Uint64()
|
|
||||||
v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
|
|
||||||
v, err = cfg.Section("").Key("TIME").Time() // RFC3339
|
|
||||||
|
|
||||||
v = cfg.Section("").Key("BOOL").MustBool()
|
|
||||||
v = cfg.Section("").Key("FLOAT64").MustFloat64()
|
|
||||||
v = cfg.Section("").Key("INT").MustInt()
|
|
||||||
v = cfg.Section("").Key("INT64").MustInt64()
|
|
||||||
v = cfg.Section("").Key("UINT").MustUint()
|
|
||||||
v = cfg.Section("").Key("UINT64").MustUint64()
|
|
||||||
v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
|
|
||||||
v = cfg.Section("").Key("TIME").MustTime() // RFC3339
|
|
||||||
|
|
||||||
// Methods start with Must also accept one argument for default value
|
|
||||||
// when key not found or fail to parse value to given type.
|
|
||||||
// Except method MustString, which you have to pass a default value.
|
|
||||||
|
|
||||||
v = cfg.Section("").Key("String").MustString("default")
|
|
||||||
v = cfg.Section("").Key("BOOL").MustBool(true)
|
|
||||||
v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
|
|
||||||
v = cfg.Section("").Key("INT").MustInt(10)
|
|
||||||
v = cfg.Section("").Key("INT64").MustInt64(99)
|
|
||||||
v = cfg.Section("").Key("UINT").MustUint(3)
|
|
||||||
v = cfg.Section("").Key("UINT64").MustUint64(6)
|
|
||||||
v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
|
|
||||||
v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
|
|
||||||
```
|
|
||||||
|
|
||||||
What if my value is three-line long?
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[advance]
|
|
||||||
ADDRESS = """404 road,
|
|
||||||
NotFound, State, 5000
|
|
||||||
Earth"""
|
|
||||||
```
|
|
||||||
|
|
||||||
Not a problem!
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("advance").Key("ADDRESS").String()
|
|
||||||
|
|
||||||
/* --- start ---
|
|
||||||
404 road,
|
|
||||||
NotFound, State, 5000
|
|
||||||
Earth
|
|
||||||
------ end --- */
|
|
||||||
```
|
|
||||||
|
|
||||||
That's cool, how about continuation lines?
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[advance]
|
|
||||||
two_lines = how about \
|
|
||||||
continuation lines?
|
|
||||||
lots_of_lines = 1 \
|
|
||||||
2 \
|
|
||||||
3 \
|
|
||||||
4
|
|
||||||
```
|
|
||||||
|
|
||||||
Piece of cake!
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
|
|
||||||
cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
|
|
||||||
```
|
|
||||||
|
|
||||||
Well, I hate continuation lines, how do I disable that?
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := ini.LoadSources(ini.LoadOptions{
|
|
||||||
IgnoreContinuation: true,
|
|
||||||
}, "filename")
|
|
||||||
```
|
|
||||||
|
|
||||||
Holy crap!
|
|
||||||
|
|
||||||
Note that single quotes around values will be stripped:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
foo = "some value" // foo: some value
|
|
||||||
bar = 'some value' // bar: some value
|
|
||||||
```
|
|
||||||
|
|
||||||
That's all? Hmm, no.
|
|
||||||
|
|
||||||
#### Helper methods of working with values
|
|
||||||
|
|
||||||
To get value with given candidates:
|
|
||||||
|
|
||||||
```go
|
|
||||||
v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
|
|
||||||
v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
|
|
||||||
v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
|
|
||||||
v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
|
|
||||||
v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
|
|
||||||
v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
|
|
||||||
v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
|
|
||||||
v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
|
|
||||||
```
|
|
||||||
|
|
||||||
Default value will be presented if value of key is not in candidates you given, and default value does not need be one of candidates.
|
|
||||||
|
|
||||||
To validate value in a given range:
|
|
||||||
|
|
||||||
```go
|
|
||||||
vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
|
|
||||||
vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
|
|
||||||
vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
|
|
||||||
vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
|
|
||||||
vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
|
|
||||||
vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
|
|
||||||
vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Auto-split values into a slice
|
|
||||||
|
|
||||||
To use zero value of type for invalid inputs:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
|
|
||||||
// Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
|
|
||||||
vals = cfg.Section("").Key("STRINGS").Strings(",")
|
|
||||||
vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
|
|
||||||
vals = cfg.Section("").Key("INTS").Ints(",")
|
|
||||||
vals = cfg.Section("").Key("INT64S").Int64s(",")
|
|
||||||
vals = cfg.Section("").Key("UINTS").Uints(",")
|
|
||||||
vals = cfg.Section("").Key("UINT64S").Uint64s(",")
|
|
||||||
vals = cfg.Section("").Key("TIMES").Times(",")
|
|
||||||
```
|
|
||||||
|
|
||||||
To exclude invalid values out of result slice:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
|
|
||||||
// Input: how, 2.2, are, you -> [2.2]
|
|
||||||
vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
|
|
||||||
vals = cfg.Section("").Key("INTS").ValidInts(",")
|
|
||||||
vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
|
|
||||||
vals = cfg.Section("").Key("UINTS").ValidUints(",")
|
|
||||||
vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
|
|
||||||
vals = cfg.Section("").Key("TIMES").ValidTimes(",")
|
|
||||||
```
|
|
||||||
|
|
||||||
Or to return nothing but error when have invalid inputs:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
|
|
||||||
// Input: how, 2.2, are, you -> error
|
|
||||||
vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
|
|
||||||
vals = cfg.Section("").Key("INTS").StrictInts(",")
|
|
||||||
vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
|
|
||||||
vals = cfg.Section("").Key("UINTS").StrictUints(",")
|
|
||||||
vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
|
|
||||||
vals = cfg.Section("").Key("TIMES").StrictTimes(",")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Save your configuration
|
|
||||||
|
|
||||||
Finally, it's time to save your configuration to somewhere.
|
|
||||||
|
|
||||||
A typical way to save configuration is writing it to a file:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// ...
|
|
||||||
err = cfg.SaveTo("my.ini")
|
|
||||||
err = cfg.SaveToIndent("my.ini", "\t")
|
|
||||||
```
|
|
||||||
|
|
||||||
Another way to save is writing to a `io.Writer` interface:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// ...
|
|
||||||
cfg.WriteTo(writer)
|
|
||||||
cfg.WriteToIndent(writer, "\t")
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, spaces are used to align "=" sign between key and values, to disable that:
|
|
||||||
|
|
||||||
```go
|
|
||||||
ini.PrettyFormat = false
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Usage
|
|
||||||
|
|
||||||
### Recursive Values
|
|
||||||
|
|
||||||
For all value of keys, there is a special syntax `%(<name>)s`, where `<name>` is the key name in same section or default section, and `%(<name>)s` will be replaced by corresponding value(empty string if key not found). You can use this syntax at most 99 level of recursions.
|
|
||||||
|
|
||||||
```ini
|
|
||||||
NAME = ini
|
|
||||||
|
|
||||||
[author]
|
|
||||||
NAME = Unknwon
|
|
||||||
GITHUB = https://github.com/%(NAME)s
|
|
||||||
|
|
||||||
[package]
|
|
||||||
FULL_NAME = github.com/go-ini/%(NAME)s
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon
|
|
||||||
cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
|
|
||||||
```
|
|
||||||
|
|
||||||
### Parent-child Sections
|
|
||||||
|
|
||||||
You can use `.` in section name to indicate parent-child relationship between two or more sections. If the key not found in the child section, library will try again on its parent section until there is no parent section.
|
|
||||||
|
|
||||||
```ini
|
|
||||||
NAME = ini
|
|
||||||
VERSION = v1
|
|
||||||
IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
|
|
||||||
|
|
||||||
[package]
|
|
||||||
CLONE_URL = https://%(IMPORT_PATH)s
|
|
||||||
|
|
||||||
[package.sub]
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Retrieve parent keys available to a child section
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Unparseable Sections
|
|
||||||
|
|
||||||
Sometimes, you have sections that do not contain key-value pairs but raw content, to handle such case, you can use `LoadOptions.UnparsableSections`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
|
|
||||||
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
|
|
||||||
|
|
||||||
body := cfg.Section("COMMENTS").Body()
|
|
||||||
|
|
||||||
/* --- start ---
|
|
||||||
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
|
|
||||||
------ end --- */
|
|
||||||
```
|
|
||||||
|
|
||||||
### Auto-increment Key Names
|
|
||||||
|
|
||||||
If key name is `-` in data source, then it would be seen as special syntax for auto-increment key name start from 1, and every section is independent on counter.
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[features]
|
|
||||||
-: Support read/write comments of keys and sections
|
|
||||||
-: Support auto-increment of key names
|
|
||||||
-: Support load multiple files to overwrite key values
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Map To Struct
|
|
||||||
|
|
||||||
Want more objective way to play with INI? Cool.
|
|
||||||
|
|
||||||
```ini
|
|
||||||
Name = Unknwon
|
|
||||||
age = 21
|
|
||||||
Male = true
|
|
||||||
Born = 1993-01-01T20:17:05Z
|
|
||||||
|
|
||||||
[Note]
|
|
||||||
Content = Hi is a good man!
|
|
||||||
Cities = HangZhou, Boston
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Note struct {
|
|
||||||
Content string
|
|
||||||
Cities []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Person struct {
|
|
||||||
Name string
|
|
||||||
Age int `ini:"age"`
|
|
||||||
Male bool
|
|
||||||
Born time.Time
|
|
||||||
Note
|
|
||||||
Created time.Time `ini:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cfg, err := ini.Load("path/to/ini")
|
|
||||||
// ...
|
|
||||||
p := new(Person)
|
|
||||||
err = cfg.MapTo(p)
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// Things can be simpler.
|
|
||||||
err = ini.MapTo(p, "path/to/ini")
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// Just map a section? Fine.
|
|
||||||
n := new(Note)
|
|
||||||
err = cfg.Section("Note").MapTo(n)
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Can I have default value for field? Absolutely.
|
|
||||||
|
|
||||||
Assign it before you map to struct. It will keep the value as it is if the key is not presented or got wrong type.
|
|
||||||
|
|
||||||
```go
|
|
||||||
// ...
|
|
||||||
p := &Person{
|
|
||||||
Name: "Joe",
|
|
||||||
}
|
|
||||||
// ...
|
|
||||||
```
|
|
||||||
|
|
||||||
It's really cool, but what's the point if you can't give me my file back from struct?
|
|
||||||
|
|
||||||
### Reflect From Struct
|
|
||||||
|
|
||||||
Why not?
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Embeded struct {
|
|
||||||
Dates []time.Time `delim:"|"`
|
|
||||||
Places []string `ini:"places,omitempty"`
|
|
||||||
None []int `ini:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Author struct {
|
|
||||||
Name string `ini:"NAME"`
|
|
||||||
Male bool
|
|
||||||
Age int
|
|
||||||
GPA float64
|
|
||||||
NeverMind string `ini:"-"`
|
|
||||||
*Embeded
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
a := &Author{"Unknwon", true, 21, 2.8, "",
|
|
||||||
&Embeded{
|
|
||||||
[]time.Time{time.Now(), time.Now()},
|
|
||||||
[]string{"HangZhou", "Boston"},
|
|
||||||
[]int{},
|
|
||||||
}}
|
|
||||||
cfg := ini.Empty()
|
|
||||||
err = ini.ReflectFrom(cfg, a)
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
So, what do I get?
|
|
||||||
|
|
||||||
```ini
|
|
||||||
NAME = Unknwon
|
|
||||||
Male = true
|
|
||||||
Age = 21
|
|
||||||
GPA = 2.8
|
|
||||||
|
|
||||||
[Embeded]
|
|
||||||
Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
|
|
||||||
places = HangZhou,Boston
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Name Mapper
|
|
||||||
|
|
||||||
To save your time and make your code cleaner, this library supports [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) between struct field and actual section and key name.
|
|
||||||
|
|
||||||
There are 2 built-in name mappers:
|
|
||||||
|
|
||||||
- `AllCapsUnderscore`: it converts to format `ALL_CAPS_UNDERSCORE` then match section or key.
|
|
||||||
- `TitleUnderscore`: it converts to format `title_underscore` then match section or key.
|
|
||||||
|
|
||||||
To use them:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Info struct {
|
|
||||||
PackageName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
|
|
||||||
// ...
|
|
||||||
|
|
||||||
cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
|
|
||||||
// ...
|
|
||||||
info := new(Info)
|
|
||||||
cfg.NameMapper = ini.AllCapsUnderscore
|
|
||||||
err = cfg.MapTo(info)
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Same rules of name mapper apply to `ini.ReflectFromWithMapper` function.
|
|
||||||
|
|
||||||
#### Value Mapper
|
|
||||||
|
|
||||||
To expand values (e.g. from environment variables), you can use the `ValueMapper` to transform values:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Env struct {
|
|
||||||
Foo string `ini:"foo"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
|
|
||||||
cfg.ValueMapper = os.ExpandEnv
|
|
||||||
// ...
|
|
||||||
env := &Env{}
|
|
||||||
err = cfg.Section("env").MapTo(env)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This would set the value of `env.Foo` to the value of the environment variable `MY_VAR`.
|
|
||||||
|
|
||||||
#### Other Notes On Map/Reflect
|
|
||||||
|
|
||||||
Any embedded struct is treated as a section by default, and there is no automatic parent-child relations in map/reflect feature:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Child struct {
|
|
||||||
Age string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Parent struct {
|
|
||||||
Name string
|
|
||||||
Child
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
City string
|
|
||||||
Parent
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Example configuration:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
City = Boston
|
|
||||||
|
|
||||||
[Parent]
|
|
||||||
Name = Unknwon
|
|
||||||
|
|
||||||
[Child]
|
|
||||||
Age = 21
|
|
||||||
```
|
|
||||||
|
|
||||||
What if, yes, I'm paranoid, I want embedded struct to be in the same section. Well, all roads lead to Rome.
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Child struct {
|
|
||||||
Age string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Parent struct {
|
|
||||||
Name string
|
|
||||||
Child `ini:"Parent"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
City string
|
|
||||||
Parent
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Example configuration:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
City = Boston
|
|
||||||
|
|
||||||
[Parent]
|
|
||||||
Name = Unknwon
|
|
||||||
Age = 21
|
|
||||||
```
|
|
||||||
|
|
||||||
## Getting Help
|
|
||||||
|
|
||||||
- [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
|
|
||||||
- [File An Issue](https://github.com/go-ini/ini/issues/new)
|
|
||||||
|
|
||||||
## FAQs
|
|
||||||
|
|
||||||
### What does `BlockMode` field do?
|
|
||||||
|
|
||||||
By default, library lets you read and write values so we need a locker to make sure your data is safe. But in cases that you are very sure about only reading data through the library, you can set `cfg.BlockMode = false` to speed up read operations about **50-70%** faster.
|
|
||||||
|
|
||||||
### Why another INI library?
|
|
||||||
|
|
||||||
Many people are using my another INI library [goconfig](https://github.com/Unknwon/goconfig), so the reason for this one is I would like to make more Go style code. Also when you set `cfg.BlockMode = false`, this one is about **10-30%** faster.
|
|
||||||
|
|
||||||
To make those changes I have to confirm API broken, so it's safer to keep it in another place and start using `gopkg.in` to version my package at this time.(PS: shorter import path)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
|
|
733
vendor/src/github.com/go-ini/ini/README_ZH.md
vendored
733
vendor/src/github.com/go-ini/ini/README_ZH.md
vendored
|
@ -1,733 +0,0 @@
|
||||||
本包提供了 Go 语言中读写 INI 文件的功能。
|
|
||||||
|
|
||||||
## 功能特性
|
|
||||||
|
|
||||||
- 支持覆盖加载多个数据源(`[]byte`、文件和 `io.ReadCloser`)
|
|
||||||
- 支持递归读取键值
|
|
||||||
- 支持读取父子分区
|
|
||||||
- 支持读取自增键名
|
|
||||||
- 支持读取多行的键值
|
|
||||||
- 支持大量辅助方法
|
|
||||||
- 支持在读取时直接转换为 Go 语言类型
|
|
||||||
- 支持读取和 **写入** 分区和键的注释
|
|
||||||
- 轻松操作分区、键值和注释
|
|
||||||
- 在保存文件时分区和键值会保持原有的顺序
|
|
||||||
|
|
||||||
## 下载安装
|
|
||||||
|
|
||||||
使用一个特定版本:
|
|
||||||
|
|
||||||
go get gopkg.in/ini.v1
|
|
||||||
|
|
||||||
使用最新版:
|
|
||||||
|
|
||||||
go get github.com/go-ini/ini
|
|
||||||
|
|
||||||
如需更新请添加 `-u` 选项。
|
|
||||||
|
|
||||||
### 测试安装
|
|
||||||
|
|
||||||
如果您想要在自己的机器上运行测试,请使用 `-t` 标记:
|
|
||||||
|
|
||||||
go get -t gopkg.in/ini.v1
|
|
||||||
|
|
||||||
如需更新请添加 `-u` 选项。
|
|
||||||
|
|
||||||
## 开始使用
|
|
||||||
|
|
||||||
### 从数据源加载
|
|
||||||
|
|
||||||
一个 **数据源** 可以是 `[]byte` 类型的原始数据,`string` 类型的文件路径或 `io.ReadCloser`。您可以加载 **任意多个** 数据源。如果您传递其它类型的数据源,则会直接返回错误。
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
|
|
||||||
```
|
|
||||||
|
|
||||||
或者从一个空白的文件开始:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg := ini.Empty()
|
|
||||||
```
|
|
||||||
|
|
||||||
当您在一开始无法决定需要加载哪些数据源时,仍可以使用 **Append()** 在需要的时候加载它们。
|
|
||||||
|
|
||||||
```go
|
|
||||||
err := cfg.Append("other file", []byte("other raw data"))
|
|
||||||
```
|
|
||||||
|
|
||||||
当您想要加载一系列文件,但是不能够确定其中哪些文件是不存在的,可以通过调用函数 `LooseLoad` 来忽略它们(`Load` 会因为文件不存在而返回错误):
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := ini.LooseLoad("filename", "filename_404")
|
|
||||||
```
|
|
||||||
|
|
||||||
更牛逼的是,当那些之前不存在的文件在重新调用 `Reload` 方法的时候突然出现了,那么它们会被正常加载。
|
|
||||||
|
|
||||||
#### 忽略键名的大小写
|
|
||||||
|
|
||||||
有时候分区和键的名称大小写混合非常烦人,这个时候就可以通过 `InsensitiveLoad` 将所有分区和键名在读取里强制转换为小写:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := ini.InsensitiveLoad("filename")
|
|
||||||
//...
|
|
||||||
|
|
||||||
// sec1 和 sec2 指向同一个分区对象
|
|
||||||
sec1, err := cfg.GetSection("Section")
|
|
||||||
sec2, err := cfg.GetSection("SecTIOn")
|
|
||||||
|
|
||||||
// key1 和 key2 指向同一个键对象
|
|
||||||
key1, err := sec1.GetKey("Key")
|
|
||||||
key2, err := sec2.GetKey("KeY")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 类似 MySQL 配置中的布尔值键
|
|
||||||
|
|
||||||
MySQL 的配置文件中会出现没有具体值的布尔类型的键:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[mysqld]
|
|
||||||
...
|
|
||||||
skip-host-cache
|
|
||||||
skip-name-resolve
|
|
||||||
```
|
|
||||||
|
|
||||||
默认情况下这被认为是缺失值而无法完成解析,但可以通过高级的加载选项对它们进行处理:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
|
|
||||||
```
|
|
||||||
|
|
||||||
这些键的值永远为 `true`,且在保存到文件时也只会输出键名。
|
|
||||||
|
|
||||||
如果您想要通过程序来生成此类键,则可以使用 `NewBooleanKey`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
key, err := sec.NewBooleanKey("skip-host-cache")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 关于注释
|
|
||||||
|
|
||||||
下述几种情况的内容将被视为注释:
|
|
||||||
|
|
||||||
1. 所有以 `#` 或 `;` 开头的行
|
|
||||||
2. 所有在 `#` 或 `;` 之后的内容
|
|
||||||
3. 分区标签后的文字 (即 `[分区名]` 之后的内容)
|
|
||||||
|
|
||||||
如果你希望使用包含 `#` 或 `;` 的值,请使用 ``` ` ``` 或 ``` """ ``` 进行包覆。
|
|
||||||
|
|
||||||
除此之外,您还可以通过 `LoadOptions` 完全忽略行内注释:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := LoadSources(LoadOptions{IgnoreInlineComment: true}, "app.ini"))
|
|
||||||
```
|
|
||||||
|
|
||||||
### 操作分区(Section)
|
|
||||||
|
|
||||||
获取指定分区:
|
|
||||||
|
|
||||||
```go
|
|
||||||
section, err := cfg.GetSection("section name")
|
|
||||||
```
|
|
||||||
|
|
||||||
如果您想要获取默认分区,则可以用空字符串代替分区名:
|
|
||||||
|
|
||||||
```go
|
|
||||||
section, err := cfg.GetSection("")
|
|
||||||
```
|
|
||||||
|
|
||||||
当您非常确定某个分区是存在的,可以使用以下简便方法:
|
|
||||||
|
|
||||||
```go
|
|
||||||
section := cfg.Section("section name")
|
|
||||||
```
|
|
||||||
|
|
||||||
如果不小心判断错了,要获取的分区其实是不存在的,那会发生什么呢?没事的,它会自动创建并返回一个对应的分区对象给您。
|
|
||||||
|
|
||||||
创建一个分区:
|
|
||||||
|
|
||||||
```go
|
|
||||||
err := cfg.NewSection("new section")
|
|
||||||
```
|
|
||||||
|
|
||||||
获取所有分区对象或名称:
|
|
||||||
|
|
||||||
```go
|
|
||||||
sections := cfg.Sections()
|
|
||||||
names := cfg.SectionStrings()
|
|
||||||
```
|
|
||||||
|
|
||||||
### 操作键(Key)
|
|
||||||
|
|
||||||
获取某个分区下的键:
|
|
||||||
|
|
||||||
```go
|
|
||||||
key, err := cfg.Section("").GetKey("key name")
|
|
||||||
```
|
|
||||||
|
|
||||||
和分区一样,您也可以直接获取键而忽略错误处理:
|
|
||||||
|
|
||||||
```go
|
|
||||||
key := cfg.Section("").Key("key name")
|
|
||||||
```
|
|
||||||
|
|
||||||
判断某个键是否存在:
|
|
||||||
|
|
||||||
```go
|
|
||||||
yes := cfg.Section("").HasKey("key name")
|
|
||||||
```
|
|
||||||
|
|
||||||
创建一个新的键:
|
|
||||||
|
|
||||||
```go
|
|
||||||
err := cfg.Section("").NewKey("name", "value")
|
|
||||||
```
|
|
||||||
|
|
||||||
获取分区下的所有键或键名:
|
|
||||||
|
|
||||||
```go
|
|
||||||
keys := cfg.Section("").Keys()
|
|
||||||
names := cfg.Section("").KeyStrings()
|
|
||||||
```
|
|
||||||
|
|
||||||
获取分区下的所有键值对的克隆:
|
|
||||||
|
|
||||||
```go
|
|
||||||
hash := cfg.Section("").KeysHash()
|
|
||||||
```
|
|
||||||
|
|
||||||
### 操作键值(Value)
|
|
||||||
|
|
||||||
获取一个类型为字符串(string)的值:
|
|
||||||
|
|
||||||
```go
|
|
||||||
val := cfg.Section("").Key("key name").String()
|
|
||||||
```
|
|
||||||
|
|
||||||
获取值的同时通过自定义函数进行处理验证:
|
|
||||||
|
|
||||||
```go
|
|
||||||
val := cfg.Section("").Key("key name").Validate(func(in string) string {
|
|
||||||
if len(in) == 0 {
|
|
||||||
return "default"
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
如果您不需要任何对值的自动转变功能(例如递归读取),可以直接获取原值(这种方式性能最佳):
|
|
||||||
|
|
||||||
```go
|
|
||||||
val := cfg.Section("").Key("key name").Value()
|
|
||||||
```
|
|
||||||
|
|
||||||
判断某个原值是否存在:
|
|
||||||
|
|
||||||
```go
|
|
||||||
yes := cfg.Section("").HasValue("test value")
|
|
||||||
```
|
|
||||||
|
|
||||||
获取其它类型的值:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 布尔值的规则:
|
|
||||||
// true 当值为:1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
|
|
||||||
// false 当值为:0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
|
|
||||||
v, err = cfg.Section("").Key("BOOL").Bool()
|
|
||||||
v, err = cfg.Section("").Key("FLOAT64").Float64()
|
|
||||||
v, err = cfg.Section("").Key("INT").Int()
|
|
||||||
v, err = cfg.Section("").Key("INT64").Int64()
|
|
||||||
v, err = cfg.Section("").Key("UINT").Uint()
|
|
||||||
v, err = cfg.Section("").Key("UINT64").Uint64()
|
|
||||||
v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
|
|
||||||
v, err = cfg.Section("").Key("TIME").Time() // RFC3339
|
|
||||||
|
|
||||||
v = cfg.Section("").Key("BOOL").MustBool()
|
|
||||||
v = cfg.Section("").Key("FLOAT64").MustFloat64()
|
|
||||||
v = cfg.Section("").Key("INT").MustInt()
|
|
||||||
v = cfg.Section("").Key("INT64").MustInt64()
|
|
||||||
v = cfg.Section("").Key("UINT").MustUint()
|
|
||||||
v = cfg.Section("").Key("UINT64").MustUint64()
|
|
||||||
v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
|
|
||||||
v = cfg.Section("").Key("TIME").MustTime() // RFC3339
|
|
||||||
|
|
||||||
// 由 Must 开头的方法名允许接收一个相同类型的参数来作为默认值,
|
|
||||||
// 当键不存在或者转换失败时,则会直接返回该默认值。
|
|
||||||
// 但是,MustString 方法必须传递一个默认值。
|
|
||||||
|
|
||||||
v = cfg.Seciont("").Key("String").MustString("default")
|
|
||||||
v = cfg.Section("").Key("BOOL").MustBool(true)
|
|
||||||
v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
|
|
||||||
v = cfg.Section("").Key("INT").MustInt(10)
|
|
||||||
v = cfg.Section("").Key("INT64").MustInt64(99)
|
|
||||||
v = cfg.Section("").Key("UINT").MustUint(3)
|
|
||||||
v = cfg.Section("").Key("UINT64").MustUint64(6)
|
|
||||||
v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
|
|
||||||
v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
|
|
||||||
```
|
|
||||||
|
|
||||||
如果我的值有好多行怎么办?
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[advance]
|
|
||||||
ADDRESS = """404 road,
|
|
||||||
NotFound, State, 5000
|
|
||||||
Earth"""
|
|
||||||
```
|
|
||||||
|
|
||||||
嗯哼?小 case!
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("advance").Key("ADDRESS").String()
|
|
||||||
|
|
||||||
/* --- start ---
|
|
||||||
404 road,
|
|
||||||
NotFound, State, 5000
|
|
||||||
Earth
|
|
||||||
------ end --- */
|
|
||||||
```
|
|
||||||
|
|
||||||
赞爆了!那要是我属于一行的内容写不下想要写到第二行怎么办?
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[advance]
|
|
||||||
two_lines = how about \
|
|
||||||
continuation lines?
|
|
||||||
lots_of_lines = 1 \
|
|
||||||
2 \
|
|
||||||
3 \
|
|
||||||
4
|
|
||||||
```
|
|
||||||
|
|
||||||
简直是小菜一碟!
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
|
|
||||||
cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
|
|
||||||
```
|
|
||||||
|
|
||||||
可是我有时候觉得两行连在一起特别没劲,怎么才能不自动连接两行呢?
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := ini.LoadSources(ini.LoadOptions{
|
|
||||||
IgnoreContinuation: true,
|
|
||||||
}, "filename")
|
|
||||||
```
|
|
||||||
|
|
||||||
哇靠给力啊!
|
|
||||||
|
|
||||||
需要注意的是,值两侧的单引号会被自动剔除:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
foo = "some value" // foo: some value
|
|
||||||
bar = 'some value' // bar: some value
|
|
||||||
```
|
|
||||||
|
|
||||||
这就是全部了?哈哈,当然不是。
|
|
||||||
|
|
||||||
#### 操作键值的辅助方法
|
|
||||||
|
|
||||||
获取键值时设定候选值:
|
|
||||||
|
|
||||||
```go
|
|
||||||
v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
|
|
||||||
v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
|
|
||||||
v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
|
|
||||||
v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
|
|
||||||
v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
|
|
||||||
v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
|
|
||||||
v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
|
|
||||||
v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
|
|
||||||
```
|
|
||||||
|
|
||||||
如果获取到的值不是候选值的任意一个,则会返回默认值,而默认值不需要是候选值中的一员。
|
|
||||||
|
|
||||||
验证获取的值是否在指定范围内:
|
|
||||||
|
|
||||||
```go
|
|
||||||
vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
|
|
||||||
vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
|
|
||||||
vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
|
|
||||||
vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
|
|
||||||
vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
|
|
||||||
vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
|
|
||||||
vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
|
|
||||||
```
|
|
||||||
|
|
||||||
##### 自动分割键值到切片(slice)
|
|
||||||
|
|
||||||
当存在无效输入时,使用零值代替:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
|
|
||||||
// Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
|
|
||||||
vals = cfg.Section("").Key("STRINGS").Strings(",")
|
|
||||||
vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
|
|
||||||
vals = cfg.Section("").Key("INTS").Ints(",")
|
|
||||||
vals = cfg.Section("").Key("INT64S").Int64s(",")
|
|
||||||
vals = cfg.Section("").Key("UINTS").Uints(",")
|
|
||||||
vals = cfg.Section("").Key("UINT64S").Uint64s(",")
|
|
||||||
vals = cfg.Section("").Key("TIMES").Times(",")
|
|
||||||
```
|
|
||||||
|
|
||||||
从结果切片中剔除无效输入:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
|
|
||||||
// Input: how, 2.2, are, you -> [2.2]
|
|
||||||
vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
|
|
||||||
vals = cfg.Section("").Key("INTS").ValidInts(",")
|
|
||||||
vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
|
|
||||||
vals = cfg.Section("").Key("UINTS").ValidUints(",")
|
|
||||||
vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
|
|
||||||
vals = cfg.Section("").Key("TIMES").ValidTimes(",")
|
|
||||||
```
|
|
||||||
|
|
||||||
当存在无效输入时,直接返回错误:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
|
|
||||||
// Input: how, 2.2, are, you -> error
|
|
||||||
vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
|
|
||||||
vals = cfg.Section("").Key("INTS").StrictInts(",")
|
|
||||||
vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
|
|
||||||
vals = cfg.Section("").Key("UINTS").StrictUints(",")
|
|
||||||
vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
|
|
||||||
vals = cfg.Section("").Key("TIMES").StrictTimes(",")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 保存配置
|
|
||||||
|
|
||||||
终于到了这个时刻,是时候保存一下配置了。
|
|
||||||
|
|
||||||
比较原始的做法是输出配置到某个文件:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// ...
|
|
||||||
err = cfg.SaveTo("my.ini")
|
|
||||||
err = cfg.SaveToIndent("my.ini", "\t")
|
|
||||||
```
|
|
||||||
|
|
||||||
另一个比较高级的做法是写入到任何实现 `io.Writer` 接口的对象中:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// ...
|
|
||||||
cfg.WriteTo(writer)
|
|
||||||
cfg.WriteToIndent(writer, "\t")
|
|
||||||
```
|
|
||||||
|
|
||||||
默认情况下,空格将被用于对齐键值之间的等号以美化输出结果,以下代码可以禁用该功能:
|
|
||||||
|
|
||||||
```go
|
|
||||||
ini.PrettyFormat = false
|
|
||||||
```
|
|
||||||
|
|
||||||
## 高级用法
|
|
||||||
|
|
||||||
### 递归读取键值
|
|
||||||
|
|
||||||
在获取所有键值的过程中,特殊语法 `%(<name>)s` 会被应用,其中 `<name>` 可以是相同分区或者默认分区下的键名。字符串 `%(<name>)s` 会被相应的键值所替代,如果指定的键不存在,则会用空字符串替代。您可以最多使用 99 层的递归嵌套。
|
|
||||||
|
|
||||||
```ini
|
|
||||||
NAME = ini
|
|
||||||
|
|
||||||
[author]
|
|
||||||
NAME = Unknwon
|
|
||||||
GITHUB = https://github.com/%(NAME)s
|
|
||||||
|
|
||||||
[package]
|
|
||||||
FULL_NAME = github.com/go-ini/%(NAME)s
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon
|
|
||||||
cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
|
|
||||||
```
|
|
||||||
|
|
||||||
### 读取父子分区
|
|
||||||
|
|
||||||
您可以在分区名称中使用 `.` 来表示两个或多个分区之间的父子关系。如果某个键在子分区中不存在,则会去它的父分区中再次寻找,直到没有父分区为止。
|
|
||||||
|
|
||||||
```ini
|
|
||||||
NAME = ini
|
|
||||||
VERSION = v1
|
|
||||||
IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
|
|
||||||
|
|
||||||
[package]
|
|
||||||
CLONE_URL = https://%(IMPORT_PATH)s
|
|
||||||
|
|
||||||
[package.sub]
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 获取上级父分区下的所有键名
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 无法解析的分区
|
|
||||||
|
|
||||||
如果遇到一些比较特殊的分区,它们不包含常见的键值对,而是没有固定格式的纯文本,则可以使用 `LoadOptions.UnparsableSections` 进行处理:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
|
|
||||||
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
|
|
||||||
|
|
||||||
body := cfg.Section("COMMENTS").Body()
|
|
||||||
|
|
||||||
/* --- start ---
|
|
||||||
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
|
|
||||||
------ end --- */
|
|
||||||
```
|
|
||||||
|
|
||||||
### 读取自增键名
|
|
||||||
|
|
||||||
如果数据源中的键名为 `-`,则认为该键使用了自增键名的特殊语法。计数器从 1 开始,并且分区之间是相互独立的。
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[features]
|
|
||||||
-: Support read/write comments of keys and sections
|
|
||||||
-: Support auto-increment of key names
|
|
||||||
-: Support load multiple files to overwrite key values
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 映射到结构
|
|
||||||
|
|
||||||
想要使用更加面向对象的方式玩转 INI 吗?好主意。
|
|
||||||
|
|
||||||
```ini
|
|
||||||
Name = Unknwon
|
|
||||||
age = 21
|
|
||||||
Male = true
|
|
||||||
Born = 1993-01-01T20:17:05Z
|
|
||||||
|
|
||||||
[Note]
|
|
||||||
Content = Hi is a good man!
|
|
||||||
Cities = HangZhou, Boston
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Note struct {
|
|
||||||
Content string
|
|
||||||
Cities []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Person struct {
|
|
||||||
Name string
|
|
||||||
Age int `ini:"age"`
|
|
||||||
Male bool
|
|
||||||
Born time.Time
|
|
||||||
Note
|
|
||||||
Created time.Time `ini:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cfg, err := ini.Load("path/to/ini")
|
|
||||||
// ...
|
|
||||||
p := new(Person)
|
|
||||||
err = cfg.MapTo(p)
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// 一切竟可以如此的简单。
|
|
||||||
err = ini.MapTo(p, "path/to/ini")
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// 嗯哼?只需要映射一个分区吗?
|
|
||||||
n := new(Note)
|
|
||||||
err = cfg.Section("Note").MapTo(n)
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
结构的字段怎么设置默认值呢?很简单,只要在映射之前对指定字段进行赋值就可以了。如果键未找到或者类型错误,该值不会发生改变。
|
|
||||||
|
|
||||||
```go
|
|
||||||
// ...
|
|
||||||
p := &Person{
|
|
||||||
Name: "Joe",
|
|
||||||
}
|
|
||||||
// ...
|
|
||||||
```
|
|
||||||
|
|
||||||
这样玩 INI 真的好酷啊!然而,如果不能还给我原来的配置文件,有什么卵用?
|
|
||||||
|
|
||||||
### 从结构反射
|
|
||||||
|
|
||||||
可是,我有说不能吗?
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Embeded struct {
|
|
||||||
Dates []time.Time `delim:"|"`
|
|
||||||
Places []string `ini:"places,omitempty"`
|
|
||||||
None []int `ini:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Author struct {
|
|
||||||
Name string `ini:"NAME"`
|
|
||||||
Male bool
|
|
||||||
Age int
|
|
||||||
GPA float64
|
|
||||||
NeverMind string `ini:"-"`
|
|
||||||
*Embeded
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
a := &Author{"Unknwon", true, 21, 2.8, "",
|
|
||||||
&Embeded{
|
|
||||||
[]time.Time{time.Now(), time.Now()},
|
|
||||||
[]string{"HangZhou", "Boston"},
|
|
||||||
[]int{},
|
|
||||||
}}
|
|
||||||
cfg := ini.Empty()
|
|
||||||
err = ini.ReflectFrom(cfg, a)
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
瞧瞧,奇迹发生了。
|
|
||||||
|
|
||||||
```ini
|
|
||||||
NAME = Unknwon
|
|
||||||
Male = true
|
|
||||||
Age = 21
|
|
||||||
GPA = 2.8
|
|
||||||
|
|
||||||
[Embeded]
|
|
||||||
Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
|
|
||||||
places = HangZhou,Boston
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 名称映射器(Name Mapper)
|
|
||||||
|
|
||||||
为了节省您的时间并简化代码,本库支持类型为 [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) 的名称映射器,该映射器负责结构字段名与分区名和键名之间的映射。
|
|
||||||
|
|
||||||
目前有 2 款内置的映射器:
|
|
||||||
|
|
||||||
- `AllCapsUnderscore`:该映射器将字段名转换至格式 `ALL_CAPS_UNDERSCORE` 后再去匹配分区名和键名。
|
|
||||||
- `TitleUnderscore`:该映射器将字段名转换至格式 `title_underscore` 后再去匹配分区名和键名。
|
|
||||||
|
|
||||||
使用方法:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Info struct{
|
|
||||||
PackageName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
|
|
||||||
// ...
|
|
||||||
|
|
||||||
cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
|
|
||||||
// ...
|
|
||||||
info := new(Info)
|
|
||||||
cfg.NameMapper = ini.AllCapsUnderscore
|
|
||||||
err = cfg.MapTo(info)
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
使用函数 `ini.ReflectFromWithMapper` 时也可应用相同的规则。
|
|
||||||
|
|
||||||
#### 值映射器(Value Mapper)
|
|
||||||
|
|
||||||
值映射器允许使用一个自定义函数自动展开值的具体内容,例如:运行时获取环境变量:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Env struct {
|
|
||||||
Foo string `ini:"foo"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
|
|
||||||
cfg.ValueMapper = os.ExpandEnv
|
|
||||||
// ...
|
|
||||||
env := &Env{}
|
|
||||||
err = cfg.Section("env").MapTo(env)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
本例中,`env.Foo` 将会是运行时所获取到环境变量 `MY_VAR` 的值。
|
|
||||||
|
|
||||||
#### 映射/反射的其它说明
|
|
||||||
|
|
||||||
任何嵌入的结构都会被默认认作一个不同的分区,并且不会自动产生所谓的父子分区关联:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Child struct {
|
|
||||||
Age string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Parent struct {
|
|
||||||
Name string
|
|
||||||
Child
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
City string
|
|
||||||
Parent
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
示例配置文件:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
City = Boston
|
|
||||||
|
|
||||||
[Parent]
|
|
||||||
Name = Unknwon
|
|
||||||
|
|
||||||
[Child]
|
|
||||||
Age = 21
|
|
||||||
```
|
|
||||||
|
|
||||||
很好,但是,我就是要嵌入结构也在同一个分区。好吧,你爹是李刚!
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Child struct {
|
|
||||||
Age string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Parent struct {
|
|
||||||
Name string
|
|
||||||
Child `ini:"Parent"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
City string
|
|
||||||
Parent
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
示例配置文件:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
City = Boston
|
|
||||||
|
|
||||||
[Parent]
|
|
||||||
Name = Unknwon
|
|
||||||
Age = 21
|
|
||||||
```
|
|
||||||
|
|
||||||
## 获取帮助
|
|
||||||
|
|
||||||
- [API 文档](https://gowalker.org/gopkg.in/ini.v1)
|
|
||||||
- [创建工单](https://github.com/go-ini/ini/issues/new)
|
|
||||||
|
|
||||||
## 常见问题
|
|
||||||
|
|
||||||
### 字段 `BlockMode` 是什么?
|
|
||||||
|
|
||||||
默认情况下,本库会在您进行读写操作时采用锁机制来确保数据时间。但在某些情况下,您非常确定只进行读操作。此时,您可以通过设置 `cfg.BlockMode = false` 来将读操作提升大约 **50-70%** 的性能。
|
|
||||||
|
|
||||||
### 为什么要写另一个 INI 解析库?
|
|
||||||
|
|
||||||
许多人都在使用我的 [goconfig](https://github.com/Unknwon/goconfig) 来完成对 INI 文件的操作,但我希望使用更加 Go 风格的代码。并且当您设置 `cfg.BlockMode = false` 时,会有大约 **10-30%** 的性能提升。
|
|
||||||
|
|
||||||
为了做出这些改变,我必须对 API 进行破坏,所以新开一个仓库是最安全的做法。除此之外,本库直接使用 `gopkg.in` 来进行版本化发布。(其实真相是导入路径更短了)
|
|
32
vendor/src/github.com/go-ini/ini/error.go
vendored
32
vendor/src/github.com/go-ini/ini/error.go
vendored
|
@ -1,32 +0,0 @@
|
||||||
// Copyright 2016 Unknwon
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package ini
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ErrDelimiterNotFound struct {
|
|
||||||
Line string
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsErrDelimiterNotFound(err error) bool {
|
|
||||||
_, ok := err.(ErrDelimiterNotFound)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrDelimiterNotFound) Error() string {
|
|
||||||
return fmt.Sprintf("key-value delimiter not found: %s", err.Line)
|
|
||||||
}
|
|
561
vendor/src/github.com/go-ini/ini/ini.go
vendored
561
vendor/src/github.com/go-ini/ini/ini.go
vendored
|
@ -1,561 +0,0 @@
|
||||||
// Copyright 2014 Unknwon
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
// Package ini provides INI file read and write functionality in Go.
|
|
||||||
package ini
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Name for default section. You can use this constant or the string literal.
|
|
||||||
// In most of cases, an empty string is all you need to access the section.
|
|
||||||
DEFAULT_SECTION = "DEFAULT"
|
|
||||||
|
|
||||||
// Maximum allowed depth when recursively substituing variable names.
|
|
||||||
_DEPTH_VALUES = 99
|
|
||||||
_VERSION = "1.28.1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Version returns current package version literal.
|
|
||||||
func Version() string {
|
|
||||||
return _VERSION
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Delimiter to determine or compose a new line.
|
|
||||||
// This variable will be changed to "\r\n" automatically on Windows
|
|
||||||
// at package init time.
|
|
||||||
LineBreak = "\n"
|
|
||||||
|
|
||||||
// Variable regexp pattern: %(variable)s
|
|
||||||
varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`)
|
|
||||||
|
|
||||||
// Indicate whether to align "=" sign with spaces to produce pretty output
|
|
||||||
// or reduce all possible spaces for compact format.
|
|
||||||
PrettyFormat = true
|
|
||||||
|
|
||||||
// Explicitly write DEFAULT section header
|
|
||||||
DefaultHeader = false
|
|
||||||
|
|
||||||
// Indicate whether to put a line between sections
|
|
||||||
PrettySection = true
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
LineBreak = "\r\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func inSlice(str string, s []string) bool {
|
|
||||||
for _, v := range s {
|
|
||||||
if str == v {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// dataSource is an interface that returns object which can be read and closed.
|
|
||||||
type dataSource interface {
|
|
||||||
ReadCloser() (io.ReadCloser, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sourceFile represents an object that contains content on the local file system.
|
|
||||||
type sourceFile struct {
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
|
|
||||||
return os.Open(s.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
type bytesReadCloser struct {
|
|
||||||
reader io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rc *bytesReadCloser) Read(p []byte) (n int, err error) {
|
|
||||||
return rc.reader.Read(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rc *bytesReadCloser) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sourceData represents an object that contains content in memory.
|
|
||||||
type sourceData struct {
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
|
|
||||||
return ioutil.NopCloser(bytes.NewReader(s.data)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sourceReadCloser represents an input stream with Close method.
|
|
||||||
type sourceReadCloser struct {
|
|
||||||
reader io.ReadCloser
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) {
|
|
||||||
return s.reader, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// File represents a combination of a or more INI file(s) in memory.
|
|
||||||
type File struct {
|
|
||||||
// Should make things safe, but sometimes doesn't matter.
|
|
||||||
BlockMode bool
|
|
||||||
// Make sure data is safe in multiple goroutines.
|
|
||||||
lock sync.RWMutex
|
|
||||||
|
|
||||||
// Allow combination of multiple data sources.
|
|
||||||
dataSources []dataSource
|
|
||||||
// Actual data is stored here.
|
|
||||||
sections map[string]*Section
|
|
||||||
|
|
||||||
// To keep data in order.
|
|
||||||
sectionList []string
|
|
||||||
|
|
||||||
options LoadOptions
|
|
||||||
|
|
||||||
NameMapper
|
|
||||||
ValueMapper
|
|
||||||
}
|
|
||||||
|
|
||||||
// newFile initializes File object with given data sources.
|
|
||||||
func newFile(dataSources []dataSource, opts LoadOptions) *File {
|
|
||||||
return &File{
|
|
||||||
BlockMode: true,
|
|
||||||
dataSources: dataSources,
|
|
||||||
sections: make(map[string]*Section),
|
|
||||||
sectionList: make([]string, 0, 10),
|
|
||||||
options: opts,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDataSource(source interface{}) (dataSource, error) {
|
|
||||||
switch s := source.(type) {
|
|
||||||
case string:
|
|
||||||
return sourceFile{s}, nil
|
|
||||||
case []byte:
|
|
||||||
return &sourceData{s}, nil
|
|
||||||
case io.ReadCloser:
|
|
||||||
return &sourceReadCloser{s}, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("error parsing data source: unknown type '%s'", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type LoadOptions struct {
|
|
||||||
// Loose indicates whether the parser should ignore nonexistent files or return error.
|
|
||||||
Loose bool
|
|
||||||
// Insensitive indicates whether the parser forces all section and key names to lowercase.
|
|
||||||
Insensitive bool
|
|
||||||
// IgnoreContinuation indicates whether to ignore continuation lines while parsing.
|
|
||||||
IgnoreContinuation bool
|
|
||||||
// IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
|
|
||||||
IgnoreInlineComment bool
|
|
||||||
// AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
|
|
||||||
// This type of keys are mostly used in my.cnf.
|
|
||||||
AllowBooleanKeys bool
|
|
||||||
// AllowShadows indicates whether to keep track of keys with same name under same section.
|
|
||||||
AllowShadows bool
|
|
||||||
// Some INI formats allow group blocks that store a block of raw content that doesn't otherwise
|
|
||||||
// conform to key/value pairs. Specify the names of those blocks here.
|
|
||||||
UnparseableSections []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
|
|
||||||
sources := make([]dataSource, len(others)+1)
|
|
||||||
sources[0], err = parseDataSource(source)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for i := range others {
|
|
||||||
sources[i+1], err = parseDataSource(others[i])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f := newFile(sources, opts)
|
|
||||||
if err = f.Reload(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load loads and parses from INI data sources.
|
|
||||||
// Arguments can be mixed of file name with string type, or raw data in []byte.
|
|
||||||
// It will return error if list contains nonexistent files.
|
|
||||||
func Load(source interface{}, others ...interface{}) (*File, error) {
|
|
||||||
return LoadSources(LoadOptions{}, source, others...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LooseLoad has exactly same functionality as Load function
|
|
||||||
// except it ignores nonexistent files instead of returning error.
|
|
||||||
func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
|
|
||||||
return LoadSources(LoadOptions{Loose: true}, source, others...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsensitiveLoad has exactly same functionality as Load function
|
|
||||||
// except it forces all section and key names to be lowercased.
|
|
||||||
func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
|
|
||||||
return LoadSources(LoadOptions{Insensitive: true}, source, others...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsensitiveLoad has exactly same functionality as Load function
|
|
||||||
// except it allows have shadow keys.
|
|
||||||
func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
|
|
||||||
return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty returns an empty file object.
|
|
||||||
func Empty() *File {
|
|
||||||
// Ignore error here, we sure our data is good.
|
|
||||||
f, _ := Load([]byte(""))
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSection creates a new section.
|
|
||||||
func (f *File) NewSection(name string) (*Section, error) {
|
|
||||||
if len(name) == 0 {
|
|
||||||
return nil, errors.New("error creating new section: empty section name")
|
|
||||||
} else if f.options.Insensitive && name != DEFAULT_SECTION {
|
|
||||||
name = strings.ToLower(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.BlockMode {
|
|
||||||
f.lock.Lock()
|
|
||||||
defer f.lock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if inSlice(name, f.sectionList) {
|
|
||||||
return f.sections[name], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
f.sectionList = append(f.sectionList, name)
|
|
||||||
f.sections[name] = newSection(f, name)
|
|
||||||
return f.sections[name], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRawSection creates a new section with an unparseable body.
|
|
||||||
func (f *File) NewRawSection(name, body string) (*Section, error) {
|
|
||||||
section, err := f.NewSection(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
section.isRawSection = true
|
|
||||||
section.rawBody = body
|
|
||||||
return section, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSections creates a list of sections.
|
|
||||||
func (f *File) NewSections(names ...string) (err error) {
|
|
||||||
for _, name := range names {
|
|
||||||
if _, err = f.NewSection(name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSection returns section by given name.
|
|
||||||
func (f *File) GetSection(name string) (*Section, error) {
|
|
||||||
if len(name) == 0 {
|
|
||||||
name = DEFAULT_SECTION
|
|
||||||
} else if f.options.Insensitive {
|
|
||||||
name = strings.ToLower(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.BlockMode {
|
|
||||||
f.lock.RLock()
|
|
||||||
defer f.lock.RUnlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
sec := f.sections[name]
|
|
||||||
if sec == nil {
|
|
||||||
return nil, fmt.Errorf("section '%s' does not exist", name)
|
|
||||||
}
|
|
||||||
return sec, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Section assumes named section exists and returns a zero-value when not.
|
|
||||||
func (f *File) Section(name string) *Section {
|
|
||||||
sec, err := f.GetSection(name)
|
|
||||||
if err != nil {
|
|
||||||
// Note: It's OK here because the only possible error is empty section name,
|
|
||||||
// but if it's empty, this piece of code won't be executed.
|
|
||||||
sec, _ = f.NewSection(name)
|
|
||||||
return sec
|
|
||||||
}
|
|
||||||
return sec
|
|
||||||
}
|
|
||||||
|
|
||||||
// Section returns list of Section.
|
|
||||||
func (f *File) Sections() []*Section {
|
|
||||||
sections := make([]*Section, len(f.sectionList))
|
|
||||||
for i := range f.sectionList {
|
|
||||||
sections[i] = f.Section(f.sectionList[i])
|
|
||||||
}
|
|
||||||
return sections
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChildSections returns a list of child sections of given section name.
|
|
||||||
func (f *File) ChildSections(name string) []*Section {
|
|
||||||
return f.Section(name).ChildSections()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SectionStrings returns list of section names.
|
|
||||||
func (f *File) SectionStrings() []string {
|
|
||||||
list := make([]string, len(f.sectionList))
|
|
||||||
copy(list, f.sectionList)
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteSection deletes a section.
|
|
||||||
func (f *File) DeleteSection(name string) {
|
|
||||||
if f.BlockMode {
|
|
||||||
f.lock.Lock()
|
|
||||||
defer f.lock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(name) == 0 {
|
|
||||||
name = DEFAULT_SECTION
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, s := range f.sectionList {
|
|
||||||
if s == name {
|
|
||||||
f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
|
|
||||||
delete(f.sections, name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) reload(s dataSource) error {
|
|
||||||
r, err := s.ReadCloser()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer r.Close()
|
|
||||||
|
|
||||||
return f.parse(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload reloads and parses all data sources.
|
|
||||||
func (f *File) Reload() (err error) {
|
|
||||||
for _, s := range f.dataSources {
|
|
||||||
if err = f.reload(s); err != nil {
|
|
||||||
// In loose mode, we create an empty default section for nonexistent files.
|
|
||||||
if os.IsNotExist(err) && f.options.Loose {
|
|
||||||
f.parse(bytes.NewBuffer(nil))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append appends one or more data sources and reloads automatically.
|
|
||||||
func (f *File) Append(source interface{}, others ...interface{}) error {
|
|
||||||
ds, err := parseDataSource(source)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.dataSources = append(f.dataSources, ds)
|
|
||||||
for _, s := range others {
|
|
||||||
ds, err = parseDataSource(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.dataSources = append(f.dataSources, ds)
|
|
||||||
}
|
|
||||||
return f.Reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteToIndent writes content into io.Writer with given indention.
|
|
||||||
// If PrettyFormat has been set to be true,
|
|
||||||
// it will align "=" sign with spaces under each section.
|
|
||||||
func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
|
|
||||||
equalSign := "="
|
|
||||||
if PrettyFormat {
|
|
||||||
equalSign = " = "
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use buffer to make sure target is safe until finish encoding.
|
|
||||||
buf := bytes.NewBuffer(nil)
|
|
||||||
for i, sname := range f.sectionList {
|
|
||||||
sec := f.Section(sname)
|
|
||||||
if len(sec.Comment) > 0 {
|
|
||||||
if sec.Comment[0] != '#' && sec.Comment[0] != ';' {
|
|
||||||
sec.Comment = "; " + sec.Comment
|
|
||||||
}
|
|
||||||
if _, err = buf.WriteString(sec.Comment + LineBreak); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if i > 0 || DefaultHeader {
|
|
||||||
if _, err = buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Write nothing if default section is empty
|
|
||||||
if len(sec.keyList) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sec.isRawSection {
|
|
||||||
if _, err = buf.WriteString(sec.rawBody); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count and generate alignment length and buffer spaces using the
|
|
||||||
// longest key. Keys may be modifed if they contain certain characters so
|
|
||||||
// we need to take that into account in our calculation.
|
|
||||||
alignLength := 0
|
|
||||||
if PrettyFormat {
|
|
||||||
for _, kname := range sec.keyList {
|
|
||||||
keyLength := len(kname)
|
|
||||||
// First case will surround key by ` and second by """
|
|
||||||
if strings.ContainsAny(kname, "\"=:") {
|
|
||||||
keyLength += 2
|
|
||||||
} else if strings.Contains(kname, "`") {
|
|
||||||
keyLength += 6
|
|
||||||
}
|
|
||||||
|
|
||||||
if keyLength > alignLength {
|
|
||||||
alignLength = keyLength
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
alignSpaces := bytes.Repeat([]byte(" "), alignLength)
|
|
||||||
|
|
||||||
KEY_LIST:
|
|
||||||
for _, kname := range sec.keyList {
|
|
||||||
key := sec.Key(kname)
|
|
||||||
if len(key.Comment) > 0 {
|
|
||||||
if len(indent) > 0 && sname != DEFAULT_SECTION {
|
|
||||||
buf.WriteString(indent)
|
|
||||||
}
|
|
||||||
if key.Comment[0] != '#' && key.Comment[0] != ';' {
|
|
||||||
key.Comment = "; " + key.Comment
|
|
||||||
}
|
|
||||||
if _, err = buf.WriteString(key.Comment + LineBreak); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(indent) > 0 && sname != DEFAULT_SECTION {
|
|
||||||
buf.WriteString(indent)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case key.isAutoIncrement:
|
|
||||||
kname = "-"
|
|
||||||
case strings.ContainsAny(kname, "\"=:"):
|
|
||||||
kname = "`" + kname + "`"
|
|
||||||
case strings.Contains(kname, "`"):
|
|
||||||
kname = `"""` + kname + `"""`
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, val := range key.ValueWithShadows() {
|
|
||||||
if _, err = buf.WriteString(kname); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.isBooleanType {
|
|
||||||
if kname != sec.keyList[len(sec.keyList)-1] {
|
|
||||||
buf.WriteString(LineBreak)
|
|
||||||
}
|
|
||||||
continue KEY_LIST
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write out alignment spaces before "=" sign
|
|
||||||
if PrettyFormat {
|
|
||||||
buf.Write(alignSpaces[:alignLength-len(kname)])
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case key value contains "\n", "`", "\"", "#" or ";"
|
|
||||||
if strings.ContainsAny(val, "\n`") {
|
|
||||||
val = `"""` + val + `"""`
|
|
||||||
} else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
|
|
||||||
val = "`" + val + "`"
|
|
||||||
}
|
|
||||||
if _, err = buf.WriteString(equalSign + val + LineBreak); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if PrettySection {
|
|
||||||
// Put a line between sections
|
|
||||||
if _, err = buf.WriteString(LineBreak); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.WriteTo(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteTo writes file content into io.Writer.
|
|
||||||
func (f *File) WriteTo(w io.Writer) (int64, error) {
|
|
||||||
return f.WriteToIndent(w, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveToIndent writes content to file system with given value indention.
|
|
||||||
func (f *File) SaveToIndent(filename, indent string) error {
|
|
||||||
// Note: Because we are truncating with os.Create,
|
|
||||||
// so it's safer to save to a temporary file location and rename afte done.
|
|
||||||
tmpPath := filename + "." + strconv.Itoa(time.Now().Nanosecond()) + ".tmp"
|
|
||||||
defer os.Remove(tmpPath)
|
|
||||||
|
|
||||||
fw, err := os.Create(tmpPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = f.WriteToIndent(fw, indent); err != nil {
|
|
||||||
fw.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fw.Close()
|
|
||||||
|
|
||||||
// Remove old file and rename the new one.
|
|
||||||
os.Remove(filename)
|
|
||||||
return os.Rename(tmpPath, filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveTo writes content to file system.
|
|
||||||
func (f *File) SaveTo(filename string) error {
|
|
||||||
return f.SaveToIndent(filename, "")
|
|
||||||
}
|
|
491
vendor/src/github.com/go-ini/ini/ini_test.go
vendored
491
vendor/src/github.com/go-ini/ini/ini_test.go
vendored
|
@ -1,491 +0,0 @@
|
||||||
// Copyright 2014 Unknwon
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package ini
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_Version(t *testing.T) {
|
|
||||||
Convey("Get version", t, func() {
|
|
||||||
So(Version(), ShouldEqual, _VERSION)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const _CONF_DATA = `
|
|
||||||
; Package name
|
|
||||||
NAME = ini
|
|
||||||
; Package version
|
|
||||||
VERSION = v1
|
|
||||||
; Package import path
|
|
||||||
IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
|
|
||||||
|
|
||||||
# Information about package author
|
|
||||||
# Bio can be written in multiple lines.
|
|
||||||
[author]
|
|
||||||
NAME = Unknwon ; Succeeding comment
|
|
||||||
E-MAIL = fake@localhost
|
|
||||||
GITHUB = https://github.com/%(NAME)s
|
|
||||||
BIO = """Gopher.
|
|
||||||
Coding addict.
|
|
||||||
Good man.
|
|
||||||
""" # Succeeding comment
|
|
||||||
|
|
||||||
[package]
|
|
||||||
CLONE_URL = https://%(IMPORT_PATH)s
|
|
||||||
|
|
||||||
[package.sub]
|
|
||||||
UNUSED_KEY = should be deleted
|
|
||||||
|
|
||||||
[features]
|
|
||||||
-: Support read/write comments of keys and sections
|
|
||||||
-: Support auto-increment of key names
|
|
||||||
-: Support load multiple files to overwrite key values
|
|
||||||
|
|
||||||
[types]
|
|
||||||
STRING = str
|
|
||||||
BOOL = true
|
|
||||||
BOOL_FALSE = false
|
|
||||||
FLOAT64 = 1.25
|
|
||||||
INT = 10
|
|
||||||
TIME = 2015-01-01T20:17:05Z
|
|
||||||
DURATION = 2h45m
|
|
||||||
UINT = 3
|
|
||||||
|
|
||||||
[array]
|
|
||||||
STRINGS = en, zh, de
|
|
||||||
FLOAT64S = 1.1, 2.2, 3.3
|
|
||||||
INTS = 1, 2, 3
|
|
||||||
UINTS = 1, 2, 3
|
|
||||||
TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
|
|
||||||
|
|
||||||
[note]
|
|
||||||
empty_lines = next line is empty\
|
|
||||||
|
|
||||||
; Comment before the section
|
|
||||||
[comments] ; This is a comment for the section too
|
|
||||||
; Comment before key
|
|
||||||
key = "value"
|
|
||||||
key2 = "value2" ; This is a comment for key2
|
|
||||||
key3 = "one", "two", "three"
|
|
||||||
|
|
||||||
[advance]
|
|
||||||
value with quotes = "some value"
|
|
||||||
value quote2 again = 'some value'
|
|
||||||
includes comment sign = ` + "`" + "my#password" + "`" + `
|
|
||||||
includes comment sign2 = ` + "`" + "my;password" + "`" + `
|
|
||||||
true = 2+3=5
|
|
||||||
"1+1=2" = true
|
|
||||||
"""6+1=7""" = true
|
|
||||||
"""` + "`" + `5+5` + "`" + `""" = 10
|
|
||||||
` + "`" + `"6+6"` + "`" + ` = 12
|
|
||||||
` + "`" + `7-2=4` + "`" + ` = false
|
|
||||||
ADDRESS = ` + "`" + `404 road,
|
|
||||||
NotFound, State, 50000` + "`" + `
|
|
||||||
|
|
||||||
two_lines = how about \
|
|
||||||
continuation lines?
|
|
||||||
lots_of_lines = 1 \
|
|
||||||
2 \
|
|
||||||
3 \
|
|
||||||
4 \
|
|
||||||
`
|
|
||||||
|
|
||||||
func Test_Load(t *testing.T) {
|
|
||||||
Convey("Load from data sources", t, func() {
|
|
||||||
|
|
||||||
Convey("Load with empty data", func() {
|
|
||||||
So(Empty(), ShouldNotBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Load with multiple data sources", func() {
|
|
||||||
cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini", ioutil.NopCloser(bytes.NewReader([]byte(_CONF_DATA))))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
|
|
||||||
f, err := Load([]byte(_CONF_DATA), "testdata/404.ini")
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
So(f, ShouldBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Load with io.ReadCloser", func() {
|
|
||||||
cfg, err := Load(ioutil.NopCloser(bytes.NewReader([]byte(_CONF_DATA))))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
|
|
||||||
So(cfg.Section("").Key("NAME").String(), ShouldEqual, "ini")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Bad load process", t, func() {
|
|
||||||
|
|
||||||
Convey("Load from invalid data sources", func() {
|
|
||||||
_, err := Load(_CONF_DATA)
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
|
|
||||||
f, err := Load("testdata/404.ini")
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
So(f, ShouldBeNil)
|
|
||||||
|
|
||||||
_, err = Load(1)
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
|
|
||||||
_, err = Load([]byte(""), 1)
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Load with bad section name", func() {
|
|
||||||
_, err := Load([]byte("[]"))
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
|
|
||||||
_, err = Load([]byte("["))
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Load with bad keys", func() {
|
|
||||||
_, err := Load([]byte(`"""name`))
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
|
|
||||||
_, err = Load([]byte(`"""name"""`))
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
|
|
||||||
_, err = Load([]byte(`""=1`))
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
|
|
||||||
_, err = Load([]byte(`=`))
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
|
|
||||||
_, err = Load([]byte(`name`))
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Load with bad values", func() {
|
|
||||||
_, err := Load([]byte(`name="""Unknwon`))
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get section and key insensitively", t, func() {
|
|
||||||
cfg, err := InsensitiveLoad([]byte(_CONF_DATA), "testdata/conf.ini")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
|
|
||||||
sec, err := cfg.GetSection("Author")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(sec, ShouldNotBeNil)
|
|
||||||
|
|
||||||
key, err := sec.GetKey("E-mail")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(key, ShouldNotBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Load with ignoring continuation lines", t, func() {
|
|
||||||
cfg, err := LoadSources(LoadOptions{IgnoreContinuation: true}, []byte(`key1=a\b\
|
|
||||||
key2=c\d\`))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
|
|
||||||
So(cfg.Section("").Key("key1").String(), ShouldEqual, `a\b\`)
|
|
||||||
So(cfg.Section("").Key("key2").String(), ShouldEqual, `c\d\`)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Load with ignoring inline comments", t, func() {
|
|
||||||
cfg, err := LoadSources(LoadOptions{IgnoreInlineComment: true}, []byte(`key1=value ;comment
|
|
||||||
key2=value #comment2`))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
|
|
||||||
So(cfg.Section("").Key("key1").String(), ShouldEqual, `value ;comment`)
|
|
||||||
So(cfg.Section("").Key("key2").String(), ShouldEqual, `value #comment2`)
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
cfg.WriteTo(&buf)
|
|
||||||
So(buf.String(), ShouldEqual, `key1 = value ;comment
|
|
||||||
key2 = value #comment2
|
|
||||||
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Load with boolean type keys", t, func() {
|
|
||||||
cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, []byte(`key1=hello
|
|
||||||
key2
|
|
||||||
#key3
|
|
||||||
key4
|
|
||||||
key5`))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
|
|
||||||
So(strings.Join(cfg.Section("").KeyStrings(), ","), ShouldEqual, "key1,key2,key4,key5")
|
|
||||||
So(cfg.Section("").Key("key2").MustBool(false), ShouldBeTrue)
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
cfg.WriteTo(&buf)
|
|
||||||
// there is always a trailing \n at the end of the section
|
|
||||||
So(buf.String(), ShouldEqual, `key1 = hello
|
|
||||||
key2
|
|
||||||
#key3
|
|
||||||
key4
|
|
||||||
key5
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_File_ChildSections(t *testing.T) {
|
|
||||||
Convey("Find child sections by parent name", t, func() {
|
|
||||||
cfg, err := Load([]byte(`
|
|
||||||
[node]
|
|
||||||
|
|
||||||
[node.biz1]
|
|
||||||
|
|
||||||
[node.biz2]
|
|
||||||
|
|
||||||
[node.biz3]
|
|
||||||
|
|
||||||
[node.bizN]
|
|
||||||
`))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
|
|
||||||
children := cfg.ChildSections("node")
|
|
||||||
names := make([]string, len(children))
|
|
||||||
for i := range children {
|
|
||||||
names[i] = children[i].name
|
|
||||||
}
|
|
||||||
So(strings.Join(names, ","), ShouldEqual, "node.biz1,node.biz2,node.biz3,node.bizN")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_LooseLoad(t *testing.T) {
|
|
||||||
Convey("Loose load from data sources", t, func() {
|
|
||||||
Convey("Loose load mixed with nonexistent file", func() {
|
|
||||||
cfg, err := LooseLoad("testdata/404.ini")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
var fake struct {
|
|
||||||
Name string `ini:"name"`
|
|
||||||
}
|
|
||||||
So(cfg.MapTo(&fake), ShouldBeNil)
|
|
||||||
|
|
||||||
cfg, err = LooseLoad([]byte("name=Unknwon"), "testdata/404.ini")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg.Section("").Key("name").String(), ShouldEqual, "Unknwon")
|
|
||||||
So(cfg.MapTo(&fake), ShouldBeNil)
|
|
||||||
So(fake.Name, ShouldEqual, "Unknwon")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_File_Append(t *testing.T) {
|
|
||||||
Convey("Append data sources", t, func() {
|
|
||||||
cfg, err := Load([]byte(""))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
|
|
||||||
So(cfg.Append([]byte(""), []byte("")), ShouldBeNil)
|
|
||||||
|
|
||||||
Convey("Append bad data sources", func() {
|
|
||||||
So(cfg.Append(1), ShouldNotBeNil)
|
|
||||||
So(cfg.Append([]byte(""), 1), ShouldNotBeNil)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_File_WriteTo(t *testing.T) {
|
|
||||||
Convey("Write to somewhere", t, func() {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
cfg := Empty()
|
|
||||||
cfg.WriteTo(&buf)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_File_SaveTo_WriteTo(t *testing.T) {
|
|
||||||
Convey("Save file", t, func() {
|
|
||||||
cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
|
|
||||||
cfg.Section("").Key("NAME").Comment = "Package name"
|
|
||||||
cfg.Section("author").Comment = `Information about package author
|
|
||||||
# Bio can be written in multiple lines.`
|
|
||||||
cfg.Section("advanced").Key("val w/ pound").SetValue("my#password")
|
|
||||||
cfg.Section("advanced").Key("longest key has a colon : yes/no").SetValue("yes")
|
|
||||||
So(cfg.SaveTo("testdata/conf_out.ini"), ShouldBeNil)
|
|
||||||
|
|
||||||
cfg.Section("author").Key("NAME").Comment = "This is author name"
|
|
||||||
|
|
||||||
So(cfg.SaveToIndent("testdata/conf_out.ini", "\t"), ShouldBeNil)
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_, err = cfg.WriteToIndent(&buf, "\t")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(buf.String(), ShouldEqual, `; Package name
|
|
||||||
NAME = ini
|
|
||||||
; Package version
|
|
||||||
VERSION = v1
|
|
||||||
; Package import path
|
|
||||||
IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
|
|
||||||
|
|
||||||
; Information about package author
|
|
||||||
# Bio can be written in multiple lines.
|
|
||||||
[author]
|
|
||||||
; This is author name
|
|
||||||
NAME = Unknwon
|
|
||||||
E-MAIL = u@gogs.io
|
|
||||||
GITHUB = https://github.com/%(NAME)s
|
|
||||||
# Succeeding comment
|
|
||||||
BIO = """Gopher.
|
|
||||||
Coding addict.
|
|
||||||
Good man.
|
|
||||||
"""
|
|
||||||
|
|
||||||
[package]
|
|
||||||
CLONE_URL = https://%(IMPORT_PATH)s
|
|
||||||
|
|
||||||
[package.sub]
|
|
||||||
UNUSED_KEY = should be deleted
|
|
||||||
|
|
||||||
[features]
|
|
||||||
- = Support read/write comments of keys and sections
|
|
||||||
- = Support auto-increment of key names
|
|
||||||
- = Support load multiple files to overwrite key values
|
|
||||||
|
|
||||||
[types]
|
|
||||||
STRING = str
|
|
||||||
BOOL = true
|
|
||||||
BOOL_FALSE = false
|
|
||||||
FLOAT64 = 1.25
|
|
||||||
INT = 10
|
|
||||||
TIME = 2015-01-01T20:17:05Z
|
|
||||||
DURATION = 2h45m
|
|
||||||
UINT = 3
|
|
||||||
|
|
||||||
[array]
|
|
||||||
STRINGS = en, zh, de
|
|
||||||
FLOAT64S = 1.1, 2.2, 3.3
|
|
||||||
INTS = 1, 2, 3
|
|
||||||
UINTS = 1, 2, 3
|
|
||||||
TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
|
|
||||||
|
|
||||||
[note]
|
|
||||||
empty_lines = next line is empty
|
|
||||||
|
|
||||||
; Comment before the section
|
|
||||||
; This is a comment for the section too
|
|
||||||
[comments]
|
|
||||||
; Comment before key
|
|
||||||
key = value
|
|
||||||
; This is a comment for key2
|
|
||||||
key2 = value2
|
|
||||||
key3 = "one", "two", "three"
|
|
||||||
|
|
||||||
[advance]
|
|
||||||
value with quotes = some value
|
|
||||||
value quote2 again = some value
|
|
||||||
includes comment sign = `+"`"+"my#password"+"`"+`
|
|
||||||
includes comment sign2 = `+"`"+"my;password"+"`"+`
|
|
||||||
true = 2+3=5
|
|
||||||
`+"`"+`1+1=2`+"`"+` = true
|
|
||||||
`+"`"+`6+1=7`+"`"+` = true
|
|
||||||
"""`+"`"+`5+5`+"`"+`""" = 10
|
|
||||||
`+"`"+`"6+6"`+"`"+` = 12
|
|
||||||
`+"`"+`7-2=4`+"`"+` = false
|
|
||||||
ADDRESS = """404 road,
|
|
||||||
NotFound, State, 50000"""
|
|
||||||
two_lines = how about continuation lines?
|
|
||||||
lots_of_lines = 1 2 3 4
|
|
||||||
|
|
||||||
[advanced]
|
|
||||||
val w/ pound = `+"`"+`my#password`+"`"+`
|
|
||||||
`+"`"+`longest key has a colon : yes/no`+"`"+` = yes
|
|
||||||
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_File_WriteTo_SectionRaw(t *testing.T) {
|
|
||||||
Convey("Write a INI with a raw section", t, func() {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
cfg, err := LoadSources(
|
|
||||||
LoadOptions{
|
|
||||||
UnparseableSections: []string{"CORE_LESSON", "COMMENTS"},
|
|
||||||
},
|
|
||||||
"testdata/aicc.ini")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
cfg.WriteToIndent(&buf, "\t")
|
|
||||||
So(buf.String(), ShouldEqual, `[Core]
|
|
||||||
Lesson_Location = 87
|
|
||||||
Lesson_Status = C
|
|
||||||
Score = 3
|
|
||||||
Time = 00:02:30
|
|
||||||
|
|
||||||
[CORE_LESSON]
|
|
||||||
my lesson state data – 1111111111111111111000000000000000001110000
|
|
||||||
111111111111111111100000000000111000000000 – end my lesson state data
|
|
||||||
[COMMENTS]
|
|
||||||
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers for slice tests.
|
|
||||||
func float64sEqual(values []float64, expected ...float64) {
|
|
||||||
So(values, ShouldHaveLength, len(expected))
|
|
||||||
for i, v := range expected {
|
|
||||||
So(values[i], ShouldEqual, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func intsEqual(values []int, expected ...int) {
|
|
||||||
So(values, ShouldHaveLength, len(expected))
|
|
||||||
for i, v := range expected {
|
|
||||||
So(values[i], ShouldEqual, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func int64sEqual(values []int64, expected ...int64) {
|
|
||||||
So(values, ShouldHaveLength, len(expected))
|
|
||||||
for i, v := range expected {
|
|
||||||
So(values[i], ShouldEqual, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func uintsEqual(values []uint, expected ...uint) {
|
|
||||||
So(values, ShouldHaveLength, len(expected))
|
|
||||||
for i, v := range expected {
|
|
||||||
So(values[i], ShouldEqual, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func uint64sEqual(values []uint64, expected ...uint64) {
|
|
||||||
So(values, ShouldHaveLength, len(expected))
|
|
||||||
for i, v := range expected {
|
|
||||||
So(values[i], ShouldEqual, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func timesEqual(values []time.Time, expected ...time.Time) {
|
|
||||||
So(values, ShouldHaveLength, len(expected))
|
|
||||||
for i, v := range expected {
|
|
||||||
So(values[i].String(), ShouldEqual, v.String())
|
|
||||||
}
|
|
||||||
}
|
|
699
vendor/src/github.com/go-ini/ini/key.go
vendored
699
vendor/src/github.com/go-ini/ini/key.go
vendored
|
@ -1,699 +0,0 @@
|
||||||
// Copyright 2014 Unknwon
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package ini
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Key represents a key under a section.
|
|
||||||
type Key struct {
|
|
||||||
s *Section
|
|
||||||
name string
|
|
||||||
value string
|
|
||||||
isAutoIncrement bool
|
|
||||||
isBooleanType bool
|
|
||||||
|
|
||||||
isShadow bool
|
|
||||||
shadows []*Key
|
|
||||||
|
|
||||||
Comment string
|
|
||||||
}
|
|
||||||
|
|
||||||
// newKey simply return a key object with given values.
|
|
||||||
func newKey(s *Section, name, val string) *Key {
|
|
||||||
return &Key{
|
|
||||||
s: s,
|
|
||||||
name: name,
|
|
||||||
value: val,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *Key) addShadow(val string) error {
|
|
||||||
if k.isShadow {
|
|
||||||
return errors.New("cannot add shadow to another shadow key")
|
|
||||||
} else if k.isAutoIncrement || k.isBooleanType {
|
|
||||||
return errors.New("cannot add shadow to auto-increment or boolean key")
|
|
||||||
}
|
|
||||||
|
|
||||||
shadow := newKey(k.s, k.name, val)
|
|
||||||
shadow.isShadow = true
|
|
||||||
k.shadows = append(k.shadows, shadow)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddShadow adds a new shadow key to itself.
|
|
||||||
func (k *Key) AddShadow(val string) error {
|
|
||||||
if !k.s.f.options.AllowShadows {
|
|
||||||
return errors.New("shadow key is not allowed")
|
|
||||||
}
|
|
||||||
return k.addShadow(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
|
|
||||||
type ValueMapper func(string) string
|
|
||||||
|
|
||||||
// Name returns name of key.
|
|
||||||
func (k *Key) Name() string {
|
|
||||||
return k.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns raw value of key for performance purpose.
|
|
||||||
func (k *Key) Value() string {
|
|
||||||
return k.value
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValueWithShadows returns raw values of key and its shadows if any.
|
|
||||||
func (k *Key) ValueWithShadows() []string {
|
|
||||||
if len(k.shadows) == 0 {
|
|
||||||
return []string{k.value}
|
|
||||||
}
|
|
||||||
vals := make([]string, len(k.shadows)+1)
|
|
||||||
vals[0] = k.value
|
|
||||||
for i := range k.shadows {
|
|
||||||
vals[i+1] = k.shadows[i].value
|
|
||||||
}
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
// transformValue takes a raw value and transforms to its final string.
|
|
||||||
func (k *Key) transformValue(val string) string {
|
|
||||||
if k.s.f.ValueMapper != nil {
|
|
||||||
val = k.s.f.ValueMapper(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fail-fast if no indicate char found for recursive value
|
|
||||||
if !strings.Contains(val, "%") {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
for i := 0; i < _DEPTH_VALUES; i++ {
|
|
||||||
vr := varPattern.FindString(val)
|
|
||||||
if len(vr) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take off leading '%(' and trailing ')s'.
|
|
||||||
noption := strings.TrimLeft(vr, "%(")
|
|
||||||
noption = strings.TrimRight(noption, ")s")
|
|
||||||
|
|
||||||
// Search in the same section.
|
|
||||||
nk, err := k.s.GetKey(noption)
|
|
||||||
if err != nil {
|
|
||||||
// Search again in default section.
|
|
||||||
nk, _ = k.s.f.Section("").GetKey(noption)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Substitute by new value and take off leading '%(' and trailing ')s'.
|
|
||||||
val = strings.Replace(val, vr, nk.value, -1)
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns string representation of value.
|
|
||||||
func (k *Key) String() string {
|
|
||||||
return k.transformValue(k.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate accepts a validate function which can
|
|
||||||
// return modifed result as key value.
|
|
||||||
func (k *Key) Validate(fn func(string) string) string {
|
|
||||||
return fn(k.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseBool returns the boolean value represented by the string.
|
|
||||||
//
|
|
||||||
// It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On,
|
|
||||||
// 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off.
|
|
||||||
// Any other value returns an error.
|
|
||||||
func parseBool(str string) (value bool, err error) {
|
|
||||||
switch str {
|
|
||||||
case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On":
|
|
||||||
return true, nil
|
|
||||||
case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off":
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bool returns bool type value.
|
|
||||||
func (k *Key) Bool() (bool, error) {
|
|
||||||
return parseBool(k.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64 returns float64 type value.
|
|
||||||
func (k *Key) Float64() (float64, error) {
|
|
||||||
return strconv.ParseFloat(k.String(), 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int returns int type value.
|
|
||||||
func (k *Key) Int() (int, error) {
|
|
||||||
return strconv.Atoi(k.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64 returns int64 type value.
|
|
||||||
func (k *Key) Int64() (int64, error) {
|
|
||||||
return strconv.ParseInt(k.String(), 10, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint returns uint type valued.
|
|
||||||
func (k *Key) Uint() (uint, error) {
|
|
||||||
u, e := strconv.ParseUint(k.String(), 10, 64)
|
|
||||||
return uint(u), e
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint64 returns uint64 type value.
|
|
||||||
func (k *Key) Uint64() (uint64, error) {
|
|
||||||
return strconv.ParseUint(k.String(), 10, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Duration returns time.Duration type value.
|
|
||||||
func (k *Key) Duration() (time.Duration, error) {
|
|
||||||
return time.ParseDuration(k.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeFormat parses with given format and returns time.Time type value.
|
|
||||||
func (k *Key) TimeFormat(format string) (time.Time, error) {
|
|
||||||
return time.Parse(format, k.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time parses with RFC3339 format and returns time.Time type value.
|
|
||||||
func (k *Key) Time() (time.Time, error) {
|
|
||||||
return k.TimeFormat(time.RFC3339)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustString returns default value if key value is empty.
|
|
||||||
func (k *Key) MustString(defaultVal string) string {
|
|
||||||
val := k.String()
|
|
||||||
if len(val) == 0 {
|
|
||||||
k.value = defaultVal
|
|
||||||
return defaultVal
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustBool always returns value without error,
|
|
||||||
// it returns false if error occurs.
|
|
||||||
func (k *Key) MustBool(defaultVal ...bool) bool {
|
|
||||||
val, err := k.Bool()
|
|
||||||
if len(defaultVal) > 0 && err != nil {
|
|
||||||
k.value = strconv.FormatBool(defaultVal[0])
|
|
||||||
return defaultVal[0]
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustFloat64 always returns value without error,
|
|
||||||
// it returns 0.0 if error occurs.
|
|
||||||
func (k *Key) MustFloat64(defaultVal ...float64) float64 {
|
|
||||||
val, err := k.Float64()
|
|
||||||
if len(defaultVal) > 0 && err != nil {
|
|
||||||
k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64)
|
|
||||||
return defaultVal[0]
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustInt always returns value without error,
|
|
||||||
// it returns 0 if error occurs.
|
|
||||||
func (k *Key) MustInt(defaultVal ...int) int {
|
|
||||||
val, err := k.Int()
|
|
||||||
if len(defaultVal) > 0 && err != nil {
|
|
||||||
k.value = strconv.FormatInt(int64(defaultVal[0]), 10)
|
|
||||||
return defaultVal[0]
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustInt64 always returns value without error,
|
|
||||||
// it returns 0 if error occurs.
|
|
||||||
func (k *Key) MustInt64(defaultVal ...int64) int64 {
|
|
||||||
val, err := k.Int64()
|
|
||||||
if len(defaultVal) > 0 && err != nil {
|
|
||||||
k.value = strconv.FormatInt(defaultVal[0], 10)
|
|
||||||
return defaultVal[0]
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustUint always returns value without error,
|
|
||||||
// it returns 0 if error occurs.
|
|
||||||
func (k *Key) MustUint(defaultVal ...uint) uint {
|
|
||||||
val, err := k.Uint()
|
|
||||||
if len(defaultVal) > 0 && err != nil {
|
|
||||||
k.value = strconv.FormatUint(uint64(defaultVal[0]), 10)
|
|
||||||
return defaultVal[0]
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustUint64 always returns value without error,
|
|
||||||
// it returns 0 if error occurs.
|
|
||||||
func (k *Key) MustUint64(defaultVal ...uint64) uint64 {
|
|
||||||
val, err := k.Uint64()
|
|
||||||
if len(defaultVal) > 0 && err != nil {
|
|
||||||
k.value = strconv.FormatUint(defaultVal[0], 10)
|
|
||||||
return defaultVal[0]
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustDuration always returns value without error,
|
|
||||||
// it returns zero value if error occurs.
|
|
||||||
func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration {
|
|
||||||
val, err := k.Duration()
|
|
||||||
if len(defaultVal) > 0 && err != nil {
|
|
||||||
k.value = defaultVal[0].String()
|
|
||||||
return defaultVal[0]
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustTimeFormat always parses with given format and returns value without error,
|
|
||||||
// it returns zero value if error occurs.
|
|
||||||
func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
|
|
||||||
val, err := k.TimeFormat(format)
|
|
||||||
if len(defaultVal) > 0 && err != nil {
|
|
||||||
k.value = defaultVal[0].Format(format)
|
|
||||||
return defaultVal[0]
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustTime always parses with RFC3339 format and returns value without error,
|
|
||||||
// it returns zero value if error occurs.
|
|
||||||
func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
|
|
||||||
return k.MustTimeFormat(time.RFC3339, defaultVal...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// In always returns value without error,
|
|
||||||
// it returns default value if error occurs or doesn't fit into candidates.
|
|
||||||
func (k *Key) In(defaultVal string, candidates []string) string {
|
|
||||||
val := k.String()
|
|
||||||
for _, cand := range candidates {
|
|
||||||
if val == cand {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultVal
|
|
||||||
}
|
|
||||||
|
|
||||||
// InFloat64 always returns value without error,
|
|
||||||
// it returns default value if error occurs or doesn't fit into candidates.
|
|
||||||
func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
|
|
||||||
val := k.MustFloat64()
|
|
||||||
for _, cand := range candidates {
|
|
||||||
if val == cand {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultVal
|
|
||||||
}
|
|
||||||
|
|
||||||
// InInt always returns value without error,
|
|
||||||
// it returns default value if error occurs or doesn't fit into candidates.
|
|
||||||
func (k *Key) InInt(defaultVal int, candidates []int) int {
|
|
||||||
val := k.MustInt()
|
|
||||||
for _, cand := range candidates {
|
|
||||||
if val == cand {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultVal
|
|
||||||
}
|
|
||||||
|
|
||||||
// InInt64 always returns value without error,
|
|
||||||
// it returns default value if error occurs or doesn't fit into candidates.
|
|
||||||
func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
|
|
||||||
val := k.MustInt64()
|
|
||||||
for _, cand := range candidates {
|
|
||||||
if val == cand {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultVal
|
|
||||||
}
|
|
||||||
|
|
||||||
// InUint always returns value without error,
|
|
||||||
// it returns default value if error occurs or doesn't fit into candidates.
|
|
||||||
func (k *Key) InUint(defaultVal uint, candidates []uint) uint {
|
|
||||||
val := k.MustUint()
|
|
||||||
for _, cand := range candidates {
|
|
||||||
if val == cand {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultVal
|
|
||||||
}
|
|
||||||
|
|
||||||
// InUint64 always returns value without error,
|
|
||||||
// it returns default value if error occurs or doesn't fit into candidates.
|
|
||||||
func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 {
|
|
||||||
val := k.MustUint64()
|
|
||||||
for _, cand := range candidates {
|
|
||||||
if val == cand {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultVal
|
|
||||||
}
|
|
||||||
|
|
||||||
// InTimeFormat always parses with given format and returns value without error,
|
|
||||||
// it returns default value if error occurs or doesn't fit into candidates.
|
|
||||||
func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
|
|
||||||
val := k.MustTimeFormat(format)
|
|
||||||
for _, cand := range candidates {
|
|
||||||
if val == cand {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultVal
|
|
||||||
}
|
|
||||||
|
|
||||||
// InTime always parses with RFC3339 format and returns value without error,
|
|
||||||
// it returns default value if error occurs or doesn't fit into candidates.
|
|
||||||
func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
|
|
||||||
return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RangeFloat64 checks if value is in given range inclusively,
|
|
||||||
// and returns default value if it's not.
|
|
||||||
func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
|
|
||||||
val := k.MustFloat64()
|
|
||||||
if val < min || val > max {
|
|
||||||
return defaultVal
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
// RangeInt checks if value is in given range inclusively,
|
|
||||||
// and returns default value if it's not.
|
|
||||||
func (k *Key) RangeInt(defaultVal, min, max int) int {
|
|
||||||
val := k.MustInt()
|
|
||||||
if val < min || val > max {
|
|
||||||
return defaultVal
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
// RangeInt64 checks if value is in given range inclusively,
|
|
||||||
// and returns default value if it's not.
|
|
||||||
func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
|
|
||||||
val := k.MustInt64()
|
|
||||||
if val < min || val > max {
|
|
||||||
return defaultVal
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
// RangeTimeFormat checks if value with given format is in given range inclusively,
|
|
||||||
// and returns default value if it's not.
|
|
||||||
func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
|
|
||||||
val := k.MustTimeFormat(format)
|
|
||||||
if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
|
|
||||||
return defaultVal
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
// RangeTime checks if value with RFC3339 format is in given range inclusively,
|
|
||||||
// and returns default value if it's not.
|
|
||||||
func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
|
|
||||||
return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strings returns list of string divided by given delimiter.
|
|
||||||
func (k *Key) Strings(delim string) []string {
|
|
||||||
str := k.String()
|
|
||||||
if len(str) == 0 {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
vals := strings.Split(str, delim)
|
|
||||||
for i := range vals {
|
|
||||||
// vals[i] = k.transformValue(strings.TrimSpace(vals[i]))
|
|
||||||
vals[i] = strings.TrimSpace(vals[i])
|
|
||||||
}
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringsWithShadows returns list of string divided by given delimiter.
|
|
||||||
// Shadows will also be appended if any.
|
|
||||||
func (k *Key) StringsWithShadows(delim string) []string {
|
|
||||||
vals := k.ValueWithShadows()
|
|
||||||
results := make([]string, 0, len(vals)*2)
|
|
||||||
for i := range vals {
|
|
||||||
if len(vals) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
results = append(results, strings.Split(vals[i], delim)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range results {
|
|
||||||
results[i] = k.transformValue(strings.TrimSpace(results[i]))
|
|
||||||
}
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64s returns list of float64 divided by given delimiter. Any invalid input will be treated as zero value.
|
|
||||||
func (k *Key) Float64s(delim string) []float64 {
|
|
||||||
vals, _ := k.parseFloat64s(k.Strings(delim), true, false)
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ints returns list of int divided by given delimiter. Any invalid input will be treated as zero value.
|
|
||||||
func (k *Key) Ints(delim string) []int {
|
|
||||||
vals, _ := k.parseInts(k.Strings(delim), true, false)
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64s returns list of int64 divided by given delimiter. Any invalid input will be treated as zero value.
|
|
||||||
func (k *Key) Int64s(delim string) []int64 {
|
|
||||||
vals, _ := k.parseInt64s(k.Strings(delim), true, false)
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uints returns list of uint divided by given delimiter. Any invalid input will be treated as zero value.
|
|
||||||
func (k *Key) Uints(delim string) []uint {
|
|
||||||
vals, _ := k.parseUints(k.Strings(delim), true, false)
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint64s returns list of uint64 divided by given delimiter. Any invalid input will be treated as zero value.
|
|
||||||
func (k *Key) Uint64s(delim string) []uint64 {
|
|
||||||
vals, _ := k.parseUint64s(k.Strings(delim), true, false)
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimesFormat parses with given format and returns list of time.Time divided by given delimiter.
|
|
||||||
// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
|
|
||||||
func (k *Key) TimesFormat(format, delim string) []time.Time {
|
|
||||||
vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false)
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
// Times parses with RFC3339 format and returns list of time.Time divided by given delimiter.
|
|
||||||
// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
|
|
||||||
func (k *Key) Times(delim string) []time.Time {
|
|
||||||
return k.TimesFormat(time.RFC3339, delim)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then
|
|
||||||
// it will not be included to result list.
|
|
||||||
func (k *Key) ValidFloat64s(delim string) []float64 {
|
|
||||||
vals, _ := k.parseFloat64s(k.Strings(delim), false, false)
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will
|
|
||||||
// not be included to result list.
|
|
||||||
func (k *Key) ValidInts(delim string) []int {
|
|
||||||
vals, _ := k.parseInts(k.Strings(delim), false, false)
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidInt64s returns list of int64 divided by given delimiter. If some value is not 64-bit integer,
|
|
||||||
// then it will not be included to result list.
|
|
||||||
func (k *Key) ValidInt64s(delim string) []int64 {
|
|
||||||
vals, _ := k.parseInt64s(k.Strings(delim), false, false)
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidUints returns list of uint divided by given delimiter. If some value is not unsigned integer,
|
|
||||||
// then it will not be included to result list.
|
|
||||||
func (k *Key) ValidUints(delim string) []uint {
|
|
||||||
vals, _ := k.parseUints(k.Strings(delim), false, false)
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidUint64s returns list of uint64 divided by given delimiter. If some value is not 64-bit unsigned
|
|
||||||
// integer, then it will not be included to result list.
|
|
||||||
func (k *Key) ValidUint64s(delim string) []uint64 {
|
|
||||||
vals, _ := k.parseUint64s(k.Strings(delim), false, false)
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
|
|
||||||
func (k *Key) ValidTimesFormat(format, delim string) []time.Time {
|
|
||||||
vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false)
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter.
|
|
||||||
func (k *Key) ValidTimes(delim string) []time.Time {
|
|
||||||
return k.ValidTimesFormat(time.RFC3339, delim)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input.
|
|
||||||
func (k *Key) StrictFloat64s(delim string) ([]float64, error) {
|
|
||||||
return k.parseFloat64s(k.Strings(delim), false, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrictInts returns list of int divided by given delimiter or error on first invalid input.
|
|
||||||
func (k *Key) StrictInts(delim string) ([]int, error) {
|
|
||||||
return k.parseInts(k.Strings(delim), false, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input.
|
|
||||||
func (k *Key) StrictInt64s(delim string) ([]int64, error) {
|
|
||||||
return k.parseInt64s(k.Strings(delim), false, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrictUints returns list of uint divided by given delimiter or error on first invalid input.
|
|
||||||
func (k *Key) StrictUints(delim string) ([]uint, error) {
|
|
||||||
return k.parseUints(k.Strings(delim), false, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input.
|
|
||||||
func (k *Key) StrictUint64s(delim string) ([]uint64, error) {
|
|
||||||
return k.parseUint64s(k.Strings(delim), false, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter
|
|
||||||
// or error on first invalid input.
|
|
||||||
func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) {
|
|
||||||
return k.parseTimesFormat(format, k.Strings(delim), false, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
|
|
||||||
// or error on first invalid input.
|
|
||||||
func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
|
|
||||||
return k.StrictTimesFormat(time.RFC3339, delim)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseFloat64s transforms strings to float64s.
|
|
||||||
func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) {
|
|
||||||
vals := make([]float64, 0, len(strs))
|
|
||||||
for _, str := range strs {
|
|
||||||
val, err := strconv.ParseFloat(str, 64)
|
|
||||||
if err != nil && returnOnInvalid {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err == nil || addInvalid {
|
|
||||||
vals = append(vals, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vals, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseInts transforms strings to ints.
|
|
||||||
func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
|
|
||||||
vals := make([]int, 0, len(strs))
|
|
||||||
for _, str := range strs {
|
|
||||||
val, err := strconv.Atoi(str)
|
|
||||||
if err != nil && returnOnInvalid {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err == nil || addInvalid {
|
|
||||||
vals = append(vals, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vals, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseInt64s transforms strings to int64s.
|
|
||||||
func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
|
|
||||||
vals := make([]int64, 0, len(strs))
|
|
||||||
for _, str := range strs {
|
|
||||||
val, err := strconv.ParseInt(str, 10, 64)
|
|
||||||
if err != nil && returnOnInvalid {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err == nil || addInvalid {
|
|
||||||
vals = append(vals, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vals, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseUints transforms strings to uints.
|
|
||||||
func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
|
|
||||||
vals := make([]uint, 0, len(strs))
|
|
||||||
for _, str := range strs {
|
|
||||||
val, err := strconv.ParseUint(str, 10, 0)
|
|
||||||
if err != nil && returnOnInvalid {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err == nil || addInvalid {
|
|
||||||
vals = append(vals, uint(val))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vals, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseUint64s transforms strings to uint64s.
|
|
||||||
func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
|
|
||||||
vals := make([]uint64, 0, len(strs))
|
|
||||||
for _, str := range strs {
|
|
||||||
val, err := strconv.ParseUint(str, 10, 64)
|
|
||||||
if err != nil && returnOnInvalid {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err == nil || addInvalid {
|
|
||||||
vals = append(vals, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vals, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseTimesFormat transforms strings to times in given format.
|
|
||||||
func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
|
|
||||||
vals := make([]time.Time, 0, len(strs))
|
|
||||||
for _, str := range strs {
|
|
||||||
val, err := time.Parse(format, str)
|
|
||||||
if err != nil && returnOnInvalid {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err == nil || addInvalid {
|
|
||||||
vals = append(vals, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vals, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetValue changes key value.
|
|
||||||
func (k *Key) SetValue(v string) {
|
|
||||||
if k.s.f.BlockMode {
|
|
||||||
k.s.f.lock.Lock()
|
|
||||||
defer k.s.f.lock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
k.value = v
|
|
||||||
k.s.keysHash[k.name] = v
|
|
||||||
}
|
|
573
vendor/src/github.com/go-ini/ini/key_test.go
vendored
573
vendor/src/github.com/go-ini/ini/key_test.go
vendored
|
@ -1,573 +0,0 @@
|
||||||
// Copyright 2014 Unknwon
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package ini
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_Key(t *testing.T) {
|
|
||||||
Convey("Test getting and setting values", t, func() {
|
|
||||||
cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
|
|
||||||
Convey("Get values in default section", func() {
|
|
||||||
sec := cfg.Section("")
|
|
||||||
So(sec, ShouldNotBeNil)
|
|
||||||
So(sec.Key("NAME").Value(), ShouldEqual, "ini")
|
|
||||||
So(sec.Key("NAME").String(), ShouldEqual, "ini")
|
|
||||||
So(sec.Key("NAME").Validate(func(in string) string {
|
|
||||||
return in
|
|
||||||
}), ShouldEqual, "ini")
|
|
||||||
So(sec.Key("NAME").Comment, ShouldEqual, "; Package name")
|
|
||||||
So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "gopkg.in/ini.v1")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get values in non-default section", func() {
|
|
||||||
sec := cfg.Section("author")
|
|
||||||
So(sec, ShouldNotBeNil)
|
|
||||||
So(sec.Key("NAME").String(), ShouldEqual, "Unknwon")
|
|
||||||
So(sec.Key("GITHUB").String(), ShouldEqual, "https://github.com/Unknwon")
|
|
||||||
|
|
||||||
sec = cfg.Section("package")
|
|
||||||
So(sec, ShouldNotBeNil)
|
|
||||||
So(sec.Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get auto-increment key names", func() {
|
|
||||||
keys := cfg.Section("features").Keys()
|
|
||||||
for i, k := range keys {
|
|
||||||
So(k.Name(), ShouldEqual, fmt.Sprintf("#%d", i+1))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get parent-keys that are available to the child section", func() {
|
|
||||||
parentKeys := cfg.Section("package.sub").ParentKeys()
|
|
||||||
for _, k := range parentKeys {
|
|
||||||
So(k.Name(), ShouldEqual, "CLONE_URL")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get overwrite value", func() {
|
|
||||||
So(cfg.Section("author").Key("E-MAIL").String(), ShouldEqual, "u@gogs.io")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get sections", func() {
|
|
||||||
sections := cfg.Sections()
|
|
||||||
for i, name := range []string{DEFAULT_SECTION, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "advance"} {
|
|
||||||
So(sections[i].Name(), ShouldEqual, name)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get parent section value", func() {
|
|
||||||
So(cfg.Section("package.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
|
|
||||||
So(cfg.Section("package.fake.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get multiple line value", func() {
|
|
||||||
So(cfg.Section("author").Key("BIO").String(), ShouldEqual, "Gopher.\nCoding addict.\nGood man.\n")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get values with type", func() {
|
|
||||||
sec := cfg.Section("types")
|
|
||||||
v1, err := sec.Key("BOOL").Bool()
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(v1, ShouldBeTrue)
|
|
||||||
|
|
||||||
v1, err = sec.Key("BOOL_FALSE").Bool()
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(v1, ShouldBeFalse)
|
|
||||||
|
|
||||||
v2, err := sec.Key("FLOAT64").Float64()
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(v2, ShouldEqual, 1.25)
|
|
||||||
|
|
||||||
v3, err := sec.Key("INT").Int()
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(v3, ShouldEqual, 10)
|
|
||||||
|
|
||||||
v4, err := sec.Key("INT").Int64()
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(v4, ShouldEqual, 10)
|
|
||||||
|
|
||||||
v5, err := sec.Key("UINT").Uint()
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(v5, ShouldEqual, 3)
|
|
||||||
|
|
||||||
v6, err := sec.Key("UINT").Uint64()
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(v6, ShouldEqual, 3)
|
|
||||||
|
|
||||||
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
v7, err := sec.Key("TIME").Time()
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(v7.String(), ShouldEqual, t.String())
|
|
||||||
|
|
||||||
Convey("Must get values with type", func() {
|
|
||||||
So(sec.Key("STRING").MustString("404"), ShouldEqual, "str")
|
|
||||||
So(sec.Key("BOOL").MustBool(), ShouldBeTrue)
|
|
||||||
So(sec.Key("FLOAT64").MustFloat64(), ShouldEqual, 1.25)
|
|
||||||
So(sec.Key("INT").MustInt(), ShouldEqual, 10)
|
|
||||||
So(sec.Key("INT").MustInt64(), ShouldEqual, 10)
|
|
||||||
So(sec.Key("UINT").MustUint(), ShouldEqual, 3)
|
|
||||||
So(sec.Key("UINT").MustUint64(), ShouldEqual, 3)
|
|
||||||
So(sec.Key("TIME").MustTime().String(), ShouldEqual, t.String())
|
|
||||||
|
|
||||||
dur, err := time.ParseDuration("2h45m")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(sec.Key("DURATION").MustDuration().Seconds(), ShouldEqual, dur.Seconds())
|
|
||||||
|
|
||||||
Convey("Must get values with default value", func() {
|
|
||||||
So(sec.Key("STRING_404").MustString("404"), ShouldEqual, "404")
|
|
||||||
So(sec.Key("BOOL_404").MustBool(true), ShouldBeTrue)
|
|
||||||
So(sec.Key("FLOAT64_404").MustFloat64(2.5), ShouldEqual, 2.5)
|
|
||||||
So(sec.Key("INT_404").MustInt(15), ShouldEqual, 15)
|
|
||||||
So(sec.Key("INT64_404").MustInt64(15), ShouldEqual, 15)
|
|
||||||
So(sec.Key("UINT_404").MustUint(6), ShouldEqual, 6)
|
|
||||||
So(sec.Key("UINT64_404").MustUint64(6), ShouldEqual, 6)
|
|
||||||
|
|
||||||
t, err := time.Parse(time.RFC3339, "2014-01-01T20:17:05Z")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(sec.Key("TIME_404").MustTime(t).String(), ShouldEqual, t.String())
|
|
||||||
|
|
||||||
So(sec.Key("DURATION_404").MustDuration(dur).Seconds(), ShouldEqual, dur.Seconds())
|
|
||||||
|
|
||||||
Convey("Must should set default as key value", func() {
|
|
||||||
So(sec.Key("STRING_404").String(), ShouldEqual, "404")
|
|
||||||
So(sec.Key("BOOL_404").String(), ShouldEqual, "true")
|
|
||||||
So(sec.Key("FLOAT64_404").String(), ShouldEqual, "2.5")
|
|
||||||
So(sec.Key("INT_404").String(), ShouldEqual, "15")
|
|
||||||
So(sec.Key("INT64_404").String(), ShouldEqual, "15")
|
|
||||||
So(sec.Key("UINT_404").String(), ShouldEqual, "6")
|
|
||||||
So(sec.Key("UINT64_404").String(), ShouldEqual, "6")
|
|
||||||
So(sec.Key("TIME_404").String(), ShouldEqual, "2014-01-01T20:17:05Z")
|
|
||||||
So(sec.Key("DURATION_404").String(), ShouldEqual, "2h45m0s")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get value with candidates", func() {
|
|
||||||
sec := cfg.Section("types")
|
|
||||||
So(sec.Key("STRING").In("", []string{"str", "arr", "types"}), ShouldEqual, "str")
|
|
||||||
So(sec.Key("FLOAT64").InFloat64(0, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
|
|
||||||
So(sec.Key("INT").InInt(0, []int{10, 20, 30}), ShouldEqual, 10)
|
|
||||||
So(sec.Key("INT").InInt64(0, []int64{10, 20, 30}), ShouldEqual, 10)
|
|
||||||
So(sec.Key("UINT").InUint(0, []uint{3, 6, 9}), ShouldEqual, 3)
|
|
||||||
So(sec.Key("UINT").InUint64(0, []uint64{3, 6, 9}), ShouldEqual, 3)
|
|
||||||
|
|
||||||
zt, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(sec.Key("TIME").InTime(zt, []time.Time{t, time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
|
|
||||||
|
|
||||||
Convey("Get value with candidates and default value", func() {
|
|
||||||
So(sec.Key("STRING_404").In("str", []string{"str", "arr", "types"}), ShouldEqual, "str")
|
|
||||||
So(sec.Key("FLOAT64_404").InFloat64(1.25, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
|
|
||||||
So(sec.Key("INT_404").InInt(10, []int{10, 20, 30}), ShouldEqual, 10)
|
|
||||||
So(sec.Key("INT64_404").InInt64(10, []int64{10, 20, 30}), ShouldEqual, 10)
|
|
||||||
So(sec.Key("UINT_404").InUint(3, []uint{3, 6, 9}), ShouldEqual, 3)
|
|
||||||
So(sec.Key("UINT_404").InUint64(3, []uint64{3, 6, 9}), ShouldEqual, 3)
|
|
||||||
So(sec.Key("TIME_404").InTime(t, []time.Time{time.Now(), time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get values in range", func() {
|
|
||||||
sec := cfg.Section("types")
|
|
||||||
So(sec.Key("FLOAT64").RangeFloat64(0, 1, 2), ShouldEqual, 1.25)
|
|
||||||
So(sec.Key("INT").RangeInt(0, 10, 20), ShouldEqual, 10)
|
|
||||||
So(sec.Key("INT").RangeInt64(0, 10, 20), ShouldEqual, 10)
|
|
||||||
|
|
||||||
minT, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
midT, err := time.Parse(time.RFC3339, "2013-01-01T01:00:00Z")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
maxT, err := time.Parse(time.RFC3339, "9999-01-01T01:00:00Z")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(sec.Key("TIME").RangeTime(t, minT, maxT).String(), ShouldEqual, t.String())
|
|
||||||
|
|
||||||
Convey("Get value in range with default value", func() {
|
|
||||||
So(sec.Key("FLOAT64").RangeFloat64(5, 0, 1), ShouldEqual, 5)
|
|
||||||
So(sec.Key("INT").RangeInt(7, 0, 5), ShouldEqual, 7)
|
|
||||||
So(sec.Key("INT").RangeInt64(7, 0, 5), ShouldEqual, 7)
|
|
||||||
So(sec.Key("TIME").RangeTime(t, minT, midT).String(), ShouldEqual, t.String())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get values into slice", func() {
|
|
||||||
sec := cfg.Section("array")
|
|
||||||
So(strings.Join(sec.Key("STRINGS").Strings(","), ","), ShouldEqual, "en,zh,de")
|
|
||||||
So(len(sec.Key("STRINGS_404").Strings(",")), ShouldEqual, 0)
|
|
||||||
|
|
||||||
vals1 := sec.Key("FLOAT64S").Float64s(",")
|
|
||||||
float64sEqual(vals1, 1.1, 2.2, 3.3)
|
|
||||||
|
|
||||||
vals2 := sec.Key("INTS").Ints(",")
|
|
||||||
intsEqual(vals2, 1, 2, 3)
|
|
||||||
|
|
||||||
vals3 := sec.Key("INTS").Int64s(",")
|
|
||||||
int64sEqual(vals3, 1, 2, 3)
|
|
||||||
|
|
||||||
vals4 := sec.Key("UINTS").Uints(",")
|
|
||||||
uintsEqual(vals4, 1, 2, 3)
|
|
||||||
|
|
||||||
vals5 := sec.Key("UINTS").Uint64s(",")
|
|
||||||
uint64sEqual(vals5, 1, 2, 3)
|
|
||||||
|
|
||||||
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
vals6 := sec.Key("TIMES").Times(",")
|
|
||||||
timesEqual(vals6, t, t, t)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get valid values into slice", func() {
|
|
||||||
sec := cfg.Section("array")
|
|
||||||
vals1 := sec.Key("FLOAT64S").ValidFloat64s(",")
|
|
||||||
float64sEqual(vals1, 1.1, 2.2, 3.3)
|
|
||||||
|
|
||||||
vals2 := sec.Key("INTS").ValidInts(",")
|
|
||||||
intsEqual(vals2, 1, 2, 3)
|
|
||||||
|
|
||||||
vals3 := sec.Key("INTS").ValidInt64s(",")
|
|
||||||
int64sEqual(vals3, 1, 2, 3)
|
|
||||||
|
|
||||||
vals4 := sec.Key("UINTS").ValidUints(",")
|
|
||||||
uintsEqual(vals4, 1, 2, 3)
|
|
||||||
|
|
||||||
vals5 := sec.Key("UINTS").ValidUint64s(",")
|
|
||||||
uint64sEqual(vals5, 1, 2, 3)
|
|
||||||
|
|
||||||
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
vals6 := sec.Key("TIMES").ValidTimes(",")
|
|
||||||
timesEqual(vals6, t, t, t)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get values one type into slice of another type", func() {
|
|
||||||
sec := cfg.Section("array")
|
|
||||||
vals1 := sec.Key("STRINGS").ValidFloat64s(",")
|
|
||||||
So(vals1, ShouldBeEmpty)
|
|
||||||
|
|
||||||
vals2 := sec.Key("STRINGS").ValidInts(",")
|
|
||||||
So(vals2, ShouldBeEmpty)
|
|
||||||
|
|
||||||
vals3 := sec.Key("STRINGS").ValidInt64s(",")
|
|
||||||
So(vals3, ShouldBeEmpty)
|
|
||||||
|
|
||||||
vals4 := sec.Key("STRINGS").ValidUints(",")
|
|
||||||
So(vals4, ShouldBeEmpty)
|
|
||||||
|
|
||||||
vals5 := sec.Key("STRINGS").ValidUint64s(",")
|
|
||||||
So(vals5, ShouldBeEmpty)
|
|
||||||
|
|
||||||
vals6 := sec.Key("STRINGS").ValidTimes(",")
|
|
||||||
So(vals6, ShouldBeEmpty)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get valid values into slice without errors", func() {
|
|
||||||
sec := cfg.Section("array")
|
|
||||||
vals1, err := sec.Key("FLOAT64S").StrictFloat64s(",")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
float64sEqual(vals1, 1.1, 2.2, 3.3)
|
|
||||||
|
|
||||||
vals2, err := sec.Key("INTS").StrictInts(",")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
intsEqual(vals2, 1, 2, 3)
|
|
||||||
|
|
||||||
vals3, err := sec.Key("INTS").StrictInt64s(",")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
int64sEqual(vals3, 1, 2, 3)
|
|
||||||
|
|
||||||
vals4, err := sec.Key("UINTS").StrictUints(",")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
uintsEqual(vals4, 1, 2, 3)
|
|
||||||
|
|
||||||
vals5, err := sec.Key("UINTS").StrictUint64s(",")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
uint64sEqual(vals5, 1, 2, 3)
|
|
||||||
|
|
||||||
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
vals6, err := sec.Key("TIMES").StrictTimes(",")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
timesEqual(vals6, t, t, t)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get invalid values into slice", func() {
|
|
||||||
sec := cfg.Section("array")
|
|
||||||
vals1, err := sec.Key("STRINGS").StrictFloat64s(",")
|
|
||||||
So(vals1, ShouldBeEmpty)
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
|
|
||||||
vals2, err := sec.Key("STRINGS").StrictInts(",")
|
|
||||||
So(vals2, ShouldBeEmpty)
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
|
|
||||||
vals3, err := sec.Key("STRINGS").StrictInt64s(",")
|
|
||||||
So(vals3, ShouldBeEmpty)
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
|
|
||||||
vals4, err := sec.Key("STRINGS").StrictUints(",")
|
|
||||||
So(vals4, ShouldBeEmpty)
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
|
|
||||||
vals5, err := sec.Key("STRINGS").StrictUint64s(",")
|
|
||||||
So(vals5, ShouldBeEmpty)
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
|
|
||||||
vals6, err := sec.Key("STRINGS").StrictTimes(",")
|
|
||||||
So(vals6, ShouldBeEmpty)
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get key hash", func() {
|
|
||||||
cfg.Section("").KeysHash()
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Set key value", func() {
|
|
||||||
k := cfg.Section("author").Key("NAME")
|
|
||||||
k.SetValue("无闻")
|
|
||||||
So(k.String(), ShouldEqual, "无闻")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get key strings", func() {
|
|
||||||
So(strings.Join(cfg.Section("types").KeyStrings(), ","), ShouldEqual, "STRING,BOOL,BOOL_FALSE,FLOAT64,INT,TIME,DURATION,UINT")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Delete a key", func() {
|
|
||||||
cfg.Section("package.sub").DeleteKey("UNUSED_KEY")
|
|
||||||
_, err := cfg.Section("package.sub").GetKey("UNUSED_KEY")
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Has Key (backwards compatible)", func() {
|
|
||||||
sec := cfg.Section("package.sub")
|
|
||||||
haskey1 := sec.Haskey("UNUSED_KEY")
|
|
||||||
haskey2 := sec.Haskey("CLONE_URL")
|
|
||||||
haskey3 := sec.Haskey("CLONE_URL_NO")
|
|
||||||
So(haskey1, ShouldBeTrue)
|
|
||||||
So(haskey2, ShouldBeTrue)
|
|
||||||
So(haskey3, ShouldBeFalse)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Has Key", func() {
|
|
||||||
sec := cfg.Section("package.sub")
|
|
||||||
haskey1 := sec.HasKey("UNUSED_KEY")
|
|
||||||
haskey2 := sec.HasKey("CLONE_URL")
|
|
||||||
haskey3 := sec.HasKey("CLONE_URL_NO")
|
|
||||||
So(haskey1, ShouldBeTrue)
|
|
||||||
So(haskey2, ShouldBeTrue)
|
|
||||||
So(haskey3, ShouldBeFalse)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Has Value", func() {
|
|
||||||
sec := cfg.Section("author")
|
|
||||||
hasvalue1 := sec.HasValue("Unknwon")
|
|
||||||
hasvalue2 := sec.HasValue("doc")
|
|
||||||
So(hasvalue1, ShouldBeTrue)
|
|
||||||
So(hasvalue2, ShouldBeFalse)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Test getting and setting bad values", t, func() {
|
|
||||||
cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
|
|
||||||
Convey("Create new key with empty name", func() {
|
|
||||||
k, err := cfg.Section("").NewKey("", "")
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
So(k, ShouldBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Create new section with empty name", func() {
|
|
||||||
s, err := cfg.NewSection("")
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
So(s, ShouldBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Create new sections with empty name", func() {
|
|
||||||
So(cfg.NewSections(""), ShouldNotBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Get section that not exists", func() {
|
|
||||||
s, err := cfg.GetSection("404")
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
So(s, ShouldBeNil)
|
|
||||||
|
|
||||||
s = cfg.Section("404")
|
|
||||||
So(s, ShouldNotBeNil)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Test key hash clone", t, func() {
|
|
||||||
cfg, err := Load([]byte(strings.Replace("network=tcp,addr=127.0.0.1:6379,db=4,pool_size=100,idle_timeout=180", ",", "\n", -1)))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
for _, v := range cfg.Section("").KeysHash() {
|
|
||||||
So(len(v), ShouldBeGreaterThan, 0)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Key has empty value", t, func() {
|
|
||||||
_conf := `key1=
|
|
||||||
key2= ; comment`
|
|
||||||
cfg, err := Load([]byte(_conf))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg.Section("").Key("key1").Value(), ShouldBeEmpty)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const _CONF_GIT_CONFIG = `
|
|
||||||
[remote "origin"]
|
|
||||||
url = https://github.com/Antergone/test1.git
|
|
||||||
url = https://github.com/Antergone/test2.git
|
|
||||||
`
|
|
||||||
|
|
||||||
func Test_Key_Shadows(t *testing.T) {
|
|
||||||
Convey("Shadows keys", t, func() {
|
|
||||||
Convey("Disable shadows", func() {
|
|
||||||
cfg, err := Load([]byte(_CONF_GIT_CONFIG))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test2.git")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Enable shadows", func() {
|
|
||||||
cfg, err := ShadowLoad([]byte(_CONF_GIT_CONFIG))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test1.git")
|
|
||||||
So(strings.Join(cfg.Section(`remote "origin"`).Key("url").ValueWithShadows(), " "), ShouldEqual,
|
|
||||||
"https://github.com/Antergone/test1.git https://github.com/Antergone/test2.git")
|
|
||||||
|
|
||||||
Convey("Save with shadows", func() {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_, err := cfg.WriteTo(&buf)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(buf.String(), ShouldEqual, `[remote "origin"]
|
|
||||||
url = https://github.com/Antergone/test1.git
|
|
||||||
url = https://github.com/Antergone/test2.git
|
|
||||||
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestFile(block bool) *File {
|
|
||||||
c, _ := Load([]byte(_CONF_DATA))
|
|
||||||
c.BlockMode = block
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func Benchmark_Key_Value(b *testing.B) {
|
|
||||||
c := newTestFile(true)
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
c.Section("").Key("NAME").Value()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Benchmark_Key_Value_NonBlock(b *testing.B) {
|
|
||||||
c := newTestFile(false)
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
c.Section("").Key("NAME").Value()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Benchmark_Key_Value_ViaSection(b *testing.B) {
|
|
||||||
c := newTestFile(true)
|
|
||||||
sec := c.Section("")
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
sec.Key("NAME").Value()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Benchmark_Key_Value_ViaSection_NonBlock(b *testing.B) {
|
|
||||||
c := newTestFile(false)
|
|
||||||
sec := c.Section("")
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
sec.Key("NAME").Value()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Benchmark_Key_Value_Direct(b *testing.B) {
|
|
||||||
c := newTestFile(true)
|
|
||||||
key := c.Section("").Key("NAME")
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
key.Value()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Benchmark_Key_Value_Direct_NonBlock(b *testing.B) {
|
|
||||||
c := newTestFile(false)
|
|
||||||
key := c.Section("").Key("NAME")
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
key.Value()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Benchmark_Key_String(b *testing.B) {
|
|
||||||
c := newTestFile(true)
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_ = c.Section("").Key("NAME").String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Benchmark_Key_String_NonBlock(b *testing.B) {
|
|
||||||
c := newTestFile(false)
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_ = c.Section("").Key("NAME").String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Benchmark_Key_String_ViaSection(b *testing.B) {
|
|
||||||
c := newTestFile(true)
|
|
||||||
sec := c.Section("")
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_ = sec.Key("NAME").String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Benchmark_Key_String_ViaSection_NonBlock(b *testing.B) {
|
|
||||||
c := newTestFile(false)
|
|
||||||
sec := c.Section("")
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_ = sec.Key("NAME").String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Benchmark_Key_SetValue(b *testing.B) {
|
|
||||||
c := newTestFile(true)
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
c.Section("").Key("NAME").SetValue("10")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Benchmark_Key_SetValue_VisSection(b *testing.B) {
|
|
||||||
c := newTestFile(true)
|
|
||||||
sec := c.Section("")
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
sec.Key("NAME").SetValue("10")
|
|
||||||
}
|
|
||||||
}
|
|
361
vendor/src/github.com/go-ini/ini/parser.go
vendored
361
vendor/src/github.com/go-ini/ini/parser.go
vendored
|
@ -1,361 +0,0 @@
|
||||||
// Copyright 2015 Unknwon
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package ini
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tokenType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
_TOKEN_INVALID tokenType = iota
|
|
||||||
_TOKEN_COMMENT
|
|
||||||
_TOKEN_SECTION
|
|
||||||
_TOKEN_KEY
|
|
||||||
)
|
|
||||||
|
|
||||||
type parser struct {
|
|
||||||
buf *bufio.Reader
|
|
||||||
isEOF bool
|
|
||||||
count int
|
|
||||||
comment *bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func newParser(r io.Reader) *parser {
|
|
||||||
return &parser{
|
|
||||||
buf: bufio.NewReader(r),
|
|
||||||
count: 1,
|
|
||||||
comment: &bytes.Buffer{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format.
|
|
||||||
// http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
|
|
||||||
func (p *parser) BOM() error {
|
|
||||||
mask, err := p.buf.Peek(2)
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
return err
|
|
||||||
} else if len(mask) < 2 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case mask[0] == 254 && mask[1] == 255:
|
|
||||||
fallthrough
|
|
||||||
case mask[0] == 255 && mask[1] == 254:
|
|
||||||
p.buf.Read(mask)
|
|
||||||
case mask[0] == 239 && mask[1] == 187:
|
|
||||||
mask, err := p.buf.Peek(3)
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
return err
|
|
||||||
} else if len(mask) < 3 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if mask[2] == 191 {
|
|
||||||
p.buf.Read(mask)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) readUntil(delim byte) ([]byte, error) {
|
|
||||||
data, err := p.buf.ReadBytes(delim)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
p.isEOF = true
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanComment(in []byte) ([]byte, bool) {
|
|
||||||
i := bytes.IndexAny(in, "#;")
|
|
||||||
if i == -1 {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return in[i:], true
|
|
||||||
}
|
|
||||||
|
|
||||||
func readKeyName(in []byte) (string, int, error) {
|
|
||||||
line := string(in)
|
|
||||||
|
|
||||||
// Check if key name surrounded by quotes.
|
|
||||||
var keyQuote string
|
|
||||||
if line[0] == '"' {
|
|
||||||
if len(line) > 6 && string(line[0:3]) == `"""` {
|
|
||||||
keyQuote = `"""`
|
|
||||||
} else {
|
|
||||||
keyQuote = `"`
|
|
||||||
}
|
|
||||||
} else if line[0] == '`' {
|
|
||||||
keyQuote = "`"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get out key name
|
|
||||||
endIdx := -1
|
|
||||||
if len(keyQuote) > 0 {
|
|
||||||
startIdx := len(keyQuote)
|
|
||||||
// FIXME: fail case -> """"""name"""=value
|
|
||||||
pos := strings.Index(line[startIdx:], keyQuote)
|
|
||||||
if pos == -1 {
|
|
||||||
return "", -1, fmt.Errorf("missing closing key quote: %s", line)
|
|
||||||
}
|
|
||||||
pos += startIdx
|
|
||||||
|
|
||||||
// Find key-value delimiter
|
|
||||||
i := strings.IndexAny(line[pos+startIdx:], "=:")
|
|
||||||
if i < 0 {
|
|
||||||
return "", -1, ErrDelimiterNotFound{line}
|
|
||||||
}
|
|
||||||
endIdx = pos + i
|
|
||||||
return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
endIdx = strings.IndexAny(line, "=:")
|
|
||||||
if endIdx < 0 {
|
|
||||||
return "", -1, ErrDelimiterNotFound{line}
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
|
|
||||||
for {
|
|
||||||
data, err := p.readUntil('\n')
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
next := string(data)
|
|
||||||
|
|
||||||
pos := strings.LastIndex(next, valQuote)
|
|
||||||
if pos > -1 {
|
|
||||||
val += next[:pos]
|
|
||||||
|
|
||||||
comment, has := cleanComment([]byte(next[pos:]))
|
|
||||||
if has {
|
|
||||||
p.comment.Write(bytes.TrimSpace(comment))
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
val += next
|
|
||||||
if p.isEOF {
|
|
||||||
return "", fmt.Errorf("missing closing key quote from '%s' to '%s'", line, next)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) readContinuationLines(val string) (string, error) {
|
|
||||||
for {
|
|
||||||
data, err := p.readUntil('\n')
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
next := strings.TrimSpace(string(data))
|
|
||||||
|
|
||||||
if len(next) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
val += next
|
|
||||||
if val[len(val)-1] != '\\' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
val = val[:len(val)-1]
|
|
||||||
}
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasSurroundedQuote check if and only if the first and last characters
|
|
||||||
// are quotes \" or \'.
|
|
||||||
// It returns false if any other parts also contain same kind of quotes.
|
|
||||||
func hasSurroundedQuote(in string, quote byte) bool {
|
|
||||||
return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote &&
|
|
||||||
strings.IndexByte(in[1:], quote) == len(in)-2
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) readValue(in []byte, ignoreContinuation, ignoreInlineComment bool) (string, error) {
|
|
||||||
line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
|
|
||||||
if len(line) == 0 {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var valQuote string
|
|
||||||
if len(line) > 3 && string(line[0:3]) == `"""` {
|
|
||||||
valQuote = `"""`
|
|
||||||
} else if line[0] == '`' {
|
|
||||||
valQuote = "`"
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(valQuote) > 0 {
|
|
||||||
startIdx := len(valQuote)
|
|
||||||
pos := strings.LastIndex(line[startIdx:], valQuote)
|
|
||||||
// Check for multi-line value
|
|
||||||
if pos == -1 {
|
|
||||||
return p.readMultilines(line, line[startIdx:], valQuote)
|
|
||||||
}
|
|
||||||
|
|
||||||
return line[startIdx : pos+startIdx], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Won't be able to reach here if value only contains whitespace
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
|
|
||||||
// Check continuation lines when desired
|
|
||||||
if !ignoreContinuation && line[len(line)-1] == '\\' {
|
|
||||||
return p.readContinuationLines(line[:len(line)-1])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if ignore inline comment
|
|
||||||
if !ignoreInlineComment {
|
|
||||||
i := strings.IndexAny(line, "#;")
|
|
||||||
if i > -1 {
|
|
||||||
p.comment.WriteString(line[i:])
|
|
||||||
line = strings.TrimSpace(line[:i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trim single quotes
|
|
||||||
if hasSurroundedQuote(line, '\'') ||
|
|
||||||
hasSurroundedQuote(line, '"') {
|
|
||||||
line = line[1 : len(line)-1]
|
|
||||||
}
|
|
||||||
return line, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse parses data through an io.Reader.
|
|
||||||
func (f *File) parse(reader io.Reader) (err error) {
|
|
||||||
p := newParser(reader)
|
|
||||||
if err = p.BOM(); err != nil {
|
|
||||||
return fmt.Errorf("BOM: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore error because default section name is never empty string.
|
|
||||||
section, _ := f.NewSection(DEFAULT_SECTION)
|
|
||||||
|
|
||||||
var line []byte
|
|
||||||
var inUnparseableSection bool
|
|
||||||
for !p.isEOF {
|
|
||||||
line, err = p.readUntil('\n')
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
|
|
||||||
if len(line) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comments
|
|
||||||
if line[0] == '#' || line[0] == ';' {
|
|
||||||
// Note: we do not care ending line break,
|
|
||||||
// it is needed for adding second line,
|
|
||||||
// so just clean it once at the end when set to value.
|
|
||||||
p.comment.Write(line)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Section
|
|
||||||
if line[0] == '[' {
|
|
||||||
// Read to the next ']' (TODO: support quoted strings)
|
|
||||||
// TODO(unknwon): use LastIndexByte when stop supporting Go1.4
|
|
||||||
closeIdx := bytes.LastIndex(line, []byte("]"))
|
|
||||||
if closeIdx == -1 {
|
|
||||||
return fmt.Errorf("unclosed section: %s", line)
|
|
||||||
}
|
|
||||||
|
|
||||||
name := string(line[1:closeIdx])
|
|
||||||
section, err = f.NewSection(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
comment, has := cleanComment(line[closeIdx+1:])
|
|
||||||
if has {
|
|
||||||
p.comment.Write(comment)
|
|
||||||
}
|
|
||||||
|
|
||||||
section.Comment = strings.TrimSpace(p.comment.String())
|
|
||||||
|
|
||||||
// Reset aotu-counter and comments
|
|
||||||
p.comment.Reset()
|
|
||||||
p.count = 1
|
|
||||||
|
|
||||||
inUnparseableSection = false
|
|
||||||
for i := range f.options.UnparseableSections {
|
|
||||||
if f.options.UnparseableSections[i] == name ||
|
|
||||||
(f.options.Insensitive && strings.ToLower(f.options.UnparseableSections[i]) == strings.ToLower(name)) {
|
|
||||||
inUnparseableSection = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if inUnparseableSection {
|
|
||||||
section.isRawSection = true
|
|
||||||
section.rawBody += string(line)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
kname, offset, err := readKeyName(line)
|
|
||||||
if err != nil {
|
|
||||||
// Treat as boolean key when desired, and whole line is key name.
|
|
||||||
if IsErrDelimiterNotFound(err) && f.options.AllowBooleanKeys {
|
|
||||||
kname, err := p.readValue(line, f.options.IgnoreContinuation, f.options.IgnoreInlineComment)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
key, err := section.NewBooleanKey(kname)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
key.Comment = strings.TrimSpace(p.comment.String())
|
|
||||||
p.comment.Reset()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto increment.
|
|
||||||
isAutoIncr := false
|
|
||||||
if kname == "-" {
|
|
||||||
isAutoIncr = true
|
|
||||||
kname = "#" + strconv.Itoa(p.count)
|
|
||||||
p.count++
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := p.readValue(line[offset:], f.options.IgnoreContinuation, f.options.IgnoreInlineComment)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := section.NewKey(kname, value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
key.isAutoIncrement = isAutoIncr
|
|
||||||
key.Comment = strings.TrimSpace(p.comment.String())
|
|
||||||
p.comment.Reset()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
42
vendor/src/github.com/go-ini/ini/parser_test.go
vendored
42
vendor/src/github.com/go-ini/ini/parser_test.go
vendored
|
@ -1,42 +0,0 @@
|
||||||
// Copyright 2016 Unknwon
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package ini
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_BOM(t *testing.T) {
|
|
||||||
Convey("Test handling BOM", t, func() {
|
|
||||||
Convey("UTF-8-BOM", func() {
|
|
||||||
cfg, err := Load("testdata/UTF-8-BOM.ini")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
|
|
||||||
So(cfg.Section("author").Key("E-MAIL").String(), ShouldEqual, "u@gogs.io")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("UTF-16-LE-BOM", func() {
|
|
||||||
cfg, err := Load("testdata/UTF-16-LE-BOM.ini")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("UTF-16-BE-BOM", func() {
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
248
vendor/src/github.com/go-ini/ini/section.go
vendored
248
vendor/src/github.com/go-ini/ini/section.go
vendored
|
@ -1,248 +0,0 @@
|
||||||
// Copyright 2014 Unknwon
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package ini
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Section represents a config section.
|
|
||||||
type Section struct {
|
|
||||||
f *File
|
|
||||||
Comment string
|
|
||||||
name string
|
|
||||||
keys map[string]*Key
|
|
||||||
keyList []string
|
|
||||||
keysHash map[string]string
|
|
||||||
|
|
||||||
isRawSection bool
|
|
||||||
rawBody string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSection(f *File, name string) *Section {
|
|
||||||
return &Section{
|
|
||||||
f: f,
|
|
||||||
name: name,
|
|
||||||
keys: make(map[string]*Key),
|
|
||||||
keyList: make([]string, 0, 10),
|
|
||||||
keysHash: make(map[string]string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns name of Section.
|
|
||||||
func (s *Section) Name() string {
|
|
||||||
return s.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Body returns rawBody of Section if the section was marked as unparseable.
|
|
||||||
// It still follows the other rules of the INI format surrounding leading/trailing whitespace.
|
|
||||||
func (s *Section) Body() string {
|
|
||||||
return strings.TrimSpace(s.rawBody)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewKey creates a new key to given section.
|
|
||||||
func (s *Section) NewKey(name, val string) (*Key, error) {
|
|
||||||
if len(name) == 0 {
|
|
||||||
return nil, errors.New("error creating new key: empty key name")
|
|
||||||
} else if s.f.options.Insensitive {
|
|
||||||
name = strings.ToLower(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.f.BlockMode {
|
|
||||||
s.f.lock.Lock()
|
|
||||||
defer s.f.lock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if inSlice(name, s.keyList) {
|
|
||||||
if s.f.options.AllowShadows {
|
|
||||||
if err := s.keys[name].addShadow(val); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
s.keys[name].value = val
|
|
||||||
}
|
|
||||||
return s.keys[name], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
s.keyList = append(s.keyList, name)
|
|
||||||
s.keys[name] = newKey(s, name, val)
|
|
||||||
s.keysHash[name] = val
|
|
||||||
return s.keys[name], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBooleanKey creates a new boolean type key to given section.
|
|
||||||
func (s *Section) NewBooleanKey(name string) (*Key, error) {
|
|
||||||
key, err := s.NewKey(name, "true")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
key.isBooleanType = true
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKey returns key in section by given name.
|
|
||||||
func (s *Section) GetKey(name string) (*Key, error) {
|
|
||||||
// FIXME: change to section level lock?
|
|
||||||
if s.f.BlockMode {
|
|
||||||
s.f.lock.RLock()
|
|
||||||
}
|
|
||||||
if s.f.options.Insensitive {
|
|
||||||
name = strings.ToLower(name)
|
|
||||||
}
|
|
||||||
key := s.keys[name]
|
|
||||||
if s.f.BlockMode {
|
|
||||||
s.f.lock.RUnlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if key == nil {
|
|
||||||
// Check if it is a child-section.
|
|
||||||
sname := s.name
|
|
||||||
for {
|
|
||||||
if i := strings.LastIndex(sname, "."); i > -1 {
|
|
||||||
sname = sname[:i]
|
|
||||||
sec, err := s.f.GetSection(sname)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return sec.GetKey(name)
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("error when getting key of section '%s': key '%s' not exists", s.name, name)
|
|
||||||
}
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasKey returns true if section contains a key with given name.
|
|
||||||
func (s *Section) HasKey(name string) bool {
|
|
||||||
key, _ := s.GetKey(name)
|
|
||||||
return key != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Haskey is a backwards-compatible name for HasKey.
|
|
||||||
func (s *Section) Haskey(name string) bool {
|
|
||||||
return s.HasKey(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasValue returns true if section contains given raw value.
|
|
||||||
func (s *Section) HasValue(value string) bool {
|
|
||||||
if s.f.BlockMode {
|
|
||||||
s.f.lock.RLock()
|
|
||||||
defer s.f.lock.RUnlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, k := range s.keys {
|
|
||||||
if value == k.value {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key assumes named Key exists in section and returns a zero-value when not.
|
|
||||||
func (s *Section) Key(name string) *Key {
|
|
||||||
key, err := s.GetKey(name)
|
|
||||||
if err != nil {
|
|
||||||
// It's OK here because the only possible error is empty key name,
|
|
||||||
// but if it's empty, this piece of code won't be executed.
|
|
||||||
key, _ = s.NewKey(name, "")
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keys returns list of keys of section.
|
|
||||||
func (s *Section) Keys() []*Key {
|
|
||||||
keys := make([]*Key, len(s.keyList))
|
|
||||||
for i := range s.keyList {
|
|
||||||
keys[i] = s.Key(s.keyList[i])
|
|
||||||
}
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParentKeys returns list of keys of parent section.
|
|
||||||
func (s *Section) ParentKeys() []*Key {
|
|
||||||
var parentKeys []*Key
|
|
||||||
sname := s.name
|
|
||||||
for {
|
|
||||||
if i := strings.LastIndex(sname, "."); i > -1 {
|
|
||||||
sname = sname[:i]
|
|
||||||
sec, err := s.f.GetSection(sname)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
parentKeys = append(parentKeys, sec.Keys()...)
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return parentKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyStrings returns list of key names of section.
|
|
||||||
func (s *Section) KeyStrings() []string {
|
|
||||||
list := make([]string, len(s.keyList))
|
|
||||||
copy(list, s.keyList)
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeysHash returns keys hash consisting of names and values.
|
|
||||||
func (s *Section) KeysHash() map[string]string {
|
|
||||||
if s.f.BlockMode {
|
|
||||||
s.f.lock.RLock()
|
|
||||||
defer s.f.lock.RUnlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
hash := map[string]string{}
|
|
||||||
for key, value := range s.keysHash {
|
|
||||||
hash[key] = value
|
|
||||||
}
|
|
||||||
return hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteKey deletes a key from section.
|
|
||||||
func (s *Section) DeleteKey(name string) {
|
|
||||||
if s.f.BlockMode {
|
|
||||||
s.f.lock.Lock()
|
|
||||||
defer s.f.lock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, k := range s.keyList {
|
|
||||||
if k == name {
|
|
||||||
s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
|
|
||||||
delete(s.keys, name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChildSections returns a list of child sections of current section.
|
|
||||||
// For example, "[parent.child1]" and "[parent.child12]" are child sections
|
|
||||||
// of section "[parent]".
|
|
||||||
func (s *Section) ChildSections() []*Section {
|
|
||||||
prefix := s.name + "."
|
|
||||||
children := make([]*Section, 0, 3)
|
|
||||||
for _, name := range s.f.sectionList {
|
|
||||||
if strings.HasPrefix(name, prefix) {
|
|
||||||
children = append(children, s.f.sections[name])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return children
|
|
||||||
}
|
|
75
vendor/src/github.com/go-ini/ini/section_test.go
vendored
75
vendor/src/github.com/go-ini/ini/section_test.go
vendored
|
@ -1,75 +0,0 @@
|
||||||
// Copyright 2014 Unknwon
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package ini
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_Section(t *testing.T) {
|
|
||||||
Convey("Test CRD sections", t, func() {
|
|
||||||
cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
|
|
||||||
Convey("Get section strings", func() {
|
|
||||||
So(strings.Join(cfg.SectionStrings(), ","), ShouldEqual, "DEFAULT,author,package,package.sub,features,types,array,note,comments,advance")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Delete a section", func() {
|
|
||||||
cfg.DeleteSection("")
|
|
||||||
So(cfg.SectionStrings()[0], ShouldNotEqual, DEFAULT_SECTION)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Create new sections", func() {
|
|
||||||
cfg.NewSections("test", "test2")
|
|
||||||
_, err := cfg.GetSection("test")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
_, err = cfg.GetSection("test2")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_SectionRaw(t *testing.T) {
|
|
||||||
Convey("Test section raw string", t, func() {
|
|
||||||
cfg, err := LoadSources(
|
|
||||||
LoadOptions{
|
|
||||||
Insensitive: true,
|
|
||||||
UnparseableSections: []string{"core_lesson", "comments"},
|
|
||||||
},
|
|
||||||
"testdata/aicc.ini")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
|
|
||||||
Convey("Get section strings", func() {
|
|
||||||
So(strings.Join(cfg.SectionStrings(), ","), ShouldEqual, "DEFAULT,core,core_lesson,comments")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Validate non-raw section", func() {
|
|
||||||
val, err := cfg.Section("core").GetKey("lesson_status")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(val.String(), ShouldEqual, "C")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Validate raw section", func() {
|
|
||||||
So(cfg.Section("core_lesson").Body(), ShouldEqual, `my lesson state data – 1111111111111111111000000000000000001110000
|
|
||||||
111111111111111111100000000000111000000000 – end my lesson state data`)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
500
vendor/src/github.com/go-ini/ini/struct.go
vendored
500
vendor/src/github.com/go-ini/ini/struct.go
vendored
|
@ -1,500 +0,0 @@
|
||||||
// Copyright 2014 Unknwon
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package ini
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NameMapper represents a ini tag name mapper.
|
|
||||||
type NameMapper func(string) string
|
|
||||||
|
|
||||||
// Built-in name getters.
|
|
||||||
var (
|
|
||||||
// AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE.
|
|
||||||
AllCapsUnderscore NameMapper = func(raw string) string {
|
|
||||||
newstr := make([]rune, 0, len(raw))
|
|
||||||
for i, chr := range raw {
|
|
||||||
if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
|
|
||||||
if i > 0 {
|
|
||||||
newstr = append(newstr, '_')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newstr = append(newstr, unicode.ToUpper(chr))
|
|
||||||
}
|
|
||||||
return string(newstr)
|
|
||||||
}
|
|
||||||
// TitleUnderscore converts to format title_underscore.
|
|
||||||
TitleUnderscore NameMapper = func(raw string) string {
|
|
||||||
newstr := make([]rune, 0, len(raw))
|
|
||||||
for i, chr := range raw {
|
|
||||||
if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
|
|
||||||
if i > 0 {
|
|
||||||
newstr = append(newstr, '_')
|
|
||||||
}
|
|
||||||
chr -= ('A' - 'a')
|
|
||||||
}
|
|
||||||
newstr = append(newstr, chr)
|
|
||||||
}
|
|
||||||
return string(newstr)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Section) parseFieldName(raw, actual string) string {
|
|
||||||
if len(actual) > 0 {
|
|
||||||
return actual
|
|
||||||
}
|
|
||||||
if s.f.NameMapper != nil {
|
|
||||||
return s.f.NameMapper(raw)
|
|
||||||
}
|
|
||||||
return raw
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDelim(actual string) string {
|
|
||||||
if len(actual) > 0 {
|
|
||||||
return actual
|
|
||||||
}
|
|
||||||
return ","
|
|
||||||
}
|
|
||||||
|
|
||||||
var reflectTime = reflect.TypeOf(time.Now()).Kind()
|
|
||||||
|
|
||||||
// setSliceWithProperType sets proper values to slice based on its type.
|
|
||||||
func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
|
|
||||||
var strs []string
|
|
||||||
if allowShadow {
|
|
||||||
strs = key.StringsWithShadows(delim)
|
|
||||||
} else {
|
|
||||||
strs = key.Strings(delim)
|
|
||||||
}
|
|
||||||
|
|
||||||
numVals := len(strs)
|
|
||||||
if numVals == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var vals interface{}
|
|
||||||
var err error
|
|
||||||
|
|
||||||
sliceOf := field.Type().Elem().Kind()
|
|
||||||
switch sliceOf {
|
|
||||||
case reflect.String:
|
|
||||||
vals = strs
|
|
||||||
case reflect.Int:
|
|
||||||
vals, err = key.parseInts(strs, true, false)
|
|
||||||
case reflect.Int64:
|
|
||||||
vals, err = key.parseInt64s(strs, true, false)
|
|
||||||
case reflect.Uint:
|
|
||||||
vals, err = key.parseUints(strs, true, false)
|
|
||||||
case reflect.Uint64:
|
|
||||||
vals, err = key.parseUint64s(strs, true, false)
|
|
||||||
case reflect.Float64:
|
|
||||||
vals, err = key.parseFloat64s(strs, true, false)
|
|
||||||
case reflectTime:
|
|
||||||
vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported type '[]%s'", sliceOf)
|
|
||||||
}
|
|
||||||
if isStrict {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
slice := reflect.MakeSlice(field.Type(), numVals, numVals)
|
|
||||||
for i := 0; i < numVals; i++ {
|
|
||||||
switch sliceOf {
|
|
||||||
case reflect.String:
|
|
||||||
slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i]))
|
|
||||||
case reflect.Int:
|
|
||||||
slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i]))
|
|
||||||
case reflect.Int64:
|
|
||||||
slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i]))
|
|
||||||
case reflect.Uint:
|
|
||||||
slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i]))
|
|
||||||
case reflect.Uint64:
|
|
||||||
slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i]))
|
|
||||||
case reflect.Float64:
|
|
||||||
slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i]))
|
|
||||||
case reflectTime:
|
|
||||||
slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
field.Set(slice)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapStrictError(err error, isStrict bool) error {
|
|
||||||
if isStrict {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// setWithProperType sets proper value to field based on its type,
|
|
||||||
// but it does not return error for failing parsing,
|
|
||||||
// because we want to use default value that is already assigned to strcut.
|
|
||||||
func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
|
|
||||||
switch t.Kind() {
|
|
||||||
case reflect.String:
|
|
||||||
if len(key.String()) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
field.SetString(key.String())
|
|
||||||
case reflect.Bool:
|
|
||||||
boolVal, err := key.Bool()
|
|
||||||
if err != nil {
|
|
||||||
return wrapStrictError(err, isStrict)
|
|
||||||
}
|
|
||||||
field.SetBool(boolVal)
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
durationVal, err := key.Duration()
|
|
||||||
// Skip zero value
|
|
||||||
if err == nil && int(durationVal) > 0 {
|
|
||||||
field.Set(reflect.ValueOf(durationVal))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
intVal, err := key.Int64()
|
|
||||||
if err != nil {
|
|
||||||
return wrapStrictError(err, isStrict)
|
|
||||||
}
|
|
||||||
field.SetInt(intVal)
|
|
||||||
// byte is an alias for uint8, so supporting uint8 breaks support for byte
|
|
||||||
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
durationVal, err := key.Duration()
|
|
||||||
// Skip zero value
|
|
||||||
if err == nil && int(durationVal) > 0 {
|
|
||||||
field.Set(reflect.ValueOf(durationVal))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
uintVal, err := key.Uint64()
|
|
||||||
if err != nil {
|
|
||||||
return wrapStrictError(err, isStrict)
|
|
||||||
}
|
|
||||||
field.SetUint(uintVal)
|
|
||||||
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
floatVal, err := key.Float64()
|
|
||||||
if err != nil {
|
|
||||||
return wrapStrictError(err, isStrict)
|
|
||||||
}
|
|
||||||
field.SetFloat(floatVal)
|
|
||||||
case reflectTime:
|
|
||||||
timeVal, err := key.Time()
|
|
||||||
if err != nil {
|
|
||||||
return wrapStrictError(err, isStrict)
|
|
||||||
}
|
|
||||||
field.Set(reflect.ValueOf(timeVal))
|
|
||||||
case reflect.Slice:
|
|
||||||
return setSliceWithProperType(key, field, delim, allowShadow, isStrict)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported type '%s'", t)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool) {
|
|
||||||
opts := strings.SplitN(tag, ",", 3)
|
|
||||||
rawName = opts[0]
|
|
||||||
if len(opts) > 1 {
|
|
||||||
omitEmpty = opts[1] == "omitempty"
|
|
||||||
}
|
|
||||||
if len(opts) > 2 {
|
|
||||||
allowShadow = opts[2] == "allowshadow"
|
|
||||||
}
|
|
||||||
return rawName, omitEmpty, allowShadow
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Section) mapTo(val reflect.Value, isStrict bool) error {
|
|
||||||
if val.Kind() == reflect.Ptr {
|
|
||||||
val = val.Elem()
|
|
||||||
}
|
|
||||||
typ := val.Type()
|
|
||||||
|
|
||||||
for i := 0; i < typ.NumField(); i++ {
|
|
||||||
field := val.Field(i)
|
|
||||||
tpField := typ.Field(i)
|
|
||||||
|
|
||||||
tag := tpField.Tag.Get("ini")
|
|
||||||
if tag == "-" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
rawName, _, allowShadow := parseTagOptions(tag)
|
|
||||||
fieldName := s.parseFieldName(tpField.Name, rawName)
|
|
||||||
if len(fieldName) == 0 || !field.CanSet() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
isAnonymous := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
|
|
||||||
isStruct := tpField.Type.Kind() == reflect.Struct
|
|
||||||
if isAnonymous {
|
|
||||||
field.Set(reflect.New(tpField.Type.Elem()))
|
|
||||||
}
|
|
||||||
|
|
||||||
if isAnonymous || isStruct {
|
|
||||||
if sec, err := s.f.GetSection(fieldName); err == nil {
|
|
||||||
if err = sec.mapTo(field, isStrict); err != nil {
|
|
||||||
return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if key, err := s.GetKey(fieldName); err == nil {
|
|
||||||
delim := parseDelim(tpField.Tag.Get("delim"))
|
|
||||||
if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil {
|
|
||||||
return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MapTo maps section to given struct.
|
|
||||||
func (s *Section) MapTo(v interface{}) error {
|
|
||||||
typ := reflect.TypeOf(v)
|
|
||||||
val := reflect.ValueOf(v)
|
|
||||||
if typ.Kind() == reflect.Ptr {
|
|
||||||
typ = typ.Elem()
|
|
||||||
val = val.Elem()
|
|
||||||
} else {
|
|
||||||
return errors.New("cannot map to non-pointer struct")
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.mapTo(val, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MapTo maps section to given struct in strict mode,
|
|
||||||
// which returns all possible error including value parsing error.
|
|
||||||
func (s *Section) StrictMapTo(v interface{}) error {
|
|
||||||
typ := reflect.TypeOf(v)
|
|
||||||
val := reflect.ValueOf(v)
|
|
||||||
if typ.Kind() == reflect.Ptr {
|
|
||||||
typ = typ.Elem()
|
|
||||||
val = val.Elem()
|
|
||||||
} else {
|
|
||||||
return errors.New("cannot map to non-pointer struct")
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.mapTo(val, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MapTo maps file to given struct.
|
|
||||||
func (f *File) MapTo(v interface{}) error {
|
|
||||||
return f.Section("").MapTo(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MapTo maps file to given struct in strict mode,
|
|
||||||
// which returns all possible error including value parsing error.
|
|
||||||
func (f *File) StrictMapTo(v interface{}) error {
|
|
||||||
return f.Section("").StrictMapTo(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MapTo maps data sources to given struct with name mapper.
|
|
||||||
func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
|
|
||||||
cfg, err := Load(source, others...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cfg.NameMapper = mapper
|
|
||||||
return cfg.MapTo(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrictMapToWithMapper maps data sources to given struct with name mapper in strict mode,
|
|
||||||
// which returns all possible error including value parsing error.
|
|
||||||
func StrictMapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
|
|
||||||
cfg, err := Load(source, others...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cfg.NameMapper = mapper
|
|
||||||
return cfg.StrictMapTo(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MapTo maps data sources to given struct.
|
|
||||||
func MapTo(v, source interface{}, others ...interface{}) error {
|
|
||||||
return MapToWithMapper(v, nil, source, others...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrictMapTo maps data sources to given struct in strict mode,
|
|
||||||
// which returns all possible error including value parsing error.
|
|
||||||
func StrictMapTo(v, source interface{}, others ...interface{}) error {
|
|
||||||
return StrictMapToWithMapper(v, nil, source, others...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// reflectSliceWithProperType does the opposite thing as setSliceWithProperType.
|
|
||||||
func reflectSliceWithProperType(key *Key, field reflect.Value, delim string) error {
|
|
||||||
slice := field.Slice(0, field.Len())
|
|
||||||
if field.Len() == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
sliceOf := field.Type().Elem().Kind()
|
|
||||||
for i := 0; i < field.Len(); i++ {
|
|
||||||
switch sliceOf {
|
|
||||||
case reflect.String:
|
|
||||||
buf.WriteString(slice.Index(i).String())
|
|
||||||
case reflect.Int, reflect.Int64:
|
|
||||||
buf.WriteString(fmt.Sprint(slice.Index(i).Int()))
|
|
||||||
case reflect.Uint, reflect.Uint64:
|
|
||||||
buf.WriteString(fmt.Sprint(slice.Index(i).Uint()))
|
|
||||||
case reflect.Float64:
|
|
||||||
buf.WriteString(fmt.Sprint(slice.Index(i).Float()))
|
|
||||||
case reflectTime:
|
|
||||||
buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported type '[]%s'", sliceOf)
|
|
||||||
}
|
|
||||||
buf.WriteString(delim)
|
|
||||||
}
|
|
||||||
key.SetValue(buf.String()[:buf.Len()-1])
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// reflectWithProperType does the opposite thing as setWithProperType.
|
|
||||||
func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string) error {
|
|
||||||
switch t.Kind() {
|
|
||||||
case reflect.String:
|
|
||||||
key.SetValue(field.String())
|
|
||||||
case reflect.Bool:
|
|
||||||
key.SetValue(fmt.Sprint(field.Bool()))
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
key.SetValue(fmt.Sprint(field.Int()))
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
key.SetValue(fmt.Sprint(field.Uint()))
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
key.SetValue(fmt.Sprint(field.Float()))
|
|
||||||
case reflectTime:
|
|
||||||
key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339)))
|
|
||||||
case reflect.Slice:
|
|
||||||
return reflectSliceWithProperType(key, field, delim)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported type '%s'", t)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CR: copied from encoding/json/encode.go with modifications of time.Time support.
|
|
||||||
// TODO: add more test coverage.
|
|
||||||
func isEmptyValue(v reflect.Value) bool {
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
|
||||||
return v.Len() == 0
|
|
||||||
case reflect.Bool:
|
|
||||||
return !v.Bool()
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return v.Int() == 0
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
return v.Uint() == 0
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return v.Float() == 0
|
|
||||||
case reflect.Interface, reflect.Ptr:
|
|
||||||
return v.IsNil()
|
|
||||||
case reflectTime:
|
|
||||||
t, ok := v.Interface().(time.Time)
|
|
||||||
return ok && t.IsZero()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Section) reflectFrom(val reflect.Value) error {
|
|
||||||
if val.Kind() == reflect.Ptr {
|
|
||||||
val = val.Elem()
|
|
||||||
}
|
|
||||||
typ := val.Type()
|
|
||||||
|
|
||||||
for i := 0; i < typ.NumField(); i++ {
|
|
||||||
field := val.Field(i)
|
|
||||||
tpField := typ.Field(i)
|
|
||||||
|
|
||||||
tag := tpField.Tag.Get("ini")
|
|
||||||
if tag == "-" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := strings.SplitN(tag, ",", 2)
|
|
||||||
if len(opts) == 2 && opts[1] == "omitempty" && isEmptyValue(field) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldName := s.parseFieldName(tpField.Name, opts[0])
|
|
||||||
if len(fieldName) == 0 || !field.CanSet() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous) ||
|
|
||||||
(tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
|
|
||||||
// Note: The only error here is section doesn't exist.
|
|
||||||
sec, err := s.f.GetSection(fieldName)
|
|
||||||
if err != nil {
|
|
||||||
// Note: fieldName can never be empty here, ignore error.
|
|
||||||
sec, _ = s.f.NewSection(fieldName)
|
|
||||||
}
|
|
||||||
if err = sec.reflectFrom(field); err != nil {
|
|
||||||
return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: Same reason as secion.
|
|
||||||
key, err := s.GetKey(fieldName)
|
|
||||||
if err != nil {
|
|
||||||
key, _ = s.NewKey(fieldName, "")
|
|
||||||
}
|
|
||||||
if err = reflectWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
|
|
||||||
return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReflectFrom reflects secion from given struct.
|
|
||||||
func (s *Section) ReflectFrom(v interface{}) error {
|
|
||||||
typ := reflect.TypeOf(v)
|
|
||||||
val := reflect.ValueOf(v)
|
|
||||||
if typ.Kind() == reflect.Ptr {
|
|
||||||
typ = typ.Elem()
|
|
||||||
val = val.Elem()
|
|
||||||
} else {
|
|
||||||
return errors.New("cannot reflect from non-pointer struct")
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.reflectFrom(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReflectFrom reflects file from given struct.
|
|
||||||
func (f *File) ReflectFrom(v interface{}) error {
|
|
||||||
return f.Section("").ReflectFrom(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReflectFrom reflects data sources from given struct with name mapper.
|
|
||||||
func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error {
|
|
||||||
cfg.NameMapper = mapper
|
|
||||||
return cfg.ReflectFrom(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReflectFrom reflects data sources from given struct.
|
|
||||||
func ReflectFrom(cfg *File, v interface{}) error {
|
|
||||||
return ReflectFromWithMapper(cfg, v, nil)
|
|
||||||
}
|
|
352
vendor/src/github.com/go-ini/ini/struct_test.go
vendored
352
vendor/src/github.com/go-ini/ini/struct_test.go
vendored
|
@ -1,352 +0,0 @@
|
||||||
// Copyright 2014 Unknwon
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package ini
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testNested struct {
|
|
||||||
Cities []string `delim:"|"`
|
|
||||||
Visits []time.Time
|
|
||||||
Years []int
|
|
||||||
Numbers []int64
|
|
||||||
Ages []uint
|
|
||||||
Populations []uint64
|
|
||||||
Coordinates []float64
|
|
||||||
Note string
|
|
||||||
Unused int `ini:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type testEmbeded struct {
|
|
||||||
GPA float64
|
|
||||||
}
|
|
||||||
|
|
||||||
type testStruct struct {
|
|
||||||
Name string `ini:"NAME"`
|
|
||||||
Age int
|
|
||||||
Male bool
|
|
||||||
Money float64
|
|
||||||
Born time.Time
|
|
||||||
Time time.Duration `ini:"Duration"`
|
|
||||||
Others testNested
|
|
||||||
*testEmbeded `ini:"grade"`
|
|
||||||
Unused int `ini:"-"`
|
|
||||||
Unsigned uint
|
|
||||||
Omitted bool `ini:"omitthis,omitempty"`
|
|
||||||
Shadows []string `ini:",,allowshadow"`
|
|
||||||
ShadowInts []int `ini:"Shadows,,allowshadow"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const _CONF_DATA_STRUCT = `
|
|
||||||
NAME = Unknwon
|
|
||||||
Age = 21
|
|
||||||
Male = true
|
|
||||||
Money = 1.25
|
|
||||||
Born = 1993-10-07T20:17:05Z
|
|
||||||
Duration = 2h45m
|
|
||||||
Unsigned = 3
|
|
||||||
omitthis = true
|
|
||||||
Shadows = 1, 2
|
|
||||||
Shadows = 3, 4
|
|
||||||
|
|
||||||
[Others]
|
|
||||||
Cities = HangZhou|Boston
|
|
||||||
Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
|
|
||||||
Years = 1993,1994
|
|
||||||
Numbers = 10010,10086
|
|
||||||
Ages = 18,19
|
|
||||||
Populations = 12345678,98765432
|
|
||||||
Coordinates = 192.168,10.11
|
|
||||||
Note = Hello world!
|
|
||||||
|
|
||||||
[grade]
|
|
||||||
GPA = 2.8
|
|
||||||
|
|
||||||
[foo.bar]
|
|
||||||
Here = there
|
|
||||||
When = then
|
|
||||||
`
|
|
||||||
|
|
||||||
type unsupport struct {
|
|
||||||
Byte byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type unsupport2 struct {
|
|
||||||
Others struct {
|
|
||||||
Cities byte
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type unsupport3 struct {
|
|
||||||
Cities byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type unsupport4 struct {
|
|
||||||
*unsupport3 `ini:"Others"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type defaultValue struct {
|
|
||||||
Name string
|
|
||||||
Age int
|
|
||||||
Male bool
|
|
||||||
Money float64
|
|
||||||
Born time.Time
|
|
||||||
Cities []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type fooBar struct {
|
|
||||||
Here, When string
|
|
||||||
}
|
|
||||||
|
|
||||||
const _INVALID_DATA_CONF_STRUCT = `
|
|
||||||
Name =
|
|
||||||
Age = age
|
|
||||||
Male = 123
|
|
||||||
Money = money
|
|
||||||
Born = nil
|
|
||||||
Cities =
|
|
||||||
`
|
|
||||||
|
|
||||||
func Test_Struct(t *testing.T) {
|
|
||||||
Convey("Map to struct", t, func() {
|
|
||||||
Convey("Map file to struct", func() {
|
|
||||||
ts := new(testStruct)
|
|
||||||
So(MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
|
|
||||||
|
|
||||||
So(ts.Name, ShouldEqual, "Unknwon")
|
|
||||||
So(ts.Age, ShouldEqual, 21)
|
|
||||||
So(ts.Male, ShouldBeTrue)
|
|
||||||
So(ts.Money, ShouldEqual, 1.25)
|
|
||||||
So(ts.Unsigned, ShouldEqual, 3)
|
|
||||||
|
|
||||||
t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(ts.Born.String(), ShouldEqual, t.String())
|
|
||||||
|
|
||||||
dur, err := time.ParseDuration("2h45m")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(ts.Time.Seconds(), ShouldEqual, dur.Seconds())
|
|
||||||
|
|
||||||
So(strings.Join(ts.Others.Cities, ","), ShouldEqual, "HangZhou,Boston")
|
|
||||||
So(ts.Others.Visits[0].String(), ShouldEqual, t.String())
|
|
||||||
So(fmt.Sprint(ts.Others.Years), ShouldEqual, "[1993 1994]")
|
|
||||||
So(fmt.Sprint(ts.Others.Numbers), ShouldEqual, "[10010 10086]")
|
|
||||||
So(fmt.Sprint(ts.Others.Ages), ShouldEqual, "[18 19]")
|
|
||||||
So(fmt.Sprint(ts.Others.Populations), ShouldEqual, "[12345678 98765432]")
|
|
||||||
So(fmt.Sprint(ts.Others.Coordinates), ShouldEqual, "[192.168 10.11]")
|
|
||||||
So(ts.Others.Note, ShouldEqual, "Hello world!")
|
|
||||||
So(ts.testEmbeded.GPA, ShouldEqual, 2.8)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Map section to struct", func() {
|
|
||||||
foobar := new(fooBar)
|
|
||||||
f, err := Load([]byte(_CONF_DATA_STRUCT))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
So(f.Section("foo.bar").MapTo(foobar), ShouldBeNil)
|
|
||||||
So(foobar.Here, ShouldEqual, "there")
|
|
||||||
So(foobar.When, ShouldEqual, "then")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Map to non-pointer struct", func() {
|
|
||||||
cfg, err := Load([]byte(_CONF_DATA_STRUCT))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
|
|
||||||
So(cfg.MapTo(testStruct{}), ShouldNotBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Map to unsupported type", func() {
|
|
||||||
cfg, err := Load([]byte(_CONF_DATA_STRUCT))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
|
|
||||||
cfg.NameMapper = func(raw string) string {
|
|
||||||
if raw == "Byte" {
|
|
||||||
return "NAME"
|
|
||||||
}
|
|
||||||
return raw
|
|
||||||
}
|
|
||||||
So(cfg.MapTo(&unsupport{}), ShouldNotBeNil)
|
|
||||||
So(cfg.MapTo(&unsupport2{}), ShouldNotBeNil)
|
|
||||||
So(cfg.MapTo(&unsupport4{}), ShouldNotBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Map to omitempty field", func() {
|
|
||||||
ts := new(testStruct)
|
|
||||||
So(MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
|
|
||||||
|
|
||||||
So(ts.Omitted, ShouldEqual, true)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Map with shadows", func() {
|
|
||||||
cfg, err := LoadSources(LoadOptions{AllowShadows: true}, []byte(_CONF_DATA_STRUCT))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
ts := new(testStruct)
|
|
||||||
So(cfg.MapTo(ts), ShouldBeNil)
|
|
||||||
|
|
||||||
So(strings.Join(ts.Shadows, " "), ShouldEqual, "1 2 3 4")
|
|
||||||
So(fmt.Sprintf("%v", ts.ShadowInts), ShouldEqual, "[1 2 3 4]")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Map from invalid data source", func() {
|
|
||||||
So(MapTo(&testStruct{}, "hi"), ShouldNotBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Map to wrong types and gain default values", func() {
|
|
||||||
cfg, err := Load([]byte(_INVALID_DATA_CONF_STRUCT))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
dv := &defaultValue{"Joe", 10, true, 1.25, t, []string{"HangZhou", "Boston"}}
|
|
||||||
So(cfg.MapTo(dv), ShouldBeNil)
|
|
||||||
So(dv.Name, ShouldEqual, "Joe")
|
|
||||||
So(dv.Age, ShouldEqual, 10)
|
|
||||||
So(dv.Male, ShouldBeTrue)
|
|
||||||
So(dv.Money, ShouldEqual, 1.25)
|
|
||||||
So(dv.Born.String(), ShouldEqual, t.String())
|
|
||||||
So(strings.Join(dv.Cities, ","), ShouldEqual, "HangZhou,Boston")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Map to struct in strict mode", t, func() {
|
|
||||||
cfg, err := Load([]byte(`
|
|
||||||
name=bruce
|
|
||||||
age=a30`))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
type Strict struct {
|
|
||||||
Name string `ini:"name"`
|
|
||||||
Age int `ini:"age"`
|
|
||||||
}
|
|
||||||
s := new(Strict)
|
|
||||||
|
|
||||||
So(cfg.Section("").StrictMapTo(s), ShouldNotBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Reflect from struct", t, func() {
|
|
||||||
type Embeded struct {
|
|
||||||
Dates []time.Time `delim:"|"`
|
|
||||||
Places []string
|
|
||||||
Years []int
|
|
||||||
Numbers []int64
|
|
||||||
Ages []uint
|
|
||||||
Populations []uint64
|
|
||||||
Coordinates []float64
|
|
||||||
None []int
|
|
||||||
}
|
|
||||||
type Author struct {
|
|
||||||
Name string `ini:"NAME"`
|
|
||||||
Male bool
|
|
||||||
Age int
|
|
||||||
Height uint
|
|
||||||
GPA float64
|
|
||||||
Date time.Time
|
|
||||||
NeverMind string `ini:"-"`
|
|
||||||
*Embeded `ini:"infos"`
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
a := &Author{"Unknwon", true, 21, 100, 2.8, t, "",
|
|
||||||
&Embeded{
|
|
||||||
[]time.Time{t, t},
|
|
||||||
[]string{"HangZhou", "Boston"},
|
|
||||||
[]int{1993, 1994},
|
|
||||||
[]int64{10010, 10086},
|
|
||||||
[]uint{18, 19},
|
|
||||||
[]uint64{12345678, 98765432},
|
|
||||||
[]float64{192.168, 10.11},
|
|
||||||
[]int{},
|
|
||||||
}}
|
|
||||||
cfg := Empty()
|
|
||||||
So(ReflectFrom(cfg, a), ShouldBeNil)
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_, err = cfg.WriteTo(&buf)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(buf.String(), ShouldEqual, `NAME = Unknwon
|
|
||||||
Male = true
|
|
||||||
Age = 21
|
|
||||||
Height = 100
|
|
||||||
GPA = 2.8
|
|
||||||
Date = 1993-10-07T20:17:05Z
|
|
||||||
|
|
||||||
[infos]
|
|
||||||
Dates = 1993-10-07T20:17:05Z|1993-10-07T20:17:05Z
|
|
||||||
Places = HangZhou,Boston
|
|
||||||
Years = 1993,1994
|
|
||||||
Numbers = 10010,10086
|
|
||||||
Ages = 18,19
|
|
||||||
Populations = 12345678,98765432
|
|
||||||
Coordinates = 192.168,10.11
|
|
||||||
None =
|
|
||||||
|
|
||||||
`)
|
|
||||||
|
|
||||||
Convey("Reflect from non-point struct", func() {
|
|
||||||
So(ReflectFrom(cfg, Author{}), ShouldNotBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Reflect from struct with omitempty", func() {
|
|
||||||
cfg := Empty()
|
|
||||||
type SpecialStruct struct {
|
|
||||||
FirstName string `ini:"first_name"`
|
|
||||||
LastName string `ini:"last_name"`
|
|
||||||
JustOmitMe string `ini:"omitempty"`
|
|
||||||
LastLogin time.Time `ini:"last_login,omitempty"`
|
|
||||||
LastLogin2 time.Time `ini:",omitempty"`
|
|
||||||
NotEmpty int `ini:"omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
So(ReflectFrom(cfg, &SpecialStruct{FirstName: "John", LastName: "Doe", NotEmpty: 9}), ShouldBeNil)
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_, err = cfg.WriteTo(&buf)
|
|
||||||
So(buf.String(), ShouldEqual, `first_name = John
|
|
||||||
last_name = Doe
|
|
||||||
omitempty = 9
|
|
||||||
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type testMapper struct {
|
|
||||||
PackageName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_NameGetter(t *testing.T) {
|
|
||||||
Convey("Test name mappers", t, func() {
|
|
||||||
So(MapToWithMapper(&testMapper{}, TitleUnderscore, []byte("packag_name=ini")), ShouldBeNil)
|
|
||||||
|
|
||||||
cfg, err := Load([]byte("PACKAGE_NAME=ini"))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cfg, ShouldNotBeNil)
|
|
||||||
|
|
||||||
cfg.NameMapper = AllCapsUnderscore
|
|
||||||
tg := new(testMapper)
|
|
||||||
So(cfg.MapTo(tg), ShouldBeNil)
|
|
||||||
So(tg.PackageName, ShouldEqual, "ini")
|
|
||||||
})
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue