mirror of
https://github.com/cubefs/cubefs.git
synced 2026-08-02 02:00:56 +00:00
init LICENSE file and vendor,util packet
This commit is contained in:
parent
b035c0ca63
commit
3893283054
201
LICENSE
Normal file
201
LICENSE
Normal file
@ -0,0 +1,201 @@
|
||||
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:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) 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
|
||||
|
||||
(d) 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.
|
||||
1
util/btree/.travis.yml
Normal file
1
util/btree/.travis.yml
Normal file
@ -0,0 +1 @@
|
||||
language: go
|
||||
202
util/btree/LICENSE
Normal file
202
util/btree/LICENSE
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
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:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) 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
|
||||
|
||||
(d) 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
util/btree/README.md
Normal file
12
util/btree/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
# BTree implementation for Go
|
||||
|
||||

|
||||
|
||||
This package provides an in-memory B-Tree implementation for Go, useful as
|
||||
an ordered, mutable data structure.
|
||||
|
||||
The API is based off of the wonderful
|
||||
http://godoc.org/github.com/petar/GoLLRB/llrb, and is meant to allow btree to
|
||||
act as a drop-in replacement for gollrb trees.
|
||||
|
||||
See http://godoc.org/github.com/google/btree for documentation.
|
||||
924
util/btree/btree.go
Normal file
924
util/btree/btree.go
Normal file
@ -0,0 +1,924 @@
|
||||
// Copyright 2014 Google Inc.
|
||||
//
|
||||
// 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 btree implements in-memory B-Trees of arbitrary degree.
|
||||
//
|
||||
// btree implements an in-memory B-Tree for use as an ordered data structure.
|
||||
// It is not meant for persistent storage solutions.
|
||||
//
|
||||
// It has a flatter structure than an equivalent red-black or other binary tree,
|
||||
// which in some cases yields better memory usage and/or performance.
|
||||
// See some discussion on the matter here:
|
||||
// http://google-opensource.blogspot.com/2013/01/c-containers-that-save-memory-and-time.html
|
||||
// Note, though, that this project is in no way related to the C++ B-Tree
|
||||
// implementation written about there.
|
||||
//
|
||||
// Within this tree, each node contains a slice of items and a (possibly nil)
|
||||
// slice of children. For basic numeric values or raw structs, this can cause
|
||||
// efficiency differences when compared to equivalent C++ template code that
|
||||
// stores values in arrays within the node:
|
||||
// * Due to the overhead of storing values as interfaces (each
|
||||
// value needs to be stored as the value itself, then 2 words for the
|
||||
// interface pointing to that value and its type), resulting in higher
|
||||
// memory use.
|
||||
// * Since interfaces can point to values anywhere in memory, values are
|
||||
// most likely not stored in contiguous blocks, resulting in a higher
|
||||
// number of cache misses.
|
||||
// These issues don't tend to matter, though, when working with strings or other
|
||||
// heap-allocated structures, since C++-equivalent structures also must store
|
||||
// pointers and also distribute their values across the heap.
|
||||
//
|
||||
// This implementation is designed to be a drop-in replacement to gollrb.LLRB
|
||||
// trees, (http://github.com/petar/gollrb), an excellent and probably the most
|
||||
// widely used ordered tree implementation in the Go ecosystem currently.
|
||||
// Its functions, therefore, exactly mirror those of
|
||||
// llrb.LLRB where possible. Unlike gollrb, though, we currently don't
|
||||
// support storing multiple equivalent values.
|
||||
package btree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Item represents a single object in the tree.
|
||||
type Item interface {
|
||||
// Less tests whether the current item is less than the given argument.
|
||||
//
|
||||
// This must provide a strict weak ordering.
|
||||
// If !a.Less(b) && !b.Less(a), we treat this to mean a == b (i.e. we can only
|
||||
// hold one of either a or b in the tree).
|
||||
Less(than Item) bool
|
||||
Copy() Item
|
||||
}
|
||||
|
||||
const (
|
||||
DefaultFreeListSize = 32
|
||||
)
|
||||
|
||||
var (
|
||||
nilItems = make(items, 16)
|
||||
nilChildren = make(children, 16)
|
||||
)
|
||||
|
||||
// FreeList represents a free list of btree nodes. By default each
|
||||
// BTree has its own FreeList, but multiple BTrees can share the same
|
||||
// FreeList.
|
||||
// Two Btrees using the same freelist are safe for concurrent write access.
|
||||
type FreeList struct {
|
||||
mu sync.Mutex
|
||||
freelist []*node
|
||||
}
|
||||
|
||||
// NewFreeList creates a new free list.
|
||||
// size is the maximum size of the returned free list.
|
||||
func NewFreeList(size int) *FreeList {
|
||||
return &FreeList{freelist: make([]*node, 0, size)}
|
||||
}
|
||||
|
||||
func (f *FreeList) newNode() (n *node) {
|
||||
f.mu.Lock()
|
||||
index := len(f.freelist) - 1
|
||||
if index < 0 {
|
||||
f.mu.Unlock()
|
||||
return new(node)
|
||||
}
|
||||
n = f.freelist[index]
|
||||
f.freelist[index] = nil
|
||||
f.freelist = f.freelist[:index]
|
||||
f.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// freeNode adds the given node to the list, returning true if it was added
|
||||
// and false if it was discarded.
|
||||
func (f *FreeList) freeNode(n *node) (out bool) {
|
||||
f.mu.Lock()
|
||||
if len(f.freelist) < cap(f.freelist) {
|
||||
f.freelist = append(f.freelist, n)
|
||||
out = true
|
||||
}
|
||||
f.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// ItemIterator allows callers of Ascend* to iterate in-order over portions of
|
||||
// the tree. When this function returns false, iteration will stop and the
|
||||
// associated Ascend* function will immediately return.
|
||||
type ItemIterator func(i Item) bool
|
||||
|
||||
// New creates a new B-Tree with the given degree.
|
||||
//
|
||||
// New(2), for example, will create a 2-3-4 tree (each node contains 1-3 items
|
||||
// and 2-4 children).
|
||||
func New(degree int) *BTree {
|
||||
return NewWithFreeList(degree, NewFreeList(DefaultFreeListSize))
|
||||
}
|
||||
|
||||
// NewWithFreeList creates a new B-Tree that uses the given node free list.
|
||||
func NewWithFreeList(degree int, f *FreeList) *BTree {
|
||||
if degree <= 1 {
|
||||
panic("bad degree")
|
||||
}
|
||||
return &BTree{
|
||||
degree: degree,
|
||||
cow: ©OnWriteContext{freelist: f},
|
||||
}
|
||||
}
|
||||
|
||||
// items stores items in a node.
|
||||
type items []Item
|
||||
|
||||
// insertAt inserts a value into the given index, pushing all subsequent values
|
||||
// forward.
|
||||
func (s *items) insertAt(index int, item Item) {
|
||||
*s = append(*s, nil)
|
||||
if index < len(*s) {
|
||||
copy((*s)[index+1:], (*s)[index:])
|
||||
}
|
||||
(*s)[index] = item
|
||||
}
|
||||
|
||||
// removeAt removes a value at a given index, pulling all subsequent values
|
||||
// back.
|
||||
func (s *items) removeAt(index int) Item {
|
||||
item := (*s)[index]
|
||||
copy((*s)[index:], (*s)[index+1:])
|
||||
(*s)[len(*s)-1] = nil
|
||||
*s = (*s)[:len(*s)-1]
|
||||
return item
|
||||
}
|
||||
|
||||
// pop removes and returns the last element in the list.
|
||||
func (s *items) pop() (out Item) {
|
||||
index := len(*s) - 1
|
||||
out = (*s)[index]
|
||||
(*s)[index] = nil
|
||||
*s = (*s)[:index]
|
||||
return
|
||||
}
|
||||
|
||||
// copy copy and return new items
|
||||
func (s *items) copy() items {
|
||||
nItems := make(items, 0, len(*s))
|
||||
for _, v := range *s {
|
||||
nItems = append(nItems, v.Copy())
|
||||
}
|
||||
return nItems
|
||||
}
|
||||
|
||||
// truncate truncates this instance at index so that it contains only the
|
||||
// first index items. index must be less than or equal to length.
|
||||
func (s *items) truncate(index int) {
|
||||
var toClear items
|
||||
*s, toClear = (*s)[:index], (*s)[index:]
|
||||
for len(toClear) > 0 {
|
||||
toClear = toClear[copy(toClear, nilItems):]
|
||||
}
|
||||
}
|
||||
|
||||
// find returns the index where the given item should be inserted into this
|
||||
// list. 'found' is true if the item already exists in the list at the given
|
||||
// index.
|
||||
func (s items) find(item Item) (index int, found bool) {
|
||||
i := sort.Search(len(s), func(i int) bool {
|
||||
return item.Less(s[i])
|
||||
})
|
||||
if i > 0 && !s[i-1].Less(item) {
|
||||
return i - 1, true
|
||||
}
|
||||
return i, false
|
||||
}
|
||||
|
||||
// children stores child nodes in a node.
|
||||
type children []*node
|
||||
|
||||
// insertAt inserts a value into the given index, pushing all subsequent values
|
||||
// forward.
|
||||
func (s *children) insertAt(index int, n *node) {
|
||||
*s = append(*s, nil)
|
||||
if index < len(*s) {
|
||||
copy((*s)[index+1:], (*s)[index:])
|
||||
}
|
||||
(*s)[index] = n
|
||||
}
|
||||
|
||||
// removeAt removes a value at a given index, pulling all subsequent values
|
||||
// back.
|
||||
func (s *children) removeAt(index int) *node {
|
||||
n := (*s)[index]
|
||||
copy((*s)[index:], (*s)[index+1:])
|
||||
(*s)[len(*s)-1] = nil
|
||||
*s = (*s)[:len(*s)-1]
|
||||
return n
|
||||
}
|
||||
|
||||
// pop removes and returns the last element in the list.
|
||||
func (s *children) pop() (out *node) {
|
||||
index := len(*s) - 1
|
||||
out = (*s)[index]
|
||||
(*s)[index] = nil
|
||||
*s = (*s)[:index]
|
||||
return
|
||||
}
|
||||
|
||||
// truncate truncates this instance at index so that it contains only the
|
||||
// first index children. index must be less than or equal to length.
|
||||
func (s *children) truncate(index int) {
|
||||
var toClear children
|
||||
*s, toClear = (*s)[:index], (*s)[index:]
|
||||
for len(toClear) > 0 {
|
||||
toClear = toClear[copy(toClear, nilChildren):]
|
||||
}
|
||||
}
|
||||
|
||||
// node is an internal node in a tree.
|
||||
//
|
||||
// It must at all times maintain the invariant that either
|
||||
// * len(children) == 0, len(items) unconstrained
|
||||
// * len(children) == len(items) + 1
|
||||
type node struct {
|
||||
items items
|
||||
children children
|
||||
cow *copyOnWriteContext
|
||||
}
|
||||
|
||||
func (n *node) mutableFor(cow *copyOnWriteContext) *node {
|
||||
if n.cow == cow {
|
||||
return n
|
||||
}
|
||||
out := cow.newNode()
|
||||
if cap(out.items) >= len(n.items) {
|
||||
out.items = out.items[:len(n.items)]
|
||||
} else {
|
||||
out.items = make(items, len(n.items), cap(n.items))
|
||||
}
|
||||
copy(out.items, n.items.copy())
|
||||
// Copy children
|
||||
if cap(out.children) >= len(n.children) {
|
||||
out.children = out.children[:len(n.children)]
|
||||
} else {
|
||||
out.children = make(children, len(n.children), cap(n.children))
|
||||
}
|
||||
copy(out.children, n.children)
|
||||
return out
|
||||
}
|
||||
|
||||
func (n *node) mutableChild(i int) *node {
|
||||
c := n.children[i].mutableFor(n.cow)
|
||||
n.children[i] = c
|
||||
return c
|
||||
}
|
||||
|
||||
// split splits the given node at the given index. The current node shrinks,
|
||||
// and this function returns the item that existed at that index and a new node
|
||||
// containing all items/children after it.
|
||||
func (n *node) split(i int) (Item, *node) {
|
||||
item := n.items[i]
|
||||
next := n.cow.newNode()
|
||||
next.items = append(next.items, n.items[i+1:]...)
|
||||
n.items.truncate(i)
|
||||
if len(n.children) > 0 {
|
||||
next.children = append(next.children, n.children[i+1:]...)
|
||||
n.children.truncate(i + 1)
|
||||
}
|
||||
return item, next
|
||||
}
|
||||
|
||||
// maybeSplitChild checks if a child should be split, and if so splits it.
|
||||
// Returns whether or not a split occurred.
|
||||
func (n *node) maybeSplitChild(i, maxItems int) bool {
|
||||
if len(n.children[i].items) < maxItems {
|
||||
return false
|
||||
}
|
||||
first := n.mutableChild(i)
|
||||
item, second := first.split(maxItems / 2)
|
||||
n.items.insertAt(i, item)
|
||||
n.children.insertAt(i+1, second)
|
||||
return true
|
||||
}
|
||||
|
||||
// insert inserts an item into the subtree rooted at this node, making sure
|
||||
// no nodes in the subtree exceed maxItems items. Should an equivalent item be
|
||||
// be found/replaced by insert, it will be returned.
|
||||
func (n *node) insert(item Item, maxItems int) Item {
|
||||
i, found := n.items.find(item)
|
||||
if found {
|
||||
out := n.items[i]
|
||||
n.items[i] = item
|
||||
return out
|
||||
}
|
||||
if len(n.children) == 0 {
|
||||
n.items.insertAt(i, item)
|
||||
return nil
|
||||
}
|
||||
if n.maybeSplitChild(i, maxItems) {
|
||||
inTree := n.items[i]
|
||||
switch {
|
||||
case item.Less(inTree):
|
||||
// no change, we want first split node
|
||||
case inTree.Less(item):
|
||||
i++ // we want second split node
|
||||
default:
|
||||
out := n.items[i]
|
||||
n.items[i] = item
|
||||
return out
|
||||
}
|
||||
}
|
||||
return n.mutableChild(i).insert(item, maxItems)
|
||||
}
|
||||
|
||||
// get finds the given key in the subtree and returns it.
|
||||
func (n *node) get(key Item) Item {
|
||||
i, found := n.items.find(key)
|
||||
if found {
|
||||
return n.items[i]
|
||||
} else if len(n.children) > 0 {
|
||||
return n.children[i].get(key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node) copyGet(key Item, cow *copyOnWriteContext) Item {
|
||||
i, found := n.items.find(key)
|
||||
if found {
|
||||
return n.items[i]
|
||||
} else if len(n.children) > 0 {
|
||||
child := n.mutableChild(i)
|
||||
return child.copyGet(key, cow)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// min returns the first item in the subtree.
|
||||
func min(n *node) Item {
|
||||
for len(n.children) > 0 {
|
||||
n = n.children[0]
|
||||
}
|
||||
if len(n.items) == 0 {
|
||||
return nil
|
||||
}
|
||||
return n.items[0]
|
||||
}
|
||||
|
||||
// max returns the last item in the subtree.
|
||||
func max(n *node) Item {
|
||||
for len(n.children) > 0 {
|
||||
n = n.children[len(n.children)-1]
|
||||
}
|
||||
if len(n.items) == 0 {
|
||||
return nil
|
||||
}
|
||||
return n.items[len(n.items)-1]
|
||||
}
|
||||
|
||||
// toRemove details what item to remove in a node.remove call.
|
||||
type toRemove int
|
||||
|
||||
const (
|
||||
removeItem toRemove = iota // removes the given item
|
||||
removeMin // removes smallest item in the subtree
|
||||
removeMax // removes largest item in the subtree
|
||||
)
|
||||
|
||||
// remove removes an item from the subtree rooted at this node.
|
||||
func (n *node) remove(item Item, minItems int, typ toRemove) Item {
|
||||
var i int
|
||||
var found bool
|
||||
switch typ {
|
||||
case removeMax:
|
||||
if len(n.children) == 0 {
|
||||
return n.items.pop()
|
||||
}
|
||||
i = len(n.items)
|
||||
case removeMin:
|
||||
if len(n.children) == 0 {
|
||||
return n.items.removeAt(0)
|
||||
}
|
||||
i = 0
|
||||
case removeItem:
|
||||
i, found = n.items.find(item)
|
||||
if len(n.children) == 0 {
|
||||
if found {
|
||||
return n.items.removeAt(i)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
panic("invalid type")
|
||||
}
|
||||
// If we get to here, we have children.
|
||||
if len(n.children[i].items) <= minItems {
|
||||
return n.growChildAndRemove(i, item, minItems, typ)
|
||||
}
|
||||
child := n.mutableChild(i)
|
||||
// Either we had enough items to begin with, or we've done some
|
||||
// merging/stealing, because we've got enough now and we're ready to return
|
||||
// stuff.
|
||||
if found {
|
||||
// The item exists at index 'i', and the child we've selected can give us a
|
||||
// predecessor, since if we've gotten here it's got > minItems items in it.
|
||||
out := n.items[i]
|
||||
// We use our special-case 'remove' call with typ=maxItem to pull the
|
||||
// predecessor of item i (the rightmost leaf of our immediate left child)
|
||||
// and set it into where we pulled the item from.
|
||||
n.items[i] = child.remove(nil, minItems, removeMax)
|
||||
return out
|
||||
}
|
||||
// Final recursive call. Once we're here, we know that the item isn't in this
|
||||
// node and that the child is big enough to remove from.
|
||||
return child.remove(item, minItems, typ)
|
||||
}
|
||||
|
||||
// growChildAndRemove grows child 'i' to make sure it's possible to remove an
|
||||
// item from it while keeping it at minItems, then calls remove to actually
|
||||
// remove it.
|
||||
//
|
||||
// Most documentation says we have to do two sets of special casing:
|
||||
// 1) item is in this node
|
||||
// 2) item is in child
|
||||
// In both cases, we need to handle the two subcases:
|
||||
// A) node has enough values that it can spare one
|
||||
// B) node doesn't have enough values
|
||||
// For the latter, we have to check:
|
||||
// a) left sibling has node to spare
|
||||
// b) right sibling has node to spare
|
||||
// c) we must merge
|
||||
// To simplify our code here, we handle cases #1 and #2 the same:
|
||||
// If a node doesn't have enough items, we make sure it does (using a,b,c).
|
||||
// We then simply redo our remove call, and the second time (regardless of
|
||||
// whether we're in case 1 or 2), we'll have enough items and can guarantee
|
||||
// that we hit case A.
|
||||
func (n *node) growChildAndRemove(i int, item Item, minItems int, typ toRemove) Item {
|
||||
if i > 0 && len(n.children[i-1].items) > minItems {
|
||||
// Steal from left child
|
||||
child := n.mutableChild(i)
|
||||
stealFrom := n.mutableChild(i - 1)
|
||||
stolenItem := stealFrom.items.pop()
|
||||
child.items.insertAt(0, n.items[i-1])
|
||||
n.items[i-1] = stolenItem
|
||||
if len(stealFrom.children) > 0 {
|
||||
child.children.insertAt(0, stealFrom.children.pop())
|
||||
}
|
||||
} else if i < len(n.items) && len(n.children[i+1].items) > minItems {
|
||||
// steal from right child
|
||||
child := n.mutableChild(i)
|
||||
stealFrom := n.mutableChild(i + 1)
|
||||
stolenItem := stealFrom.items.removeAt(0)
|
||||
child.items = append(child.items, n.items[i])
|
||||
n.items[i] = stolenItem
|
||||
if len(stealFrom.children) > 0 {
|
||||
child.children = append(child.children, stealFrom.children.removeAt(0))
|
||||
}
|
||||
} else {
|
||||
if i >= len(n.items) {
|
||||
i--
|
||||
}
|
||||
child := n.mutableChild(i)
|
||||
// merge with right child
|
||||
mergeItem := n.items.removeAt(i)
|
||||
mergeChild := n.children.removeAt(i + 1)
|
||||
child.items = append(child.items, mergeItem)
|
||||
child.items = append(child.items, mergeChild.items...)
|
||||
child.children = append(child.children, mergeChild.children...)
|
||||
n.cow.freeNode(mergeChild)
|
||||
}
|
||||
return n.remove(item, minItems, typ)
|
||||
}
|
||||
|
||||
type direction int
|
||||
|
||||
const (
|
||||
descend = direction(-1)
|
||||
ascend = direction(+1)
|
||||
)
|
||||
|
||||
// iterate provides a simple method for iterating over elements in the tree.
|
||||
//
|
||||
// When ascending, the 'start' should be less than 'stop' and when descending,
|
||||
// the 'start' should be greater than 'stop'. Setting 'includeStart' to true
|
||||
// will force the iterator to include the first item when it equals 'start',
|
||||
// thus creating a "greaterOrEqual" or "lessThanEqual" rather than just a
|
||||
// "greaterThan" or "lessThan" queries.
|
||||
func (n *node) iterate(dir direction, start, stop Item, includeStart bool, hit bool, iter ItemIterator) (bool, bool) {
|
||||
var ok, found bool
|
||||
var index int
|
||||
switch dir {
|
||||
case ascend:
|
||||
if start != nil {
|
||||
index, _ = n.items.find(start)
|
||||
}
|
||||
for i := index; i < len(n.items); i++ {
|
||||
if len(n.children) > 0 {
|
||||
if hit, ok = n.children[i].iterate(dir, start, stop, includeStart, hit, iter); !ok {
|
||||
return hit, false
|
||||
}
|
||||
}
|
||||
if !includeStart && !hit && start != nil && !start.Less(n.items[i]) {
|
||||
hit = true
|
||||
continue
|
||||
}
|
||||
hit = true
|
||||
if stop != nil && !n.items[i].Less(stop) {
|
||||
return hit, false
|
||||
}
|
||||
if !iter(n.items[i]) {
|
||||
return hit, false
|
||||
}
|
||||
}
|
||||
if len(n.children) > 0 {
|
||||
if hit, ok = n.children[len(n.children)-1].iterate(dir, start, stop, includeStart, hit, iter); !ok {
|
||||
return hit, false
|
||||
}
|
||||
}
|
||||
case descend:
|
||||
if start != nil {
|
||||
index, found = n.items.find(start)
|
||||
if !found {
|
||||
index = index - 1
|
||||
}
|
||||
} else {
|
||||
index = len(n.items) - 1
|
||||
}
|
||||
for i := index; i >= 0; i-- {
|
||||
if start != nil && !n.items[i].Less(start) {
|
||||
if !includeStart || hit || start.Less(n.items[i]) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(n.children) > 0 {
|
||||
if hit, ok = n.children[i+1].iterate(dir, start, stop, includeStart, hit, iter); !ok {
|
||||
return hit, false
|
||||
}
|
||||
}
|
||||
if stop != nil && !stop.Less(n.items[i]) {
|
||||
return hit, false // continue
|
||||
}
|
||||
hit = true
|
||||
if !iter(n.items[i]) {
|
||||
return hit, false
|
||||
}
|
||||
}
|
||||
if len(n.children) > 0 {
|
||||
if hit, ok = n.children[0].iterate(dir, start, stop, includeStart, hit, iter); !ok {
|
||||
return hit, false
|
||||
}
|
||||
}
|
||||
}
|
||||
return hit, true
|
||||
}
|
||||
|
||||
// Used for testing/debugging purposes.
|
||||
func (n *node) print(w io.Writer, level int) {
|
||||
fmt.Fprintf(w, "%sNODE:%v\n", strings.Repeat(" ", level), n.items)
|
||||
for _, c := range n.children {
|
||||
c.print(w, level+1)
|
||||
}
|
||||
}
|
||||
|
||||
// BTree is an implementation of a B-Tree.
|
||||
//
|
||||
// BTree stores Item instances in an ordered structure, allowing easy insertion,
|
||||
// removal, and iteration.
|
||||
//
|
||||
// Write operations are not safe for concurrent mutation by multiple
|
||||
// goroutines, but Read operations are.
|
||||
type BTree struct {
|
||||
degree int
|
||||
length int
|
||||
root *node
|
||||
cow *copyOnWriteContext
|
||||
}
|
||||
|
||||
// copyOnWriteContext pointers determine node ownership... a tree with a write
|
||||
// context equivalent to a node's write context is allowed to modify that node.
|
||||
// A tree whose write context does not match a node's is not allowed to modify
|
||||
// it, and must create a new, writable copy (IE: it's a Clone).
|
||||
//
|
||||
// When doing any write operation, we maintain the invariant that the current
|
||||
// node's context is equal to the context of the tree that requested the write.
|
||||
// We do this by, before we descend into any node, creating a copy with the
|
||||
// correct context if the contexts don't match.
|
||||
//
|
||||
// Since the node we're currently visiting on any write has the requesting
|
||||
// tree's context, that node is modifiable in place. Children of that node may
|
||||
// not share context, but before we descend into them, we'll make a mutable
|
||||
// copy.
|
||||
type copyOnWriteContext struct {
|
||||
freelist *FreeList
|
||||
}
|
||||
|
||||
// Clone clones the btree, lazily. Clone should not be called concurrently,
|
||||
// but the original tree (t) and the new tree (t2) can be used concurrently
|
||||
// once the Clone call completes.
|
||||
//
|
||||
// The internal tree structure of b is marked read-only and shared between t and
|
||||
// t2. Writes to both t and t2 use copy-on-write logic, creating new nodes
|
||||
// whenever one of b's original nodes would have been modified. Read operations
|
||||
// should have no performance degredation. Write operations for both t and t2
|
||||
// will initially experience minor slow-downs caused by additional allocs and
|
||||
// copies due to the aforementioned copy-on-write logic, but should converge to
|
||||
// the original performance characteristics of the original tree.
|
||||
func (t *BTree) Clone() (t2 *BTree) {
|
||||
// Create two entirely new copy-on-write contexts.
|
||||
// This operation effectively creates three trees:
|
||||
// the original, shared nodes (old b.cow)
|
||||
// the new b.cow nodes
|
||||
// the new out.cow nodes
|
||||
cow1, cow2 := *t.cow, *t.cow
|
||||
out := *t
|
||||
t.cow = &cow1
|
||||
out.cow = &cow2
|
||||
return &out
|
||||
}
|
||||
|
||||
// maxItems returns the max number of items to allow per node.
|
||||
func (t *BTree) maxItems() int {
|
||||
return t.degree*2 - 1
|
||||
}
|
||||
|
||||
// minItems returns the min number of items to allow per node (ignored for the
|
||||
// root node).
|
||||
func (t *BTree) minItems() int {
|
||||
return t.degree - 1
|
||||
}
|
||||
|
||||
func (c *copyOnWriteContext) newNode() (n *node) {
|
||||
n = c.freelist.newNode()
|
||||
n.cow = c
|
||||
return
|
||||
}
|
||||
|
||||
type freeType int
|
||||
|
||||
const (
|
||||
ftFreelistFull freeType = iota // node was freed (available for GC, not stored in freelist)
|
||||
ftStored // node was stored in the freelist for later use
|
||||
ftNotOwned // node was ignored by COW, since it's owned by another one
|
||||
)
|
||||
|
||||
// freeNode frees a node within a given COW context, if it's owned by that
|
||||
// context. It returns what happened to the node (see freeType const
|
||||
// documentation).
|
||||
func (c *copyOnWriteContext) freeNode(n *node) freeType {
|
||||
if n.cow == c {
|
||||
// clear to allow GC
|
||||
n.items.truncate(0)
|
||||
n.children.truncate(0)
|
||||
n.cow = nil
|
||||
if c.freelist.freeNode(n) {
|
||||
return ftStored
|
||||
} else {
|
||||
return ftFreelistFull
|
||||
}
|
||||
} else {
|
||||
return ftNotOwned
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceOrInsert adds the given item to the tree. If an item in the tree
|
||||
// already equals the given one, it is removed from the tree and returned.
|
||||
// Otherwise, nil is returned.
|
||||
//
|
||||
// nil cannot be added to the tree (will panic).
|
||||
func (t *BTree) ReplaceOrInsert(item Item) Item {
|
||||
if item == nil {
|
||||
panic("nil item being added to BTree")
|
||||
}
|
||||
if t.root == nil {
|
||||
t.root = t.cow.newNode()
|
||||
t.root.items = append(t.root.items, item)
|
||||
t.length++
|
||||
return nil
|
||||
} else {
|
||||
t.root = t.root.mutableFor(t.cow)
|
||||
if len(t.root.items) >= t.maxItems() {
|
||||
item2, second := t.root.split(t.maxItems() / 2)
|
||||
oldroot := t.root
|
||||
t.root = t.cow.newNode()
|
||||
t.root.items = append(t.root.items, item2)
|
||||
t.root.children = append(t.root.children, oldroot, second)
|
||||
}
|
||||
}
|
||||
out := t.root.insert(item, t.maxItems())
|
||||
if out == nil {
|
||||
t.length++
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Delete removes an item equal to the passed in item from the tree, returning
|
||||
// it. If no such item exists, returns nil.
|
||||
func (t *BTree) Delete(item Item) Item {
|
||||
return t.deleteItem(item, removeItem)
|
||||
}
|
||||
|
||||
// DeleteMin removes the smallest item in the tree and returns it.
|
||||
// If no such item exists, returns nil.
|
||||
func (t *BTree) DeleteMin() Item {
|
||||
return t.deleteItem(nil, removeMin)
|
||||
}
|
||||
|
||||
// DeleteMax removes the largest item in the tree and returns it.
|
||||
// If no such item exists, returns nil.
|
||||
func (t *BTree) DeleteMax() Item {
|
||||
return t.deleteItem(nil, removeMax)
|
||||
}
|
||||
|
||||
func (t *BTree) deleteItem(item Item, typ toRemove) Item {
|
||||
if t.root == nil || len(t.root.items) == 0 {
|
||||
return nil
|
||||
}
|
||||
t.root = t.root.mutableFor(t.cow)
|
||||
out := t.root.remove(item, t.minItems(), typ)
|
||||
if len(t.root.items) == 0 && len(t.root.children) > 0 {
|
||||
oldroot := t.root
|
||||
t.root = t.root.children[0]
|
||||
t.cow.freeNode(oldroot)
|
||||
}
|
||||
if out != nil {
|
||||
t.length--
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// AscendRange calls the iterator for every value in the tree within the range
|
||||
// [greaterOrEqual, lessThan), until iterator returns false.
|
||||
func (t *BTree) AscendRange(greaterOrEqual, lessThan Item, iterator ItemIterator) {
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
t.root.iterate(ascend, greaterOrEqual, lessThan, true, false, iterator)
|
||||
}
|
||||
|
||||
// AscendLessThan calls the iterator for every value in the tree within the range
|
||||
// [first, pivot), until iterator returns false.
|
||||
func (t *BTree) AscendLessThan(pivot Item, iterator ItemIterator) {
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
t.root.iterate(ascend, nil, pivot, false, false, iterator)
|
||||
}
|
||||
|
||||
// AscendGreaterOrEqual calls the iterator for every value in the tree within
|
||||
// the range [pivot, last], until iterator returns false.
|
||||
func (t *BTree) AscendGreaterOrEqual(pivot Item, iterator ItemIterator) {
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
t.root.iterate(ascend, pivot, nil, true, false, iterator)
|
||||
}
|
||||
|
||||
// Ascend calls the iterator for every value in the tree within the range
|
||||
// [first, last], until iterator returns false.
|
||||
func (t *BTree) Ascend(iterator ItemIterator) {
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
t.root.iterate(ascend, nil, nil, false, false, iterator)
|
||||
}
|
||||
|
||||
// DescendRange calls the iterator for every value in the tree within the range
|
||||
// [lessOrEqual, greaterThan), until iterator returns false.
|
||||
func (t *BTree) DescendRange(lessOrEqual, greaterThan Item, iterator ItemIterator) {
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
t.root.iterate(descend, lessOrEqual, greaterThan, true, false, iterator)
|
||||
}
|
||||
|
||||
// DescendLessOrEqual calls the iterator for every value in the tree within the range
|
||||
// [pivot, first], until iterator returns false.
|
||||
func (t *BTree) DescendLessOrEqual(pivot Item, iterator ItemIterator) {
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
t.root.iterate(descend, pivot, nil, true, false, iterator)
|
||||
}
|
||||
|
||||
// DescendGreaterThan calls the iterator for every value in the tree within
|
||||
// the range (pivot, last], until iterator returns false.
|
||||
func (t *BTree) DescendGreaterThan(pivot Item, iterator ItemIterator) {
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
t.root.iterate(descend, nil, pivot, false, false, iterator)
|
||||
}
|
||||
|
||||
// Descend calls the iterator for every value in the tree within the range
|
||||
// [last, first], until iterator returns false.
|
||||
func (t *BTree) Descend(iterator ItemIterator) {
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
t.root.iterate(descend, nil, nil, false, false, iterator)
|
||||
}
|
||||
|
||||
// Get looks for the key item in the tree, returning it. It returns nil if
|
||||
// unable to find that item.
|
||||
func (t *BTree) Get(key Item) Item {
|
||||
if t.root == nil {
|
||||
return nil
|
||||
}
|
||||
return t.root.get(key)
|
||||
}
|
||||
|
||||
func (t *BTree) CopyGet(key Item) Item {
|
||||
if t.root == nil {
|
||||
return nil
|
||||
}
|
||||
t.root = t.root.mutableFor(t.cow)
|
||||
item := t.root.copyGet(key, t.cow)
|
||||
return item
|
||||
}
|
||||
|
||||
// Min returns the smallest item in the tree, or nil if the tree is empty.
|
||||
func (t *BTree) Min() Item {
|
||||
if t.root == nil {
|
||||
return nil
|
||||
}
|
||||
return min(t.root)
|
||||
}
|
||||
|
||||
// Max returns the largest item in the tree, or nil if the tree is empty.
|
||||
func (t *BTree) Max() Item {
|
||||
if t.root == nil {
|
||||
return nil
|
||||
}
|
||||
return max(t.root)
|
||||
}
|
||||
|
||||
// Has returns true if the given key is in the tree.
|
||||
func (t *BTree) Has(key Item) bool {
|
||||
return t.Get(key) != nil
|
||||
}
|
||||
|
||||
// Len returns the number of items currently in the tree.
|
||||
func (t *BTree) Len() int {
|
||||
return t.length
|
||||
}
|
||||
|
||||
// Clear removes all items from the btree. If addNodesToFreelist is true,
|
||||
// t's nodes are added to its freelist as part of this call, until the freelist
|
||||
// is full. Otherwise, the root node is simply dereferenced and the subtree
|
||||
// left to Go's normal GC processes.
|
||||
//
|
||||
// This can be much faster
|
||||
// than calling Delete on all elements, because that requires finding/removing
|
||||
// each element in the tree and updating the tree accordingly. It also is
|
||||
// somewhat faster than creating a new tree to replace the old one, because
|
||||
// nodes from the old tree are reclaimed into the freelist for use by the new
|
||||
// one, instead of being lost to the garbage collector.
|
||||
//
|
||||
// This call takes:
|
||||
// O(1): when addNodesToFreelist is false, this is a single operation.
|
||||
// O(1): when the freelist is already full, it breaks out immediately
|
||||
// O(freelist size): when the freelist is empty and the nodes are all owned
|
||||
// by this tree, nodes are added to the freelist until full.
|
||||
// O(tree size): when all nodes are owned by another tree, all nodes are
|
||||
// iterated over looking for nodes to add to the freelist, and due to
|
||||
// ownership, none are.
|
||||
func (t *BTree) Clear(addNodesToFreelist bool) {
|
||||
if t.root != nil && addNodesToFreelist {
|
||||
t.root.reset(t.cow)
|
||||
}
|
||||
t.root, t.length = nil, 0
|
||||
}
|
||||
|
||||
// reset returns a subtree to the freelist. It breaks out immediately if the
|
||||
// freelist is full, since the only benefit of iterating is to fill that
|
||||
// freelist up. Returns true if parent reset call should continue.
|
||||
func (n *node) reset(c *copyOnWriteContext) bool {
|
||||
for _, child := range n.children {
|
||||
if !child.reset(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return c.freeNode(n) != ftFreelistFull
|
||||
}
|
||||
|
||||
// Int implements the Item interface for integers.
|
||||
type Int int
|
||||
|
||||
func (a Int) Copy() Item {
|
||||
return a
|
||||
}
|
||||
|
||||
// Less returns true if int(a) < int(b).
|
||||
func (a Int) Less(b Item) bool {
|
||||
return a < b.(Int)
|
||||
}
|
||||
76
util/btree/btree_mem.go
Normal file
76
util/btree/btree_mem.go
Normal file
@ -0,0 +1,76 @@
|
||||
// Copyright 2014 Google Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// This binary compares memory usage between btree and gollrb.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/google/btree"
|
||||
"github.com/petar/GoLLRB/llrb"
|
||||
)
|
||||
|
||||
var (
|
||||
size = flag.Int("size", 1000000, "size of the tree to build")
|
||||
degree = flag.Int("degree", 8, "degree of btree")
|
||||
gollrb = flag.Bool("llrb", false, "use llrb instead of btree")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
vals := rand.Perm(*size)
|
||||
var t, v interface{}
|
||||
v = vals
|
||||
var stats runtime.MemStats
|
||||
for i := 0; i < 10; i++ {
|
||||
runtime.GC()
|
||||
}
|
||||
fmt.Println("-------- BEFORE ----------")
|
||||
runtime.ReadMemStats(&stats)
|
||||
fmt.Printf("%+v\n", stats)
|
||||
start := time.Now()
|
||||
if *gollrb {
|
||||
tr := llrb.New()
|
||||
for _, v := range vals {
|
||||
tr.ReplaceOrInsert(llrb.Int(v))
|
||||
}
|
||||
t = tr // keep it around
|
||||
} else {
|
||||
tr := btree.New(*degree)
|
||||
for _, v := range vals {
|
||||
tr.ReplaceOrInsert(btree.Int(v))
|
||||
}
|
||||
t = tr // keep it around
|
||||
}
|
||||
fmt.Printf("%v inserts in %v\n", *size, time.Since(start))
|
||||
fmt.Println("-------- AFTER ----------")
|
||||
runtime.ReadMemStats(&stats)
|
||||
fmt.Printf("%+v\n", stats)
|
||||
for i := 0; i < 10; i++ {
|
||||
runtime.GC()
|
||||
}
|
||||
fmt.Println("-------- AFTER GC ----------")
|
||||
runtime.ReadMemStats(&stats)
|
||||
fmt.Printf("%+v\n", stats)
|
||||
if t == v {
|
||||
fmt.Println("to make sure vals and tree aren't GC'd")
|
||||
}
|
||||
}
|
||||
785
util/btree/btree_test.go
Normal file
785
util/btree/btree_test.go
Normal file
@ -0,0 +1,785 @@
|
||||
// Copyright 2014 Google Inc.
|
||||
//
|
||||
// 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 btree
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
seed := time.Now().Unix()
|
||||
fmt.Println(seed)
|
||||
rand.Seed(seed)
|
||||
}
|
||||
|
||||
// perm returns a random permutation of n Int items in the range [0, n).
|
||||
func perm(n int) (out []Item) {
|
||||
for _, v := range rand.Perm(n) {
|
||||
out = append(out, Int(v))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// rang returns an ordered list of Int items in the range [0, n).
|
||||
func rang(n int) (out []Item) {
|
||||
for i := 0; i < n; i++ {
|
||||
out = append(out, Int(i))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// all extracts all items from a tree in order as a slice.
|
||||
func all(t *BTree) (out []Item) {
|
||||
t.Ascend(func(a Item) bool {
|
||||
out = append(out, a)
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// rangerev returns a reversed ordered list of Int items in the range [0, n).
|
||||
func rangrev(n int) (out []Item) {
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
out = append(out, Int(i))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// allrev extracts all items from a tree in reverse order as a slice.
|
||||
func allrev(t *BTree) (out []Item) {
|
||||
t.Descend(func(a Item) bool {
|
||||
out = append(out, a)
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var btreeDegree = flag.Int("degree", 32, "B-Tree degree")
|
||||
|
||||
func TestBTree(t *testing.T) {
|
||||
tr := New(*btreeDegree)
|
||||
const treeSize = 10000
|
||||
for i := 0; i < 10; i++ {
|
||||
if min := tr.Min(); min != nil {
|
||||
t.Fatalf("empty min, got %+v", min)
|
||||
}
|
||||
if max := tr.Max(); max != nil {
|
||||
t.Fatalf("empty max, got %+v", max)
|
||||
}
|
||||
for _, item := range perm(treeSize) {
|
||||
if x := tr.ReplaceOrInsert(item); x != nil {
|
||||
t.Fatal("insert found item", item)
|
||||
}
|
||||
}
|
||||
for _, item := range perm(treeSize) {
|
||||
if x := tr.ReplaceOrInsert(item); x == nil {
|
||||
t.Fatal("insert didn't find item", item)
|
||||
}
|
||||
}
|
||||
if min, want := tr.Min(), Item(Int(0)); min != want {
|
||||
t.Fatalf("min: want %+v, got %+v", want, min)
|
||||
}
|
||||
if max, want := tr.Max(), Item(Int(treeSize-1)); max != want {
|
||||
t.Fatalf("max: want %+v, got %+v", want, max)
|
||||
}
|
||||
got := all(tr)
|
||||
want := rang(treeSize)
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("mismatch:\n got: %v\nwant: %v", got, want)
|
||||
}
|
||||
|
||||
gotrev := allrev(tr)
|
||||
wantrev := rangrev(treeSize)
|
||||
if !reflect.DeepEqual(gotrev, wantrev) {
|
||||
t.Fatalf("mismatch:\n got: %v\nwant: %v", got, want)
|
||||
}
|
||||
|
||||
for _, item := range perm(treeSize) {
|
||||
if x := tr.Delete(item); x == nil {
|
||||
t.Fatalf("didn't find %v", item)
|
||||
}
|
||||
}
|
||||
if got = all(tr); len(got) > 0 {
|
||||
t.Fatalf("some left!: %v", got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleBTree() {
|
||||
tr := New(*btreeDegree)
|
||||
for i := Int(0); i < 10; i++ {
|
||||
tr.ReplaceOrInsert(i)
|
||||
}
|
||||
fmt.Println("len: ", tr.Len())
|
||||
fmt.Println("get3: ", tr.Get(Int(3)))
|
||||
fmt.Println("get100: ", tr.Get(Int(100)))
|
||||
fmt.Println("del4: ", tr.Delete(Int(4)))
|
||||
fmt.Println("del100: ", tr.Delete(Int(100)))
|
||||
fmt.Println("replace5: ", tr.ReplaceOrInsert(Int(5)))
|
||||
fmt.Println("replace100:", tr.ReplaceOrInsert(Int(100)))
|
||||
fmt.Println("min: ", tr.Min())
|
||||
fmt.Println("delmin: ", tr.DeleteMin())
|
||||
fmt.Println("max: ", tr.Max())
|
||||
fmt.Println("delmax: ", tr.DeleteMax())
|
||||
fmt.Println("len: ", tr.Len())
|
||||
// Output:
|
||||
// len: 10
|
||||
// get3: 3
|
||||
// get100: <nil>
|
||||
// del4: 4
|
||||
// del100: <nil>
|
||||
// replace5: 5
|
||||
// replace100: <nil>
|
||||
// min: 0
|
||||
// delmin: 0
|
||||
// max: 100
|
||||
// delmax: 100
|
||||
// len: 8
|
||||
}
|
||||
|
||||
func TestDeleteMin(t *testing.T) {
|
||||
tr := New(3)
|
||||
for _, v := range perm(100) {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
var got []Item
|
||||
for v := tr.DeleteMin(); v != nil; v = tr.DeleteMin() {
|
||||
got = append(got, v)
|
||||
}
|
||||
if want := rang(100); !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteMax(t *testing.T) {
|
||||
tr := New(3)
|
||||
for _, v := range perm(100) {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
var got []Item
|
||||
for v := tr.DeleteMax(); v != nil; v = tr.DeleteMax() {
|
||||
got = append(got, v)
|
||||
}
|
||||
// Reverse our list.
|
||||
for i := 0; i < len(got)/2; i++ {
|
||||
got[i], got[len(got)-i-1] = got[len(got)-i-1], got[i]
|
||||
}
|
||||
if want := rang(100); !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAscendRange(t *testing.T) {
|
||||
tr := New(2)
|
||||
for _, v := range perm(100) {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
var got []Item
|
||||
tr.AscendRange(Int(40), Int(60), func(a Item) bool {
|
||||
got = append(got, a)
|
||||
return true
|
||||
})
|
||||
if want := rang(100)[40:60]; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want)
|
||||
}
|
||||
got = got[:0]
|
||||
tr.AscendRange(Int(40), Int(60), func(a Item) bool {
|
||||
if a.(Int) > 50 {
|
||||
return false
|
||||
}
|
||||
got = append(got, a)
|
||||
return true
|
||||
})
|
||||
if want := rang(100)[40:51]; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescendRange(t *testing.T) {
|
||||
tr := New(2)
|
||||
for _, v := range perm(100) {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
var got []Item
|
||||
tr.DescendRange(Int(60), Int(40), func(a Item) bool {
|
||||
got = append(got, a)
|
||||
return true
|
||||
})
|
||||
if want := rangrev(100)[39:59]; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("descendrange:\n got: %v\nwant: %v", got, want)
|
||||
}
|
||||
got = got[:0]
|
||||
tr.DescendRange(Int(60), Int(40), func(a Item) bool {
|
||||
if a.(Int) < 50 {
|
||||
return false
|
||||
}
|
||||
got = append(got, a)
|
||||
return true
|
||||
})
|
||||
if want := rangrev(100)[39:50]; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("descendrange:\n got: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
func TestAscendLessThan(t *testing.T) {
|
||||
tr := New(*btreeDegree)
|
||||
for _, v := range perm(100) {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
var got []Item
|
||||
tr.AscendLessThan(Int(60), func(a Item) bool {
|
||||
got = append(got, a)
|
||||
return true
|
||||
})
|
||||
if want := rang(100)[:60]; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want)
|
||||
}
|
||||
got = got[:0]
|
||||
tr.AscendLessThan(Int(60), func(a Item) bool {
|
||||
if a.(Int) > 50 {
|
||||
return false
|
||||
}
|
||||
got = append(got, a)
|
||||
return true
|
||||
})
|
||||
if want := rang(100)[:51]; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescendLessOrEqual(t *testing.T) {
|
||||
tr := New(*btreeDegree)
|
||||
for _, v := range perm(100) {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
var got []Item
|
||||
tr.DescendLessOrEqual(Int(40), func(a Item) bool {
|
||||
got = append(got, a)
|
||||
return true
|
||||
})
|
||||
if want := rangrev(100)[59:]; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("descendlessorequal:\n got: %v\nwant: %v", got, want)
|
||||
}
|
||||
got = got[:0]
|
||||
tr.DescendLessOrEqual(Int(60), func(a Item) bool {
|
||||
if a.(Int) < 50 {
|
||||
return false
|
||||
}
|
||||
got = append(got, a)
|
||||
return true
|
||||
})
|
||||
if want := rangrev(100)[39:50]; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("descendlessorequal:\n got: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
func TestAscendGreaterOrEqual(t *testing.T) {
|
||||
tr := New(*btreeDegree)
|
||||
for _, v := range perm(100) {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
var got []Item
|
||||
tr.AscendGreaterOrEqual(Int(40), func(a Item) bool {
|
||||
got = append(got, a)
|
||||
return true
|
||||
})
|
||||
if want := rang(100)[40:]; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want)
|
||||
}
|
||||
got = got[:0]
|
||||
tr.AscendGreaterOrEqual(Int(40), func(a Item) bool {
|
||||
if a.(Int) > 50 {
|
||||
return false
|
||||
}
|
||||
got = append(got, a)
|
||||
return true
|
||||
})
|
||||
if want := rang(100)[40:51]; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescendGreaterThan(t *testing.T) {
|
||||
tr := New(*btreeDegree)
|
||||
for _, v := range perm(100) {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
var got []Item
|
||||
tr.DescendGreaterThan(Int(40), func(a Item) bool {
|
||||
got = append(got, a)
|
||||
return true
|
||||
})
|
||||
if want := rangrev(100)[:59]; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("descendgreaterthan:\n got: %v\nwant: %v", got, want)
|
||||
}
|
||||
got = got[:0]
|
||||
tr.DescendGreaterThan(Int(40), func(a Item) bool {
|
||||
if a.(Int) < 50 {
|
||||
return false
|
||||
}
|
||||
got = append(got, a)
|
||||
return true
|
||||
})
|
||||
if want := rangrev(100)[:50]; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("descendgreaterthan:\n got: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
const benchmarkTreeSize = 10000
|
||||
|
||||
func BenchmarkInsert(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := perm(benchmarkTreeSize)
|
||||
b.StartTimer()
|
||||
i := 0
|
||||
for i < b.N {
|
||||
tr := New(*btreeDegree)
|
||||
for _, item := range insertP {
|
||||
tr.ReplaceOrInsert(item)
|
||||
i++
|
||||
if i >= b.N {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSeek(b *testing.B) {
|
||||
b.StopTimer()
|
||||
size := 100000
|
||||
insertP := perm(size)
|
||||
tr := New(*btreeDegree)
|
||||
for _, item := range insertP {
|
||||
tr.ReplaceOrInsert(item)
|
||||
}
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr.AscendGreaterOrEqual(Int(i%size), func(i Item) bool { return false })
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDeleteInsert(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := perm(benchmarkTreeSize)
|
||||
tr := New(*btreeDegree)
|
||||
for _, item := range insertP {
|
||||
tr.ReplaceOrInsert(item)
|
||||
}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr.Delete(insertP[i%benchmarkTreeSize])
|
||||
tr.ReplaceOrInsert(insertP[i%benchmarkTreeSize])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDeleteInsertCloneOnce(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := perm(benchmarkTreeSize)
|
||||
tr := New(*btreeDegree)
|
||||
for _, item := range insertP {
|
||||
tr.ReplaceOrInsert(item)
|
||||
}
|
||||
tr = tr.Clone()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr.Delete(insertP[i%benchmarkTreeSize])
|
||||
tr.ReplaceOrInsert(insertP[i%benchmarkTreeSize])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDeleteInsertCloneEachTime(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := perm(benchmarkTreeSize)
|
||||
tr := New(*btreeDegree)
|
||||
for _, item := range insertP {
|
||||
tr.ReplaceOrInsert(item)
|
||||
}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr = tr.Clone()
|
||||
tr.Delete(insertP[i%benchmarkTreeSize])
|
||||
tr.ReplaceOrInsert(insertP[i%benchmarkTreeSize])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDelete(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := perm(benchmarkTreeSize)
|
||||
removeP := perm(benchmarkTreeSize)
|
||||
b.StartTimer()
|
||||
i := 0
|
||||
for i < b.N {
|
||||
b.StopTimer()
|
||||
tr := New(*btreeDegree)
|
||||
for _, v := range insertP {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
b.StartTimer()
|
||||
for _, item := range removeP {
|
||||
tr.Delete(item)
|
||||
i++
|
||||
if i >= b.N {
|
||||
return
|
||||
}
|
||||
}
|
||||
if tr.Len() > 0 {
|
||||
panic(tr.Len())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGet(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := perm(benchmarkTreeSize)
|
||||
removeP := perm(benchmarkTreeSize)
|
||||
b.StartTimer()
|
||||
i := 0
|
||||
for i < b.N {
|
||||
b.StopTimer()
|
||||
tr := New(*btreeDegree)
|
||||
for _, v := range insertP {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
b.StartTimer()
|
||||
for _, item := range removeP {
|
||||
tr.Get(item)
|
||||
i++
|
||||
if i >= b.N {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGetCloneEachTime(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := perm(benchmarkTreeSize)
|
||||
removeP := perm(benchmarkTreeSize)
|
||||
b.StartTimer()
|
||||
i := 0
|
||||
for i < b.N {
|
||||
b.StopTimer()
|
||||
tr := New(*btreeDegree)
|
||||
for _, v := range insertP {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
b.StartTimer()
|
||||
for _, item := range removeP {
|
||||
tr = tr.Clone()
|
||||
tr.Get(item)
|
||||
i++
|
||||
if i >= b.N {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type byInts []Item
|
||||
|
||||
func (a byInts) Len() int {
|
||||
return len(a)
|
||||
}
|
||||
|
||||
func (a byInts) Less(i, j int) bool {
|
||||
return a[i].(Int) < a[j].(Int)
|
||||
}
|
||||
|
||||
func (a byInts) Swap(i, j int) {
|
||||
a[i], a[j] = a[j], a[i]
|
||||
}
|
||||
|
||||
func BenchmarkAscend(b *testing.B) {
|
||||
arr := perm(benchmarkTreeSize)
|
||||
tr := New(*btreeDegree)
|
||||
for _, v := range arr {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
sort.Sort(byInts(arr))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
j := 0
|
||||
tr.Ascend(func(item Item) bool {
|
||||
if item.(Int) != arr[j].(Int) {
|
||||
b.Fatalf("mismatch: expected: %v, got %v", arr[j].(Int), item.(Int))
|
||||
}
|
||||
j++
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDescend(b *testing.B) {
|
||||
arr := perm(benchmarkTreeSize)
|
||||
tr := New(*btreeDegree)
|
||||
for _, v := range arr {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
sort.Sort(byInts(arr))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
j := len(arr) - 1
|
||||
tr.Descend(func(item Item) bool {
|
||||
if item.(Int) != arr[j].(Int) {
|
||||
b.Fatalf("mismatch: expected: %v, got %v", arr[j].(Int), item.(Int))
|
||||
}
|
||||
j--
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
func BenchmarkAscendRange(b *testing.B) {
|
||||
arr := perm(benchmarkTreeSize)
|
||||
tr := New(*btreeDegree)
|
||||
for _, v := range arr {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
sort.Sort(byInts(arr))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
j := 100
|
||||
tr.AscendRange(Int(100), arr[len(arr)-100], func(item Item) bool {
|
||||
if item.(Int) != arr[j].(Int) {
|
||||
b.Fatalf("mismatch: expected: %v, got %v", arr[j].(Int), item.(Int))
|
||||
}
|
||||
j++
|
||||
return true
|
||||
})
|
||||
if j != len(arr)-100 {
|
||||
b.Fatalf("expected: %v, got %v", len(arr)-100, j)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDescendRange(b *testing.B) {
|
||||
arr := perm(benchmarkTreeSize)
|
||||
tr := New(*btreeDegree)
|
||||
for _, v := range arr {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
sort.Sort(byInts(arr))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
j := len(arr) - 100
|
||||
tr.DescendRange(arr[len(arr)-100], Int(100), func(item Item) bool {
|
||||
if item.(Int) != arr[j].(Int) {
|
||||
b.Fatalf("mismatch: expected: %v, got %v", arr[j].(Int), item.(Int))
|
||||
}
|
||||
j--
|
||||
return true
|
||||
})
|
||||
if j != 100 {
|
||||
b.Fatalf("expected: %v, got %v", len(arr)-100, j)
|
||||
}
|
||||
}
|
||||
}
|
||||
func BenchmarkAscendGreaterOrEqual(b *testing.B) {
|
||||
arr := perm(benchmarkTreeSize)
|
||||
tr := New(*btreeDegree)
|
||||
for _, v := range arr {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
sort.Sort(byInts(arr))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
j := 100
|
||||
k := 0
|
||||
tr.AscendGreaterOrEqual(Int(100), func(item Item) bool {
|
||||
if item.(Int) != arr[j].(Int) {
|
||||
b.Fatalf("mismatch: expected: %v, got %v", arr[j].(Int), item.(Int))
|
||||
}
|
||||
j++
|
||||
k++
|
||||
return true
|
||||
})
|
||||
if j != len(arr) {
|
||||
b.Fatalf("expected: %v, got %v", len(arr), j)
|
||||
}
|
||||
if k != len(arr)-100 {
|
||||
b.Fatalf("expected: %v, got %v", len(arr)-100, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
func BenchmarkDescendLessOrEqual(b *testing.B) {
|
||||
arr := perm(benchmarkTreeSize)
|
||||
tr := New(*btreeDegree)
|
||||
for _, v := range arr {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
sort.Sort(byInts(arr))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
j := len(arr) - 100
|
||||
k := len(arr)
|
||||
tr.DescendLessOrEqual(arr[len(arr)-100], func(item Item) bool {
|
||||
if item.(Int) != arr[j].(Int) {
|
||||
b.Fatalf("mismatch: expected: %v, got %v", arr[j].(Int), item.(Int))
|
||||
}
|
||||
j--
|
||||
k--
|
||||
return true
|
||||
})
|
||||
if j != -1 {
|
||||
b.Fatalf("expected: %v, got %v", -1, j)
|
||||
}
|
||||
if k != 99 {
|
||||
b.Fatalf("expected: %v, got %v", 99, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cloneTestSize = 10000
|
||||
|
||||
func cloneTest(t *testing.T, b *BTree, start int, p []Item, wg *sync.WaitGroup, trees *[]*BTree) {
|
||||
t.Logf("Starting new clone at %v", start)
|
||||
*trees = append(*trees, b)
|
||||
for i := start; i < cloneTestSize; i++ {
|
||||
b.ReplaceOrInsert(p[i])
|
||||
if i%(cloneTestSize/5) == 0 {
|
||||
wg.Add(1)
|
||||
go cloneTest(t, b.Clone(), i+1, p, wg, trees)
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func TestCloneConcurrentOperations(t *testing.T) {
|
||||
b := New(*btreeDegree)
|
||||
trees := []*BTree{}
|
||||
p := perm(cloneTestSize)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go cloneTest(t, b, 0, p, &wg, &trees)
|
||||
wg.Wait()
|
||||
want := rang(cloneTestSize)
|
||||
t.Logf("Starting equality checks on %d trees", len(trees))
|
||||
for i, tree := range trees {
|
||||
if !reflect.DeepEqual(want, all(tree)) {
|
||||
t.Errorf("tree %v mismatch", i)
|
||||
}
|
||||
}
|
||||
t.Log("Removing half from first half")
|
||||
toRemove := rang(cloneTestSize)[cloneTestSize/2:]
|
||||
for i := 0; i < len(trees)/2; i++ {
|
||||
tree := trees[i]
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
for _, item := range toRemove {
|
||||
tree.Delete(item)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
t.Log("Checking all values again")
|
||||
for i, tree := range trees {
|
||||
var wantpart []Item
|
||||
if i < len(trees)/2 {
|
||||
wantpart = want[:cloneTestSize/2]
|
||||
} else {
|
||||
wantpart = want
|
||||
}
|
||||
if got := all(tree); !reflect.DeepEqual(wantpart, got) {
|
||||
t.Errorf("tree %v mismatch, want %v got %v", i, len(want), len(got))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDeleteAndRestore(b *testing.B) {
|
||||
items := perm(16392)
|
||||
b.ResetTimer()
|
||||
b.Run(`CopyBigFreeList`, func(b *testing.B) {
|
||||
fl := NewFreeList(16392)
|
||||
tr := NewWithFreeList(*btreeDegree, fl)
|
||||
for _, v := range items {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
dels := make([]Item, 0, tr.Len())
|
||||
tr.Ascend(ItemIterator(func(b Item) bool {
|
||||
dels = append(dels, b)
|
||||
return true
|
||||
}))
|
||||
for _, del := range dels {
|
||||
tr.Delete(del)
|
||||
}
|
||||
// tr is now empty, we make a new empty copy of it.
|
||||
tr = NewWithFreeList(*btreeDegree, fl)
|
||||
for _, v := range items {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
}
|
||||
})
|
||||
b.Run(`Copy`, func(b *testing.B) {
|
||||
tr := New(*btreeDegree)
|
||||
for _, v := range items {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
dels := make([]Item, 0, tr.Len())
|
||||
tr.Ascend(ItemIterator(func(b Item) bool {
|
||||
dels = append(dels, b)
|
||||
return true
|
||||
}))
|
||||
for _, del := range dels {
|
||||
tr.Delete(del)
|
||||
}
|
||||
// tr is now empty, we make a new empty copy of it.
|
||||
tr = New(*btreeDegree)
|
||||
for _, v := range items {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
}
|
||||
})
|
||||
b.Run(`ClearBigFreelist`, func(b *testing.B) {
|
||||
fl := NewFreeList(16392)
|
||||
tr := NewWithFreeList(*btreeDegree, fl)
|
||||
for _, v := range items {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr.Clear(true)
|
||||
for _, v := range items {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
}
|
||||
})
|
||||
b.Run(`Clear`, func(b *testing.B) {
|
||||
tr := New(*btreeDegree)
|
||||
for _, v := range items {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr.Clear(true)
|
||||
for _, v := range items {
|
||||
tr.ReplaceOrInsert(v)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
78
util/buf/buffer_pool.go
Normal file
78
util/buf/buffer_pool.go
Normal file
@ -0,0 +1,78 @@
|
||||
package buf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/chubaofs/cfs/util"
|
||||
)
|
||||
|
||||
var (
|
||||
Buffers = NewBufferPool()
|
||||
)
|
||||
|
||||
const (
|
||||
HeaderBufferPoolSize = 8192
|
||||
BlockBufferPoolSize = 1024
|
||||
TinyBufferPoolSize = 100
|
||||
)
|
||||
|
||||
// BufferPool defines the struct of a buffered pool with 4 objects.
|
||||
type BufferPool struct {
|
||||
pools [3]chan []byte
|
||||
}
|
||||
|
||||
// NewBufferPool returns a new buffered pool.
|
||||
func NewBufferPool() (bufferP *BufferPool) {
|
||||
bufferP = &BufferPool{}
|
||||
bufferP.pools[0] = make(chan []byte, HeaderBufferPoolSize)
|
||||
bufferP.pools[1] = make(chan []byte, HeaderBufferPoolSize)
|
||||
bufferP.pools[2] = make(chan []byte, HeaderBufferPoolSize)
|
||||
|
||||
return bufferP
|
||||
}
|
||||
|
||||
func (bufferP *BufferPool) get(index int, size int) (data []byte) {
|
||||
select {
|
||||
case data = <-bufferP.pools[index]:
|
||||
return
|
||||
default:
|
||||
return make([]byte, size)
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the data based on the given size. Different size corresponds to different object in the pool.
|
||||
func (bufferP *BufferPool) Get(size int) (data []byte, err error) {
|
||||
if size == util.PacketHeaderSize {
|
||||
return bufferP.get(0, size), nil
|
||||
} else if size == util.BlockSize {
|
||||
return bufferP.get(1, size), nil
|
||||
} else if size == util.DefaultTinySizeLimit {
|
||||
return bufferP.get(2, size), nil
|
||||
}
|
||||
return nil, fmt.Errorf("can only support 45 or 65536 bytes")
|
||||
}
|
||||
|
||||
func (bufferP *BufferPool) put(index int, data []byte) {
|
||||
select {
|
||||
case bufferP.pools[index] <- data:
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Put puts the given data into the buffer pool.
|
||||
func (bufferP *BufferPool) Put(data []byte) {
|
||||
if data == nil {
|
||||
return
|
||||
}
|
||||
size := len(data)
|
||||
if size == util.PacketHeaderSize {
|
||||
bufferP.put(0, data)
|
||||
} else if size == util.BlockSize {
|
||||
bufferP.put(1, data)
|
||||
} else if size == util.DefaultTinySizeLimit {
|
||||
bufferP.put(2, data)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
141
util/config/config.go
Normal file
141
util/config/config.go
Normal file
@ -0,0 +1,141 @@
|
||||
// Copyright 2018 The Chubao Authors.
|
||||
//
|
||||
// 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 config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Config defines the struct of a configuration in general.
|
||||
type Config struct {
|
||||
data map[string]interface{}
|
||||
Raw []byte
|
||||
}
|
||||
|
||||
func newConfig() *Config {
|
||||
result := new(Config)
|
||||
result.data = make(map[string]interface{})
|
||||
return result
|
||||
}
|
||||
|
||||
// LoadConfigFile loads config information from a JSON file.
|
||||
func LoadConfigFile(filename string) *Config {
|
||||
result := newConfig()
|
||||
err := result.parse(filename)
|
||||
if err != nil {
|
||||
log.Fatalf("error loading config file %s: %s", filename, err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// LoadConfigString loads config information from a JSON string.
|
||||
func LoadConfigString(s string) *Config {
|
||||
result := newConfig()
|
||||
err := json.Unmarshal([]byte(s), &result.data)
|
||||
if err != nil {
|
||||
log.Fatalf("error parsing config string %s: %s", s, err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *Config) parse(fileName string) error {
|
||||
jsonFileBytes, err := ioutil.ReadFile(fileName)
|
||||
c.Raw = jsonFileBytes
|
||||
if err == nil {
|
||||
err = json.Unmarshal(jsonFileBytes, &c.data)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// GetString returns a string for the config key.
|
||||
func (c *Config) GetString(key string) string {
|
||||
x, present := c.data[key]
|
||||
if !present {
|
||||
return ""
|
||||
}
|
||||
if result, isString := x.(string); isString {
|
||||
return result
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetFloat returns a float value for the config key.
|
||||
func (c *Config) GetFloat(key string) float64 {
|
||||
x, present := c.data[key]
|
||||
if !present {
|
||||
return -1
|
||||
}
|
||||
if result, isFloat := x.(float64); isFloat {
|
||||
return result
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetBool returns a bool value for the config key.
|
||||
func (c *Config) GetBool(key string) bool {
|
||||
x, present := c.data[key]
|
||||
if !present {
|
||||
return false
|
||||
}
|
||||
if result, isBool := x.(bool); isBool {
|
||||
return result
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetBool returns a int value for the config key.
|
||||
func (c *Config) GetInt(key string) int64 {
|
||||
x, present := c.data[key]
|
||||
if !present {
|
||||
return 0
|
||||
}
|
||||
if result, isInt := x.(int64); isInt {
|
||||
return result
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetBool returns a int64 value for the config key.
|
||||
func (c *Config) GetInt64(key string) int64 {
|
||||
x, present := c.data[key]
|
||||
if !present {
|
||||
return 0
|
||||
}
|
||||
if result, isInt := x.(int64); isInt {
|
||||
return result
|
||||
}
|
||||
if result, isFloat := x.(float64); isFloat {
|
||||
return int64(result)
|
||||
}
|
||||
if result, isString := x.(string); isString {
|
||||
r, err := strconv.ParseInt(result, 10, 64)
|
||||
if err == nil {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetArray returns an array for the config key.
|
||||
func (c *Config) GetArray(key string) []interface{} {
|
||||
result, present := c.data[key]
|
||||
if !present {
|
||||
return []interface{}(nil)
|
||||
}
|
||||
return result.([]interface{})
|
||||
}
|
||||
190
util/conn_pool.go
Normal file
190
util/conn_pool.go
Normal file
@ -0,0 +1,190 @@
|
||||
// Copyright 2018 The Chubao Authors.
|
||||
//
|
||||
// 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 util
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Object struct {
|
||||
conn *net.TCPConn
|
||||
idle int64
|
||||
}
|
||||
|
||||
const (
|
||||
ConnectIdleTime = 30
|
||||
)
|
||||
|
||||
type ConnectPool struct {
|
||||
sync.RWMutex
|
||||
pools map[string]*Pool
|
||||
mincap int
|
||||
maxcap int
|
||||
timeout int64
|
||||
}
|
||||
|
||||
func NewConnectPool() (cp *ConnectPool) {
|
||||
cp = &ConnectPool{pools: make(map[string]*Pool), mincap: 5, maxcap: 80, timeout: int64(time.Second * ConnectIdleTime)}
|
||||
go cp.autoRelease()
|
||||
|
||||
return cp
|
||||
}
|
||||
|
||||
func (cp *ConnectPool) GetConnect(targetAddr string) (c *net.TCPConn, err error) {
|
||||
cp.RLock()
|
||||
pool, ok := cp.pools[targetAddr]
|
||||
cp.RUnlock()
|
||||
if !ok {
|
||||
cp.Lock()
|
||||
pool = NewPool(cp.mincap, cp.maxcap, cp.timeout, targetAddr)
|
||||
cp.pools[targetAddr] = pool
|
||||
cp.Unlock()
|
||||
}
|
||||
|
||||
return pool.GetConnectFromPool()
|
||||
}
|
||||
|
||||
func (cp *ConnectPool) PutConnect(c *net.TCPConn, forceClose bool) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
if forceClose {
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
addr := c.RemoteAddr().String()
|
||||
cp.RLock()
|
||||
pool, ok := cp.pools[addr]
|
||||
cp.RUnlock()
|
||||
if !ok {
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
object := &Object{conn: c, idle: time.Now().UnixNano()}
|
||||
pool.PutConnectObjectToPool(object)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (cp *ConnectPool) autoRelease() {
|
||||
for {
|
||||
pools := make([]*Pool, 0)
|
||||
cp.RLock()
|
||||
for _, pool := range cp.pools {
|
||||
pools = append(pools, pool)
|
||||
}
|
||||
cp.RUnlock()
|
||||
for _, pool := range pools {
|
||||
pool.autoRelease()
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type Pool struct {
|
||||
objects chan *Object
|
||||
mincap int
|
||||
maxcap int
|
||||
target string
|
||||
timeout int64
|
||||
}
|
||||
|
||||
func NewPool(min, max int, timeout int64, target string) (p *Pool) {
|
||||
p = new(Pool)
|
||||
p.mincap = min
|
||||
p.maxcap = max
|
||||
p.target = target
|
||||
p.objects = make(chan *Object, max)
|
||||
p.timeout = timeout
|
||||
p.initAllConnect()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Pool) initAllConnect() {
|
||||
for i := 0; i < p.mincap; i++ {
|
||||
c, err := net.Dial("tcp", p.target)
|
||||
if err == nil {
|
||||
conn := c.(*net.TCPConn)
|
||||
conn.SetKeepAlive(true)
|
||||
conn.SetNoDelay(true)
|
||||
o := &Object{conn: conn, idle: time.Now().UnixNano()}
|
||||
p.PutConnectObjectToPool(o)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) PutConnectObjectToPool(o *Object) {
|
||||
select {
|
||||
case p.objects <- o:
|
||||
return
|
||||
default:
|
||||
if o.conn != nil {
|
||||
o.conn.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) autoRelease() {
|
||||
connectLen := len(p.objects)
|
||||
for i := 0; i < connectLen; i++ {
|
||||
select {
|
||||
case o := <-p.objects:
|
||||
if time.Now().UnixNano()-int64(o.idle) > p.timeout {
|
||||
o.conn.Close()
|
||||
} else {
|
||||
p.PutConnectObjectToPool(o)
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) NewConnect(target string) (c *net.TCPConn, err error) {
|
||||
var connect net.Conn
|
||||
connect, err = net.Dial("tcp", p.target)
|
||||
if err == nil {
|
||||
conn := connect.(*net.TCPConn)
|
||||
conn.SetKeepAlive(true)
|
||||
conn.SetNoDelay(true)
|
||||
c = conn
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Pool) GetConnectFromPool() (c *net.TCPConn, err error) {
|
||||
var (
|
||||
o *Object
|
||||
)
|
||||
for i := 0; i < len(p.objects); i++ {
|
||||
select {
|
||||
case o = <-p.objects:
|
||||
if time.Now().UnixNano()-int64(o.idle) > p.timeout {
|
||||
o.conn.Close()
|
||||
o = nil
|
||||
break
|
||||
}
|
||||
return o.conn, nil
|
||||
default:
|
||||
return p.NewConnect(p.target)
|
||||
}
|
||||
}
|
||||
|
||||
return p.NewConnect(p.target)
|
||||
}
|
||||
211
util/master_helper.go
Normal file
211
util/master_helper.go
Normal file
@ -0,0 +1,211 @@
|
||||
// Copyright 2018 The Chubao Authors.
|
||||
//
|
||||
// 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 util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/chubaofs/cfs/util/log"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoValidMaster = errors.New("no valid master")
|
||||
)
|
||||
|
||||
// MasterHelper defines the helper struct to manage the master.
|
||||
type MasterHelper interface {
|
||||
AddNode(address string)
|
||||
Nodes() []string
|
||||
Leader() string
|
||||
Request(method, path string, param map[string]string, body []byte) (data []byte, err error)
|
||||
}
|
||||
|
||||
type masterHelper struct {
|
||||
sync.RWMutex
|
||||
masters []string
|
||||
leaderAddr string
|
||||
}
|
||||
|
||||
// AddNode add the given address as the master address.
|
||||
func (helper *masterHelper) AddNode(address string) {
|
||||
helper.Lock()
|
||||
helper.updateMaster(address)
|
||||
helper.Unlock()
|
||||
}
|
||||
|
||||
// Leader returns the current leader address.
|
||||
func (helper *masterHelper) Leader() (addr string) {
|
||||
helper.RLock()
|
||||
addr = helper.leaderAddr
|
||||
helper.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Change the leader address.
|
||||
func (helper *masterHelper) setLeader(addr string) {
|
||||
helper.Lock()
|
||||
helper.leaderAddr = addr
|
||||
helper.Unlock()
|
||||
}
|
||||
|
||||
// Request sends out the request through the helper.
|
||||
func (helper *masterHelper) Request(method, path string, param map[string]string, reqData []byte) (respData []byte, err error) {
|
||||
respData, err = helper.request(method, path, param, reqData)
|
||||
return
|
||||
}
|
||||
|
||||
func (helper *masterHelper) request(method, path string, param map[string]string, reqData []byte) (repsData []byte, err error) {
|
||||
leaderAddr, nodes := helper.prepareRequest()
|
||||
host := leaderAddr
|
||||
for i := -1; i < len(nodes); i++ {
|
||||
if i == -1 {
|
||||
if host == "" {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
host = nodes[i]
|
||||
}
|
||||
var resp *http.Response
|
||||
resp, err = helper.httpRequest(method, fmt.Sprintf("http://%s%s", host,
|
||||
path), param, reqData)
|
||||
if err != nil {
|
||||
log.LogErrorf("[masterHelper] %s", err)
|
||||
continue
|
||||
}
|
||||
stateCode := resp.StatusCode
|
||||
repsData, err = ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
log.LogErrorf("[masterHelper] %s", err)
|
||||
continue
|
||||
}
|
||||
switch stateCode {
|
||||
case http.StatusForbidden:
|
||||
curMasterAddr := strings.TrimSpace(string(repsData))
|
||||
curMasterAddr = strings.Replace(curMasterAddr, "\n", "", -1)
|
||||
if len(curMasterAddr) == 0 {
|
||||
log.LogErrorf("[masterHelper] request[%s] response statudCode"+
|
||||
"[403], respBody is empty", host)
|
||||
err = ErrNoValidMaster
|
||||
return
|
||||
}
|
||||
repsData, err = helper.request(method, path, param, reqData)
|
||||
return
|
||||
case http.StatusOK:
|
||||
if leaderAddr != host {
|
||||
helper.setLeader(host)
|
||||
}
|
||||
var body = &struct {
|
||||
Code int32 `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data json.RawMessage
|
||||
}{}
|
||||
if err := json.Unmarshal(repsData, body); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal response body err:%v", err)
|
||||
|
||||
}
|
||||
// o represent proto.ErrCodeSuccess
|
||||
if body.Code != 0 {
|
||||
return nil, fmt.Errorf("request error, code[%d], msg[%s]", body.Code, body.Msg)
|
||||
}
|
||||
return []byte(body.Data), nil
|
||||
default:
|
||||
log.LogErrorf("[masterHelper] master[%v] uri[%v] statusCode[%v] respBody[%v].",
|
||||
resp.Request.URL.String(), host, stateCode, string(repsData))
|
||||
continue
|
||||
}
|
||||
}
|
||||
err = ErrNoValidMaster
|
||||
return
|
||||
}
|
||||
|
||||
// Nodes returns all master addresses.
|
||||
func (helper *masterHelper) Nodes() (nodes []string) {
|
||||
helper.RLock()
|
||||
nodes = helper.masters
|
||||
helper.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// prepareRequest returns the leader address and all master addresses.
|
||||
func (helper *masterHelper) prepareRequest() (addr string, nodes []string) {
|
||||
helper.RLock()
|
||||
addr = helper.leaderAddr
|
||||
nodes = helper.masters
|
||||
helper.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (helper *masterHelper) httpRequest(method, url string, param map[string]string, reqData []byte) (resp *http.Response, err error) {
|
||||
client := &http.Client{}
|
||||
reader := bytes.NewReader(reqData)
|
||||
client.Timeout = time.Second * 3
|
||||
var req *http.Request
|
||||
fullUrl := helper.mergeRequestUrl(url, param)
|
||||
log.LogDebugf("action[httpRequest] method[%v] url[%v] reqBodyLen[%v].", method, fullUrl, len(reqData))
|
||||
if req, err = http.NewRequest(method, fullUrl, reader); err != nil {
|
||||
return
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Connection", "close")
|
||||
resp, err = client.Do(req)
|
||||
return
|
||||
}
|
||||
|
||||
func (helper *masterHelper) updateMaster(address string) {
|
||||
contains := false
|
||||
for _, master := range helper.masters {
|
||||
if master == address {
|
||||
contains = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !contains {
|
||||
helper.masters = append(helper.masters, address)
|
||||
}
|
||||
helper.leaderAddr = address
|
||||
}
|
||||
|
||||
func (helper *masterHelper) mergeRequestUrl(url string, params map[string]string) string {
|
||||
if params != nil && len(params) > 0 {
|
||||
buff := bytes.NewBuffer([]byte(url))
|
||||
isFirstParam := true
|
||||
for k, v := range params {
|
||||
if isFirstParam {
|
||||
buff.WriteString("?")
|
||||
isFirstParam = false
|
||||
} else {
|
||||
buff.WriteString("&")
|
||||
}
|
||||
buff.WriteString(k)
|
||||
buff.WriteString("=")
|
||||
buff.WriteString(v)
|
||||
}
|
||||
return buff.String()
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
// NewMasterHelper returns a new MasterHelper instance.
|
||||
func NewMasterHelper() MasterHelper {
|
||||
return &masterHelper{}
|
||||
}
|
||||
67
util/mem.go
Normal file
67
util/mem.go
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright 2018 The Chubao Authors.
|
||||
//
|
||||
// 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 util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const MEMINFO = "/proc/meminfo"
|
||||
|
||||
// GetMemInfo returns the memory information.
|
||||
func GetMemInfo() (total, used uint64, err error) {
|
||||
fp, err := os.Open(MEMINFO)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// TODO Unhandled errors
|
||||
defer fp.Close()
|
||||
var (
|
||||
val uint64
|
||||
free uint64
|
||||
buffer uint64
|
||||
cached uint64
|
||||
)
|
||||
scan := bufio.NewScanner(fp)
|
||||
for scan.Scan() {
|
||||
line := scan.Text()
|
||||
fields := strings.Split(line, ":")
|
||||
if len(fields) != 2 {
|
||||
continue
|
||||
}
|
||||
key := fields[0]
|
||||
value := strings.TrimSpace(fields[1])
|
||||
value = strings.Replace(value, " kB", "", -1)
|
||||
val, err = strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch key {
|
||||
case "MemTotal":
|
||||
total = val * KB
|
||||
case "MemFree":
|
||||
free = val * KB
|
||||
case "Buffers":
|
||||
buffer = val * KB
|
||||
case "Cached":
|
||||
cached = val * KB
|
||||
}
|
||||
}
|
||||
used = total - free - buffer - cached
|
||||
return
|
||||
}
|
||||
80
util/unit.go
Normal file
80
util/unit.go
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright 2018 The Chubao Authors.
|
||||
//
|
||||
// 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 util
|
||||
|
||||
import "regexp"
|
||||
|
||||
const (
|
||||
_ = iota
|
||||
KB = 1 << (10 * iota)
|
||||
MB
|
||||
GB
|
||||
TB
|
||||
PB
|
||||
DefaultDataPartitionSize = 120 * GB
|
||||
TaskWorkerInterval = 1
|
||||
)
|
||||
|
||||
const (
|
||||
BlockCount = 1024
|
||||
BlockSize = 65536 * 2
|
||||
ReadBlockSize = BlockSize
|
||||
PerBlockCrcSize = 4
|
||||
ExtentSize = BlockCount * BlockSize
|
||||
PacketHeaderSize = 57
|
||||
BlockHeaderSize = 4096
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultTinySizeLimit = 1 * MB // TODO explain tiny extent?
|
||||
)
|
||||
|
||||
func Min(a, b int) int {
|
||||
if a > b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func Max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// IsIPV4 returns if it is IPV4 address.
|
||||
func IsIPV4(val interface{}) bool {
|
||||
ip4Pattern := `((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)`
|
||||
ip4 := regexpCompile(ip4Pattern)
|
||||
return isMatch(ip4, val)
|
||||
}
|
||||
|
||||
func regexpCompile(str string) *regexp.Regexp {
|
||||
return regexp.MustCompile("^" + str + "$")
|
||||
}
|
||||
|
||||
func isMatch(exp *regexp.Regexp, val interface{}) bool {
|
||||
switch v := val.(type) {
|
||||
case []rune:
|
||||
return exp.MatchString(string(v))
|
||||
case []byte:
|
||||
return exp.Match(v)
|
||||
case string:
|
||||
return exp.MatchString(v)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
93
vendor/bazil.org/fuse/LICENSE
generated
vendored
Normal file
93
vendor/bazil.org/fuse/LICENSE
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
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/bazil.org/fuse/README.md
generated
vendored
Normal file
23
vendor/bazil.org/fuse/README.md
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
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.
|
||||
121
vendor/bazil.org/fuse/block_pool.go
generated
vendored
Normal file
121
vendor/bazil.org/fuse/block_pool.go
generated
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
NumOfBlockPool = 9
|
||||
)
|
||||
|
||||
const (
|
||||
BlockSize = 4096
|
||||
)
|
||||
|
||||
const (
|
||||
PoolSize4K = BlockSize * (1 << iota)
|
||||
PoolSize8K
|
||||
PoolSize16K
|
||||
PoolSize32K
|
||||
PoolSize64K
|
||||
PoolSize128K
|
||||
PoolSize256K
|
||||
PoolSize512K
|
||||
PoolSize1024K
|
||||
)
|
||||
|
||||
const (
|
||||
PoolSizeWithHeader4K = BlockSize*(1<<iota) + OutHeaderSize
|
||||
PoolSizeWithHeader8K
|
||||
PoolSizeWithHeader16K
|
||||
PoolSizeWithHeader32K
|
||||
PoolSizeWithHeader64K
|
||||
PoolSizeWithHeader128K
|
||||
PoolSizeWithHeader256K
|
||||
PoolSizeWithHeader512K
|
||||
PoolSizeWithHeader1024K
|
||||
)
|
||||
|
||||
var ReadBlockPool = [NumOfBlockPool]*sync.Pool{}
|
||||
|
||||
func InitReadBlockPool() {
|
||||
ReadBlockPool[0] = &sync.Pool{New: func() interface{} {
|
||||
return make([]byte, PoolSizeWithHeader4K)
|
||||
}}
|
||||
ReadBlockPool[1] = &sync.Pool{New: func() interface{} {
|
||||
return make([]byte, PoolSizeWithHeader8K)
|
||||
}}
|
||||
ReadBlockPool[2] = &sync.Pool{New: func() interface{} {
|
||||
return make([]byte, PoolSizeWithHeader16K)
|
||||
}}
|
||||
ReadBlockPool[3] = &sync.Pool{New: func() interface{} {
|
||||
return make([]byte, PoolSizeWithHeader32K)
|
||||
}}
|
||||
ReadBlockPool[4] = &sync.Pool{New: func() interface{} {
|
||||
return make([]byte, PoolSizeWithHeader64K)
|
||||
}}
|
||||
ReadBlockPool[5] = &sync.Pool{New: func() interface{} {
|
||||
return make([]byte, PoolSizeWithHeader128K)
|
||||
}}
|
||||
ReadBlockPool[6] = &sync.Pool{New: func() interface{} {
|
||||
return make([]byte, PoolSizeWithHeader256K)
|
||||
}}
|
||||
ReadBlockPool[7] = &sync.Pool{New: func() interface{} {
|
||||
return make([]byte, PoolSizeWithHeader512K)
|
||||
}}
|
||||
ReadBlockPool[8] = &sync.Pool{New: func() interface{} {
|
||||
return make([]byte, PoolSizeWithHeader1024K)
|
||||
}}
|
||||
}
|
||||
|
||||
func GetBlockBuf(size int) []byte {
|
||||
var data []byte
|
||||
switch size {
|
||||
case PoolSize4K:
|
||||
data = ReadBlockPool[0].Get().([]byte)
|
||||
case PoolSize8K:
|
||||
data = ReadBlockPool[1].Get().([]byte)
|
||||
case PoolSize16K:
|
||||
data = ReadBlockPool[2].Get().([]byte)
|
||||
case PoolSize32K:
|
||||
data = ReadBlockPool[3].Get().([]byte)
|
||||
case PoolSize64K:
|
||||
data = ReadBlockPool[4].Get().([]byte)
|
||||
case PoolSize128K:
|
||||
data = ReadBlockPool[5].Get().([]byte)
|
||||
case PoolSize256K:
|
||||
data = ReadBlockPool[6].Get().([]byte)
|
||||
case PoolSize512K:
|
||||
data = ReadBlockPool[7].Get().([]byte)
|
||||
case PoolSize1024K:
|
||||
data = ReadBlockPool[8].Get().([]byte)
|
||||
default:
|
||||
data = make([]byte, OutHeaderSize+size)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func PutBlockBuf(data []byte) {
|
||||
switch len(data) {
|
||||
case PoolSizeWithHeader4K:
|
||||
ReadBlockPool[0].Put(data)
|
||||
case PoolSizeWithHeader8K:
|
||||
ReadBlockPool[1].Put(data)
|
||||
case PoolSizeWithHeader16K:
|
||||
ReadBlockPool[2].Put(data)
|
||||
case PoolSizeWithHeader32K:
|
||||
ReadBlockPool[3].Put(data)
|
||||
case PoolSizeWithHeader64K:
|
||||
ReadBlockPool[4].Put(data)
|
||||
case PoolSizeWithHeader128K:
|
||||
ReadBlockPool[5].Put(data)
|
||||
case PoolSizeWithHeader256K:
|
||||
ReadBlockPool[6].Put(data)
|
||||
case PoolSizeWithHeader512K:
|
||||
ReadBlockPool[7].Put(data)
|
||||
case PoolSizeWithHeader1024K:
|
||||
ReadBlockPool[8].Put(data)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
35
vendor/bazil.org/fuse/buffer.go
generated
vendored
Normal file
35
vendor/bazil.org/fuse/buffer.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
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/bazil.org/fuse/debug.go
generated
vendored
Normal file
21
vendor/bazil.org/fuse/debug.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
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
|
||||
4
vendor/bazil.org/fuse/doc/.gitignore
generated
vendored
Normal file
4
vendor/bazil.org/fuse/doc/.gitignore
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/*.seq.svg
|
||||
|
||||
# not ignoring *.seq.png; we want those committed to the repo
|
||||
# for embedding on Github
|
||||
6
vendor/bazil.org/fuse/doc/README.md
generated
vendored
Normal file
6
vendor/bazil.org/fuse/doc/README.md
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# 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)
|
||||
32
vendor/bazil.org/fuse/doc/mount-linux-error-init.seq
generated
vendored
Normal file
32
vendor/bazil.org/fuse/doc/mount-linux-error-init.seq
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
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 ...
|
||||
}
|
||||
BIN
vendor/bazil.org/fuse/doc/mount-linux-error-init.seq.png
generated
vendored
Normal file
BIN
vendor/bazil.org/fuse/doc/mount-linux-error-init.seq.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
41
vendor/bazil.org/fuse/doc/mount-linux.seq
generated
vendored
Normal file
41
vendor/bazil.org/fuse/doc/mount-linux.seq
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
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/bazil.org/fuse/doc/mount-linux.seq.png
generated
vendored
Normal file
BIN
vendor/bazil.org/fuse/doc/mount-linux.seq.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
32
vendor/bazil.org/fuse/doc/mount-osx-error-init.seq
generated
vendored
Normal file
32
vendor/bazil.org/fuse/doc/mount-osx-error-init.seq
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
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 ...
|
||||
}
|
||||
BIN
vendor/bazil.org/fuse/doc/mount-osx-error-init.seq.png
generated
vendored
Normal file
BIN
vendor/bazil.org/fuse/doc/mount-osx-error-init.seq.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
45
vendor/bazil.org/fuse/doc/mount-osx.seq
generated
vendored
Normal file
45
vendor/bazil.org/fuse/doc/mount-osx.seq
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
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/bazil.org/fuse/doc/mount-osx.seq.png
generated
vendored
Normal file
BIN
vendor/bazil.org/fuse/doc/mount-osx.seq.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
30
vendor/bazil.org/fuse/doc/mount-sequence.md
generated
vendored
Normal file
30
vendor/bazil.org/fuse/doc/mount-sequence.md
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
# 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.
|
||||
|
||||

|
||||
|
||||
On OS X, the mount becomes visible only after `InitRequest` (and maybe
|
||||
more) have been served.
|
||||
|
||||

|
||||
|
||||
|
||||
## Errors
|
||||
|
||||
Let's see what happens if `InitRequest` gets an error response. On
|
||||
Linux, the mountpoint is there but all operations will fail:
|
||||
|
||||

|
||||
|
||||
On OS X, the mount never happened:
|
||||
|
||||

|
||||
16
vendor/bazil.org/fuse/doc/writing-docs.md
generated
vendored
Normal file
16
vendor/bazil.org/fuse/doc/writing-docs.md
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
# 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/bazil.org/fuse/error_darwin.go
generated
vendored
Normal file
17
vendor/bazil.org/fuse/error_darwin.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
ENOATTR = Errno(syscall.ENOATTR)
|
||||
)
|
||||
|
||||
const (
|
||||
errNoXattr = ENOATTR
|
||||
)
|
||||
|
||||
func init() {
|
||||
errnoNames[errNoXattr] = "ENOATTR"
|
||||
}
|
||||
15
vendor/bazil.org/fuse/error_freebsd.go
generated
vendored
Normal file
15
vendor/bazil.org/fuse/error_freebsd.go
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
package fuse
|
||||
|
||||
import "syscall"
|
||||
|
||||
const (
|
||||
ENOATTR = Errno(syscall.ENOATTR)
|
||||
)
|
||||
|
||||
const (
|
||||
errNoXattr = ENOATTR
|
||||
)
|
||||
|
||||
func init() {
|
||||
errnoNames[errNoXattr] = "ENOATTR"
|
||||
}
|
||||
17
vendor/bazil.org/fuse/error_linux.go
generated
vendored
Normal file
17
vendor/bazil.org/fuse/error_linux.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
ENODATA = Errno(syscall.ENODATA)
|
||||
)
|
||||
|
||||
const (
|
||||
errNoXattr = ENODATA
|
||||
)
|
||||
|
||||
func init() {
|
||||
errnoNames[errNoXattr] = "ENODATA"
|
||||
}
|
||||
31
vendor/bazil.org/fuse/error_std.go
generated
vendored
Normal file
31
vendor/bazil.org/fuse/error_std.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
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
|
||||
184
vendor/bazil.org/fuse/examples/clockfs/clockfs.go
generated
vendored
Normal file
184
vendor/bazil.org/fuse/examples/clockfs/clockfs.go
generated
vendored
Normal file
@ -0,0 +1,184 @@
|
||||
// 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/bazil.org/fuse/examples/hellofs/hello.go
generated
vendored
Normal file
101
vendor/bazil.org/fuse/examples/hellofs/hello.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
// 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
|
||||
}
|
||||
54
vendor/bazil.org/fuse/fs/bench/bench_create_test.go
generated
vendored
Normal file
54
vendor/bazil.org/fuse/fs/bench/bench_create_test.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
package bench_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
"bazil.org/fuse/fs/fstestutil"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type dummyFile struct {
|
||||
fstestutil.File
|
||||
}
|
||||
|
||||
type benchCreateDir struct {
|
||||
fstestutil.Dir
|
||||
}
|
||||
|
||||
var _ fs.NodeCreater = (*benchCreateDir)(nil)
|
||||
|
||||
func (f *benchCreateDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
|
||||
child := &dummyFile{}
|
||||
return child, child, nil
|
||||
}
|
||||
|
||||
func BenchmarkCreate(b *testing.B) {
|
||||
f := &benchCreateDir{}
|
||||
mnt, err := fstestutil.MountedT(b, fstestutil.SimpleFS{f}, nil)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer mnt.Close()
|
||||
|
||||
// prepare file names to decrease test overhead
|
||||
names := make([]string, 0, b.N)
|
||||
for i := 0; i < b.N; i++ {
|
||||
// zero-padded so cost stays the same on every iteration
|
||||
names = append(names, mnt.Dir+"/"+fmt.Sprintf("%08x", i))
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
f, err := os.Create(names[i])
|
||||
if err != nil {
|
||||
b.Fatalf("WriteFile: %v", err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
b.StopTimer()
|
||||
}
|
||||
42
vendor/bazil.org/fuse/fs/bench/bench_lookup_test.go
generated
vendored
Normal file
42
vendor/bazil.org/fuse/fs/bench/bench_lookup_test.go
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
package bench_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
"bazil.org/fuse/fs/fstestutil"
|
||||
)
|
||||
|
||||
type benchLookupDir struct {
|
||||
fstestutil.Dir
|
||||
}
|
||||
|
||||
var _ fs.NodeRequestLookuper = (*benchLookupDir)(nil)
|
||||
|
||||
func (f *benchLookupDir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (fs.Node, error) {
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
|
||||
func BenchmarkLookup(b *testing.B) {
|
||||
f := &benchLookupDir{}
|
||||
mnt, err := fstestutil.MountedT(b, fstestutil.SimpleFS{f}, nil)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer mnt.Close()
|
||||
|
||||
name := mnt.Dir + "/does-not-exist"
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := os.Stat(name); !os.IsNotExist(err) {
|
||||
b.Fatalf("Stat: wrong error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
b.StopTimer()
|
||||
}
|
||||
268
vendor/bazil.org/fuse/fs/bench/bench_readwrite_test.go
generated
vendored
Normal file
268
vendor/bazil.org/fuse/fs/bench/bench_readwrite_test.go
generated
vendored
Normal file
@ -0,0 +1,268 @@
|
||||
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/bazil.org/fuse/fs/bench/doc.go
generated
vendored
Normal file
5
vendor/bazil.org/fuse/fs/bench/doc.go
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
// 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
|
||||
70
vendor/bazil.org/fuse/fs/fstestutil/checkdir.go
generated
vendored
Normal file
70
vendor/bazil.org/fuse/fs/fstestutil/checkdir.go
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
package fstestutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
// FileInfoCheck is a function that validates an os.FileInfo according
|
||||
// to some criteria.
|
||||
type FileInfoCheck func(fi os.FileInfo) error
|
||||
|
||||
type checkDirError struct {
|
||||
missing map[string]struct{}
|
||||
extra map[string]os.FileMode
|
||||
}
|
||||
|
||||
func (e *checkDirError) Error() string {
|
||||
return fmt.Sprintf("wrong directory contents: missing %v, extra %v", e.missing, e.extra)
|
||||
}
|
||||
|
||||
// CheckDir checks the contents of the directory at path, making sure
|
||||
// every directory entry listed in want is present. If the check is
|
||||
// not nil, it must also pass.
|
||||
//
|
||||
// If want contains the impossible filename "", unexpected files are
|
||||
// checked with that. If the key is not in want, unexpected files are
|
||||
// an error.
|
||||
//
|
||||
// Missing entries, that are listed in want but not seen, are an
|
||||
// error.
|
||||
func CheckDir(path string, want map[string]FileInfoCheck) error {
|
||||
problems := &checkDirError{
|
||||
missing: make(map[string]struct{}, len(want)),
|
||||
extra: make(map[string]os.FileMode),
|
||||
}
|
||||
for k := range want {
|
||||
if k == "" {
|
||||
continue
|
||||
}
|
||||
problems.missing[k] = struct{}{}
|
||||
}
|
||||
|
||||
fis, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot read directory: %v", err)
|
||||
}
|
||||
|
||||
for _, fi := range fis {
|
||||
check, ok := want[fi.Name()]
|
||||
if !ok {
|
||||
check, ok = want[""]
|
||||
}
|
||||
if !ok {
|
||||
problems.extra[fi.Name()] = fi.Mode()
|
||||
continue
|
||||
}
|
||||
delete(problems.missing, fi.Name())
|
||||
if check != nil {
|
||||
if err := check(fi); err != nil {
|
||||
return fmt.Errorf("check failed: %v: %v", fi.Name(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(problems.missing) > 0 || len(problems.extra) > 0 {
|
||||
return problems
|
||||
}
|
||||
return nil
|
||||
}
|
||||
65
vendor/bazil.org/fuse/fs/fstestutil/debug.go
generated
vendored
Normal file
65
vendor/bazil.org/fuse/fs/fstestutil/debug.go
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
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
vendor/bazil.org/fuse/fs/fstestutil/doc.go
generated
vendored
Normal file
1
vendor/bazil.org/fuse/fs/fstestutil/doc.go
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
package fstestutil // import "bazil.org/fuse/fs/fstestutil"
|
||||
141
vendor/bazil.org/fuse/fs/fstestutil/mounted.go
generated
vendored
Normal file
141
vendor/bazil.org/fuse/fs/fstestutil/mounted.go
generated
vendored
Normal file
@ -0,0 +1,141 @@
|
||||
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...)
|
||||
}
|
||||
26
vendor/bazil.org/fuse/fs/fstestutil/mountinfo.go
generated
vendored
Normal file
26
vendor/bazil.org/fuse/fs/fstestutil/mountinfo.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
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)
|
||||
}
|
||||
29
vendor/bazil.org/fuse/fs/fstestutil/mountinfo_darwin.go
generated
vendored
Normal file
29
vendor/bazil.org/fuse/fs/fstestutil/mountinfo_darwin.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
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
|
||||
}
|
||||
7
vendor/bazil.org/fuse/fs/fstestutil/mountinfo_freebsd.go
generated
vendored
Normal file
7
vendor/bazil.org/fuse/fs/fstestutil/mountinfo_freebsd.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
package fstestutil
|
||||
|
||||
import "errors"
|
||||
|
||||
func getMountInfo(mnt string) (*MountInfo, error) {
|
||||
return nil, errors.New("FreeBSD has no useful mount information")
|
||||
}
|
||||
51
vendor/bazil.org/fuse/fs/fstestutil/mountinfo_linux.go
generated
vendored
Normal file
51
vendor/bazil.org/fuse/fs/fstestutil/mountinfo_linux.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
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
|
||||
}
|
||||
28
vendor/bazil.org/fuse/fs/fstestutil/record/buffer.go
generated
vendored
Normal file
28
vendor/bazil.org/fuse/fs/fstestutil/record/buffer.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
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()
|
||||
}
|
||||
409
vendor/bazil.org/fuse/fs/fstestutil/record/record.go
generated
vendored
Normal file
409
vendor/bazil.org/fuse/fs/fstestutil/record/record.go
generated
vendored
Normal file
@ -0,0 +1,409 @@
|
||||
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))
|
||||
}
|
||||
55
vendor/bazil.org/fuse/fs/fstestutil/record/wait.go
generated
vendored
Normal file
55
vendor/bazil.org/fuse/fs/fstestutil/record/wait.go
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
55
vendor/bazil.org/fuse/fs/fstestutil/testfs.go
generated
vendored
Normal file
55
vendor/bazil.org/fuse/fs/fstestutil/testfs.go
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
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/bazil.org/fuse/fs/helpers_test.go
generated
vendored
Normal file
67
vendor/bazil.org/fuse/fs/helpers_test.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
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())
|
||||
}
|
||||
1569
vendor/bazil.org/fuse/fs/serve.go
generated
vendored
Normal file
1569
vendor/bazil.org/fuse/fs/serve.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
30
vendor/bazil.org/fuse/fs/serve_darwin_test.go
generated
vendored
Normal file
30
vendor/bazil.org/fuse/fs/serve_darwin_test.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
package fs_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"bazil.org/fuse/fs/fstestutil"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type exchangeData struct {
|
||||
fstestutil.File
|
||||
// this struct cannot be zero size or multiple instances may look identical
|
||||
_ int
|
||||
}
|
||||
|
||||
func TestExchangeDataNotSupported(t *testing.T) {
|
||||
t.Parallel()
|
||||
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{
|
||||
"one": &exchangeData{},
|
||||
"two": &exchangeData{},
|
||||
}}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer mnt.Close()
|
||||
|
||||
if err := unix.Exchangedata(mnt.Dir+"/one", mnt.Dir+"/two", 0); err != unix.ENOTSUP {
|
||||
t.Fatalf("expected ENOTSUP from exchangedata: %v", err)
|
||||
}
|
||||
}
|
||||
2867
vendor/bazil.org/fuse/fs/serve_test.go
generated
vendored
Normal file
2867
vendor/bazil.org/fuse/fs/serve_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
99
vendor/bazil.org/fuse/fs/tree.go
generated
vendored
Normal file
99
vendor/bazil.org/fuse/fs/tree.go
generated
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
// 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
|
||||
}
|
||||
2310
vendor/bazil.org/fuse/fuse.go
generated
vendored
Normal file
2310
vendor/bazil.org/fuse/fuse.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9
vendor/bazil.org/fuse/fuse_darwin.go
generated
vendored
Normal file
9
vendor/bazil.org/fuse/fuse_darwin.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
package fuse
|
||||
|
||||
// Maximum file write size we are prepared to receive from the kernel.
|
||||
//
|
||||
// This value has to be >=16MB or OSXFUSE (3.4.0 observed) will
|
||||
// forcibly close the /dev/fuse file descriptor on a Setxattr with a
|
||||
// 16MB value. See TestSetxattr16MB and
|
||||
// https://github.com/bazil/fuse/issues/42
|
||||
const maxWrite = 16 * 1024 * 1024
|
||||
6
vendor/bazil.org/fuse/fuse_freebsd.go
generated
vendored
Normal file
6
vendor/bazil.org/fuse/fuse_freebsd.go
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
package fuse
|
||||
|
||||
// Maximum file write size we are prepared to receive from the kernel.
|
||||
//
|
||||
// This number is just a guess.
|
||||
const maxWrite = 128 * 1024
|
||||
776
vendor/bazil.org/fuse/fuse_kernel.go
generated
vendored
Normal file
776
vendor/bazil.org/fuse/fuse_kernel.go
generated
vendored
Normal file
@ -0,0 +1,776 @@
|
||||
// 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
|
||||
}
|
||||
|
||||
const OutHeaderSize = int(unsafe.Sizeof(outHeader{}))
|
||||
|
||||
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/bazil.org/fuse/fuse_kernel_darwin.go
generated
vendored
Normal file
88
vendor/bazil.org/fuse/fuse_kernel_darwin.go
generated
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
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/bazil.org/fuse/fuse_kernel_freebsd.go
generated
vendored
Normal file
62
vendor/bazil.org/fuse/fuse_kernel_freebsd.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
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/bazil.org/fuse/fuse_kernel_linux.go
generated
vendored
Normal file
70
vendor/bazil.org/fuse/fuse_kernel_linux.go
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
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/bazil.org/fuse/fuse_kernel_std.go
generated
vendored
Normal file
1
vendor/bazil.org/fuse/fuse_kernel_std.go
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
package fuse
|
||||
63
vendor/bazil.org/fuse/fuse_kernel_test.go
generated
vendored
Normal file
63
vendor/bazil.org/fuse/fuse_kernel_test.go
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
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/bazil.org/fuse/fuse_linux.go
generated
vendored
Normal file
7
vendor/bazil.org/fuse/fuse_linux.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
package fuse
|
||||
|
||||
// Maximum file write size we are prepared to receive from the kernel.
|
||||
//
|
||||
// Linux 4.2.0 has been observed to cap this value at 128kB
|
||||
// (FUSE_MAX_PAGES_PER_REQ=32, 4kB pages).
|
||||
const maxWrite = 1024 * 1024
|
||||
20
vendor/bazil.org/fuse/fuseutil/fuseutil.go
generated
vendored
Normal file
20
vendor/bazil.org/fuse/fuseutil/fuseutil.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
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/bazil.org/fuse/mount.go
generated
vendored
Normal file
38
vendor/bazil.org/fuse/mount.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrOSXFUSENotFound is returned from Mount when the OSXFUSE
|
||||
// installation is not detected.
|
||||
//
|
||||
// Only happens on OS X. Make sure OSXFUSE is installed, or see
|
||||
// OSXFUSELocations for customization.
|
||||
ErrOSXFUSENotFound = errors.New("cannot locate OSXFUSE")
|
||||
)
|
||||
|
||||
func neverIgnoreLine(line string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func lineLogger(wg *sync.WaitGroup, prefix string, ignore func(line string) bool, r io.ReadCloser) {
|
||||
defer wg.Done()
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if ignore(line) {
|
||||
continue
|
||||
}
|
||||
log.Printf("%s: %s", prefix, line)
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.Printf("%s, error reading: %v", prefix, err)
|
||||
}
|
||||
}
|
||||
208
vendor/bazil.org/fuse/mount_darwin.go
generated
vendored
Normal file
208
vendor/bazil.org/fuse/mount_darwin.go
generated
vendored
Normal file
@ -0,0 +1,208 @@
|
||||
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/bazil.org/fuse/mount_freebsd.go
generated
vendored
Normal file
111
vendor/bazil.org/fuse/mount_freebsd.go
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
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/bazil.org/fuse/mount_linux.go
generated
vendored
Normal file
150
vendor/bazil.org/fuse/mount_linux.go
generated
vendored
Normal file
@ -0,0 +1,150 @@
|
||||
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
|
||||
}
|
||||
322
vendor/bazil.org/fuse/options.go
generated
vendored
Normal file
322
vendor/bazil.org/fuse/options.go
generated
vendored
Normal file
@ -0,0 +1,322 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
func AutoInvalData(enable int64) MountOption {
|
||||
if enable > 0 {
|
||||
return func(conf *mountConfig) error {
|
||||
conf.initFlags |= InitAutoInvalData
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return func(conf *mountConfig) error {
|
||||
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
|
||||
}
|
||||
}
|
||||
64
vendor/bazil.org/fuse/options_daemon_timeout_test.go
generated
vendored
Normal file
64
vendor/bazil.org/fuse/options_daemon_timeout_test.go
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
// Test for adjustable timeout between a FUSE request and the daemon's response.
|
||||
//
|
||||
// +build darwin freebsd
|
||||
|
||||
package fuse_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
"bazil.org/fuse/fs/fstestutil"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type slowCreaterDir struct {
|
||||
fstestutil.Dir
|
||||
}
|
||||
|
||||
var _ fs.NodeCreater = slowCreaterDir{}
|
||||
|
||||
func (c slowCreaterDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
|
||||
time.Sleep(10 * time.Second)
|
||||
// pick a really distinct error, to identify it later
|
||||
return nil, nil, fuse.Errno(syscall.ENAMETOOLONG)
|
||||
}
|
||||
|
||||
func TestMountOptionDaemonTimeout(t *testing.T) {
|
||||
if runtime.GOOS != "darwin" && runtime.GOOS != "freebsd" {
|
||||
return
|
||||
}
|
||||
if testing.Short() {
|
||||
t.Skip("skipping time-based test in short mode")
|
||||
}
|
||||
t.Parallel()
|
||||
|
||||
mnt, err := fstestutil.MountedT(t,
|
||||
fstestutil.SimpleFS{slowCreaterDir{}},
|
||||
nil,
|
||||
fuse.DaemonTimeout("2"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer mnt.Close()
|
||||
|
||||
// This should fail by the kernel timing out the request.
|
||||
f, err := os.Create(mnt.Dir + "/child")
|
||||
if err == nil {
|
||||
f.Close()
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
perr, ok := err.(*os.PathError)
|
||||
if !ok {
|
||||
t.Fatalf("expected PathError, got %T: %v", err, err)
|
||||
}
|
||||
if perr.Err == syscall.ENAMETOOLONG {
|
||||
t.Fatalf("expected other than ENAMETOOLONG, got %T: %v", err, err)
|
||||
}
|
||||
}
|
||||
35
vendor/bazil.org/fuse/options_darwin.go
generated
vendored
Normal file
35
vendor/bazil.org/fuse/options_darwin.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
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/bazil.org/fuse/options_freebsd.go
generated
vendored
Normal file
28
vendor/bazil.org/fuse/options_freebsd.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
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/bazil.org/fuse/options_helper_test.go
generated
vendored
Normal file
10
vendor/bazil.org/fuse/options_helper_test.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
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/bazil.org/fuse/options_linux.go
generated
vendored
Normal file
25
vendor/bazil.org/fuse/options_linux.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
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
|
||||
}
|
||||
31
vendor/bazil.org/fuse/options_nocomma_test.go
generated
vendored
Normal file
31
vendor/bazil.org/fuse/options_nocomma_test.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
// 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/bazil.org/fuse/options_test.go
generated
vendored
Normal file
231
vendor/bazil.org/fuse/options_test.go
generated
vendored
Normal file
@ -0,0 +1,231 @@
|
||||
package fuse_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
"bazil.org/fuse/fs/fstestutil"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func init() {
|
||||
fstestutil.DebugByDefault()
|
||||
}
|
||||
|
||||
func TestMountOptionFSName(t *testing.T) {
|
||||
if runtime.GOOS == "freebsd" {
|
||||
t.Skip("FreeBSD does not support FSName")
|
||||
}
|
||||
t.Parallel()
|
||||
const name = "FuseTestMarker"
|
||||
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
|
||||
fuse.FSName(name),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer mnt.Close()
|
||||
|
||||
info, err := fstestutil.GetMountInfo(mnt.Dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if g, e := info.FSName, name; g != e {
|
||||
t.Errorf("wrong FSName: %q != %q", g, e)
|
||||
}
|
||||
}
|
||||
|
||||
func testMountOptionFSNameEvil(t *testing.T, evil string) {
|
||||
if runtime.GOOS == "freebsd" {
|
||||
t.Skip("FreeBSD does not support FSName")
|
||||
}
|
||||
t.Parallel()
|
||||
var name = "FuseTest" + evil + "Marker"
|
||||
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
|
||||
fuse.FSName(name),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer mnt.Close()
|
||||
|
||||
info, err := fstestutil.GetMountInfo(mnt.Dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if g, e := info.FSName, name; g != e {
|
||||
t.Errorf("wrong FSName: %q != %q", g, e)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountOptionFSNameEvilComma(t *testing.T) {
|
||||
if runtime.GOOS == "darwin" {
|
||||
// see TestMountOptionCommaError for a test that enforces we
|
||||
// at least give a nice error, instead of corrupting the mount
|
||||
// options
|
||||
t.Skip("TODO: OS X gets this wrong, commas in mount options cannot be escaped at all")
|
||||
}
|
||||
testMountOptionFSNameEvil(t, ",")
|
||||
}
|
||||
|
||||
func TestMountOptionFSNameEvilSpace(t *testing.T) {
|
||||
testMountOptionFSNameEvil(t, " ")
|
||||
}
|
||||
|
||||
func TestMountOptionFSNameEvilTab(t *testing.T) {
|
||||
testMountOptionFSNameEvil(t, "\t")
|
||||
}
|
||||
|
||||
func TestMountOptionFSNameEvilNewline(t *testing.T) {
|
||||
testMountOptionFSNameEvil(t, "\n")
|
||||
}
|
||||
|
||||
func TestMountOptionFSNameEvilBackslash(t *testing.T) {
|
||||
testMountOptionFSNameEvil(t, `\`)
|
||||
}
|
||||
|
||||
func TestMountOptionFSNameEvilBackslashDouble(t *testing.T) {
|
||||
// catch double-unescaping, if it were to happen
|
||||
testMountOptionFSNameEvil(t, `\\`)
|
||||
}
|
||||
|
||||
func TestMountOptionSubtype(t *testing.T) {
|
||||
if runtime.GOOS == "darwin" {
|
||||
t.Skip("OS X does not support Subtype")
|
||||
}
|
||||
if runtime.GOOS == "freebsd" {
|
||||
t.Skip("FreeBSD does not support Subtype")
|
||||
}
|
||||
t.Parallel()
|
||||
const name = "FuseTestMarker"
|
||||
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
|
||||
fuse.Subtype(name),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer mnt.Close()
|
||||
|
||||
info, err := fstestutil.GetMountInfo(mnt.Dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if g, e := info.Type, "fuse."+name; g != e {
|
||||
t.Errorf("wrong Subtype: %q != %q", g, e)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO test LocalVolume
|
||||
|
||||
// TODO test AllowOther; hard because needs system-level authorization
|
||||
|
||||
func TestMountOptionAllowOtherThenAllowRoot(t *testing.T) {
|
||||
t.Parallel()
|
||||
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
|
||||
fuse.AllowOther(),
|
||||
fuse.AllowRoot(),
|
||||
)
|
||||
if err == nil {
|
||||
mnt.Close()
|
||||
}
|
||||
if g, e := err, fuse.ErrCannotCombineAllowOtherAndAllowRoot; g != e {
|
||||
t.Fatalf("wrong error: %v != %v", g, e)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO test AllowRoot; hard because needs system-level authorization
|
||||
|
||||
func TestMountOptionAllowRootThenAllowOther(t *testing.T) {
|
||||
t.Parallel()
|
||||
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
|
||||
fuse.AllowRoot(),
|
||||
fuse.AllowOther(),
|
||||
)
|
||||
if err == nil {
|
||||
mnt.Close()
|
||||
}
|
||||
if g, e := err, fuse.ErrCannotCombineAllowOtherAndAllowRoot; g != e {
|
||||
t.Fatalf("wrong error: %v != %v", g, e)
|
||||
}
|
||||
}
|
||||
|
||||
type unwritableFile struct{}
|
||||
|
||||
func (f unwritableFile) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
a.Mode = 0000
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestMountOptionDefaultPermissions(t *testing.T) {
|
||||
if runtime.GOOS == "freebsd" {
|
||||
t.Skip("FreeBSD does not support DefaultPermissions")
|
||||
}
|
||||
t.Parallel()
|
||||
|
||||
mnt, err := fstestutil.MountedT(t,
|
||||
fstestutil.SimpleFS{
|
||||
&fstestutil.ChildMap{"child": unwritableFile{}},
|
||||
},
|
||||
nil,
|
||||
fuse.DefaultPermissions(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer mnt.Close()
|
||||
|
||||
// This will be prevented by kernel-level access checking when
|
||||
// DefaultPermissions is used.
|
||||
f, err := os.OpenFile(mnt.Dir+"/child", os.O_WRONLY, 0000)
|
||||
if err == nil {
|
||||
f.Close()
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
if !os.IsPermission(err) {
|
||||
t.Fatalf("expected a permission error, got %T: %v", err, err)
|
||||
}
|
||||
}
|
||||
|
||||
type createrDir struct {
|
||||
fstestutil.Dir
|
||||
}
|
||||
|
||||
var _ fs.NodeCreater = createrDir{}
|
||||
|
||||
func (createrDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
|
||||
// pick a really distinct error, to identify it later
|
||||
return nil, nil, fuse.Errno(syscall.ENAMETOOLONG)
|
||||
}
|
||||
|
||||
func TestMountOptionReadOnly(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mnt, err := fstestutil.MountedT(t,
|
||||
fstestutil.SimpleFS{createrDir{}},
|
||||
nil,
|
||||
fuse.ReadOnly(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer mnt.Close()
|
||||
|
||||
// This will be prevented by kernel-level access checking when
|
||||
// ReadOnly is used.
|
||||
f, err := os.Create(mnt.Dir + "/child")
|
||||
if err == nil {
|
||||
f.Close()
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
perr, ok := err.(*os.PathError)
|
||||
if !ok {
|
||||
t.Fatalf("expected PathError, got %T: %v", err, err)
|
||||
}
|
||||
if perr.Err != syscall.EROFS {
|
||||
t.Fatalf("expected EROFS, got %T: %v", err, err)
|
||||
}
|
||||
}
|
||||
75
vendor/bazil.org/fuse/protocol.go
generated
vendored
Normal file
75
vendor/bazil.org/fuse/protocol.go
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
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/bazil.org/fuse/syscallx/doc.go
generated
vendored
Normal file
13
vendor/bazil.org/fuse/syscallx/doc.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
// Package syscallx provides wrappers that make syscalls on various
|
||||
// platforms more interoperable.
|
||||
//
|
||||
// The API intentionally omits the OS X-specific position and option
|
||||
// arguments for extended attribute calls.
|
||||
//
|
||||
// Not having position means it might not be useful for accessing the
|
||||
// resource fork. If that's needed by code inside fuse, a function
|
||||
// with a different name may be added on the side.
|
||||
//
|
||||
// Options can be implemented with separate wrappers, in the style of
|
||||
// Linux getxattr/lgetxattr/fgetxattr.
|
||||
package syscallx // import "bazil.org/fuse/syscallx"
|
||||
34
vendor/bazil.org/fuse/syscallx/generate
generated
vendored
Executable file
34
vendor/bazil.org/fuse/syscallx/generate
generated
vendored
Executable file
@ -0,0 +1,34 @@
|
||||
#!/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/bazil.org/fuse/syscallx/msync.go
generated
vendored
Normal file
9
vendor/bazil.org/fuse/syscallx/msync.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
package syscallx
|
||||
|
||||
/* This is the source file for msync_*.go, to regenerate run
|
||||
|
||||
./generate
|
||||
|
||||
*/
|
||||
|
||||
//sys Msync(b []byte, flags int) (err error)
|
||||
24
vendor/bazil.org/fuse/syscallx/msync_386.go
generated
vendored
Normal file
24
vendor/bazil.org/fuse/syscallx/msync_386.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
// 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
|
||||
}
|
||||
24
vendor/bazil.org/fuse/syscallx/msync_amd64.go
generated
vendored
Normal file
24
vendor/bazil.org/fuse/syscallx/msync_amd64.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
// 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
|
||||
}
|
||||
4
vendor/bazil.org/fuse/syscallx/syscallx.go
generated
vendored
Normal file
4
vendor/bazil.org/fuse/syscallx/syscallx.go
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
package syscallx
|
||||
|
||||
// make us look more like package syscall, so mksyscall.pl output works
|
||||
var _zero uintptr
|
||||
26
vendor/bazil.org/fuse/syscallx/syscallx_std.go
generated
vendored
Normal file
26
vendor/bazil.org/fuse/syscallx/syscallx_std.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
// +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)
|
||||
}
|
||||
38
vendor/bazil.org/fuse/syscallx/xattr_darwin.go
generated
vendored
Normal file
38
vendor/bazil.org/fuse/syscallx/xattr_darwin.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
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)
|
||||
}
|
||||
97
vendor/bazil.org/fuse/syscallx/xattr_darwin_386.go
generated
vendored
Normal file
97
vendor/bazil.org/fuse/syscallx/xattr_darwin_386.go
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
// 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
|
||||
}
|
||||
97
vendor/bazil.org/fuse/syscallx/xattr_darwin_amd64.go
generated
vendored
Normal file
97
vendor/bazil.org/fuse/syscallx/xattr_darwin_amd64.go
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
// 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/bazil.org/fuse/unmount.go
generated
vendored
Normal file
6
vendor/bazil.org/fuse/unmount.go
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
package fuse
|
||||
|
||||
// Unmount tries to unmount the filesystem mounted at dir.
|
||||
func Unmount(dir string) error {
|
||||
return unmount(dir)
|
||||
}
|
||||
21
vendor/bazil.org/fuse/unmount_linux.go
generated
vendored
Normal file
21
vendor/bazil.org/fuse/unmount_linux.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
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/bazil.org/fuse/unmount_std.go
generated
vendored
Normal file
17
vendor/bazil.org/fuse/unmount_std.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// +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
|
||||
}
|
||||
20
vendor/github.com/beorn7/perks/LICENSE
generated
vendored
Normal file
20
vendor/github.com/beorn7/perks/LICENSE
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright (C) 2013 Blake Mizerany
|
||||
|
||||
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.
|
||||
2388
vendor/github.com/beorn7/perks/quantile/exampledata.txt
generated
vendored
Normal file
2388
vendor/github.com/beorn7/perks/quantile/exampledata.txt
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
316
vendor/github.com/beorn7/perks/quantile/stream.go
generated
vendored
Normal file
316
vendor/github.com/beorn7/perks/quantile/stream.go
generated
vendored
Normal file
@ -0,0 +1,316 @@
|
||||
// Package quantile computes approximate quantiles over an unbounded data
|
||||
// stream within low memory and CPU bounds.
|
||||
//
|
||||
// A small amount of accuracy is traded to achieve the above properties.
|
||||
//
|
||||
// Multiple streams can be merged before calling Query to generate a single set
|
||||
// of results. This is meaningful when the streams represent the same type of
|
||||
// data. See Merge and Samples.
|
||||
//
|
||||
// For more detailed information about the algorithm used, see:
|
||||
//
|
||||
// Effective Computation of Biased Quantiles over Data Streams
|
||||
//
|
||||
// http://www.cs.rutgers.edu/~muthu/bquant.pdf
|
||||
package quantile
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Sample holds an observed value and meta information for compression. JSON
|
||||
// tags have been added for convenience.
|
||||
type Sample struct {
|
||||
Value float64 `json:",string"`
|
||||
Width float64 `json:",string"`
|
||||
Delta float64 `json:",string"`
|
||||
}
|
||||
|
||||
// Samples represents a slice of samples. It implements sort.Interface.
|
||||
type Samples []Sample
|
||||
|
||||
func (a Samples) Len() int { return len(a) }
|
||||
func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value }
|
||||
func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
type invariant func(s *stream, r float64) float64
|
||||
|
||||
// NewLowBiased returns an initialized Stream for low-biased quantiles
|
||||
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
||||
// error guarantees can still be given even for the lower ranks of the data
|
||||
// distribution.
|
||||
//
|
||||
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
||||
// returned by a query is guaranteed to be within (1±Epsilon)*Quantile.
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
||||
// properties.
|
||||
func NewLowBiased(epsilon float64) *Stream {
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
return 2 * epsilon * r
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
// NewHighBiased returns an initialized Stream for high-biased quantiles
|
||||
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
||||
// error guarantees can still be given even for the higher ranks of the data
|
||||
// distribution.
|
||||
//
|
||||
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
||||
// returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile).
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
||||
// properties.
|
||||
func NewHighBiased(epsilon float64) *Stream {
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
return 2 * epsilon * (s.n - r)
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
// NewTargeted returns an initialized Stream concerned with a particular set of
|
||||
// quantile values that are supplied a priori. Knowing these a priori reduces
|
||||
// space and computation time. The targets map maps the desired quantiles to
|
||||
// their absolute errors, i.e. the true quantile of a value returned by a query
|
||||
// is guaranteed to be within (Quantile±Epsilon).
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
|
||||
func NewTargeted(targetMap map[float64]float64) *Stream {
|
||||
// Convert map to slice to avoid slow iterations on a map.
|
||||
// ƒ is called on the hot path, so converting the map to a slice
|
||||
// beforehand results in significant CPU savings.
|
||||
targets := targetMapToSlice(targetMap)
|
||||
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
var m = math.MaxFloat64
|
||||
var f float64
|
||||
for _, t := range targets {
|
||||
if t.quantile*s.n <= r {
|
||||
f = (2 * t.epsilon * r) / t.quantile
|
||||
} else {
|
||||
f = (2 * t.epsilon * (s.n - r)) / (1 - t.quantile)
|
||||
}
|
||||
if f < m {
|
||||
m = f
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
type target struct {
|
||||
quantile float64
|
||||
epsilon float64
|
||||
}
|
||||
|
||||
func targetMapToSlice(targetMap map[float64]float64) []target {
|
||||
targets := make([]target, 0, len(targetMap))
|
||||
|
||||
for quantile, epsilon := range targetMap {
|
||||
t := target{
|
||||
quantile: quantile,
|
||||
epsilon: epsilon,
|
||||
}
|
||||
targets = append(targets, t)
|
||||
}
|
||||
|
||||
return targets
|
||||
}
|
||||
|
||||
// Stream computes quantiles for a stream of float64s. It is not thread-safe by
|
||||
// design. Take care when using across multiple goroutines.
|
||||
type Stream struct {
|
||||
*stream
|
||||
b Samples
|
||||
sorted bool
|
||||
}
|
||||
|
||||
func newStream(ƒ invariant) *Stream {
|
||||
x := &stream{ƒ: ƒ}
|
||||
return &Stream{x, make(Samples, 0, 500), true}
|
||||
}
|
||||
|
||||
// Insert inserts v into the stream.
|
||||
func (s *Stream) Insert(v float64) {
|
||||
s.insert(Sample{Value: v, Width: 1})
|
||||
}
|
||||
|
||||
func (s *Stream) insert(sample Sample) {
|
||||
s.b = append(s.b, sample)
|
||||
s.sorted = false
|
||||
if len(s.b) == cap(s.b) {
|
||||
s.flush()
|
||||
}
|
||||
}
|
||||
|
||||
// Query returns the computed qth percentiles value. If s was created with
|
||||
// NewTargeted, and q is not in the set of quantiles provided a priori, Query
|
||||
// will return an unspecified result.
|
||||
func (s *Stream) Query(q float64) float64 {
|
||||
if !s.flushed() {
|
||||
// Fast path when there hasn't been enough data for a flush;
|
||||
// this also yields better accuracy for small sets of data.
|
||||
l := len(s.b)
|
||||
if l == 0 {
|
||||
return 0
|
||||
}
|
||||
i := int(math.Ceil(float64(l) * q))
|
||||
if i > 0 {
|
||||
i -= 1
|
||||
}
|
||||
s.maybeSort()
|
||||
return s.b[i].Value
|
||||
}
|
||||
s.flush()
|
||||
return s.stream.query(q)
|
||||
}
|
||||
|
||||
// Merge merges samples into the underlying streams samples. This is handy when
|
||||
// merging multiple streams from separate threads, database shards, etc.
|
||||
//
|
||||
// ATTENTION: This method is broken and does not yield correct results. The
|
||||
// underlying algorithm is not capable of merging streams correctly.
|
||||
func (s *Stream) Merge(samples Samples) {
|
||||
sort.Sort(samples)
|
||||
s.stream.merge(samples)
|
||||
}
|
||||
|
||||
// Reset reinitializes and clears the list reusing the samples buffer memory.
|
||||
func (s *Stream) Reset() {
|
||||
s.stream.reset()
|
||||
s.b = s.b[:0]
|
||||
}
|
||||
|
||||
// Samples returns stream samples held by s.
|
||||
func (s *Stream) Samples() Samples {
|
||||
if !s.flushed() {
|
||||
return s.b
|
||||
}
|
||||
s.flush()
|
||||
return s.stream.samples()
|
||||
}
|
||||
|
||||
// Count returns the total number of samples observed in the stream
|
||||
// since initialization.
|
||||
func (s *Stream) Count() int {
|
||||
return len(s.b) + s.stream.count()
|
||||
}
|
||||
|
||||
func (s *Stream) flush() {
|
||||
s.maybeSort()
|
||||
s.stream.merge(s.b)
|
||||
s.b = s.b[:0]
|
||||
}
|
||||
|
||||
func (s *Stream) maybeSort() {
|
||||
if !s.sorted {
|
||||
s.sorted = true
|
||||
sort.Sort(s.b)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) flushed() bool {
|
||||
return len(s.stream.l) > 0
|
||||
}
|
||||
|
||||
type stream struct {
|
||||
n float64
|
||||
l []Sample
|
||||
ƒ invariant
|
||||
}
|
||||
|
||||
func (s *stream) reset() {
|
||||
s.l = s.l[:0]
|
||||
s.n = 0
|
||||
}
|
||||
|
||||
func (s *stream) insert(v float64) {
|
||||
s.merge(Samples{{v, 1, 0}})
|
||||
}
|
||||
|
||||
func (s *stream) merge(samples Samples) {
|
||||
// TODO(beorn7): This tries to merge not only individual samples, but
|
||||
// whole summaries. The paper doesn't mention merging summaries at
|
||||
// all. Unittests show that the merging is inaccurate. Find out how to
|
||||
// do merges properly.
|
||||
var r float64
|
||||
i := 0
|
||||
for _, sample := range samples {
|
||||
for ; i < len(s.l); i++ {
|
||||
c := s.l[i]
|
||||
if c.Value > sample.Value {
|
||||
// Insert at position i.
|
||||
s.l = append(s.l, Sample{})
|
||||
copy(s.l[i+1:], s.l[i:])
|
||||
s.l[i] = Sample{
|
||||
sample.Value,
|
||||
sample.Width,
|
||||
math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1),
|
||||
// TODO(beorn7): How to calculate delta correctly?
|
||||
}
|
||||
i++
|
||||
goto inserted
|
||||
}
|
||||
r += c.Width
|
||||
}
|
||||
s.l = append(s.l, Sample{sample.Value, sample.Width, 0})
|
||||
i++
|
||||
inserted:
|
||||
s.n += sample.Width
|
||||
r += sample.Width
|
||||
}
|
||||
s.compress()
|
||||
}
|
||||
|
||||
func (s *stream) count() int {
|
||||
return int(s.n)
|
||||
}
|
||||
|
||||
func (s *stream) query(q float64) float64 {
|
||||
t := math.Ceil(q * s.n)
|
||||
t += math.Ceil(s.ƒ(s, t) / 2)
|
||||
p := s.l[0]
|
||||
var r float64
|
||||
for _, c := range s.l[1:] {
|
||||
r += p.Width
|
||||
if r+c.Width+c.Delta > t {
|
||||
return p.Value
|
||||
}
|
||||
p = c
|
||||
}
|
||||
return p.Value
|
||||
}
|
||||
|
||||
func (s *stream) compress() {
|
||||
if len(s.l) < 2 {
|
||||
return
|
||||
}
|
||||
x := s.l[len(s.l)-1]
|
||||
xi := len(s.l) - 1
|
||||
r := s.n - 1 - x.Width
|
||||
|
||||
for i := len(s.l) - 2; i >= 0; i-- {
|
||||
c := s.l[i]
|
||||
if c.Width+x.Width+x.Delta <= s.ƒ(s, r) {
|
||||
x.Width += c.Width
|
||||
s.l[xi] = x
|
||||
// Remove element at i.
|
||||
copy(s.l[i:], s.l[i+1:])
|
||||
s.l = s.l[:len(s.l)-1]
|
||||
xi -= 1
|
||||
} else {
|
||||
x = c
|
||||
xi = i
|
||||
}
|
||||
r -= c.Width
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stream) samples() Samples {
|
||||
samples := make(Samples, len(s.l))
|
||||
copy(samples, s.l)
|
||||
return samples
|
||||
}
|
||||
15
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
Normal file
15
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
145
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
Normal file
145
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
Normal file
@ -0,0 +1,145 @@
|
||||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// Go versions prior to 1.4 are disabled because they use a different layout
|
||||
// for interfaces which make the implementation of unsafeReflectValue more complex.
|
||||
// +build !js,!appengine,!safe,!disableunsafe,go1.4
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||
// not access to the unsafe package is available.
|
||||
UnsafeDisabled = false
|
||||
|
||||
// ptrSize is the size of a pointer on the current arch.
|
||||
ptrSize = unsafe.Sizeof((*byte)(nil))
|
||||
)
|
||||
|
||||
type flag uintptr
|
||||
|
||||
var (
|
||||
// flagRO indicates whether the value field of a reflect.Value
|
||||
// is read-only.
|
||||
flagRO flag
|
||||
|
||||
// flagAddr indicates whether the address of the reflect.Value's
|
||||
// value may be taken.
|
||||
flagAddr flag
|
||||
)
|
||||
|
||||
// flagKindMask holds the bits that make up the kind
|
||||
// part of the flags field. In all the supported versions,
|
||||
// it is in the lower 5 bits.
|
||||
const flagKindMask = flag(0x1f)
|
||||
|
||||
// Different versions of Go have used different
|
||||
// bit layouts for the flags type. This table
|
||||
// records the known combinations.
|
||||
var okFlags = []struct {
|
||||
ro, addr flag
|
||||
}{{
|
||||
// From Go 1.4 to 1.5
|
||||
ro: 1 << 5,
|
||||
addr: 1 << 7,
|
||||
}, {
|
||||
// Up to Go tip.
|
||||
ro: 1<<5 | 1<<6,
|
||||
addr: 1 << 8,
|
||||
}}
|
||||
|
||||
var flagValOffset = func() uintptr {
|
||||
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
||||
if !ok {
|
||||
panic("reflect.Value has no flag field")
|
||||
}
|
||||
return field.Offset
|
||||
}()
|
||||
|
||||
// flagField returns a pointer to the flag field of a reflect.Value.
|
||||
func flagField(v *reflect.Value) *flag {
|
||||
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
|
||||
}
|
||||
|
||||
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
||||
// the typical safety restrictions preventing access to unaddressable and
|
||||
// unexported data. It works by digging the raw pointer to the underlying
|
||||
// value out of the protected value and generating a new unprotected (unsafe)
|
||||
// reflect.Value to it.
|
||||
//
|
||||
// This allows us to check for implementations of the Stringer and error
|
||||
// interfaces to be used for pretty printing ordinarily unaddressable and
|
||||
// inaccessible values such as unexported struct fields.
|
||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||
if !v.IsValid() || (v.CanInterface() && v.CanAddr()) {
|
||||
return v
|
||||
}
|
||||
flagFieldPtr := flagField(&v)
|
||||
*flagFieldPtr &^= flagRO
|
||||
*flagFieldPtr |= flagAddr
|
||||
return v
|
||||
}
|
||||
|
||||
// Sanity checks against future reflect package changes
|
||||
// to the type or semantics of the Value.flag field.
|
||||
func init() {
|
||||
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
||||
if !ok {
|
||||
panic("reflect.Value has no flag field")
|
||||
}
|
||||
if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
|
||||
panic("reflect.Value flag field has changed kind")
|
||||
}
|
||||
type t0 int
|
||||
var t struct {
|
||||
A t0
|
||||
// t0 will have flagEmbedRO set.
|
||||
t0
|
||||
// a will have flagStickyRO set
|
||||
a t0
|
||||
}
|
||||
vA := reflect.ValueOf(t).FieldByName("A")
|
||||
va := reflect.ValueOf(t).FieldByName("a")
|
||||
vt0 := reflect.ValueOf(t).FieldByName("t0")
|
||||
|
||||
// Infer flagRO from the difference between the flags
|
||||
// for the (otherwise identical) fields in t.
|
||||
flagPublic := *flagField(&vA)
|
||||
flagWithRO := *flagField(&va) | *flagField(&vt0)
|
||||
flagRO = flagPublic ^ flagWithRO
|
||||
|
||||
// Infer flagAddr from the difference between a value
|
||||
// taken from a pointer and not.
|
||||
vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A")
|
||||
flagNoPtr := *flagField(&vA)
|
||||
flagPtr := *flagField(&vPtrA)
|
||||
flagAddr = flagNoPtr ^ flagPtr
|
||||
|
||||
// Check that the inferred flags tally with one of the known versions.
|
||||
for _, f := range okFlags {
|
||||
if flagRO == f.ro && flagAddr == f.addr {
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("reflect.Value read-only flag has changed semantics")
|
||||
}
|
||||
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
Normal file
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is running on Google App Engine, compiled by GopherJS, or
|
||||
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// +build js appengine safe disableunsafe !go1.4
|
||||
|
||||
package spew
|
||||
|
||||
import "reflect"
|
||||
|
||||
const (
|
||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||
// not access to the unsafe package is available.
|
||||
UnsafeDisabled = true
|
||||
)
|
||||
|
||||
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
||||
// that bypasses the typical safety restrictions preventing access to
|
||||
// unaddressable and unexported data. However, doing this relies on access to
|
||||
// the unsafe package. This is a stub version which simply returns the passed
|
||||
// reflect.Value when the unsafe package is not available.
|
||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||
return v
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user