Initial commit
Proof-of-concept implementation. Bugs will occur.
This commit is contained in:
31
vendor/github.com/goccy/go-yaml/.codecov.yml
generated
vendored
Normal file
31
vendor/github.com/goccy/go-yaml/.codecov.yml
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
codecov:
|
||||
require_ci_to_pass: yes
|
||||
|
||||
coverage:
|
||||
precision: 2
|
||||
round: down
|
||||
range: "70...100"
|
||||
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 75%
|
||||
threshold: 2%
|
||||
patch: off
|
||||
changes: no
|
||||
|
||||
parsers:
|
||||
gcov:
|
||||
branch_detection:
|
||||
conditional: yes
|
||||
loop: yes
|
||||
method: no
|
||||
macro: no
|
||||
|
||||
comment:
|
||||
layout: "header,diff"
|
||||
behavior: default
|
||||
require_changes: no
|
||||
|
||||
ignore:
|
||||
- ast
|
||||
3
vendor/github.com/goccy/go-yaml/.gitignore
generated
vendored
Normal file
3
vendor/github.com/goccy/go-yaml/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
bin/
|
||||
.idea/
|
||||
cover.out
|
||||
65
vendor/github.com/goccy/go-yaml/.golangci.yml
generated
vendored
Normal file
65
vendor/github.com/goccy/go-yaml/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
version: "2"
|
||||
linters:
|
||||
default: none
|
||||
enable:
|
||||
- errcheck
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- perfsprint
|
||||
- staticcheck
|
||||
- unused
|
||||
settings:
|
||||
errcheck:
|
||||
without_tests: true
|
||||
govet:
|
||||
disable:
|
||||
- tests
|
||||
misspell:
|
||||
locale: US
|
||||
perfsprint:
|
||||
int-conversion: false
|
||||
err-error: false
|
||||
errorf: true
|
||||
sprintf1: false
|
||||
strconcat: false
|
||||
staticcheck:
|
||||
checks:
|
||||
- -ST1000
|
||||
- -ST1005
|
||||
- all
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
rules:
|
||||
- linters:
|
||||
- staticcheck
|
||||
path: _test\.go
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
formatters:
|
||||
enable:
|
||||
- gci
|
||||
- gofmt
|
||||
settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/goccy/go-yaml)
|
||||
- blank
|
||||
- dot
|
||||
gofmt:
|
||||
simplify: true
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
186
vendor/github.com/goccy/go-yaml/CHANGELOG.md
generated
vendored
Normal file
186
vendor/github.com/goccy/go-yaml/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
# 1.11.2 - 2023-09-15
|
||||
|
||||
### Fix bugs
|
||||
|
||||
- Fix quoted comments ( #370 )
|
||||
- Fix handle of space at start or last ( #376 )
|
||||
- Fix sequence with comment ( #390 )
|
||||
|
||||
# 1.11.1 - 2023-09-14
|
||||
|
||||
### Fix bugs
|
||||
|
||||
- Handle `\r` in a double-quoted string the same as `\n` ( #372 )
|
||||
- Replace loop with n.Values = append(n.Values, target.Values...) ( #380 )
|
||||
- Skip encoding an inline field if it is null ( #386 )
|
||||
- Fix comment parsing with null value ( #388 )
|
||||
|
||||
# 1.11.0 - 2023-04-03
|
||||
|
||||
### Features
|
||||
|
||||
- Supports dynamically switch encode and decode processing for a given type
|
||||
|
||||
# 1.10.1 - 2023-03-28
|
||||
|
||||
### Features
|
||||
|
||||
- Quote YAML 1.1 bools at encoding time for compatibility with other legacy parsers
|
||||
- Add support of 32-bit architecture
|
||||
|
||||
### Fix bugs
|
||||
|
||||
- Don't trim all space characters in block style sequence
|
||||
- Support strings starting with `@`
|
||||
|
||||
# 1.10.0 - 2023-03-01
|
||||
|
||||
### Fix bugs
|
||||
|
||||
Reversible conversion of comments was not working in various cases, which has been corrected.
|
||||
**Breaking Change** exists in the comment map interface. However, if you are dealing with CommentMap directly, there is no problem.
|
||||
|
||||
|
||||
# 1.9.8 - 2022-12-19
|
||||
|
||||
### Fix feature
|
||||
|
||||
- Append new line at the end of file ( #329 )
|
||||
|
||||
### Fix bugs
|
||||
|
||||
- Fix custom marshaler ( #333, #334 )
|
||||
- Fix behavior when struct fields conflicted( #335 )
|
||||
- Fix position calculation for literal, folded and raw folded strings ( #330 )
|
||||
|
||||
# 1.9.7 - 2022-12-03
|
||||
|
||||
### Fix bugs
|
||||
|
||||
- Fix handling of quoted map key ( #328 )
|
||||
- Fix resusing process of scanning context ( #322 )
|
||||
|
||||
## v1.9.6 - 2022-10-26
|
||||
|
||||
### New Features
|
||||
|
||||
- Introduce MapKeyNode interface to limit node types for map key ( #312 )
|
||||
|
||||
### Fix bugs
|
||||
|
||||
- Quote strings with special characters in flow mode ( #270 )
|
||||
- typeError implements PrettyPrinter interface ( #280 )
|
||||
- Fix incorrect const type ( #284 )
|
||||
- Fix large literals type inference on 32 bits ( #293 )
|
||||
- Fix UTF-8 characters ( #294 )
|
||||
- Fix decoding of unknown aliases ( #317 )
|
||||
- Fix stream encoder for insert a separator between each encoded document ( #318 )
|
||||
|
||||
### Update
|
||||
|
||||
- Update golang.org/x/sys ( #289 )
|
||||
- Update Go version in CI ( #295 )
|
||||
- Add test cases for missing keys to struct literals ( #300 )
|
||||
|
||||
## v1.9.5 - 2022-01-12
|
||||
|
||||
### New Features
|
||||
|
||||
* Add UseSingleQuote option ( #265 )
|
||||
|
||||
### Fix bugs
|
||||
|
||||
* Preserve defaults while decoding nested structs ( #260 )
|
||||
* Fix minor typo in decodeInit error ( #264 )
|
||||
* Handle empty sequence entries ( #275 )
|
||||
* Fix encoding of sequence with multiline string ( #276 )
|
||||
* Fix encoding of BytesMarshaler type ( #277 )
|
||||
* Fix indentState logic for multi-line value ( #278 )
|
||||
|
||||
## v1.9.4 - 2021-10-12
|
||||
|
||||
### Fix bugs
|
||||
|
||||
* Keep prev/next reference between tokens containing comments when filtering comment tokens ( #257 )
|
||||
* Supports escaping reserved keywords in PathBuilder ( #258 )
|
||||
|
||||
## v1.9.3 - 2021-09-07
|
||||
|
||||
### New Features
|
||||
|
||||
* Support encoding and decoding `time.Duration` fields ( #246 )
|
||||
* Allow reserved characters for key name in YAMLPath ( #251 )
|
||||
* Support getting YAMLPath from ast.Node ( #252 )
|
||||
* Support CommentToMap option ( #253 )
|
||||
|
||||
### Fix bugs
|
||||
|
||||
* Fix encoding nested sequences with `yaml.IndentSequence` ( #241 )
|
||||
* Fix error reporting on inline structs in strict mode ( #244, #245 )
|
||||
* Fix encoding of large floats ( #247 )
|
||||
|
||||
### Improve workflow
|
||||
|
||||
* Migrate CI from CircleCI to GitHub Action ( #249 )
|
||||
* Add workflow for ycat ( #250 )
|
||||
|
||||
## v1.9.2 - 2021-07-26
|
||||
|
||||
### Support WithComment option ( #238 )
|
||||
|
||||
`yaml.WithComment` is a option for encoding with comment.
|
||||
The position where you want to add a comment is represented by YAMLPath, and it is the key of `yaml.CommentMap`.
|
||||
Also, you can select `Head` comment or `Line` comment as the comment type.
|
||||
|
||||
## v1.9.1 - 2021-07-20
|
||||
|
||||
### Fix DecodeFromNode ( #237 )
|
||||
|
||||
- Fix YAML handling where anchor exists
|
||||
|
||||
## v1.9.0 - 2021-07-19
|
||||
|
||||
### New features
|
||||
|
||||
- Support encoding of comment node ( #233 )
|
||||
- Support `yaml.NodeToValue(ast.Node, interface{}, ...DecodeOption) error` ( #236 )
|
||||
- Can convert a AST node to a value directly
|
||||
|
||||
### Fix decoder for comment
|
||||
|
||||
- Fix parsing of literal with comment ( #234 )
|
||||
|
||||
### Rename API ( #235 )
|
||||
|
||||
- Rename `MarshalWithContext` to `MarshalContext`
|
||||
- Rename `UnmarshalWithContext` to `UnmarshalContext`
|
||||
|
||||
## v1.8.10 - 2021-07-02
|
||||
|
||||
### Fixed bugs
|
||||
|
||||
- Fix searching anchor by alias name ( #212 )
|
||||
- Fixing Issue 186, scanner should account for newline characters when processing multi-line text. Without this source annotations line/column number (for this and all subsequent tokens) is inconsistent with plain text editors. e.g. https://github.com/goccy/go-yaml/issues/186. This addresses the issue specifically for single and double quote text only. ( #210 )
|
||||
- Add error for unterminated flow mapping node ( #213 )
|
||||
- Handle missing required field validation ( #221 )
|
||||
- Nicely format unexpected node type errors ( #229 )
|
||||
- Support to encode map which has defined type key ( #231 )
|
||||
|
||||
### New features
|
||||
|
||||
- Support sequence indentation by EncodeOption ( #232 )
|
||||
|
||||
## v1.8.9 - 2021-03-01
|
||||
|
||||
### Fixed bugs
|
||||
|
||||
- Fix origin buffer for DocumentHeader and DocumentEnd and Directive
|
||||
- Fix origin buffer for anchor value
|
||||
- Fix syntax error about map value
|
||||
- Fix parsing MergeKey ('<<') characters
|
||||
- Fix encoding of float value
|
||||
- Fix incorrect column annotation when single or double quotes are used
|
||||
|
||||
### New features
|
||||
|
||||
- Support to encode/decode of ast.Node directly
|
||||
21
vendor/github.com/goccy/go-yaml/LICENSE
generated
vendored
Normal file
21
vendor/github.com/goccy/go-yaml/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Masaaki Goshima
|
||||
|
||||
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.
|
||||
55
vendor/github.com/goccy/go-yaml/Makefile
generated
vendored
Normal file
55
vendor/github.com/goccy/go-yaml/Makefile
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
## Location to install dependencies to
|
||||
LOCALBIN ?= $(shell pwd)/bin
|
||||
TESTMOD := testdata/go_test.mod
|
||||
|
||||
$(LOCALBIN):
|
||||
mkdir -p $(LOCALBIN)
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -v -race ./...
|
||||
go test -v -race ./testdata -modfile=$(TESTMOD)
|
||||
|
||||
.PHONY: simple-test
|
||||
simple-test:
|
||||
go test -v ./...
|
||||
go test -v ./testdata -modfile=$(TESTMOD)
|
||||
|
||||
.PHONY: fuzz
|
||||
fuzz:
|
||||
go test -fuzz=Fuzz -fuzztime 60s
|
||||
|
||||
.PHONY: cover
|
||||
cover:
|
||||
go test -coverpkg=.,./ast,./lexer,./parser,./printer,./scanner,./token -coverprofile=cover.out -modfile=$(TESTMOD) ./... ./testdata
|
||||
|
||||
.PHONY: cover-html
|
||||
cover-html: cover
|
||||
go tool cover -html=cover.out
|
||||
|
||||
.PHONY: ycat/build
|
||||
ycat/build: $(LOCALBIN)
|
||||
cd ./cmd/ycat && go build -o $(LOCALBIN)/ycat .
|
||||
|
||||
.PHONY: lint
|
||||
lint: golangci-lint ## Run golangci-lint
|
||||
@$(GOLANGCI_LINT) run
|
||||
|
||||
.PHONY: fmt
|
||||
fmt: golangci-lint ## Ensure consistent code style
|
||||
@go mod tidy
|
||||
@go fmt ./...
|
||||
@$(GOLANGCI_LINT) run --fix
|
||||
|
||||
## Tool Binaries
|
||||
GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint
|
||||
|
||||
## Tool Versions
|
||||
GOLANGCI_VERSION := 2.1.2
|
||||
|
||||
.PHONY: golangci-lint
|
||||
.PHONY: $(GOLANGCI_LINT)
|
||||
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
|
||||
$(GOLANGCI_LINT): $(LOCALBIN)
|
||||
@test -s $(LOCALBIN)/golangci-lint && $(LOCALBIN)/golangci-lint version --short | grep -q $(GOLANGCI_VERSION) || \
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(LOCALBIN) v$(GOLANGCI_VERSION)
|
||||
420
vendor/github.com/goccy/go-yaml/README.md
generated
vendored
Normal file
420
vendor/github.com/goccy/go-yaml/README.md
generated
vendored
Normal file
@@ -0,0 +1,420 @@
|
||||
# YAML support for the Go language
|
||||
|
||||
[](https://pkg.go.dev/github.com/goccy/go-yaml)
|
||||

|
||||
[](https://codecov.io/gh/goccy/go-yaml)
|
||||
[](https://goreportcard.com/report/github.com/goccy/go-yaml)
|
||||
|
||||
<img width="300px" src="https://user-images.githubusercontent.com/209884/67159116-64d94b80-f37b-11e9-9b28-f8379636a43c.png"></img>
|
||||
|
||||
## This library has **NO** relation to the go-yaml/yaml library
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This library is developed from scratch to replace [`go-yaml/yaml`](https://github.com/go-yaml/yaml).
|
||||
> If you're looking for a better YAML library, this one should be helpful.
|
||||
|
||||
# Why a new library?
|
||||
|
||||
As of this writing, there already exists a de facto standard library for YAML processing for Go: [https://github.com/go-yaml/yaml](https://github.com/go-yaml/yaml). However, we believe that a new YAML library is necessary for the following reasons:
|
||||
|
||||
- Not actively maintained
|
||||
- `go-yaml/yaml` has ported the libyaml written in C to Go, so the source code is not written in Go style
|
||||
- There is a lot of content that cannot be parsed
|
||||
- YAML is often used for configuration, and it is common to include validation along with it. However, the errors in `go-yaml/yaml` are not intuitive, and it is difficult to provide meaningful validation errors
|
||||
- When creating tools that use YAML, there are cases where reversible transformation of YAML is required. However, to perform reversible transformations of content that includes Comments or Anchors/Aliases, manipulating the AST is the only option
|
||||
- Non-intuitive [Marshaler](https://pkg.go.dev/gopkg.in/yaml.v3#Marshaler) / [Unmarshaler](https://pkg.go.dev/gopkg.in/yaml.v3#Unmarshaler)
|
||||
|
||||
By the way, libraries such as [ghodss/yaml](https://github.com/ghodss/yaml) and [sigs.k8s.io/yaml](https://github.com/kubernetes-sigs/yaml) also depend on go-yaml/yaml, so if you are using these libraries, the same issues apply: they cannot parse things that go-yaml/yaml cannot parse, and they inherit many of the problems that go-yaml/yaml has.
|
||||
|
||||
# Features
|
||||
|
||||
- No dependencies
|
||||
- A better parser than `go-yaml/yaml`.
|
||||
- [Support recursive processing](https://github.com/apple/device-management/blob/release/docs/schema.yaml)
|
||||
- Higher coverage in the [YAML Test Suite](https://github.com/yaml/yaml-test-suite?tab=readme-ov-file)
|
||||
- YAML Test Suite consists of 402 cases in total, of which `gopkg.in/yaml.v3` passes `295`. In addition to passing all those test cases, `goccy/go-yaml` successfully passes nearly 60 additional test cases ( 2024/12/15 )
|
||||
- The test code is [here](https://github.com/goccy/go-yaml/blob/master/yaml_test_suite_test.go#L77)
|
||||
- Ease and sustainability of maintenance
|
||||
- The main maintainer is [@goccy](https://github.com/goccy), but we are also building a system to develop as a team with trusted developers
|
||||
- Since it is written from scratch, the code is easy to read for Gophers
|
||||
- An API structure that allows the use of not only `Encoder`/`Decoder` but also `Tokenizer` and `Parser` functionalities.
|
||||
- [lexer.Tokenize](https://pkg.go.dev/github.com/goccy/go-yaml@v1.15.4/lexer#Tokenize)
|
||||
- [parser.Parse](https://pkg.go.dev/github.com/goccy/go-yaml@v1.15.4/parser#Parse)
|
||||
- Filtering, replacing, and merging YAML content using YAML Path
|
||||
- Reversible transformation without using the AST for YAML that includes Anchors, Aliases, and Comments
|
||||
- Customize the Marshal/Unmarshal behavior for primitive types and third-party library types ([RegisterCustomMarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#RegisterCustomMarshaler), [RegisterCustomUnmarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#RegisterCustomUnmarshaler))
|
||||
- Respects `encoding/json` behavior
|
||||
- Accept the `json` tag. Note that not all options from the `json` tag will have significance when parsing YAML documents. If both tags exist, `yaml` tag will take precedence.
|
||||
- [json.Marshaler](https://pkg.go.dev/encoding/json#Marshaler) style [marshaler](https://pkg.go.dev/github.com/goccy/go-yaml#BytesMarshaler)
|
||||
- [json.Unmarshaler](https://pkg.go.dev/encoding/json#Unmarshaler) style [unmarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#BytesUnmarshaler)
|
||||
- Options for using `MarshalJSON` and `UnmarshalJSON` ([UseJSONMarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#UseJSONMarshaler), [UseJSONUnmarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#UseJSONUnmarshaler))
|
||||
- Pretty format for error notifications
|
||||
- Smart validation processing combined with [go-playground/validator](https://github.com/go-playground/validator)
|
||||
- [example test code is here](https://github.com/goccy/go-yaml/blob/45889c98b0a0967240eb595a1bd6896e2f575106/testdata/validate_test.go#L12)
|
||||
- Allow referencing elements declared in another file via anchors
|
||||
|
||||
# Users
|
||||
|
||||
The repositories that use goccy/go-yaml are listed here.
|
||||
|
||||
- https://github.com/goccy/go-yaml/wiki/Users
|
||||
|
||||
The source data is [here](https://github.com/goccy/go-yaml/network/dependents).
|
||||
It is already being used in many repositories. Now it's your turn 😄
|
||||
|
||||
# Playground
|
||||
|
||||
The Playground visualizes how go-yaml processes YAML text. Use it to assist with your debugging or issue reporting.
|
||||
|
||||
https://goccy.github.io/go-yaml
|
||||
|
||||
# Installation
|
||||
|
||||
```sh
|
||||
go get github.com/goccy/go-yaml
|
||||
```
|
||||
|
||||
# Synopsis
|
||||
|
||||
## 1. Simple Encode/Decode
|
||||
|
||||
Has an interface like `go-yaml/yaml` using `reflect`
|
||||
|
||||
```go
|
||||
var v struct {
|
||||
A int
|
||||
B string
|
||||
}
|
||||
v.A = 1
|
||||
v.B = "hello"
|
||||
bytes, err := yaml.Marshal(v)
|
||||
if err != nil {
|
||||
//...
|
||||
}
|
||||
fmt.Println(string(bytes)) // "a: 1\nb: hello\n"
|
||||
```
|
||||
|
||||
```go
|
||||
yml := `
|
||||
%YAML 1.2
|
||||
---
|
||||
a: 1
|
||||
b: c
|
||||
`
|
||||
var v struct {
|
||||
A int
|
||||
B string
|
||||
}
|
||||
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
To control marshal/unmarshal behavior, you can use the `yaml` tag.
|
||||
|
||||
```go
|
||||
yml := `---
|
||||
foo: 1
|
||||
bar: c
|
||||
`
|
||||
var v struct {
|
||||
A int `yaml:"foo"`
|
||||
B string `yaml:"bar"`
|
||||
}
|
||||
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
For convenience, we also accept the `json` tag. Note that not all options from
|
||||
the `json` tag will have significance when parsing YAML documents. If both
|
||||
tags exist, `yaml` tag will take precedence.
|
||||
|
||||
```go
|
||||
yml := `---
|
||||
foo: 1
|
||||
bar: c
|
||||
`
|
||||
var v struct {
|
||||
A int `json:"foo"`
|
||||
B string `json:"bar"`
|
||||
}
|
||||
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
For custom marshal/unmarshaling, implement either `Bytes` or `Interface` variant of marshaler/unmarshaler. The difference is that while `BytesMarshaler`/`BytesUnmarshaler` behaves like [`encoding/json`](https://pkg.go.dev/encoding/json) and `InterfaceMarshaler`/`InterfaceUnmarshaler` behaves like [`gopkg.in/yaml.v2`](https://pkg.go.dev/gopkg.in/yaml.v2).
|
||||
|
||||
Semantically both are the same, but they differ in performance. Because indentation matters in YAML, you cannot simply accept a valid YAML fragment from a Marshaler, and expect it to work when it is attached to the parent container's serialized form. Therefore when we receive use the `BytesMarshaler`, which returns `[]byte`, we must decode it once to figure out how to make it work in the given context. If you use the `InterfaceMarshaler`, we can skip the decoding.
|
||||
|
||||
If you are repeatedly marshaling complex objects, the latter is always better
|
||||
performance wise. But if you are, for example, just providing a choice between
|
||||
a config file format that is read only once, the former is probably easier to
|
||||
code.
|
||||
|
||||
## 2. Reference elements declared in another file
|
||||
|
||||
`testdata` directory contains `anchor.yml` file:
|
||||
|
||||
```shell
|
||||
├── testdata
|
||||
└── anchor.yml
|
||||
```
|
||||
|
||||
And `anchor.yml` is defined as follows:
|
||||
|
||||
```yaml
|
||||
a: &a
|
||||
b: 1
|
||||
c: hello
|
||||
```
|
||||
|
||||
Then, if `yaml.ReferenceDirs("testdata")` option is passed to `yaml.Decoder`,
|
||||
`Decoder` tries to find the anchor definition from YAML files the under `testdata` directory.
|
||||
|
||||
```go
|
||||
buf := bytes.NewBufferString("a: *a\n")
|
||||
dec := yaml.NewDecoder(buf, yaml.ReferenceDirs("testdata"))
|
||||
var v struct {
|
||||
A struct {
|
||||
B int
|
||||
C string
|
||||
}
|
||||
}
|
||||
if err := dec.Decode(&v); err != nil {
|
||||
//...
|
||||
}
|
||||
fmt.Printf("%+v\n", v) // {A:{B:1 C:hello}}
|
||||
```
|
||||
|
||||
## 3. Encode with `Anchor` and `Alias`
|
||||
|
||||
### 3.1. Explicitly declared `Anchor` name and `Alias` name
|
||||
|
||||
If you want to use `anchor`, you can define it as a struct tag.
|
||||
If the value specified for an anchor is a pointer type and the same address as the pointer is found, the value is automatically set to alias.
|
||||
If an explicit alias name is specified, an error is raised if its value is different from the value specified in the anchor.
|
||||
|
||||
```go
|
||||
type T struct {
|
||||
A int
|
||||
B string
|
||||
}
|
||||
var v struct {
|
||||
C *T `yaml:"c,anchor=x"`
|
||||
D *T `yaml:"d,alias=x"`
|
||||
}
|
||||
v.C = &T{A: 1, B: "hello"}
|
||||
v.D = v.C
|
||||
bytes, err := yaml.Marshal(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(bytes))
|
||||
/*
|
||||
c: &x
|
||||
a: 1
|
||||
b: hello
|
||||
d: *x
|
||||
*/
|
||||
```
|
||||
|
||||
### 3.2. Implicitly declared `Anchor` and `Alias` names
|
||||
|
||||
If you do not explicitly declare the anchor name, the default behavior is to
|
||||
use the equivalent of `strings.ToLower($FieldName)` as the name of the anchor.
|
||||
If the value specified for an anchor is a pointer type and the same address as the pointer is found, the value is automatically set to alias.
|
||||
|
||||
```go
|
||||
type T struct {
|
||||
I int
|
||||
S string
|
||||
}
|
||||
var v struct {
|
||||
A *T `yaml:"a,anchor"`
|
||||
B *T `yaml:"b,anchor"`
|
||||
C *T `yaml:"c"`
|
||||
D *T `yaml:"d"`
|
||||
}
|
||||
v.A = &T{I: 1, S: "hello"}
|
||||
v.B = &T{I: 2, S: "world"}
|
||||
v.C = v.A // C has same pointer address to A
|
||||
v.D = v.B // D has same pointer address to B
|
||||
bytes, err := yaml.Marshal(v)
|
||||
if err != nil {
|
||||
//...
|
||||
}
|
||||
fmt.Println(string(bytes))
|
||||
/*
|
||||
a: &a
|
||||
i: 1
|
||||
s: hello
|
||||
b: &b
|
||||
i: 2
|
||||
s: world
|
||||
c: *a
|
||||
d: *b
|
||||
*/
|
||||
```
|
||||
|
||||
### 3.3 MergeKey and Alias
|
||||
|
||||
Merge key and alias ( `<<: *alias` ) can be used by embedding a structure with the `inline,alias` tag.
|
||||
|
||||
```go
|
||||
type Person struct {
|
||||
*Person `yaml:",omitempty,inline,alias"` // embed Person type for default value
|
||||
Name string `yaml:",omitempty"`
|
||||
Age int `yaml:",omitempty"`
|
||||
}
|
||||
defaultPerson := &Person{
|
||||
Name: "John Smith",
|
||||
Age: 20,
|
||||
}
|
||||
people := []*Person{
|
||||
{
|
||||
Person: defaultPerson, // assign default value
|
||||
Name: "Ken", // override Name property
|
||||
Age: 10, // override Age property
|
||||
},
|
||||
{
|
||||
Person: defaultPerson, // assign default value only
|
||||
},
|
||||
}
|
||||
var doc struct {
|
||||
Default *Person `yaml:"default,anchor"`
|
||||
People []*Person `yaml:"people"`
|
||||
}
|
||||
doc.Default = defaultPerson
|
||||
doc.People = people
|
||||
bytes, err := yaml.Marshal(doc)
|
||||
if err != nil {
|
||||
//...
|
||||
}
|
||||
fmt.Println(string(bytes))
|
||||
/*
|
||||
default: &default
|
||||
name: John Smith
|
||||
age: 20
|
||||
people:
|
||||
- <<: *default
|
||||
name: Ken
|
||||
age: 10
|
||||
- <<: *default
|
||||
*/
|
||||
```
|
||||
|
||||
## 4. Pretty Formatted Errors
|
||||
|
||||
Error values produced during parsing have two extra features over regular
|
||||
error values.
|
||||
|
||||
First, by default, they contain extra information on the location of the error
|
||||
from the source YAML document, to make it easier to find the error location.
|
||||
|
||||
Second, the error messages can optionally be colorized.
|
||||
|
||||
If you would like to control exactly how the output looks like, consider
|
||||
using `yaml.FormatError`, which accepts two boolean values to
|
||||
control turning these features on or off.
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/209884/67358124-587f0980-f59a-11e9-96fc-7205aab77695.png"></img>
|
||||
|
||||
## 5. Use YAMLPath
|
||||
|
||||
```go
|
||||
yml := `
|
||||
store:
|
||||
book:
|
||||
- author: john
|
||||
price: 10
|
||||
- author: ken
|
||||
price: 12
|
||||
bicycle:
|
||||
color: red
|
||||
price: 19.95
|
||||
`
|
||||
path, err := yaml.PathString("$.store.book[*].author")
|
||||
if err != nil {
|
||||
//...
|
||||
}
|
||||
var authors []string
|
||||
if err := path.Read(strings.NewReader(yml), &authors); err != nil {
|
||||
//...
|
||||
}
|
||||
fmt.Println(authors)
|
||||
// [john ken]
|
||||
```
|
||||
|
||||
### 5.1 Print customized error with YAML source code
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
)
|
||||
|
||||
func main() {
|
||||
yml := `
|
||||
a: 1
|
||||
b: "hello"
|
||||
`
|
||||
var v struct {
|
||||
A int
|
||||
B string
|
||||
}
|
||||
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if v.A != 2 {
|
||||
// output error with YAML source
|
||||
path, err := yaml.PathString("$.a")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
source, err := path.AnnotateSource([]byte(yml), true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("a value expected 2 but actual %d:\n%s\n", v.A, string(source))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
output result is the following:
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/209884/84148813-7aca8680-aa9a-11ea-8fc9-37dece2ebdac.png"></img>
|
||||
|
||||
|
||||
# Tools
|
||||
|
||||
## ycat
|
||||
|
||||
print yaml file with color
|
||||
|
||||
<img width="713" alt="ycat" src="https://user-images.githubusercontent.com/209884/66986084-19b00600-f0f9-11e9-9f0e-1f91eb072fe0.png">
|
||||
|
||||
### Installation
|
||||
|
||||
```sh
|
||||
git clone https://github.com/goccy/go-yaml.git
|
||||
cd go-yaml/cmd/ycat && go install .
|
||||
```
|
||||
|
||||
|
||||
# For Developers
|
||||
|
||||
> [!NOTE]
|
||||
> In this project, we manage such test code under the `testdata` directory to avoid adding dependencies on libraries that are only needed for testing to the top `go.mod` file. Therefore, if you want to add test cases that use 3rd party libraries, please add the test code to the `testdata` directory.
|
||||
|
||||
# Looking for Sponsors
|
||||
|
||||
I'm looking for sponsors this library. This library is being developed as a personal project in my spare time. If you want a quick response or problem resolution when using this library in your project, please register as a [sponsor](https://github.com/sponsors/goccy). I will cooperate as much as possible. Of course, this library is developed as an MIT license, so you can use it freely for free.
|
||||
|
||||
# License
|
||||
|
||||
MIT
|
||||
2381
vendor/github.com/goccy/go-yaml/ast/ast.go
generated
vendored
Normal file
2381
vendor/github.com/goccy/go-yaml/ast/ast.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
37
vendor/github.com/goccy/go-yaml/context.go
generated
vendored
Normal file
37
vendor/github.com/goccy/go-yaml/context.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
package yaml
|
||||
|
||||
import "context"
|
||||
|
||||
type (
|
||||
ctxMergeKey struct{}
|
||||
ctxAnchorKey struct{}
|
||||
)
|
||||
|
||||
func withMerge(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, ctxMergeKey{}, true)
|
||||
}
|
||||
|
||||
func isMerge(ctx context.Context) bool {
|
||||
v, ok := ctx.Value(ctxMergeKey{}).(bool)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func withAnchor(ctx context.Context, name string) context.Context {
|
||||
anchorMap := getAnchorMap(ctx)
|
||||
if anchorMap == nil {
|
||||
anchorMap = make(map[string]struct{})
|
||||
}
|
||||
anchorMap[name] = struct{}{}
|
||||
return context.WithValue(ctx, ctxAnchorKey{}, anchorMap)
|
||||
}
|
||||
|
||||
func getAnchorMap(ctx context.Context) map[string]struct{} {
|
||||
v, ok := ctx.Value(ctxAnchorKey{}).(map[string]struct{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return v
|
||||
}
|
||||
2037
vendor/github.com/goccy/go-yaml/decode.go
generated
vendored
Normal file
2037
vendor/github.com/goccy/go-yaml/decode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1074
vendor/github.com/goccy/go-yaml/encode.go
generated
vendored
Normal file
1074
vendor/github.com/goccy/go-yaml/encode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
77
vendor/github.com/goccy/go-yaml/error.go
generated
vendored
Normal file
77
vendor/github.com/goccy/go-yaml/error.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/goccy/go-yaml/ast"
|
||||
"github.com/goccy/go-yaml/internal/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidQuery = errors.New("invalid query")
|
||||
ErrInvalidPath = errors.New("invalid path instance")
|
||||
ErrInvalidPathString = errors.New("invalid path string")
|
||||
ErrNotFoundNode = errors.New("node not found")
|
||||
ErrUnknownCommentPositionType = errors.New("unknown comment position type")
|
||||
ErrInvalidCommentMapValue = errors.New("invalid comment map value. it must be not nil value")
|
||||
ErrDecodeRequiredPointerType = errors.New("required pointer type value")
|
||||
ErrExceededMaxDepth = errors.New("exceeded max depth")
|
||||
FormatErrorWithToken = errors.FormatError
|
||||
)
|
||||
|
||||
type (
|
||||
SyntaxError = errors.SyntaxError
|
||||
TypeError = errors.TypeError
|
||||
OverflowError = errors.OverflowError
|
||||
DuplicateKeyError = errors.DuplicateKeyError
|
||||
UnknownFieldError = errors.UnknownFieldError
|
||||
UnexpectedNodeTypeError = errors.UnexpectedNodeTypeError
|
||||
Error = errors.Error
|
||||
)
|
||||
|
||||
func ErrUnsupportedHeadPositionType(node ast.Node) error {
|
||||
return fmt.Errorf("unsupported comment head position for %s", node.Type())
|
||||
}
|
||||
|
||||
func ErrUnsupportedLinePositionType(node ast.Node) error {
|
||||
return fmt.Errorf("unsupported comment line position for %s", node.Type())
|
||||
}
|
||||
|
||||
func ErrUnsupportedFootPositionType(node ast.Node) error {
|
||||
return fmt.Errorf("unsupported comment foot position for %s", node.Type())
|
||||
}
|
||||
|
||||
// IsInvalidQueryError whether err is ErrInvalidQuery or not.
|
||||
func IsInvalidQueryError(err error) bool {
|
||||
return errors.Is(err, ErrInvalidQuery)
|
||||
}
|
||||
|
||||
// IsInvalidPathError whether err is ErrInvalidPath or not.
|
||||
func IsInvalidPathError(err error) bool {
|
||||
return errors.Is(err, ErrInvalidPath)
|
||||
}
|
||||
|
||||
// IsInvalidPathStringError whether err is ErrInvalidPathString or not.
|
||||
func IsInvalidPathStringError(err error) bool {
|
||||
return errors.Is(err, ErrInvalidPathString)
|
||||
}
|
||||
|
||||
// IsNotFoundNodeError whether err is ErrNotFoundNode or not.
|
||||
func IsNotFoundNodeError(err error) bool {
|
||||
return errors.Is(err, ErrNotFoundNode)
|
||||
}
|
||||
|
||||
// IsInvalidTokenTypeError whether err is ast.ErrInvalidTokenType or not.
|
||||
func IsInvalidTokenTypeError(err error) bool {
|
||||
return errors.Is(err, ast.ErrInvalidTokenType)
|
||||
}
|
||||
|
||||
// IsInvalidAnchorNameError whether err is ast.ErrInvalidAnchorName or not.
|
||||
func IsInvalidAnchorNameError(err error) bool {
|
||||
return errors.Is(err, ast.ErrInvalidAnchorName)
|
||||
}
|
||||
|
||||
// IsInvalidAliasNameError whether err is ast.ErrInvalidAliasName or not.
|
||||
func IsInvalidAliasNameError(err error) bool {
|
||||
return errors.Is(err, ast.ErrInvalidAliasName)
|
||||
}
|
||||
246
vendor/github.com/goccy/go-yaml/internal/errors/error.go
generated
vendored
Normal file
246
vendor/github.com/goccy/go-yaml/internal/errors/error.go
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/goccy/go-yaml/ast"
|
||||
"github.com/goccy/go-yaml/printer"
|
||||
"github.com/goccy/go-yaml/token"
|
||||
)
|
||||
|
||||
var (
|
||||
As = errors.As
|
||||
Is = errors.Is
|
||||
New = errors.New
|
||||
)
|
||||
|
||||
const (
|
||||
defaultFormatColor = false
|
||||
defaultIncludeSource = true
|
||||
)
|
||||
|
||||
type Error interface {
|
||||
error
|
||||
GetToken() *token.Token
|
||||
GetMessage() string
|
||||
FormatError(bool, bool) string
|
||||
}
|
||||
|
||||
var (
|
||||
_ Error = new(SyntaxError)
|
||||
_ Error = new(TypeError)
|
||||
_ Error = new(OverflowError)
|
||||
_ Error = new(DuplicateKeyError)
|
||||
_ Error = new(UnknownFieldError)
|
||||
_ Error = new(UnexpectedNodeTypeError)
|
||||
)
|
||||
|
||||
type SyntaxError struct {
|
||||
Message string
|
||||
Token *token.Token
|
||||
}
|
||||
|
||||
type TypeError struct {
|
||||
DstType reflect.Type
|
||||
SrcType reflect.Type
|
||||
StructFieldName *string
|
||||
Token *token.Token
|
||||
}
|
||||
|
||||
type OverflowError struct {
|
||||
DstType reflect.Type
|
||||
SrcNum string
|
||||
Token *token.Token
|
||||
}
|
||||
|
||||
type DuplicateKeyError struct {
|
||||
Message string
|
||||
Token *token.Token
|
||||
}
|
||||
|
||||
type UnknownFieldError struct {
|
||||
Message string
|
||||
Token *token.Token
|
||||
}
|
||||
|
||||
type UnexpectedNodeTypeError struct {
|
||||
Actual ast.NodeType
|
||||
Expected ast.NodeType
|
||||
Token *token.Token
|
||||
}
|
||||
|
||||
// ErrSyntax create syntax error instance with message and token
|
||||
func ErrSyntax(msg string, tk *token.Token) *SyntaxError {
|
||||
return &SyntaxError{
|
||||
Message: msg,
|
||||
Token: tk,
|
||||
}
|
||||
}
|
||||
|
||||
// ErrOverflow creates an overflow error instance with message and a token.
|
||||
func ErrOverflow(dstType reflect.Type, num string, tk *token.Token) *OverflowError {
|
||||
return &OverflowError{
|
||||
DstType: dstType,
|
||||
SrcNum: num,
|
||||
Token: tk,
|
||||
}
|
||||
}
|
||||
|
||||
// ErrTypeMismatch cerates an type mismatch error instance with token.
|
||||
func ErrTypeMismatch(dstType, srcType reflect.Type, token *token.Token) *TypeError {
|
||||
return &TypeError{
|
||||
DstType: dstType,
|
||||
SrcType: srcType,
|
||||
Token: token,
|
||||
}
|
||||
}
|
||||
|
||||
// ErrDuplicateKey creates an duplicate key error instance with token.
|
||||
func ErrDuplicateKey(msg string, tk *token.Token) *DuplicateKeyError {
|
||||
return &DuplicateKeyError{
|
||||
Message: msg,
|
||||
Token: tk,
|
||||
}
|
||||
}
|
||||
|
||||
// ErrUnknownField creates an unknown field error instance with token.
|
||||
func ErrUnknownField(msg string, tk *token.Token) *UnknownFieldError {
|
||||
return &UnknownFieldError{
|
||||
Message: msg,
|
||||
Token: tk,
|
||||
}
|
||||
}
|
||||
|
||||
func ErrUnexpectedNodeType(actual, expected ast.NodeType, tk *token.Token) *UnexpectedNodeTypeError {
|
||||
return &UnexpectedNodeTypeError{
|
||||
Actual: actual,
|
||||
Expected: expected,
|
||||
Token: tk,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *SyntaxError) GetMessage() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func (e *SyntaxError) GetToken() *token.Token {
|
||||
return e.Token
|
||||
}
|
||||
|
||||
func (e *SyntaxError) Error() string {
|
||||
return e.FormatError(defaultFormatColor, defaultIncludeSource)
|
||||
}
|
||||
|
||||
func (e *SyntaxError) FormatError(colored, inclSource bool) string {
|
||||
return FormatError(e.Message, e.Token, colored, inclSource)
|
||||
}
|
||||
|
||||
func (e *OverflowError) GetMessage() string {
|
||||
return e.msg()
|
||||
}
|
||||
|
||||
func (e *OverflowError) GetToken() *token.Token {
|
||||
return e.Token
|
||||
}
|
||||
|
||||
func (e *OverflowError) Error() string {
|
||||
return e.FormatError(defaultFormatColor, defaultIncludeSource)
|
||||
}
|
||||
|
||||
func (e *OverflowError) FormatError(colored, inclSource bool) string {
|
||||
return FormatError(e.msg(), e.Token, colored, inclSource)
|
||||
}
|
||||
|
||||
func (e *OverflowError) msg() string {
|
||||
return fmt.Sprintf("cannot unmarshal %s into Go value of type %s ( overflow )", e.SrcNum, e.DstType)
|
||||
}
|
||||
|
||||
func (e *TypeError) msg() string {
|
||||
if e.StructFieldName != nil {
|
||||
return fmt.Sprintf("cannot unmarshal %s into Go struct field %s of type %s", e.SrcType, *e.StructFieldName, e.DstType)
|
||||
}
|
||||
return fmt.Sprintf("cannot unmarshal %s into Go value of type %s", e.SrcType, e.DstType)
|
||||
}
|
||||
|
||||
func (e *TypeError) GetMessage() string {
|
||||
return e.msg()
|
||||
}
|
||||
|
||||
func (e *TypeError) GetToken() *token.Token {
|
||||
return e.Token
|
||||
}
|
||||
|
||||
func (e *TypeError) Error() string {
|
||||
return e.FormatError(defaultFormatColor, defaultIncludeSource)
|
||||
}
|
||||
|
||||
func (e *TypeError) FormatError(colored, inclSource bool) string {
|
||||
return FormatError(e.msg(), e.Token, colored, inclSource)
|
||||
}
|
||||
|
||||
func (e *DuplicateKeyError) GetMessage() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func (e *DuplicateKeyError) GetToken() *token.Token {
|
||||
return e.Token
|
||||
}
|
||||
|
||||
func (e *DuplicateKeyError) Error() string {
|
||||
return e.FormatError(defaultFormatColor, defaultIncludeSource)
|
||||
}
|
||||
|
||||
func (e *DuplicateKeyError) FormatError(colored, inclSource bool) string {
|
||||
return FormatError(e.Message, e.Token, colored, inclSource)
|
||||
}
|
||||
|
||||
func (e *UnknownFieldError) GetMessage() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func (e *UnknownFieldError) GetToken() *token.Token {
|
||||
return e.Token
|
||||
}
|
||||
|
||||
func (e *UnknownFieldError) Error() string {
|
||||
return e.FormatError(defaultFormatColor, defaultIncludeSource)
|
||||
}
|
||||
|
||||
func (e *UnknownFieldError) FormatError(colored, inclSource bool) string {
|
||||
return FormatError(e.Message, e.Token, colored, inclSource)
|
||||
}
|
||||
|
||||
func (e *UnexpectedNodeTypeError) GetMessage() string {
|
||||
return e.msg()
|
||||
}
|
||||
|
||||
func (e *UnexpectedNodeTypeError) GetToken() *token.Token {
|
||||
return e.Token
|
||||
}
|
||||
|
||||
func (e *UnexpectedNodeTypeError) Error() string {
|
||||
return e.FormatError(defaultFormatColor, defaultIncludeSource)
|
||||
}
|
||||
|
||||
func (e *UnexpectedNodeTypeError) FormatError(colored, inclSource bool) string {
|
||||
return FormatError(e.msg(), e.Token, colored, inclSource)
|
||||
}
|
||||
|
||||
func (e *UnexpectedNodeTypeError) msg() string {
|
||||
return fmt.Sprintf("%s was used where %s is expected", e.Actual.YAMLName(), e.Expected.YAMLName())
|
||||
}
|
||||
|
||||
func FormatError(errMsg string, token *token.Token, colored, inclSource bool) string {
|
||||
var pp printer.Printer
|
||||
if token == nil {
|
||||
return pp.PrintErrorMessage(errMsg, colored)
|
||||
}
|
||||
pos := fmt.Sprintf("[%d:%d] ", token.Position.Line, token.Position.Column)
|
||||
msg := pp.PrintErrorMessage(fmt.Sprintf("%s%s", pos, errMsg), colored)
|
||||
if inclSource {
|
||||
msg += "\n" + pp.PrintErrorToken(token, colored)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
541
vendor/github.com/goccy/go-yaml/internal/format/format.go
generated
vendored
Normal file
541
vendor/github.com/goccy/go-yaml/internal/format/format.go
generated
vendored
Normal file
@@ -0,0 +1,541 @@
|
||||
package format
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml/ast"
|
||||
"github.com/goccy/go-yaml/token"
|
||||
)
|
||||
|
||||
func FormatNodeWithResolvedAlias(n ast.Node, anchorNodeMap map[string]ast.Node) string {
|
||||
tk := getFirstToken(n)
|
||||
if tk == nil {
|
||||
return ""
|
||||
}
|
||||
formatter := newFormatter(tk, hasComment(n))
|
||||
formatter.anchorNodeMap = anchorNodeMap
|
||||
return formatter.format(n)
|
||||
}
|
||||
|
||||
func FormatNode(n ast.Node) string {
|
||||
tk := getFirstToken(n)
|
||||
if tk == nil {
|
||||
return ""
|
||||
}
|
||||
return newFormatter(tk, hasComment(n)).format(n)
|
||||
}
|
||||
|
||||
func FormatFile(file *ast.File) string {
|
||||
if len(file.Docs) == 0 {
|
||||
return ""
|
||||
}
|
||||
tk := getFirstToken(file.Docs[0])
|
||||
if tk == nil {
|
||||
return ""
|
||||
}
|
||||
return newFormatter(tk, hasCommentFile(file)).formatFile(file)
|
||||
}
|
||||
|
||||
func hasCommentFile(f *ast.File) bool {
|
||||
for _, doc := range f.Docs {
|
||||
if hasComment(doc.Body) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasComment(n ast.Node) bool {
|
||||
if n == nil {
|
||||
return false
|
||||
}
|
||||
switch nn := n.(type) {
|
||||
case *ast.DocumentNode:
|
||||
return hasComment(nn.Body)
|
||||
case *ast.NullNode:
|
||||
return nn.Comment != nil
|
||||
case *ast.BoolNode:
|
||||
return nn.Comment != nil
|
||||
case *ast.IntegerNode:
|
||||
return nn.Comment != nil
|
||||
case *ast.FloatNode:
|
||||
return nn.Comment != nil
|
||||
case *ast.StringNode:
|
||||
return nn.Comment != nil
|
||||
case *ast.InfinityNode:
|
||||
return nn.Comment != nil
|
||||
case *ast.NanNode:
|
||||
return nn.Comment != nil
|
||||
case *ast.LiteralNode:
|
||||
return nn.Comment != nil
|
||||
case *ast.DirectiveNode:
|
||||
if nn.Comment != nil {
|
||||
return true
|
||||
}
|
||||
for _, value := range nn.Values {
|
||||
if hasComment(value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case *ast.TagNode:
|
||||
if nn.Comment != nil {
|
||||
return true
|
||||
}
|
||||
return hasComment(nn.Value)
|
||||
case *ast.MappingNode:
|
||||
if nn.Comment != nil || nn.FootComment != nil {
|
||||
return true
|
||||
}
|
||||
for _, value := range nn.Values {
|
||||
if value.Comment != nil || value.FootComment != nil {
|
||||
return true
|
||||
}
|
||||
if hasComment(value.Key) {
|
||||
return true
|
||||
}
|
||||
if hasComment(value.Value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case *ast.MappingKeyNode:
|
||||
return nn.Comment != nil
|
||||
case *ast.MergeKeyNode:
|
||||
return nn.Comment != nil
|
||||
case *ast.SequenceNode:
|
||||
if nn.Comment != nil || nn.FootComment != nil {
|
||||
return true
|
||||
}
|
||||
for _, entry := range nn.Entries {
|
||||
if entry.Comment != nil || entry.HeadComment != nil || entry.LineComment != nil {
|
||||
return true
|
||||
}
|
||||
if hasComment(entry.Value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case *ast.AnchorNode:
|
||||
if nn.Comment != nil {
|
||||
return true
|
||||
}
|
||||
if hasComment(nn.Name) || hasComment(nn.Value) {
|
||||
return true
|
||||
}
|
||||
case *ast.AliasNode:
|
||||
if nn.Comment != nil {
|
||||
return true
|
||||
}
|
||||
if hasComment(nn.Value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getFirstToken(n ast.Node) *token.Token {
|
||||
if n == nil {
|
||||
return nil
|
||||
}
|
||||
switch nn := n.(type) {
|
||||
case *ast.DocumentNode:
|
||||
if nn.Start != nil {
|
||||
return nn.Start
|
||||
}
|
||||
return getFirstToken(nn.Body)
|
||||
case *ast.NullNode:
|
||||
return nn.Token
|
||||
case *ast.BoolNode:
|
||||
return nn.Token
|
||||
case *ast.IntegerNode:
|
||||
return nn.Token
|
||||
case *ast.FloatNode:
|
||||
return nn.Token
|
||||
case *ast.StringNode:
|
||||
return nn.Token
|
||||
case *ast.InfinityNode:
|
||||
return nn.Token
|
||||
case *ast.NanNode:
|
||||
return nn.Token
|
||||
case *ast.LiteralNode:
|
||||
return nn.Start
|
||||
case *ast.DirectiveNode:
|
||||
return nn.Start
|
||||
case *ast.TagNode:
|
||||
return nn.Start
|
||||
case *ast.MappingNode:
|
||||
if nn.IsFlowStyle {
|
||||
return nn.Start
|
||||
}
|
||||
if len(nn.Values) == 0 {
|
||||
return nn.Start
|
||||
}
|
||||
return getFirstToken(nn.Values[0].Key)
|
||||
case *ast.MappingKeyNode:
|
||||
return nn.Start
|
||||
case *ast.MergeKeyNode:
|
||||
return nn.Token
|
||||
case *ast.SequenceNode:
|
||||
return nn.Start
|
||||
case *ast.AnchorNode:
|
||||
return nn.Start
|
||||
case *ast.AliasNode:
|
||||
return nn.Start
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Formatter struct {
|
||||
existsComment bool
|
||||
tokenToOriginMap map[*token.Token]string
|
||||
anchorNodeMap map[string]ast.Node
|
||||
}
|
||||
|
||||
func newFormatter(tk *token.Token, existsComment bool) *Formatter {
|
||||
tokenToOriginMap := make(map[*token.Token]string)
|
||||
for tk.Prev != nil {
|
||||
tk = tk.Prev
|
||||
}
|
||||
tokenToOriginMap[tk] = tk.Origin
|
||||
|
||||
var origin string
|
||||
for tk.Next != nil {
|
||||
tk = tk.Next
|
||||
if tk.Type == token.CommentType {
|
||||
origin += strings.Repeat("\n", strings.Count(normalizeNewLineChars(tk.Origin), "\n"))
|
||||
continue
|
||||
}
|
||||
origin += tk.Origin
|
||||
tokenToOriginMap[tk] = origin
|
||||
origin = ""
|
||||
}
|
||||
return &Formatter{
|
||||
existsComment: existsComment,
|
||||
tokenToOriginMap: tokenToOriginMap,
|
||||
}
|
||||
}
|
||||
|
||||
func getIndentNumByFirstLineToken(tk *token.Token) int {
|
||||
defaultIndent := tk.Position.Column - 1
|
||||
|
||||
// key: value
|
||||
// ^
|
||||
// next
|
||||
if tk.Type == token.SequenceEntryType {
|
||||
// If the current token is the sequence entry.
|
||||
// the indent is calculated from the column value of the current token.
|
||||
return defaultIndent
|
||||
}
|
||||
|
||||
// key: value
|
||||
// ^
|
||||
// next
|
||||
if tk.Next != nil && tk.Next.Type == token.MappingValueType {
|
||||
// If the current token is the key in the mapping-value,
|
||||
// the indent is calculated from the column value of the current token.
|
||||
return defaultIndent
|
||||
}
|
||||
|
||||
if tk.Prev == nil {
|
||||
return defaultIndent
|
||||
}
|
||||
prev := tk.Prev
|
||||
|
||||
// key: value
|
||||
// ^
|
||||
// prev
|
||||
if prev.Type == token.MappingValueType {
|
||||
// If the current token is the value in the mapping-value,
|
||||
// the indent is calculated from the column value of the key two steps back.
|
||||
if prev.Prev == nil {
|
||||
return defaultIndent
|
||||
}
|
||||
return prev.Prev.Position.Column - 1
|
||||
}
|
||||
|
||||
// - value
|
||||
// ^
|
||||
// prev
|
||||
if prev.Type == token.SequenceEntryType {
|
||||
// If the value is not a mapping-value and the previous token was a sequence entry,
|
||||
// the indent is calculated using the column value of the sequence entry token.
|
||||
return prev.Position.Column - 1
|
||||
}
|
||||
|
||||
return defaultIndent
|
||||
}
|
||||
|
||||
func (f *Formatter) format(n ast.Node) string {
|
||||
return f.trimSpacePrefix(
|
||||
f.trimIndentSpace(
|
||||
getIndentNumByFirstLineToken(getFirstToken(n)),
|
||||
f.trimNewLineCharPrefix(f.formatNode(n)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (f *Formatter) formatFile(file *ast.File) string {
|
||||
if len(file.Docs) == 0 {
|
||||
return ""
|
||||
}
|
||||
var ret string
|
||||
for _, doc := range file.Docs {
|
||||
ret += f.formatDocument(doc)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (f *Formatter) origin(tk *token.Token) string {
|
||||
if tk == nil {
|
||||
return ""
|
||||
}
|
||||
if f.existsComment {
|
||||
return tk.Origin
|
||||
}
|
||||
return f.tokenToOriginMap[tk]
|
||||
}
|
||||
|
||||
func (f *Formatter) formatDocument(n *ast.DocumentNode) string {
|
||||
return f.origin(n.Start) + f.formatNode(n.Body) + f.origin(n.End)
|
||||
}
|
||||
|
||||
func (f *Formatter) formatNull(n *ast.NullNode) string {
|
||||
return f.origin(n.Token) + f.formatCommentGroup(n.Comment)
|
||||
}
|
||||
|
||||
func (f *Formatter) formatString(n *ast.StringNode) string {
|
||||
return f.origin(n.Token) + f.formatCommentGroup(n.Comment)
|
||||
}
|
||||
|
||||
func (f *Formatter) formatInteger(n *ast.IntegerNode) string {
|
||||
return f.origin(n.Token) + f.formatCommentGroup(n.Comment)
|
||||
}
|
||||
|
||||
func (f *Formatter) formatFloat(n *ast.FloatNode) string {
|
||||
return f.origin(n.Token) + f.formatCommentGroup(n.Comment)
|
||||
}
|
||||
|
||||
func (f *Formatter) formatBool(n *ast.BoolNode) string {
|
||||
return f.origin(n.Token) + f.formatCommentGroup(n.Comment)
|
||||
}
|
||||
|
||||
func (f *Formatter) formatInfinity(n *ast.InfinityNode) string {
|
||||
return f.origin(n.Token) + f.formatCommentGroup(n.Comment)
|
||||
}
|
||||
|
||||
func (f *Formatter) formatNan(n *ast.NanNode) string {
|
||||
return f.origin(n.Token) + f.formatCommentGroup(n.Comment)
|
||||
}
|
||||
|
||||
func (f *Formatter) formatLiteral(n *ast.LiteralNode) string {
|
||||
return f.origin(n.Start) + f.formatCommentGroup(n.Comment) + f.origin(n.Value.Token)
|
||||
}
|
||||
|
||||
func (f *Formatter) formatMergeKey(n *ast.MergeKeyNode) string {
|
||||
return f.origin(n.Token)
|
||||
}
|
||||
|
||||
func (f *Formatter) formatMappingValue(n *ast.MappingValueNode) string {
|
||||
return f.formatCommentGroup(n.Comment) +
|
||||
f.origin(n.Key.GetToken()) + ":" + f.formatCommentGroup(n.Key.GetComment()) + f.formatNode(n.Value) +
|
||||
f.formatCommentGroup(n.FootComment)
|
||||
}
|
||||
|
||||
func (f *Formatter) formatDirective(n *ast.DirectiveNode) string {
|
||||
ret := f.origin(n.Start) + f.formatNode(n.Name)
|
||||
for _, val := range n.Values {
|
||||
ret += f.formatNode(val)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (f *Formatter) formatMapping(n *ast.MappingNode) string {
|
||||
var ret string
|
||||
if n.IsFlowStyle {
|
||||
ret = f.origin(n.Start)
|
||||
} else {
|
||||
ret += f.formatCommentGroup(n.Comment)
|
||||
}
|
||||
for _, value := range n.Values {
|
||||
if value.CollectEntry != nil {
|
||||
ret += f.origin(value.CollectEntry)
|
||||
}
|
||||
ret += f.formatMappingValue(value)
|
||||
}
|
||||
if n.IsFlowStyle {
|
||||
ret += f.origin(n.End)
|
||||
ret += f.formatCommentGroup(n.Comment)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (f *Formatter) formatTag(n *ast.TagNode) string {
|
||||
return f.origin(n.Start) + f.formatNode(n.Value)
|
||||
}
|
||||
|
||||
func (f *Formatter) formatMappingKey(n *ast.MappingKeyNode) string {
|
||||
return f.origin(n.Start) + f.formatNode(n.Value)
|
||||
}
|
||||
|
||||
func (f *Formatter) formatSequence(n *ast.SequenceNode) string {
|
||||
var ret string
|
||||
if n.IsFlowStyle {
|
||||
ret = f.origin(n.Start)
|
||||
} else {
|
||||
// add head comment.
|
||||
ret += f.formatCommentGroup(n.Comment)
|
||||
}
|
||||
for _, entry := range n.Entries {
|
||||
ret += f.formatNode(entry)
|
||||
}
|
||||
if n.IsFlowStyle {
|
||||
ret += f.origin(n.End)
|
||||
ret += f.formatCommentGroup(n.Comment)
|
||||
}
|
||||
ret += f.formatCommentGroup(n.FootComment)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (f *Formatter) formatSequenceEntry(n *ast.SequenceEntryNode) string {
|
||||
return f.formatCommentGroup(n.HeadComment) + f.origin(n.Start) + f.formatCommentGroup(n.LineComment) + f.formatNode(n.Value)
|
||||
}
|
||||
|
||||
func (f *Formatter) formatAnchor(n *ast.AnchorNode) string {
|
||||
return f.origin(n.Start) + f.formatNode(n.Name) + f.formatNode(n.Value)
|
||||
}
|
||||
|
||||
func (f *Formatter) formatAlias(n *ast.AliasNode) string {
|
||||
if f.anchorNodeMap != nil {
|
||||
anchorName := n.Value.GetToken().Value
|
||||
node := f.anchorNodeMap[anchorName]
|
||||
if node != nil {
|
||||
formatted := f.formatNode(node)
|
||||
// If formatted text contains newline characters, indentation needs to be considered.
|
||||
if strings.Contains(formatted, "\n") {
|
||||
// If the first character is not a newline, the first line should be output without indentation.
|
||||
isIgnoredFirstLine := !strings.HasPrefix(formatted, "\n")
|
||||
formatted = f.addIndentSpace(n.GetToken().Position.IndentNum, formatted, isIgnoredFirstLine)
|
||||
}
|
||||
return formatted
|
||||
}
|
||||
}
|
||||
return f.origin(n.Start) + f.formatNode(n.Value)
|
||||
}
|
||||
|
||||
func (f *Formatter) formatNode(n ast.Node) string {
|
||||
switch nn := n.(type) {
|
||||
case *ast.DocumentNode:
|
||||
return f.formatDocument(nn)
|
||||
case *ast.NullNode:
|
||||
return f.formatNull(nn)
|
||||
case *ast.BoolNode:
|
||||
return f.formatBool(nn)
|
||||
case *ast.IntegerNode:
|
||||
return f.formatInteger(nn)
|
||||
case *ast.FloatNode:
|
||||
return f.formatFloat(nn)
|
||||
case *ast.StringNode:
|
||||
return f.formatString(nn)
|
||||
case *ast.InfinityNode:
|
||||
return f.formatInfinity(nn)
|
||||
case *ast.NanNode:
|
||||
return f.formatNan(nn)
|
||||
case *ast.LiteralNode:
|
||||
return f.formatLiteral(nn)
|
||||
case *ast.DirectiveNode:
|
||||
return f.formatDirective(nn)
|
||||
case *ast.TagNode:
|
||||
return f.formatTag(nn)
|
||||
case *ast.MappingNode:
|
||||
return f.formatMapping(nn)
|
||||
case *ast.MappingKeyNode:
|
||||
return f.formatMappingKey(nn)
|
||||
case *ast.MappingValueNode:
|
||||
return f.formatMappingValue(nn)
|
||||
case *ast.MergeKeyNode:
|
||||
return f.formatMergeKey(nn)
|
||||
case *ast.SequenceNode:
|
||||
return f.formatSequence(nn)
|
||||
case *ast.SequenceEntryNode:
|
||||
return f.formatSequenceEntry(nn)
|
||||
case *ast.AnchorNode:
|
||||
return f.formatAnchor(nn)
|
||||
case *ast.AliasNode:
|
||||
return f.formatAlias(nn)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *Formatter) formatCommentGroup(g *ast.CommentGroupNode) string {
|
||||
if g == nil {
|
||||
return ""
|
||||
}
|
||||
var ret string
|
||||
for _, cm := range g.Comments {
|
||||
ret += f.formatComment(cm)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (f *Formatter) formatComment(n *ast.CommentNode) string {
|
||||
if n == nil {
|
||||
return ""
|
||||
}
|
||||
return n.Token.Origin
|
||||
}
|
||||
|
||||
// nolint: unused
|
||||
func (f *Formatter) formatIndent(col int) string {
|
||||
if col <= 1 {
|
||||
return ""
|
||||
}
|
||||
return strings.Repeat(" ", col-1)
|
||||
}
|
||||
|
||||
func (f *Formatter) trimNewLineCharPrefix(v string) string {
|
||||
return strings.TrimLeftFunc(v, func(r rune) bool {
|
||||
return r == '\n' || r == '\r'
|
||||
})
|
||||
}
|
||||
|
||||
func (f *Formatter) trimSpacePrefix(v string) string {
|
||||
return strings.TrimLeftFunc(v, func(r rune) bool {
|
||||
return r == ' '
|
||||
})
|
||||
}
|
||||
|
||||
func (f *Formatter) trimIndentSpace(trimIndentNum int, v string) string {
|
||||
if trimIndentNum == 0 {
|
||||
return v
|
||||
}
|
||||
lines := strings.Split(normalizeNewLineChars(v), "\n")
|
||||
out := make([]string, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
var cnt int
|
||||
out = append(out, strings.TrimLeftFunc(line, func(r rune) bool {
|
||||
cnt++
|
||||
return r == ' ' && cnt <= trimIndentNum
|
||||
}))
|
||||
}
|
||||
return strings.Join(out, "\n")
|
||||
}
|
||||
|
||||
func (f *Formatter) addIndentSpace(indentNum int, v string, isIgnoredFirstLine bool) string {
|
||||
if indentNum == 0 {
|
||||
return v
|
||||
}
|
||||
indent := strings.Repeat(" ", indentNum)
|
||||
lines := strings.Split(normalizeNewLineChars(v), "\n")
|
||||
out := make([]string, 0, len(lines))
|
||||
for idx, line := range lines {
|
||||
if line == "" || (isIgnoredFirstLine && idx == 0) {
|
||||
out = append(out, line)
|
||||
continue
|
||||
}
|
||||
out = append(out, indent+line)
|
||||
}
|
||||
return strings.Join(out, "\n")
|
||||
}
|
||||
|
||||
// normalizeNewLineChars normalize CRLF and CR to LF.
|
||||
func normalizeNewLineChars(v string) string {
|
||||
return strings.ReplaceAll(strings.ReplaceAll(v, "\r\n", "\n"), "\r", "\n")
|
||||
}
|
||||
23
vendor/github.com/goccy/go-yaml/lexer/lexer.go
generated
vendored
Normal file
23
vendor/github.com/goccy/go-yaml/lexer/lexer.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
package lexer
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/goccy/go-yaml/scanner"
|
||||
"github.com/goccy/go-yaml/token"
|
||||
)
|
||||
|
||||
// Tokenize split to token instances from string
|
||||
func Tokenize(src string) token.Tokens {
|
||||
var s scanner.Scanner
|
||||
s.Init(src)
|
||||
var tokens token.Tokens
|
||||
for {
|
||||
subTokens, err := s.Scan()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
tokens.Add(subTokens...)
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
352
vendor/github.com/goccy/go-yaml/option.go
generated
vendored
Normal file
352
vendor/github.com/goccy/go-yaml/option.go
generated
vendored
Normal file
@@ -0,0 +1,352 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
"github.com/goccy/go-yaml/ast"
|
||||
)
|
||||
|
||||
// DecodeOption functional option type for Decoder
|
||||
type DecodeOption func(d *Decoder) error
|
||||
|
||||
// ReferenceReaders pass to Decoder that reference to anchor defined by passed readers
|
||||
func ReferenceReaders(readers ...io.Reader) DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.referenceReaders = append(d.referenceReaders, readers...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ReferenceFiles pass to Decoder that reference to anchor defined by passed files
|
||||
func ReferenceFiles(files ...string) DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.referenceFiles = files
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ReferenceDirs pass to Decoder that reference to anchor defined by files under the passed dirs
|
||||
func ReferenceDirs(dirs ...string) DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.referenceDirs = dirs
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// RecursiveDir search yaml file recursively from passed dirs by ReferenceDirs option
|
||||
func RecursiveDir(isRecursive bool) DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.isRecursiveDir = isRecursive
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Validator set StructValidator instance to Decoder
|
||||
func Validator(v StructValidator) DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.validator = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Strict enable DisallowUnknownField
|
||||
func Strict() DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.disallowUnknownField = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DisallowUnknownField causes the Decoder to return an error when the destination
|
||||
// is a struct and the input contains object keys which do not match any
|
||||
// non-ignored, exported fields in the destination.
|
||||
func DisallowUnknownField() DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.disallowUnknownField = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AllowFieldPrefixes, when paired with [DisallowUnknownField], allows fields
|
||||
// with the specified prefixes to bypass the unknown field check.
|
||||
func AllowFieldPrefixes(prefixes ...string) DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.allowedFieldPrefixes = append(d.allowedFieldPrefixes, prefixes...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AllowDuplicateMapKey ignore syntax error when mapping keys that are duplicates.
|
||||
func AllowDuplicateMapKey() DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.allowDuplicateMapKey = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// UseOrderedMap can be interpreted as a map,
|
||||
// and uses MapSlice ( ordered map ) aggressively if there is no type specification
|
||||
func UseOrderedMap() DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.useOrderedMap = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// UseJSONUnmarshaler if neither `BytesUnmarshaler` nor `InterfaceUnmarshaler` is implemented
|
||||
// and `UnmashalJSON([]byte)error` is implemented, convert the argument from `YAML` to `JSON` and then call it.
|
||||
func UseJSONUnmarshaler() DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.useJSONUnmarshaler = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// CustomUnmarshaler overrides any decoding process for the type specified in generics.
|
||||
//
|
||||
// NOTE: If RegisterCustomUnmarshaler and CustomUnmarshaler of DecodeOption are specified for the same type,
|
||||
// the CustomUnmarshaler specified in DecodeOption takes precedence.
|
||||
func CustomUnmarshaler[T any](unmarshaler func(*T, []byte) error) DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
var typ *T
|
||||
d.customUnmarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}, b []byte) error {
|
||||
return unmarshaler(v.(*T), b)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// CustomUnmarshalerContext overrides any decoding process for the type specified in generics.
|
||||
// Similar to CustomUnmarshaler, but allows passing a context to the unmarshaler function.
|
||||
func CustomUnmarshalerContext[T any](unmarshaler func(context.Context, *T, []byte) error) DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
var typ *T
|
||||
d.customUnmarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}, b []byte) error {
|
||||
return unmarshaler(ctx, v.(*T), b)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeOption functional option type for Encoder
|
||||
type EncodeOption func(e *Encoder) error
|
||||
|
||||
// Indent change indent number
|
||||
func Indent(spaces int) EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
e.indentNum = spaces
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// IndentSequence causes sequence values to be indented the same value as Indent
|
||||
func IndentSequence(indent bool) EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
e.indentSequence = indent
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// UseSingleQuote determines if single or double quotes should be preferred for strings.
|
||||
func UseSingleQuote(sq bool) EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
e.singleQuote = sq
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Flow encoding by flow style
|
||||
func Flow(isFlowStyle bool) EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
e.isFlowStyle = isFlowStyle
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSmartAnchor when multiple map values share the same pointer,
|
||||
// an anchor is automatically assigned to the first occurrence, and aliases are used for subsequent elements.
|
||||
// The map key name is used as the anchor name by default.
|
||||
// If key names conflict, a suffix is automatically added to avoid collisions.
|
||||
// This is an experimental feature and cannot be used simultaneously with anchor tags.
|
||||
func WithSmartAnchor() EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
e.enableSmartAnchor = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// UseLiteralStyleIfMultiline causes encoding multiline strings with a literal syntax,
|
||||
// no matter what characters they include
|
||||
func UseLiteralStyleIfMultiline(useLiteralStyleIfMultiline bool) EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
e.useLiteralStyleIfMultiline = useLiteralStyleIfMultiline
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// JSON encode in JSON format
|
||||
func JSON() EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
e.isJSONStyle = true
|
||||
e.isFlowStyle = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalAnchor call back if encoder find an anchor during encoding
|
||||
func MarshalAnchor(callback func(*ast.AnchorNode, interface{}) error) EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
e.anchorCallback = callback
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// UseJSONMarshaler if neither `BytesMarshaler` nor `InterfaceMarshaler`
|
||||
// nor `encoding.TextMarshaler` is implemented and `MarshalJSON()([]byte, error)` is implemented,
|
||||
// call `MarshalJSON` to convert the returned `JSON` to `YAML` for processing.
|
||||
func UseJSONMarshaler() EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
e.useJSONMarshaler = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// CustomMarshaler overrides any encoding process for the type specified in generics.
|
||||
//
|
||||
// NOTE: If type T implements MarshalYAML for pointer receiver, the type specified in CustomMarshaler must be *T.
|
||||
// If RegisterCustomMarshaler and CustomMarshaler of EncodeOption are specified for the same type,
|
||||
// the CustomMarshaler specified in EncodeOption takes precedence.
|
||||
func CustomMarshaler[T any](marshaler func(T) ([]byte, error)) EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
var typ T
|
||||
e.customMarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}) ([]byte, error) {
|
||||
return marshaler(v.(T))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// CustomMarshalerContext overrides any encoding process for the type specified in generics.
|
||||
// Similar to CustomMarshaler, but allows passing a context to the marshaler function.
|
||||
func CustomMarshalerContext[T any](marshaler func(context.Context, T) ([]byte, error)) EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
var typ T
|
||||
e.customMarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}) ([]byte, error) {
|
||||
return marshaler(ctx, v.(T))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AutoInt automatically converts floating-point numbers to integers when the fractional part is zero.
|
||||
// For example, a value of 1.0 will be encoded as 1.
|
||||
func AutoInt() EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
e.autoInt = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OmitEmpty behaves in the same way as the interpretation of the omitempty tag in the encoding/json library.
|
||||
// set on all the fields.
|
||||
// In the current implementation, the omitempty tag is not implemented in the same way as encoding/json,
|
||||
// so please specify this option if you expect the same behavior.
|
||||
func OmitEmpty() EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
e.omitEmpty = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OmitZero forces the encoder to assume an `omitzero` struct tag is
|
||||
// set on all the fields. See `Marshal` commentary for the `omitzero` tag logic.
|
||||
func OmitZero() EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
e.omitZero = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// CommentPosition type of the position for comment.
|
||||
type CommentPosition int
|
||||
|
||||
const (
|
||||
CommentHeadPosition CommentPosition = CommentPosition(iota)
|
||||
CommentLinePosition
|
||||
CommentFootPosition
|
||||
)
|
||||
|
||||
func (p CommentPosition) String() string {
|
||||
switch p {
|
||||
case CommentHeadPosition:
|
||||
return "Head"
|
||||
case CommentLinePosition:
|
||||
return "Line"
|
||||
case CommentFootPosition:
|
||||
return "Foot"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// LineComment create a one-line comment for CommentMap.
|
||||
func LineComment(text string) *Comment {
|
||||
return &Comment{
|
||||
Texts: []string{text},
|
||||
Position: CommentLinePosition,
|
||||
}
|
||||
}
|
||||
|
||||
// HeadComment create a multiline comment for CommentMap.
|
||||
func HeadComment(texts ...string) *Comment {
|
||||
return &Comment{
|
||||
Texts: texts,
|
||||
Position: CommentHeadPosition,
|
||||
}
|
||||
}
|
||||
|
||||
// FootComment create a multiline comment for CommentMap.
|
||||
func FootComment(texts ...string) *Comment {
|
||||
return &Comment{
|
||||
Texts: texts,
|
||||
Position: CommentFootPosition,
|
||||
}
|
||||
}
|
||||
|
||||
// Comment raw data for comment.
|
||||
type Comment struct {
|
||||
Texts []string
|
||||
Position CommentPosition
|
||||
}
|
||||
|
||||
// CommentMap map of the position of the comment and the comment information.
|
||||
type CommentMap map[string][]*Comment
|
||||
|
||||
// WithComment add a comment using the location and text information given in the CommentMap.
|
||||
func WithComment(cm CommentMap) EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
commentMap := map[*Path][]*Comment{}
|
||||
for k, v := range cm {
|
||||
path, err := PathString(k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commentMap[path] = v
|
||||
}
|
||||
e.commentMap = commentMap
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// CommentToMap apply the position and content of comments in a YAML document to a CommentMap.
|
||||
func CommentToMap(cm CommentMap) DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
if cm == nil {
|
||||
return ErrInvalidCommentMapValue
|
||||
}
|
||||
d.toCommentMap = cm
|
||||
return nil
|
||||
}
|
||||
}
|
||||
28
vendor/github.com/goccy/go-yaml/parser/color.go
generated
vendored
Normal file
28
vendor/github.com/goccy/go-yaml/parser/color.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
package parser
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
colorFgHiBlack int = iota + 90
|
||||
colorFgHiRed
|
||||
colorFgHiGreen
|
||||
colorFgHiYellow
|
||||
colorFgHiBlue
|
||||
colorFgHiMagenta
|
||||
colorFgHiCyan
|
||||
)
|
||||
|
||||
var colorTable = []int{
|
||||
colorFgHiRed,
|
||||
colorFgHiGreen,
|
||||
colorFgHiYellow,
|
||||
colorFgHiBlue,
|
||||
colorFgHiMagenta,
|
||||
colorFgHiCyan,
|
||||
}
|
||||
|
||||
func colorize(idx int, content string) string {
|
||||
colorIdx := idx % len(colorTable)
|
||||
color := colorTable[colorIdx]
|
||||
return fmt.Sprintf("\x1b[1;%dm", color) + content + "\x1b[22;0m"
|
||||
}
|
||||
187
vendor/github.com/goccy/go-yaml/parser/context.go
generated
vendored
Normal file
187
vendor/github.com/goccy/go-yaml/parser/context.go
generated
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml/token"
|
||||
)
|
||||
|
||||
// context context at parsing
|
||||
type context struct {
|
||||
tokenRef *tokenRef
|
||||
path string
|
||||
isFlow bool
|
||||
}
|
||||
|
||||
type tokenRef struct {
|
||||
tokens []*Token
|
||||
size int
|
||||
idx int
|
||||
}
|
||||
|
||||
var pathSpecialChars = []string{
|
||||
"$", "*", ".", "[", "]",
|
||||
}
|
||||
|
||||
func containsPathSpecialChar(path string) bool {
|
||||
for _, char := range pathSpecialChars {
|
||||
if strings.Contains(path, char) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func normalizePath(path string) string {
|
||||
if containsPathSpecialChar(path) {
|
||||
return fmt.Sprintf("'%s'", path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func (c *context) currentToken() *Token {
|
||||
if c.tokenRef.idx >= c.tokenRef.size {
|
||||
return nil
|
||||
}
|
||||
return c.tokenRef.tokens[c.tokenRef.idx]
|
||||
}
|
||||
|
||||
func (c *context) isComment() bool {
|
||||
return c.currentToken().Type() == token.CommentType
|
||||
}
|
||||
|
||||
func (c *context) nextToken() *Token {
|
||||
if c.tokenRef.idx+1 >= c.tokenRef.size {
|
||||
return nil
|
||||
}
|
||||
return c.tokenRef.tokens[c.tokenRef.idx+1]
|
||||
}
|
||||
|
||||
func (c *context) nextNotCommentToken() *Token {
|
||||
for i := c.tokenRef.idx + 1; i < c.tokenRef.size; i++ {
|
||||
tk := c.tokenRef.tokens[i]
|
||||
if tk.Type() == token.CommentType {
|
||||
continue
|
||||
}
|
||||
return tk
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) isTokenNotFound() bool {
|
||||
return c.currentToken() == nil
|
||||
}
|
||||
|
||||
func (c *context) withGroup(g *TokenGroup) *context {
|
||||
ctx := *c
|
||||
ctx.tokenRef = &tokenRef{
|
||||
tokens: g.Tokens,
|
||||
size: len(g.Tokens),
|
||||
}
|
||||
return &ctx
|
||||
}
|
||||
|
||||
func (c *context) withChild(path string) *context {
|
||||
ctx := *c
|
||||
ctx.path = c.path + "." + normalizePath(path)
|
||||
return &ctx
|
||||
}
|
||||
|
||||
func (c *context) withIndex(idx uint) *context {
|
||||
ctx := *c
|
||||
ctx.path = c.path + "[" + fmt.Sprint(idx) + "]"
|
||||
return &ctx
|
||||
}
|
||||
|
||||
func (c *context) withFlow(isFlow bool) *context {
|
||||
ctx := *c
|
||||
ctx.isFlow = isFlow
|
||||
return &ctx
|
||||
}
|
||||
|
||||
func newContext() *context {
|
||||
return &context{
|
||||
path: "$",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) goNext() {
|
||||
ref := c.tokenRef
|
||||
if ref.size <= ref.idx+1 {
|
||||
ref.idx = ref.size
|
||||
} else {
|
||||
ref.idx++
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) next() bool {
|
||||
return c.tokenRef.idx < c.tokenRef.size
|
||||
}
|
||||
|
||||
func (c *context) insertNullToken(tk *Token) *Token {
|
||||
nullToken := c.createImplicitNullToken(tk)
|
||||
c.insertToken(nullToken)
|
||||
c.goNext()
|
||||
|
||||
return nullToken
|
||||
}
|
||||
|
||||
func (c *context) addNullValueToken(tk *Token) *Token {
|
||||
nullToken := c.createImplicitNullToken(tk)
|
||||
rawTk := nullToken.RawToken()
|
||||
|
||||
// add space for map or sequence value.
|
||||
rawTk.Position.Column++
|
||||
|
||||
c.addToken(nullToken)
|
||||
c.goNext()
|
||||
|
||||
return nullToken
|
||||
}
|
||||
|
||||
func (c *context) createImplicitNullToken(base *Token) *Token {
|
||||
pos := *(base.RawToken().Position)
|
||||
pos.Column++
|
||||
tk := token.New("null", " null", &pos)
|
||||
tk.Type = token.ImplicitNullType
|
||||
return &Token{Token: tk}
|
||||
}
|
||||
|
||||
func (c *context) insertToken(tk *Token) {
|
||||
ref := c.tokenRef
|
||||
idx := ref.idx
|
||||
if ref.size < idx {
|
||||
return
|
||||
}
|
||||
if ref.size == idx {
|
||||
curToken := ref.tokens[ref.size-1]
|
||||
tk.RawToken().Next = curToken.RawToken()
|
||||
curToken.RawToken().Prev = tk.RawToken()
|
||||
|
||||
ref.tokens = append(ref.tokens, tk)
|
||||
ref.size = len(ref.tokens)
|
||||
return
|
||||
}
|
||||
|
||||
curToken := ref.tokens[idx]
|
||||
tk.RawToken().Next = curToken.RawToken()
|
||||
curToken.RawToken().Prev = tk.RawToken()
|
||||
|
||||
ref.tokens = append(ref.tokens[:idx+1], ref.tokens[idx:]...)
|
||||
ref.tokens[idx] = tk
|
||||
ref.size = len(ref.tokens)
|
||||
}
|
||||
|
||||
func (c *context) addToken(tk *Token) {
|
||||
ref := c.tokenRef
|
||||
lastTk := ref.tokens[ref.size-1]
|
||||
if lastTk.Group != nil {
|
||||
lastTk = lastTk.Group.Last()
|
||||
}
|
||||
lastTk.RawToken().Next = tk.RawToken()
|
||||
tk.RawToken().Prev = lastTk.RawToken()
|
||||
|
||||
ref.tokens = append(ref.tokens, tk)
|
||||
ref.size = len(ref.tokens)
|
||||
}
|
||||
257
vendor/github.com/goccy/go-yaml/parser/node.go
generated
vendored
Normal file
257
vendor/github.com/goccy/go-yaml/parser/node.go
generated
vendored
Normal file
@@ -0,0 +1,257 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/goccy/go-yaml/ast"
|
||||
"github.com/goccy/go-yaml/internal/errors"
|
||||
"github.com/goccy/go-yaml/token"
|
||||
)
|
||||
|
||||
func newMappingNode(ctx *context, tk *Token, isFlow bool, values ...*ast.MappingValueNode) (*ast.MappingNode, error) {
|
||||
node := ast.Mapping(tk.RawToken(), isFlow, values...)
|
||||
node.SetPath(ctx.path)
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func newMappingValueNode(ctx *context, colonTk, entryTk *Token, key ast.MapKeyNode, value ast.Node) (*ast.MappingValueNode, error) {
|
||||
node := ast.MappingValue(colonTk.RawToken(), key, value)
|
||||
node.SetPath(ctx.path)
|
||||
node.CollectEntry = entryTk.RawToken()
|
||||
if key.GetToken().Position.Line == value.GetToken().Position.Line {
|
||||
// originally key was commented, but now that null value has been added, value must be commented.
|
||||
if err := setLineComment(ctx, value, colonTk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// set line comment by colonTk or entryTk.
|
||||
if err := setLineComment(ctx, value, entryTk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := setLineComment(ctx, key, colonTk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// set line comment by colonTk or entryTk.
|
||||
if err := setLineComment(ctx, key, entryTk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func newMappingKeyNode(ctx *context, tk *Token) (*ast.MappingKeyNode, error) {
|
||||
node := ast.MappingKey(tk.RawToken())
|
||||
node.SetPath(ctx.path)
|
||||
if err := setLineComment(ctx, node, tk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func newAnchorNode(ctx *context, tk *Token) (*ast.AnchorNode, error) {
|
||||
node := ast.Anchor(tk.RawToken())
|
||||
node.SetPath(ctx.path)
|
||||
if err := setLineComment(ctx, node, tk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func newAliasNode(ctx *context, tk *Token) (*ast.AliasNode, error) {
|
||||
node := ast.Alias(tk.RawToken())
|
||||
node.SetPath(ctx.path)
|
||||
if err := setLineComment(ctx, node, tk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func newDirectiveNode(ctx *context, tk *Token) (*ast.DirectiveNode, error) {
|
||||
node := ast.Directive(tk.RawToken())
|
||||
node.SetPath(ctx.path)
|
||||
if err := setLineComment(ctx, node, tk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func newMergeKeyNode(ctx *context, tk *Token) (*ast.MergeKeyNode, error) {
|
||||
node := ast.MergeKey(tk.RawToken())
|
||||
node.SetPath(ctx.path)
|
||||
if err := setLineComment(ctx, node, tk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func newNullNode(ctx *context, tk *Token) (*ast.NullNode, error) {
|
||||
node := ast.Null(tk.RawToken())
|
||||
node.SetPath(ctx.path)
|
||||
if err := setLineComment(ctx, node, tk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func newBoolNode(ctx *context, tk *Token) (*ast.BoolNode, error) {
|
||||
node := ast.Bool(tk.RawToken())
|
||||
node.SetPath(ctx.path)
|
||||
if err := setLineComment(ctx, node, tk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func newIntegerNode(ctx *context, tk *Token) (*ast.IntegerNode, error) {
|
||||
node := ast.Integer(tk.RawToken())
|
||||
node.SetPath(ctx.path)
|
||||
if err := setLineComment(ctx, node, tk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func newFloatNode(ctx *context, tk *Token) (*ast.FloatNode, error) {
|
||||
node := ast.Float(tk.RawToken())
|
||||
node.SetPath(ctx.path)
|
||||
if err := setLineComment(ctx, node, tk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func newInfinityNode(ctx *context, tk *Token) (*ast.InfinityNode, error) {
|
||||
node := ast.Infinity(tk.RawToken())
|
||||
node.SetPath(ctx.path)
|
||||
if err := setLineComment(ctx, node, tk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func newNanNode(ctx *context, tk *Token) (*ast.NanNode, error) {
|
||||
node := ast.Nan(tk.RawToken())
|
||||
node.SetPath(ctx.path)
|
||||
if err := setLineComment(ctx, node, tk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func newStringNode(ctx *context, tk *Token) (*ast.StringNode, error) {
|
||||
node := ast.String(tk.RawToken())
|
||||
node.SetPath(ctx.path)
|
||||
if err := setLineComment(ctx, node, tk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func newLiteralNode(ctx *context, tk *Token) (*ast.LiteralNode, error) {
|
||||
node := ast.Literal(tk.RawToken())
|
||||
node.SetPath(ctx.path)
|
||||
if err := setLineComment(ctx, node, tk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func newTagNode(ctx *context, tk *Token) (*ast.TagNode, error) {
|
||||
node := ast.Tag(tk.RawToken())
|
||||
node.SetPath(ctx.path)
|
||||
if err := setLineComment(ctx, node, tk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func newSequenceNode(ctx *context, tk *Token, isFlow bool) (*ast.SequenceNode, error) {
|
||||
node := ast.Sequence(tk.RawToken(), isFlow)
|
||||
node.SetPath(ctx.path)
|
||||
if err := setLineComment(ctx, node, tk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func newTagDefaultScalarValueNode(ctx *context, tag *token.Token) (ast.ScalarNode, error) {
|
||||
pos := *(tag.Position)
|
||||
pos.Column++
|
||||
|
||||
var (
|
||||
tk *Token
|
||||
node ast.ScalarNode
|
||||
)
|
||||
switch token.ReservedTagKeyword(tag.Value) {
|
||||
case token.IntegerTag:
|
||||
tk = &Token{Token: token.New("0", "0", &pos)}
|
||||
n, err := newIntegerNode(ctx, tk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node = n
|
||||
case token.FloatTag:
|
||||
tk = &Token{Token: token.New("0", "0", &pos)}
|
||||
n, err := newFloatNode(ctx, tk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node = n
|
||||
case token.StringTag, token.BinaryTag, token.TimestampTag:
|
||||
tk = &Token{Token: token.New("", "", &pos)}
|
||||
n, err := newStringNode(ctx, tk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node = n
|
||||
case token.BooleanTag:
|
||||
tk = &Token{Token: token.New("false", "false", &pos)}
|
||||
n, err := newBoolNode(ctx, tk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node = n
|
||||
case token.NullTag:
|
||||
tk = &Token{Token: token.New("null", "null", &pos)}
|
||||
n, err := newNullNode(ctx, tk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node = n
|
||||
default:
|
||||
return nil, errors.ErrSyntax(fmt.Sprintf("cannot assign default value for %q tag", tag.Value), tag)
|
||||
}
|
||||
ctx.insertToken(tk)
|
||||
ctx.goNext()
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func setLineComment(ctx *context, node ast.Node, tk *Token) error {
|
||||
if tk == nil || tk.LineComment == nil {
|
||||
return nil
|
||||
}
|
||||
comment := ast.CommentGroup([]*token.Token{tk.LineComment})
|
||||
comment.SetPath(ctx.path)
|
||||
if err := node.SetComment(comment); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setHeadComment(cm *ast.CommentGroupNode, value ast.Node) error {
|
||||
if cm == nil {
|
||||
return nil
|
||||
}
|
||||
switch n := value.(type) {
|
||||
case *ast.MappingNode:
|
||||
if len(n.Values) != 0 && value.GetComment() == nil {
|
||||
cm.SetPath(n.Values[0].GetPath())
|
||||
return n.Values[0].SetComment(cm)
|
||||
}
|
||||
case *ast.MappingValueNode:
|
||||
cm.SetPath(n.GetPath())
|
||||
return n.SetComment(cm)
|
||||
}
|
||||
cm.SetPath(value.GetPath())
|
||||
return value.SetComment(cm)
|
||||
}
|
||||
12
vendor/github.com/goccy/go-yaml/parser/option.go
generated
vendored
Normal file
12
vendor/github.com/goccy/go-yaml/parser/option.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
package parser
|
||||
|
||||
// Option represents parser's option.
|
||||
type Option func(p *parser)
|
||||
|
||||
// AllowDuplicateMapKey allow the use of keys with the same name in the same map,
|
||||
// but by default, this is not permitted.
|
||||
func AllowDuplicateMapKey() Option {
|
||||
return func(p *parser) {
|
||||
p.allowDuplicateMapKey = true
|
||||
}
|
||||
}
|
||||
1330
vendor/github.com/goccy/go-yaml/parser/parser.go
generated
vendored
Normal file
1330
vendor/github.com/goccy/go-yaml/parser/parser.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
746
vendor/github.com/goccy/go-yaml/parser/token.go
generated
vendored
Normal file
746
vendor/github.com/goccy/go-yaml/parser/token.go
generated
vendored
Normal file
@@ -0,0 +1,746 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml/internal/errors"
|
||||
"github.com/goccy/go-yaml/token"
|
||||
)
|
||||
|
||||
type TokenGroupType int
|
||||
|
||||
const (
|
||||
TokenGroupNone TokenGroupType = iota
|
||||
TokenGroupDirective
|
||||
TokenGroupDirectiveName
|
||||
TokenGroupDocument
|
||||
TokenGroupDocumentBody
|
||||
TokenGroupAnchor
|
||||
TokenGroupAnchorName
|
||||
TokenGroupAlias
|
||||
TokenGroupLiteral
|
||||
TokenGroupFolded
|
||||
TokenGroupScalarTag
|
||||
TokenGroupMapKey
|
||||
TokenGroupMapKeyValue
|
||||
)
|
||||
|
||||
func (t TokenGroupType) String() string {
|
||||
switch t {
|
||||
case TokenGroupNone:
|
||||
return "none"
|
||||
case TokenGroupDirective:
|
||||
return "directive"
|
||||
case TokenGroupDirectiveName:
|
||||
return "directive_name"
|
||||
case TokenGroupDocument:
|
||||
return "document"
|
||||
case TokenGroupDocumentBody:
|
||||
return "document_body"
|
||||
case TokenGroupAnchor:
|
||||
return "anchor"
|
||||
case TokenGroupAnchorName:
|
||||
return "anchor_name"
|
||||
case TokenGroupAlias:
|
||||
return "alias"
|
||||
case TokenGroupLiteral:
|
||||
return "literal"
|
||||
case TokenGroupFolded:
|
||||
return "folded"
|
||||
case TokenGroupScalarTag:
|
||||
return "scalar_tag"
|
||||
case TokenGroupMapKey:
|
||||
return "map_key"
|
||||
case TokenGroupMapKeyValue:
|
||||
return "map_key_value"
|
||||
}
|
||||
return "none"
|
||||
}
|
||||
|
||||
type Token struct {
|
||||
Token *token.Token
|
||||
Group *TokenGroup
|
||||
LineComment *token.Token
|
||||
}
|
||||
|
||||
func (t *Token) RawToken() *token.Token {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
if t.Token != nil {
|
||||
return t.Token
|
||||
}
|
||||
return t.Group.RawToken()
|
||||
}
|
||||
|
||||
func (t *Token) Type() token.Type {
|
||||
if t == nil {
|
||||
return 0
|
||||
}
|
||||
if t.Token != nil {
|
||||
return t.Token.Type
|
||||
}
|
||||
return t.Group.TokenType()
|
||||
}
|
||||
|
||||
func (t *Token) GroupType() TokenGroupType {
|
||||
if t == nil {
|
||||
return TokenGroupNone
|
||||
}
|
||||
if t.Token != nil {
|
||||
return TokenGroupNone
|
||||
}
|
||||
return t.Group.Type
|
||||
}
|
||||
|
||||
func (t *Token) Line() int {
|
||||
if t == nil {
|
||||
return 0
|
||||
}
|
||||
if t.Token != nil {
|
||||
return t.Token.Position.Line
|
||||
}
|
||||
return t.Group.Line()
|
||||
}
|
||||
|
||||
func (t *Token) Column() int {
|
||||
if t == nil {
|
||||
return 0
|
||||
}
|
||||
if t.Token != nil {
|
||||
return t.Token.Position.Column
|
||||
}
|
||||
return t.Group.Column()
|
||||
}
|
||||
|
||||
func (t *Token) SetGroupType(typ TokenGroupType) {
|
||||
if t.Group == nil {
|
||||
return
|
||||
}
|
||||
t.Group.Type = typ
|
||||
}
|
||||
|
||||
func (t *Token) Dump() {
|
||||
ctx := new(groupTokenRenderContext)
|
||||
if t.Token != nil {
|
||||
fmt.Fprint(os.Stdout, t.Token.Value)
|
||||
return
|
||||
}
|
||||
t.Group.dump(ctx)
|
||||
fmt.Fprintf(os.Stdout, "\n")
|
||||
}
|
||||
|
||||
func (t *Token) dump(ctx *groupTokenRenderContext) {
|
||||
if t.Token != nil {
|
||||
fmt.Fprint(os.Stdout, t.Token.Value)
|
||||
return
|
||||
}
|
||||
t.Group.dump(ctx)
|
||||
}
|
||||
|
||||
type groupTokenRenderContext struct {
|
||||
num int
|
||||
}
|
||||
|
||||
type TokenGroup struct {
|
||||
Type TokenGroupType
|
||||
Tokens []*Token
|
||||
}
|
||||
|
||||
func (g *TokenGroup) First() *Token {
|
||||
if len(g.Tokens) == 0 {
|
||||
return nil
|
||||
}
|
||||
return g.Tokens[0]
|
||||
}
|
||||
|
||||
func (g *TokenGroup) Last() *Token {
|
||||
if len(g.Tokens) == 0 {
|
||||
return nil
|
||||
}
|
||||
return g.Tokens[len(g.Tokens)-1]
|
||||
}
|
||||
|
||||
func (g *TokenGroup) dump(ctx *groupTokenRenderContext) {
|
||||
num := ctx.num
|
||||
fmt.Fprint(os.Stdout, colorize(num, "("))
|
||||
ctx.num++
|
||||
for _, tk := range g.Tokens {
|
||||
tk.dump(ctx)
|
||||
}
|
||||
fmt.Fprint(os.Stdout, colorize(num, ")"))
|
||||
}
|
||||
|
||||
func (g *TokenGroup) RawToken() *token.Token {
|
||||
if len(g.Tokens) == 0 {
|
||||
return nil
|
||||
}
|
||||
return g.Tokens[0].RawToken()
|
||||
}
|
||||
|
||||
func (g *TokenGroup) Line() int {
|
||||
if len(g.Tokens) == 0 {
|
||||
return 0
|
||||
}
|
||||
return g.Tokens[0].Line()
|
||||
}
|
||||
|
||||
func (g *TokenGroup) Column() int {
|
||||
if len(g.Tokens) == 0 {
|
||||
return 0
|
||||
}
|
||||
return g.Tokens[0].Column()
|
||||
}
|
||||
|
||||
func (g *TokenGroup) TokenType() token.Type {
|
||||
if len(g.Tokens) == 0 {
|
||||
return 0
|
||||
}
|
||||
return g.Tokens[0].Type()
|
||||
}
|
||||
|
||||
func CreateGroupedTokens(tokens token.Tokens) ([]*Token, error) {
|
||||
var err error
|
||||
tks := newTokens(tokens)
|
||||
tks = createLineCommentTokenGroups(tks)
|
||||
tks, err = createLiteralAndFoldedTokenGroups(tks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tks, err = createAnchorAndAliasTokenGroups(tks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tks, err = createScalarTagTokenGroups(tks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tks, err = createAnchorWithScalarTagTokenGroups(tks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tks, err = createMapKeyTokenGroups(tks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tks = createMapKeyValueTokenGroups(tks)
|
||||
tks, err = createDirectiveTokenGroups(tks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tks, err = createDocumentTokens(tks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tks, nil
|
||||
}
|
||||
|
||||
func newTokens(tks token.Tokens) []*Token {
|
||||
ret := make([]*Token, 0, len(tks))
|
||||
for _, tk := range tks {
|
||||
ret = append(ret, &Token{Token: tk})
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func createLineCommentTokenGroups(tokens []*Token) []*Token {
|
||||
ret := make([]*Token, 0, len(tokens))
|
||||
for i := 0; i < len(tokens); i++ {
|
||||
tk := tokens[i]
|
||||
switch tk.Type() {
|
||||
case token.CommentType:
|
||||
if i > 0 && tokens[i-1].Line() == tk.Line() {
|
||||
tokens[i-1].LineComment = tk.RawToken()
|
||||
} else {
|
||||
ret = append(ret, tk)
|
||||
}
|
||||
default:
|
||||
ret = append(ret, tk)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func createLiteralAndFoldedTokenGroups(tokens []*Token) ([]*Token, error) {
|
||||
ret := make([]*Token, 0, len(tokens))
|
||||
for i := 0; i < len(tokens); i++ {
|
||||
tk := tokens[i]
|
||||
switch tk.Type() {
|
||||
case token.LiteralType:
|
||||
tks := []*Token{tk}
|
||||
if i+1 < len(tokens) {
|
||||
tks = append(tks, tokens[i+1])
|
||||
}
|
||||
ret = append(ret, &Token{
|
||||
Group: &TokenGroup{
|
||||
Type: TokenGroupLiteral,
|
||||
Tokens: tks,
|
||||
},
|
||||
})
|
||||
i++
|
||||
case token.FoldedType:
|
||||
tks := []*Token{tk}
|
||||
if i+1 < len(tokens) {
|
||||
tks = append(tks, tokens[i+1])
|
||||
}
|
||||
ret = append(ret, &Token{
|
||||
Group: &TokenGroup{
|
||||
Type: TokenGroupFolded,
|
||||
Tokens: tks,
|
||||
},
|
||||
})
|
||||
i++
|
||||
default:
|
||||
ret = append(ret, tk)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func createAnchorAndAliasTokenGroups(tokens []*Token) ([]*Token, error) {
|
||||
ret := make([]*Token, 0, len(tokens))
|
||||
for i := 0; i < len(tokens); i++ {
|
||||
tk := tokens[i]
|
||||
switch tk.Type() {
|
||||
case token.AnchorType:
|
||||
if i+1 >= len(tokens) {
|
||||
return nil, errors.ErrSyntax("undefined anchor name", tk.RawToken())
|
||||
}
|
||||
if i+2 >= len(tokens) {
|
||||
return nil, errors.ErrSyntax("undefined anchor value", tk.RawToken())
|
||||
}
|
||||
anchorName := &Token{
|
||||
Group: &TokenGroup{
|
||||
Type: TokenGroupAnchorName,
|
||||
Tokens: []*Token{tk, tokens[i+1]},
|
||||
},
|
||||
}
|
||||
valueTk := tokens[i+2]
|
||||
if tk.Line() == valueTk.Line() && valueTk.Type() == token.SequenceEntryType {
|
||||
return nil, errors.ErrSyntax("sequence entries are not allowed after anchor on the same line", valueTk.RawToken())
|
||||
}
|
||||
if tk.Line() == valueTk.Line() && isScalarType(valueTk) {
|
||||
ret = append(ret, &Token{
|
||||
Group: &TokenGroup{
|
||||
Type: TokenGroupAnchor,
|
||||
Tokens: []*Token{anchorName, valueTk},
|
||||
},
|
||||
})
|
||||
i++
|
||||
} else {
|
||||
ret = append(ret, anchorName)
|
||||
}
|
||||
i++
|
||||
case token.AliasType:
|
||||
if i+1 == len(tokens) {
|
||||
return nil, errors.ErrSyntax("undefined alias name", tk.RawToken())
|
||||
}
|
||||
ret = append(ret, &Token{
|
||||
Group: &TokenGroup{
|
||||
Type: TokenGroupAlias,
|
||||
Tokens: []*Token{tk, tokens[i+1]},
|
||||
},
|
||||
})
|
||||
i++
|
||||
default:
|
||||
ret = append(ret, tk)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func createScalarTagTokenGroups(tokens []*Token) ([]*Token, error) {
|
||||
ret := make([]*Token, 0, len(tokens))
|
||||
for i := 0; i < len(tokens); i++ {
|
||||
tk := tokens[i]
|
||||
if tk.Type() != token.TagType {
|
||||
ret = append(ret, tk)
|
||||
continue
|
||||
}
|
||||
tag := tk.RawToken()
|
||||
if strings.HasPrefix(tag.Value, "!!") {
|
||||
// secondary tag.
|
||||
switch token.ReservedTagKeyword(tag.Value) {
|
||||
case token.IntegerTag, token.FloatTag, token.StringTag, token.BinaryTag, token.TimestampTag, token.BooleanTag, token.NullTag:
|
||||
if len(tokens) <= i+1 {
|
||||
ret = append(ret, tk)
|
||||
continue
|
||||
}
|
||||
if tk.Line() != tokens[i+1].Line() {
|
||||
ret = append(ret, tk)
|
||||
continue
|
||||
}
|
||||
if tokens[i+1].GroupType() == TokenGroupAnchorName {
|
||||
ret = append(ret, tk)
|
||||
continue
|
||||
}
|
||||
if isScalarType(tokens[i+1]) {
|
||||
ret = append(ret, &Token{
|
||||
Group: &TokenGroup{
|
||||
Type: TokenGroupScalarTag,
|
||||
Tokens: []*Token{tk, tokens[i+1]},
|
||||
},
|
||||
})
|
||||
i++
|
||||
} else {
|
||||
ret = append(ret, tk)
|
||||
}
|
||||
case token.MergeTag:
|
||||
if len(tokens) <= i+1 {
|
||||
ret = append(ret, tk)
|
||||
continue
|
||||
}
|
||||
if tk.Line() != tokens[i+1].Line() {
|
||||
ret = append(ret, tk)
|
||||
continue
|
||||
}
|
||||
if tokens[i+1].GroupType() == TokenGroupAnchorName {
|
||||
ret = append(ret, tk)
|
||||
continue
|
||||
}
|
||||
if tokens[i+1].Type() != token.MergeKeyType {
|
||||
return nil, errors.ErrSyntax("could not find merge key", tokens[i+1].RawToken())
|
||||
}
|
||||
ret = append(ret, &Token{
|
||||
Group: &TokenGroup{
|
||||
Type: TokenGroupScalarTag,
|
||||
Tokens: []*Token{tk, tokens[i+1]},
|
||||
},
|
||||
})
|
||||
i++
|
||||
default:
|
||||
ret = append(ret, tk)
|
||||
}
|
||||
} else {
|
||||
if len(tokens) <= i+1 {
|
||||
ret = append(ret, tk)
|
||||
continue
|
||||
}
|
||||
if tk.Line() != tokens[i+1].Line() {
|
||||
ret = append(ret, tk)
|
||||
continue
|
||||
}
|
||||
if tokens[i+1].GroupType() == TokenGroupAnchorName {
|
||||
ret = append(ret, tk)
|
||||
continue
|
||||
}
|
||||
if isFlowType(tokens[i+1]) {
|
||||
ret = append(ret, tk)
|
||||
continue
|
||||
}
|
||||
ret = append(ret, &Token{
|
||||
Group: &TokenGroup{
|
||||
Type: TokenGroupScalarTag,
|
||||
Tokens: []*Token{tk, tokens[i+1]},
|
||||
},
|
||||
})
|
||||
i++
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func createAnchorWithScalarTagTokenGroups(tokens []*Token) ([]*Token, error) {
|
||||
ret := make([]*Token, 0, len(tokens))
|
||||
for i := 0; i < len(tokens); i++ {
|
||||
tk := tokens[i]
|
||||
switch tk.GroupType() {
|
||||
case TokenGroupAnchorName:
|
||||
if i+1 >= len(tokens) {
|
||||
return nil, errors.ErrSyntax("undefined anchor value", tk.RawToken())
|
||||
}
|
||||
valueTk := tokens[i+1]
|
||||
if tk.Line() == valueTk.Line() && valueTk.GroupType() == TokenGroupScalarTag {
|
||||
ret = append(ret, &Token{
|
||||
Group: &TokenGroup{
|
||||
Type: TokenGroupAnchor,
|
||||
Tokens: []*Token{tk, tokens[i+1]},
|
||||
},
|
||||
})
|
||||
i++
|
||||
} else {
|
||||
ret = append(ret, tk)
|
||||
}
|
||||
default:
|
||||
ret = append(ret, tk)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func createMapKeyTokenGroups(tokens []*Token) ([]*Token, error) {
|
||||
tks, err := createMapKeyByMappingKey(tokens)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return createMapKeyByMappingValue(tks)
|
||||
}
|
||||
|
||||
func createMapKeyByMappingKey(tokens []*Token) ([]*Token, error) {
|
||||
ret := make([]*Token, 0, len(tokens))
|
||||
for i := 0; i < len(tokens); i++ {
|
||||
tk := tokens[i]
|
||||
switch tk.Type() {
|
||||
case token.MappingKeyType:
|
||||
if i+1 >= len(tokens) {
|
||||
return nil, errors.ErrSyntax("undefined map key", tk.RawToken())
|
||||
}
|
||||
ret = append(ret, &Token{
|
||||
Group: &TokenGroup{
|
||||
Type: TokenGroupMapKey,
|
||||
Tokens: []*Token{tk, tokens[i+1]},
|
||||
},
|
||||
})
|
||||
i++
|
||||
default:
|
||||
ret = append(ret, tk)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func createMapKeyByMappingValue(tokens []*Token) ([]*Token, error) {
|
||||
ret := make([]*Token, 0, len(tokens))
|
||||
for i := 0; i < len(tokens); i++ {
|
||||
tk := tokens[i]
|
||||
switch tk.Type() {
|
||||
case token.MappingValueType:
|
||||
if i == 0 {
|
||||
return nil, errors.ErrSyntax("unexpected key name", tk.RawToken())
|
||||
}
|
||||
mapKeyTk := tokens[i-1]
|
||||
if isNotMapKeyType(mapKeyTk) {
|
||||
return nil, errors.ErrSyntax("found an invalid key for this map", tokens[i].RawToken())
|
||||
}
|
||||
newTk := &Token{Token: mapKeyTk.Token, Group: mapKeyTk.Group}
|
||||
mapKeyTk.Token = nil
|
||||
mapKeyTk.Group = &TokenGroup{
|
||||
Type: TokenGroupMapKey,
|
||||
Tokens: []*Token{newTk, tk},
|
||||
}
|
||||
default:
|
||||
ret = append(ret, tk)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func createMapKeyValueTokenGroups(tokens []*Token) []*Token {
|
||||
ret := make([]*Token, 0, len(tokens))
|
||||
for i := 0; i < len(tokens); i++ {
|
||||
tk := tokens[i]
|
||||
switch tk.GroupType() {
|
||||
case TokenGroupMapKey:
|
||||
if len(tokens) <= i+1 {
|
||||
ret = append(ret, tk)
|
||||
continue
|
||||
}
|
||||
valueTk := tokens[i+1]
|
||||
if tk.Line() != valueTk.Line() {
|
||||
ret = append(ret, tk)
|
||||
continue
|
||||
}
|
||||
if valueTk.GroupType() == TokenGroupAnchorName {
|
||||
ret = append(ret, tk)
|
||||
continue
|
||||
}
|
||||
if valueTk.Type() == token.TagType && valueTk.GroupType() != TokenGroupScalarTag {
|
||||
ret = append(ret, tk)
|
||||
continue
|
||||
}
|
||||
|
||||
if isScalarType(valueTk) || valueTk.Type() == token.TagType {
|
||||
ret = append(ret, &Token{
|
||||
Group: &TokenGroup{
|
||||
Type: TokenGroupMapKeyValue,
|
||||
Tokens: []*Token{tk, valueTk},
|
||||
},
|
||||
})
|
||||
i++
|
||||
} else {
|
||||
ret = append(ret, tk)
|
||||
continue
|
||||
}
|
||||
default:
|
||||
ret = append(ret, tk)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func createDirectiveTokenGroups(tokens []*Token) ([]*Token, error) {
|
||||
ret := make([]*Token, 0, len(tokens))
|
||||
for i := 0; i < len(tokens); i++ {
|
||||
tk := tokens[i]
|
||||
switch tk.Type() {
|
||||
case token.DirectiveType:
|
||||
if i+1 >= len(tokens) {
|
||||
return nil, errors.ErrSyntax("undefined directive value", tk.RawToken())
|
||||
}
|
||||
directiveName := &Token{
|
||||
Group: &TokenGroup{
|
||||
Type: TokenGroupDirectiveName,
|
||||
Tokens: []*Token{tk, tokens[i+1]},
|
||||
},
|
||||
}
|
||||
i++
|
||||
var valueTks []*Token
|
||||
for j := i + 1; j < len(tokens); j++ {
|
||||
if tokens[j].Line() != tk.Line() {
|
||||
break
|
||||
}
|
||||
valueTks = append(valueTks, tokens[j])
|
||||
i++
|
||||
}
|
||||
if i+1 >= len(tokens) || tokens[i+1].Type() != token.DocumentHeaderType {
|
||||
return nil, errors.ErrSyntax("unexpected directive value. document not started", tk.RawToken())
|
||||
}
|
||||
if len(valueTks) != 0 {
|
||||
ret = append(ret, &Token{
|
||||
Group: &TokenGroup{
|
||||
Type: TokenGroupDirective,
|
||||
Tokens: append([]*Token{directiveName}, valueTks...),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
ret = append(ret, directiveName)
|
||||
}
|
||||
default:
|
||||
ret = append(ret, tk)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func createDocumentTokens(tokens []*Token) ([]*Token, error) {
|
||||
var ret []*Token
|
||||
for i := 0; i < len(tokens); i++ {
|
||||
tk := tokens[i]
|
||||
switch tk.Type() {
|
||||
case token.DocumentHeaderType:
|
||||
if i != 0 {
|
||||
ret = append(ret, &Token{
|
||||
Group: &TokenGroup{Tokens: tokens[:i]},
|
||||
})
|
||||
}
|
||||
if i+1 == len(tokens) {
|
||||
// if current token is last token, add DocumentHeader only tokens to ret.
|
||||
return append(ret, &Token{
|
||||
Group: &TokenGroup{
|
||||
Type: TokenGroupDocument,
|
||||
Tokens: []*Token{tk},
|
||||
},
|
||||
}), nil
|
||||
}
|
||||
if tokens[i+1].Type() == token.DocumentHeaderType {
|
||||
return append(ret, &Token{
|
||||
Group: &TokenGroup{
|
||||
Type: TokenGroupDocument,
|
||||
Tokens: []*Token{tk},
|
||||
},
|
||||
}), nil
|
||||
}
|
||||
if tokens[i].Line() == tokens[i+1].Line() {
|
||||
switch tokens[i+1].GroupType() {
|
||||
case TokenGroupMapKey, TokenGroupMapKeyValue:
|
||||
return nil, errors.ErrSyntax("value cannot be placed after document separator", tokens[i+1].RawToken())
|
||||
}
|
||||
switch tokens[i+1].Type() {
|
||||
case token.SequenceEntryType:
|
||||
return nil, errors.ErrSyntax("value cannot be placed after document separator", tokens[i+1].RawToken())
|
||||
}
|
||||
}
|
||||
tks, err := createDocumentTokens(tokens[i+1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(tks) != 0 {
|
||||
tks[0].SetGroupType(TokenGroupDocument)
|
||||
tks[0].Group.Tokens = append([]*Token{tk}, tks[0].Group.Tokens...)
|
||||
return append(ret, tks...), nil
|
||||
}
|
||||
return append(ret, &Token{
|
||||
Group: &TokenGroup{
|
||||
Type: TokenGroupDocument,
|
||||
Tokens: []*Token{tk},
|
||||
},
|
||||
}), nil
|
||||
case token.DocumentEndType:
|
||||
if i != 0 {
|
||||
ret = append(ret, &Token{
|
||||
Group: &TokenGroup{
|
||||
Type: TokenGroupDocument,
|
||||
Tokens: tokens[0 : i+1],
|
||||
},
|
||||
})
|
||||
}
|
||||
if i+1 == len(tokens) {
|
||||
return ret, nil
|
||||
}
|
||||
if isScalarType(tokens[i+1]) {
|
||||
return nil, errors.ErrSyntax("unexpected end content", tokens[i+1].RawToken())
|
||||
}
|
||||
|
||||
tks, err := createDocumentTokens(tokens[i+1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(ret, tks...), nil
|
||||
}
|
||||
}
|
||||
return append(ret, &Token{
|
||||
Group: &TokenGroup{
|
||||
Type: TokenGroupDocument,
|
||||
Tokens: tokens,
|
||||
},
|
||||
}), nil
|
||||
}
|
||||
|
||||
func isScalarType(tk *Token) bool {
|
||||
switch tk.GroupType() {
|
||||
case TokenGroupMapKey, TokenGroupMapKeyValue:
|
||||
return false
|
||||
}
|
||||
typ := tk.Type()
|
||||
return typ == token.AnchorType ||
|
||||
typ == token.AliasType ||
|
||||
typ == token.LiteralType ||
|
||||
typ == token.FoldedType ||
|
||||
typ == token.NullType ||
|
||||
typ == token.ImplicitNullType ||
|
||||
typ == token.BoolType ||
|
||||
typ == token.IntegerType ||
|
||||
typ == token.BinaryIntegerType ||
|
||||
typ == token.OctetIntegerType ||
|
||||
typ == token.HexIntegerType ||
|
||||
typ == token.FloatType ||
|
||||
typ == token.InfinityType ||
|
||||
typ == token.NanType ||
|
||||
typ == token.StringType ||
|
||||
typ == token.SingleQuoteType ||
|
||||
typ == token.DoubleQuoteType
|
||||
}
|
||||
|
||||
func isNotMapKeyType(tk *Token) bool {
|
||||
typ := tk.Type()
|
||||
return typ == token.DirectiveType ||
|
||||
typ == token.DocumentHeaderType ||
|
||||
typ == token.DocumentEndType ||
|
||||
typ == token.CollectEntryType ||
|
||||
typ == token.MappingStartType ||
|
||||
typ == token.MappingValueType ||
|
||||
typ == token.MappingEndType ||
|
||||
typ == token.SequenceStartType ||
|
||||
typ == token.SequenceEntryType ||
|
||||
typ == token.SequenceEndType
|
||||
}
|
||||
|
||||
func isFlowType(tk *Token) bool {
|
||||
typ := tk.Type()
|
||||
return typ == token.MappingStartType ||
|
||||
typ == token.MappingEndType ||
|
||||
typ == token.SequenceStartType ||
|
||||
typ == token.SequenceEntryType
|
||||
}
|
||||
835
vendor/github.com/goccy/go-yaml/path.go
generated
vendored
Normal file
835
vendor/github.com/goccy/go-yaml/path.go
generated
vendored
Normal file
@@ -0,0 +1,835 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml/ast"
|
||||
"github.com/goccy/go-yaml/parser"
|
||||
"github.com/goccy/go-yaml/printer"
|
||||
)
|
||||
|
||||
// PathString create Path from string
|
||||
//
|
||||
// YAMLPath rule
|
||||
// $ : the root object/element
|
||||
// . : child operator
|
||||
// .. : recursive descent
|
||||
// [num] : object/element of array by number
|
||||
// [*] : all objects/elements for array.
|
||||
//
|
||||
// If you want to use reserved characters such as `.` and `*` as a key name,
|
||||
// enclose them in single quotation as follows ( $.foo.'bar.baz-*'.hoge ).
|
||||
// If you want to use a single quote with reserved characters, escape it with `\` ( $.foo.'bar.baz\'s value'.hoge ).
|
||||
func PathString(s string) (*Path, error) {
|
||||
buf := []rune(s)
|
||||
length := len(buf)
|
||||
cursor := 0
|
||||
builder := &PathBuilder{}
|
||||
for cursor < length {
|
||||
c := buf[cursor]
|
||||
switch c {
|
||||
case '$':
|
||||
builder = builder.Root()
|
||||
cursor++
|
||||
case '.':
|
||||
b, buf, c, err := parsePathDot(builder, buf, cursor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
length = len(buf)
|
||||
builder = b
|
||||
cursor = c
|
||||
case '[':
|
||||
b, buf, c, err := parsePathIndex(builder, buf, cursor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
length = len(buf)
|
||||
builder = b
|
||||
cursor = c
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid path at %d: %w", cursor, ErrInvalidPathString)
|
||||
}
|
||||
}
|
||||
return builder.Build(), nil
|
||||
}
|
||||
|
||||
func parsePathRecursive(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) {
|
||||
length := len(buf)
|
||||
cursor += 2 // skip .. characters
|
||||
start := cursor
|
||||
for ; cursor < length; cursor++ {
|
||||
c := buf[cursor]
|
||||
switch c {
|
||||
case '$':
|
||||
return nil, nil, 0, fmt.Errorf("specified '$' after '..' character: %w", ErrInvalidPathString)
|
||||
case '*':
|
||||
return nil, nil, 0, fmt.Errorf("specified '*' after '..' character: %w", ErrInvalidPathString)
|
||||
case '.', '[':
|
||||
goto end
|
||||
case ']':
|
||||
return nil, nil, 0, fmt.Errorf("specified ']' after '..' character: %w", ErrInvalidPathString)
|
||||
}
|
||||
}
|
||||
end:
|
||||
if start == cursor {
|
||||
return nil, nil, 0, fmt.Errorf("not found recursive selector: %w", ErrInvalidPathString)
|
||||
}
|
||||
return b.Recursive(string(buf[start:cursor])), buf, cursor, nil
|
||||
}
|
||||
|
||||
func parsePathDot(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) {
|
||||
if b.root == nil || b.node == nil {
|
||||
return nil, nil, 0, fmt.Errorf("required '$' character at first: %w", ErrInvalidPathString)
|
||||
}
|
||||
length := len(buf)
|
||||
if cursor+1 < length && buf[cursor+1] == '.' {
|
||||
b, buf, c, err := parsePathRecursive(b, buf, cursor)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
return b, buf, c, nil
|
||||
}
|
||||
cursor++ // skip . character
|
||||
start := cursor
|
||||
|
||||
// if started single quote, looking for end single quote char
|
||||
if cursor < length && buf[cursor] == '\'' {
|
||||
return parseQuotedKey(b, buf, cursor)
|
||||
}
|
||||
for ; cursor < length; cursor++ {
|
||||
c := buf[cursor]
|
||||
switch c {
|
||||
case '$':
|
||||
return nil, nil, 0, fmt.Errorf("specified '$' after '.' character: %w", ErrInvalidPathString)
|
||||
case '*':
|
||||
return nil, nil, 0, fmt.Errorf("specified '*' after '.' character: %w", ErrInvalidPathString)
|
||||
case '.', '[':
|
||||
goto end
|
||||
case ']':
|
||||
return nil, nil, 0, fmt.Errorf("specified ']' after '.' character: %w", ErrInvalidPathString)
|
||||
}
|
||||
}
|
||||
end:
|
||||
if start == cursor {
|
||||
return nil, nil, 0, fmt.Errorf("could not find by empty key: %w", ErrInvalidPathString)
|
||||
}
|
||||
return b.child(string(buf[start:cursor])), buf, cursor, nil
|
||||
}
|
||||
|
||||
func parseQuotedKey(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) {
|
||||
if b.root == nil || b.node == nil {
|
||||
return nil, nil, 0, fmt.Errorf("required '$' character at first: %w", ErrInvalidPathString)
|
||||
}
|
||||
|
||||
cursor++ // skip single quote
|
||||
start := cursor
|
||||
length := len(buf)
|
||||
var foundEndDelim bool
|
||||
for ; cursor < length; cursor++ {
|
||||
switch buf[cursor] {
|
||||
case '\\':
|
||||
buf = append(append([]rune{}, buf[:cursor]...), buf[cursor+1:]...)
|
||||
length = len(buf)
|
||||
case '\'':
|
||||
foundEndDelim = true
|
||||
goto end
|
||||
}
|
||||
}
|
||||
end:
|
||||
if !foundEndDelim {
|
||||
return nil, nil, 0, fmt.Errorf("could not find end delimiter for key: %w", ErrInvalidPathString)
|
||||
}
|
||||
if start == cursor {
|
||||
return nil, nil, 0, fmt.Errorf("could not find by empty key: %w", ErrInvalidPathString)
|
||||
}
|
||||
selector := buf[start:cursor]
|
||||
cursor++
|
||||
if cursor < length {
|
||||
switch buf[cursor] {
|
||||
case '$':
|
||||
return nil, nil, 0, fmt.Errorf("specified '$' after '.' character: %w", ErrInvalidPathString)
|
||||
case '*':
|
||||
return nil, nil, 0, fmt.Errorf("specified '*' after '.' character: %w", ErrInvalidPathString)
|
||||
case ']':
|
||||
return nil, nil, 0, fmt.Errorf("specified ']' after '.' character: %w", ErrInvalidPathString)
|
||||
}
|
||||
}
|
||||
return b.child(string(selector)), buf, cursor, nil
|
||||
}
|
||||
|
||||
func parsePathIndex(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) {
|
||||
if b.root == nil || b.node == nil {
|
||||
return nil, nil, 0, fmt.Errorf("required '$' character at first: %w", ErrInvalidPathString)
|
||||
}
|
||||
|
||||
length := len(buf)
|
||||
cursor++ // skip '[' character
|
||||
if length <= cursor {
|
||||
return nil, nil, 0, fmt.Errorf("unexpected end of YAML Path: %w", ErrInvalidPathString)
|
||||
}
|
||||
c := buf[cursor]
|
||||
switch c {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*':
|
||||
start := cursor
|
||||
cursor++
|
||||
for ; cursor < length; cursor++ {
|
||||
c := buf[cursor]
|
||||
switch c {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if buf[cursor] != ']' {
|
||||
return nil, nil, 0, fmt.Errorf("invalid character %s at %d: %w", string(buf[cursor]), cursor, ErrInvalidPathString)
|
||||
}
|
||||
numOrAll := string(buf[start:cursor])
|
||||
if numOrAll == "*" {
|
||||
return b.IndexAll(), buf, cursor + 1, nil
|
||||
}
|
||||
num, err := strconv.ParseInt(numOrAll, 10, 64)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
return b.Index(uint(num)), buf, cursor + 1, nil
|
||||
}
|
||||
return nil, nil, 0, fmt.Errorf("invalid character %q at %d: %w", c, cursor, ErrInvalidPathString)
|
||||
}
|
||||
|
||||
// Path represent YAMLPath ( like a JSONPath ).
|
||||
type Path struct {
|
||||
node pathNode
|
||||
}
|
||||
|
||||
// String path to text.
|
||||
func (p *Path) String() string {
|
||||
return p.node.String()
|
||||
}
|
||||
|
||||
// Read decode from r and set extracted value by YAMLPath to v.
|
||||
func (p *Path) Read(r io.Reader, v interface{}) error {
|
||||
node, err := p.ReadNode(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Unmarshal([]byte(node.String()), v); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadNode create AST from r and extract node by YAMLPath.
|
||||
func (p *Path) ReadNode(r io.Reader) (ast.Node, error) {
|
||||
if p.node == nil {
|
||||
return nil, ErrInvalidPath
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := parser.ParseBytes(buf.Bytes(), 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node, err := p.FilterFile(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// Filter filter from target by YAMLPath and set it to v.
|
||||
func (p *Path) Filter(target, v interface{}) error {
|
||||
b, err := Marshal(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.Read(bytes.NewBuffer(b), v); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FilterFile filter from ast.File by YAMLPath.
|
||||
func (p *Path) FilterFile(f *ast.File) (ast.Node, error) {
|
||||
for _, doc := range f.Docs {
|
||||
// For simplicity, directives cannot be the target of operations
|
||||
if doc.Body != nil && doc.Body.Type() == ast.DirectiveType {
|
||||
continue
|
||||
}
|
||||
node, err := p.FilterNode(doc.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if node != nil {
|
||||
return node, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("failed to find path ( %s ): %w", p.node, ErrNotFoundNode)
|
||||
}
|
||||
|
||||
// FilterNode filter from node by YAMLPath.
|
||||
func (p *Path) FilterNode(node ast.Node) (ast.Node, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
n, err := p.node.filter(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// MergeFromReader merge YAML text into ast.File.
|
||||
func (p *Path) MergeFromReader(dst *ast.File, src io.Reader) error {
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, src); err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := parser.ParseBytes(buf.Bytes(), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.MergeFromFile(dst, file); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MergeFromFile merge ast.File into ast.File.
|
||||
func (p *Path) MergeFromFile(dst *ast.File, src *ast.File) error {
|
||||
base, err := p.FilterFile(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, doc := range src.Docs {
|
||||
if err := ast.Merge(base, doc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MergeFromNode merge ast.Node into ast.File.
|
||||
func (p *Path) MergeFromNode(dst *ast.File, src ast.Node) error {
|
||||
base, err := p.FilterFile(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ast.Merge(base, src); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReplaceWithReader replace ast.File with io.Reader.
|
||||
func (p *Path) ReplaceWithReader(dst *ast.File, src io.Reader) error {
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, src); err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := parser.ParseBytes(buf.Bytes(), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.ReplaceWithFile(dst, file); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReplaceWithFile replace ast.File with ast.File.
|
||||
func (p *Path) ReplaceWithFile(dst *ast.File, src *ast.File) error {
|
||||
for _, doc := range src.Docs {
|
||||
if err := p.ReplaceWithNode(dst, doc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReplaceNode replace ast.File with ast.Node.
|
||||
func (p *Path) ReplaceWithNode(dst *ast.File, node ast.Node) error {
|
||||
for _, doc := range dst.Docs {
|
||||
// For simplicity, directives cannot be the target of operations
|
||||
if doc.Body != nil && doc.Body.Type() == ast.DirectiveType {
|
||||
continue
|
||||
}
|
||||
if node.Type() == ast.DocumentType {
|
||||
node = node.(*ast.DocumentNode).Body
|
||||
}
|
||||
if err := p.node.replace(doc.Body, node); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AnnotateSource add annotation to passed source ( see section 5.1 in README.md ).
|
||||
func (p *Path) AnnotateSource(source []byte, colored bool) ([]byte, error) {
|
||||
file, err := parser.ParseBytes(source, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node, err := p.FilterFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var pp printer.Printer
|
||||
return []byte(pp.PrintErrorToken(node.GetToken(), colored)), nil
|
||||
}
|
||||
|
||||
// PathBuilder represent builder for YAMLPath.
|
||||
type PathBuilder struct {
|
||||
root *rootNode
|
||||
node pathNode
|
||||
}
|
||||
|
||||
// Root add '$' to current path.
|
||||
func (b *PathBuilder) Root() *PathBuilder {
|
||||
root := newRootNode()
|
||||
return &PathBuilder{root: root, node: root}
|
||||
}
|
||||
|
||||
// IndexAll add '[*]' to current path.
|
||||
func (b *PathBuilder) IndexAll() *PathBuilder {
|
||||
b.node = b.node.chain(newIndexAllNode())
|
||||
return b
|
||||
}
|
||||
|
||||
// Recursive add '..selector' to current path.
|
||||
func (b *PathBuilder) Recursive(selector string) *PathBuilder {
|
||||
b.node = b.node.chain(newRecursiveNode(selector))
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *PathBuilder) containsReservedPathCharacters(path string) bool {
|
||||
if strings.Contains(path, ".") {
|
||||
return true
|
||||
}
|
||||
if strings.Contains(path, "*") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *PathBuilder) enclosedSingleQuote(name string) bool {
|
||||
return strings.HasPrefix(name, "'") && strings.HasSuffix(name, "'")
|
||||
}
|
||||
|
||||
func (b *PathBuilder) normalizeSelectorName(name string) string {
|
||||
if b.enclosedSingleQuote(name) {
|
||||
// already escaped name
|
||||
return name
|
||||
}
|
||||
if b.containsReservedPathCharacters(name) {
|
||||
escapedName := strings.ReplaceAll(name, `'`, `\'`)
|
||||
return "'" + escapedName + "'"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func (b *PathBuilder) child(name string) *PathBuilder {
|
||||
b.node = b.node.chain(newSelectorNode(name))
|
||||
return b
|
||||
}
|
||||
|
||||
// Child add '.name' to current path.
|
||||
func (b *PathBuilder) Child(name string) *PathBuilder {
|
||||
return b.child(b.normalizeSelectorName(name))
|
||||
}
|
||||
|
||||
// Index add '[idx]' to current path.
|
||||
func (b *PathBuilder) Index(idx uint) *PathBuilder {
|
||||
b.node = b.node.chain(newIndexNode(idx))
|
||||
return b
|
||||
}
|
||||
|
||||
// Build build YAMLPath.
|
||||
func (b *PathBuilder) Build() *Path {
|
||||
return &Path{node: b.root}
|
||||
}
|
||||
|
||||
type pathNode interface {
|
||||
fmt.Stringer
|
||||
chain(pathNode) pathNode
|
||||
filter(ast.Node) (ast.Node, error)
|
||||
replace(ast.Node, ast.Node) error
|
||||
}
|
||||
|
||||
type basePathNode struct {
|
||||
child pathNode
|
||||
}
|
||||
|
||||
func (n *basePathNode) chain(node pathNode) pathNode {
|
||||
n.child = node
|
||||
return node
|
||||
}
|
||||
|
||||
type rootNode struct {
|
||||
*basePathNode
|
||||
}
|
||||
|
||||
func newRootNode() *rootNode {
|
||||
return &rootNode{basePathNode: &basePathNode{}}
|
||||
}
|
||||
|
||||
func (n *rootNode) String() string {
|
||||
s := "$"
|
||||
if n.child != nil {
|
||||
s += n.child.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (n *rootNode) filter(node ast.Node) (ast.Node, error) {
|
||||
if n.child == nil {
|
||||
return node, nil
|
||||
}
|
||||
filtered, err := n.child.filter(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func (n *rootNode) replace(node ast.Node, target ast.Node) error {
|
||||
if n.child == nil {
|
||||
return nil
|
||||
}
|
||||
if err := n.child.replace(node, target); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type selectorNode struct {
|
||||
*basePathNode
|
||||
selector string
|
||||
}
|
||||
|
||||
func newSelectorNode(selector string) *selectorNode {
|
||||
return &selectorNode{
|
||||
basePathNode: &basePathNode{},
|
||||
selector: selector,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *selectorNode) filter(node ast.Node) (ast.Node, error) {
|
||||
selector := n.selector
|
||||
if len(selector) > 1 && selector[0] == '\'' && selector[len(selector)-1] == '\'' {
|
||||
selector = selector[1 : len(selector)-1]
|
||||
}
|
||||
switch node.Type() {
|
||||
case ast.MappingType:
|
||||
for _, value := range node.(*ast.MappingNode).Values {
|
||||
key := value.Key.GetToken().Value
|
||||
if len(key) > 0 {
|
||||
switch key[0] {
|
||||
case '"':
|
||||
var err error
|
||||
key, err = strconv.Unquote(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case '\'':
|
||||
if len(key) > 1 && key[len(key)-1] == '\'' {
|
||||
key = key[1 : len(key)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
if key == selector {
|
||||
if n.child == nil {
|
||||
return value.Value, nil
|
||||
}
|
||||
filtered, err := n.child.filter(value.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
}
|
||||
case ast.MappingValueType:
|
||||
value, _ := node.(*ast.MappingValueNode)
|
||||
key := value.Key.GetToken().Value
|
||||
if key == selector {
|
||||
if n.child == nil {
|
||||
return value.Value, nil
|
||||
}
|
||||
filtered, err := n.child.filter(value.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("expected node type is map or map value. but got %s: %w", node.Type(), ErrInvalidQuery)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n *selectorNode) replaceMapValue(value *ast.MappingValueNode, target ast.Node) error {
|
||||
key := value.Key.GetToken().Value
|
||||
if key != n.selector {
|
||||
return nil
|
||||
}
|
||||
if n.child == nil {
|
||||
if err := value.Replace(target); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := n.child.replace(value.Value, target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *selectorNode) replace(node ast.Node, target ast.Node) error {
|
||||
switch node.Type() {
|
||||
case ast.MappingType:
|
||||
for _, value := range node.(*ast.MappingNode).Values {
|
||||
if err := n.replaceMapValue(value, target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case ast.MappingValueType:
|
||||
value, _ := node.(*ast.MappingValueNode)
|
||||
if err := n.replaceMapValue(value, target); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("expected node type is map or map value. but got %s: %w", node.Type(), ErrInvalidQuery)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *selectorNode) String() string {
|
||||
var builder PathBuilder
|
||||
selector := builder.normalizeSelectorName(n.selector)
|
||||
s := fmt.Sprintf(".%s", selector)
|
||||
if n.child != nil {
|
||||
s += n.child.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type indexNode struct {
|
||||
*basePathNode
|
||||
selector uint
|
||||
}
|
||||
|
||||
func newIndexNode(selector uint) *indexNode {
|
||||
return &indexNode{
|
||||
basePathNode: &basePathNode{},
|
||||
selector: selector,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *indexNode) filter(node ast.Node) (ast.Node, error) {
|
||||
if node.Type() != ast.SequenceType {
|
||||
return nil, fmt.Errorf("expected sequence type node. but got %s: %w", node.Type(), ErrInvalidQuery)
|
||||
}
|
||||
sequence, _ := node.(*ast.SequenceNode)
|
||||
if n.selector >= uint(len(sequence.Values)) {
|
||||
return nil, fmt.Errorf("expected index is %d. but got sequences has %d items: %w", n.selector, len(sequence.Values), ErrInvalidQuery)
|
||||
}
|
||||
value := sequence.Values[n.selector]
|
||||
if n.child == nil {
|
||||
return value, nil
|
||||
}
|
||||
filtered, err := n.child.filter(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func (n *indexNode) replace(node ast.Node, target ast.Node) error {
|
||||
if node.Type() != ast.SequenceType {
|
||||
return fmt.Errorf("expected sequence type node. but got %s: %w", node.Type(), ErrInvalidQuery)
|
||||
}
|
||||
sequence, _ := node.(*ast.SequenceNode)
|
||||
if n.selector >= uint(len(sequence.Values)) {
|
||||
return fmt.Errorf("expected index is %d. but got sequences has %d items: %w", n.selector, len(sequence.Values), ErrInvalidQuery)
|
||||
}
|
||||
if n.child == nil {
|
||||
if err := sequence.Replace(int(n.selector), target); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := n.child.replace(sequence.Values[n.selector], target); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *indexNode) String() string {
|
||||
s := fmt.Sprintf("[%d]", n.selector)
|
||||
if n.child != nil {
|
||||
s += n.child.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type indexAllNode struct {
|
||||
*basePathNode
|
||||
}
|
||||
|
||||
func newIndexAllNode() *indexAllNode {
|
||||
return &indexAllNode{
|
||||
basePathNode: &basePathNode{},
|
||||
}
|
||||
}
|
||||
|
||||
func (n *indexAllNode) String() string {
|
||||
s := "[*]"
|
||||
if n.child != nil {
|
||||
s += n.child.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (n *indexAllNode) filter(node ast.Node) (ast.Node, error) {
|
||||
if node.Type() != ast.SequenceType {
|
||||
return nil, fmt.Errorf("expected sequence type node. but got %s: %w", node.Type(), ErrInvalidQuery)
|
||||
}
|
||||
sequence, _ := node.(*ast.SequenceNode)
|
||||
if n.child == nil {
|
||||
return sequence, nil
|
||||
}
|
||||
out := *sequence
|
||||
out.Values = []ast.Node{}
|
||||
for _, value := range sequence.Values {
|
||||
filtered, err := n.child.filter(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Values = append(out.Values, filtered)
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (n *indexAllNode) replace(node ast.Node, target ast.Node) error {
|
||||
if node.Type() != ast.SequenceType {
|
||||
return fmt.Errorf("expected sequence type node. but got %s: %w", node.Type(), ErrInvalidQuery)
|
||||
}
|
||||
sequence, _ := node.(*ast.SequenceNode)
|
||||
if n.child == nil {
|
||||
for idx := range sequence.Values {
|
||||
if err := sequence.Replace(idx, target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for _, value := range sequence.Values {
|
||||
if err := n.child.replace(value, target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type recursiveNode struct {
|
||||
*basePathNode
|
||||
selector string
|
||||
}
|
||||
|
||||
func newRecursiveNode(selector string) *recursiveNode {
|
||||
return &recursiveNode{
|
||||
basePathNode: &basePathNode{},
|
||||
selector: selector,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *recursiveNode) String() string {
|
||||
s := fmt.Sprintf("..%s", n.selector)
|
||||
if n.child != nil {
|
||||
s += n.child.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (n *recursiveNode) filterNode(node ast.Node) (*ast.SequenceNode, error) {
|
||||
sequence := &ast.SequenceNode{BaseNode: &ast.BaseNode{}}
|
||||
switch typedNode := node.(type) {
|
||||
case *ast.MappingNode:
|
||||
for _, value := range typedNode.Values {
|
||||
seq, err := n.filterNode(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sequence.Values = append(sequence.Values, seq.Values...)
|
||||
}
|
||||
case *ast.MappingValueNode:
|
||||
key := typedNode.Key.GetToken().Value
|
||||
if n.selector == key {
|
||||
sequence.Values = append(sequence.Values, typedNode.Value)
|
||||
}
|
||||
seq, err := n.filterNode(typedNode.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sequence.Values = append(sequence.Values, seq.Values...)
|
||||
case *ast.SequenceNode:
|
||||
for _, value := range typedNode.Values {
|
||||
seq, err := n.filterNode(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sequence.Values = append(sequence.Values, seq.Values...)
|
||||
}
|
||||
}
|
||||
return sequence, nil
|
||||
}
|
||||
|
||||
func (n *recursiveNode) filter(node ast.Node) (ast.Node, error) {
|
||||
sequence, err := n.filterNode(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sequence.Start = node.GetToken()
|
||||
return sequence, nil
|
||||
}
|
||||
|
||||
func (n *recursiveNode) replaceNode(node ast.Node, target ast.Node) error {
|
||||
switch typedNode := node.(type) {
|
||||
case *ast.MappingNode:
|
||||
for _, value := range typedNode.Values {
|
||||
if err := n.replaceNode(value, target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case *ast.MappingValueNode:
|
||||
key := typedNode.Key.GetToken().Value
|
||||
if n.selector == key {
|
||||
if err := typedNode.Replace(target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := n.replaceNode(typedNode.Value, target); err != nil {
|
||||
return err
|
||||
}
|
||||
case *ast.SequenceNode:
|
||||
for _, value := range typedNode.Values {
|
||||
if err := n.replaceNode(value, target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *recursiveNode) replace(node ast.Node, target ast.Node) error {
|
||||
if err := n.replaceNode(node, target); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
83
vendor/github.com/goccy/go-yaml/printer/color.go
generated
vendored
Normal file
83
vendor/github.com/goccy/go-yaml/printer/color.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
// This source inspired by https://github.com/fatih/color.
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ColorAttribute int
|
||||
|
||||
const (
|
||||
ColorReset ColorAttribute = iota
|
||||
ColorBold
|
||||
ColorFaint
|
||||
ColorItalic
|
||||
ColorUnderline
|
||||
ColorBlinkSlow
|
||||
ColorBlinkRapid
|
||||
ColorReverseVideo
|
||||
ColorConcealed
|
||||
ColorCrossedOut
|
||||
)
|
||||
|
||||
const (
|
||||
ColorFgHiBlack ColorAttribute = iota + 90
|
||||
ColorFgHiRed
|
||||
ColorFgHiGreen
|
||||
ColorFgHiYellow
|
||||
ColorFgHiBlue
|
||||
ColorFgHiMagenta
|
||||
ColorFgHiCyan
|
||||
ColorFgHiWhite
|
||||
)
|
||||
|
||||
const (
|
||||
ColorResetBold ColorAttribute = iota + 22
|
||||
ColorResetItalic
|
||||
ColorResetUnderline
|
||||
ColorResetBlinking
|
||||
|
||||
ColorResetReversed
|
||||
ColorResetConcealed
|
||||
ColorResetCrossedOut
|
||||
)
|
||||
|
||||
const escape = "\x1b"
|
||||
|
||||
var colorResetMap = map[ColorAttribute]ColorAttribute{
|
||||
ColorBold: ColorResetBold,
|
||||
ColorFaint: ColorResetBold,
|
||||
ColorItalic: ColorResetItalic,
|
||||
ColorUnderline: ColorResetUnderline,
|
||||
ColorBlinkSlow: ColorResetBlinking,
|
||||
ColorBlinkRapid: ColorResetBlinking,
|
||||
ColorReverseVideo: ColorResetReversed,
|
||||
ColorConcealed: ColorResetConcealed,
|
||||
ColorCrossedOut: ColorResetCrossedOut,
|
||||
}
|
||||
|
||||
func format(attrs ...ColorAttribute) string {
|
||||
format := make([]string, 0, len(attrs))
|
||||
for _, attr := range attrs {
|
||||
format = append(format, fmt.Sprint(attr))
|
||||
}
|
||||
return fmt.Sprintf("%s[%sm", escape, strings.Join(format, ";"))
|
||||
}
|
||||
|
||||
func unformat(attrs ...ColorAttribute) string {
|
||||
format := make([]string, len(attrs))
|
||||
for _, attr := range attrs {
|
||||
v := fmt.Sprint(ColorReset)
|
||||
reset, exists := colorResetMap[attr]
|
||||
if exists {
|
||||
v = fmt.Sprint(reset)
|
||||
}
|
||||
format = append(format, v)
|
||||
}
|
||||
return fmt.Sprintf("%s[%sm", escape, strings.Join(format, ";"))
|
||||
}
|
||||
|
||||
func colorize(msg string, attrs ...ColorAttribute) string {
|
||||
return format(attrs...) + msg + unformat(attrs...)
|
||||
}
|
||||
353
vendor/github.com/goccy/go-yaml/printer/printer.go
generated
vendored
Normal file
353
vendor/github.com/goccy/go-yaml/printer/printer.go
generated
vendored
Normal file
@@ -0,0 +1,353 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml/ast"
|
||||
"github.com/goccy/go-yaml/token"
|
||||
)
|
||||
|
||||
// Property additional property set for each the token
|
||||
type Property struct {
|
||||
Prefix string
|
||||
Suffix string
|
||||
}
|
||||
|
||||
// PrintFunc returns property instance
|
||||
type PrintFunc func() *Property
|
||||
|
||||
// Printer create text from token collection or ast
|
||||
type Printer struct {
|
||||
LineNumber bool
|
||||
LineNumberFormat func(num int) string
|
||||
MapKey PrintFunc
|
||||
Anchor PrintFunc
|
||||
Alias PrintFunc
|
||||
Bool PrintFunc
|
||||
String PrintFunc
|
||||
Number PrintFunc
|
||||
Comment PrintFunc
|
||||
}
|
||||
|
||||
func defaultLineNumberFormat(num int) string {
|
||||
return fmt.Sprintf("%2d | ", num)
|
||||
}
|
||||
|
||||
func (p *Printer) property(tk *token.Token) *Property {
|
||||
prop := &Property{}
|
||||
switch tk.PreviousType() {
|
||||
case token.AnchorType:
|
||||
if p.Anchor != nil {
|
||||
return p.Anchor()
|
||||
}
|
||||
return prop
|
||||
case token.AliasType:
|
||||
if p.Alias != nil {
|
||||
return p.Alias()
|
||||
}
|
||||
return prop
|
||||
}
|
||||
switch tk.NextType() {
|
||||
case token.MappingValueType:
|
||||
if p.MapKey != nil {
|
||||
return p.MapKey()
|
||||
}
|
||||
return prop
|
||||
}
|
||||
switch tk.Type {
|
||||
case token.BoolType:
|
||||
if p.Bool != nil {
|
||||
return p.Bool()
|
||||
}
|
||||
return prop
|
||||
case token.AnchorType:
|
||||
if p.Anchor != nil {
|
||||
return p.Anchor()
|
||||
}
|
||||
return prop
|
||||
case token.AliasType:
|
||||
if p.Anchor != nil {
|
||||
return p.Alias()
|
||||
}
|
||||
return prop
|
||||
case token.StringType, token.SingleQuoteType, token.DoubleQuoteType:
|
||||
if p.String != nil {
|
||||
return p.String()
|
||||
}
|
||||
return prop
|
||||
case token.IntegerType, token.FloatType:
|
||||
if p.Number != nil {
|
||||
return p.Number()
|
||||
}
|
||||
return prop
|
||||
case token.CommentType:
|
||||
if p.Comment != nil {
|
||||
return p.Comment()
|
||||
}
|
||||
return prop
|
||||
default:
|
||||
}
|
||||
return prop
|
||||
}
|
||||
|
||||
// PrintTokens create text from token collection
|
||||
func (p *Printer) PrintTokens(tokens token.Tokens) string {
|
||||
if len(tokens) == 0 {
|
||||
return ""
|
||||
}
|
||||
if p.LineNumber {
|
||||
if p.LineNumberFormat == nil {
|
||||
p.LineNumberFormat = defaultLineNumberFormat
|
||||
}
|
||||
}
|
||||
texts := []string{}
|
||||
lineNumber := tokens[0].Position.Line
|
||||
for _, tk := range tokens {
|
||||
lines := strings.Split(tk.Origin, "\n")
|
||||
prop := p.property(tk)
|
||||
header := ""
|
||||
if p.LineNumber {
|
||||
header = p.LineNumberFormat(lineNumber)
|
||||
}
|
||||
if len(lines) == 1 {
|
||||
line := prop.Prefix + lines[0] + prop.Suffix
|
||||
if len(texts) == 0 {
|
||||
texts = append(texts, header+line)
|
||||
lineNumber++
|
||||
} else {
|
||||
text := texts[len(texts)-1]
|
||||
texts[len(texts)-1] = text + line
|
||||
}
|
||||
} else {
|
||||
for idx, src := range lines {
|
||||
if p.LineNumber {
|
||||
header = p.LineNumberFormat(lineNumber)
|
||||
}
|
||||
line := prop.Prefix + src + prop.Suffix
|
||||
if idx == 0 {
|
||||
if len(texts) == 0 {
|
||||
texts = append(texts, header+line)
|
||||
lineNumber++
|
||||
} else {
|
||||
text := texts[len(texts)-1]
|
||||
texts[len(texts)-1] = text + line
|
||||
}
|
||||
} else {
|
||||
texts = append(texts, fmt.Sprintf("%s%s", header, line))
|
||||
lineNumber++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(texts, "\n")
|
||||
}
|
||||
|
||||
// PrintNode create text from ast.Node
|
||||
func (p *Printer) PrintNode(node ast.Node) []byte {
|
||||
return []byte(fmt.Sprintf("%+v\n", node))
|
||||
}
|
||||
|
||||
func (p *Printer) setDefaultColorSet() {
|
||||
p.Bool = func() *Property {
|
||||
return &Property{
|
||||
Prefix: format(ColorFgHiMagenta),
|
||||
Suffix: format(ColorReset),
|
||||
}
|
||||
}
|
||||
p.Number = func() *Property {
|
||||
return &Property{
|
||||
Prefix: format(ColorFgHiMagenta),
|
||||
Suffix: format(ColorReset),
|
||||
}
|
||||
}
|
||||
p.MapKey = func() *Property {
|
||||
return &Property{
|
||||
Prefix: format(ColorFgHiCyan),
|
||||
Suffix: format(ColorReset),
|
||||
}
|
||||
}
|
||||
p.Anchor = func() *Property {
|
||||
return &Property{
|
||||
Prefix: format(ColorFgHiYellow),
|
||||
Suffix: format(ColorReset),
|
||||
}
|
||||
}
|
||||
p.Alias = func() *Property {
|
||||
return &Property{
|
||||
Prefix: format(ColorFgHiYellow),
|
||||
Suffix: format(ColorReset),
|
||||
}
|
||||
}
|
||||
p.String = func() *Property {
|
||||
return &Property{
|
||||
Prefix: format(ColorFgHiGreen),
|
||||
Suffix: format(ColorReset),
|
||||
}
|
||||
}
|
||||
p.Comment = func() *Property {
|
||||
return &Property{
|
||||
Prefix: format(ColorFgHiBlack),
|
||||
Suffix: format(ColorReset),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Printer) PrintErrorMessage(msg string, isColored bool) string {
|
||||
if isColored {
|
||||
return fmt.Sprintf("%s%s%s",
|
||||
format(ColorFgHiRed),
|
||||
msg,
|
||||
format(ColorReset),
|
||||
)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func (p *Printer) removeLeftSideNewLineChar(src string) string {
|
||||
return strings.TrimLeft(strings.TrimLeft(strings.TrimLeft(src, "\r"), "\n"), "\r\n")
|
||||
}
|
||||
|
||||
func (p *Printer) removeRightSideNewLineChar(src string) string {
|
||||
return strings.TrimRight(strings.TrimRight(strings.TrimRight(src, "\r"), "\n"), "\r\n")
|
||||
}
|
||||
|
||||
func (p *Printer) removeRightSideWhiteSpaceChar(src string) string {
|
||||
return p.removeRightSideNewLineChar(strings.TrimRight(src, " "))
|
||||
}
|
||||
|
||||
func (p *Printer) newLineCount(s string) int {
|
||||
src := []rune(s)
|
||||
size := len(src)
|
||||
cnt := 0
|
||||
for i := 0; i < size; i++ {
|
||||
c := src[i]
|
||||
switch c {
|
||||
case '\r':
|
||||
if i+1 < size && src[i+1] == '\n' {
|
||||
i++
|
||||
}
|
||||
cnt++
|
||||
case '\n':
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
return cnt
|
||||
}
|
||||
|
||||
func (p *Printer) isNewLineLastChar(s string) bool {
|
||||
for i := len(s) - 1; i > 0; i-- {
|
||||
c := s[i]
|
||||
switch c {
|
||||
case ' ':
|
||||
continue
|
||||
case '\n', '\r':
|
||||
return true
|
||||
}
|
||||
break
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Printer) printBeforeTokens(tk *token.Token, minLine, extLine int) token.Tokens {
|
||||
for tk.Prev != nil {
|
||||
if tk.Prev.Position.Line < minLine {
|
||||
break
|
||||
}
|
||||
tk = tk.Prev
|
||||
}
|
||||
minTk := tk.Clone()
|
||||
if minTk.Prev != nil {
|
||||
// add white spaces to minTk by prev token
|
||||
prev := minTk.Prev
|
||||
whiteSpaceLen := len(prev.Origin) - len(strings.TrimRight(prev.Origin, " "))
|
||||
minTk.Origin = strings.Repeat(" ", whiteSpaceLen) + minTk.Origin
|
||||
}
|
||||
minTk.Origin = p.removeLeftSideNewLineChar(minTk.Origin)
|
||||
tokens := token.Tokens{minTk}
|
||||
tk = minTk.Next
|
||||
for tk != nil && tk.Position.Line <= extLine {
|
||||
clonedTk := tk.Clone()
|
||||
tokens.Add(clonedTk)
|
||||
tk = clonedTk.Next
|
||||
}
|
||||
lastTk := tokens[len(tokens)-1]
|
||||
trimmedOrigin := p.removeRightSideWhiteSpaceChar(lastTk.Origin)
|
||||
suffix := lastTk.Origin[len(trimmedOrigin):]
|
||||
lastTk.Origin = trimmedOrigin
|
||||
|
||||
if lastTk.Next != nil && len(suffix) > 1 {
|
||||
next := lastTk.Next.Clone()
|
||||
// add suffix to header of next token
|
||||
if suffix[0] == '\n' || suffix[0] == '\r' {
|
||||
suffix = suffix[1:]
|
||||
}
|
||||
next.Origin = suffix + next.Origin
|
||||
lastTk.Next = next
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
func (p *Printer) printAfterTokens(tk *token.Token, maxLine int) token.Tokens {
|
||||
tokens := token.Tokens{}
|
||||
if tk == nil {
|
||||
return tokens
|
||||
}
|
||||
if tk.Position.Line > maxLine {
|
||||
return tokens
|
||||
}
|
||||
minTk := tk.Clone()
|
||||
minTk.Origin = p.removeLeftSideNewLineChar(minTk.Origin)
|
||||
tokens.Add(minTk)
|
||||
tk = minTk.Next
|
||||
for tk != nil && tk.Position.Line <= maxLine {
|
||||
clonedTk := tk.Clone()
|
||||
tokens.Add(clonedTk)
|
||||
tk = clonedTk.Next
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
func (p *Printer) setupErrorTokenFormat(annotateLine int, isColored bool) {
|
||||
prefix := func(annotateLine, num int) string {
|
||||
if annotateLine == num {
|
||||
return fmt.Sprintf("> %2d | ", num)
|
||||
}
|
||||
return fmt.Sprintf(" %2d | ", num)
|
||||
}
|
||||
p.LineNumber = true
|
||||
p.LineNumberFormat = func(num int) string {
|
||||
if isColored {
|
||||
return colorize(prefix(annotateLine, num), ColorBold, ColorFgHiWhite)
|
||||
}
|
||||
return prefix(annotateLine, num)
|
||||
}
|
||||
if isColored {
|
||||
p.setDefaultColorSet()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Printer) PrintErrorToken(tk *token.Token, isColored bool) string {
|
||||
errToken := tk
|
||||
curLine := tk.Position.Line
|
||||
curExtLine := curLine + p.newLineCount(p.removeLeftSideNewLineChar(tk.Origin))
|
||||
if p.isNewLineLastChar(tk.Origin) {
|
||||
// if last character ( exclude white space ) is new line character, ignore it.
|
||||
curExtLine--
|
||||
}
|
||||
|
||||
minLine := int(math.Max(float64(curLine-3), 1))
|
||||
maxLine := curExtLine + 3
|
||||
p.setupErrorTokenFormat(curLine, isColored)
|
||||
|
||||
beforeTokens := p.printBeforeTokens(tk, minLine, curExtLine)
|
||||
lastTk := beforeTokens[len(beforeTokens)-1]
|
||||
afterTokens := p.printAfterTokens(lastTk.Next, maxLine)
|
||||
|
||||
beforeSource := p.PrintTokens(beforeTokens)
|
||||
prefixSpaceNum := len(fmt.Sprintf(" %2d | ", curLine))
|
||||
annotateLine := strings.Repeat(" ", prefixSpaceNum+errToken.Position.Column-1) + "^"
|
||||
afterSource := p.PrintTokens(afterTokens)
|
||||
return fmt.Sprintf("%s\n%s\n%s", beforeSource, annotateLine, afterSource)
|
||||
}
|
||||
452
vendor/github.com/goccy/go-yaml/scanner/context.go
generated
vendored
Normal file
452
vendor/github.com/goccy/go-yaml/scanner/context.go
generated
vendored
Normal file
@@ -0,0 +1,452 @@
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/goccy/go-yaml/token"
|
||||
)
|
||||
|
||||
// Context context at scanning
|
||||
type Context struct {
|
||||
idx int
|
||||
size int
|
||||
notSpaceCharPos int
|
||||
notSpaceOrgCharPos int
|
||||
src []rune
|
||||
buf []rune
|
||||
obuf []rune
|
||||
tokens token.Tokens
|
||||
mstate *MultiLineState
|
||||
}
|
||||
|
||||
type MultiLineState struct {
|
||||
opt string
|
||||
firstLineIndentColumn int
|
||||
prevLineIndentColumn int
|
||||
lineIndentColumn int
|
||||
lastNotSpaceOnlyLineIndentColumn int
|
||||
spaceOnlyIndentColumn int
|
||||
foldedNewLine bool
|
||||
isRawFolded bool
|
||||
isLiteral bool
|
||||
isFolded bool
|
||||
}
|
||||
|
||||
var (
|
||||
ctxPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return createContext()
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func createContext() *Context {
|
||||
return &Context{
|
||||
idx: 0,
|
||||
tokens: token.Tokens{},
|
||||
}
|
||||
}
|
||||
|
||||
func newContext(src []rune) *Context {
|
||||
ctx, _ := ctxPool.Get().(*Context)
|
||||
ctx.reset(src)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (c *Context) release() {
|
||||
ctxPool.Put(c)
|
||||
}
|
||||
|
||||
func (c *Context) clear() {
|
||||
c.resetBuffer()
|
||||
c.mstate = nil
|
||||
}
|
||||
|
||||
func (c *Context) reset(src []rune) {
|
||||
c.idx = 0
|
||||
c.size = len(src)
|
||||
c.src = src
|
||||
c.tokens = c.tokens[:0]
|
||||
c.resetBuffer()
|
||||
c.mstate = nil
|
||||
}
|
||||
|
||||
func (c *Context) resetBuffer() {
|
||||
c.buf = c.buf[:0]
|
||||
c.obuf = c.obuf[:0]
|
||||
c.notSpaceCharPos = 0
|
||||
c.notSpaceOrgCharPos = 0
|
||||
}
|
||||
|
||||
func (c *Context) breakMultiLine() {
|
||||
c.mstate = nil
|
||||
}
|
||||
|
||||
func (c *Context) getMultiLineState() *MultiLineState {
|
||||
return c.mstate
|
||||
}
|
||||
|
||||
func (c *Context) setLiteral(lastDelimColumn int, opt string) {
|
||||
mstate := &MultiLineState{
|
||||
isLiteral: true,
|
||||
opt: opt,
|
||||
}
|
||||
indent := firstLineIndentColumnByOpt(opt)
|
||||
if indent > 0 {
|
||||
mstate.firstLineIndentColumn = lastDelimColumn + indent
|
||||
}
|
||||
c.mstate = mstate
|
||||
}
|
||||
|
||||
func (c *Context) setFolded(lastDelimColumn int, opt string) {
|
||||
mstate := &MultiLineState{
|
||||
isFolded: true,
|
||||
opt: opt,
|
||||
}
|
||||
indent := firstLineIndentColumnByOpt(opt)
|
||||
if indent > 0 {
|
||||
mstate.firstLineIndentColumn = lastDelimColumn + indent
|
||||
}
|
||||
c.mstate = mstate
|
||||
}
|
||||
|
||||
func (c *Context) setRawFolded(column int) {
|
||||
mstate := &MultiLineState{
|
||||
isRawFolded: true,
|
||||
}
|
||||
mstate.updateIndentColumn(column)
|
||||
c.mstate = mstate
|
||||
}
|
||||
|
||||
func firstLineIndentColumnByOpt(opt string) int {
|
||||
opt = strings.TrimPrefix(opt, "-")
|
||||
opt = strings.TrimPrefix(opt, "+")
|
||||
opt = strings.TrimSuffix(opt, "-")
|
||||
opt = strings.TrimSuffix(opt, "+")
|
||||
i, _ := strconv.ParseInt(opt, 10, 64)
|
||||
return int(i)
|
||||
}
|
||||
|
||||
func (s *MultiLineState) lastDelimColumn() int {
|
||||
if s.firstLineIndentColumn == 0 {
|
||||
return 0
|
||||
}
|
||||
return s.firstLineIndentColumn - 1
|
||||
}
|
||||
|
||||
func (s *MultiLineState) updateIndentColumn(column int) {
|
||||
if s.firstLineIndentColumn == 0 {
|
||||
s.firstLineIndentColumn = column
|
||||
}
|
||||
if s.lineIndentColumn == 0 {
|
||||
s.lineIndentColumn = column
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MultiLineState) updateSpaceOnlyIndentColumn(column int) {
|
||||
if s.firstLineIndentColumn != 0 {
|
||||
return
|
||||
}
|
||||
s.spaceOnlyIndentColumn = column
|
||||
}
|
||||
|
||||
func (s *MultiLineState) validateIndentAfterSpaceOnly(column int) error {
|
||||
if s.firstLineIndentColumn != 0 {
|
||||
return nil
|
||||
}
|
||||
if s.spaceOnlyIndentColumn > column {
|
||||
return errors.New("invalid number of indent is specified after space only")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MultiLineState) validateIndentColumn() error {
|
||||
if firstLineIndentColumnByOpt(s.opt) == 0 {
|
||||
return nil
|
||||
}
|
||||
if s.firstLineIndentColumn > s.lineIndentColumn {
|
||||
return errors.New("invalid number of indent is specified in the multi-line header")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MultiLineState) updateNewLineState() {
|
||||
s.prevLineIndentColumn = s.lineIndentColumn
|
||||
if s.lineIndentColumn != 0 {
|
||||
s.lastNotSpaceOnlyLineIndentColumn = s.lineIndentColumn
|
||||
}
|
||||
s.foldedNewLine = true
|
||||
s.lineIndentColumn = 0
|
||||
}
|
||||
|
||||
func (s *MultiLineState) isIndentColumn(column int) bool {
|
||||
if s.firstLineIndentColumn == 0 {
|
||||
return column == 1
|
||||
}
|
||||
return s.firstLineIndentColumn > column
|
||||
}
|
||||
|
||||
func (s *MultiLineState) addIndent(ctx *Context, column int) {
|
||||
if s.firstLineIndentColumn == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// If the first line of the document has already been evaluated, the number is treated as the threshold, since the `firstLineIndentColumn` is a positive number.
|
||||
if column < s.firstLineIndentColumn {
|
||||
return
|
||||
}
|
||||
|
||||
// `c.foldedNewLine` is a variable that is set to true for every newline.
|
||||
if !s.isLiteral && s.foldedNewLine {
|
||||
s.foldedNewLine = false
|
||||
}
|
||||
// Since addBuf ignore space character, add to the buffer directly.
|
||||
ctx.buf = append(ctx.buf, ' ')
|
||||
ctx.notSpaceCharPos = len(ctx.buf)
|
||||
}
|
||||
|
||||
// updateNewLineInFolded if Folded or RawFolded context and the content on the current line starts at the same column as the previous line,
|
||||
// treat the new-line-char as a space.
|
||||
func (s *MultiLineState) updateNewLineInFolded(ctx *Context, column int) {
|
||||
if s.isLiteral {
|
||||
return
|
||||
}
|
||||
|
||||
// Folded or RawFolded.
|
||||
|
||||
if !s.foldedNewLine {
|
||||
return
|
||||
}
|
||||
var (
|
||||
lastChar rune
|
||||
prevLastChar rune
|
||||
)
|
||||
if len(ctx.buf) != 0 {
|
||||
lastChar = ctx.buf[len(ctx.buf)-1]
|
||||
}
|
||||
if len(ctx.buf) > 1 {
|
||||
prevLastChar = ctx.buf[len(ctx.buf)-2]
|
||||
}
|
||||
if s.lineIndentColumn == s.prevLineIndentColumn {
|
||||
// ---
|
||||
// >
|
||||
// a
|
||||
// b
|
||||
if lastChar == '\n' {
|
||||
ctx.buf[len(ctx.buf)-1] = ' '
|
||||
}
|
||||
} else if s.prevLineIndentColumn == 0 && s.lastNotSpaceOnlyLineIndentColumn == column {
|
||||
// if previous line is indent-space and new-line-char only, prevLineIndentColumn is zero.
|
||||
// In this case, last new-line-char is removed.
|
||||
// ---
|
||||
// >
|
||||
// a
|
||||
//
|
||||
// b
|
||||
if lastChar == '\n' && prevLastChar == '\n' {
|
||||
ctx.buf = ctx.buf[:len(ctx.buf)-1]
|
||||
ctx.notSpaceCharPos = len(ctx.buf)
|
||||
}
|
||||
}
|
||||
s.foldedNewLine = false
|
||||
}
|
||||
|
||||
func (s *MultiLineState) hasTrimAllEndNewlineOpt() bool {
|
||||
return strings.HasPrefix(s.opt, "-") || strings.HasSuffix(s.opt, "-") || s.isRawFolded
|
||||
}
|
||||
|
||||
func (s *MultiLineState) hasKeepAllEndNewlineOpt() bool {
|
||||
return strings.HasPrefix(s.opt, "+") || strings.HasSuffix(s.opt, "+")
|
||||
}
|
||||
|
||||
func (c *Context) addToken(tk *token.Token) {
|
||||
if tk == nil {
|
||||
return
|
||||
}
|
||||
c.tokens = append(c.tokens, tk)
|
||||
}
|
||||
|
||||
func (c *Context) addBuf(r rune) {
|
||||
if len(c.buf) == 0 && (r == ' ' || r == '\t') {
|
||||
return
|
||||
}
|
||||
c.buf = append(c.buf, r)
|
||||
if r != ' ' && r != '\t' {
|
||||
c.notSpaceCharPos = len(c.buf)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) addBufWithTab(r rune) {
|
||||
if len(c.buf) == 0 && r == ' ' {
|
||||
return
|
||||
}
|
||||
c.buf = append(c.buf, r)
|
||||
if r != ' ' {
|
||||
c.notSpaceCharPos = len(c.buf)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) addOriginBuf(r rune) {
|
||||
c.obuf = append(c.obuf, r)
|
||||
if r != ' ' && r != '\t' {
|
||||
c.notSpaceOrgCharPos = len(c.obuf)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) removeRightSpaceFromBuf() {
|
||||
trimmedBuf := c.obuf[:c.notSpaceOrgCharPos]
|
||||
buflen := len(trimmedBuf)
|
||||
diff := len(c.obuf) - buflen
|
||||
if diff > 0 {
|
||||
c.obuf = c.obuf[:buflen]
|
||||
c.buf = c.bufferedSrc()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) isEOS() bool {
|
||||
return len(c.src)-1 <= c.idx
|
||||
}
|
||||
|
||||
func (c *Context) isNextEOS() bool {
|
||||
return len(c.src) <= c.idx+1
|
||||
}
|
||||
|
||||
func (c *Context) next() bool {
|
||||
return c.idx < c.size
|
||||
}
|
||||
|
||||
func (c *Context) source(s, e int) string {
|
||||
return string(c.src[s:e])
|
||||
}
|
||||
|
||||
func (c *Context) previousChar() rune {
|
||||
if c.idx > 0 {
|
||||
return c.src[c.idx-1]
|
||||
}
|
||||
return rune(0)
|
||||
}
|
||||
|
||||
func (c *Context) currentChar() rune {
|
||||
if c.size > c.idx {
|
||||
return c.src[c.idx]
|
||||
}
|
||||
return rune(0)
|
||||
}
|
||||
|
||||
func (c *Context) nextChar() rune {
|
||||
if c.size > c.idx+1 {
|
||||
return c.src[c.idx+1]
|
||||
}
|
||||
return rune(0)
|
||||
}
|
||||
|
||||
func (c *Context) repeatNum(r rune) int {
|
||||
cnt := 0
|
||||
for i := c.idx; i < c.size; i++ {
|
||||
if c.src[i] == r {
|
||||
cnt++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return cnt
|
||||
}
|
||||
|
||||
func (c *Context) progress(num int) {
|
||||
c.idx += num
|
||||
}
|
||||
|
||||
func (c *Context) existsBuffer() bool {
|
||||
return len(c.bufferedSrc()) != 0
|
||||
}
|
||||
|
||||
func (c *Context) isMultiLine() bool {
|
||||
return c.mstate != nil
|
||||
}
|
||||
|
||||
func (c *Context) bufferedSrc() []rune {
|
||||
src := c.buf[:c.notSpaceCharPos]
|
||||
if c.isMultiLine() {
|
||||
mstate := c.getMultiLineState()
|
||||
// remove end '\n' character and trailing empty lines.
|
||||
// https://yaml.org/spec/1.2.2/#8112-block-chomping-indicator
|
||||
if mstate.hasTrimAllEndNewlineOpt() {
|
||||
// If the '-' flag is specified, all trailing newline characters will be removed.
|
||||
src = []rune(strings.TrimRight(string(src), "\n"))
|
||||
} else if !mstate.hasKeepAllEndNewlineOpt() {
|
||||
// Normally, all but one of the trailing newline characters are removed.
|
||||
var newLineCharCount int
|
||||
for i := len(src) - 1; i >= 0; i-- {
|
||||
if src[i] == '\n' {
|
||||
newLineCharCount++
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
removedNewLineCharCount := newLineCharCount - 1
|
||||
for removedNewLineCharCount > 0 {
|
||||
src = []rune(strings.TrimSuffix(string(src), "\n"))
|
||||
removedNewLineCharCount--
|
||||
}
|
||||
}
|
||||
|
||||
// If the text ends with a space character, remove all of them.
|
||||
if mstate.hasTrimAllEndNewlineOpt() {
|
||||
src = []rune(strings.TrimRight(string(src), " "))
|
||||
}
|
||||
if string(src) == "\n" {
|
||||
// If the content consists only of a newline,
|
||||
// it can be considered as the document ending without any specified value,
|
||||
// so it is treated as an empty string.
|
||||
src = []rune{}
|
||||
}
|
||||
if mstate.hasKeepAllEndNewlineOpt() && len(src) == 0 {
|
||||
src = []rune{'\n'}
|
||||
}
|
||||
}
|
||||
return src
|
||||
}
|
||||
|
||||
func (c *Context) bufferedToken(pos *token.Position) *token.Token {
|
||||
if c.idx == 0 {
|
||||
return nil
|
||||
}
|
||||
source := c.bufferedSrc()
|
||||
if len(source) == 0 {
|
||||
c.buf = c.buf[:0] // clear value's buffer only.
|
||||
return nil
|
||||
}
|
||||
var tk *token.Token
|
||||
if c.isMultiLine() {
|
||||
tk = token.String(string(source), string(c.obuf), pos)
|
||||
} else {
|
||||
tk = token.New(string(source), string(c.obuf), pos)
|
||||
}
|
||||
c.setTokenTypeByPrevTag(tk)
|
||||
c.resetBuffer()
|
||||
return tk
|
||||
}
|
||||
|
||||
func (c *Context) setTokenTypeByPrevTag(tk *token.Token) {
|
||||
lastTk := c.lastToken()
|
||||
if lastTk == nil {
|
||||
return
|
||||
}
|
||||
if lastTk.Type != token.TagType {
|
||||
return
|
||||
}
|
||||
tag := token.ReservedTagKeyword(lastTk.Value)
|
||||
if _, exists := token.ReservedTagKeywordMap[tag]; !exists {
|
||||
tk.Type = token.StringType
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) lastToken() *token.Token {
|
||||
if len(c.tokens) != 0 {
|
||||
return c.tokens[len(c.tokens)-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
17
vendor/github.com/goccy/go-yaml/scanner/error.go
generated
vendored
Normal file
17
vendor/github.com/goccy/go-yaml/scanner/error.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
package scanner
|
||||
|
||||
import "github.com/goccy/go-yaml/token"
|
||||
|
||||
type InvalidTokenError struct {
|
||||
Token *token.Token
|
||||
}
|
||||
|
||||
func (e *InvalidTokenError) Error() string {
|
||||
return e.Token.Error
|
||||
}
|
||||
|
||||
func ErrInvalidToken(tk *token.Token) *InvalidTokenError {
|
||||
return &InvalidTokenError{
|
||||
Token: tk,
|
||||
}
|
||||
}
|
||||
1536
vendor/github.com/goccy/go-yaml/scanner/scanner.go
generated
vendored
Normal file
1536
vendor/github.com/goccy/go-yaml/scanner/scanner.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
113
vendor/github.com/goccy/go-yaml/stdlib_quote.go
generated
vendored
Normal file
113
vendor/github.com/goccy/go-yaml/stdlib_quote.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copied and trimmed down from https://github.com/golang/go/blob/e3769299cd3484e018e0e2a6e1b95c2b18ce4f41/src/strconv/quote.go
|
||||
// We want to use the standard library's private "quoteWith" function rather than write our own so that we get robust unicode support.
|
||||
// Every private function called by quoteWith was copied.
|
||||
// There are 2 modifications to simplify the code:
|
||||
// 1. The unicode.IsPrint function was substituted for the custom implementation of IsPrint
|
||||
// 2. All code paths reachable only when ASCIIonly or grphicOnly are set to true were removed.
|
||||
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
lowerhex = "0123456789abcdef"
|
||||
)
|
||||
|
||||
func quoteWith(s string, quote byte) string {
|
||||
return string(appendQuotedWith(make([]byte, 0, 3*len(s)/2), s, quote))
|
||||
}
|
||||
|
||||
func appendQuotedWith(buf []byte, s string, quote byte) []byte {
|
||||
// Often called with big strings, so preallocate. If there's quoting,
|
||||
// this is conservative but still helps a lot.
|
||||
if cap(buf)-len(buf) < len(s) {
|
||||
nBuf := make([]byte, len(buf), len(buf)+1+len(s)+1)
|
||||
copy(nBuf, buf)
|
||||
buf = nBuf
|
||||
}
|
||||
buf = append(buf, quote)
|
||||
for width := 0; len(s) > 0; s = s[width:] {
|
||||
r := rune(s[0])
|
||||
width = 1
|
||||
if r >= utf8.RuneSelf {
|
||||
r, width = utf8.DecodeRuneInString(s)
|
||||
}
|
||||
if width == 1 && r == utf8.RuneError {
|
||||
buf = append(buf, `\x`...)
|
||||
buf = append(buf, lowerhex[s[0]>>4])
|
||||
buf = append(buf, lowerhex[s[0]&0xF])
|
||||
continue
|
||||
}
|
||||
buf = appendEscapedRune(buf, r, quote)
|
||||
}
|
||||
buf = append(buf, quote)
|
||||
return buf
|
||||
}
|
||||
|
||||
func appendEscapedRune(buf []byte, r rune, quote byte) []byte {
|
||||
var runeTmp [utf8.UTFMax]byte
|
||||
// goccy/go-yaml patch on top of the standard library's appendEscapedRune function.
|
||||
//
|
||||
// We use this to implement the YAML single-quoted string, where the only escape sequence is '', which represents a single quote.
|
||||
// The below snippet from the standard library is for escaping e.g. \ with \\, which is not what we want for the single-quoted string.
|
||||
//
|
||||
// if r == rune(quote) || r == '\\' { // always backslashed
|
||||
// buf = append(buf, '\\')
|
||||
// buf = append(buf, byte(r))
|
||||
// return buf
|
||||
// }
|
||||
if r == rune(quote) {
|
||||
buf = append(buf, byte(r))
|
||||
buf = append(buf, byte(r))
|
||||
return buf
|
||||
}
|
||||
if unicode.IsPrint(r) {
|
||||
n := utf8.EncodeRune(runeTmp[:], r)
|
||||
buf = append(buf, runeTmp[:n]...)
|
||||
return buf
|
||||
}
|
||||
switch r {
|
||||
case '\a':
|
||||
buf = append(buf, `\a`...)
|
||||
case '\b':
|
||||
buf = append(buf, `\b`...)
|
||||
case '\f':
|
||||
buf = append(buf, `\f`...)
|
||||
case '\n':
|
||||
buf = append(buf, `\n`...)
|
||||
case '\r':
|
||||
buf = append(buf, `\r`...)
|
||||
case '\t':
|
||||
buf = append(buf, `\t`...)
|
||||
case '\v':
|
||||
buf = append(buf, `\v`...)
|
||||
default:
|
||||
switch {
|
||||
case r < ' ':
|
||||
buf = append(buf, `\x`...)
|
||||
buf = append(buf, lowerhex[byte(r)>>4])
|
||||
buf = append(buf, lowerhex[byte(r)&0xF])
|
||||
case r > utf8.MaxRune:
|
||||
r = 0xFFFD
|
||||
fallthrough
|
||||
case r < 0x10000:
|
||||
buf = append(buf, `\u`...)
|
||||
for s := 12; s >= 0; s -= 4 {
|
||||
buf = append(buf, lowerhex[r>>uint(s)&0xF])
|
||||
}
|
||||
default:
|
||||
buf = append(buf, `\U`...)
|
||||
for s := 28; s >= 0; s -= 4 {
|
||||
buf = append(buf, lowerhex[r>>uint(s)&0xF])
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf
|
||||
}
|
||||
128
vendor/github.com/goccy/go-yaml/struct.go
generated
vendored
Normal file
128
vendor/github.com/goccy/go-yaml/struct.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// StructTagName tag keyword for Marshal/Unmarshal
|
||||
StructTagName = "yaml"
|
||||
)
|
||||
|
||||
// StructField information for each the field in structure
|
||||
type StructField struct {
|
||||
FieldName string
|
||||
RenderName string
|
||||
AnchorName string
|
||||
AliasName string
|
||||
IsAutoAnchor bool
|
||||
IsAutoAlias bool
|
||||
IsOmitEmpty bool
|
||||
IsOmitZero bool
|
||||
IsFlow bool
|
||||
IsInline bool
|
||||
}
|
||||
|
||||
func getTag(field reflect.StructField) string {
|
||||
// If struct tag `yaml` exist, use that. If no `yaml`
|
||||
// exists, but `json` does, use that and try the best to
|
||||
// adhere to its rules
|
||||
tag := field.Tag.Get(StructTagName)
|
||||
if tag == "" {
|
||||
tag = field.Tag.Get(`json`)
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func structField(field reflect.StructField) *StructField {
|
||||
tag := getTag(field)
|
||||
fieldName := strings.ToLower(field.Name)
|
||||
options := strings.Split(tag, ",")
|
||||
if len(options) > 0 {
|
||||
if options[0] != "" {
|
||||
fieldName = options[0]
|
||||
}
|
||||
}
|
||||
sf := &StructField{
|
||||
FieldName: field.Name,
|
||||
RenderName: fieldName,
|
||||
}
|
||||
if len(options) > 1 {
|
||||
for _, opt := range options[1:] {
|
||||
switch {
|
||||
case opt == "omitempty":
|
||||
sf.IsOmitEmpty = true
|
||||
case opt == "omitzero":
|
||||
sf.IsOmitZero = true
|
||||
case opt == "flow":
|
||||
sf.IsFlow = true
|
||||
case opt == "inline":
|
||||
sf.IsInline = true
|
||||
case strings.HasPrefix(opt, "anchor"):
|
||||
anchor := strings.Split(opt, "=")
|
||||
if len(anchor) > 1 {
|
||||
sf.AnchorName = anchor[1]
|
||||
} else {
|
||||
sf.IsAutoAnchor = true
|
||||
}
|
||||
case strings.HasPrefix(opt, "alias"):
|
||||
alias := strings.Split(opt, "=")
|
||||
if len(alias) > 1 {
|
||||
sf.AliasName = alias[1]
|
||||
} else {
|
||||
sf.IsAutoAlias = true
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
return sf
|
||||
}
|
||||
|
||||
func isIgnoredStructField(field reflect.StructField) bool {
|
||||
if field.PkgPath != "" && !field.Anonymous {
|
||||
// private field
|
||||
return true
|
||||
}
|
||||
return getTag(field) == "-"
|
||||
}
|
||||
|
||||
type StructFieldMap map[string]*StructField
|
||||
|
||||
func (m StructFieldMap) isIncludedRenderName(name string) bool {
|
||||
for _, v := range m {
|
||||
if !v.IsInline && v.RenderName == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m StructFieldMap) hasMergeProperty() bool {
|
||||
for _, v := range m {
|
||||
if v.IsOmitEmpty && v.IsInline && v.IsAutoAlias {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func structFieldMap(structType reflect.Type) (StructFieldMap, error) {
|
||||
fieldMap := StructFieldMap{}
|
||||
renderNameMap := map[string]struct{}{}
|
||||
for i := 0; i < structType.NumField(); i++ {
|
||||
field := structType.Field(i)
|
||||
if isIgnoredStructField(field) {
|
||||
continue
|
||||
}
|
||||
sf := structField(field)
|
||||
if _, exists := renderNameMap[sf.RenderName]; exists {
|
||||
return nil, fmt.Errorf("duplicated struct field name %s", sf.RenderName)
|
||||
}
|
||||
fieldMap[sf.FieldName] = sf
|
||||
renderNameMap[sf.RenderName] = struct{}{}
|
||||
}
|
||||
return fieldMap, nil
|
||||
}
|
||||
1177
vendor/github.com/goccy/go-yaml/token/token.go
generated
vendored
Normal file
1177
vendor/github.com/goccy/go-yaml/token/token.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
13
vendor/github.com/goccy/go-yaml/validate.go
generated
vendored
Normal file
13
vendor/github.com/goccy/go-yaml/validate.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
package yaml
|
||||
|
||||
// StructValidator need to implement Struct method only
|
||||
// ( see https://pkg.go.dev/github.com/go-playground/validator/v10#Validate.Struct )
|
||||
type StructValidator interface {
|
||||
Struct(interface{}) error
|
||||
}
|
||||
|
||||
// FieldError need to implement StructField method only
|
||||
// ( see https://pkg.go.dev/github.com/go-playground/validator/v10#FieldError )
|
||||
type FieldError interface {
|
||||
StructField() string
|
||||
}
|
||||
357
vendor/github.com/goccy/go-yaml/yaml.go
generated
vendored
Normal file
357
vendor/github.com/goccy/go-yaml/yaml.go
generated
vendored
Normal file
@@ -0,0 +1,357 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/goccy/go-yaml/ast"
|
||||
"github.com/goccy/go-yaml/internal/errors"
|
||||
)
|
||||
|
||||
// BytesMarshaler interface may be implemented by types to customize their
|
||||
// behavior when being marshaled into a YAML document. The returned value
|
||||
// is marshaled in place of the original value implementing Marshaler.
|
||||
//
|
||||
// If an error is returned by MarshalYAML, the marshaling procedure stops
|
||||
// and returns with the provided error.
|
||||
type BytesMarshaler interface {
|
||||
MarshalYAML() ([]byte, error)
|
||||
}
|
||||
|
||||
// BytesMarshalerContext interface use BytesMarshaler with context.Context.
|
||||
type BytesMarshalerContext interface {
|
||||
MarshalYAML(context.Context) ([]byte, error)
|
||||
}
|
||||
|
||||
// InterfaceMarshaler interface has MarshalYAML compatible with github.com/go-yaml/yaml package.
|
||||
type InterfaceMarshaler interface {
|
||||
MarshalYAML() (interface{}, error)
|
||||
}
|
||||
|
||||
// InterfaceMarshalerContext interface use InterfaceMarshaler with context.Context.
|
||||
type InterfaceMarshalerContext interface {
|
||||
MarshalYAML(context.Context) (interface{}, error)
|
||||
}
|
||||
|
||||
// BytesUnmarshaler interface may be implemented by types to customize their
|
||||
// behavior when being unmarshaled from a YAML document.
|
||||
type BytesUnmarshaler interface {
|
||||
UnmarshalYAML([]byte) error
|
||||
}
|
||||
|
||||
// BytesUnmarshalerContext interface use BytesUnmarshaler with context.Context.
|
||||
type BytesUnmarshalerContext interface {
|
||||
UnmarshalYAML(context.Context, []byte) error
|
||||
}
|
||||
|
||||
// InterfaceUnmarshaler interface has UnmarshalYAML compatible with github.com/go-yaml/yaml package.
|
||||
type InterfaceUnmarshaler interface {
|
||||
UnmarshalYAML(func(interface{}) error) error
|
||||
}
|
||||
|
||||
// InterfaceUnmarshalerContext interface use InterfaceUnmarshaler with context.Context.
|
||||
type InterfaceUnmarshalerContext interface {
|
||||
UnmarshalYAML(context.Context, func(interface{}) error) error
|
||||
}
|
||||
|
||||
// NodeUnmarshaler interface is similar to BytesUnmarshaler but provide related AST node instead of raw YAML source.
|
||||
type NodeUnmarshaler interface {
|
||||
UnmarshalYAML(ast.Node) error
|
||||
}
|
||||
|
||||
// NodeUnmarshalerContext interface is similar to BytesUnmarshaler but provide related AST node instead of raw YAML source.
|
||||
type NodeUnmarshalerContext interface {
|
||||
UnmarshalYAML(context.Context, ast.Node) error
|
||||
}
|
||||
|
||||
// MapItem is an item in a MapSlice.
|
||||
type MapItem struct {
|
||||
Key, Value interface{}
|
||||
}
|
||||
|
||||
// MapSlice encodes and decodes as a YAML map.
|
||||
// The order of keys is preserved when encoding and decoding.
|
||||
type MapSlice []MapItem
|
||||
|
||||
// ToMap convert to map[interface{}]interface{}.
|
||||
func (s MapSlice) ToMap() map[interface{}]interface{} {
|
||||
v := map[interface{}]interface{}{}
|
||||
for _, item := range s {
|
||||
v[item.Key] = item.Value
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Marshal serializes the value provided into a YAML document. The structure
|
||||
// of the generated document will reflect the structure of the value itself.
|
||||
// Maps and pointers (to struct, string, int, etc) are accepted as the in value.
|
||||
//
|
||||
// Struct fields are only marshaled if they are exported (have an upper case
|
||||
// first letter), and are marshaled using the field name lowercased as the
|
||||
// default key. Custom keys may be defined via the "yaml" name in the field
|
||||
// tag: the content preceding the first comma is used as the key, and the
|
||||
// following comma-separated options are used to tweak the marshaling process.
|
||||
// Conflicting names result in a runtime error.
|
||||
//
|
||||
// The field tag format accepted is:
|
||||
//
|
||||
// `(...) yaml:"[<key>][,<flag1>[,<flag2>]]" (...)`
|
||||
//
|
||||
// The following flags are currently supported:
|
||||
//
|
||||
// omitempty Only include the field if it's not set to the zero
|
||||
// value for the type or to empty slices or maps.
|
||||
// Zero valued structs will be omitted if all their public
|
||||
// fields are zero, unless they implement an IsZero
|
||||
// method (see the IsZeroer interface type), in which
|
||||
// case the field will be included if that method returns true.
|
||||
// Note that this definition is slightly different from the Go's
|
||||
// encoding/json 'omitempty' definition. It combines some elements
|
||||
// of 'omitempty' and 'omitzero'. See https://github.com/goccy/go-yaml/issues/695.
|
||||
//
|
||||
// omitzero The omitzero tag behaves in the same way as the interpretation of the omitzero tag in the encoding/json library.
|
||||
// 1) If the field type has an "IsZero() bool" method, that will be used to determine whether the value is zero.
|
||||
// 2) Otherwise, the value is zero if it is the zero value for its type.
|
||||
//
|
||||
// flow Marshal using a flow style (useful for structs,
|
||||
// sequences and maps).
|
||||
//
|
||||
// inline Inline the field, which must be a struct or a map,
|
||||
// causing all of its fields or keys to be processed as if
|
||||
// they were part of the outer struct. For maps, keys must
|
||||
// not conflict with the yaml keys of other struct fields.
|
||||
//
|
||||
// anchor Marshal with anchor. If want to define anchor name explicitly, use anchor=name style.
|
||||
// Otherwise, if used 'anchor' name only, used the field name lowercased as the anchor name
|
||||
//
|
||||
// alias Marshal with alias. If want to define alias name explicitly, use alias=name style.
|
||||
// Otherwise, If omitted alias name and the field type is pointer type,
|
||||
// assigned anchor name automatically from same pointer address.
|
||||
//
|
||||
// In addition, if the key is "-", the field is ignored.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// type T struct {
|
||||
// F int `yaml:"a,omitempty"`
|
||||
// B int
|
||||
// }
|
||||
// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
|
||||
// yaml.Marshal(&T{F: 1}) // Returns "a: 1\nb: 0\n"
|
||||
func Marshal(v interface{}) ([]byte, error) {
|
||||
return MarshalWithOptions(v)
|
||||
}
|
||||
|
||||
// MarshalWithOptions serializes the value provided into a YAML document with EncodeOptions.
|
||||
func MarshalWithOptions(v interface{}, opts ...EncodeOption) ([]byte, error) {
|
||||
return MarshalContext(context.Background(), v, opts...)
|
||||
}
|
||||
|
||||
// MarshalContext serializes the value provided into a YAML document with context.Context and EncodeOptions.
|
||||
func MarshalContext(ctx context.Context, v interface{}, opts ...EncodeOption) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
if err := NewEncoder(&buf, opts...).EncodeContext(ctx, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// ValueToNode convert from value to ast.Node.
|
||||
func ValueToNode(v interface{}, opts ...EncodeOption) (ast.Node, error) {
|
||||
var buf bytes.Buffer
|
||||
node, err := NewEncoder(&buf, opts...).EncodeToNode(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// Unmarshal decodes the first document found within the in byte slice
|
||||
// and assigns decoded values into the out value.
|
||||
//
|
||||
// Struct fields are only unmarshalled if they are exported (have an
|
||||
// upper case first letter), and are unmarshalled using the field name
|
||||
// lowercased as the default key. Custom keys may be defined via the
|
||||
// "yaml" name in the field tag: the content preceding the first comma
|
||||
// is used as the key, and the following comma-separated options are
|
||||
// used to tweak the marshaling process (see Marshal).
|
||||
// Conflicting names result in a runtime error.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// type T struct {
|
||||
// F int `yaml:"a,omitempty"`
|
||||
// B int
|
||||
// }
|
||||
// var t T
|
||||
// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
|
||||
//
|
||||
// See the documentation of Marshal for the format of tags and a list of
|
||||
// supported tag options.
|
||||
func Unmarshal(data []byte, v interface{}) error {
|
||||
return UnmarshalWithOptions(data, v)
|
||||
}
|
||||
|
||||
// UnmarshalWithOptions decodes with DecodeOptions the first document found within the in byte slice
|
||||
// and assigns decoded values into the out value.
|
||||
func UnmarshalWithOptions(data []byte, v interface{}, opts ...DecodeOption) error {
|
||||
return UnmarshalContext(context.Background(), data, v, opts...)
|
||||
}
|
||||
|
||||
// UnmarshalContext decodes with context.Context and DecodeOptions.
|
||||
func UnmarshalContext(ctx context.Context, data []byte, v interface{}, opts ...DecodeOption) error {
|
||||
dec := NewDecoder(bytes.NewBuffer(data), opts...)
|
||||
if err := dec.DecodeContext(ctx, v); err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NodeToValue converts node to the value pointed to by v.
|
||||
func NodeToValue(node ast.Node, v interface{}, opts ...DecodeOption) error {
|
||||
var buf bytes.Buffer
|
||||
if err := NewDecoder(&buf, opts...).DecodeFromNode(node, v); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FormatError is a utility function that takes advantage of the metadata
|
||||
// stored in the errors returned by this package's parser.
|
||||
//
|
||||
// If the second argument `colored` is true, the error message is colorized.
|
||||
// If the third argument `inclSource` is true, the error message will
|
||||
// contain snippets of the YAML source that was used.
|
||||
func FormatError(e error, colored, inclSource bool) string {
|
||||
var yamlErr Error
|
||||
if errors.As(e, &yamlErr) {
|
||||
return yamlErr.FormatError(colored, inclSource)
|
||||
}
|
||||
|
||||
return e.Error()
|
||||
}
|
||||
|
||||
// YAMLToJSON convert YAML bytes to JSON.
|
||||
func YAMLToJSON(bytes []byte) ([]byte, error) {
|
||||
var v interface{}
|
||||
if err := UnmarshalWithOptions(bytes, &v, UseOrderedMap()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := MarshalWithOptions(v, JSON())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// JSONToYAML convert JSON bytes to YAML.
|
||||
func JSONToYAML(bytes []byte) ([]byte, error) {
|
||||
var v interface{}
|
||||
if err := UnmarshalWithOptions(bytes, &v, UseOrderedMap()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
var (
|
||||
globalCustomMarshalerMu sync.Mutex
|
||||
globalCustomUnmarshalerMu sync.Mutex
|
||||
globalCustomMarshalerMap = map[reflect.Type]func(context.Context, interface{}) ([]byte, error){}
|
||||
globalCustomUnmarshalerMap = map[reflect.Type]func(context.Context, interface{}, []byte) error{}
|
||||
)
|
||||
|
||||
// RegisterCustomMarshaler overrides any encoding process for the type specified in generics.
|
||||
// If you want to switch the behavior for each encoder, use `CustomMarshaler` defined as EncodeOption.
|
||||
//
|
||||
// NOTE: If type T implements MarshalYAML for pointer receiver, the type specified in RegisterCustomMarshaler must be *T.
|
||||
// If RegisterCustomMarshaler and CustomMarshaler of EncodeOption are specified for the same type,
|
||||
// the CustomMarshaler specified in EncodeOption takes precedence.
|
||||
func RegisterCustomMarshaler[T any](marshaler func(T) ([]byte, error)) {
|
||||
globalCustomMarshalerMu.Lock()
|
||||
defer globalCustomMarshalerMu.Unlock()
|
||||
|
||||
var typ T
|
||||
globalCustomMarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}) ([]byte, error) {
|
||||
return marshaler(v.(T))
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterCustomMarshalerContext overrides any encoding process for the type specified in generics.
|
||||
// Similar to RegisterCustomMarshalerContext, but allows passing a context to the unmarshaler function.
|
||||
func RegisterCustomMarshalerContext[T any](marshaler func(context.Context, T) ([]byte, error)) {
|
||||
globalCustomMarshalerMu.Lock()
|
||||
defer globalCustomMarshalerMu.Unlock()
|
||||
|
||||
var typ T
|
||||
globalCustomMarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}) ([]byte, error) {
|
||||
return marshaler(ctx, v.(T))
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterCustomUnmarshaler overrides any decoding process for the type specified in generics.
|
||||
// If you want to switch the behavior for each decoder, use `CustomUnmarshaler` defined as DecodeOption.
|
||||
//
|
||||
// NOTE: If RegisterCustomUnmarshaler and CustomUnmarshaler of DecodeOption are specified for the same type,
|
||||
// the CustomUnmarshaler specified in DecodeOption takes precedence.
|
||||
func RegisterCustomUnmarshaler[T any](unmarshaler func(*T, []byte) error) {
|
||||
globalCustomUnmarshalerMu.Lock()
|
||||
defer globalCustomUnmarshalerMu.Unlock()
|
||||
|
||||
var typ *T
|
||||
globalCustomUnmarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}, b []byte) error {
|
||||
return unmarshaler(v.(*T), b)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterCustomUnmarshalerContext overrides any decoding process for the type specified in generics.
|
||||
// Similar to RegisterCustomUnmarshalerContext, but allows passing a context to the unmarshaler function.
|
||||
func RegisterCustomUnmarshalerContext[T any](unmarshaler func(context.Context, *T, []byte) error) {
|
||||
globalCustomUnmarshalerMu.Lock()
|
||||
defer globalCustomUnmarshalerMu.Unlock()
|
||||
|
||||
var typ *T
|
||||
globalCustomUnmarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}, b []byte) error {
|
||||
return unmarshaler(ctx, v.(*T), b)
|
||||
}
|
||||
}
|
||||
|
||||
// RawMessage is a raw encoded YAML value. It implements [BytesMarshaler] and
|
||||
// [BytesUnmarshaler] and can be used to delay YAML decoding or precompute a YAML
|
||||
// encoding.
|
||||
// It also implements [json.Marshaler] and [json.Unmarshaler].
|
||||
//
|
||||
// This is similar to [json.RawMessage] in the stdlib.
|
||||
type RawMessage []byte
|
||||
|
||||
func (m RawMessage) MarshalYAML() ([]byte, error) {
|
||||
if m == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *RawMessage) UnmarshalYAML(dt []byte) error {
|
||||
if m == nil {
|
||||
return errors.New("yaml.RawMessage: UnmarshalYAML on nil pointer")
|
||||
}
|
||||
*m = append((*m)[0:0], dt...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RawMessage) UnmarshalJSON(b []byte) error {
|
||||
return m.UnmarshalYAML(b)
|
||||
}
|
||||
|
||||
func (m RawMessage) MarshalJSON() ([]byte, error) {
|
||||
return YAMLToJSON(m)
|
||||
}
|
||||
75
vendor/github.com/hanwen/go-fuse/v2/AUTHORS
generated
vendored
Normal file
75
vendor/github.com/hanwen/go-fuse/v2/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
Adam Goode <agoode@google.com>
|
||||
Adam H. Leventhal <adam.leventhal@gmail.com>
|
||||
Alex Fishman <alex@fuse-t.org>
|
||||
Amir Hardon <ahardon@gmail.com>
|
||||
Andrew Chambers <ac@acha.ninja>
|
||||
Brandon Duffany <brandon@buildbuddy.io>
|
||||
C.U <github@wmchris.de>
|
||||
Chris Marget <cmarget@mutualink.net>
|
||||
Daniel Martí <mvdan@mvdan.cc>
|
||||
Dmitriy Smotrov <dsxack@gmail.com>
|
||||
Dustin Oprea <myselfasunder@gmail.com>
|
||||
Ed Schouten <ed.schouten@prodrive-technologies.com>
|
||||
Eliot Courtney <edcourtney@google.com>
|
||||
Fazlul Shahriar <fshahriar@gmail.com>
|
||||
Frederick Akalin <akalin@gmail.com>
|
||||
Garret Kelly <gdk@google.com>
|
||||
Glonee <glonee@foxmail.com>
|
||||
Google Inc.
|
||||
Grant Monroe <grant@tnarg.com>
|
||||
Haitao Li <lihaitao@gmail.com>
|
||||
Han-Wen Nienhuys <hanwenn@gmail.com>
|
||||
Henry Wang <henwang@amazon.com>
|
||||
Ivan Krasin <imkrasin@gmail.com>
|
||||
Ivan Volosyuk <ivan.volosyuk@gmail.com>
|
||||
Jakob Unterwurzacher <jakobunt@gmail.com>
|
||||
James D. Nurmi <james@abneptis.com>
|
||||
Jan Pfeifer <janpf@google.com>
|
||||
Jeff <leterip@me.com>
|
||||
Jeff Hodges <jeff@somethingsimilar.com>
|
||||
Jille Timmermans <jille@quis.cx>
|
||||
Johannes Brüderl <johannes.bruederl@gmail.com>
|
||||
Jonathon Reinhart <Jonathon.Reinhart@gmail.com>
|
||||
Kaoet Ibe <kaoet.ibe@outlook.com>
|
||||
Kirill Smelkov <kirr@nexedi.com>
|
||||
Kohei Tokunaga <ktokunaga.mail@gmail.com>
|
||||
Levin Zimmermann <levin.zimmermann@nexedi.com>
|
||||
Logan Hanks <logan@bitcasa.com>
|
||||
Lucas Manning <lucas.manning21@gmail.com>
|
||||
M. J. Fromberger <michael.j.fromberger@gmail.com>
|
||||
Manuel Klimek <klimek@google.com>
|
||||
Maria Shaldibina <mshaldibina@pivotal.io>
|
||||
Mark Karpeles <magicaltux@gmail.com>
|
||||
Mike Gray <mike@mikegray.org>
|
||||
Natalie Fioretti <naadl.93+github@gmail.com>
|
||||
Nick Cooper <gh@smoogle.org>
|
||||
Nick Craig-Wood <nick@craig-wood.com>
|
||||
OneOfOne <oneofone@gmail.com>
|
||||
Orivej Desh <orivej@gmx.fr>
|
||||
Patrick Crosby <pcrosby@gmail.com>
|
||||
Paul Jolly <paul@myitcv.org.uk>
|
||||
Paul Warren <paul.warren@emc.com>
|
||||
Rueian <rueiancsie@gmail.com>
|
||||
Ryan Guest <ryanguest@gmail.com>
|
||||
Ryan Lamore <rlamore@salesforce.com>
|
||||
Sebastien Binet <binet@cern.ch>
|
||||
Shayan Pooya <shayan@arista.com>
|
||||
Stavros Panakakis <stavrospanakakis@gmail.com>
|
||||
Tamas Kerecsen <kerecsen@gmail.com>
|
||||
Tiziano Santoro <tzn@google.com>
|
||||
Tommy Lindgren <tommy.lindgren@gmail.com>
|
||||
Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
|
||||
Valient Gough <vgough@pobox.com>
|
||||
WeidiDeng <weidi_deng@icloud.com>
|
||||
Xiaoyi <ashi009@users.noreply.github.com>
|
||||
Yasin Turan <turyasin@amazon.com>
|
||||
Yongwoo Park <nnnlife@gmail.com>
|
||||
Yufeng Cheng <chengyufeng@megvii.com>
|
||||
ZheNing Hu <adlternative@gmail.com>
|
||||
Zoey Greer <zoey@buildbuddy.io>
|
||||
abitduck <abitduck@hotmail.com>
|
||||
companycy <companycy@gmail.com>
|
||||
hotaery <626910647@qq.com>
|
||||
lch <lchopn@gmail.com>
|
||||
midchildan <git@midchildan.org>
|
||||
sunjiapeng <782615313@qq.com>
|
||||
30
vendor/github.com/hanwen/go-fuse/v2/LICENSE
generated
vendored
Normal file
30
vendor/github.com/hanwen/go-fuse/v2/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
New BSD License
|
||||
|
||||
Copyright (c) 2010 the Go-FUSE 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 Ivan Krasin 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.
|
||||
|
||||
50
vendor/github.com/hanwen/go-fuse/v2/fs/README.md
generated
vendored
Normal file
50
vendor/github.com/hanwen/go-fuse/v2/fs/README.md
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
|
||||
Objective
|
||||
=========
|
||||
|
||||
A high-performance FUSE API that minimizes pitfalls with writing
|
||||
correct filesystems.
|
||||
|
||||
Decisions
|
||||
=========
|
||||
|
||||
* Nodes contain references to their children. This is useful
|
||||
because most filesystems will need to construct tree-like
|
||||
structures.
|
||||
|
||||
* Nodes contain references to their parents. As a result, we can
|
||||
derive the path for each Inode, and there is no need for a
|
||||
separate PathFS.
|
||||
|
||||
* Nodes can be "persistent", meaning their lifetime is not under
|
||||
control of the kernel. This is useful for constructing FS trees
|
||||
in advance, rather than driven by LOOKUP.
|
||||
|
||||
* The NodeID (used for communicating with the kernel, not to be
|
||||
confused with the inode number reported by `ls -i`) is generated
|
||||
internally and immutable for an Inode. This avoids any races
|
||||
between LOOKUP, NOTIFY and FORGET.
|
||||
|
||||
* The mode of an Inode is defined on creation. Files cannot change
|
||||
type during their lifetime. This also prevents the common error
|
||||
of forgetting to return the filetype in Lookup/GetAttr.
|
||||
|
||||
* No global treelock, to ensure scalability.
|
||||
|
||||
* Support for hard links. libfuse doesn't support this in the
|
||||
high-level API. Extra care for race conditions is needed when
|
||||
looking up the same file through different paths.
|
||||
|
||||
* do not issue Notify{Entry,Delete} as part of
|
||||
AddChild/RmChild/MvChild: because NodeIDs are unique and
|
||||
immutable, there is no confusion about which nodes are
|
||||
invalidated, and the notification doesn't have to happen under
|
||||
lock.
|
||||
|
||||
* Directory reading uses the FileHandles as well, the API for read
|
||||
is one DirEntry at a time. FileHandles may implement seeking, and we
|
||||
call the Seek if we see Offsets change in the incoming request.
|
||||
|
||||
* Method names are based on syscall names. Where there is no
|
||||
syscall (eg. "open directory"), we bias towards writing
|
||||
everything together (Opendir)
|
||||
822
vendor/github.com/hanwen/go-fuse/v2/fs/api.go
generated
vendored
Normal file
822
vendor/github.com/hanwen/go-fuse/v2/fs/api.go
generated
vendored
Normal file
@@ -0,0 +1,822 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package fs provides infrastructure to build tree-organized filesystems.
|
||||
//
|
||||
// # Structure of a file system implementation
|
||||
//
|
||||
// To create a file system, you should first define types for the
|
||||
// nodes of the file system tree.
|
||||
//
|
||||
// type myNode struct {
|
||||
// fs.Inode
|
||||
// }
|
||||
//
|
||||
// // Node types must be InodeEmbedders
|
||||
// var _ = (fs.InodeEmbedder)((*myNode)(nil))
|
||||
//
|
||||
// // Node types should implement some file system operations, eg. Lookup
|
||||
// var _ = (fs.NodeLookuper)((*myNode)(nil))
|
||||
//
|
||||
// func (n *myNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
|
||||
// ops := myNode{}
|
||||
// out.Mode = 0755
|
||||
// out.Size = 42
|
||||
// return n.NewInode(ctx, &ops, fs.StableAttr{Mode: syscall.S_IFREG}), 0
|
||||
// }
|
||||
//
|
||||
// The method names are inspired on the system call names, so we have
|
||||
// Listxattr rather than ListXAttr.
|
||||
//
|
||||
// the file system is mounted by calling mount on the root of the tree,
|
||||
//
|
||||
// server, err := fs.Mount("/tmp/mnt", &myNode{}, &fs.Options{})
|
||||
// ..
|
||||
// // start serving the file system
|
||||
// server.Wait()
|
||||
//
|
||||
// # Error handling
|
||||
//
|
||||
// All error reporting must use the syscall.Errno type. This is an
|
||||
// integer with predefined error codes, where the value 0 (`OK`)
|
||||
// should be used to indicate success.
|
||||
//
|
||||
// # File system concepts
|
||||
//
|
||||
// The FUSE API is very similar to Linux' internal VFS API for
|
||||
// defining file systems in the kernel. It is therefore useful to
|
||||
// understand some terminology.
|
||||
//
|
||||
// File content: the raw bytes that we store inside regular files.
|
||||
//
|
||||
// Path: a /-separated string path that describes location of a node
|
||||
// in the file system tree. For example
|
||||
//
|
||||
// dir1/file
|
||||
//
|
||||
// describes path root → dir1 → file.
|
||||
//
|
||||
// There can be several paths leading from tree root to a particular node,
|
||||
// known as hard-linking, for example
|
||||
//
|
||||
// root
|
||||
// / \
|
||||
// dir1 dir2
|
||||
// \ /
|
||||
// file
|
||||
//
|
||||
// Inode: ("index node") points to the file content, and stores
|
||||
// metadata (size, timestamps) about a file or directory. Each
|
||||
// inode has a type (directory, symlink, regular file, etc.) and
|
||||
// an identity (a 64-bit number, unique to the file
|
||||
// system). Directories can have children.
|
||||
//
|
||||
// The inode in the kernel is represented in Go-FUSE as the Inode
|
||||
// type.
|
||||
//
|
||||
// While common OS APIs are phrased in terms of paths (strings), the
|
||||
// precise semantics of a file system are better described in terms of
|
||||
// Inodes. This allows us to specify what happens in corner cases,
|
||||
// such as writing data to deleted files.
|
||||
//
|
||||
// File descriptor: a handle returned to opening a file. File
|
||||
// descriptors always refer to a single inode.
|
||||
//
|
||||
// Dentry: a dirent maps (parent inode number, name string) tuple to
|
||||
// child inode, thus representing a parent/child relation (or the
|
||||
// absense thereof). Dentries do not have an equivalent type inside
|
||||
// Go-FUSE, but the result of Lookup operation essentially is a
|
||||
// dentry, which the kernel puts in a cache.
|
||||
//
|
||||
// # Kernel caching
|
||||
//
|
||||
// The kernel caches several pieces of information from the FUSE process:
|
||||
//
|
||||
// 1. File contents: enabled with the fuse.FOPEN_KEEP_CACHE return flag
|
||||
// in Open, manipulated with ReadCache and WriteCache, and invalidated
|
||||
// with Inode.NotifyContent
|
||||
//
|
||||
// 2. File Attributes (size, mtime, etc.): controlled with the
|
||||
// attribute timeout fields in fuse.AttrOut and fuse.EntryOut, which
|
||||
// get be populated from Getattr and Lookup
|
||||
//
|
||||
// 3. Dentries (parent/child relations in the FS tree):
|
||||
// controlled with the timeout fields in fuse.EntryOut, and
|
||||
// invalidated with Inode.NotifyEntry and Inode.NotifyDelete.
|
||||
//
|
||||
// Without entry timeouts, every operation on file "a/b/c"
|
||||
// must first do lookups for "a", "a/b" and "a/b/c", which is
|
||||
// expensive because of context switches between the kernel and the
|
||||
// FUSE process.
|
||||
//
|
||||
// Unsuccessful entry lookups can also be cached by setting an entry
|
||||
// timeout when Lookup returns ENOENT.
|
||||
//
|
||||
// The libfuse C library specifies 1 second timeouts for both
|
||||
// attribute and directory entries, but no timeout for negative
|
||||
// entries. by default. This can be achieve in go-fuse by setting
|
||||
// options on mount, eg.
|
||||
//
|
||||
// sec := time.Second
|
||||
// opts := fs.Options{
|
||||
// EntryTimeout: &sec,
|
||||
// AttrTimeout: &sec,
|
||||
// }
|
||||
//
|
||||
// # Interrupts
|
||||
//
|
||||
// If the process accessing a FUSE file system is interrupted, the
|
||||
// kernel sends an interrupt message, which cancels the context passed
|
||||
// to the NodeXxxxx methods. If the file system chooses to honor this
|
||||
// cancellation, the method must return [syscall.EINTR]. All unmasked
|
||||
// signals generate an interrupt. In particular, the SIGURG signal
|
||||
// (which the Go runtime uses for managing goroutine preemption) also
|
||||
// generates an interrupt.
|
||||
//
|
||||
// # Locking
|
||||
//
|
||||
// Locks for networked filesystems are supported through the suite of
|
||||
// Getlk, Setlk and Setlkw methods. They alllow locks on regions of
|
||||
// regular files.
|
||||
//
|
||||
// # Parallelism
|
||||
//
|
||||
// The VFS layer in the kernel is optimized to be highly parallel, and
|
||||
// this parallelism also affects FUSE file systems: many FUSE
|
||||
// operations can run in parallel, and this invites race
|
||||
// conditions. It is strongly recommended to test your FUSE file
|
||||
// system issuing file operations in parallel, and using the race
|
||||
// detector to weed out data races.
|
||||
//
|
||||
// # Deadlocks
|
||||
//
|
||||
// The Go runtime multiplexes Goroutines onto operating system
|
||||
// threads, and makes assumptions that some system calls do not
|
||||
// block. When accessing a file system from the same process that
|
||||
// serves the file system (e.g. in unittests), this can lead to
|
||||
// deadlocks, especially when GOMAXPROCS=1, when the Go runtime
|
||||
// assumes a system call does not block, but actually is served by the
|
||||
// Go-FUSE process.
|
||||
//
|
||||
// The following deadlocks are known:
|
||||
//
|
||||
// 1. Spawning a subprocess uses a fork/exec sequence: the process
|
||||
// forks itself into a parent and child. The parent waits for the
|
||||
// child to signal that the exec failed or succeeded, while the child
|
||||
// prepares for calling exec(). Any setup step in the child that
|
||||
// triggers a FUSE request can cause a deadlock.
|
||||
//
|
||||
// 1a. If the subprocess has a directory specified, the child will
|
||||
// chdir into that directory. This generates an ACCESS operation on
|
||||
// the directory.
|
||||
//
|
||||
// This deadlock can be avoided by disabling the ACCESS
|
||||
// operation: return syscall.ENOSYS in the Access implementation, and
|
||||
// ensure it is triggered called before initiating the subprocess.
|
||||
//
|
||||
// 1b. If the subprocess inherits files, the child process uses dup3()
|
||||
// to remap file descriptors. If the destination fd happens to be
|
||||
// backed by Go-FUSE, the dup3() call will implicitly close the fd,
|
||||
// generating a FLUSH operation, eg.
|
||||
//
|
||||
// f1, err := os.Open("/fusemnt/file1")
|
||||
// // f1.Fd() == 3
|
||||
// f2, err := os.Open("/fusemnt/file1")
|
||||
// // f2.Fd() == 4
|
||||
//
|
||||
// cmd := exec.Command("/bin/true")
|
||||
// cmd.ExtraFiles = []*os.File{f2}
|
||||
// // f2 (fd 4) is moved to fd 3. Deadlocks with GOMAXPROCS=1.
|
||||
// cmd.Start()
|
||||
//
|
||||
// This deadlock can be avoided by ensuring that file descriptors
|
||||
// pointing into FUSE mounts and file descriptors passed into
|
||||
// subprocesses do not overlap, e.g. inserting the following before
|
||||
// the above example:
|
||||
//
|
||||
// for {
|
||||
// f, _ := os.Open("/dev/null")
|
||||
// defer f.Close()
|
||||
// if f.Fd() > 3 {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// The library tries to reserve fd 3, because FUSE mounts are created
|
||||
// by calling "fusermount" with an inherited file descriptor, but the
|
||||
// same problem may occur for other file descriptors.
|
||||
//
|
||||
// 1c. If the executable is on the FUSE mount. In this case, the child
|
||||
// calls exec, which reads the file to execute, which triggers an OPEN
|
||||
// opcode. This can be worked around by invoking the subprocess
|
||||
// through a wrapper, eg `bash -c file/on/fuse-mount`.
|
||||
//
|
||||
// 2. The Go runtime uses the epoll system call to understand which
|
||||
// goroutines can respond to I/O. The runtime assumes that epoll does
|
||||
// not block, but if files are on a FUSE filesystem, the kernel will
|
||||
// generate a POLL operation. To prevent this from happening, Go-FUSE
|
||||
// disables the POLL opcode on mount. To ensure this has happened, call
|
||||
// WaitMount.
|
||||
//
|
||||
// 3. Memory mapping a file served by FUSE. Accessing the mapped
|
||||
// memory generates a page fault, which blocks the OS thread running
|
||||
// the goroutine.
|
||||
//
|
||||
// # Dynamically discovered file systems
|
||||
//
|
||||
// File system data usually cannot fit all in RAM, so the kernel must
|
||||
// discover the file system dynamically: as you are entering and list
|
||||
// directory contents, the kernel asks the FUSE server about the files
|
||||
// and directories you are busy reading/writing, and forgets parts of
|
||||
// your file system when it is low on memory.
|
||||
//
|
||||
// The two important operations for dynamic file systems are:
|
||||
// 1. Lookup, part of the NodeLookuper interface for discovering
|
||||
// individual children of directories, and 2. Readdir, part of the
|
||||
// NodeReaddirer interface for listing the contents of a directory.
|
||||
//
|
||||
// # Static in-memory file systems
|
||||
//
|
||||
// For small, read-only file systems, getting the locking mechanics of
|
||||
// Lookup correct is tedious, so Go-FUSE provides a feature to
|
||||
// simplify building such file systems.
|
||||
//
|
||||
// Instead of discovering the FS tree on the fly, you can construct
|
||||
// the entire tree from an OnAdd method. Then, that in-memory tree
|
||||
// structure becomes the source of truth. This means that Go-FUSE must
|
||||
// remember Inodes even if the kernel is no longer interested in
|
||||
// them. This is done by instantiating "persistent" inodes from the
|
||||
// OnAdd method of the root node. See the ZipFS example for a
|
||||
// runnable example of how to do this.
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
)
|
||||
|
||||
// InodeEmbedder is an interface for structs that embed Inode.
|
||||
//
|
||||
// InodeEmbedder objects usually should implement some of the NodeXxxx
|
||||
// interfaces, to provide user-defined file system behaviors.
|
||||
//
|
||||
// In general, if an InodeEmbedder does not implement specific
|
||||
// filesystem methods, the filesystem will react as if it is a
|
||||
// read-only filesystem with a predefined tree structure.
|
||||
type InodeEmbedder interface {
|
||||
// inode is used internally to link Inode to a Node.
|
||||
//
|
||||
// See Inode() for the public API to retrieve an inode from Node.
|
||||
embed() *Inode
|
||||
|
||||
// EmbeddedInode returns a pointer to the embedded inode.
|
||||
EmbeddedInode() *Inode
|
||||
}
|
||||
|
||||
// Statfs implements statistics for the filesystem that holds this
|
||||
// Inode. If not defined, the `out` argument will zeroed with an OK
|
||||
// result. This is because OSX filesystems must Statfs, or the mount
|
||||
// will not work.
|
||||
type NodeStatfser interface {
|
||||
Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno
|
||||
}
|
||||
|
||||
// Access should return if the caller can access the file with the
|
||||
// given mode. This is used for two purposes: to determine if a user
|
||||
// may enter a directory, and to implement the access system
|
||||
// call. In the latter case, the context has data about the real
|
||||
// UID. For example, a root-SUID binary called by user susan gets the
|
||||
// UID and GID for susan here.
|
||||
//
|
||||
// If not defined, a default implementation will check traditional
|
||||
// unix permissions of the Getattr result agains the caller. If access
|
||||
// permissions must be obeyed precisely, the filesystem should return
|
||||
// permissions from GetAttr/Lookup, and set [Options.NullPermissions].
|
||||
// Without [Options.NullPermissions], a missing permission (mode =
|
||||
// 0000) is interpreted as 0755 for directories, and chdir is always
|
||||
// allowed.
|
||||
type NodeAccesser interface {
|
||||
Access(ctx context.Context, mask uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// GetAttr reads attributes for an Inode. The library will ensure that
|
||||
// Mode and Ino are set correctly. For files that are not opened with
|
||||
// FOPEN_DIRECTIO, Size should be set so it can be read correctly. If
|
||||
// returning zeroed permissions, the default behavior is to change the
|
||||
// mode of 0755 (directory) or 0644 (files). This can be switched off
|
||||
// with the Options.NullPermissions setting. If blksize is unset, 4096
|
||||
// is assumed, and the 'blocks' field is set accordingly. The 'f'
|
||||
// argument is provided for consistency, however, in practice the
|
||||
// kernel never sends a file handle, even if the Getattr call
|
||||
// originated from an fstat system call.
|
||||
type NodeGetattrer interface {
|
||||
Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno
|
||||
}
|
||||
|
||||
// SetAttr sets attributes for an Inode. Default is to return ENOTSUP.
|
||||
type NodeSetattrer interface {
|
||||
Setattr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno
|
||||
}
|
||||
|
||||
// OnAdd is called when this InodeEmbedder is initialized.
|
||||
type NodeOnAdder interface {
|
||||
OnAdd(ctx context.Context)
|
||||
}
|
||||
|
||||
// Getxattr should read data for the given attribute into
|
||||
// `dest` and return the number of bytes. If `dest` is too
|
||||
// small, it should return ERANGE and the size of the attribute.
|
||||
// If not defined, Getxattr will return ENOATTR.
|
||||
type NodeGetxattrer interface {
|
||||
Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno)
|
||||
}
|
||||
|
||||
// Setxattr should store data for the given attribute. See
|
||||
// setxattr(2) for information about flags.
|
||||
// If not defined, Setxattr will return ENOATTR.
|
||||
type NodeSetxattrer interface {
|
||||
Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// Removexattr should delete the given attribute.
|
||||
// If not defined, Removexattr will return ENOATTR.
|
||||
type NodeRemovexattrer interface {
|
||||
Removexattr(ctx context.Context, attr string) syscall.Errno
|
||||
}
|
||||
|
||||
// Listxattr should read all attributes (null terminated) into
|
||||
// `dest`. If the `dest` buffer is too small, it should return ERANGE
|
||||
// and the correct size. If not defined, return an empty list and
|
||||
// success.
|
||||
type NodeListxattrer interface {
|
||||
Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno)
|
||||
}
|
||||
|
||||
// Readlink reads the content of a symlink.
|
||||
type NodeReadlinker interface {
|
||||
Readlink(ctx context.Context) ([]byte, syscall.Errno)
|
||||
}
|
||||
|
||||
// Open opens an Inode (of regular file type) for reading. It
|
||||
// is optional but recommended to return a FileHandle.
|
||||
type NodeOpener interface {
|
||||
Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// Reads data from a file. The data should be returned as
|
||||
// ReadResult, which may be constructed from the incoming
|
||||
// `dest` buffer. If the file was opened without FileHandle,
|
||||
// the FileHandle argument here is nil. The default
|
||||
// implementation forwards to the FileHandle.
|
||||
type NodeReader interface {
|
||||
Read(ctx context.Context, f FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno)
|
||||
}
|
||||
|
||||
// Writes the data into the file handle at given offset. After
|
||||
// returning, the data will be reused and may not referenced.
|
||||
// The default implementation forwards to the FileHandle.
|
||||
type NodeWriter interface {
|
||||
Write(ctx context.Context, f FileHandle, data []byte, off int64) (written uint32, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// Fsync is a signal to ensure writes to the Inode are flushed
|
||||
// to stable storage.
|
||||
type NodeFsyncer interface {
|
||||
Fsync(ctx context.Context, f FileHandle, flags uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// Flush is called for the close(2) call on a file descriptor. In case
|
||||
// of a descriptor that was duplicated using dup(2), it may be called
|
||||
// more than once for the same FileHandle. The default implementation
|
||||
// forwards to the FileHandle, or if the handle does not support
|
||||
// FileFlusher, returns OK.
|
||||
type NodeFlusher interface {
|
||||
Flush(ctx context.Context, f FileHandle) syscall.Errno
|
||||
}
|
||||
|
||||
// This is called to before a FileHandle is forgotten. The
|
||||
// kernel ignores the return value of this method,
|
||||
// so any cleanup that requires specific synchronization or
|
||||
// could fail with I/O errors should happen in Flush instead.
|
||||
// The default implementation forwards to the FileHandle.
|
||||
type NodeReleaser interface {
|
||||
Release(ctx context.Context, f FileHandle) syscall.Errno
|
||||
|
||||
// TODO - what about ReleaseIn?
|
||||
}
|
||||
|
||||
// Allocate preallocates space for future writes, so they will
|
||||
// never encounter ESPACE.
|
||||
type NodeAllocater interface {
|
||||
Allocate(ctx context.Context, f FileHandle, off uint64, size uint64, mode uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// CopyFileRange copies data between sections of two files,
|
||||
// without the data having to pass through the calling process.
|
||||
type NodeCopyFileRanger interface {
|
||||
CopyFileRange(ctx context.Context, fhIn FileHandle,
|
||||
offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
|
||||
len uint64, flags uint64) (uint32, syscall.Errno)
|
||||
|
||||
// Ugh. should have been called Copyfilerange
|
||||
}
|
||||
|
||||
type NodeStatxer interface {
|
||||
Statx(ctx context.Context, f FileHandle, flags uint32, mask uint32, out *fuse.StatxOut) syscall.Errno
|
||||
}
|
||||
|
||||
// Lseek is used to implement holes: it should return the
|
||||
// first offset beyond `off` where there is data (SEEK_DATA)
|
||||
// or where there is a hole (SEEK_HOLE).
|
||||
type NodeLseeker interface {
|
||||
Lseek(ctx context.Context, f FileHandle, Off uint64, whence uint32) (uint64, syscall.Errno)
|
||||
}
|
||||
|
||||
// Getlk returns locks that would conflict with the given input
|
||||
// lock. If no locks conflict, the output has type L_UNLCK. See
|
||||
// fcntl(2) for more information.
|
||||
// If not defined, returns ENOTSUP
|
||||
type NodeGetlker interface {
|
||||
Getlk(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) syscall.Errno
|
||||
}
|
||||
|
||||
// Setlk obtains a lock on a file, or fail if the lock could not
|
||||
// obtained. See fcntl(2) for more information. If not defined,
|
||||
// returns ENOTSUP
|
||||
type NodeSetlker interface {
|
||||
Setlk(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// Setlkw obtains a lock on a file, waiting if necessary. See fcntl(2)
|
||||
// for more information. If not defined, returns ENOTSUP
|
||||
type NodeSetlkwer interface {
|
||||
Setlkw(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// Ioctl implements an ioctl on an open file.
|
||||
type NodeIoctler interface {
|
||||
Ioctl(ctx context.Context, f FileHandle, cmd uint32, arg uint64, input []byte, output []byte) (result int32, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// OnForget is called when the node becomes unreachable. This can
|
||||
// happen because the kernel issues a FORGET request,
|
||||
// ForgetPersistent() is called on the inode, the last child of the
|
||||
// directory disappears, or (for the root node) unmounting the file
|
||||
// system. Implementers must make sure that the inode cannot be
|
||||
// revived concurrently by a LOOKUP call. Modifying the tree using
|
||||
// RmChild and AddChild can also trigger a spurious OnForget; use
|
||||
// MvChild instead.
|
||||
type NodeOnForgetter interface {
|
||||
OnForget()
|
||||
}
|
||||
|
||||
// DirStream lists directory entries.
|
||||
type DirStream interface {
|
||||
// HasNext indicates if there are further entries. HasNext
|
||||
// might be called on already closed streams.
|
||||
HasNext() bool
|
||||
|
||||
// Next retrieves the next entry. It is only called if HasNext
|
||||
// has previously returned true. The Errno return may be used to
|
||||
// indicate I/O errors
|
||||
Next() (fuse.DirEntry, syscall.Errno)
|
||||
|
||||
// Close releases resources related to this directory
|
||||
// stream.
|
||||
Close()
|
||||
}
|
||||
|
||||
// Lookup should find a direct child of a directory by the child's name. If
|
||||
// the entry does not exist, it should return ENOENT and optionally
|
||||
// set a NegativeTimeout in `out`. If it does exist, it should return
|
||||
// attribute data in `out` and return the Inode for the child. A new
|
||||
// inode can be created using `Inode.NewInode`. The new Inode will be
|
||||
// added to the FS tree automatically if the return status is OK.
|
||||
//
|
||||
// If a directory does not implement NodeLookuper, the library looks
|
||||
// for an existing child with the given name.
|
||||
//
|
||||
// The input to a Lookup is {parent directory, name string}.
|
||||
//
|
||||
// Lookup, if successful, must return an *Inode. Once the Inode is
|
||||
// returned to the kernel, the kernel can issue further operations,
|
||||
// such as Open or Getxattr on that node.
|
||||
//
|
||||
// A successful Lookup also returns an EntryOut. Among others, this
|
||||
// contains file attributes (mode, size, mtime, etc.).
|
||||
//
|
||||
// FUSE supports other operations that modify the namespace. For
|
||||
// example, the Symlink, Create, Mknod, Link methods all create new
|
||||
// children in directories. Hence, they also return *Inode and must
|
||||
// populate their fuse.EntryOut arguments.
|
||||
type NodeLookuper interface {
|
||||
Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno)
|
||||
}
|
||||
|
||||
// NodeWrapChilder wraps a FS node implementation in another one. If
|
||||
// defined, it is called automatically from NewInode and
|
||||
// NewPersistentInode. Thus, existing file system implementations,
|
||||
// even from other packages, can be customized by wrapping them. The
|
||||
// following example is a loopback file system that forbids deletions.
|
||||
//
|
||||
// type NoDelete struct {
|
||||
// *fs.LoopbackNode
|
||||
// }
|
||||
// func (w *NoDelete) Unlink(ctx context.Context, name string) syscall.Errno {
|
||||
// return syscall.EPERM
|
||||
// }
|
||||
// func (w *NoDelete) WrapChild(ctx context.Context, ops fs.InodeEmbedder) fs.InodeEmbedder {
|
||||
// return &NoDelete{ops.(*LoopbackNode)}
|
||||
// }
|
||||
//
|
||||
// See also the LoopbackReuse example for a more practical
|
||||
// application.
|
||||
type NodeWrapChilder interface {
|
||||
WrapChild(ctx context.Context, ops InodeEmbedder) InodeEmbedder
|
||||
}
|
||||
|
||||
// OpenDir opens a directory Inode for reading its
|
||||
// contents. The actual reading is driven from Readdir, so
|
||||
// this method is just for performing sanity/permission
|
||||
// checks. The default is to return success.
|
||||
type NodeOpendirer interface {
|
||||
Opendir(ctx context.Context) syscall.Errno
|
||||
}
|
||||
|
||||
// Readdir opens a stream of directory entries.
|
||||
//
|
||||
// Readdir essentiallly returns a list of strings, and it is allowed
|
||||
// for Readdir to return different results from Lookup. For example,
|
||||
// you can return nothing for Readdir ("ls my-fuse-mount" is empty),
|
||||
// while still implementing Lookup ("ls my-fuse-mount/a-specific-file"
|
||||
// shows a single file). The DirStream returned must be deterministic;
|
||||
// a randomized result (e.g. due to map iteration) can lead to entries
|
||||
// disappearing if multiple processes read the same directory
|
||||
// concurrently.
|
||||
//
|
||||
// If a directory does not implement NodeReaddirer, a list of
|
||||
// currently known children from the tree is returned. This means that
|
||||
// static in-memory file systems need not implement NodeReaddirer.
|
||||
type NodeReaddirer interface {
|
||||
Readdir(ctx context.Context) (DirStream, syscall.Errno)
|
||||
}
|
||||
|
||||
// Mkdir is similar to Lookup, but must create a directory entry and Inode.
|
||||
// Default is to return ENOTSUP.
|
||||
type NodeMkdirer interface {
|
||||
Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno)
|
||||
}
|
||||
|
||||
// Mknod is similar to Lookup, but must create a device entry and Inode.
|
||||
// Default is to return ENOTSUP.
|
||||
type NodeMknoder interface {
|
||||
Mknod(ctx context.Context, name string, mode uint32, dev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno)
|
||||
}
|
||||
|
||||
// Link is similar to Lookup, but must create a new link to an existing Inode.
|
||||
// Default is to return ENOTSUP.
|
||||
type NodeLinker interface {
|
||||
Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// Symlink is similar to Lookup, but must create a new symbolic link.
|
||||
// Default is to return ENOTSUP.
|
||||
type NodeSymlinker interface {
|
||||
Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// Create is similar to Lookup, but should create a new
|
||||
// child. It typically also returns a FileHandle as a
|
||||
// reference for future reads/writes.
|
||||
// Default is to return EROFS.
|
||||
type NodeCreater interface {
|
||||
Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (node *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// Unlink should remove a child from this directory. If the
|
||||
// return status is OK, the Inode is removed as child in the
|
||||
// FS tree automatically. Default is to return success.
|
||||
type NodeUnlinker interface {
|
||||
Unlink(ctx context.Context, name string) syscall.Errno
|
||||
}
|
||||
|
||||
// Rmdir is like Unlink but for directories.
|
||||
// Default is to return success.
|
||||
type NodeRmdirer interface {
|
||||
Rmdir(ctx context.Context, name string) syscall.Errno
|
||||
}
|
||||
|
||||
// Rename should move a child from one directory to a different
|
||||
// one. The change is effected in the FS tree if the return status is
|
||||
// OK. Default is to return ENOTSUP.
|
||||
type NodeRenamer interface {
|
||||
Rename(ctx context.Context, name string, newParent InodeEmbedder, newName string, flags uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// FileHandle is a resource identifier for opened files. Usually, a
|
||||
// FileHandle should implement some of the FileXxxx interfaces.
|
||||
//
|
||||
// All of the FileXxxx operations can also be implemented at the
|
||||
// InodeEmbedder level, for example, one can implement NodeReader
|
||||
// instead of FileReader.
|
||||
//
|
||||
// FileHandles are useful in two cases: First, if the underlying
|
||||
// storage systems needs a handle for reading/writing. This is the
|
||||
// case with Unix system calls, which need a file descriptor (See also
|
||||
// the function `NewLoopbackFile`). Second, it is useful for
|
||||
// implementing files whose contents are not tied to an inode. For
|
||||
// example, a file like `/proc/interrupts` has no fixed content, but
|
||||
// changes on each open call. This means that each file handle must
|
||||
// have its own view of the content; this view can be tied to a
|
||||
// FileHandle. Files that have such dynamic content should return the
|
||||
// FOPEN_DIRECT_IO flag from their `Open` method. See directio_test.go
|
||||
// for an example.
|
||||
type FileHandle interface {
|
||||
}
|
||||
|
||||
// FilePassthroughFder is a file backed by a physical
|
||||
// file. PassthroughFd should return an open file descriptor (and
|
||||
// true), and the kernel will execute read/write operations directly
|
||||
// on the backing file, bypassing the FUSE process. This function will
|
||||
// be called once when processing the Create or Open operation, so
|
||||
// there is no concern about concurrent access to the Fd. If the
|
||||
// function returns false, passthrough will not be used for this file.
|
||||
type FilePassthroughFder interface {
|
||||
PassthroughFd() (int, bool)
|
||||
}
|
||||
|
||||
// See NodeReleaser.
|
||||
type FileReleaser interface {
|
||||
Release(ctx context.Context) syscall.Errno
|
||||
}
|
||||
|
||||
// See NodeGetattrer.
|
||||
type FileGetattrer interface {
|
||||
Getattr(ctx context.Context, out *fuse.AttrOut) syscall.Errno
|
||||
}
|
||||
|
||||
type FileStatxer interface {
|
||||
Statx(ctx context.Context, flags uint32, mask uint32, out *fuse.StatxOut) syscall.Errno
|
||||
}
|
||||
|
||||
// See NodeReader.
|
||||
type FileReader interface {
|
||||
Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno)
|
||||
}
|
||||
|
||||
// See NodeWriter.
|
||||
type FileWriter interface {
|
||||
Write(ctx context.Context, data []byte, off int64) (written uint32, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// See NodeGetlker.
|
||||
type FileGetlker interface {
|
||||
Getlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) syscall.Errno
|
||||
}
|
||||
|
||||
// See NodeSetlker.
|
||||
type FileSetlker interface {
|
||||
Setlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// See NodeSetlkwer.
|
||||
type FileSetlkwer interface {
|
||||
Setlkw(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// See NodeLseeker.
|
||||
type FileLseeker interface {
|
||||
Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno)
|
||||
}
|
||||
|
||||
// See NodeFlusher.
|
||||
type FileFlusher interface {
|
||||
Flush(ctx context.Context) syscall.Errno
|
||||
}
|
||||
|
||||
// See NodeFsync.
|
||||
type FileFsyncer interface {
|
||||
Fsync(ctx context.Context, flags uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// See NodeFsync.
|
||||
type FileSetattrer interface {
|
||||
Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno
|
||||
}
|
||||
|
||||
// See NodeAllocater.
|
||||
type FileAllocater interface {
|
||||
Allocate(ctx context.Context, off uint64, size uint64, mode uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// See NodeIoctler.
|
||||
type FileIoctler interface {
|
||||
Ioctl(ctx context.Context, cmd uint32, arg uint64, input []byte, output []byte) (result int32, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// Opens a directory. This supersedes NodeOpendirer, allowing to pass
|
||||
// back flags (eg. FOPEN_CACHE_DIR).
|
||||
type NodeOpendirHandler interface {
|
||||
OpendirHandle(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// FileReaddirenter is a directory that supports reading.
|
||||
type FileReaddirenter interface {
|
||||
// Read a single directory entry.
|
||||
Readdirent(ctx context.Context) (*fuse.DirEntry, syscall.Errno)
|
||||
}
|
||||
|
||||
// FileLookuper is a directory handle that supports lookup. If this is
|
||||
// defined, FileLookuper.Lookup on the directory is called for
|
||||
// READDIRPLUS calls, rather than NodeLookuper.Lookup. The name passed
|
||||
// in will always be the last name produced by Readdirent. If a child
|
||||
// with the given name already exists, that should be returned. In
|
||||
// case of directory seeks that straddle response boundaries,
|
||||
// Readdirent may be called without a subsequent Lookup call.
|
||||
type FileLookuper interface {
|
||||
Lookup(ctx context.Context, name string, out *fuse.EntryOut) (child *Inode, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// FileFsyncer is a directory that supports fsyncdir.
|
||||
type FileFsyncdirer interface {
|
||||
Fsyncdir(ctx context.Context, flags uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// FileSeekdirer is directory that supports seeking. `off` is an
|
||||
// opaque uint64 value, where only the value 0 is reserved for the
|
||||
// start of the stream. (See https://lwn.net/Articles/544520/ for
|
||||
// background).
|
||||
type FileSeekdirer interface {
|
||||
Seekdir(ctx context.Context, off uint64) syscall.Errno
|
||||
}
|
||||
|
||||
// FileReleasedirer is a directory that supports a cleanup operation.
|
||||
type FileReleasedirer interface {
|
||||
Releasedir(ctx context.Context, releaseFlags uint32)
|
||||
}
|
||||
|
||||
// Options are options for the entire filesystem.
|
||||
type Options struct {
|
||||
// MountOptions contain the options for mounting the fuse server.
|
||||
fuse.MountOptions
|
||||
|
||||
// EntryTimeout, if non-nil, defines the overall entry timeout
|
||||
// for the file system. See [fuse.EntryOut] for more information.
|
||||
EntryTimeout *time.Duration
|
||||
|
||||
// AttrTimeout, if non-nil, defines the overall attribute
|
||||
// timeout for the file system. See [fuse.AttrOut] for more
|
||||
// information.
|
||||
AttrTimeout *time.Duration
|
||||
|
||||
// NegativeTimeout, if non-nil, defines the overall entry timeout
|
||||
// for failed lookups (fuse.ENOENT). See [fuse.EntryOut] for
|
||||
// more information.
|
||||
NegativeTimeout *time.Duration
|
||||
|
||||
// FirstAutomaticIno is start of the automatic inode numbers that are handed
|
||||
// out sequentially.
|
||||
//
|
||||
// If unset, the default is 2^63.
|
||||
FirstAutomaticIno uint64
|
||||
|
||||
// OnAdd, if non-nil, is an alternative way to specify the OnAdd
|
||||
// functionality of the root node.
|
||||
OnAdd func(ctx context.Context)
|
||||
|
||||
// NullPermissions, if set, leaves null file permissions
|
||||
// alone. Otherwise, they are set to 755 (dirs) or 644 (other
|
||||
// files.), which is necessary for doing a chdir into the FUSE
|
||||
// directories.
|
||||
NullPermissions bool
|
||||
|
||||
// UID, if nonzero, is the default UID to use instead of the
|
||||
// zero (zero) UID.
|
||||
UID uint32
|
||||
|
||||
// GID, if nonzero, is the default GID to use instead of the
|
||||
// zero (zero) GID.
|
||||
GID uint32
|
||||
|
||||
// ServerCallbacks are optional callbacks to stub out notification functions
|
||||
// for testing a filesystem without mounting it.
|
||||
ServerCallbacks ServerCallbacks
|
||||
|
||||
// Logger is a sink for diagnostic messages. Diagnostic
|
||||
// messages are printed under conditions where we cannot
|
||||
// return error, but want to signal something seems off
|
||||
// anyway. If unset, no messages are printed.
|
||||
//
|
||||
// This field shadows (and thus, is distinct) from
|
||||
// MountOptions.Logger.
|
||||
Logger *log.Logger
|
||||
|
||||
// RootStableAttr is an optional way to set e.g. Ino and/or Gen for
|
||||
// the root directory when calling fs.Mount(), Mode is ignored.
|
||||
RootStableAttr *StableAttr
|
||||
}
|
||||
1327
vendor/github.com/hanwen/go-fuse/v2/fs/bridge.go
generated
vendored
Normal file
1327
vendor/github.com/hanwen/go-fuse/v2/fs/bridge.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
60
vendor/github.com/hanwen/go-fuse/v2/fs/bridge_linux.go
generated
vendored
Normal file
60
vendor/github.com/hanwen/go-fuse/v2/fs/bridge_linux.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
)
|
||||
|
||||
// see rawBridge.setAttr
|
||||
func (b *rawBridge) setStatx(out *fuse.Statx) {
|
||||
if !b.options.NullPermissions && out.Mode&07777 == 0 {
|
||||
out.Mode |= 0644
|
||||
if out.Mode&syscall.S_IFDIR != 0 {
|
||||
out.Mode |= 0111
|
||||
}
|
||||
}
|
||||
if b.options.UID != 0 && out.Uid == 0 {
|
||||
out.Uid = b.options.UID
|
||||
}
|
||||
if b.options.GID != 0 && out.Gid == 0 {
|
||||
out.Gid = b.options.GID
|
||||
}
|
||||
setStatxBlocks(out)
|
||||
}
|
||||
|
||||
// see rawBridge.setAttrTimeout
|
||||
func (b *rawBridge) setStatxTimeout(out *fuse.StatxOut) {
|
||||
if b.options.AttrTimeout != nil && out.Timeout() == 0 {
|
||||
out.SetTimeout(*b.options.AttrTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *rawBridge) Statx(cancel <-chan struct{}, in *fuse.StatxIn, out *fuse.StatxOut) fuse.Status {
|
||||
n, fe := b.inode(in.NodeId, in.Fh)
|
||||
var fh FileHandle
|
||||
if fe != nil {
|
||||
fh = fe.file
|
||||
}
|
||||
|
||||
ctx := &fuse.Context{Caller: in.Caller, Cancel: cancel}
|
||||
|
||||
errno := syscall.ENOSYS
|
||||
if sx, ok := n.ops.(NodeStatxer); ok {
|
||||
errno = sx.Statx(ctx, fh, in.SxFlags, in.SxMask, out)
|
||||
} else if fsx, ok := n.ops.(FileStatxer); ok {
|
||||
errno = fsx.Statx(ctx, in.SxFlags, in.SxMask, out)
|
||||
}
|
||||
|
||||
if errno == 0 {
|
||||
if out.Ino != 0 && n.stableAttr.Ino > 1 && out.Ino != n.stableAttr.Ino {
|
||||
b.logf("warning: rawBridge.getattr: overriding ino %d with %d", out.Ino, n.stableAttr.Ino)
|
||||
}
|
||||
out.Ino = n.stableAttr.Ino
|
||||
out.Mode = (out.Statx.Mode & 07777) | uint16(n.stableAttr.Mode)
|
||||
b.setStatx(&out.Statx)
|
||||
b.setStatxTimeout(out)
|
||||
}
|
||||
|
||||
return errnoToStatus(errno)
|
||||
}
|
||||
9
vendor/github.com/hanwen/go-fuse/v2/fs/bridge_nonlinux.go
generated
vendored
Normal file
9
vendor/github.com/hanwen/go-fuse/v2/fs/bridge_nonlinux.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build !linux
|
||||
|
||||
package fs
|
||||
|
||||
import "github.com/hanwen/go-fuse/v2/fuse"
|
||||
|
||||
func (b *rawBridge) Statx(cancel <-chan struct{}, in *fuse.StatxIn, out *fuse.StatxOut) fuse.Status {
|
||||
return fuse.ENOSYS
|
||||
}
|
||||
33
vendor/github.com/hanwen/go-fuse/v2/fs/constants.go
generated
vendored
Normal file
33
vendor/github.com/hanwen/go-fuse/v2/fs/constants.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"github.com/hanwen/go-fuse/v2/internal/xattr"
|
||||
)
|
||||
|
||||
// OK is the Errno return value to indicate absense of errors.
|
||||
var OK = syscall.Errno(0)
|
||||
|
||||
// ToErrno exhumes the syscall.Errno error from wrapped error values.
|
||||
func ToErrno(err error) syscall.Errno {
|
||||
s := fuse.ToStatus(err)
|
||||
return syscall.Errno(s)
|
||||
}
|
||||
|
||||
// RENAME_EXCHANGE is a flag argument for renameat2()
|
||||
const RENAME_EXCHANGE = 0x2
|
||||
|
||||
// seek to the next data
|
||||
const _SEEK_DATA = 3
|
||||
|
||||
// seek to the next hole
|
||||
const _SEEK_HOLE = 4
|
||||
|
||||
// ENOATTR indicates that an extended attribute was not present.
|
||||
const ENOATTR = xattr.ENOATTR
|
||||
5
vendor/github.com/hanwen/go-fuse/v2/fs/default.go
generated
vendored
Normal file
5
vendor/github.com/hanwen/go-fuse/v2/fs/default.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
216
vendor/github.com/hanwen/go-fuse/v2/fs/dirstream.go
generated
vendored
Normal file
216
vendor/github.com/hanwen/go-fuse/v2/fs/dirstream.go
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type dirArray struct {
|
||||
idx int
|
||||
entries []fuse.DirEntry
|
||||
}
|
||||
|
||||
func (a *dirArray) HasNext() bool {
|
||||
return a.idx < len(a.entries)
|
||||
}
|
||||
|
||||
func (a *dirArray) Next() (fuse.DirEntry, syscall.Errno) {
|
||||
e := a.entries[a.idx]
|
||||
a.idx++
|
||||
e.Off = uint64(a.idx)
|
||||
return e, 0
|
||||
}
|
||||
|
||||
func (a *dirArray) Seekdir(ctx context.Context, off uint64) syscall.Errno {
|
||||
idx := int(off)
|
||||
if idx < 0 || idx > len(a.entries) {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
a.idx = idx
|
||||
return 0
|
||||
}
|
||||
|
||||
func (a *dirArray) Close() {
|
||||
|
||||
}
|
||||
|
||||
func (a *dirArray) Releasedir(ctx context.Context, releaseFlags uint32) {}
|
||||
|
||||
func (a *dirArray) Readdirent(ctx context.Context) (de *fuse.DirEntry, errno syscall.Errno) {
|
||||
if !a.HasNext() {
|
||||
return nil, 0
|
||||
}
|
||||
e, errno := a.Next()
|
||||
return &e, errno
|
||||
}
|
||||
|
||||
// NewLoopbackDirStream opens a directory for reading as a DirStream
|
||||
func NewLoopbackDirStream(name string) (DirStream, syscall.Errno) {
|
||||
// TODO: should return concrete type.
|
||||
fd, err := syscall.Open(name, syscall.O_DIRECTORY|syscall.O_CLOEXEC, 0755)
|
||||
if err != nil {
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
return NewLoopbackDirStreamFd(fd)
|
||||
}
|
||||
|
||||
// NewListDirStream wraps a slice of DirEntry as a DirStream.
|
||||
func NewListDirStream(list []fuse.DirEntry) DirStream {
|
||||
return &dirArray{entries: list}
|
||||
}
|
||||
|
||||
// implement FileReaddirenter/FileReleasedirer
|
||||
type dirStreamAsFile struct {
|
||||
creator func(context.Context) (DirStream, syscall.Errno)
|
||||
ds DirStream
|
||||
}
|
||||
|
||||
func (d *dirStreamAsFile) Releasedir(ctx context.Context, releaseFlags uint32) {
|
||||
if d.ds != nil {
|
||||
d.ds.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dirStreamAsFile) Readdirent(ctx context.Context) (de *fuse.DirEntry, errno syscall.Errno) {
|
||||
if d.ds == nil {
|
||||
d.ds, errno = d.creator(ctx)
|
||||
if errno != 0 {
|
||||
return nil, errno
|
||||
}
|
||||
}
|
||||
if !d.ds.HasNext() {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
e, errno := d.ds.Next()
|
||||
return &e, errno
|
||||
}
|
||||
|
||||
func (d *dirStreamAsFile) Seekdir(ctx context.Context, off uint64) syscall.Errno {
|
||||
if d.ds == nil {
|
||||
var errno syscall.Errno
|
||||
d.ds, errno = d.creator(ctx)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
}
|
||||
if sd, ok := d.ds.(FileSeekdirer); ok {
|
||||
return sd.Seekdir(ctx, off)
|
||||
}
|
||||
return syscall.ENOTSUP
|
||||
}
|
||||
|
||||
type loopbackDirStream struct {
|
||||
buf []byte
|
||||
|
||||
// Protects mutable members
|
||||
mu sync.Mutex
|
||||
|
||||
// mutable
|
||||
todo []byte
|
||||
todoErrno syscall.Errno
|
||||
fd int
|
||||
}
|
||||
|
||||
// NewLoopbackDirStreamFd reads the directory opened at file descriptor fd as
|
||||
// a DirStream
|
||||
func NewLoopbackDirStreamFd(fd int) (DirStream, syscall.Errno) {
|
||||
ds := &loopbackDirStream{
|
||||
buf: make([]byte, 4096),
|
||||
fd: fd,
|
||||
}
|
||||
ds.load()
|
||||
return ds, OK
|
||||
}
|
||||
|
||||
func (ds *loopbackDirStream) Close() {
|
||||
ds.mu.Lock()
|
||||
defer ds.mu.Unlock()
|
||||
if ds.fd != -1 {
|
||||
syscall.Close(ds.fd)
|
||||
ds.fd = -1
|
||||
}
|
||||
}
|
||||
|
||||
var _ = (FileReleasedirer)((*loopbackDirStream)(nil))
|
||||
|
||||
func (ds *loopbackDirStream) Releasedir(ctx context.Context, flags uint32) {
|
||||
ds.Close()
|
||||
}
|
||||
|
||||
var _ = (FileSeekdirer)((*loopbackDirStream)(nil))
|
||||
|
||||
func (ds *loopbackDirStream) Seekdir(ctx context.Context, off uint64) syscall.Errno {
|
||||
ds.mu.Lock()
|
||||
defer ds.mu.Unlock()
|
||||
_, errno := unix.Seek(ds.fd, int64(off), unix.SEEK_SET)
|
||||
if errno != nil {
|
||||
return ToErrno(errno)
|
||||
}
|
||||
|
||||
ds.todo = nil
|
||||
ds.todoErrno = 0
|
||||
ds.load()
|
||||
return 0
|
||||
}
|
||||
|
||||
var _ = (FileFsyncdirer)((*loopbackDirStream)(nil))
|
||||
|
||||
func (ds *loopbackDirStream) Fsyncdir(ctx context.Context, flags uint32) syscall.Errno {
|
||||
ds.mu.Lock()
|
||||
defer ds.mu.Unlock()
|
||||
return ToErrno(syscall.Fsync(ds.fd))
|
||||
}
|
||||
|
||||
func (ds *loopbackDirStream) HasNext() bool {
|
||||
ds.mu.Lock()
|
||||
defer ds.mu.Unlock()
|
||||
return len(ds.todo) > 0 || ds.todoErrno != 0
|
||||
}
|
||||
|
||||
var _ = (FileReaddirenter)((*loopbackDirStream)(nil))
|
||||
|
||||
func (ds *loopbackDirStream) Readdirent(ctx context.Context) (*fuse.DirEntry, syscall.Errno) {
|
||||
if !ds.HasNext() {
|
||||
return nil, 0
|
||||
}
|
||||
de, errno := ds.Next()
|
||||
return &de, errno
|
||||
}
|
||||
|
||||
func (ds *loopbackDirStream) Next() (fuse.DirEntry, syscall.Errno) {
|
||||
ds.mu.Lock()
|
||||
defer ds.mu.Unlock()
|
||||
|
||||
if ds.todoErrno != 0 {
|
||||
return fuse.DirEntry{}, ds.todoErrno
|
||||
}
|
||||
var res fuse.DirEntry
|
||||
n := res.Parse(ds.todo)
|
||||
ds.todo = ds.todo[n:]
|
||||
if len(ds.todo) == 0 {
|
||||
ds.load()
|
||||
}
|
||||
return res, 0
|
||||
}
|
||||
|
||||
func (ds *loopbackDirStream) load() {
|
||||
if len(ds.todo) > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
n, err := getdents(ds.fd, ds.buf)
|
||||
if n < 0 {
|
||||
n = 0
|
||||
}
|
||||
ds.todo = ds.buf[:n]
|
||||
ds.todoErrno = ToErrno(err)
|
||||
}
|
||||
11
vendor/github.com/hanwen/go-fuse/v2/fs/dirstream_darwin.go
generated
vendored
Normal file
11
vendor/github.com/hanwen/go-fuse/v2/fs/dirstream_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func getdents(fd int, buf []byte) (int, error) {
|
||||
return unix.Getdirentries(fd, buf, nil)
|
||||
}
|
||||
13
vendor/github.com/hanwen/go-fuse/v2/fs/dirstream_unix.go
generated
vendored
Normal file
13
vendor/github.com/hanwen/go-fuse/v2/fs/dirstream_unix.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
//go:build !darwin
|
||||
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func getdents(fd int, buf []byte) (int, error) {
|
||||
return unix.Getdents(fd, buf)
|
||||
}
|
||||
277
vendor/github.com/hanwen/go-fuse/v2/fs/files.go
generated
vendored
Normal file
277
vendor/github.com/hanwen/go-fuse/v2/fs/files.go
generated
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"github.com/hanwen/go-fuse/v2/internal/fallocate"
|
||||
"github.com/hanwen/go-fuse/v2/internal/ioctl"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// NewLoopbackFile creates a FileHandle out of a file descriptor. All
|
||||
// operations are implemented. When using the Fd from a *os.File, call
|
||||
// syscall.Dup() on the fd, to avoid os.File's finalizer from closing
|
||||
// the file descriptor.
|
||||
func NewLoopbackFile(fd int) FileHandle {
|
||||
return &loopbackFile{fd: fd}
|
||||
}
|
||||
|
||||
type loopbackFile struct {
|
||||
mu sync.Mutex
|
||||
fd int
|
||||
}
|
||||
|
||||
var _ = (FileHandle)((*loopbackFile)(nil))
|
||||
var _ = (FileReleaser)((*loopbackFile)(nil))
|
||||
var _ = (FileGetattrer)((*loopbackFile)(nil))
|
||||
var _ = (FileReader)((*loopbackFile)(nil))
|
||||
var _ = (FileWriter)((*loopbackFile)(nil))
|
||||
var _ = (FileGetlker)((*loopbackFile)(nil))
|
||||
var _ = (FileSetlker)((*loopbackFile)(nil))
|
||||
var _ = (FileSetlkwer)((*loopbackFile)(nil))
|
||||
var _ = (FileLseeker)((*loopbackFile)(nil))
|
||||
var _ = (FileFlusher)((*loopbackFile)(nil))
|
||||
var _ = (FileFsyncer)((*loopbackFile)(nil))
|
||||
var _ = (FileSetattrer)((*loopbackFile)(nil))
|
||||
var _ = (FileAllocater)((*loopbackFile)(nil))
|
||||
var _ = (FilePassthroughFder)((*loopbackFile)(nil))
|
||||
|
||||
func (f *loopbackFile) PassthroughFd() (int, bool) {
|
||||
// This Fd is not accessed concurrently, but lock anyway for uniformity.
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return f.fd, true
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Read(ctx context.Context, buf []byte, off int64) (res fuse.ReadResult, errno syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
r := fuse.ReadResultFd(uintptr(f.fd), off, len(buf))
|
||||
return r, OK
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Write(ctx context.Context, data []byte, off int64) (uint32, syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
n, err := syscall.Pwrite(f.fd, data, off)
|
||||
return uint32(n), ToErrno(err)
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Release(ctx context.Context) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.fd != -1 {
|
||||
err := syscall.Close(f.fd)
|
||||
f.fd = -1
|
||||
return ToErrno(err)
|
||||
}
|
||||
return syscall.EBADF
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Flush(ctx context.Context) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
// Since Flush() may be called for each dup'd fd, we don't
|
||||
// want to really close the file, we just want to flush. This
|
||||
// is achieved by closing a dup'd fd.
|
||||
newFd, err := syscall.Dup(f.fd)
|
||||
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
err = syscall.Close(newFd)
|
||||
return ToErrno(err)
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
r := ToErrno(syscall.Fsync(f.fd))
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
const (
|
||||
_OFD_GETLK = 36
|
||||
_OFD_SETLK = 37
|
||||
_OFD_SETLKW = 38
|
||||
)
|
||||
|
||||
func (f *loopbackFile) Getlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (errno syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
flk := syscall.Flock_t{}
|
||||
lk.ToFlockT(&flk)
|
||||
errno = ToErrno(syscall.FcntlFlock(uintptr(f.fd), _OFD_GETLK, &flk))
|
||||
out.FromFlockT(&flk)
|
||||
return
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Setlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) (errno syscall.Errno) {
|
||||
return f.setLock(ctx, owner, lk, flags, false)
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Setlkw(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) (errno syscall.Errno) {
|
||||
return f.setLock(ctx, owner, lk, flags, true)
|
||||
}
|
||||
|
||||
func (f *loopbackFile) setLock(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, blocking bool) (errno syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if (flags & fuse.FUSE_LK_FLOCK) != 0 {
|
||||
var op int
|
||||
switch lk.Typ {
|
||||
case syscall.F_RDLCK:
|
||||
op = syscall.LOCK_SH
|
||||
case syscall.F_WRLCK:
|
||||
op = syscall.LOCK_EX
|
||||
case syscall.F_UNLCK:
|
||||
op = syscall.LOCK_UN
|
||||
default:
|
||||
return syscall.EINVAL
|
||||
}
|
||||
if !blocking {
|
||||
op |= syscall.LOCK_NB
|
||||
}
|
||||
return ToErrno(syscall.Flock(f.fd, op))
|
||||
} else {
|
||||
flk := syscall.Flock_t{}
|
||||
lk.ToFlockT(&flk)
|
||||
var op int
|
||||
if blocking {
|
||||
op = _OFD_SETLKW
|
||||
} else {
|
||||
op = _OFD_SETLK
|
||||
}
|
||||
return ToErrno(syscall.FcntlFlock(uintptr(f.fd), op, &flk))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
|
||||
if errno := f.setAttr(ctx, in); errno != 0 {
|
||||
return errno
|
||||
}
|
||||
|
||||
return f.Getattr(ctx, out)
|
||||
}
|
||||
|
||||
func (f *loopbackFile) fchmod(mode uint32) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return ToErrno(syscall.Fchmod(f.fd, mode))
|
||||
}
|
||||
|
||||
func (f *loopbackFile) fchown(uid, gid int) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return ToErrno(syscall.Fchown(f.fd, uid, gid))
|
||||
}
|
||||
|
||||
func (f *loopbackFile) ftruncate(sz uint64) syscall.Errno {
|
||||
return ToErrno(syscall.Ftruncate(f.fd, int64(sz)))
|
||||
}
|
||||
|
||||
func (f *loopbackFile) setAttr(ctx context.Context, in *fuse.SetAttrIn) syscall.Errno {
|
||||
var errno syscall.Errno
|
||||
if mode, ok := in.GetMode(); ok {
|
||||
if errno := f.fchmod(mode); errno != 0 {
|
||||
return errno
|
||||
}
|
||||
}
|
||||
|
||||
uid32, uOk := in.GetUID()
|
||||
gid32, gOk := in.GetGID()
|
||||
if uOk || gOk {
|
||||
uid := -1
|
||||
gid := -1
|
||||
|
||||
if uOk {
|
||||
uid = int(uid32)
|
||||
}
|
||||
if gOk {
|
||||
gid = int(gid32)
|
||||
}
|
||||
if errno := f.fchown(uid, gid); errno != 0 {
|
||||
return errno
|
||||
}
|
||||
}
|
||||
|
||||
mtime, mok := in.GetMTime()
|
||||
atime, aok := in.GetATime()
|
||||
|
||||
if mok || aok {
|
||||
ap := &atime
|
||||
mp := &mtime
|
||||
if !aok {
|
||||
ap = nil
|
||||
}
|
||||
if !mok {
|
||||
mp = nil
|
||||
}
|
||||
errno = f.utimens(ap, mp)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
}
|
||||
|
||||
if sz, ok := in.GetSize(); ok {
|
||||
if errno := f.ftruncate(sz); errno != 0 {
|
||||
return errno
|
||||
}
|
||||
}
|
||||
return OK
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Getattr(ctx context.Context, a *fuse.AttrOut) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
st := syscall.Stat_t{}
|
||||
err := syscall.Fstat(f.fd, &st)
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
a.FromStat(&st)
|
||||
|
||||
return OK
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
n, err := unix.Seek(f.fd, int64(off), int(whence))
|
||||
return uint64(n), ToErrno(err)
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
err := fallocate.Fallocate(f.fd, mode, int64(off), int64(sz))
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
return OK
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Ioctl(ctx context.Context, cmd uint32, arg uint64, input []byte, output []byte) (result int32, errno syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
argWord := uintptr(arg)
|
||||
ioc := ioctl.Command(cmd)
|
||||
if ioc.Read() {
|
||||
argWord = uintptr(unsafe.Pointer(&input[0]))
|
||||
} else if ioc.Write() {
|
||||
argWord = uintptr(unsafe.Pointer(&output[0]))
|
||||
}
|
||||
|
||||
res, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(f.fd), uintptr(cmd), argWord)
|
||||
return int32(res), errno
|
||||
}
|
||||
32
vendor/github.com/hanwen/go-fuse/v2/fs/files_darwin.go
generated
vendored
Normal file
32
vendor/github.com/hanwen/go-fuse/v2/fs/files_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"github.com/hanwen/go-fuse/v2/internal/utimens"
|
||||
)
|
||||
|
||||
func setBlocks(out *fuse.Attr) {
|
||||
}
|
||||
|
||||
// MacOS before High Sierra lacks utimensat() and UTIME_OMIT.
|
||||
// We emulate using utimes() and extra Getattr() calls.
|
||||
func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno {
|
||||
var attr fuse.AttrOut
|
||||
if a == nil || m == nil {
|
||||
errno := f.Getattr(context.Background(), &attr)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
}
|
||||
tv := utimens.Fill(a, m, &attr.Attr)
|
||||
err := syscall.Futimes(int(f.fd), tv)
|
||||
return ToErrno(err)
|
||||
}
|
||||
6
vendor/github.com/hanwen/go-fuse/v2/fs/files_freebsd.go
generated
vendored
Normal file
6
vendor/github.com/hanwen/go-fuse/v2/fs/files_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
package fs
|
||||
|
||||
import "github.com/hanwen/go-fuse/v2/fuse"
|
||||
|
||||
func setBlocks(out *fuse.Attr) {
|
||||
}
|
||||
46
vendor/github.com/hanwen/go-fuse/v2/fs/files_linux.go
generated
vendored
Normal file
46
vendor/github.com/hanwen/go-fuse/v2/fs/files_linux.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func setBlocks(out *fuse.Attr) {
|
||||
if out.Blksize > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
out.Blksize = 4096
|
||||
pages := (out.Size + 4095) / 4096
|
||||
out.Blocks = pages * 8
|
||||
}
|
||||
|
||||
func setStatxBlocks(out *fuse.Statx) {
|
||||
if out.Blksize > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
out.Blksize = 4096
|
||||
pages := (out.Size + 4095) / 4096
|
||||
out.Blocks = pages * 8
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Statx(ctx context.Context, flags uint32, mask uint32, out *fuse.StatxOut) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
st := unix.Statx_t{}
|
||||
err := unix.Statx(f.fd, "", int(flags), int(mask), &st)
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
out.FromStatx(&st)
|
||||
|
||||
return OK
|
||||
}
|
||||
30
vendor/github.com/hanwen/go-fuse/v2/fs/files_unix.go
generated
vendored
Normal file
30
vendor/github.com/hanwen/go-fuse/v2/fs/files_unix.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
//go:build !darwin
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
)
|
||||
|
||||
// Utimens - file handle based version of loopbackFileSystem.Utimens()
|
||||
func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno {
|
||||
var ts [2]syscall.Timespec
|
||||
ts[0] = fuse.UtimeToTimespec(a)
|
||||
ts[1] = fuse.UtimeToTimespec(m)
|
||||
err := futimens(int(f.fd), &ts)
|
||||
return ToErrno(err)
|
||||
}
|
||||
|
||||
// futimens - futimens(3) calls utimensat(2) with "pathname" set to null and
|
||||
// "flags" set to zero
|
||||
func futimens(fd int, times *[2]syscall.Timespec) (err error) {
|
||||
_, _, e1 := syscall.Syscall6(syscall.SYS_UTIMENSAT, uintptr(fd), 0, uintptr(unsafe.Pointer(times)), uintptr(0), 0, 0)
|
||||
if e1 != 0 {
|
||||
err = syscall.Errno(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
762
vendor/github.com/hanwen/go-fuse/v2/fs/inode.go
generated
vendored
Normal file
762
vendor/github.com/hanwen/go-fuse/v2/fs/inode.go
generated
vendored
Normal file
@@ -0,0 +1,762 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
)
|
||||
|
||||
// StableAttr holds immutable attributes of a object in the filesystem.
|
||||
type StableAttr struct {
|
||||
// Each Inode has a type, which does not change over the
|
||||
// lifetime of the inode, for example fuse.S_IFDIR. The default (0)
|
||||
// is interpreted as S_IFREG (regular file).
|
||||
Mode uint32
|
||||
|
||||
// The inode number must be unique among the currently live
|
||||
// objects in the file system. It is used to communicate to
|
||||
// the kernel about this file object. The value uint64(-1)
|
||||
// is reserved. When using Ino==0, a unique, sequential
|
||||
// number is assigned (starting at 2^63 by default) on Inode creation.
|
||||
Ino uint64
|
||||
|
||||
// When reusing a previously used inode number for a new
|
||||
// object, the new object must have a different Gen
|
||||
// number. This is irrelevant if the FS is not exported over
|
||||
// NFS
|
||||
Gen uint64
|
||||
}
|
||||
|
||||
// Reserved returns if the StableAttr is using reserved Inode numbers.
|
||||
func (i *StableAttr) Reserved() bool {
|
||||
return i.Ino == ^uint64(0) // fuse.pollHackInode = ^uint64(0)
|
||||
}
|
||||
|
||||
// Inode is a node in VFS tree. Inodes are one-to-one mapped to
|
||||
// Operations instances, which is the extension interface for file
|
||||
// systems. One can create fully-formed trees of Inodes ahead of time
|
||||
// by creating "persistent" Inodes.
|
||||
//
|
||||
// The Inode struct contains a lock, so it should not be
|
||||
// copied. Inodes should be obtained by calling Inode.NewInode() or
|
||||
// Inode.NewPersistentInode().
|
||||
type Inode struct {
|
||||
stableAttr StableAttr
|
||||
|
||||
ops InodeEmbedder
|
||||
bridge *rawBridge
|
||||
|
||||
// The *Node ID* is an arbitrary uint64 identifier chosen by the FUSE library.
|
||||
// It is used the identify *nodes* (files/directories/symlinks/...) in the
|
||||
// communication between the FUSE library and the Linux kernel.
|
||||
nodeId uint64
|
||||
|
||||
// Following data is mutable.
|
||||
|
||||
// file handles.
|
||||
// protected by bridge.mu
|
||||
openFiles []uint32
|
||||
|
||||
// backing files, protected by bridge.mu
|
||||
backingIDRefcount int
|
||||
backingID int32
|
||||
backingFd int
|
||||
|
||||
// mu protects the following mutable fields. When locking
|
||||
// multiple Inodes, locks must be acquired using
|
||||
// lockNodes/unlockNodes
|
||||
mu sync.Mutex
|
||||
|
||||
// persistent indicates that this node should not be removed
|
||||
// from the tree, even if there are no live references. This
|
||||
// must be set on creation, and can only be changed to false
|
||||
// by calling removeRef.
|
||||
// When you change this, you MUST increment changeCounter.
|
||||
persistent bool
|
||||
|
||||
// changeCounter increments every time the mutable state
|
||||
// (lookupCount, persistent, children, parents) protected by
|
||||
// mu is modified.
|
||||
//
|
||||
// This is used in places where we have to relock inode into inode
|
||||
// group lock, and after locking the group we have to check if inode
|
||||
// did not changed, and if it changed - retry the operation.
|
||||
changeCounter uint32
|
||||
|
||||
// Number of kernel refs to this node.
|
||||
// When you change this, you MUST increment changeCounter.
|
||||
lookupCount uint64
|
||||
|
||||
// Children of this Inode.
|
||||
// When you change this, you MUST increment changeCounter.
|
||||
children inodeChildren
|
||||
|
||||
// Parents of this Inode. Can be more than one due to hard links.
|
||||
// When you change this, you MUST increment changeCounter.
|
||||
parents inodeParents
|
||||
}
|
||||
|
||||
func (n *Inode) IsDir() bool {
|
||||
return n.stableAttr.Mode&syscall.S_IFMT == syscall.S_IFDIR
|
||||
}
|
||||
|
||||
func (n *Inode) embed() *Inode {
|
||||
return n
|
||||
}
|
||||
|
||||
func (n *Inode) EmbeddedInode() *Inode {
|
||||
return n
|
||||
}
|
||||
|
||||
func initInode(n *Inode, ops InodeEmbedder, attr StableAttr, bridge *rawBridge, persistent bool, nodeId uint64) {
|
||||
n.ops = ops
|
||||
n.stableAttr = attr
|
||||
n.bridge = bridge
|
||||
n.persistent = persistent
|
||||
n.nodeId = nodeId
|
||||
if attr.Mode == fuse.S_IFDIR {
|
||||
n.children.init()
|
||||
}
|
||||
}
|
||||
|
||||
// Set node ID and mode in EntryOut
|
||||
func (n *Inode) setEntryOut(out *fuse.EntryOut) {
|
||||
out.NodeId = n.nodeId
|
||||
out.Ino = n.stableAttr.Ino
|
||||
out.Mode = (out.Attr.Mode & 07777) | n.stableAttr.Mode
|
||||
}
|
||||
|
||||
// StableAttr returns the (Ino, Gen) tuple for this node.
|
||||
func (n *Inode) StableAttr() StableAttr {
|
||||
return n.stableAttr
|
||||
}
|
||||
|
||||
// Mode returns the filetype
|
||||
func (n *Inode) Mode() uint32 {
|
||||
return n.stableAttr.Mode
|
||||
}
|
||||
|
||||
// Returns the root of the tree
|
||||
func (n *Inode) Root() *Inode {
|
||||
return n.bridge.root
|
||||
}
|
||||
|
||||
// Returns whether this is the root of the tree
|
||||
func (n *Inode) IsRoot() bool {
|
||||
return n.bridge.root == n
|
||||
}
|
||||
|
||||
func modeStr(m uint32) string {
|
||||
return map[uint32]string{
|
||||
syscall.S_IFREG: "reg",
|
||||
syscall.S_IFLNK: "lnk",
|
||||
syscall.S_IFDIR: "dir",
|
||||
syscall.S_IFSOCK: "soc",
|
||||
syscall.S_IFIFO: "pip",
|
||||
syscall.S_IFCHR: "chr",
|
||||
syscall.S_IFBLK: "blk",
|
||||
}[m]
|
||||
}
|
||||
|
||||
func (a StableAttr) String() string {
|
||||
return fmt.Sprintf("i%d g%d (%s)",
|
||||
a.Ino, a.Gen, modeStr(a.Mode))
|
||||
}
|
||||
|
||||
// debugString is used for debugging. Racy.
|
||||
func (n *Inode) String() string {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
|
||||
return fmt.Sprintf("%s: %s", n.stableAttr.String(), n.children.String())
|
||||
}
|
||||
|
||||
// sortNodes rearranges inode group in consistent order.
|
||||
//
|
||||
// The nodes are ordered by their in-RAM address, which gives consistency
|
||||
// property: for any A and B inodes, sortNodes will either always order A < B,
|
||||
// or always order A > B.
|
||||
//
|
||||
// See lockNodes where this property is used to avoid deadlock when taking
|
||||
// locks on inode group.
|
||||
func sortNodes(ns []*Inode) {
|
||||
sort.Slice(ns, func(i, j int) bool {
|
||||
return nodeLess(ns[i], ns[j])
|
||||
})
|
||||
}
|
||||
|
||||
func nodeLess(a, b *Inode) bool {
|
||||
return uintptr(unsafe.Pointer(a)) < uintptr(unsafe.Pointer(b))
|
||||
}
|
||||
|
||||
// lockNodes locks group of inodes.
|
||||
//
|
||||
// It always lock the inodes in the same order - to avoid deadlocks.
|
||||
// It also avoids locking an inode more than once, if it was specified multiple times.
|
||||
// An example when an inode might be given multiple times is if dir/a and dir/b
|
||||
// are hardlinked to the same inode and the caller needs to take locks on dir children.
|
||||
func lockNodes(ns ...*Inode) {
|
||||
sortNodes(ns)
|
||||
|
||||
// The default value nil prevents trying to lock nil nodes.
|
||||
var nprev *Inode
|
||||
for _, n := range ns {
|
||||
if n != nprev {
|
||||
n.mu.Lock()
|
||||
nprev = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lockNode2 locks a and b in order consistent with lockNodes.
|
||||
func lockNode2(a, b *Inode) {
|
||||
if a == b {
|
||||
a.mu.Lock()
|
||||
} else if nodeLess(a, b) {
|
||||
a.mu.Lock()
|
||||
b.mu.Lock()
|
||||
} else {
|
||||
b.mu.Lock()
|
||||
a.mu.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
// unlockNode2 unlocks a and b
|
||||
func unlockNode2(a, b *Inode) {
|
||||
if a == b {
|
||||
a.mu.Unlock()
|
||||
} else {
|
||||
a.mu.Unlock()
|
||||
b.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// unlockNodes releases locks taken by lockNodes.
|
||||
func unlockNodes(ns ...*Inode) {
|
||||
// we don't need to unlock in the same order that was used in lockNodes.
|
||||
// however it still helps to have nodes sorted to avoid duplicates.
|
||||
sortNodes(ns)
|
||||
|
||||
var nprev *Inode
|
||||
for _, n := range ns {
|
||||
if n != nprev {
|
||||
n.mu.Unlock()
|
||||
nprev = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Forgotten returns true if the kernel holds no references to this
|
||||
// inode. This can be used for background cleanup tasks, since the
|
||||
// kernel has no way of reviving forgotten nodes by its own
|
||||
// initiative.
|
||||
//
|
||||
// Bugs: Forgotten() may momentarily return true in the window between
|
||||
// creation (NewInode) and adding the node into the tree, which
|
||||
// happens after Lookup/Mkdir/etc. return.
|
||||
//
|
||||
// Deprecated: use NodeOnForgetter instead.
|
||||
func (n *Inode) Forgotten() bool {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
return n.lookupCount == 0 && n.parents.count() == 0 && !n.persistent
|
||||
}
|
||||
|
||||
// Operations returns the object implementing the file system
|
||||
// operations.
|
||||
func (n *Inode) Operations() InodeEmbedder {
|
||||
return n.ops
|
||||
}
|
||||
|
||||
// Path returns a path string to the inode relative to `root`.
|
||||
// Pass nil to walk the hierarchy as far up as possible.
|
||||
//
|
||||
// If you set `root`, Path() warns if it finds an orphaned Inode, i.e.
|
||||
// if it does not end up at `root` after walking the hierarchy.
|
||||
func (n *Inode) Path(root *Inode) string {
|
||||
var segments []string
|
||||
p := n
|
||||
for p != nil && p != root {
|
||||
// We don't try to take all locks at the same time, because
|
||||
// the caller won't use the "path" string under lock anyway.
|
||||
p.mu.Lock()
|
||||
// Get last known parent
|
||||
pd := p.parents.get()
|
||||
p.mu.Unlock()
|
||||
if pd == nil {
|
||||
p = nil
|
||||
break
|
||||
}
|
||||
segments = append(segments, pd.name)
|
||||
p = pd.parent
|
||||
}
|
||||
|
||||
if root != nil && root != p {
|
||||
deletedPlaceholder := fmt.Sprintf(".go-fuse.%d/deleted", rand.Uint64())
|
||||
n.bridge.logf("warning: Inode.Path: n%d is orphaned, replacing segment with %q",
|
||||
n.nodeId, deletedPlaceholder)
|
||||
// NOSUBMIT - should replace rather than append?
|
||||
segments = append(segments, deletedPlaceholder)
|
||||
}
|
||||
|
||||
i := 0
|
||||
j := len(segments) - 1
|
||||
|
||||
for i < j {
|
||||
segments[i], segments[j] = segments[j], segments[i]
|
||||
i++
|
||||
j--
|
||||
}
|
||||
|
||||
path := strings.Join(segments, "/")
|
||||
return path
|
||||
}
|
||||
|
||||
// setEntry does `iparent[name] = ichild` linking.
|
||||
//
|
||||
// setEntry must not be called simultaneously for any of iparent or ichild.
|
||||
// This, for example could be satisfied if both iparent and ichild are locked,
|
||||
// but it could be also valid if only iparent is locked and ichild was just
|
||||
// created and only one goroutine keeps referencing it.
|
||||
func (iparent *Inode) setEntry(name string, ichild *Inode) {
|
||||
if ichild.stableAttr.Mode == syscall.S_IFDIR {
|
||||
// Directories cannot have more than one parent. Clear the map.
|
||||
// This special-case is neccessary because ichild may still have a
|
||||
// parent that was forgotten (i.e. removed from bridge.inoMap).
|
||||
ichild.parents.clear()
|
||||
}
|
||||
iparent.children.set(iparent, name, ichild)
|
||||
}
|
||||
|
||||
// NewPersistentInode returns an Inode whose lifetime is not in
|
||||
// control of the kernel.
|
||||
//
|
||||
// When the kernel is short on memory, it will forget cached file
|
||||
// system information (directory entries and inode metadata). This is
|
||||
// announced with FORGET messages. There are no guarantees if or when
|
||||
// this happens. When it happens, these are handled transparently by
|
||||
// go-fuse: all Inodes created with NewInode are released
|
||||
// automatically. NewPersistentInode creates inodes that go-fuse keeps
|
||||
// in memory, even if the kernel is not interested in them. This is
|
||||
// convenient for building static trees up-front.
|
||||
func (n *Inode) NewPersistentInode(ctx context.Context, node InodeEmbedder, id StableAttr) *Inode {
|
||||
return n.newInode(ctx, node, id, true)
|
||||
}
|
||||
|
||||
// ForgetPersistent manually marks the node as no longer important. If
|
||||
// it has no children, and if the kernel as no references, the nodes
|
||||
// gets removed from the tree.
|
||||
func (n *Inode) ForgetPersistent() {
|
||||
n.removeRef(0, true)
|
||||
}
|
||||
|
||||
// NewInode returns an inode for the given InodeEmbedder. The mode
|
||||
// should be standard mode argument (eg. S_IFDIR). The inode number in
|
||||
// id.Ino argument is used to implement hard-links. If it is given,
|
||||
// and another node with the same ID is known, the new inode may be
|
||||
// ignored, and the old one used instead. If the parent inode
|
||||
// implements NodeWrapChilder, the returned Inode will have a
|
||||
// different InodeEmbedder from the one passed in.
|
||||
func (n *Inode) NewInode(ctx context.Context, node InodeEmbedder, id StableAttr) *Inode {
|
||||
return n.newInode(ctx, node, id, false)
|
||||
}
|
||||
|
||||
func (n *Inode) newInode(ctx context.Context, ops InodeEmbedder, id StableAttr, persistent bool) *Inode {
|
||||
if wc, ok := n.ops.(NodeWrapChilder); ok {
|
||||
ops = wc.WrapChild(ctx, ops)
|
||||
}
|
||||
return n.bridge.newInode(ctx, ops, id, persistent)
|
||||
}
|
||||
|
||||
// removeRef decreases references. Returns if this operation caused
|
||||
// the node to be forgotten (for kernel references), and whether it is
|
||||
// live (ie. was not dropped from the tree)
|
||||
func (n *Inode) removeRef(nlookup uint64, dropPersistence bool) (hasLookups, isPersistent, hasChildren bool) {
|
||||
var beforeLookups, beforePersistence, beforeChildren bool
|
||||
var unusedParents []*Inode
|
||||
beforeLookups, hasLookups, beforePersistence, isPersistent, beforeChildren, hasChildren, unusedParents = n.removeRefInner(nlookup, dropPersistence, unusedParents)
|
||||
|
||||
if !hasLookups && !isPersistent && !hasChildren && (beforeChildren || beforeLookups || beforePersistence) {
|
||||
if nf, ok := n.ops.(NodeOnForgetter); ok {
|
||||
nf.OnForget()
|
||||
}
|
||||
}
|
||||
|
||||
for len(unusedParents) > 0 {
|
||||
l := len(unusedParents)
|
||||
p := unusedParents[l-1]
|
||||
unusedParents = unusedParents[:l-1]
|
||||
_, _, _, _, _, _, unusedParents = p.removeRefInner(0, false, unusedParents)
|
||||
|
||||
if nf, ok := p.ops.(NodeOnForgetter); ok {
|
||||
nf.OnForget()
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (n *Inode) removeRefInner(nlookup uint64, dropPersistence bool, inputUnusedParents []*Inode) (beforeLookups, hasLookups, beforePersistent, isPersistent, beforeChildren, hasChildren bool, unusedParents []*Inode) {
|
||||
var lockme []*Inode
|
||||
var parents []parentData
|
||||
|
||||
unusedParents = inputUnusedParents
|
||||
|
||||
n.mu.Lock()
|
||||
beforeLookups = n.lookupCount > 0
|
||||
beforePersistent = n.persistent
|
||||
beforeChildren = n.children.len() > 0
|
||||
if nlookup > 0 && dropPersistence {
|
||||
log.Panic("only one allowed")
|
||||
} else if nlookup > n.lookupCount {
|
||||
log.Panicf("n%d lookupCount underflow: lookupCount=%d, decrement=%d", n.nodeId, n.lookupCount, nlookup)
|
||||
} else if nlookup > 0 {
|
||||
n.lookupCount -= nlookup
|
||||
n.changeCounter++
|
||||
} else if dropPersistence && n.persistent {
|
||||
n.persistent = false
|
||||
n.changeCounter++
|
||||
}
|
||||
|
||||
n.bridge.mu.Lock()
|
||||
if n.lookupCount == 0 {
|
||||
// Dropping the node from stableAttrs guarantees that no new references to this node are
|
||||
// handed out to the kernel, hence we can also safely delete it from kernelNodeIds.
|
||||
delete(n.bridge.stableAttrs, n.stableAttr)
|
||||
delete(n.bridge.kernelNodeIds, n.nodeId)
|
||||
}
|
||||
n.bridge.mu.Unlock()
|
||||
|
||||
retry:
|
||||
for {
|
||||
lockme = append(lockme[:0], n)
|
||||
parents = parents[:0]
|
||||
nChange := n.changeCounter
|
||||
hasLookups = n.lookupCount > 0
|
||||
hasChildren = n.children.len() > 0
|
||||
isPersistent = n.persistent
|
||||
for _, p := range n.parents.all() {
|
||||
parents = append(parents, p)
|
||||
lockme = append(lockme, p.parent)
|
||||
}
|
||||
n.mu.Unlock()
|
||||
|
||||
if hasLookups || hasChildren || isPersistent {
|
||||
return
|
||||
}
|
||||
|
||||
lockNodes(lockme...)
|
||||
if n.changeCounter != nChange {
|
||||
unlockNodes(lockme...)
|
||||
// could avoid unlocking and relocking n here.
|
||||
n.mu.Lock()
|
||||
continue retry
|
||||
}
|
||||
|
||||
for _, p := range parents {
|
||||
parentNode := p.parent
|
||||
if parentNode.children.get(p.name) != n {
|
||||
// another node has replaced us already
|
||||
continue
|
||||
}
|
||||
parentNode.children.del(p.parent, p.name)
|
||||
|
||||
if parentNode.children.len() == 0 && parentNode.lookupCount == 0 && !parentNode.persistent {
|
||||
unusedParents = append(unusedParents, parentNode)
|
||||
}
|
||||
}
|
||||
|
||||
if n.lookupCount != 0 {
|
||||
log.Panicf("n%d %p lookupCount changed: %d", n.nodeId, n, n.lookupCount)
|
||||
}
|
||||
|
||||
unlockNodes(lockme...)
|
||||
break
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetChild returns a child node with the given name, or nil if the
|
||||
// directory has no child by that name.
|
||||
func (n *Inode) GetChild(name string) *Inode {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
return n.children.get(name)
|
||||
}
|
||||
|
||||
// AddChild adds a child to this node. If overwrite is false, fail if
|
||||
// the destination already exists.
|
||||
func (n *Inode) AddChild(name string, ch *Inode, overwrite bool) (success bool) {
|
||||
if len(name) == 0 {
|
||||
log.Panic("empty name for inode")
|
||||
}
|
||||
|
||||
retry:
|
||||
for {
|
||||
lockNode2(n, ch)
|
||||
prev := n.children.get(name)
|
||||
parentCounter := n.changeCounter
|
||||
if prev == nil {
|
||||
n.children.set(n, name, ch)
|
||||
unlockNode2(n, ch)
|
||||
return true
|
||||
}
|
||||
unlockNode2(n, ch)
|
||||
if !overwrite {
|
||||
return false
|
||||
}
|
||||
lockme := [3]*Inode{n, ch, prev}
|
||||
|
||||
lockNodes(lockme[:]...)
|
||||
if parentCounter != n.changeCounter {
|
||||
unlockNodes(lockme[:]...)
|
||||
continue retry
|
||||
}
|
||||
|
||||
prev.parents.delete(parentData{name, n})
|
||||
n.children.set(n, name, ch)
|
||||
prev.changeCounter++
|
||||
unlockNodes(lockme[:]...)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Children returns the list of children of this directory Inode.
|
||||
func (n *Inode) Children() map[string]*Inode {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
return n.children.toMap()
|
||||
}
|
||||
|
||||
// childrenList returns the list of children of this directory Inode.
|
||||
// The result is guaranteed to be stable as long as the directory did
|
||||
// not change.
|
||||
func (n *Inode) childrenList() []childEntry {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
return n.children.list()
|
||||
}
|
||||
|
||||
// Parents returns a parent of this Inode, or nil if this Inode is
|
||||
// deleted or is the root
|
||||
func (n *Inode) Parent() (string, *Inode) {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
p := n.parents.get()
|
||||
if p == nil {
|
||||
return "", nil
|
||||
}
|
||||
return p.name, p.parent
|
||||
}
|
||||
|
||||
// RmAllChildren recursively drops a tree, forgetting all persistent
|
||||
// nodes.
|
||||
func (n *Inode) RmAllChildren() {
|
||||
for {
|
||||
chs := n.Children()
|
||||
if len(chs) == 0 {
|
||||
break
|
||||
}
|
||||
for nm, ch := range chs {
|
||||
ch.RmAllChildren()
|
||||
n.RmChild(nm)
|
||||
}
|
||||
}
|
||||
n.removeRef(0, true)
|
||||
}
|
||||
|
||||
// RmChild removes multiple children. Returns whether the removal
|
||||
// succeeded and whether the node is still live afterward. The removal
|
||||
// is transactional: it only succeeds if all names are children, and
|
||||
// if they all were removed successfully. If the removal was
|
||||
// successful, and there are no children left, the node may be removed
|
||||
// from the FS tree. In that case, RmChild returns live==false.
|
||||
func (n *Inode) RmChild(names ...string) (success, live bool) {
|
||||
var lockme []*Inode
|
||||
|
||||
retry:
|
||||
for {
|
||||
n.mu.Lock()
|
||||
lockme = append(lockme[:0], n)
|
||||
nChange := n.changeCounter
|
||||
for _, nm := range names {
|
||||
ch := n.children.get(nm)
|
||||
if ch == nil {
|
||||
n.mu.Unlock()
|
||||
return false, true
|
||||
}
|
||||
lockme = append(lockme, ch)
|
||||
}
|
||||
n.mu.Unlock()
|
||||
|
||||
lockNodes(lockme...)
|
||||
|
||||
if n.changeCounter != nChange {
|
||||
unlockNodes(lockme...)
|
||||
continue retry
|
||||
}
|
||||
|
||||
for _, nm := range names {
|
||||
n.children.del(n, nm)
|
||||
}
|
||||
|
||||
live = n.lookupCount > 0 || n.children.len() > 0 || n.persistent
|
||||
unlockNodes(lockme...)
|
||||
|
||||
// removal successful
|
||||
break
|
||||
}
|
||||
|
||||
if !live {
|
||||
hasLookups, isPersistent, hasChildren := n.removeRef(0, false)
|
||||
return true, (hasLookups || isPersistent || hasChildren)
|
||||
}
|
||||
|
||||
return true, true
|
||||
}
|
||||
|
||||
// MvChild executes a rename. If overwrite is set, a child at the
|
||||
// destination will be overwritten, should it exist. It returns false
|
||||
// if 'overwrite' is false, and the destination exists.
|
||||
func (n *Inode) MvChild(old string, newParent *Inode, newName string, overwrite bool) bool {
|
||||
if len(newName) == 0 {
|
||||
log.Panicf("empty newName for MvChild")
|
||||
}
|
||||
|
||||
retry:
|
||||
for {
|
||||
lockNode2(n, newParent)
|
||||
counter1 := n.changeCounter
|
||||
counter2 := newParent.changeCounter
|
||||
|
||||
oldChild := n.children.get(old)
|
||||
destChild := newParent.children.get(newName)
|
||||
unlockNode2(n, newParent)
|
||||
|
||||
if destChild != nil && !overwrite {
|
||||
return false
|
||||
}
|
||||
|
||||
lockNodes(n, newParent, oldChild, destChild)
|
||||
if counter2 != newParent.changeCounter || counter1 != n.changeCounter {
|
||||
unlockNodes(n, newParent, oldChild, destChild)
|
||||
continue retry
|
||||
}
|
||||
|
||||
if oldChild != nil {
|
||||
n.children.del(n, old)
|
||||
}
|
||||
|
||||
if destChild != nil {
|
||||
// This can cause the child to be slated for
|
||||
// removal; see below
|
||||
newParent.children.del(newParent, newName)
|
||||
}
|
||||
|
||||
if oldChild != nil {
|
||||
newParent.children.set(newParent, newName, oldChild)
|
||||
}
|
||||
|
||||
unlockNodes(n, newParent, oldChild, destChild)
|
||||
|
||||
if destChild != nil {
|
||||
destChild.removeRef(0, false)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// ExchangeChild swaps the entries at (n, oldName) and (newParent,
|
||||
// newName).
|
||||
func (n *Inode) ExchangeChild(oldName string, newParent *Inode, newName string) {
|
||||
oldParent := n
|
||||
retry:
|
||||
for {
|
||||
lockNode2(oldParent, newParent)
|
||||
counter1 := oldParent.changeCounter
|
||||
counter2 := newParent.changeCounter
|
||||
|
||||
oldChild := oldParent.children.get(oldName)
|
||||
destChild := newParent.children.get(newName)
|
||||
unlockNode2(oldParent, newParent)
|
||||
|
||||
if destChild == oldChild {
|
||||
return
|
||||
}
|
||||
|
||||
lockNodes(oldParent, newParent, oldChild, destChild)
|
||||
if counter2 != newParent.changeCounter || counter1 != oldParent.changeCounter {
|
||||
unlockNodes(oldParent, newParent, oldChild, destChild)
|
||||
continue retry
|
||||
}
|
||||
|
||||
// Detach
|
||||
if oldChild != nil {
|
||||
oldParent.children.del(oldParent, oldName)
|
||||
}
|
||||
|
||||
if destChild != nil {
|
||||
newParent.children.del(newParent, newName)
|
||||
}
|
||||
|
||||
// Attach
|
||||
if oldChild != nil {
|
||||
newParent.children.set(newParent, newName, oldChild)
|
||||
}
|
||||
|
||||
if destChild != nil {
|
||||
oldParent.children.set(oldParent, oldName, destChild)
|
||||
}
|
||||
unlockNodes(oldParent, newParent, oldChild, destChild)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// NotifyEntry notifies the kernel that data for a (directory, name)
|
||||
// tuple should be invalidated. On next access, a LOOKUP operation
|
||||
// will be started.
|
||||
func (n *Inode) NotifyEntry(name string) syscall.Errno {
|
||||
status := n.bridge.server.EntryNotify(n.nodeId, name)
|
||||
return syscall.Errno(status)
|
||||
}
|
||||
|
||||
// NotifyDelete notifies the kernel that the given inode was removed
|
||||
// from this directory as entry under the given name. It is equivalent
|
||||
// to NotifyEntry, but also sends an event to inotify watchers.
|
||||
func (n *Inode) NotifyDelete(name string, child *Inode) syscall.Errno {
|
||||
// XXX arg ordering?
|
||||
return syscall.Errno(n.bridge.server.DeleteNotify(n.nodeId, child.nodeId, name))
|
||||
|
||||
}
|
||||
|
||||
// NotifyContent notifies the kernel that content under the given
|
||||
// inode should be flushed from buffers.
|
||||
func (n *Inode) NotifyContent(off, sz int64) syscall.Errno {
|
||||
// XXX how does this work for directories?
|
||||
return syscall.Errno(n.bridge.server.InodeNotify(n.nodeId, off, sz))
|
||||
}
|
||||
|
||||
// WriteCache stores data in the kernel cache.
|
||||
func (n *Inode) WriteCache(offset int64, data []byte) syscall.Errno {
|
||||
return syscall.Errno(n.bridge.server.InodeNotifyStoreCache(n.nodeId, offset, data))
|
||||
}
|
||||
|
||||
// ReadCache reads data from the kernel cache.
|
||||
func (n *Inode) ReadCache(offset int64, dest []byte) (count int, errno syscall.Errno) {
|
||||
c, s := n.bridge.server.InodeRetrieveCache(n.nodeId, offset, dest)
|
||||
return c, syscall.Errno(s)
|
||||
}
|
||||
133
vendor/github.com/hanwen/go-fuse/v2/fs/inode_children.go
generated
vendored
Normal file
133
vendor/github.com/hanwen/go-fuse/v2/fs/inode_children.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2023 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type childEntry struct {
|
||||
Name string
|
||||
Inode *Inode
|
||||
|
||||
// TODO: store int64 changeCounter of the parent, so we can
|
||||
// use the changeCounter as a directory offset.
|
||||
}
|
||||
|
||||
// inodeChildren is a hashmap with deterministic ordering. It is
|
||||
// important to return the children in a deterministic order for 2
|
||||
// reasons:
|
||||
//
|
||||
// 1. if the ordering is non-deterministic, multiple concurrent
|
||||
// readdirs can lead to cache corruption (see issue #391)
|
||||
//
|
||||
// 2. it simplifies the implementation of directory seeking: the NFS
|
||||
// protocol doesn't open and close directories. Instead, a directory
|
||||
// read must always be continued from a previously handed out offset.
|
||||
//
|
||||
// By storing the entries in insertion order, and marking them with a
|
||||
// int64 logical timestamp, the logical timestamp can serve as readdir
|
||||
// cookie.
|
||||
type inodeChildren struct {
|
||||
// index into children slice.
|
||||
childrenMap map[string]int
|
||||
children []childEntry
|
||||
}
|
||||
|
||||
func (c *inodeChildren) init() {
|
||||
c.childrenMap = make(map[string]int)
|
||||
}
|
||||
|
||||
func (c *inodeChildren) String() string {
|
||||
var ss []string
|
||||
for _, e := range c.children {
|
||||
ch := e.Inode
|
||||
ss = append(ss, fmt.Sprintf("%q=i%d[%s]", e.Name, ch.stableAttr.Ino, modeStr(ch.stableAttr.Mode)))
|
||||
}
|
||||
return strings.Join(ss, ",")
|
||||
}
|
||||
|
||||
func (c *inodeChildren) get(name string) *Inode {
|
||||
idx, ok := c.childrenMap[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.children[idx].Inode
|
||||
}
|
||||
|
||||
func (c *inodeChildren) compact() {
|
||||
nc := make([]childEntry, 0, 2*len(c.childrenMap)+1)
|
||||
nm := make(map[string]int, len(c.childrenMap))
|
||||
for _, e := range c.children {
|
||||
if e.Inode == nil {
|
||||
continue
|
||||
}
|
||||
nm[e.Name] = len(nc)
|
||||
nc = append(nc, e)
|
||||
}
|
||||
|
||||
c.childrenMap = nm
|
||||
c.children = nc
|
||||
}
|
||||
|
||||
func (c *inodeChildren) set(parent *Inode, name string, ch *Inode) {
|
||||
idx, ok := c.childrenMap[name]
|
||||
if !ok {
|
||||
if cap(c.children) == len(c.children) {
|
||||
c.compact()
|
||||
}
|
||||
|
||||
idx = len(c.children)
|
||||
c.children = append(c.children, childEntry{})
|
||||
}
|
||||
|
||||
c.childrenMap[name] = idx
|
||||
c.children[idx] = childEntry{Name: name, Inode: ch}
|
||||
parent.changeCounter++
|
||||
|
||||
ch.parents.add(parentData{name, parent})
|
||||
ch.changeCounter++
|
||||
}
|
||||
|
||||
func (c *inodeChildren) len() int {
|
||||
return len(c.childrenMap)
|
||||
}
|
||||
|
||||
func (c *inodeChildren) toMap() map[string]*Inode {
|
||||
r := make(map[string]*Inode, len(c.childrenMap))
|
||||
for _, e := range c.children {
|
||||
if e.Inode != nil {
|
||||
r[e.Name] = e.Inode
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *inodeChildren) del(parent *Inode, name string) {
|
||||
idx, ok := c.childrenMap[name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
ch := c.children[idx].Inode
|
||||
|
||||
delete(c.childrenMap, name)
|
||||
c.children[idx] = childEntry{}
|
||||
ch.parents.delete(parentData{name, parent})
|
||||
ch.changeCounter++
|
||||
parent.changeCounter++
|
||||
}
|
||||
|
||||
func (c *inodeChildren) list() []childEntry {
|
||||
r := make([]childEntry, 0, len(c.childrenMap))
|
||||
for _, e := range c.children {
|
||||
if e.Inode != nil {
|
||||
r = append(r, e)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
101
vendor/github.com/hanwen/go-fuse/v2/fs/inode_parents.go
generated
vendored
Normal file
101
vendor/github.com/hanwen/go-fuse/v2/fs/inode_parents.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright 2021 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
// inodeParents stores zero or more parents of an Inode,
|
||||
// remembering which one is the most recent.
|
||||
//
|
||||
// No internal locking: the caller is responsible for preventing
|
||||
// concurrent access.
|
||||
type inodeParents struct {
|
||||
// newest is the most-recently add()'ed parent.
|
||||
// nil when we don't have any parents.
|
||||
newest *parentData
|
||||
// other are parents in addition to the newest.
|
||||
// nil or empty when we have <= 1 parents.
|
||||
other map[parentData]struct{}
|
||||
}
|
||||
|
||||
// add adds a parent to the store.
|
||||
func (p *inodeParents) add(n parentData) {
|
||||
// one and only parent
|
||||
if p.newest == nil {
|
||||
p.newest = &n
|
||||
}
|
||||
// already known as `newest`
|
||||
if *p.newest == n {
|
||||
return
|
||||
}
|
||||
// old `newest` gets displaced into `other`
|
||||
if p.other == nil {
|
||||
p.other = make(map[parentData]struct{})
|
||||
}
|
||||
p.other[*p.newest] = struct{}{}
|
||||
// new parent becomes `newest` (possibly moving up from `other`)
|
||||
delete(p.other, n)
|
||||
p.newest = &n
|
||||
}
|
||||
|
||||
// get returns the most recent parent
|
||||
// or nil if there is no parent at all.
|
||||
func (p *inodeParents) get() *parentData {
|
||||
return p.newest
|
||||
}
|
||||
|
||||
// all returns all known parents
|
||||
// or nil if there is no parent at all.
|
||||
func (p *inodeParents) all() []parentData {
|
||||
count := p.count()
|
||||
if count == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]parentData, 0, count)
|
||||
out = append(out, *p.newest)
|
||||
for i := range p.other {
|
||||
out = append(out, i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (p *inodeParents) delete(n parentData) {
|
||||
// We have zero parents, so we can't delete any.
|
||||
if p.newest == nil {
|
||||
return
|
||||
}
|
||||
// If it's not the `newest` it must be in `other` (or nowhere).
|
||||
if *p.newest != n {
|
||||
delete(p.other, n)
|
||||
return
|
||||
}
|
||||
// We want to delete `newest`, but there is no other to replace it.
|
||||
if len(p.other) == 0 {
|
||||
p.newest = nil
|
||||
return
|
||||
}
|
||||
// Move random entry from `other` over `newest`.
|
||||
var i parentData
|
||||
for i = range p.other {
|
||||
p.newest = &i
|
||||
break
|
||||
}
|
||||
delete(p.other, i)
|
||||
}
|
||||
|
||||
func (p *inodeParents) clear() {
|
||||
p.newest = nil
|
||||
p.other = nil
|
||||
}
|
||||
|
||||
func (p *inodeParents) count() int {
|
||||
if p.newest == nil {
|
||||
return 0
|
||||
}
|
||||
return 1 + len(p.other)
|
||||
}
|
||||
|
||||
type parentData struct {
|
||||
name string
|
||||
parent *Inode
|
||||
}
|
||||
549
vendor/github.com/hanwen/go-fuse/v2/fs/loopback.go
generated
vendored
Normal file
549
vendor/github.com/hanwen/go-fuse/v2/fs/loopback.go
generated
vendored
Normal file
@@ -0,0 +1,549 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"github.com/hanwen/go-fuse/v2/internal/openat"
|
||||
"github.com/hanwen/go-fuse/v2/internal/renameat"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// LoopbackRoot holds the parameters for creating a new loopback
|
||||
// filesystem. Loopback filesystem delegate their operations to an
|
||||
// underlying POSIX file system.
|
||||
type LoopbackRoot struct {
|
||||
// The path to the root of the underlying file system.
|
||||
Path string
|
||||
|
||||
// The device on which the Path resides. This must be set if
|
||||
// the underlying filesystem crosses file systems.
|
||||
Dev uint64
|
||||
|
||||
// NewNode returns a new InodeEmbedder to be used to respond
|
||||
// to a LOOKUP/CREATE/MKDIR/MKNOD opcode. If not set, use a
|
||||
// LoopbackNode.
|
||||
//
|
||||
// Deprecated: use NodeWrapChilder instead.
|
||||
NewNode func(rootData *LoopbackRoot, parent *Inode, name string, st *syscall.Stat_t) InodeEmbedder
|
||||
|
||||
// RootNode is the root of the Loopback. This must be set if
|
||||
// the Loopback file system is not the root of the FUSE
|
||||
// mount. It is set automatically by NewLoopbackRoot.
|
||||
RootNode InodeEmbedder
|
||||
}
|
||||
|
||||
func (r *LoopbackRoot) newNode(parent *Inode, name string, st *syscall.Stat_t) InodeEmbedder {
|
||||
if r.NewNode != nil {
|
||||
return r.NewNode(r, parent, name, st)
|
||||
}
|
||||
return &LoopbackNode{
|
||||
RootData: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LoopbackRoot) idFromStat(st *syscall.Stat_t) StableAttr {
|
||||
// We compose an inode number by the underlying inode, and
|
||||
// mixing in the device number. In traditional filesystems,
|
||||
// the inode numbers are small. The device numbers are also
|
||||
// small (typically 16 bit). Finally, we mask out the root
|
||||
// device number of the root, so a loopback FS that does not
|
||||
// encompass multiple mounts will reflect the inode numbers of
|
||||
// the underlying filesystem
|
||||
swapped := (uint64(st.Dev) << 32) | (uint64(st.Dev) >> 32)
|
||||
swappedRootDev := (r.Dev << 32) | (r.Dev >> 32)
|
||||
return StableAttr{
|
||||
Mode: uint32(st.Mode),
|
||||
Gen: 1,
|
||||
// This should work well for traditional backing FSes,
|
||||
// not so much for other go-fuse FS-es
|
||||
Ino: (swapped ^ swappedRootDev) ^ st.Ino,
|
||||
}
|
||||
}
|
||||
|
||||
// LoopbackNode is a filesystem node in a loopback file system. It is
|
||||
// public so it can be used as a basis for other loopback based
|
||||
// filesystems. See NewLoopbackFile or LoopbackRoot for more
|
||||
// information.
|
||||
type LoopbackNode struct {
|
||||
Inode
|
||||
|
||||
// RootData points back to the root of the loopback filesystem.
|
||||
RootData *LoopbackRoot
|
||||
}
|
||||
|
||||
// loopbackNodeEmbedder can only be implemented by the LoopbackNode
|
||||
// concrete type.
|
||||
type loopbackNodeEmbedder interface {
|
||||
loopbackNode() *LoopbackNode
|
||||
}
|
||||
|
||||
func (n *LoopbackNode) loopbackNode() *LoopbackNode {
|
||||
return n
|
||||
}
|
||||
|
||||
var _ = (NodeStatfser)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
|
||||
s := syscall.Statfs_t{}
|
||||
err := syscall.Statfs(n.path(), &s)
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
out.FromStatfsT(&s)
|
||||
return OK
|
||||
}
|
||||
|
||||
// path returns the full path to the file in the underlying file
|
||||
// system.
|
||||
func (n *LoopbackNode) root() *Inode {
|
||||
var rootNode *Inode
|
||||
if n.RootData.RootNode != nil {
|
||||
rootNode = n.RootData.RootNode.EmbeddedInode()
|
||||
} else {
|
||||
rootNode = n.Root()
|
||||
}
|
||||
|
||||
return rootNode
|
||||
}
|
||||
|
||||
// relativePath returns the path the node, relative to to the root directory
|
||||
func (n *LoopbackNode) relativePath() string {
|
||||
return n.Path(n.root())
|
||||
}
|
||||
|
||||
// path returns the absolute path to the node
|
||||
func (n *LoopbackNode) path() string {
|
||||
return filepath.Join(n.RootData.Path, n.relativePath())
|
||||
}
|
||||
|
||||
var _ = (NodeLookuper)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
|
||||
p := filepath.Join(n.path(), name)
|
||||
|
||||
st := syscall.Stat_t{}
|
||||
err := syscall.Lstat(p, &st)
|
||||
if err != nil {
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
|
||||
out.Attr.FromStat(&st)
|
||||
node := n.RootData.newNode(n.EmbeddedInode(), name, &st)
|
||||
ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st))
|
||||
return ch, 0
|
||||
}
|
||||
|
||||
// preserveOwner sets uid and gid of `path` according to the caller information
|
||||
// in `ctx`.
|
||||
func (n *LoopbackNode) preserveOwner(ctx context.Context, path string) error {
|
||||
if os.Getuid() != 0 {
|
||||
return nil
|
||||
}
|
||||
caller, ok := fuse.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return syscall.Lchown(path, int(caller.Uid), int(caller.Gid))
|
||||
}
|
||||
|
||||
var _ = (NodeMknoder)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) {
|
||||
p := filepath.Join(n.path(), name)
|
||||
err := syscall.Mknod(p, mode, intDev(rdev))
|
||||
if err != nil {
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
n.preserveOwner(ctx, p)
|
||||
st := syscall.Stat_t{}
|
||||
if err := syscall.Lstat(p, &st); err != nil {
|
||||
syscall.Rmdir(p)
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
|
||||
out.Attr.FromStat(&st)
|
||||
|
||||
node := n.RootData.newNode(n.EmbeddedInode(), name, &st)
|
||||
ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st))
|
||||
|
||||
return ch, 0
|
||||
}
|
||||
|
||||
var _ = (NodeMkdirer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) {
|
||||
p := filepath.Join(n.path(), name)
|
||||
err := os.Mkdir(p, os.FileMode(mode))
|
||||
if err != nil {
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
n.preserveOwner(ctx, p)
|
||||
st := syscall.Stat_t{}
|
||||
if err := syscall.Lstat(p, &st); err != nil {
|
||||
syscall.Rmdir(p)
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
|
||||
out.Attr.FromStat(&st)
|
||||
|
||||
node := n.RootData.newNode(n.EmbeddedInode(), name, &st)
|
||||
ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st))
|
||||
|
||||
return ch, 0
|
||||
}
|
||||
|
||||
var _ = (NodeRmdirer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Rmdir(ctx context.Context, name string) syscall.Errno {
|
||||
p := filepath.Join(n.path(), name)
|
||||
err := syscall.Rmdir(p)
|
||||
return ToErrno(err)
|
||||
}
|
||||
|
||||
var _ = (NodeUnlinker)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Unlink(ctx context.Context, name string) syscall.Errno {
|
||||
p := filepath.Join(n.path(), name)
|
||||
err := syscall.Unlink(p)
|
||||
return ToErrno(err)
|
||||
}
|
||||
|
||||
var _ = (NodeRenamer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Rename(ctx context.Context, name string, newParent InodeEmbedder, newName string, flags uint32) syscall.Errno {
|
||||
e2, ok := newParent.(loopbackNodeEmbedder)
|
||||
if !ok {
|
||||
return syscall.EXDEV
|
||||
}
|
||||
|
||||
if e2.loopbackNode().RootData != n.RootData {
|
||||
return syscall.EXDEV
|
||||
}
|
||||
|
||||
if flags != 0 {
|
||||
return n.rename2(name, e2.loopbackNode(), newName, flags)
|
||||
}
|
||||
|
||||
p1 := filepath.Join(n.path(), name)
|
||||
p2 := filepath.Join(e2.loopbackNode().path(), newName)
|
||||
|
||||
err := syscall.Rename(p1, p2)
|
||||
return ToErrno(err)
|
||||
}
|
||||
|
||||
var _ = (NodeCreater)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (inode *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno) {
|
||||
p := filepath.Join(n.path(), name)
|
||||
flags = flags &^ syscall.O_APPEND
|
||||
fd, err := syscall.Open(p, int(flags)|os.O_CREATE, mode)
|
||||
if err != nil {
|
||||
return nil, nil, 0, ToErrno(err)
|
||||
}
|
||||
n.preserveOwner(ctx, p)
|
||||
st := syscall.Stat_t{}
|
||||
if err := syscall.Fstat(fd, &st); err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, nil, 0, ToErrno(err)
|
||||
}
|
||||
|
||||
node := n.RootData.newNode(n.EmbeddedInode(), name, &st)
|
||||
ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st))
|
||||
lf := NewLoopbackFile(fd)
|
||||
|
||||
out.FromStat(&st)
|
||||
return ch, lf, 0, 0
|
||||
}
|
||||
|
||||
func (n *LoopbackNode) rename2(name string, newParent *LoopbackNode, newName string, flags uint32) syscall.Errno {
|
||||
fd1, err := syscall.Open(n.path(), syscall.O_DIRECTORY, 0)
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
defer syscall.Close(fd1)
|
||||
p2 := newParent.path()
|
||||
fd2, err := syscall.Open(p2, syscall.O_DIRECTORY, 0)
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
defer syscall.Close(fd2)
|
||||
|
||||
var st syscall.Stat_t
|
||||
if err := syscall.Fstat(fd1, &st); err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
|
||||
// Double check that nodes didn't change from under us.
|
||||
if n.root() != n.EmbeddedInode() && n.Inode.StableAttr().Ino != n.RootData.idFromStat(&st).Ino {
|
||||
return syscall.EBUSY
|
||||
}
|
||||
if err := syscall.Fstat(fd2, &st); err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
|
||||
if (newParent.root() != newParent.EmbeddedInode()) && newParent.Inode.StableAttr().Ino != n.RootData.idFromStat(&st).Ino {
|
||||
return syscall.EBUSY
|
||||
}
|
||||
|
||||
return ToErrno(renameat.Renameat(fd1, name, fd2, newName, uint(flags)))
|
||||
}
|
||||
|
||||
var _ = (NodeSymlinker)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
|
||||
p := filepath.Join(n.path(), name)
|
||||
err := syscall.Symlink(target, p)
|
||||
if err != nil {
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
n.preserveOwner(ctx, p)
|
||||
st := syscall.Stat_t{}
|
||||
if err := syscall.Lstat(p, &st); err != nil {
|
||||
syscall.Unlink(p)
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
node := n.RootData.newNode(n.EmbeddedInode(), name, &st)
|
||||
ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st))
|
||||
|
||||
out.Attr.FromStat(&st)
|
||||
return ch, 0
|
||||
}
|
||||
|
||||
var _ = (NodeLinker)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
|
||||
|
||||
p := filepath.Join(n.path(), name)
|
||||
err := syscall.Link(filepath.Join(n.RootData.Path, target.EmbeddedInode().Path(nil)), p)
|
||||
if err != nil {
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
st := syscall.Stat_t{}
|
||||
if err := syscall.Lstat(p, &st); err != nil {
|
||||
syscall.Unlink(p)
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
node := n.RootData.newNode(n.EmbeddedInode(), name, &st)
|
||||
ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st))
|
||||
|
||||
out.Attr.FromStat(&st)
|
||||
return ch, 0
|
||||
}
|
||||
|
||||
var _ = (NodeReadlinker)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
|
||||
p := n.path()
|
||||
|
||||
for l := 256; ; l *= 2 {
|
||||
buf := make([]byte, l)
|
||||
sz, err := syscall.Readlink(p, buf)
|
||||
if err != nil {
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
|
||||
if sz < len(buf) {
|
||||
return buf[:sz], 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var _ = (NodeOpener)((*LoopbackNode)(nil))
|
||||
|
||||
// Symlink-safe through use of OpenSymlinkAware.
|
||||
func (n *LoopbackNode) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) {
|
||||
flags = flags &^ (syscall.O_APPEND | fuse.FMODE_EXEC)
|
||||
|
||||
f, err := openat.OpenSymlinkAware(n.RootData.Path, n.relativePath(), int(flags), 0)
|
||||
if err != nil {
|
||||
return nil, 0, ToErrno(err)
|
||||
}
|
||||
lf := NewLoopbackFile(f)
|
||||
return lf, 0, 0
|
||||
}
|
||||
|
||||
var _ = (NodeOpendirHandler)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) OpendirHandle(ctx context.Context, flags uint32) (FileHandle, uint32, syscall.Errno) {
|
||||
ds, errno := NewLoopbackDirStream(n.path())
|
||||
if errno != 0 {
|
||||
return nil, 0, errno
|
||||
}
|
||||
return ds, 0, errno
|
||||
}
|
||||
|
||||
var _ = (NodeReaddirer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Readdir(ctx context.Context) (DirStream, syscall.Errno) {
|
||||
return NewLoopbackDirStream(n.path())
|
||||
}
|
||||
|
||||
var _ = (NodeGetattrer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno {
|
||||
if f != nil {
|
||||
if fga, ok := f.(FileGetattrer); ok {
|
||||
return fga.Getattr(ctx, out)
|
||||
}
|
||||
}
|
||||
|
||||
p := n.path()
|
||||
|
||||
var err error
|
||||
st := syscall.Stat_t{}
|
||||
if &n.Inode == n.Root() {
|
||||
err = syscall.Stat(p, &st)
|
||||
} else {
|
||||
err = syscall.Lstat(p, &st)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
out.FromStat(&st)
|
||||
return OK
|
||||
}
|
||||
|
||||
var _ = (NodeSetattrer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
|
||||
p := n.path()
|
||||
fsa, ok := f.(FileSetattrer)
|
||||
if ok && fsa != nil {
|
||||
fsa.Setattr(ctx, in, out)
|
||||
} else {
|
||||
if m, ok := in.GetMode(); ok {
|
||||
if err := syscall.Chmod(p, m); err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
}
|
||||
|
||||
uid, uok := in.GetUID()
|
||||
gid, gok := in.GetGID()
|
||||
if uok || gok {
|
||||
suid := -1
|
||||
sgid := -1
|
||||
if uok {
|
||||
suid = int(uid)
|
||||
}
|
||||
if gok {
|
||||
sgid = int(gid)
|
||||
}
|
||||
if err := syscall.Chown(p, suid, sgid); err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
}
|
||||
|
||||
mtime, mok := in.GetMTime()
|
||||
atime, aok := in.GetATime()
|
||||
|
||||
if mok || aok {
|
||||
ta := unix.Timespec{Nsec: unix_UTIME_OMIT}
|
||||
tm := unix.Timespec{Nsec: unix_UTIME_OMIT}
|
||||
var err error
|
||||
if aok {
|
||||
ta, err = unix.TimeToTimespec(atime)
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
}
|
||||
if mok {
|
||||
tm, err = unix.TimeToTimespec(mtime)
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
}
|
||||
ts := []unix.Timespec{ta, tm}
|
||||
if err := unix.UtimesNanoAt(unix.AT_FDCWD, p, ts, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
}
|
||||
|
||||
if sz, ok := in.GetSize(); ok {
|
||||
if err := syscall.Truncate(p, int64(sz)); err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fga, ok := f.(FileGetattrer)
|
||||
if ok && fga != nil {
|
||||
fga.Getattr(ctx, out)
|
||||
} else {
|
||||
st := syscall.Stat_t{}
|
||||
err := syscall.Lstat(p, &st)
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
out.FromStat(&st)
|
||||
}
|
||||
return OK
|
||||
}
|
||||
|
||||
var _ = (NodeGetxattrer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
|
||||
sz, err := unix.Lgetxattr(n.path(), attr, dest)
|
||||
return uint32(sz), ToErrno(err)
|
||||
}
|
||||
|
||||
var _ = (NodeSetxattrer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno {
|
||||
err := unix.Lsetxattr(n.path(), attr, data, int(flags))
|
||||
return ToErrno(err)
|
||||
}
|
||||
|
||||
var _ = (NodeRemovexattrer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno {
|
||||
err := unix.Lremovexattr(n.path(), attr)
|
||||
return ToErrno(err)
|
||||
}
|
||||
|
||||
var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle,
|
||||
offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
|
||||
len uint64, flags uint64) (uint32, syscall.Errno) {
|
||||
lfIn, ok := fhIn.(*loopbackFile)
|
||||
if !ok {
|
||||
return 0, unix.ENOTSUP
|
||||
}
|
||||
lfOut, ok := fhOut.(*loopbackFile)
|
||||
if !ok {
|
||||
return 0, unix.ENOTSUP
|
||||
}
|
||||
signedOffIn := int64(offIn)
|
||||
signedOffOut := int64(offOut)
|
||||
doCopyFileRange(lfIn.fd, signedOffIn, lfOut.fd, signedOffOut, int(len), int(flags))
|
||||
return 0, syscall.ENOSYS
|
||||
}
|
||||
|
||||
// NewLoopbackRoot returns a root node for a loopback file system whose
|
||||
// root is at the given root. This node implements all NodeXxxxer
|
||||
// operations available.
|
||||
func NewLoopbackRoot(rootPath string) (InodeEmbedder, error) {
|
||||
var st syscall.Stat_t
|
||||
err := syscall.Stat(rootPath, &st)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
root := &LoopbackRoot{
|
||||
Path: rootPath,
|
||||
Dev: uint64(st.Dev),
|
||||
}
|
||||
|
||||
rootNode := root.newNode(nil, "", &st)
|
||||
root.RootNode = rootNode
|
||||
return rootNode, nil
|
||||
}
|
||||
36
vendor/github.com/hanwen/go-fuse/v2/fs/loopback_darwin.go
generated
vendored
Normal file
36
vendor/github.com/hanwen/go-fuse/v2/fs/loopback_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const unix_UTIME_OMIT = 0x0
|
||||
|
||||
// timeToTimeval - Convert time.Time to syscall.Timeval
|
||||
//
|
||||
// Note: This does not use syscall.NsecToTimespec because
|
||||
// that does not work properly for times before 1970,
|
||||
// see https://github.com/golang/go/issues/12777
|
||||
func timeToTimeval(t *time.Time) syscall.Timeval {
|
||||
var tv syscall.Timeval
|
||||
tv.Usec = int32(t.Nanosecond() / 1000)
|
||||
tv.Sec = t.Unix()
|
||||
return tv
|
||||
}
|
||||
|
||||
func doCopyFileRange(fdIn int, offIn int64, fdOut int, offOut int64,
|
||||
len int, flags int) (uint32, syscall.Errno) {
|
||||
return 0, syscall.ENOSYS
|
||||
}
|
||||
|
||||
func intDev(dev uint32) int {
|
||||
return int(dev)
|
||||
}
|
||||
80
vendor/github.com/hanwen/go-fuse/v2/fs/loopback_freebsd.go
generated
vendored
Normal file
80
vendor/github.com/hanwen/go-fuse/v2/fs/loopback_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/internal/xattr"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const unix_UTIME_OMIT = unix.UTIME_OMIT
|
||||
|
||||
// FreeBSD has added copy_file_range(2) since FreeBSD 12. However,
|
||||
// golang.org/x/sys/unix hasn't add corresponding syscall constant or
|
||||
// wrap function. Here we define the syscall constant until sys/unix
|
||||
// provides.
|
||||
const sys_COPY_FILE_RANGE = 569
|
||||
|
||||
// TODO: replace the manual syscall when sys/unix provides CopyFileRange
|
||||
// for FreeBSD
|
||||
func doCopyFileRange(fdIn int, offIn int64, fdOut int, offOut int64,
|
||||
len int, flags int) (uint32, syscall.Errno) {
|
||||
count, _, errno := unix.Syscall6(sys_COPY_FILE_RANGE,
|
||||
uintptr(fdIn), uintptr(offIn), uintptr(fdOut), uintptr(offOut),
|
||||
uintptr(len), uintptr(flags),
|
||||
)
|
||||
return uint32(count), errno
|
||||
}
|
||||
|
||||
func intDev(dev uint32) uint64 {
|
||||
return uint64(dev)
|
||||
}
|
||||
|
||||
// Since FUSE on FreeBSD expect Linux flavor data format of
|
||||
// listxattr, we should reconstruct it with data returned by
|
||||
// FreeBSD's syscall. And here we have added a "user." prefix
|
||||
// to put them under "user" namespace, which is readable and
|
||||
// writable for normal user, for a userspace implemented FS.
|
||||
func rebuildAttrBuf(attrList [][]byte) []byte {
|
||||
ret := make([]byte, 0)
|
||||
for _, attrName := range attrList {
|
||||
nsAttrName := append([]byte("user."), attrName...)
|
||||
ret = append(ret, nsAttrName...)
|
||||
ret = append(ret, 0x0)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
var _ = (NodeListxattrer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
|
||||
// In order to simulate same data format as Linux does,
|
||||
// and the size of returned buf is required to match, we must
|
||||
// call unix.Llistxattr twice.
|
||||
sz, err := unix.Llistxattr(n.path(), nil)
|
||||
if err != nil {
|
||||
return uint32(sz), ToErrno(err)
|
||||
}
|
||||
rawBuf := make([]byte, sz)
|
||||
sz, err = unix.Llistxattr(n.path(), rawBuf)
|
||||
if err != nil {
|
||||
return uint32(sz), ToErrno(err)
|
||||
}
|
||||
attrList := xattr.ParseAttrNames(rawBuf)
|
||||
rebuiltBuf := rebuildAttrBuf(attrList)
|
||||
sz = len(rebuiltBuf)
|
||||
if len(dest) != 0 {
|
||||
// When len(dest) is 0, which means that caller wants to get
|
||||
// the size. If len(dest) is less than len(rebuiltBuf), but greater
|
||||
// than 0 dest will be also filled with data from rebuiltBuf,
|
||||
// but truncated to len(dest). copy() function will do the same.
|
||||
// And this behaviour is same as FreeBSD's syscall extattr_list_file(2).
|
||||
sz = copy(dest, rebuiltBuf)
|
||||
}
|
||||
return uint32(sz), ToErrno(err)
|
||||
}
|
||||
50
vendor/github.com/hanwen/go-fuse/v2/fs/loopback_linux.go
generated
vendored
Normal file
50
vendor/github.com/hanwen/go-fuse/v2/fs/loopback_linux.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const unix_UTIME_OMIT = unix.UTIME_OMIT
|
||||
|
||||
func doCopyFileRange(fdIn int, offIn int64, fdOut int, offOut int64,
|
||||
len int, flags int) (uint32, syscall.Errno) {
|
||||
count, err := unix.CopyFileRange(fdIn, &offIn, fdOut, &offOut, len, flags)
|
||||
return uint32(count), ToErrno(err)
|
||||
}
|
||||
|
||||
func intDev(dev uint32) int {
|
||||
return int(dev)
|
||||
}
|
||||
|
||||
var _ = (NodeStatxer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Statx(ctx context.Context, f FileHandle,
|
||||
flags uint32, mask uint32,
|
||||
out *fuse.StatxOut) syscall.Errno {
|
||||
if f != nil {
|
||||
if fga, ok := f.(FileStatxer); ok {
|
||||
return fga.Statx(ctx, flags, mask, out)
|
||||
}
|
||||
}
|
||||
|
||||
p := n.path()
|
||||
|
||||
st := unix.Statx_t{}
|
||||
err := unix.Statx(unix.AT_FDCWD, p, int(flags), int(mask), &st)
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
out.FromStatx(&st)
|
||||
return OK
|
||||
}
|
||||
20
vendor/github.com/hanwen/go-fuse/v2/fs/loopback_unix.go
generated
vendored
Normal file
20
vendor/github.com/hanwen/go-fuse/v2/fs/loopback_unix.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
//go:build !freebsd
|
||||
|
||||
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var _ = (NodeListxattrer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
|
||||
sz, err := unix.Llistxattr(n.path(), dest)
|
||||
return uint32(sz), ToErrno(err)
|
||||
}
|
||||
121
vendor/github.com/hanwen/go-fuse/v2/fs/mem.go
generated
vendored
Normal file
121
vendor/github.com/hanwen/go-fuse/v2/fs/mem.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
)
|
||||
|
||||
// MemRegularFile is a filesystem node that holds a data
|
||||
// slice in memory.
|
||||
type MemRegularFile struct {
|
||||
Inode
|
||||
|
||||
mu sync.Mutex
|
||||
Data []byte
|
||||
Attr fuse.Attr
|
||||
}
|
||||
|
||||
var _ = (NodeOpener)((*MemRegularFile)(nil))
|
||||
var _ = (NodeReader)((*MemRegularFile)(nil))
|
||||
var _ = (NodeWriter)((*MemRegularFile)(nil))
|
||||
var _ = (NodeSetattrer)((*MemRegularFile)(nil))
|
||||
var _ = (NodeFlusher)((*MemRegularFile)(nil))
|
||||
var _ = (NodeAllocater)((*MemRegularFile)(nil))
|
||||
|
||||
func (f *MemRegularFile) Allocate(ctx context.Context, fh FileHandle, off uint64, size uint64, mode uint32) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
oldSz := len(f.Data)
|
||||
if uint64(cap(f.Data)) < off+size {
|
||||
n := make([]byte, off+size)
|
||||
copy(n, f.Data)
|
||||
f.Data = n
|
||||
}
|
||||
if keepSizeMode(mode) {
|
||||
f.Data = f.Data[:oldSz]
|
||||
} else if len(f.Data) < int(off+size) {
|
||||
f.Data = f.Data[:off+size]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *MemRegularFile) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) {
|
||||
return nil, fuse.FOPEN_KEEP_CACHE, OK
|
||||
}
|
||||
|
||||
func (f *MemRegularFile) Write(ctx context.Context, fh FileHandle, data []byte, off int64) (uint32, syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
end := int64(len(data)) + off
|
||||
if int64(len(f.Data)) < end {
|
||||
n := make([]byte, end)
|
||||
copy(n, f.Data)
|
||||
f.Data = n
|
||||
}
|
||||
|
||||
copy(f.Data[off:off+int64(len(data))], data)
|
||||
|
||||
return uint32(len(data)), 0
|
||||
}
|
||||
|
||||
var _ = (NodeGetattrer)((*MemRegularFile)(nil))
|
||||
|
||||
func (f *MemRegularFile) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
out.Attr = f.Attr
|
||||
out.Attr.Size = uint64(len(f.Data))
|
||||
return OK
|
||||
}
|
||||
|
||||
func (f *MemRegularFile) Setattr(ctx context.Context, fh FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if sz, ok := in.GetSize(); ok {
|
||||
f.Data = f.Data[:sz]
|
||||
}
|
||||
out.Attr = f.Attr
|
||||
out.Size = uint64(len(f.Data))
|
||||
return OK
|
||||
}
|
||||
|
||||
func (f *MemRegularFile) Flush(ctx context.Context, fh FileHandle) syscall.Errno {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *MemRegularFile) Read(ctx context.Context, fh FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
end := int(off) + len(dest)
|
||||
if end > len(f.Data) {
|
||||
end = len(f.Data)
|
||||
}
|
||||
return fuse.ReadResultData(f.Data[off:end]), OK
|
||||
}
|
||||
|
||||
// MemSymlink is an inode holding a symlink in memory.
|
||||
type MemSymlink struct {
|
||||
Inode
|
||||
Attr fuse.Attr
|
||||
Data []byte
|
||||
}
|
||||
|
||||
var _ = (NodeReadlinker)((*MemSymlink)(nil))
|
||||
|
||||
func (l *MemSymlink) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
|
||||
return l.Data, OK
|
||||
}
|
||||
|
||||
var _ = (NodeGetattrer)((*MemSymlink)(nil))
|
||||
|
||||
func (l *MemSymlink) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno {
|
||||
out.Attr = l.Attr
|
||||
return OK
|
||||
}
|
||||
11
vendor/github.com/hanwen/go-fuse/v2/fs/mem_linux.go
generated
vendored
Normal file
11
vendor/github.com/hanwen/go-fuse/v2/fs/mem_linux.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright 2025 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func keepSizeMode(mode uint32) bool {
|
||||
return mode&unix.FALLOC_FL_KEEP_SIZE != 0
|
||||
}
|
||||
11
vendor/github.com/hanwen/go-fuse/v2/fs/mem_unix.go
generated
vendored
Normal file
11
vendor/github.com/hanwen/go-fuse/v2/fs/mem_unix.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
//go:build !linux
|
||||
|
||||
// Copyright 2025 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
func keepSizeMode(mode uint32) bool {
|
||||
return false
|
||||
}
|
||||
40
vendor/github.com/hanwen/go-fuse/v2/fs/mount.go
generated
vendored
Normal file
40
vendor/github.com/hanwen/go-fuse/v2/fs/mount.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
)
|
||||
|
||||
// Mount mounts the given NodeFS on the directory, and starts serving
|
||||
// requests. This is a convenience wrapper around NewNodeFS and
|
||||
// fuse.NewServer. If nil is given as options, default settings are
|
||||
// applied, which are 1 second entry and attribute timeout.
|
||||
func Mount(dir string, root InodeEmbedder, options *Options) (*fuse.Server, error) {
|
||||
if options == nil {
|
||||
oneSec := time.Second
|
||||
options = &Options{
|
||||
EntryTimeout: &oneSec,
|
||||
AttrTimeout: &oneSec,
|
||||
}
|
||||
}
|
||||
|
||||
rawFS := NewNodeFS(root, options)
|
||||
server, err := fuse.NewServer(rawFS, dir, &options.MountOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go server.Serve()
|
||||
if err := server.WaitMount(); err != nil {
|
||||
// we don't shutdown the serve loop. If the mount does
|
||||
// not succeed, the loop won't work and exit.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return server, nil
|
||||
}
|
||||
1
vendor/github.com/hanwen/go-fuse/v2/fuse/.gitignore
generated
vendored
Normal file
1
vendor/github.com/hanwen/go-fuse/v2/fuse/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
version.gen.go
|
||||
478
vendor/github.com/hanwen/go-fuse/v2/fuse/api.go
generated
vendored
Normal file
478
vendor/github.com/hanwen/go-fuse/v2/fuse/api.go
generated
vendored
Normal file
@@ -0,0 +1,478 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package fuse provides APIs to implement filesystems in
|
||||
// userspace in terms of raw FUSE protocol.
|
||||
//
|
||||
// A filesystem is implemented by implementing its server that provides a
|
||||
// RawFileSystem interface. Typically the server embeds
|
||||
// NewDefaultRawFileSystem() and implements only subset of filesystem methods:
|
||||
//
|
||||
// type MyFS struct {
|
||||
// fuse.RawFileSystem
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// func NewMyFS() *MyFS {
|
||||
// return &MyFS{
|
||||
// RawFileSystem: fuse.NewDefaultRawFileSystem(),
|
||||
// ...
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Mkdir implements "mkdir" request handler.
|
||||
// //
|
||||
// // For other requests - not explicitly implemented by MyFS - ENOSYS
|
||||
// // will be typically returned to client.
|
||||
// func (fs *MyFS) Mkdir(...) {
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// Then the filesystem can be mounted and served to a client (typically OS
|
||||
// kernel) by creating Server:
|
||||
//
|
||||
// fs := NewMyFS() // implements RawFileSystem
|
||||
// fssrv, err := fuse.NewServer(fs, mountpoint, &fuse.MountOptions{...})
|
||||
// if err != nil {
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// and letting the server do its work:
|
||||
//
|
||||
// // either synchronously - .Serve() blocks until the filesystem is unmounted.
|
||||
// fssrv.Serve()
|
||||
//
|
||||
// // or in the background - .Serve() is spawned in another goroutine, but
|
||||
// // before interacting with fssrv from current context we have to wait
|
||||
// // until the filesystem mounting is complete.
|
||||
// go fssrv.Serve()
|
||||
// err = fssrv.WaitMount()
|
||||
// if err != nil {
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// The server will serve clients by dispatching their requests to the
|
||||
// filesystem implementation and conveying responses back. For example "mkdir"
|
||||
// FUSE request dispatches to call
|
||||
//
|
||||
// fs.Mkdir(*MkdirIn, ..., *EntryOut)
|
||||
//
|
||||
// "stat" to call
|
||||
//
|
||||
// fs.GetAttr(*GetAttrIn, *AttrOut)
|
||||
//
|
||||
// etc. Please refer to RawFileSystem documentation for details.
|
||||
//
|
||||
// Typically, each call of the API happens in its own
|
||||
// goroutine, so take care to make the file system thread-safe.
|
||||
//
|
||||
// Be careful when you access the FUSE mount from the same process. An access can
|
||||
// tie up two OS threads (one on the request side and one on the FUSE server side).
|
||||
// This can deadlock if there is no free thread to handle the FUSE server side.
|
||||
// Run your program with GOMAXPROCS=1 to make the problem easier to reproduce,
|
||||
// see https://github.com/hanwen/go-fuse/issues/261 for an example of that
|
||||
// problem.
|
||||
//
|
||||
// # Higher level interfaces
|
||||
//
|
||||
// As said above this packages provides way to implement filesystems in terms of
|
||||
// raw FUSE protocol.
|
||||
//
|
||||
// Package github.com/hanwen/go-fuse/v2/fs provides way to implement
|
||||
// filesystems in terms of paths and/or inodes.
|
||||
//
|
||||
// # Mount styles
|
||||
//
|
||||
// The NewServer() handles mounting the filesystem, which
|
||||
// involves opening `/dev/fuse` and calling the
|
||||
// `mount(2)` syscall. The latter needs root permissions.
|
||||
// This is handled in one of three ways:
|
||||
//
|
||||
// 1) go-fuse opens `/dev/fuse` and executes the `fusermount`
|
||||
// setuid-root helper to call `mount(2)` for us. This is the default.
|
||||
// Does not need root permissions but needs `fusermount` installed.
|
||||
//
|
||||
// 2) If `MountOptions.DirectMount` is set, go-fuse calls `mount(2)` itself.
|
||||
// Needs root permissions, but works without `fusermount`.
|
||||
//
|
||||
// 3) If `mountPoint` has the magic `/dev/fd/N` syntax, it means that that a
|
||||
// privileged parent process:
|
||||
//
|
||||
// * Opened /dev/fuse
|
||||
//
|
||||
// * Called mount(2) on a real mountpoint directory that we don't know about
|
||||
//
|
||||
// * Inherited the fd to /dev/fuse to us
|
||||
//
|
||||
// * Informs us about the fd number via /dev/fd/N
|
||||
//
|
||||
// This magic syntax originates from libfuse [1] and allows the FUSE server to
|
||||
// run without any privileges and without needing `fusermount`, as the parent
|
||||
// process performs all privileged operations.
|
||||
//
|
||||
// The "privileged parent" is usually a container manager like Singularity [2],
|
||||
// but for testing, it can also be the `mount.fuse3` helper with the
|
||||
// `drop_privileges,setuid=$USER` flags. Example below for gocryptfs:
|
||||
//
|
||||
// $ sudo mount.fuse3 "/usr/local/bin/gocryptfs#/tmp/cipher" /tmp/mnt -o drop_privileges,setuid=$USER
|
||||
//
|
||||
// [1] https://github.com/libfuse/libfuse/commit/64e11073b9347fcf9c6d1eea143763ba9e946f70
|
||||
//
|
||||
// [2] https://sylabs.io/guides/3.7/user-guide/bind_paths_and_mounts.html#fuse-mounts
|
||||
//
|
||||
// # Aborting a file system
|
||||
//
|
||||
// A caller that has an open file in a buggy or crashed FUSE
|
||||
// filesystem will be hung. The easiest way to clean up this situation
|
||||
// is through the fusectl filesystem. By writing into
|
||||
// /sys/fs/fuse/connection/$ID/abort, reads from the FUSE device fail,
|
||||
// and all callers receive ENOTCONN (transport endpoint not connected)
|
||||
// on their pending syscalls. The FUSE connection ID can be found as
|
||||
// the Dev field in the Stat_t result for a file in the mount.
|
||||
package fuse
|
||||
|
||||
import "log"
|
||||
|
||||
// Types for users to implement.
|
||||
|
||||
// The result of Read is an array of bytes, but for performance
|
||||
// reasons, we can also return data as a file-descriptor/offset/size
|
||||
// tuple. If the backing store for a file is another filesystem, this
|
||||
// reduces the amount of copying between the kernel and the FUSE
|
||||
// server. The ReadResult interface captures both cases.
|
||||
type ReadResult interface {
|
||||
// Returns the raw bytes for the read, possibly using the
|
||||
// passed buffer. The buffer should be larger than the return
|
||||
// value from Size.
|
||||
Bytes(buf []byte) ([]byte, Status)
|
||||
|
||||
// Size returns how many bytes this return value takes at most.
|
||||
Size() int
|
||||
|
||||
// Done() is called after sending the data to the kernel.
|
||||
Done()
|
||||
}
|
||||
|
||||
type MountOptions struct {
|
||||
AllowOther bool
|
||||
|
||||
// Options are the options passed as -o string to fusermount.
|
||||
Options []string
|
||||
|
||||
// MaxBackground controls the maximum number of allowed backgruond
|
||||
// asynchronous I/O requests.
|
||||
//
|
||||
// If unset, the default is _DEFAULT_BACKGROUND_TASKS, 12.
|
||||
// Concurrency for synchronous I/O is not limited.
|
||||
MaxBackground int
|
||||
|
||||
// MaxWrite is the max size for read and write requests. If 0, use
|
||||
// go-fuse default (currently 64 kiB).
|
||||
// This number is internally capped at MAX_KERNEL_WRITE (higher values don't make
|
||||
// sense).
|
||||
//
|
||||
// Non-direct-io reads are mostly served via kernel readahead, which is
|
||||
// additionally subject to the MaxReadAhead limit.
|
||||
//
|
||||
// Implementation notes:
|
||||
//
|
||||
// There's four values the Linux kernel looks at when deciding the request size:
|
||||
// * MaxWrite, passed via InitOut.MaxWrite. Limits the WRITE size.
|
||||
// * max_read, passed via a string mount option. Limits the READ size.
|
||||
// go-fuse sets max_read equal to MaxWrite.
|
||||
// You can see the current max_read value in /proc/self/mounts .
|
||||
// * MaxPages, passed via InitOut.MaxPages. In Linux 4.20 and later, the value
|
||||
// can go up to 1 MiB and go-fuse calculates the MaxPages value acc.
|
||||
// to MaxWrite, rounding up.
|
||||
// On older kernels, the value is fixed at 128 kiB and the
|
||||
// passed value is ignored. No request can be larger than MaxPages, so
|
||||
// READ and WRITE are effectively capped at MaxPages.
|
||||
// * MaxReadAhead, passed via InitOut.MaxReadAhead.
|
||||
MaxWrite int
|
||||
|
||||
// MaxReadAhead is the max read ahead size to use. It controls how much data the
|
||||
// kernel reads in advance to satisfy future read requests from applications.
|
||||
// How much exactly is subject to clever heuristics in the kernel
|
||||
// (see https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/readahead.c?h=v6.2-rc5#n375
|
||||
// if you are brave) and hence also depends on the kernel version.
|
||||
//
|
||||
// If 0, use kernel default. This number is capped at the kernel maximum
|
||||
// (128 kiB on Linux) and cannot be larger than MaxWrite.
|
||||
//
|
||||
// MaxReadAhead only affects buffered reads (=non-direct-io), but even then, the
|
||||
// kernel can and does send larger reads to satisfy read reqests from applications
|
||||
// (up to MaxWrite or VM_READAHEAD_PAGES=128 kiB, whichever is less).
|
||||
MaxReadAhead int
|
||||
|
||||
// IgnoreSecurityLabels, if set, makes security related xattr
|
||||
// requests return NO_DATA without passing through the
|
||||
// user defined filesystem. You should only set this if you
|
||||
// file system implements extended attributes, and you are not
|
||||
// interested in security labels.
|
||||
IgnoreSecurityLabels bool // ignoring labels should be provided as a fusermount mount option.
|
||||
|
||||
// RememberInodes, if set, makes go-fuse never forget inodes.
|
||||
// This may be useful for NFS.
|
||||
RememberInodes bool
|
||||
|
||||
// FsName is the name of the filesystem, shown in "df -T"
|
||||
// and friends (as the first column, "Filesystem").
|
||||
FsName string
|
||||
|
||||
// Name is the "fuse.<name>" suffix, shown in "df -T" and friends
|
||||
// (as the second column, "Type")
|
||||
Name string
|
||||
|
||||
// SingleThreaded, if set, wraps the file system in a single-threaded
|
||||
// locking wrapper.
|
||||
SingleThreaded bool
|
||||
|
||||
// DisableXAttrs, if set, returns ENOSYS for Getxattr calls, so the kernel
|
||||
// does not issue any Xattr operations at all.
|
||||
DisableXAttrs bool
|
||||
|
||||
// Debug, if set, enables verbose debugging information.
|
||||
Debug bool
|
||||
|
||||
// Logger, if set, is an alternate log sink for debug statements.
|
||||
//
|
||||
// To increase signal/noise ratio Go-FUSE uses abbreviations in its debug log
|
||||
// output. Here is how to read it:
|
||||
//
|
||||
// - `iX` means `inode X`;
|
||||
// - `gX` means `generation X`;
|
||||
// - `tA` and `tE` means timeout for attributes and directory entry correspondingly;
|
||||
// - `[<off> +<size>)` means data range from `<off>` inclusive till `<off>+<size>` exclusive;
|
||||
// - `Xb` means `X bytes`.
|
||||
// - `pX` means the request originated from PID `x`. 0 means the request originated from the kernel.
|
||||
//
|
||||
// Every line is prefixed with either `rx <unique>` (receive from kernel) or `tx <unique>` (send to kernel)
|
||||
//
|
||||
// Example debug log output:
|
||||
//
|
||||
// rx 2: LOOKUP i1 [".wcfs"] 6b p5874
|
||||
// tx 2: OK, {i3 g2 tE=1s tA=1s {M040755 SZ=0 L=0 1000:1000 B0*0 i0:3 A 0.000000 M 0.000000 C 0.000000}}
|
||||
// rx 3: LOOKUP i3 ["zurl"] 5b p5874
|
||||
// tx 3: OK, {i4 g3 tE=1s tA=1s {M0100644 SZ=33 L=1 1000:1000 B0*0 i0:4 A 0.000000 M 0.000000 C 0.000000}}
|
||||
// rx 4: OPEN i4 {O_RDONLY,0x8000} p5874
|
||||
// tx 4: 38=function not implemented, {Fh 0 }
|
||||
// rx 5: READ i4 {Fh 0 [0 +4096) L 0 RDONLY,0x8000} p5874
|
||||
// tx 5: OK, 33b data "file:///"...
|
||||
// rx 6: GETATTR i4 {Fh 0} p5874
|
||||
// tx 6: OK, {tA=1s {M0100644 SZ=33 L=1 1000:1000 B0*0 i0:4 A 0.000000 M 0.000000 C 0.000000}}
|
||||
// rx 7: FLUSH i4 {Fh 0} p5874
|
||||
// tx 7: OK
|
||||
// rx 8: LOOKUP i1 ["head"] 5b p5874
|
||||
// tx 8: OK, {i5 g4 tE=1s tA=1s {M040755 SZ=0 L=0 1000:1000 B0*0 i0:5 A 0.000000 M 0.000000 C 0.000000}}
|
||||
// rx 9: LOOKUP i5 ["bigfile"] 8b p5874
|
||||
// tx 9: OK, {i6 g5 tE=1s tA=1s {M040755 SZ=0 L=0 1000:1000 B0*0 i0:6 A 0.000000 M 0.000000 C 0.000000}}
|
||||
// rx 10: FLUSH i4 {Fh 0} p5874
|
||||
// tx 10: OK
|
||||
// rx 11: GETATTR i1 {Fh 0} p5874
|
||||
// tx 11: OK, {tA=1s {M040755 SZ=0 L=1 1000:1000 B0*0 i0:1 A 0.000000 M 0.000000 C 0.000000}}
|
||||
Logger *log.Logger
|
||||
|
||||
// EnableLocks, if set, asks the kernel to forward file locks to FUSE
|
||||
// When used, you must implement the GetLk/SetLk/SetLkw methods.
|
||||
EnableLocks bool
|
||||
|
||||
// EnableSymlinkCaching, if set, makes the kernel cache all Readlink return values.
|
||||
// The filesystem must use content notification to force the
|
||||
// kernel to issue a new Readlink call.
|
||||
EnableSymlinkCaching bool
|
||||
|
||||
// ExplicitDataCacheControl, if set, asks the kernel not to do automatic
|
||||
// data cache invalidation. The filesystem is fully responsible for
|
||||
// invalidating data cache.
|
||||
ExplicitDataCacheControl bool
|
||||
|
||||
// SyncRead, if set, makes go-fuse enable the
|
||||
// FUSE_CAP_ASYNC_READ capability.
|
||||
// The kernel then submits multiple concurrent reads to service
|
||||
// userspace requests and kernel readahead.
|
||||
//
|
||||
// Setting SyncRead disables the FUSE_CAP_ASYNC_READ capability.
|
||||
// The kernel then only sends one read request per file handle at a time,
|
||||
// and orders the requests by offset.
|
||||
//
|
||||
// This is useful if reading out of order or concurrently is expensive for
|
||||
// (example: Amazon Cloud Drive).
|
||||
//
|
||||
// See the comment to FUSE_CAP_ASYNC_READ in
|
||||
// https://github.com/libfuse/libfuse/blob/master/include/fuse_common.h
|
||||
// for more details.
|
||||
SyncRead bool
|
||||
|
||||
// DirectMount, if set, makes go-fuse first attempt to use syscall.Mount instead of
|
||||
// fusermount to mount the filesystem. This will not update /etc/mtab
|
||||
// but might be needed if fusermount is not available.
|
||||
// Also, Server.Unmount will attempt syscall.Unmount before calling
|
||||
// fusermount.
|
||||
DirectMount bool
|
||||
|
||||
// DirectMountStrict, if set, is like DirectMount but no fallback to fusermount is
|
||||
// performed. If both DirectMount and DirectMountStrict are set,
|
||||
// DirectMountStrict wins.
|
||||
DirectMountStrict bool
|
||||
|
||||
// DirectMountFlags are the mountflags passed to syscall.Mount. If zero, the
|
||||
// default value used by fusermount are used: syscall.MS_NOSUID|syscall.MS_NODEV.
|
||||
//
|
||||
// If you actually *want* zero flags, pass syscall.MS_MGC_VAL, which is ignored
|
||||
// by the kernel. See `man 2 mount` for details about MS_MGC_VAL.
|
||||
DirectMountFlags uintptr
|
||||
|
||||
// EnableAcl, if set, enables kernel ACL support.
|
||||
//
|
||||
// See the comments to FUSE_CAP_POSIX_ACL
|
||||
// in https://github.com/libfuse/libfuse/blob/master/include/fuse_common.h
|
||||
// for details.
|
||||
EnableAcl bool
|
||||
|
||||
// DisableReadDirPlus, if set, disables the ReadDirPlus capability so
|
||||
// ReadDir is used instead. Simple directory queries (i.e. 'ls' without
|
||||
// '-l') can be faster with ReadDir, as no per-file stat calls are needed.
|
||||
DisableReadDirPlus bool
|
||||
|
||||
// DisableSplice, if set, disables splicing from files to the FUSE device.
|
||||
DisableSplice bool
|
||||
|
||||
// MaxStackDepth is the maximum stacking depth for passthrough files.
|
||||
// If unset, the default is 1.
|
||||
MaxStackDepth int
|
||||
|
||||
// RawFileSystem, if set, enables an ID-mapped mount if the Kernel supports
|
||||
// it.
|
||||
//
|
||||
// An ID-mapped mount allows the device to be mounted on the system with the
|
||||
// IDs remapped (via mount_setattr, move_mount syscalls) to those of the
|
||||
// user on the local system.
|
||||
//
|
||||
// Enabling this flag automatically sets the "default_permissions" mount
|
||||
// option. This is required by FUSE to delegate the UID/GID-based permission
|
||||
// checks to the kernel. For requests that create new inodes, FUSE will send
|
||||
// the mapped UID/GIDs. For all other requests, FUSE will send "-1".
|
||||
IDMappedMount bool
|
||||
}
|
||||
|
||||
// RawFileSystem is an interface close to the FUSE wire protocol.
|
||||
//
|
||||
// Unless you really know what you are doing, you should not implement
|
||||
// this, but rather the interfaces associated with
|
||||
// fs.InodeEmbedder. The details of getting interactions with open
|
||||
// files, renames, and threading right etc. are somewhat tricky and
|
||||
// not very interesting.
|
||||
//
|
||||
// Each FUSE request results in a corresponding method called by Server.
|
||||
// Several calls may be made simultaneously, because the server typically calls
|
||||
// each method in separate goroutine.
|
||||
//
|
||||
// A null implementation is provided by NewDefaultRawFileSystem.
|
||||
//
|
||||
// After a successful FUSE API call returns, you may not read input or
|
||||
// write output data: for performance reasons, memory is reused for
|
||||
// following requests, and reading/writing the request data will lead
|
||||
// to race conditions. If you spawn a background routine from a FUSE
|
||||
// API call, any incoming request data it wants to reference should be
|
||||
// copied over.
|
||||
//
|
||||
// If a FS operation is interrupted, the `cancel` channel is
|
||||
// closed. The fileystem can honor this request by returning EINTR. In
|
||||
// this case, the outstanding request data is not reused. Interrupts
|
||||
// occur if the process accessing the file system receives any signal
|
||||
// that is not ignored. In particular, the Go runtime uses signals to
|
||||
// manage goroutine preemption, so Go programs under load naturally
|
||||
// generate interupt opcodes when they access a FUSE filesystem.
|
||||
type RawFileSystem interface {
|
||||
String() string
|
||||
|
||||
// If called, provide debug output through the log package.
|
||||
SetDebug(debug bool)
|
||||
|
||||
// Lookup is called by the kernel when the VFS wants to know
|
||||
// about a file inside a directory. Many lookup calls can
|
||||
// occur in parallel, but only one call happens for each (dir,
|
||||
// name) pair.
|
||||
Lookup(cancel <-chan struct{}, header *InHeader, name string, out *EntryOut) (status Status)
|
||||
|
||||
// Forget is called when the kernel discards entries from its
|
||||
// dentry cache. This happens on unmount, and when the kernel
|
||||
// is short on memory. Since it is not guaranteed to occur at
|
||||
// any moment, and since there is no return value, Forget
|
||||
// should not do I/O, as there is no channel to report back
|
||||
// I/O errors.
|
||||
Forget(nodeid, nlookup uint64)
|
||||
|
||||
// Attributes.
|
||||
GetAttr(cancel <-chan struct{}, input *GetAttrIn, out *AttrOut) (code Status)
|
||||
SetAttr(cancel <-chan struct{}, input *SetAttrIn, out *AttrOut) (code Status)
|
||||
|
||||
// Modifying structure.
|
||||
Mknod(cancel <-chan struct{}, input *MknodIn, name string, out *EntryOut) (code Status)
|
||||
Mkdir(cancel <-chan struct{}, input *MkdirIn, name string, out *EntryOut) (code Status)
|
||||
Unlink(cancel <-chan struct{}, header *InHeader, name string) (code Status)
|
||||
Rmdir(cancel <-chan struct{}, header *InHeader, name string) (code Status)
|
||||
Rename(cancel <-chan struct{}, input *RenameIn, oldName string, newName string) (code Status)
|
||||
Link(cancel <-chan struct{}, input *LinkIn, filename string, out *EntryOut) (code Status)
|
||||
|
||||
Symlink(cancel <-chan struct{}, header *InHeader, pointedTo string, linkName string, out *EntryOut) (code Status)
|
||||
Readlink(cancel <-chan struct{}, header *InHeader) (out []byte, code Status)
|
||||
Access(cancel <-chan struct{}, input *AccessIn) (code Status)
|
||||
|
||||
// Extended attributes.
|
||||
|
||||
// GetXAttr reads an extended attribute, and should return the
|
||||
// number of bytes. If the buffer is too small, return ERANGE,
|
||||
// with the required buffer size.
|
||||
GetXAttr(cancel <-chan struct{}, header *InHeader, attr string, dest []byte) (sz uint32, code Status)
|
||||
|
||||
// ListXAttr lists extended attributes as '\0' delimited byte
|
||||
// slice, and return the number of bytes. If the buffer is too
|
||||
// small, return ERANGE, with the required buffer size.
|
||||
ListXAttr(cancel <-chan struct{}, header *InHeader, dest []byte) (uint32, Status)
|
||||
|
||||
// SetAttr writes an extended attribute.
|
||||
SetXAttr(cancel <-chan struct{}, input *SetXAttrIn, attr string, data []byte) Status
|
||||
|
||||
// RemoveXAttr removes an extended attribute.
|
||||
RemoveXAttr(cancel <-chan struct{}, header *InHeader, attr string) (code Status)
|
||||
|
||||
// File handling.
|
||||
Create(cancel <-chan struct{}, input *CreateIn, name string, out *CreateOut) (code Status)
|
||||
Open(cancel <-chan struct{}, input *OpenIn, out *OpenOut) (status Status)
|
||||
Read(cancel <-chan struct{}, input *ReadIn, buf []byte) (ReadResult, Status)
|
||||
Lseek(cancel <-chan struct{}, in *LseekIn, out *LseekOut) Status
|
||||
|
||||
// File locking
|
||||
GetLk(cancel <-chan struct{}, input *LkIn, out *LkOut) (code Status)
|
||||
SetLk(cancel <-chan struct{}, input *LkIn) (code Status)
|
||||
SetLkw(cancel <-chan struct{}, input *LkIn) (code Status)
|
||||
|
||||
Release(cancel <-chan struct{}, input *ReleaseIn)
|
||||
Write(cancel <-chan struct{}, input *WriteIn, data []byte) (written uint32, code Status)
|
||||
CopyFileRange(cancel <-chan struct{}, input *CopyFileRangeIn) (written uint32, code Status)
|
||||
Ioctl(cancel <-chan struct{}, input *IoctlIn, inbuf []byte, output *IoctlOut, outbuf []byte) (code Status)
|
||||
|
||||
Flush(cancel <-chan struct{}, input *FlushIn) Status
|
||||
Fsync(cancel <-chan struct{}, input *FsyncIn) (code Status)
|
||||
Fallocate(cancel <-chan struct{}, input *FallocateIn) (code Status)
|
||||
|
||||
// Directory handling
|
||||
OpenDir(cancel <-chan struct{}, input *OpenIn, out *OpenOut) (status Status)
|
||||
ReadDir(cancel <-chan struct{}, input *ReadIn, out *DirEntryList) Status
|
||||
ReadDirPlus(cancel <-chan struct{}, input *ReadIn, out *DirEntryList) Status
|
||||
ReleaseDir(input *ReleaseIn)
|
||||
FsyncDir(cancel <-chan struct{}, input *FsyncIn) (code Status)
|
||||
|
||||
StatFs(cancel <-chan struct{}, input *InHeader, out *StatfsOut) (code Status)
|
||||
|
||||
Statx(cancel <-chan struct{}, input *StatxIn, out *StatxOut) (code Status)
|
||||
// This is called on processing the first request. The
|
||||
// filesystem implementation can use the server argument to
|
||||
// talk back to the kernel (through notify methods).
|
||||
Init(*Server)
|
||||
|
||||
// Called after processing the last request.
|
||||
OnUnmount()
|
||||
}
|
||||
79
vendor/github.com/hanwen/go-fuse/v2/fuse/attr.go
generated
vendored
Normal file
79
vendor/github.com/hanwen/go-fuse/v2/fuse/attr.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (a *Attr) IsFifo() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFIFO }
|
||||
|
||||
// IsChar reports whether the FileInfo describes a character special file.
|
||||
func (a *Attr) IsChar() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFCHR }
|
||||
|
||||
// IsDir reports whether the FileInfo describes a directory.
|
||||
func (a *Attr) IsDir() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFDIR }
|
||||
|
||||
// IsBlock reports whether the FileInfo describes a block special file.
|
||||
func (a *Attr) IsBlock() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFBLK }
|
||||
|
||||
// IsRegular reports whether the FileInfo describes a regular file.
|
||||
func (a *Attr) IsRegular() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFREG }
|
||||
|
||||
// IsSymlink reports whether the FileInfo describes a symbolic link.
|
||||
func (a *Attr) IsSymlink() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFLNK }
|
||||
|
||||
// IsSocket reports whether the FileInfo describes a socket.
|
||||
func (a *Attr) IsSocket() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFSOCK }
|
||||
|
||||
func (a *Attr) SetTimes(access *time.Time, mod *time.Time, chstatus *time.Time) {
|
||||
if access != nil {
|
||||
a.Atime = uint64(access.Unix())
|
||||
a.Atimensec = uint32(access.Nanosecond())
|
||||
}
|
||||
if mod != nil {
|
||||
a.Mtime = uint64(mod.Unix())
|
||||
a.Mtimensec = uint32(mod.Nanosecond())
|
||||
}
|
||||
if chstatus != nil {
|
||||
a.Ctime = uint64(chstatus.Unix())
|
||||
a.Ctimensec = uint32(chstatus.Nanosecond())
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Attr) ChangeTime() time.Time {
|
||||
return time.Unix(int64(a.Ctime), int64(a.Ctimensec))
|
||||
}
|
||||
|
||||
func (a *Attr) AccessTime() time.Time {
|
||||
return time.Unix(int64(a.Atime), int64(a.Atimensec))
|
||||
}
|
||||
|
||||
func (a *Attr) ModTime() time.Time {
|
||||
return time.Unix(int64(a.Mtime), int64(a.Mtimensec))
|
||||
}
|
||||
|
||||
func ToStatT(f os.FileInfo) *syscall.Stat_t {
|
||||
s, _ := f.Sys().(*syscall.Stat_t)
|
||||
if s != nil {
|
||||
return s
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ToAttr(f os.FileInfo) *Attr {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
s := ToStatT(f)
|
||||
if s != nil {
|
||||
a := &Attr{}
|
||||
a.FromStat(s)
|
||||
return a
|
||||
}
|
||||
return nil
|
||||
}
|
||||
51
vendor/github.com/hanwen/go-fuse/v2/fuse/attr_linux.go
generated
vendored
Normal file
51
vendor/github.com/hanwen/go-fuse/v2/fuse/attr_linux.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func (a *Attr) FromStat(s *syscall.Stat_t) {
|
||||
a.Ino = uint64(s.Ino)
|
||||
a.Size = uint64(s.Size)
|
||||
a.Blocks = uint64(s.Blocks)
|
||||
a.Atime = uint64(s.Atim.Sec)
|
||||
a.Atimensec = uint32(s.Atim.Nsec)
|
||||
a.Mtime = uint64(s.Mtim.Sec)
|
||||
a.Mtimensec = uint32(s.Mtim.Nsec)
|
||||
a.Ctime = uint64(s.Ctim.Sec)
|
||||
a.Ctimensec = uint32(s.Ctim.Nsec)
|
||||
a.Mode = s.Mode
|
||||
a.Nlink = uint32(s.Nlink)
|
||||
a.Uid = uint32(s.Uid)
|
||||
a.Gid = uint32(s.Gid)
|
||||
a.Rdev = uint32(s.Rdev)
|
||||
a.Blksize = uint32(s.Blksize)
|
||||
}
|
||||
|
||||
func (a *Statx) FromStatx(s *unix.Statx_t) {
|
||||
a.Ino = uint64(s.Ino)
|
||||
a.Size = uint64(s.Size)
|
||||
a.Blocks = uint64(s.Blocks)
|
||||
a.Atime.FromStatxTimestamp(&s.Atime)
|
||||
a.Btime.FromStatxTimestamp(&s.Btime)
|
||||
a.Ctime.FromStatxTimestamp(&s.Ctime)
|
||||
a.Mtime.FromStatxTimestamp(&s.Mtime)
|
||||
a.Mode = s.Mode
|
||||
a.Nlink = uint32(s.Nlink)
|
||||
a.Uid = uint32(s.Uid)
|
||||
a.Gid = uint32(s.Gid)
|
||||
a.Blksize = uint32(s.Blksize)
|
||||
a.AttributesMask = s.Attributes_mask
|
||||
a.Mask = s.Mask
|
||||
a.Attributes = s.Attributes
|
||||
a.RdevMinor = s.Rdev_minor
|
||||
a.RdevMajor = s.Rdev_major
|
||||
a.DevMajor = s.Dev_major
|
||||
a.DevMinor = s.Dev_minor
|
||||
}
|
||||
29
vendor/github.com/hanwen/go-fuse/v2/fuse/attr_unix.go
generated
vendored
Normal file
29
vendor/github.com/hanwen/go-fuse/v2/fuse/attr_unix.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
//go:build !linux
|
||||
|
||||
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func (a *Attr) FromStat(s *syscall.Stat_t) {
|
||||
a.Ino = uint64(s.Ino)
|
||||
a.Size = uint64(s.Size)
|
||||
a.Blocks = uint64(s.Blocks)
|
||||
a.Atime = uint64(s.Atimespec.Sec)
|
||||
a.Atimensec = uint32(s.Atimespec.Nsec)
|
||||
a.Mtime = uint64(s.Mtimespec.Sec)
|
||||
a.Mtimensec = uint32(s.Mtimespec.Nsec)
|
||||
a.Ctime = uint64(s.Ctimespec.Sec)
|
||||
a.Ctimensec = uint32(s.Ctimespec.Nsec)
|
||||
a.Mode = uint32(s.Mode)
|
||||
a.Nlink = uint32(s.Nlink)
|
||||
a.Uid = uint32(s.Uid)
|
||||
a.Gid = uint32(s.Gid)
|
||||
a.Rdev = uint32(s.Rdev)
|
||||
a.Blksize = uint32(s.Blksize)
|
||||
}
|
||||
82
vendor/github.com/hanwen/go-fuse/v2/fuse/bufferpool.go
generated
vendored
Normal file
82
vendor/github.com/hanwen/go-fuse/v2/fuse/bufferpool.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// bufferPool implements explicit memory management. It is used for
|
||||
// minimizing the GC overhead of communicating with the kernel.
|
||||
type bufferPool struct {
|
||||
lock sync.Mutex
|
||||
|
||||
// For each page size multiple a list of slice pointers.
|
||||
buffersBySize []*sync.Pool
|
||||
|
||||
// Number of outstanding allocations. Used for testing.
|
||||
countersBySize []int
|
||||
}
|
||||
|
||||
var pageSize = os.Getpagesize()
|
||||
|
||||
func (p *bufferPool) counters() []int {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
d := make([]int, len(p.countersBySize))
|
||||
copy(d, p.countersBySize)
|
||||
return d
|
||||
}
|
||||
|
||||
func (p *bufferPool) getPool(pageCount int, delta int) *sync.Pool {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
for len(p.buffersBySize) <= pageCount {
|
||||
p.buffersBySize = append(p.buffersBySize, nil)
|
||||
p.countersBySize = append(p.countersBySize, 0)
|
||||
}
|
||||
if p.buffersBySize[pageCount] == nil {
|
||||
p.buffersBySize[pageCount] = &sync.Pool{
|
||||
New: func() interface{} { return make([]byte, pageSize*pageCount) },
|
||||
}
|
||||
}
|
||||
p.countersBySize[pageCount] += delta
|
||||
return p.buffersBySize[pageCount]
|
||||
}
|
||||
|
||||
// AllocBuffer creates a buffer of at least the given size. After use,
|
||||
// it should be deallocated with FreeBuffer().
|
||||
func (p *bufferPool) AllocBuffer(size uint32) []byte {
|
||||
sz := int(size)
|
||||
if sz < pageSize {
|
||||
sz = pageSize
|
||||
}
|
||||
|
||||
if sz%pageSize != 0 {
|
||||
sz += pageSize
|
||||
}
|
||||
pages := sz / pageSize
|
||||
|
||||
b := p.getPool(pages, 1).Get().([]byte)
|
||||
return b[:size]
|
||||
}
|
||||
|
||||
// FreeBuffer takes back a buffer if it was allocated through
|
||||
// AllocBuffer. It is not an error to call FreeBuffer() on a slice
|
||||
// obtained elsewhere.
|
||||
func (p *bufferPool) FreeBuffer(slice []byte) {
|
||||
if slice == nil {
|
||||
return
|
||||
}
|
||||
if cap(slice)%pageSize != 0 || cap(slice) == 0 {
|
||||
return
|
||||
}
|
||||
pages := cap(slice) / pageSize
|
||||
slice = slice[:cap(slice)]
|
||||
|
||||
p.getPool(pages, -1).Put(slice)
|
||||
}
|
||||
44
vendor/github.com/hanwen/go-fuse/v2/fuse/constants.go
generated
vendored
Normal file
44
vendor/github.com/hanwen/go-fuse/v2/fuse/constants.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
FUSE_ROOT_ID = 1
|
||||
|
||||
FUSE_UNKNOWN_INO = 0xffffffff
|
||||
|
||||
CUSE_UNRESTRICTED_IOCTL = (1 << 0)
|
||||
|
||||
FUSE_LK_FLOCK = (1 << 0)
|
||||
|
||||
FUSE_RELEASE_FLUSH = (1 << 0)
|
||||
FUSE_RELEASE_FLOCK_UNLOCK = (1 << 1)
|
||||
|
||||
FUSE_IOCTL_MAX_IOV = 256
|
||||
|
||||
FUSE_POLL_SCHEDULE_NOTIFY = (1 << 0)
|
||||
|
||||
CUSE_INIT_INFO_MAX = 4096
|
||||
|
||||
S_IFDIR = syscall.S_IFDIR
|
||||
S_IFREG = syscall.S_IFREG
|
||||
S_IFLNK = syscall.S_IFLNK
|
||||
S_IFIFO = syscall.S_IFIFO
|
||||
|
||||
CUSE_INIT = 4096
|
||||
|
||||
O_ANYWRITE = uint32(os.O_WRONLY | os.O_RDWR | os.O_APPEND | os.O_CREATE | os.O_TRUNC)
|
||||
|
||||
// FMODE_EXEC is a passed to OPEN requests if the file is
|
||||
// being executed.
|
||||
FMODE_EXEC = 0x20
|
||||
|
||||
logicalBlockSize = 512
|
||||
)
|
||||
9
vendor/github.com/hanwen/go-fuse/v2/fuse/constants_freebsd.go
generated
vendored
Normal file
9
vendor/github.com/hanwen/go-fuse/v2/fuse/constants_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
// arbitrary values
|
||||
const syscall_O_LARGEFILE = 1 << 29
|
||||
const syscall_O_NOATIME = 1 << 30
|
||||
12
vendor/github.com/hanwen/go-fuse/v2/fuse/constants_linux.go
generated
vendored
Normal file
12
vendor/github.com/hanwen/go-fuse/v2/fuse/constants_linux.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const syscall_O_LARGEFILE = syscall.O_LARGEFILE
|
||||
const syscall_O_NOATIME = syscall.O_NOATIME
|
||||
61
vendor/github.com/hanwen/go-fuse/v2/fuse/context.go
generated
vendored
Normal file
61
vendor/github.com/hanwen/go-fuse/v2/fuse/context.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Context passes along cancelation signal and request data (PID, GID,
|
||||
// UID). The name of this class predates the standard "context"
|
||||
// package from Go, but it does implement the context.Context
|
||||
// interface.
|
||||
//
|
||||
// When a FUSE request is canceled, and the file system chooses to honor
|
||||
// the cancellation, the response should be EINTR.
|
||||
type Context struct {
|
||||
Caller
|
||||
Cancel <-chan struct{}
|
||||
}
|
||||
|
||||
func (c *Context) Deadline() (time.Time, bool) {
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
func (c *Context) Done() <-chan struct{} {
|
||||
return c.Cancel
|
||||
}
|
||||
|
||||
func (c *Context) Err() error {
|
||||
select {
|
||||
case <-c.Cancel:
|
||||
return context.Canceled
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type callerKeyType struct{}
|
||||
|
||||
var callerKey callerKeyType
|
||||
|
||||
func FromContext(ctx context.Context) (*Caller, bool) {
|
||||
v, ok := ctx.Value(callerKey).(*Caller)
|
||||
return v, ok
|
||||
}
|
||||
|
||||
func NewContext(ctx context.Context, caller *Caller) context.Context {
|
||||
return context.WithValue(ctx, callerKey, caller)
|
||||
}
|
||||
|
||||
func (c *Context) Value(key interface{}) interface{} {
|
||||
if key == callerKey {
|
||||
return &c.Caller
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ = context.Context((*Context)(nil))
|
||||
180
vendor/github.com/hanwen/go-fuse/v2/fuse/defaultraw.go
generated
vendored
Normal file
180
vendor/github.com/hanwen/go-fuse/v2/fuse/defaultraw.go
generated
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// NewDefaultRawFileSystem returns ENOSYS (not implemented) for all
|
||||
// operations.
|
||||
func NewDefaultRawFileSystem() RawFileSystem {
|
||||
return (*defaultRawFileSystem)(nil)
|
||||
}
|
||||
|
||||
type defaultRawFileSystem struct{}
|
||||
|
||||
func (fs *defaultRawFileSystem) Init(*Server) {
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) OnUnmount() {
|
||||
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) String() string {
|
||||
return os.Args[0]
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) SetDebug(dbg bool) {
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) StatFs(cancel <-chan struct{}, header *InHeader, out *StatfsOut) Status {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Lookup(cancel <-chan struct{}, header *InHeader, name string, out *EntryOut) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Forget(nodeID, nlookup uint64) {
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) GetAttr(cancel <-chan struct{}, input *GetAttrIn, out *AttrOut) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Open(cancel <-chan struct{}, input *OpenIn, out *OpenOut) (status Status) {
|
||||
return OK
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) SetAttr(cancel <-chan struct{}, input *SetAttrIn, out *AttrOut) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Readlink(cancel <-chan struct{}, header *InHeader) (out []byte, code Status) {
|
||||
return nil, ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Mknod(cancel <-chan struct{}, input *MknodIn, name string, out *EntryOut) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Mkdir(cancel <-chan struct{}, input *MkdirIn, name string, out *EntryOut) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Unlink(cancel <-chan struct{}, header *InHeader, name string) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Rmdir(cancel <-chan struct{}, header *InHeader, name string) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Symlink(cancel <-chan struct{}, header *InHeader, pointedTo string, linkName string, out *EntryOut) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Rename(cancel <-chan struct{}, input *RenameIn, oldName string, newName string) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Link(cancel <-chan struct{}, input *LinkIn, name string, out *EntryOut) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) GetXAttr(cancel <-chan struct{}, header *InHeader, attr string, dest []byte) (size uint32, code Status) {
|
||||
return 0, ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) SetXAttr(cancel <-chan struct{}, input *SetXAttrIn, attr string, data []byte) Status {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) ListXAttr(cancel <-chan struct{}, header *InHeader, dest []byte) (n uint32, code Status) {
|
||||
return 0, ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) RemoveXAttr(cancel <-chan struct{}, header *InHeader, attr string) Status {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Access(cancel <-chan struct{}, input *AccessIn) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Create(cancel <-chan struct{}, input *CreateIn, name string, out *CreateOut) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) OpenDir(cancel <-chan struct{}, input *OpenIn, out *OpenOut) (status Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Read(cancel <-chan struct{}, input *ReadIn, buf []byte) (ReadResult, Status) {
|
||||
return nil, ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) GetLk(cancel <-chan struct{}, in *LkIn, out *LkOut) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) SetLk(cancel <-chan struct{}, in *LkIn) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) SetLkw(cancel <-chan struct{}, in *LkIn) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Release(cancel <-chan struct{}, input *ReleaseIn) {
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Write(cancel <-chan struct{}, input *WriteIn, data []byte) (written uint32, code Status) {
|
||||
return 0, ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Flush(cancel <-chan struct{}, input *FlushIn) Status {
|
||||
return OK
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Fsync(cancel <-chan struct{}, input *FsyncIn) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) ReadDir(cancel <-chan struct{}, input *ReadIn, l *DirEntryList) Status {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) ReadDirPlus(cancel <-chan struct{}, input *ReadIn, l *DirEntryList) Status {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) ReleaseDir(input *ReleaseIn) {
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) FsyncDir(cancel <-chan struct{}, input *FsyncIn) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Fallocate(cancel <-chan struct{}, in *FallocateIn) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) CopyFileRange(cancel <-chan struct{}, input *CopyFileRangeIn) (written uint32, code Status) {
|
||||
return 0, ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Ioctl(cancel <-chan struct{}, input *IoctlIn, inbuf []byte, output *IoctlOut, outbuf []byte) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Lseek(cancel <-chan struct{}, in *LseekIn, out *LseekOut) Status {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func (fs *defaultRawFileSystem) Statx(cancel <-chan struct{}, input *StatxIn, out *StatxOut) (code Status) {
|
||||
return ENOSYS
|
||||
}
|
||||
185
vendor/github.com/hanwen/go-fuse/v2/fuse/direntry.go
generated
vendored
Normal file
185
vendor/github.com/hanwen/go-fuse/v2/fuse/direntry.go
generated
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const direntSize = int(unsafe.Sizeof(_Dirent{}))
|
||||
|
||||
// DirEntry is a type for PathFileSystem and NodeFileSystem to return
|
||||
// directory contents in.
|
||||
type DirEntry struct {
|
||||
// Mode is the file's mode. Only the high bits (eg. S_IFDIR)
|
||||
// are considered.
|
||||
Mode uint32
|
||||
|
||||
// Name is the basename of the file in the directory.
|
||||
Name string
|
||||
|
||||
// Ino is the inode number.
|
||||
Ino uint64
|
||||
|
||||
// Off is the offset in the directory stream. The offset is
|
||||
// thought to be after the entry.
|
||||
Off uint64
|
||||
}
|
||||
|
||||
func (d *DirEntry) String() string {
|
||||
return fmt.Sprintf("%d: %q ino=%d (%o)", d.Off, d.Name, d.Ino, d.Mode)
|
||||
}
|
||||
|
||||
// Parse reads an entry from getdents(2) buffer. It returns the number
|
||||
// of bytes consumed.
|
||||
func (d *DirEntry) Parse(buf []byte) int {
|
||||
// We can't use syscall.Dirent here, because it declares a
|
||||
// [256]byte name, which may run beyond the end of ds.todo.
|
||||
// when that happens in the race detector, it causes a panic
|
||||
// "converted pointer straddles multiple allocations"
|
||||
de := (*dirent)(unsafe.Pointer(&buf[0]))
|
||||
off := unsafe.Offsetof(dirent{}.Name)
|
||||
nameBytes := buf[off : off+uintptr(de.nameLength())]
|
||||
n := de.Reclen
|
||||
|
||||
l := bytes.IndexByte(nameBytes, 0)
|
||||
if l >= 0 {
|
||||
nameBytes = nameBytes[:l]
|
||||
}
|
||||
*d = DirEntry{
|
||||
Ino: de.Ino,
|
||||
Mode: (uint32(de.Type) << 12),
|
||||
Name: string(nameBytes),
|
||||
Off: uint64(de.Off),
|
||||
}
|
||||
return int(n)
|
||||
}
|
||||
|
||||
// DirEntryList holds the return value for READDIR and READDIRPLUS
|
||||
// opcodes.
|
||||
type DirEntryList struct {
|
||||
buf []byte
|
||||
// capacity of the underlying buffer
|
||||
size int
|
||||
|
||||
// Offset holds the offset for the next entry to be added. It
|
||||
// is the offset supplied at construction time, or the Offset
|
||||
// of the last DirEntry that was added.
|
||||
Offset uint64
|
||||
|
||||
// pointer to the last serialized _Dirent. Used by FixMode().
|
||||
lastDirent *_Dirent
|
||||
}
|
||||
|
||||
// NewDirEntryList creates a DirEntryList with the given data buffer
|
||||
// and offset.
|
||||
func NewDirEntryList(data []byte, off uint64) *DirEntryList {
|
||||
return &DirEntryList{
|
||||
buf: data[:0],
|
||||
size: len(data),
|
||||
Offset: off,
|
||||
}
|
||||
}
|
||||
|
||||
// AddDirEntry tries to add an entry, and reports whether it
|
||||
// succeeded. If adding a 0 offset entry, the offset is taken to be
|
||||
// the last offset + 1.
|
||||
func (l *DirEntryList) AddDirEntry(e DirEntry) bool {
|
||||
// TODO: take pointer arg, merge with AddDirLookupEntry.
|
||||
return l.addDirEntry(&e, 0)
|
||||
}
|
||||
|
||||
func (l *DirEntryList) addDirEntry(e *DirEntry, prefix int) bool {
|
||||
if e.Ino == 0 {
|
||||
e.Ino = FUSE_UNKNOWN_INO
|
||||
}
|
||||
if e.Off == 0 {
|
||||
e.Off = l.Offset + 1
|
||||
}
|
||||
padding := (8 - len(e.Name)&7) & 7
|
||||
delta := padding + direntSize + len(e.Name) + prefix
|
||||
oldLen := len(l.buf)
|
||||
newLen := delta + oldLen
|
||||
|
||||
if newLen > l.size {
|
||||
return false
|
||||
}
|
||||
l.buf = l.buf[:newLen]
|
||||
oldLen += prefix
|
||||
dirent := (*_Dirent)(unsafe.Pointer(&l.buf[oldLen]))
|
||||
dirent.Off = e.Off
|
||||
dirent.Ino = e.Ino
|
||||
dirent.NameLen = uint32(len(e.Name))
|
||||
dirent.Typ = modeToType(e.Mode)
|
||||
oldLen += direntSize
|
||||
copy(l.buf[oldLen:], e.Name)
|
||||
oldLen += len(e.Name)
|
||||
|
||||
if padding > 0 {
|
||||
l.buf[oldLen] = 0
|
||||
}
|
||||
l.Offset = dirent.Off
|
||||
return true
|
||||
}
|
||||
|
||||
// Add adds a direntry to the DirEntryList, returning wheither it
|
||||
// succeeded. Prefix is the amount of padding to add before the DirEntry.
|
||||
//
|
||||
// Deprecated: use AddDirLookupEntry or AddDirEntry.
|
||||
func (l *DirEntryList) Add(prefix int, name string, inode uint64, mode uint32) bool {
|
||||
// TODO: remove.
|
||||
e := DirEntry{
|
||||
Name: name,
|
||||
Mode: mode,
|
||||
Off: l.Offset + 1,
|
||||
Ino: inode,
|
||||
}
|
||||
return l.addDirEntry(&e, prefix)
|
||||
}
|
||||
|
||||
// AddDirLookupEntry is used for ReadDirPlus. If reserves and zeroes
|
||||
// space for an EntryOut struct and serializes the DirEntry. If adding
|
||||
// a 0 offset entry, the offset is taken to be the last offset + 1.
|
||||
// If the entry does not fit, it returns nil.
|
||||
func (l *DirEntryList) AddDirLookupEntry(e DirEntry) *EntryOut {
|
||||
// The resulting READDIRPLUS output buffer looks like this in memory:
|
||||
// 1) EntryOut{}
|
||||
// 2) _Dirent{}
|
||||
// 3) Name (null-terminated)
|
||||
// 4) Padding to align to 8 bytes
|
||||
// [repeat]
|
||||
|
||||
// TODO: should take pointer as argument.
|
||||
const entryOutSize = int(unsafe.Sizeof(EntryOut{}))
|
||||
oldLen := len(l.buf)
|
||||
ok := l.addDirEntry(&e, entryOutSize)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
l.lastDirent = (*_Dirent)(unsafe.Pointer(&l.buf[oldLen+entryOutSize]))
|
||||
entryOut := (*EntryOut)(unsafe.Pointer(&l.buf[oldLen]))
|
||||
*entryOut = EntryOut{}
|
||||
return entryOut
|
||||
}
|
||||
|
||||
// modeToType converts a file *mode* (as used in syscall.Stat_t.Mode)
|
||||
// to a file *type* (as used in _Dirent.Typ).
|
||||
// Equivalent to IFTODT() in libc (see man 5 dirent).
|
||||
func modeToType(mode uint32) uint32 {
|
||||
return (mode & 0170000) >> 12
|
||||
}
|
||||
|
||||
// FixMode overrides the file mode of the last direntry that was added. This can
|
||||
// be needed when a directory changes while READDIRPLUS is running.
|
||||
// Only the file type bits of mode are considered, the rest is masked out.
|
||||
func (l *DirEntryList) FixMode(mode uint32) {
|
||||
l.lastDirent.Typ = modeToType(mode)
|
||||
}
|
||||
|
||||
func (l *DirEntryList) bytes() []byte {
|
||||
return l.buf
|
||||
}
|
||||
19
vendor/github.com/hanwen/go-fuse/v2/fuse/direntry_darwin.go
generated
vendored
Normal file
19
vendor/github.com/hanwen/go-fuse/v2/fuse/direntry_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
// Like syscall.Dirent, but without the [256]byte name.
|
||||
type dirent struct {
|
||||
Ino uint64
|
||||
Off int64
|
||||
Reclen uint16
|
||||
Namlen uint16
|
||||
Type uint8
|
||||
Name [1]uint8 // align to 4 bytes for 32 bits.
|
||||
}
|
||||
|
||||
func (de *dirent) nameLength() int {
|
||||
return int(de.Namlen)
|
||||
}
|
||||
20
vendor/github.com/hanwen/go-fuse/v2/fuse/direntry_freebsd.go
generated
vendored
Normal file
20
vendor/github.com/hanwen/go-fuse/v2/fuse/direntry_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
type dirent struct {
|
||||
Ino uint64
|
||||
Off int64
|
||||
Reclen uint16
|
||||
Type uint8
|
||||
Pad0 uint8
|
||||
Namlen uint16
|
||||
Pad1 uint16
|
||||
Name [1]int8
|
||||
}
|
||||
|
||||
func (de *dirent) nameLength() int {
|
||||
return int(de.Namlen)
|
||||
}
|
||||
22
vendor/github.com/hanwen/go-fuse/v2/fuse/direntry_linux.go
generated
vendored
Normal file
22
vendor/github.com/hanwen/go-fuse/v2/fuse/direntry_linux.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// Like syscall.Dirent, but without the [256]byte name. This is
|
||||
// equivalent to the linux_dirent64 struct, returned from the
|
||||
// getdents64 syscall.
|
||||
type dirent struct {
|
||||
Ino uint64
|
||||
Off int64
|
||||
Reclen uint16
|
||||
Type uint8
|
||||
Name [1]uint8 // align to 4 bytes for 32 bits.
|
||||
}
|
||||
|
||||
func (de *dirent) nameLength() int {
|
||||
return int(de.Reclen) - int(unsafe.Offsetof(dirent{}.Name))
|
||||
}
|
||||
89
vendor/github.com/hanwen/go-fuse/v2/fuse/misc.go
generated
vendored
Normal file
89
vendor/github.com/hanwen/go-fuse/v2/fuse/misc.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Random odds and ends.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (code Status) String() string {
|
||||
if code <= 0 {
|
||||
return []string{
|
||||
"OK",
|
||||
"NOTIFY_POLL",
|
||||
"NOTIFY_INVAL_INODE",
|
||||
"NOTIFY_INVAL_ENTRY",
|
||||
"NOTIFY_STORE_CACHE",
|
||||
"NOTIFY_RETRIEVE_CACHE",
|
||||
"NOTIFY_DELETE",
|
||||
}[-code]
|
||||
}
|
||||
return fmt.Sprintf("%d=%v", int(code), syscall.Errno(code))
|
||||
}
|
||||
|
||||
func (code Status) Ok() bool {
|
||||
return code == OK
|
||||
}
|
||||
|
||||
// ToStatus extracts an errno number from Go error objects. If it
|
||||
// fails, it logs an error and returns ENOSYS.
|
||||
func ToStatus(err error) Status {
|
||||
switch err {
|
||||
case nil:
|
||||
return OK
|
||||
case os.ErrPermission:
|
||||
return EPERM
|
||||
case os.ErrExist:
|
||||
return Status(syscall.EEXIST)
|
||||
case os.ErrNotExist:
|
||||
return ENOENT
|
||||
case os.ErrInvalid:
|
||||
return EINVAL
|
||||
}
|
||||
|
||||
switch t := err.(type) {
|
||||
case syscall.Errno:
|
||||
return Status(t)
|
||||
case *os.SyscallError:
|
||||
return Status(t.Err.(syscall.Errno))
|
||||
case *os.PathError:
|
||||
return ToStatus(t.Err)
|
||||
case *os.LinkError:
|
||||
return ToStatus(t.Err)
|
||||
}
|
||||
log.Println("can't convert error type:", err)
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
func CurrentOwner() *Owner {
|
||||
return &Owner{
|
||||
Uid: uint32(os.Getuid()),
|
||||
Gid: uint32(os.Getgid()),
|
||||
}
|
||||
}
|
||||
|
||||
// UtimeToTimespec converts a "Time" pointer as passed to Utimens to a
|
||||
// "Timespec" that can be passed to the utimensat syscall.
|
||||
// A nil pointer is converted to the special UTIME_OMIT value.
|
||||
//
|
||||
// Deprecated: use unix.TimeToTimespec from the x/sys/unix package instead.
|
||||
func UtimeToTimespec(t *time.Time) (ts syscall.Timespec) {
|
||||
if t == nil {
|
||||
ts.Nsec = _UTIME_OMIT
|
||||
} else {
|
||||
ts = syscall.NsecToTimespec(t.UnixNano())
|
||||
// Go bug https://github.com/golang/go/issues/12777
|
||||
if ts.Nsec < 0 {
|
||||
ts.Nsec = 0
|
||||
}
|
||||
}
|
||||
return ts
|
||||
}
|
||||
4
vendor/github.com/hanwen/go-fuse/v2/fuse/misc_darwin.go
generated
vendored
Normal file
4
vendor/github.com/hanwen/go-fuse/v2/fuse/misc_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
package fuse
|
||||
|
||||
// Ref: https://github.com/apple/darwin-xnu/blob/main/bsd/sys/stat.h#L576
|
||||
const _UTIME_OMIT = -2
|
||||
7
vendor/github.com/hanwen/go-fuse/v2/fuse/misc_unix.go
generated
vendored
Normal file
7
vendor/github.com/hanwen/go-fuse/v2/fuse/misc_unix.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
//go:build !darwin
|
||||
|
||||
package fuse
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const _UTIME_OMIT = unix.UTIME_OMIT
|
||||
72
vendor/github.com/hanwen/go-fuse/v2/fuse/mount.go
generated
vendored
Normal file
72
vendor/github.com/hanwen/go-fuse/v2/fuse/mount.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var reservedFDs []*os.File
|
||||
|
||||
func init() {
|
||||
// Both Darwin and Linux invoke a subprocess with one
|
||||
// inherited file descriptor to create the mount. To protect
|
||||
// against deadlock, we must ensure that file descriptor 3
|
||||
// never points to a FUSE filesystem. We do this by simply
|
||||
// grabbing fd 3 and never releasing it. (This is not
|
||||
// completely foolproof: a preceding init routine could grab fd 3,
|
||||
// and then release it later.)
|
||||
for {
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("os.Pipe(): %v", err))
|
||||
}
|
||||
w.Close()
|
||||
if r.Fd() > 3 {
|
||||
r.Close()
|
||||
break
|
||||
}
|
||||
reservedFDs = append(reservedFDs, r)
|
||||
}
|
||||
}
|
||||
|
||||
func getConnection(local *os.File) (int, error) {
|
||||
conn, err := net.FileConn(local)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer conn.Close()
|
||||
unixConn := conn.(*net.UnixConn)
|
||||
|
||||
var data [4]byte
|
||||
control := make([]byte, 4*256)
|
||||
|
||||
_, oobn, _, _, err := unixConn.ReadMsgUnix(data[:], control[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
messages, err := syscall.ParseSocketControlMessage(control[:oobn])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(messages) != 1 {
|
||||
return 0, fmt.Errorf("getConnection: expect 1 control message, got %#v", messages)
|
||||
}
|
||||
message := messages[0]
|
||||
|
||||
fds, err := syscall.ParseUnixRights(&message)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(fds) != 1 {
|
||||
return 0, fmt.Errorf("getConnection: expect 1 fd, got %#v", fds)
|
||||
}
|
||||
fd := fds[0]
|
||||
|
||||
if fd < 0 {
|
||||
return 0, fmt.Errorf("getConnection: fd < 0: %d", fd)
|
||||
}
|
||||
return fd, nil
|
||||
}
|
||||
114
vendor/github.com/hanwen/go-fuse/v2/fuse/mount_darwin.go
generated
vendored
Normal file
114
vendor/github.com/hanwen/go-fuse/v2/fuse/mount_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func getMaxWrite() int {
|
||||
return 1 << 20
|
||||
}
|
||||
|
||||
func unixgramSocketpair() (l, r *os.File, err error) {
|
||||
fd, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
|
||||
if err != nil {
|
||||
return nil, nil, os.NewSyscallError("socketpair",
|
||||
err.(syscall.Errno))
|
||||
}
|
||||
l = os.NewFile(uintptr(fd[0]), "socketpair-half1")
|
||||
r = os.NewFile(uintptr(fd[1]), "socketpair-half2")
|
||||
return
|
||||
}
|
||||
|
||||
// Create a FUSE FS on the specified mount point. The returned
|
||||
// mount point is always absolute.
|
||||
func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
|
||||
local, remote, err := unixgramSocketpair()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer local.Close()
|
||||
defer remote.Close()
|
||||
|
||||
bin, err := fusermountBinary()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
cmd := exec.Command(bin,
|
||||
"-o", strings.Join(opts.optionsStrings(), ","),
|
||||
"-o", fmt.Sprintf("iosize=%d", opts.MaxWrite),
|
||||
mountPoint)
|
||||
cmd.ExtraFiles = []*os.File{remote} // fd would be (index + 3)
|
||||
cmd.Env = append(os.Environ(),
|
||||
"_FUSE_CALL_BY_LIB=",
|
||||
"_FUSE_DAEMON_PATH="+os.Args[0],
|
||||
"_FUSE_COMMFD=3",
|
||||
"_FUSE_COMMVERS=2",
|
||||
"MOUNT_OSXFUSE_CALL_BY_LIB=",
|
||||
"MOUNT_OSXFUSE_DAEMON_PATH="+os.Args[0])
|
||||
|
||||
var out, errOut bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &errOut
|
||||
|
||||
if err = cmd.Start(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fd, err = getConnection(local)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
// On macos, mount_osxfuse is not just a suid
|
||||
// wrapper. It interacts with the FS setup, and will
|
||||
// not exit until the filesystem has successfully
|
||||
// responded to INIT and STATFS. This means we can
|
||||
// only wait on the mount_osxfuse process after the
|
||||
// server has fully started
|
||||
if err := cmd.Wait(); err != nil {
|
||||
err = fmt.Errorf("mount_osxfusefs failed: %v. Stderr: %s, Stdout: %s",
|
||||
err, errOut.String(), out.String())
|
||||
}
|
||||
|
||||
ready <- err
|
||||
close(ready)
|
||||
}()
|
||||
|
||||
// golang sets CLOEXEC on file descriptors when they are
|
||||
// acquired through normal operations (e.g. open).
|
||||
// Buf for fd, we have to set CLOEXEC manually
|
||||
syscall.CloseOnExec(fd)
|
||||
|
||||
return fd, err
|
||||
}
|
||||
|
||||
func unmount(dir string, opts *MountOptions) error {
|
||||
return syscall.Unmount(dir, 0)
|
||||
}
|
||||
|
||||
func fusermountBinary() (string, error) {
|
||||
binPaths := []string{
|
||||
"/Library/Filesystems/macfuse.fs/Contents/Resources/mount_macfuse",
|
||||
"/Library/Filesystems/osxfuse.fs/Contents/Resources/mount_osxfuse",
|
||||
}
|
||||
|
||||
for _, path := range binPaths {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no FUSE mount utility found")
|
||||
}
|
||||
146
vendor/github.com/hanwen/go-fuse/v2/fuse/mount_freebsd.go
generated
vendored
Normal file
146
vendor/github.com/hanwen/go-fuse/v2/fuse/mount_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func getMaxWrite() int {
|
||||
return 1 << 20
|
||||
}
|
||||
|
||||
func callMountFuseFs(mountPoint string, opts *MountOptions) (devFuseFd int, err error) {
|
||||
bin, err := fusermountBinary()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Convenience for cleaning up open FDs in the event of an error.
|
||||
closeFd := func(fd int) {
|
||||
if err != nil {
|
||||
syscall.Close(fd)
|
||||
}
|
||||
}
|
||||
|
||||
// Use syscall.Open instead of os.OpenFile to avoid Go garbage collecting an
|
||||
// [os.File], which will close the FD later, but we need to keep it open for
|
||||
// the life of the FUSE mount.
|
||||
const devFuse = "/dev/fuse"
|
||||
devFuseFd, err = syscall.Open(devFuse, syscall.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf(
|
||||
"failed to get a RDWR file descriptor for %s: %w",
|
||||
devFuse, err)
|
||||
}
|
||||
defer closeFd(devFuseFd)
|
||||
// We will also defensively direct the forked process's stdin to the null
|
||||
// device to prevent any unintended side effects.
|
||||
devNullFd, err := syscall.Open(os.DevNull, syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf(
|
||||
"failed to get a RDONLY file descriptor for %s: %w",
|
||||
os.DevNull, err)
|
||||
}
|
||||
defer closeFd(devNullFd)
|
||||
|
||||
// Use syscall.ForkExec directly instead of exec.Command because we need to
|
||||
// pass raw file descriptors to the child, rather than a garbage collected
|
||||
// os.File.
|
||||
env := []string{"MOUNT_FUSEFS_CALL_BY_LIB=1"}
|
||||
argv := []string{
|
||||
bin,
|
||||
"--safe",
|
||||
"-o", strings.Join(opts.optionsStrings(), ","),
|
||||
"3",
|
||||
mountPoint,
|
||||
}
|
||||
fds := []uintptr{
|
||||
uintptr(devNullFd),
|
||||
uintptr(os.Stdout.Fd()),
|
||||
uintptr(os.Stderr.Fd()),
|
||||
uintptr(devFuseFd),
|
||||
}
|
||||
pid, err := syscall.ForkExec(bin, argv, &syscall.ProcAttr{
|
||||
Env: env,
|
||||
Files: fds,
|
||||
})
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("failed to fork mount_fusefs: %w", err)
|
||||
}
|
||||
|
||||
var ws syscall.WaitStatus
|
||||
_, err = syscall.Wait4(pid, &ws, 0, nil)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf(
|
||||
"failed to wait for exit status of mount_fusefs: %w", err)
|
||||
}
|
||||
if ws.ExitStatus() != 0 {
|
||||
return -1, fmt.Errorf(
|
||||
"mount_fusefs: exited with status %d", ws.ExitStatus())
|
||||
}
|
||||
|
||||
return devFuseFd, nil
|
||||
}
|
||||
|
||||
func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
|
||||
// Note: opts.DirectMount is not supported in FreeBSD, but the intended
|
||||
// behavior is to *attempt* a direct mount when it's set, not to return an
|
||||
// error. So in this case, we just ignore it and use the binary from
|
||||
// fusermountBinary().
|
||||
|
||||
// Using the same logic from libfuse to prevent chaos
|
||||
for {
|
||||
f, err := os.OpenFile("/dev/null", os.O_RDWR, 0o000)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
if f.Fd() > 2 {
|
||||
f.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Magic `/dev/fd/N` mountpoint. See the docs for NewServer() for how this
|
||||
// works.
|
||||
|
||||
fd = parseFuseFd(mountPoint)
|
||||
if fd >= 0 {
|
||||
if opts.Debug {
|
||||
opts.Logger.Printf("mount: magic mountpoint %q, using fd %d", mountPoint, fd)
|
||||
}
|
||||
} else {
|
||||
// Usual case: mount via the `fusermount` suid helper
|
||||
fd, err = callMountFuseFs(mountPoint, opts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// golang sets CLOEXEC on file descriptors when they are
|
||||
// acquired through normal operations (e.g. open).
|
||||
// However, for raw FDs, we have to set CLOEXEC manually.
|
||||
syscall.CloseOnExec(fd)
|
||||
|
||||
close(ready)
|
||||
return fd, err
|
||||
}
|
||||
|
||||
func unmount(mountPoint string, opts *MountOptions) (err error) {
|
||||
_ = opts
|
||||
return syscall.Unmount(mountPoint, 0)
|
||||
}
|
||||
|
||||
func fusermountBinary() (string, error) {
|
||||
binPaths := []string{
|
||||
"/sbin/mount_fusefs",
|
||||
}
|
||||
|
||||
for _, path := range binPaths {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no FUSE mount utility found")
|
||||
}
|
||||
279
vendor/github.com/hanwen/go-fuse/v2/fuse/mount_linux.go
generated
vendored
Normal file
279
vendor/github.com/hanwen/go-fuse/v2/fuse/mount_linux.go
generated
vendored
Normal file
@@ -0,0 +1,279 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func unixgramSocketpair() (l, r *os.File, err error) {
|
||||
fd, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_SEQPACKET, 0)
|
||||
if err != nil {
|
||||
return nil, nil, os.NewSyscallError("socketpair",
|
||||
err.(syscall.Errno))
|
||||
}
|
||||
l = os.NewFile(uintptr(fd[0]), "socketpair-half1")
|
||||
r = os.NewFile(uintptr(fd[1]), "socketpair-half2")
|
||||
return
|
||||
}
|
||||
|
||||
// Create a FUSE FS on the specified mount point without using
|
||||
// fusermount.
|
||||
func mountDirect(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
|
||||
fd, err = syscall.Open("/dev/fuse", os.O_RDWR, 0) // use syscall.Open since we want an int fd
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// managed to open dev/fuse, attempt to mount
|
||||
source := opts.FsName
|
||||
if source == "" {
|
||||
source = opts.Name
|
||||
}
|
||||
|
||||
var flags uintptr = syscall.MS_NOSUID | syscall.MS_NODEV
|
||||
if opts.DirectMountFlags != 0 {
|
||||
flags = opts.DirectMountFlags
|
||||
}
|
||||
|
||||
var st syscall.Stat_t
|
||||
err = syscall.Stat(mountPoint, &st)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// some values we need to pass to mount - we do as fusermount does.
|
||||
// override possible since opts.Options comes after.
|
||||
//
|
||||
// Only the options listed below are understood by the kernel:
|
||||
// https://elixir.bootlin.com/linux/v6.14.2/source/fs/fuse/inode.c#L772
|
||||
// https://elixir.bootlin.com/linux/v6.14.2/source/fs/fs_context.c#L41
|
||||
// https://elixir.bootlin.com/linux/v6.14.2/source/fs/fs_context.c#L50
|
||||
// Everything else will cause an EINVAL error from syscall.Mount() and
|
||||
// a corresponding error message in the kernel logs.
|
||||
var r = []string{
|
||||
fmt.Sprintf("fd=%d", fd),
|
||||
fmt.Sprintf("rootmode=%o", st.Mode&syscall.S_IFMT),
|
||||
fmt.Sprintf("user_id=%d", os.Geteuid()),
|
||||
fmt.Sprintf("group_id=%d", os.Getegid()),
|
||||
// match what we do with fusermount
|
||||
fmt.Sprintf("max_read=%d", opts.MaxWrite),
|
||||
}
|
||||
|
||||
// In syscall.Mount(), [no]dev/suid/exec must be
|
||||
// passed as bits in the flags parameter, not as strings.
|
||||
for _, o := range opts.Options {
|
||||
switch o {
|
||||
case "nodev":
|
||||
flags |= syscall.MS_NODEV
|
||||
case "dev":
|
||||
flags &^= syscall.MS_NODEV
|
||||
case "nosuid":
|
||||
flags |= syscall.MS_NOSUID
|
||||
case "suid":
|
||||
flags &^= syscall.MS_NOSUID
|
||||
case "noexec":
|
||||
flags |= syscall.MS_NOEXEC
|
||||
case "exec":
|
||||
flags &^= syscall.MS_NOEXEC
|
||||
default:
|
||||
r = append(r, o)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.AllowOther {
|
||||
r = append(r, "allow_other")
|
||||
}
|
||||
if opts.IDMappedMount && !opts.containsOption("default_permissions") {
|
||||
r = append(r, "default_permissions")
|
||||
}
|
||||
|
||||
if opts.Debug {
|
||||
opts.Logger.Printf("mountDirect: calling syscall.Mount(%q, %q, %q, %#x, %q)",
|
||||
source, mountPoint, "fuse."+opts.Name, flags, strings.Join(r, ","))
|
||||
}
|
||||
err = syscall.Mount(source, mountPoint, "fuse."+opts.Name, flags, strings.Join(r, ","))
|
||||
if err != nil {
|
||||
syscall.Close(fd)
|
||||
return
|
||||
}
|
||||
|
||||
// success
|
||||
close(ready)
|
||||
return
|
||||
}
|
||||
|
||||
// callFusermount calls the `fusermount` suid helper with the right options so
|
||||
// that it:
|
||||
// * opens `/dev/fuse`
|
||||
// * mount()s this file descriptor to `mountPoint`
|
||||
// * passes this file descriptor back to us via a unix domain socket
|
||||
// This file descriptor is returned as `fd`.
|
||||
func callFusermount(mountPoint string, opts *MountOptions) (fd int, err error) {
|
||||
local, remote, err := unixgramSocketpair()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer local.Close()
|
||||
defer remote.Close()
|
||||
|
||||
bin, err := fusermountBinary()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
cmd := []string{bin, mountPoint}
|
||||
if s := opts.optionsStrings(); len(s) > 0 {
|
||||
cmd = append(cmd, "-o", strings.Join(s, ","))
|
||||
}
|
||||
if opts.Debug {
|
||||
opts.Logger.Printf("callFusermount: executing %q", cmd)
|
||||
}
|
||||
proc, err := os.StartProcess(bin,
|
||||
cmd,
|
||||
&os.ProcAttr{
|
||||
Env: []string{"_FUSE_COMMFD=3"},
|
||||
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr, remote}})
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
w, err := proc.Wait()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !w.Success() {
|
||||
err = fmt.Errorf("fusermount exited with code %v\n", w.Sys())
|
||||
return
|
||||
}
|
||||
|
||||
fd, err = getConnection(local)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Create a FUSE FS on the specified mount point. The returned
|
||||
// mount point is always absolute.
|
||||
func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
|
||||
if opts.DirectMount || opts.DirectMountStrict {
|
||||
fd, err := mountDirect(mountPoint, opts, ready)
|
||||
if err == nil {
|
||||
return fd, nil
|
||||
} else if opts.Debug {
|
||||
opts.Logger.Printf("mount: failed to do direct mount: %s", err)
|
||||
}
|
||||
if opts.DirectMountStrict {
|
||||
return -1, err
|
||||
}
|
||||
}
|
||||
|
||||
// Magic `/dev/fd/N` mountpoint. See the docs for NewServer() for how this
|
||||
// works.
|
||||
fd = parseFuseFd(mountPoint)
|
||||
if fd >= 0 {
|
||||
if opts.Debug {
|
||||
opts.Logger.Printf("mount: magic mountpoint %q, using fd %d", mountPoint, fd)
|
||||
}
|
||||
} else {
|
||||
// Usual case: mount via the `fusermount` suid helper
|
||||
fd, err = callFusermount(mountPoint, opts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// golang sets CLOEXEC on file descriptors when they are
|
||||
// acquired through normal operations (e.g. open).
|
||||
// Buf for fd, we have to set CLOEXEC manually
|
||||
syscall.CloseOnExec(fd)
|
||||
close(ready)
|
||||
return fd, err
|
||||
}
|
||||
|
||||
func unmount(mountPoint string, opts *MountOptions) (err error) {
|
||||
if opts.DirectMount || opts.DirectMountStrict {
|
||||
// Attempt to directly unmount, if fails fallback to fusermount method
|
||||
err := syscall.Unmount(mountPoint, 0)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if opts.DirectMountStrict {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
bin, err := fusermountBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
errBuf := bytes.Buffer{}
|
||||
cmd := exec.Command(bin, "-u", mountPoint)
|
||||
cmd.Stderr = &errBuf
|
||||
if opts.Debug {
|
||||
opts.Logger.Printf("unmount: executing %q", cmd.Args)
|
||||
}
|
||||
err = cmd.Run()
|
||||
if errBuf.Len() > 0 {
|
||||
return fmt.Errorf("%s (code %v)\n",
|
||||
errBuf.String(), err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// lookPathFallback - search binary in PATH and, if that fails,
|
||||
// in fallbackDir. This is useful if PATH is possible empty.
|
||||
func lookPathFallback(file string, fallbackDir string) (string, error) {
|
||||
binPath, err := exec.LookPath(file)
|
||||
if err == nil {
|
||||
return binPath, nil
|
||||
}
|
||||
|
||||
abs := path.Join(fallbackDir, file)
|
||||
return exec.LookPath(abs)
|
||||
}
|
||||
|
||||
// fusermountBinary returns the path to the `fusermount3` binary, or, if not
|
||||
// found, the `fusermount` binary.
|
||||
func fusermountBinary() (string, error) {
|
||||
if path, err := lookPathFallback("fusermount3", "/bin"); err == nil {
|
||||
return path, nil
|
||||
}
|
||||
return lookPathFallback("fusermount", "/bin")
|
||||
}
|
||||
|
||||
func umountBinary() (string, error) {
|
||||
return lookPathFallback("umount", "/bin")
|
||||
}
|
||||
|
||||
func getMaxWrite() int {
|
||||
return maxPageLimit() * syscall.Getpagesize()
|
||||
}
|
||||
|
||||
func maxPageLimit() (lim int) {
|
||||
lim = 256
|
||||
|
||||
d, err := os.ReadFile("/proc/sys/fs/fuse/max_pages_limit")
|
||||
if err != nil {
|
||||
return lim
|
||||
}
|
||||
|
||||
newLim, err := strconv.Atoi(string(d))
|
||||
if err != nil {
|
||||
return lim
|
||||
}
|
||||
|
||||
return newLim
|
||||
}
|
||||
755
vendor/github.com/hanwen/go-fuse/v2/fuse/opcode.go
generated
vendored
Normal file
755
vendor/github.com/hanwen/go-fuse/v2/fuse/opcode.go
generated
vendored
Normal file
@@ -0,0 +1,755 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
_OP_LOOKUP = uint32(1)
|
||||
_OP_FORGET = uint32(2)
|
||||
_OP_GETATTR = uint32(3)
|
||||
_OP_SETATTR = uint32(4)
|
||||
_OP_READLINK = uint32(5)
|
||||
_OP_SYMLINK = uint32(6)
|
||||
_OP_MKNOD = uint32(8)
|
||||
_OP_MKDIR = uint32(9)
|
||||
_OP_UNLINK = uint32(10)
|
||||
_OP_RMDIR = uint32(11)
|
||||
_OP_RENAME = uint32(12)
|
||||
_OP_LINK = uint32(13)
|
||||
_OP_OPEN = uint32(14)
|
||||
_OP_READ = uint32(15)
|
||||
_OP_WRITE = uint32(16)
|
||||
_OP_STATFS = uint32(17)
|
||||
_OP_RELEASE = uint32(18)
|
||||
_OP_FSYNC = uint32(20)
|
||||
_OP_SETXATTR = uint32(21)
|
||||
_OP_GETXATTR = uint32(22)
|
||||
_OP_LISTXATTR = uint32(23)
|
||||
_OP_REMOVEXATTR = uint32(24)
|
||||
_OP_FLUSH = uint32(25)
|
||||
_OP_INIT = uint32(26)
|
||||
_OP_OPENDIR = uint32(27)
|
||||
_OP_READDIR = uint32(28)
|
||||
_OP_RELEASEDIR = uint32(29)
|
||||
_OP_FSYNCDIR = uint32(30)
|
||||
_OP_GETLK = uint32(31)
|
||||
_OP_SETLK = uint32(32)
|
||||
_OP_SETLKW = uint32(33)
|
||||
_OP_ACCESS = uint32(34)
|
||||
_OP_CREATE = uint32(35)
|
||||
_OP_INTERRUPT = uint32(36)
|
||||
_OP_BMAP = uint32(37)
|
||||
_OP_DESTROY = uint32(38)
|
||||
_OP_IOCTL = uint32(39)
|
||||
_OP_POLL = uint32(40)
|
||||
_OP_NOTIFY_REPLY = uint32(41)
|
||||
_OP_BATCH_FORGET = uint32(42)
|
||||
_OP_FALLOCATE = uint32(43) // protocol version 19.
|
||||
_OP_READDIRPLUS = uint32(44) // protocol version 21.
|
||||
_OP_RENAME2 = uint32(45) // protocol version 23.
|
||||
_OP_LSEEK = uint32(46) // protocol version 24
|
||||
_OP_COPY_FILE_RANGE = uint32(47) // protocol version 28.
|
||||
|
||||
_OP_SETUPMAPPING = 48
|
||||
_OP_REMOVEMAPPING = 49
|
||||
_OP_SYNCFS = 50
|
||||
_OP_TMPFILE = 51
|
||||
_OP_STATX = 52
|
||||
|
||||
// The following entries don't have to be compatible across Go-FUSE versions.
|
||||
_OP_NOTIFY_INVAL_ENTRY = uint32(100)
|
||||
_OP_NOTIFY_INVAL_INODE = uint32(101)
|
||||
_OP_NOTIFY_STORE_CACHE = uint32(102)
|
||||
_OP_NOTIFY_RETRIEVE_CACHE = uint32(103)
|
||||
_OP_NOTIFY_DELETE = uint32(104) // protocol version 18
|
||||
|
||||
_OPCODE_COUNT = uint32(105)
|
||||
|
||||
// Constants from Linux kernel fs/fuse/fuse_i.h
|
||||
// Default MaxPages value in all kernel versions
|
||||
_FUSE_DEFAULT_MAX_PAGES_PER_REQ = 32
|
||||
// Upper MaxPages limit in Linux v4.20+ (v4.19 and older: 32)
|
||||
_FUSE_MAX_MAX_PAGES = 256
|
||||
)
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
func doInit(server *protocolServer, req *request) {
|
||||
input := (*InitIn)(req.inData())
|
||||
if input.Major != _FUSE_KERNEL_VERSION {
|
||||
log.Printf("Major versions does not match. Given %d, want %d\n", input.Major, _FUSE_KERNEL_VERSION)
|
||||
req.status = EIO
|
||||
return
|
||||
}
|
||||
if input.Minor < _MINIMUM_MINOR_VERSION {
|
||||
log.Printf("Minor version is less than we support. Given %d, want at least %d\n", input.Minor, _MINIMUM_MINOR_VERSION)
|
||||
req.status = EIO
|
||||
return
|
||||
}
|
||||
|
||||
kernelFlags := input.Flags64()
|
||||
server.kernelSettings = *input
|
||||
kernelFlags &= (CAP_ASYNC_READ | CAP_BIG_WRITES | CAP_FILE_OPS |
|
||||
CAP_READDIRPLUS | CAP_NO_OPEN_SUPPORT | CAP_PARALLEL_DIROPS | CAP_MAX_PAGES | CAP_RENAME_SWAP | CAP_PASSTHROUGH | CAP_ALLOW_IDMAP)
|
||||
|
||||
if server.opts.EnableLocks {
|
||||
kernelFlags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS
|
||||
}
|
||||
if server.opts.EnableSymlinkCaching {
|
||||
kernelFlags |= CAP_CACHE_SYMLINKS
|
||||
}
|
||||
if server.opts.EnableAcl {
|
||||
kernelFlags |= CAP_POSIX_ACL
|
||||
}
|
||||
if server.opts.SyncRead {
|
||||
// Clear CAP_ASYNC_READ
|
||||
kernelFlags &= ^uint64(CAP_ASYNC_READ)
|
||||
}
|
||||
if server.opts.DisableReadDirPlus {
|
||||
// Clear CAP_READDIRPLUS
|
||||
kernelFlags &= ^uint64(CAP_READDIRPLUS)
|
||||
}
|
||||
if !server.opts.IDMappedMount {
|
||||
// Clear CAP_ALLOW_IDMAP
|
||||
kernelFlags &= ^uint64(CAP_ALLOW_IDMAP)
|
||||
}
|
||||
|
||||
if server.opts.ExplicitDataCacheControl {
|
||||
// we don't want CAP_AUTO_INVAL_DATA even if we cannot go into fully explicit mode
|
||||
kernelFlags |= input.Flags64() & CAP_EXPLICIT_INVAL_DATA
|
||||
} else {
|
||||
kernelFlags |= input.Flags64() & CAP_AUTO_INVAL_DATA
|
||||
}
|
||||
|
||||
// maxPages is the maximum request size we want the kernel to use, in units of
|
||||
// memory pages (usually 4kiB). Linux v4.19 and older ignore this and always use
|
||||
// 128kiB.
|
||||
maxPages := (server.opts.MaxWrite-1)/syscall.Getpagesize() + 1 // Round up
|
||||
|
||||
out := (*InitOut)(req.outData())
|
||||
*out = InitOut{
|
||||
Major: _FUSE_KERNEL_VERSION,
|
||||
Minor: _OUR_MINOR_VERSION,
|
||||
MaxReadAhead: input.MaxReadAhead,
|
||||
MaxWrite: uint32(server.opts.MaxWrite),
|
||||
CongestionThreshold: uint16(server.opts.MaxBackground * 3 / 4),
|
||||
MaxBackground: uint16(server.opts.MaxBackground),
|
||||
MaxPages: uint16(maxPages),
|
||||
MaxStackDepth: uint32(server.opts.MaxStackDepth),
|
||||
}
|
||||
out.setFlags(kernelFlags)
|
||||
if server.opts.MaxReadAhead != 0 && uint32(server.opts.MaxReadAhead) < out.MaxReadAhead {
|
||||
out.MaxReadAhead = uint32(server.opts.MaxReadAhead)
|
||||
}
|
||||
if out.Minor > input.Minor {
|
||||
out.Minor = input.Minor
|
||||
}
|
||||
|
||||
req.status = OK
|
||||
}
|
||||
|
||||
func doOpen(server *protocolServer, req *request) {
|
||||
out := (*OpenOut)(req.outData())
|
||||
status := server.fileSystem.Open(req.cancel, (*OpenIn)(req.inData()), out)
|
||||
req.status = status
|
||||
if status != OK {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func doCreate(server *protocolServer, req *request) {
|
||||
out := (*CreateOut)(req.outData())
|
||||
status := server.fileSystem.Create(req.cancel, (*CreateIn)(req.inData()), req.filename(), out)
|
||||
req.status = status
|
||||
}
|
||||
|
||||
func doReadDir(server *protocolServer, req *request) {
|
||||
in := (*ReadIn)(req.inData())
|
||||
out := NewDirEntryList(req.outPayload, uint64(in.Offset))
|
||||
code := server.fileSystem.ReadDir(req.cancel, in, out)
|
||||
req.outPayload = out.bytes()
|
||||
req.status = code
|
||||
}
|
||||
|
||||
func doReadDirPlus(server *protocolServer, req *request) {
|
||||
in := (*ReadIn)(req.inData())
|
||||
out := NewDirEntryList(req.outPayload, uint64(in.Offset))
|
||||
|
||||
code := server.fileSystem.ReadDirPlus(req.cancel, in, out)
|
||||
req.outPayload = out.bytes()
|
||||
req.status = code
|
||||
}
|
||||
|
||||
func doOpenDir(server *protocolServer, req *request) {
|
||||
out := (*OpenOut)(req.outData())
|
||||
status := server.fileSystem.OpenDir(req.cancel, (*OpenIn)(req.inData()), out)
|
||||
req.status = status
|
||||
}
|
||||
|
||||
func doSetattr(server *protocolServer, req *request) {
|
||||
out := (*AttrOut)(req.outData())
|
||||
req.status = server.fileSystem.SetAttr(req.cancel, (*SetAttrIn)(req.inData()), out)
|
||||
}
|
||||
|
||||
func doWrite(server *protocolServer, req *request) {
|
||||
n, status := server.fileSystem.Write(req.cancel, (*WriteIn)(req.inData()), req.inPayload)
|
||||
o := (*WriteOut)(req.outData())
|
||||
o.Size = n
|
||||
req.status = status
|
||||
}
|
||||
|
||||
func doNotifyReply(server *protocolServer, req *request) {
|
||||
reply := (*NotifyRetrieveIn)(req.inData())
|
||||
server.retrieveMu.Lock()
|
||||
reading := server.retrieveTab[reply.Unique]
|
||||
delete(server.retrieveTab, reply.Unique)
|
||||
server.retrieveMu.Unlock()
|
||||
|
||||
badf := func(format string, argv ...interface{}) {
|
||||
server.opts.Logger.Printf("notify reply: "+format, argv...)
|
||||
}
|
||||
|
||||
if reading == nil {
|
||||
badf("unexpected unique - ignoring")
|
||||
return
|
||||
}
|
||||
|
||||
reading.n = 0
|
||||
reading.st = EIO
|
||||
defer close(reading.ready)
|
||||
|
||||
if reading.nodeid != reply.NodeId {
|
||||
badf("inode mismatch: expected %s, got %s", reading.nodeid, reply.NodeId)
|
||||
return
|
||||
}
|
||||
|
||||
if reading.offset != reply.Offset {
|
||||
badf("offset mismatch: expected @%d, got @%d", reading.offset, reply.Offset)
|
||||
return
|
||||
}
|
||||
|
||||
if len(reading.dest) < len(req.inPayload) {
|
||||
badf("too much data: requested %db, got %db (will use only %db)", len(reading.dest), len(req.inPayload), len(reading.dest))
|
||||
}
|
||||
|
||||
reading.n = copy(reading.dest, req.inPayload)
|
||||
reading.st = OK
|
||||
}
|
||||
|
||||
const _SECURITY_CAPABILITY = "security.capability"
|
||||
const _SECURITY_ACL = "system.posix_acl_access"
|
||||
const _SECURITY_ACL_DEFAULT = "system.posix_acl_default"
|
||||
|
||||
func doGetXAttr(server *protocolServer, req *request) {
|
||||
if server.opts.DisableXAttrs {
|
||||
req.status = ENOSYS
|
||||
return
|
||||
}
|
||||
|
||||
if server.opts.IgnoreSecurityLabels && req.inHeader().Opcode == _OP_GETXATTR {
|
||||
fn := req.filename()
|
||||
if fn == _SECURITY_CAPABILITY || fn == _SECURITY_ACL_DEFAULT ||
|
||||
fn == _SECURITY_ACL {
|
||||
req.status = ENOATTR
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
input := (*GetXAttrIn)(req.inData())
|
||||
out := (*GetXAttrOut)(req.outData())
|
||||
|
||||
var n uint32
|
||||
switch req.inHeader().Opcode {
|
||||
case _OP_GETXATTR:
|
||||
n, req.status = server.fileSystem.GetXAttr(req.cancel, req.inHeader(), req.filename(), req.outPayload)
|
||||
case _OP_LISTXATTR:
|
||||
n, req.status = server.fileSystem.ListXAttr(req.cancel, req.inHeader(), req.outPayload)
|
||||
default:
|
||||
req.status = ENOSYS
|
||||
}
|
||||
|
||||
if input.Size == 0 && req.status == ERANGE {
|
||||
// For input.size==0, returning ERANGE is an error.
|
||||
req.status = OK
|
||||
out.Size = n
|
||||
} else if req.status.Ok() {
|
||||
// ListXAttr called with an empty buffer returns the current size of
|
||||
// the list but does not touch the buffer (see man 2 listxattr).
|
||||
if len(req.outPayload) > 0 {
|
||||
req.outPayload = req.outPayload[:n]
|
||||
}
|
||||
out.Size = n
|
||||
} else {
|
||||
req.outPayload = req.outPayload[:0]
|
||||
}
|
||||
}
|
||||
|
||||
func doGetAttr(server *protocolServer, req *request) {
|
||||
out := (*AttrOut)(req.outData())
|
||||
s := server.fileSystem.GetAttr(req.cancel, (*GetAttrIn)(req.inData()), out)
|
||||
req.status = s
|
||||
}
|
||||
|
||||
// doForget - forget one NodeId
|
||||
func doForget(server *protocolServer, req *request) {
|
||||
if !server.opts.RememberInodes {
|
||||
server.fileSystem.Forget(req.inHeader().NodeId, (*ForgetIn)(req.inData()).Nlookup)
|
||||
}
|
||||
}
|
||||
|
||||
// doBatchForget - forget a list of NodeIds
|
||||
func doBatchForget(server *protocolServer, req *request) {
|
||||
in := (*_BatchForgetIn)(req.inData())
|
||||
wantBytes := uintptr(in.Count) * unsafe.Sizeof(_ForgetOne{})
|
||||
if uintptr(len(req.inPayload)) < wantBytes {
|
||||
// We have no return value to complain, so log an error.
|
||||
server.opts.Logger.Printf("Too few bytes for batch forget. Got %d bytes, want %d (%d entries)",
|
||||
len(req.inPayload), wantBytes, in.Count)
|
||||
}
|
||||
|
||||
forgets := unsafe.Slice((*_ForgetOne)(unsafe.Pointer(&req.inPayload[0])), in.Count)
|
||||
for i, f := range forgets {
|
||||
if server.opts.Debug {
|
||||
server.opts.Logger.Printf("doBatchForget: rx %d %d/%d: FORGET n%d {Nlookup=%d}",
|
||||
req.inHeader().Unique, i+1, len(forgets), f.NodeId, f.Nlookup)
|
||||
}
|
||||
if f.NodeId == pollHackInode {
|
||||
continue
|
||||
}
|
||||
server.fileSystem.Forget(f.NodeId, f.Nlookup)
|
||||
}
|
||||
}
|
||||
|
||||
func doReadlink(server *protocolServer, req *request) {
|
||||
req.outPayload, req.status = server.fileSystem.Readlink(req.cancel, req.inHeader())
|
||||
}
|
||||
|
||||
func doLookup(server *protocolServer, req *request) {
|
||||
out := (*EntryOut)(req.outData())
|
||||
req.status = server.fileSystem.Lookup(req.cancel, req.inHeader(), req.filename(), out)
|
||||
}
|
||||
|
||||
func doMknod(server *protocolServer, req *request) {
|
||||
out := (*EntryOut)(req.outData())
|
||||
|
||||
req.status = server.fileSystem.Mknod(req.cancel, (*MknodIn)(req.inData()), req.filename(), out)
|
||||
}
|
||||
|
||||
func doMkdir(server *protocolServer, req *request) {
|
||||
out := (*EntryOut)(req.outData())
|
||||
req.status = server.fileSystem.Mkdir(req.cancel, (*MkdirIn)(req.inData()), req.filename(), out)
|
||||
}
|
||||
|
||||
func doUnlink(server *protocolServer, req *request) {
|
||||
req.status = server.fileSystem.Unlink(req.cancel, req.inHeader(), req.filename())
|
||||
}
|
||||
|
||||
func doRmdir(server *protocolServer, req *request) {
|
||||
req.status = server.fileSystem.Rmdir(req.cancel, req.inHeader(), req.filename())
|
||||
}
|
||||
|
||||
func doLink(server *protocolServer, req *request) {
|
||||
out := (*EntryOut)(req.outData())
|
||||
req.status = server.fileSystem.Link(req.cancel, (*LinkIn)(req.inData()), req.filename(), out)
|
||||
}
|
||||
|
||||
func doRead(server *protocolServer, req *request) {
|
||||
in := (*ReadIn)(req.inData())
|
||||
req.readResult, req.status = server.fileSystem.Read(req.cancel, in, req.outPayload)
|
||||
if fd, ok := req.readResult.(*readResultFd); ok {
|
||||
req.fdData = fd
|
||||
} else if req.readResult != nil && req.status.Ok() {
|
||||
req.outPayload, req.status = req.readResult.Bytes(req.outPayload)
|
||||
}
|
||||
}
|
||||
|
||||
func doFlush(server *protocolServer, req *request) {
|
||||
req.status = server.fileSystem.Flush(req.cancel, (*FlushIn)(req.inData()))
|
||||
}
|
||||
|
||||
func doRelease(server *protocolServer, req *request) {
|
||||
server.fileSystem.Release(req.cancel, (*ReleaseIn)(req.inData()))
|
||||
}
|
||||
|
||||
func doFsync(server *protocolServer, req *request) {
|
||||
req.status = server.fileSystem.Fsync(req.cancel, (*FsyncIn)(req.inData()))
|
||||
}
|
||||
|
||||
func doReleaseDir(server *protocolServer, req *request) {
|
||||
server.fileSystem.ReleaseDir((*ReleaseIn)(req.inData()))
|
||||
}
|
||||
|
||||
func doFsyncDir(server *protocolServer, req *request) {
|
||||
req.status = server.fileSystem.FsyncDir(req.cancel, (*FsyncIn)(req.inData()))
|
||||
}
|
||||
|
||||
func doSetXAttr(server *protocolServer, req *request) {
|
||||
i := bytes.IndexByte(req.inPayload, 0)
|
||||
req.status = server.fileSystem.SetXAttr(req.cancel, (*SetXAttrIn)(req.inData()), string(req.inPayload[:i]), req.inPayload[i+1:])
|
||||
}
|
||||
|
||||
func doRemoveXAttr(server *protocolServer, req *request) {
|
||||
req.status = server.fileSystem.RemoveXAttr(req.cancel, req.inHeader(), req.filename())
|
||||
}
|
||||
|
||||
func doAccess(server *protocolServer, req *request) {
|
||||
req.status = server.fileSystem.Access(req.cancel, (*AccessIn)(req.inData()))
|
||||
}
|
||||
|
||||
func doSymlink(server *protocolServer, req *request) {
|
||||
out := (*EntryOut)(req.outData())
|
||||
n1, n2 := req.filenames()
|
||||
|
||||
req.status = server.fileSystem.Symlink(req.cancel, req.inHeader(), n2, n1, out)
|
||||
}
|
||||
|
||||
func doRename(server *protocolServer, req *request) {
|
||||
if server.kernelSettings.supportsRenameSwap() {
|
||||
doRename2(server, req)
|
||||
return
|
||||
}
|
||||
in1 := (*Rename1In)(req.inData())
|
||||
in := RenameIn{
|
||||
InHeader: in1.InHeader,
|
||||
Newdir: in1.Newdir,
|
||||
}
|
||||
n1, n2 := req.filenames()
|
||||
req.status = server.fileSystem.Rename(req.cancel, &in, n1, n2)
|
||||
}
|
||||
|
||||
func doRename2(server *protocolServer, req *request) {
|
||||
n1, n2 := req.filenames()
|
||||
req.status = server.fileSystem.Rename(req.cancel, (*RenameIn)(req.inData()), n1, n2)
|
||||
}
|
||||
|
||||
func doStatFs(server *protocolServer, req *request) {
|
||||
out := (*StatfsOut)(req.outData())
|
||||
req.status = server.fileSystem.StatFs(req.cancel, req.inHeader(), out)
|
||||
if req.status == ENOSYS && runtime.GOOS == "darwin" {
|
||||
// OSX FUSE requires Statfs to be implemented for the
|
||||
// mount to succeed.
|
||||
*out = StatfsOut{}
|
||||
req.status = OK
|
||||
}
|
||||
}
|
||||
|
||||
func doIoctl(server *protocolServer, req *request) {
|
||||
req.status = server.fileSystem.Ioctl(req.cancel, (*IoctlIn)(req.inData()), req.inPayload, (*IoctlOut)(req.outData()),
|
||||
req.outPayload)
|
||||
}
|
||||
|
||||
func doDestroy(server *protocolServer, req *request) {
|
||||
req.status = OK
|
||||
}
|
||||
|
||||
func doFallocate(server *protocolServer, req *request) {
|
||||
req.status = server.fileSystem.Fallocate(req.cancel, (*FallocateIn)(req.inData()))
|
||||
}
|
||||
|
||||
func doGetLk(server *protocolServer, req *request) {
|
||||
req.status = server.fileSystem.GetLk(req.cancel, (*LkIn)(req.inData()), (*LkOut)(req.outData()))
|
||||
}
|
||||
|
||||
func doSetLk(server *protocolServer, req *request) {
|
||||
req.status = server.fileSystem.SetLk(req.cancel, (*LkIn)(req.inData()))
|
||||
}
|
||||
|
||||
func doSetLkw(server *protocolServer, req *request) {
|
||||
req.status = server.fileSystem.SetLkw(req.cancel, (*LkIn)(req.inData()))
|
||||
}
|
||||
|
||||
func doLseek(server *protocolServer, req *request) {
|
||||
in := (*LseekIn)(req.inData())
|
||||
out := (*LseekOut)(req.outData())
|
||||
req.status = server.fileSystem.Lseek(req.cancel, in, out)
|
||||
}
|
||||
|
||||
func doCopyFileRange(server *protocolServer, req *request) {
|
||||
in := (*CopyFileRangeIn)(req.inData())
|
||||
out := (*WriteOut)(req.outData())
|
||||
|
||||
out.Size, req.status = server.fileSystem.CopyFileRange(req.cancel, in)
|
||||
}
|
||||
|
||||
func doInterrupt(server *protocolServer, req *request) {
|
||||
input := (*InterruptIn)(req.inData())
|
||||
req.status = server.interruptRequest(input.Unique)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
type operationFunc func(*protocolServer, *request)
|
||||
type castPointerFunc func(unsafe.Pointer) interface{}
|
||||
|
||||
type operationHandler struct {
|
||||
Name string
|
||||
Func operationFunc
|
||||
InputSize uintptr
|
||||
OutputSize uintptr
|
||||
|
||||
InType interface{}
|
||||
OutType interface{}
|
||||
FileNames int
|
||||
FileNameOut bool
|
||||
}
|
||||
|
||||
var operationHandlers []*operationHandler
|
||||
|
||||
func operationName(op uint32) string {
|
||||
h := getHandler(op)
|
||||
if h == nil {
|
||||
return "unknown"
|
||||
}
|
||||
return h.Name
|
||||
}
|
||||
|
||||
func getHandler(o uint32) *operationHandler {
|
||||
if o >= _OPCODE_COUNT {
|
||||
return nil
|
||||
}
|
||||
return operationHandlers[o]
|
||||
}
|
||||
|
||||
// maximum size of all input headers
|
||||
var maxInputSize uintptr
|
||||
|
||||
func init() {
|
||||
operationHandlers = make([]*operationHandler, _OPCODE_COUNT)
|
||||
for i := range operationHandlers {
|
||||
operationHandlers[i] = &operationHandler{Name: fmt.Sprintf("OPCODE-%d", i)}
|
||||
}
|
||||
|
||||
fileOps := []uint32{_OP_READLINK, _OP_NOTIFY_INVAL_ENTRY, _OP_NOTIFY_DELETE}
|
||||
for _, op := range fileOps {
|
||||
operationHandlers[op].FileNameOut = true
|
||||
}
|
||||
|
||||
for op, v := range map[uint32]string{
|
||||
_OP_LOOKUP: "LOOKUP",
|
||||
_OP_FORGET: "FORGET",
|
||||
_OP_BATCH_FORGET: "BATCH_FORGET",
|
||||
_OP_GETATTR: "GETATTR",
|
||||
_OP_SETATTR: "SETATTR",
|
||||
_OP_READLINK: "READLINK",
|
||||
_OP_SYMLINK: "SYMLINK",
|
||||
_OP_MKNOD: "MKNOD",
|
||||
_OP_MKDIR: "MKDIR",
|
||||
_OP_UNLINK: "UNLINK",
|
||||
_OP_RMDIR: "RMDIR",
|
||||
_OP_RENAME: "RENAME",
|
||||
_OP_LINK: "LINK",
|
||||
_OP_OPEN: "OPEN",
|
||||
_OP_READ: "READ",
|
||||
_OP_WRITE: "WRITE",
|
||||
_OP_STATFS: "STATFS",
|
||||
_OP_RELEASE: "RELEASE",
|
||||
_OP_FSYNC: "FSYNC",
|
||||
_OP_SETXATTR: "SETXATTR",
|
||||
_OP_GETXATTR: "GETXATTR",
|
||||
_OP_LISTXATTR: "LISTXATTR",
|
||||
_OP_REMOVEXATTR: "REMOVEXATTR",
|
||||
_OP_FLUSH: "FLUSH",
|
||||
_OP_INIT: "INIT",
|
||||
_OP_OPENDIR: "OPENDIR",
|
||||
_OP_READDIR: "READDIR",
|
||||
_OP_RELEASEDIR: "RELEASEDIR",
|
||||
_OP_FSYNCDIR: "FSYNCDIR",
|
||||
_OP_GETLK: "GETLK",
|
||||
_OP_SETLK: "SETLK",
|
||||
_OP_SETLKW: "SETLKW",
|
||||
_OP_ACCESS: "ACCESS",
|
||||
_OP_CREATE: "CREATE",
|
||||
_OP_INTERRUPT: "INTERRUPT",
|
||||
_OP_BMAP: "BMAP",
|
||||
_OP_DESTROY: "DESTROY",
|
||||
_OP_IOCTL: "IOCTL",
|
||||
_OP_POLL: "POLL",
|
||||
_OP_NOTIFY_REPLY: "NOTIFY_REPLY",
|
||||
_OP_NOTIFY_INVAL_ENTRY: "NOTIFY_INVAL_ENTRY",
|
||||
_OP_NOTIFY_INVAL_INODE: "NOTIFY_INVAL_INODE",
|
||||
_OP_NOTIFY_STORE_CACHE: "NOTIFY_STORE",
|
||||
_OP_NOTIFY_RETRIEVE_CACHE: "NOTIFY_RETRIEVE",
|
||||
_OP_NOTIFY_DELETE: "NOTIFY_DELETE",
|
||||
_OP_FALLOCATE: "FALLOCATE",
|
||||
_OP_READDIRPLUS: "READDIRPLUS",
|
||||
_OP_RENAME2: "RENAME2",
|
||||
_OP_LSEEK: "LSEEK",
|
||||
_OP_COPY_FILE_RANGE: "COPY_FILE_RANGE",
|
||||
_OP_SETUPMAPPING: "SETUPMAPPING",
|
||||
_OP_REMOVEMAPPING: "REMOVEMAPPING",
|
||||
_OP_SYNCFS: "SYNCFS",
|
||||
_OP_TMPFILE: "TMPFILE",
|
||||
} {
|
||||
operationHandlers[op].Name = v
|
||||
}
|
||||
|
||||
for op, v := range map[uint32]operationFunc{
|
||||
_OP_OPEN: doOpen,
|
||||
_OP_READDIR: doReadDir,
|
||||
_OP_WRITE: doWrite,
|
||||
_OP_OPENDIR: doOpenDir,
|
||||
_OP_CREATE: doCreate,
|
||||
_OP_SETATTR: doSetattr,
|
||||
_OP_GETXATTR: doGetXAttr,
|
||||
_OP_LISTXATTR: doGetXAttr,
|
||||
_OP_GETATTR: doGetAttr,
|
||||
_OP_FORGET: doForget,
|
||||
_OP_BATCH_FORGET: doBatchForget,
|
||||
_OP_READLINK: doReadlink,
|
||||
_OP_INIT: doInit,
|
||||
_OP_LOOKUP: doLookup,
|
||||
_OP_MKNOD: doMknod,
|
||||
_OP_MKDIR: doMkdir,
|
||||
_OP_UNLINK: doUnlink,
|
||||
_OP_RMDIR: doRmdir,
|
||||
_OP_LINK: doLink,
|
||||
_OP_READ: doRead,
|
||||
_OP_FLUSH: doFlush,
|
||||
_OP_RELEASE: doRelease,
|
||||
_OP_FSYNC: doFsync,
|
||||
_OP_RELEASEDIR: doReleaseDir,
|
||||
_OP_FSYNCDIR: doFsyncDir,
|
||||
_OP_SETXATTR: doSetXAttr,
|
||||
_OP_REMOVEXATTR: doRemoveXAttr,
|
||||
_OP_GETLK: doGetLk,
|
||||
_OP_SETLK: doSetLk,
|
||||
_OP_SETLKW: doSetLkw,
|
||||
_OP_ACCESS: doAccess,
|
||||
_OP_SYMLINK: doSymlink,
|
||||
_OP_RENAME: doRename,
|
||||
_OP_STATFS: doStatFs,
|
||||
_OP_IOCTL: doIoctl,
|
||||
_OP_DESTROY: doDestroy,
|
||||
_OP_NOTIFY_REPLY: doNotifyReply,
|
||||
_OP_FALLOCATE: doFallocate,
|
||||
_OP_READDIRPLUS: doReadDirPlus,
|
||||
_OP_RENAME2: doRename2,
|
||||
_OP_INTERRUPT: doInterrupt,
|
||||
_OP_COPY_FILE_RANGE: doCopyFileRange,
|
||||
_OP_LSEEK: doLseek,
|
||||
} {
|
||||
operationHandlers[op].Func = v
|
||||
}
|
||||
|
||||
// Outputs.
|
||||
for op, f := range map[uint32]interface{}{
|
||||
_OP_BMAP: _BmapOut{},
|
||||
_OP_COPY_FILE_RANGE: WriteOut{},
|
||||
_OP_CREATE: CreateOut{},
|
||||
_OP_GETATTR: AttrOut{},
|
||||
_OP_GETLK: LkOut{},
|
||||
_OP_GETXATTR: GetXAttrOut{},
|
||||
_OP_INIT: InitOut{},
|
||||
_OP_IOCTL: IoctlOut{},
|
||||
_OP_LINK: EntryOut{},
|
||||
_OP_LISTXATTR: GetXAttrOut{},
|
||||
_OP_LOOKUP: EntryOut{},
|
||||
_OP_LSEEK: LseekOut{},
|
||||
_OP_MKDIR: EntryOut{},
|
||||
_OP_MKNOD: EntryOut{},
|
||||
_OP_NOTIFY_DELETE: NotifyInvalDeleteOut{},
|
||||
_OP_NOTIFY_INVAL_ENTRY: NotifyInvalEntryOut{},
|
||||
_OP_NOTIFY_INVAL_INODE: NotifyInvalInodeOut{},
|
||||
_OP_NOTIFY_RETRIEVE_CACHE: NotifyRetrieveOut{},
|
||||
_OP_NOTIFY_STORE_CACHE: NotifyStoreOut{},
|
||||
_OP_OPEN: OpenOut{},
|
||||
_OP_OPENDIR: OpenOut{},
|
||||
_OP_POLL: _PollOut{},
|
||||
_OP_SETATTR: AttrOut{},
|
||||
_OP_STATFS: StatfsOut{},
|
||||
_OP_SYMLINK: EntryOut{},
|
||||
_OP_WRITE: WriteOut{},
|
||||
} {
|
||||
operationHandlers[op].OutType = f
|
||||
operationHandlers[op].OutputSize = typSize(f)
|
||||
}
|
||||
|
||||
// Inputs.
|
||||
for op, f := range map[uint32]interface{}{
|
||||
_OP_ACCESS: AccessIn{},
|
||||
_OP_BATCH_FORGET: _BatchForgetIn{},
|
||||
_OP_BMAP: _BmapIn{},
|
||||
_OP_COPY_FILE_RANGE: CopyFileRangeIn{},
|
||||
_OP_CREATE: CreateIn{},
|
||||
_OP_FALLOCATE: FallocateIn{},
|
||||
_OP_FLUSH: FlushIn{},
|
||||
_OP_FORGET: ForgetIn{},
|
||||
_OP_FSYNC: FsyncIn{},
|
||||
_OP_FSYNCDIR: FsyncIn{},
|
||||
_OP_GETATTR: GetAttrIn{},
|
||||
_OP_GETLK: LkIn{},
|
||||
_OP_GETXATTR: GetXAttrIn{},
|
||||
_OP_INIT: InitIn{},
|
||||
_OP_INTERRUPT: InterruptIn{},
|
||||
_OP_IOCTL: IoctlIn{},
|
||||
_OP_LINK: LinkIn{},
|
||||
_OP_LISTXATTR: GetXAttrIn{},
|
||||
_OP_LSEEK: LseekIn{},
|
||||
_OP_MKDIR: MkdirIn{},
|
||||
_OP_MKNOD: MknodIn{},
|
||||
_OP_NOTIFY_REPLY: NotifyRetrieveIn{},
|
||||
_OP_OPEN: OpenIn{},
|
||||
_OP_OPENDIR: OpenIn{},
|
||||
_OP_POLL: _PollIn{},
|
||||
_OP_READ: ReadIn{},
|
||||
_OP_READDIR: ReadIn{},
|
||||
_OP_READDIRPLUS: ReadIn{},
|
||||
_OP_RELEASE: ReleaseIn{},
|
||||
_OP_RELEASEDIR: ReleaseIn{},
|
||||
_OP_RENAME2: RenameIn{},
|
||||
_OP_RENAME: Rename1In{},
|
||||
_OP_SETATTR: SetAttrIn{},
|
||||
_OP_SETLK: LkIn{},
|
||||
_OP_SETLKW: LkIn{},
|
||||
_OP_SETXATTR: SetXAttrIn{},
|
||||
_OP_WRITE: WriteIn{},
|
||||
} {
|
||||
operationHandlers[op].InType = f
|
||||
sz := typSize(f)
|
||||
operationHandlers[op].InputSize = sz
|
||||
if maxInputSize < sz {
|
||||
maxInputSize = sz
|
||||
}
|
||||
}
|
||||
|
||||
// File name args.
|
||||
for op, count := range map[uint32]int{
|
||||
_OP_CREATE: 1,
|
||||
_OP_SETXATTR: 1,
|
||||
_OP_GETXATTR: 1,
|
||||
_OP_LINK: 1,
|
||||
_OP_LOOKUP: 1,
|
||||
_OP_MKDIR: 1,
|
||||
_OP_MKNOD: 1,
|
||||
_OP_REMOVEXATTR: 1,
|
||||
_OP_RENAME: 2,
|
||||
_OP_RENAME2: 2,
|
||||
_OP_RMDIR: 1,
|
||||
_OP_SYMLINK: 2,
|
||||
_OP_UNLINK: 1,
|
||||
} {
|
||||
operationHandlers[op].FileNames = count
|
||||
}
|
||||
|
||||
checkFixedBufferSize()
|
||||
}
|
||||
|
||||
func checkFixedBufferSize() {
|
||||
var r requestAlloc
|
||||
sizeOfOutHeader := unsafe.Sizeof(OutHeader{})
|
||||
for code, h := range operationHandlers {
|
||||
if h.OutputSize+sizeOfOutHeader > unsafe.Sizeof(r.outBuf) {
|
||||
log.Panicf("request output buffer too small: code %v, sz %d + %d %v", code, h.OutputSize, sizeOfOutHeader, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
26
vendor/github.com/hanwen/go-fuse/v2/fuse/opcode_linux.go
generated
vendored
Normal file
26
vendor/github.com/hanwen/go-fuse/v2/fuse/opcode_linux.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func doStatx(server *protocolServer, req *request) {
|
||||
in := (*StatxIn)(req.inData())
|
||||
out := (*StatxOut)(req.outData())
|
||||
|
||||
req.status = server.fileSystem.Statx(req.cancel, in, out)
|
||||
}
|
||||
|
||||
func init() {
|
||||
operationHandlers[_OP_STATX] = &operationHandler{
|
||||
Name: "STATX",
|
||||
Func: doStatx,
|
||||
InType: StatxIn{},
|
||||
OutType: StatxOut{},
|
||||
InputSize: unsafe.Sizeof(StatxIn{}),
|
||||
OutputSize: unsafe.Sizeof(StatxOut{}),
|
||||
}
|
||||
checkFixedBufferSize()
|
||||
}
|
||||
46
vendor/github.com/hanwen/go-fuse/v2/fuse/passthrough_linux.go
generated
vendored
Normal file
46
vendor/github.com/hanwen/go-fuse/v2/fuse/passthrough_linux.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
_DEV_IOC_BACKING_OPEN = 0x4010e501
|
||||
_DEV_IOC_BACKING_CLOSE = 0x4004e502
|
||||
)
|
||||
|
||||
// RegisterBackingFd registers the given file descriptor in the
|
||||
// kernel, so the kernel can bypass FUSE and access the backing file
|
||||
// directly for read and write calls. On success a backing ID is
|
||||
// returned. The backing ID should unregistered using
|
||||
// UnregisterBackingFd() once the file is released. Within the
|
||||
// kernel, an inode can only have a single backing file, so multiple
|
||||
// Open/Create calls should coordinate to return a consistent backing
|
||||
// ID.
|
||||
func (ms *Server) RegisterBackingFd(m *BackingMap) (int32, syscall.Errno) {
|
||||
ms.writeMu.Lock()
|
||||
id, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(ms.mountFd), uintptr(_DEV_IOC_BACKING_OPEN), uintptr(unsafe.Pointer(m)))
|
||||
ms.writeMu.Unlock()
|
||||
if ms.opts.Debug {
|
||||
ms.opts.Logger.Printf("ioctl: BACKING_OPEN %v: id %d (%v)", m.string(), id, errno)
|
||||
}
|
||||
return int32(id), errno
|
||||
}
|
||||
|
||||
// UnregisterBackingFd unregisters the given ID in the kernel. The ID
|
||||
// should have been acquired before using RegisterBackingFd.
|
||||
func (ms *Server) UnregisterBackingFd(id int32) syscall.Errno {
|
||||
ms.writeMu.Lock()
|
||||
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(ms.mountFd), uintptr(_DEV_IOC_BACKING_CLOSE), uintptr(unsafe.Pointer(&id)))
|
||||
ms.writeMu.Unlock()
|
||||
|
||||
if ms.opts.Debug {
|
||||
ms.opts.Logger.Printf("ioctl: BACKING_CLOSE id %d: %v", id, errno)
|
||||
}
|
||||
return errno
|
||||
}
|
||||
52
vendor/github.com/hanwen/go-fuse/v2/fuse/poll.go
generated
vendored
Normal file
52
vendor/github.com/hanwen/go-fuse/v2/fuse/poll.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package fuse
|
||||
|
||||
// Go 1.9 introduces polling for file I/O. The implementation causes
|
||||
// the runtime's epoll to take up the last GOMAXPROCS slot, and if
|
||||
// that happens, we won't have any threads left to service FUSE's
|
||||
// _OP_POLL request. Prevent this by forcing _OP_POLL to happen, so we
|
||||
// can say ENOSYS and prevent further _OP_POLL requests.
|
||||
const pollHackName = ".go-fuse-epoll-hack"
|
||||
const pollHackInode = ^uint64(0)
|
||||
|
||||
func doPollHackLookup(ms *protocolServer, req *request) {
|
||||
attr := Attr{
|
||||
Ino: pollHackInode,
|
||||
Mode: S_IFREG | 0644,
|
||||
Nlink: 1,
|
||||
}
|
||||
switch req.inHeader().Opcode {
|
||||
case _OP_LOOKUP:
|
||||
out := (*EntryOut)(req.outData())
|
||||
*out = EntryOut{
|
||||
NodeId: pollHackInode,
|
||||
Attr: attr,
|
||||
}
|
||||
req.status = OK
|
||||
case _OP_OPEN:
|
||||
out := (*OpenOut)(req.outData())
|
||||
*out = OpenOut{
|
||||
Fh: pollHackInode,
|
||||
}
|
||||
req.status = OK
|
||||
case _OP_GETATTR, _OP_SETATTR:
|
||||
out := (*AttrOut)(req.outData())
|
||||
out.Attr = attr
|
||||
req.status = OK
|
||||
case _OP_GETXATTR:
|
||||
// Kernel will try to read acl xattrs. Pretend we don't have any.
|
||||
req.status = ENODATA
|
||||
case _OP_POLL:
|
||||
req.status = ENOSYS
|
||||
|
||||
case _OP_ACCESS, _OP_FLUSH, _OP_RELEASE:
|
||||
// Avoid upsetting the OSX mount process.
|
||||
req.status = OK
|
||||
default:
|
||||
// We want to avoid switching off features through our
|
||||
// poll hack, so don't use ENOSYS. It would be nice if
|
||||
// we could transmit no error code at all, but for
|
||||
// some opcodes, we'd have to invent credible data to
|
||||
// return as well.
|
||||
req.status = ERANGE
|
||||
}
|
||||
}
|
||||
56
vendor/github.com/hanwen/go-fuse/v2/fuse/poll_darwin.go
generated
vendored
Normal file
56
vendor/github.com/hanwen/go-fuse/v2/fuse/poll_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type pollFd struct {
|
||||
Fd int32
|
||||
Events int16
|
||||
Revents int16
|
||||
}
|
||||
|
||||
func sysPoll(fds []pollFd, timeout int) (n int, err error) {
|
||||
r0, _, e1 := syscall.Syscall(syscall.SYS_POLL, uintptr(unsafe.Pointer(&fds[0])),
|
||||
uintptr(len(fds)), uintptr(timeout))
|
||||
n = int(r0)
|
||||
if e1 != 0 {
|
||||
err = syscall.Errno(e1)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func pollHack(mountPoint string) error {
|
||||
const (
|
||||
POLLIN = 0x1
|
||||
POLLPRI = 0x2
|
||||
POLLOUT = 0x4
|
||||
POLLRDHUP = 0x2000
|
||||
POLLERR = 0x8
|
||||
POLLHUP = 0x10
|
||||
)
|
||||
|
||||
fd, err := syscall.Open(filepath.Join(mountPoint, pollHackName), syscall.O_RDONLY, 0)
|
||||
if err == syscall.EPERM {
|
||||
// This can happen due to macos sandboxing, see
|
||||
// https://github.com/hanwen/go-fuse/issues/572#issuecomment-2904052441
|
||||
// If we don't have access to access our own mount
|
||||
// point, we also can't deadlock ourselves with poll, so this is fine.
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pollData := []pollFd{{
|
||||
Fd: int32(fd),
|
||||
Events: POLLIN | POLLPRI | POLLOUT,
|
||||
}}
|
||||
|
||||
// Trigger _OP_POLL, so we can say ENOSYS. We don't care about
|
||||
// the return value.
|
||||
sysPoll(pollData, 0)
|
||||
syscall.Close(fd)
|
||||
return nil
|
||||
}
|
||||
27
vendor/github.com/hanwen/go-fuse/v2/fuse/poll_unix.go
generated
vendored
Normal file
27
vendor/github.com/hanwen/go-fuse/v2/fuse/poll_unix.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
//go:build !darwin
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func pollHack(mountPoint string) error {
|
||||
fd, err := syscall.Open(filepath.Join(mountPoint, pollHackName), syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pollData := []unix.PollFd{{
|
||||
Fd: int32(fd),
|
||||
Events: unix.POLLIN | unix.POLLPRI | unix.POLLOUT,
|
||||
}}
|
||||
|
||||
// Trigger _OP_POLL, so we can say ENOSYS. We don't care about
|
||||
// the return value.
|
||||
unix.Poll(pollData, 0)
|
||||
syscall.Close(fd)
|
||||
return nil
|
||||
}
|
||||
415
vendor/github.com/hanwen/go-fuse/v2/fuse/print.go
generated
vendored
Normal file
415
vendor/github.com/hanwen/go-fuse/v2/fuse/print.go
generated
vendored
Normal file
@@ -0,0 +1,415 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
isTest bool
|
||||
writeFlagNames = newFlagNames([]flagNameEntry{
|
||||
{WRITE_CACHE, "CACHE"},
|
||||
{WRITE_LOCKOWNER, "LOCKOWNER"},
|
||||
})
|
||||
readFlagNames = newFlagNames([]flagNameEntry{
|
||||
{READ_LOCKOWNER, "LOCKOWNER"},
|
||||
})
|
||||
initFlagNames = newFlagNames([]flagNameEntry{
|
||||
{CAP_ASYNC_READ, "ASYNC_READ"},
|
||||
{CAP_POSIX_LOCKS, "POSIX_LOCKS"},
|
||||
{CAP_FILE_OPS, "FILE_OPS"},
|
||||
{CAP_ATOMIC_O_TRUNC, "ATOMIC_O_TRUNC"},
|
||||
{CAP_EXPORT_SUPPORT, "EXPORT_SUPPORT"},
|
||||
{CAP_BIG_WRITES, "BIG_WRITES"},
|
||||
{CAP_DONT_MASK, "DONT_MASK"},
|
||||
{CAP_SPLICE_WRITE, "SPLICE_WRITE"},
|
||||
{CAP_SPLICE_MOVE, "SPLICE_MOVE"},
|
||||
{CAP_SPLICE_READ, "SPLICE_READ"},
|
||||
{CAP_FLOCK_LOCKS, "FLOCK_LOCKS"},
|
||||
{CAP_IOCTL_DIR, "IOCTL_DIR"},
|
||||
{CAP_AUTO_INVAL_DATA, "AUTO_INVAL_DATA"},
|
||||
{CAP_READDIRPLUS, "READDIRPLUS"},
|
||||
{CAP_READDIRPLUS_AUTO, "READDIRPLUS_AUTO"},
|
||||
{CAP_ASYNC_DIO, "ASYNC_DIO"},
|
||||
{CAP_WRITEBACK_CACHE, "WRITEBACK_CACHE"},
|
||||
{CAP_NO_OPEN_SUPPORT, "NO_OPEN_SUPPORT"},
|
||||
{CAP_PARALLEL_DIROPS, "PARALLEL_DIROPS"},
|
||||
{CAP_POSIX_ACL, "POSIX_ACL"},
|
||||
{CAP_HANDLE_KILLPRIV, "HANDLE_KILLPRIV"},
|
||||
{CAP_ABORT_ERROR, "ABORT_ERROR"},
|
||||
{CAP_MAX_PAGES, "MAX_PAGES"},
|
||||
{CAP_CACHE_SYMLINKS, "CACHE_SYMLINKS"},
|
||||
{CAP_SECURITY_CTX, "SECURITY_CTX"},
|
||||
{CAP_HAS_INODE_DAX, "HAS_INODE_DAX"},
|
||||
{CAP_CREATE_SUPP_GROUP, "CREATE_SUPP_GROUP"},
|
||||
{CAP_HAS_EXPIRE_ONLY, "HAS_EXPIRE_ONLY"},
|
||||
{CAP_DIRECT_IO_ALLOW_MMAP, "DIRECT_IO_ALLOW_MMAP"},
|
||||
{CAP_PASSTHROUGH, "PASSTHROUGH"},
|
||||
{CAP_NO_EXPORT_SUPPORT, "NO_EXPORT_SUPPORT"},
|
||||
{CAP_HAS_RESEND, "HAS_RESEND"},
|
||||
{CAP_ALLOW_IDMAP, "ALLOW_IDMAP"},
|
||||
{CAP_OVER_IO_URING, "IO_URING"},
|
||||
{CAP_REQUEST_TIMEOUT, "REQUEST_TIMEOUT"},
|
||||
})
|
||||
releaseFlagNames = newFlagNames([]flagNameEntry{
|
||||
{RELEASE_FLUSH, "FLUSH"},
|
||||
})
|
||||
openFlagNames = newFlagNames([]flagNameEntry{
|
||||
{int64(os.O_WRONLY), "WRONLY"},
|
||||
{int64(os.O_RDWR), "RDWR"},
|
||||
{int64(os.O_APPEND), "APPEND"},
|
||||
{int64(syscall.O_ASYNC), "ASYNC"},
|
||||
{int64(os.O_CREATE), "CREAT"},
|
||||
{int64(os.O_EXCL), "EXCL"},
|
||||
{int64(syscall.O_NOCTTY), "NOCTTY"},
|
||||
{int64(syscall.O_NONBLOCK), "NONBLOCK"},
|
||||
{int64(os.O_SYNC), "SYNC"},
|
||||
{int64(os.O_TRUNC), "TRUNC"},
|
||||
{int64(syscall.O_CLOEXEC), "CLOEXEC"},
|
||||
{int64(syscall.O_DIRECTORY), "DIRECTORY"},
|
||||
})
|
||||
fuseOpenFlagNames = newFlagNames([]flagNameEntry{
|
||||
{FOPEN_DIRECT_IO, "DIRECT"},
|
||||
{FOPEN_KEEP_CACHE, "CACHE"},
|
||||
{FOPEN_NONSEEKABLE, "NONSEEK"},
|
||||
{FOPEN_CACHE_DIR, "CACHE_DIR"},
|
||||
{FOPEN_STREAM, "STREAM"},
|
||||
{FOPEN_NOFLUSH, "NOFLUSH"},
|
||||
{FOPEN_PARALLEL_DIRECT_WRITES, "PARALLEL_DIRECT_WRITES"},
|
||||
{FOPEN_PASSTHROUGH, "PASSTHROUGH"},
|
||||
})
|
||||
ioctlFlagNames = newFlagNames([]flagNameEntry{
|
||||
{IOCTL_COMPAT, "COMPAT"},
|
||||
{IOCTL_UNRESTRICTED, "UNRESTRICTED"},
|
||||
{IOCTL_RETRY, "RETRY"},
|
||||
{IOCTL_DIR, "DIR"},
|
||||
})
|
||||
accessFlagName = newFlagNames([]flagNameEntry{
|
||||
{X_OK, "x"},
|
||||
{W_OK, "w"},
|
||||
{R_OK, "r"},
|
||||
})
|
||||
getAttrFlagNames = newFlagNames([]flagNameEntry{
|
||||
{FUSE_GETATTR_FH, "FH"},
|
||||
})
|
||||
renameFlagNames = newFlagNames([]flagNameEntry{
|
||||
{1, "NOREPLACE"},
|
||||
{2, "EXCHANGE"},
|
||||
{4, "WHITEOUT"},
|
||||
})
|
||||
)
|
||||
|
||||
// flagNames associate flag bits to their names.
|
||||
type flagNames [64]flagNameEntry
|
||||
|
||||
// flagNameEntry describes one flag value.
|
||||
//
|
||||
// Usually a flag constitues only one bit, but, for example at least O_SYNC and
|
||||
// O_TMPFILE are represented by a value with two bits set. To handle such
|
||||
// situations we map all bits of a flag to the same flagNameEntry.
|
||||
type flagNameEntry struct {
|
||||
bits int64
|
||||
name string
|
||||
}
|
||||
|
||||
// newFlagNames creates flagNames from flag->name map.
|
||||
func newFlagNames(names []flagNameEntry) *flagNames {
|
||||
var v flagNames
|
||||
for _, e := range names {
|
||||
v.set(e.bits, e.name)
|
||||
}
|
||||
return &v
|
||||
}
|
||||
|
||||
// set associates flag value with name.
|
||||
func (names *flagNames) set(flag int64, name string) {
|
||||
entry := flagNameEntry{bits: flag, name: name}
|
||||
for i := 0; i < 64; i++ {
|
||||
if flag&(1<<i) != 0 {
|
||||
if ie := names[i]; ie.bits != 0 && isTest {
|
||||
panic(fmt.Sprintf("%s (%x) overlaps with %s (%x)", name, flag, ie.name, ie.bits))
|
||||
}
|
||||
names[i] = entry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func flagString(names *flagNames, fl int64, def string) string {
|
||||
s := []string{}
|
||||
// emit flags in their numeric order
|
||||
for i := range names {
|
||||
entry := &names[i]
|
||||
if entry.bits == 0 {
|
||||
continue
|
||||
}
|
||||
if fl&entry.bits == entry.bits {
|
||||
s = append(s, entry.name)
|
||||
fl ^= entry.bits
|
||||
if fl == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if fl != 0 {
|
||||
s = append(s, fmt.Sprintf("0x%x", fl))
|
||||
}
|
||||
if len(s) == 0 && def != "" {
|
||||
return def
|
||||
}
|
||||
|
||||
return strings.Join(s, ",")
|
||||
}
|
||||
|
||||
func (in *ForgetIn) string() string {
|
||||
return fmt.Sprintf("{Nlookup=%d}", in.Nlookup)
|
||||
}
|
||||
|
||||
func (in *_BatchForgetIn) string() string {
|
||||
return fmt.Sprintf("{Count=%d}", in.Count)
|
||||
}
|
||||
|
||||
func (in *MkdirIn) string() string {
|
||||
return fmt.Sprintf("{0%o (mask 0%o)}", in.Mode, in.Umask)
|
||||
}
|
||||
|
||||
func (in *Rename1In) string() string {
|
||||
return fmt.Sprintf("{i%d}", in.Newdir)
|
||||
}
|
||||
|
||||
func (in *RenameIn) string() string {
|
||||
return fmt.Sprintf("{i%d %s}", in.Newdir, flagString(renameFlagNames, int64(in.Flags), "0"))
|
||||
}
|
||||
|
||||
func (in *SetAttrIn) string() string {
|
||||
s := []string{}
|
||||
if in.Valid&FATTR_MODE != 0 {
|
||||
s = append(s, fmt.Sprintf("mode 0%o", in.Mode))
|
||||
}
|
||||
if in.Valid&FATTR_UID != 0 {
|
||||
s = append(s, fmt.Sprintf("uid %d", in.Uid))
|
||||
}
|
||||
if in.Valid&FATTR_GID != 0 {
|
||||
s = append(s, fmt.Sprintf("gid %d", in.Gid))
|
||||
}
|
||||
if in.Valid&FATTR_SIZE != 0 {
|
||||
s = append(s, fmt.Sprintf("size %d", in.Size))
|
||||
}
|
||||
if in.Valid&FATTR_ATIME != 0 {
|
||||
s = append(s, fmt.Sprintf("atime %d.%09d", in.Atime, in.Atimensec))
|
||||
}
|
||||
if in.Valid&FATTR_MTIME != 0 {
|
||||
s = append(s, fmt.Sprintf("mtime %d.%09d", in.Mtime, in.Mtimensec))
|
||||
}
|
||||
if in.Valid&FATTR_FH != 0 {
|
||||
s = append(s, fmt.Sprintf("fh %d", in.Fh))
|
||||
}
|
||||
// TODO - FATTR_ATIME_NOW = (1 << 7), FATTR_MTIME_NOW = (1 << 8), FATTR_LOCKOWNER = (1 << 9)
|
||||
return fmt.Sprintf("{%s}", strings.Join(s, ", "))
|
||||
}
|
||||
|
||||
func (in *ReleaseIn) string() string {
|
||||
return fmt.Sprintf("{Fh %d %s %s L%d}",
|
||||
in.Fh, flagString(openFlagNames, int64(in.Flags), ""),
|
||||
flagString(releaseFlagNames, int64(in.ReleaseFlags), ""),
|
||||
in.LockOwner)
|
||||
}
|
||||
|
||||
func (in *OpenIn) string() string {
|
||||
return fmt.Sprintf("{%s}", flagString(openFlagNames, int64(in.Flags), "O_RDONLY"))
|
||||
}
|
||||
|
||||
func (in *OpenOut) string() string {
|
||||
backing := ""
|
||||
if in.BackingID != 0 {
|
||||
backing = fmt.Sprintf("backing=%d ", in.BackingID)
|
||||
}
|
||||
return fmt.Sprintf("{Fh %d %s%s}", in.Fh, backing,
|
||||
flagString(fuseOpenFlagNames, int64(in.OpenFlags), ""))
|
||||
}
|
||||
|
||||
func (in *InitIn) string() string {
|
||||
return fmt.Sprintf("{%d.%d Ra %d %s}",
|
||||
in.Major, in.Minor, in.MaxReadAhead,
|
||||
flagString(initFlagNames, int64(in.Flags64()), ""))
|
||||
}
|
||||
|
||||
func (o *InitOut) string() string {
|
||||
return fmt.Sprintf("{%d.%d Ra %d %s %d/%d Wr %d Tg %d MaxPages %d MaxStack %d}",
|
||||
o.Major, o.Minor, o.MaxReadAhead,
|
||||
flagString(initFlagNames, int64(o.Flags64()), ""),
|
||||
o.CongestionThreshold, o.MaxBackground, o.MaxWrite,
|
||||
o.TimeGran, o.MaxPages, o.MaxStackDepth)
|
||||
}
|
||||
|
||||
func (s *FsyncIn) string() string {
|
||||
return fmt.Sprintf("{Fh %d Flags %x}", s.Fh, s.FsyncFlags)
|
||||
}
|
||||
|
||||
func (in *SetXAttrIn) string() string {
|
||||
return fmt.Sprintf("{sz %d f%o}", in.Size, in.Flags)
|
||||
}
|
||||
|
||||
func (in *GetXAttrIn) string() string {
|
||||
return fmt.Sprintf("{sz %d}", in.Size)
|
||||
}
|
||||
|
||||
func (o *GetXAttrOut) string() string {
|
||||
return fmt.Sprintf("{sz %d}", o.Size)
|
||||
}
|
||||
|
||||
func (in *AccessIn) string() string {
|
||||
return fmt.Sprintf("{u=%d g=%d %s}",
|
||||
in.Uid,
|
||||
in.Gid,
|
||||
flagString(accessFlagName, int64(in.Mask), ""))
|
||||
}
|
||||
|
||||
func (in *FlushIn) string() string {
|
||||
return fmt.Sprintf("{Fh %d}", in.Fh)
|
||||
}
|
||||
|
||||
func (o *AttrOut) string() string {
|
||||
return fmt.Sprintf(
|
||||
"{tA=%gs %v}",
|
||||
ft(o.AttrValid, o.AttrValidNsec), &o.Attr)
|
||||
}
|
||||
|
||||
// ft converts (seconds , nanoseconds) -> float(seconds)
|
||||
func ft(tsec uint64, tnsec uint32) float64 {
|
||||
return float64(tsec) + float64(tnsec)*1e-9
|
||||
}
|
||||
|
||||
// Returned by LOOKUP
|
||||
func (o *EntryOut) string() string {
|
||||
return fmt.Sprintf("{n%d g%d tE=%gs tA=%gs %v}",
|
||||
o.NodeId, o.Generation, ft(o.EntryValid, o.EntryValidNsec),
|
||||
ft(o.AttrValid, o.AttrValidNsec), &o.Attr)
|
||||
}
|
||||
|
||||
func (o *CreateOut) string() string {
|
||||
return fmt.Sprintf("{n%d g%d %v %v}", o.NodeId, o.Generation, &o.EntryOut, o.OpenOut.string())
|
||||
}
|
||||
|
||||
func (o *StatfsOut) string() string {
|
||||
return fmt.Sprintf(
|
||||
"{blocks (%d,%d)/%d files %d/%d bs%d nl%d frs%d}",
|
||||
o.Bfree, o.Bavail, o.Blocks, o.Ffree, o.Files,
|
||||
o.Bsize, o.NameLen, o.Frsize)
|
||||
}
|
||||
|
||||
func (o *NotifyInvalEntryOut) string() string {
|
||||
return fmt.Sprintf("{parent i%d sz %d}", o.Parent, o.NameLen)
|
||||
}
|
||||
|
||||
func (o *NotifyInvalInodeOut) string() string {
|
||||
return fmt.Sprintf("{i%d [%d +%d)}", o.Ino, o.Off, o.Length)
|
||||
}
|
||||
|
||||
func (o *NotifyInvalDeleteOut) string() string {
|
||||
return fmt.Sprintf("{parent i%d ch i%d sz %d}", o.Parent, o.Child, o.NameLen)
|
||||
}
|
||||
|
||||
func (o *NotifyStoreOut) string() string {
|
||||
return fmt.Sprintf("{n%d [%d +%d)}", o.Nodeid, o.Offset, o.Size)
|
||||
}
|
||||
|
||||
func (o *NotifyRetrieveOut) string() string {
|
||||
return fmt.Sprintf("{> %d: n%d [%d +%d)}", o.NotifyUnique, o.Nodeid, o.Offset, o.Size)
|
||||
}
|
||||
|
||||
func (i *NotifyRetrieveIn) string() string {
|
||||
return fmt.Sprintf("{[%d +%d)}", i.Offset, i.Size)
|
||||
}
|
||||
|
||||
func (f *FallocateIn) string() string {
|
||||
return fmt.Sprintf("{Fh %d [%d +%d) mod 0%o}",
|
||||
f.Fh, f.Offset, f.Length, f.Mode)
|
||||
}
|
||||
|
||||
func (f *LinkIn) string() string {
|
||||
return fmt.Sprintf("{Oldnodeid: n%d}", f.Oldnodeid)
|
||||
}
|
||||
|
||||
func (o *WriteOut) string() string {
|
||||
return fmt.Sprintf("{%db }", o.Size)
|
||||
|
||||
}
|
||||
func (i *CopyFileRangeIn) string() string {
|
||||
return fmt.Sprintf("{Fh %d [%d +%d) => n%d Fh %d [%d, %d)}",
|
||||
i.FhIn, i.OffIn, i.Len, i.NodeIdOut, i.FhOut, i.OffOut, i.Len)
|
||||
}
|
||||
|
||||
func (in *InterruptIn) string() string {
|
||||
return fmt.Sprintf("{ix %d}", in.Unique)
|
||||
}
|
||||
|
||||
var seekNames = map[uint32]string{
|
||||
0: "SET",
|
||||
1: "CUR",
|
||||
2: "END",
|
||||
3: "DATA",
|
||||
4: "HOLE",
|
||||
}
|
||||
|
||||
func (in *LseekIn) string() string {
|
||||
return fmt.Sprintf("{Fh %d [%s +%d)}", in.Fh,
|
||||
seekNames[in.Whence], in.Offset)
|
||||
}
|
||||
|
||||
func (o *LseekOut) string() string {
|
||||
return fmt.Sprintf("{%d}", o.Offset)
|
||||
}
|
||||
|
||||
func (p *_PollIn) string() string {
|
||||
return fmt.Sprintf("Fh %d Kh %d Flags 0x%x", p.Fh, p.Kh, p.Flags)
|
||||
}
|
||||
|
||||
// Print pretty prints FUSE data types for kernel communication
|
||||
func Print(obj interface{}) string {
|
||||
t, ok := obj.(interface {
|
||||
string() string
|
||||
})
|
||||
if ok {
|
||||
return t.string()
|
||||
}
|
||||
return fmt.Sprintf("%T: %v", obj, obj)
|
||||
}
|
||||
|
||||
func (a *Attr) string() string {
|
||||
return fmt.Sprintf(
|
||||
"{M0%o SZ=%d L=%d "+
|
||||
"%d:%d "+
|
||||
"B%d*%d i%d:%d "+
|
||||
"A %f "+
|
||||
"M %f "+
|
||||
"C %f}",
|
||||
a.Mode, a.Size, a.Nlink,
|
||||
a.Uid, a.Gid,
|
||||
a.Blocks, a.Blksize,
|
||||
a.Rdev, a.Ino, ft(a.Atime, a.Atimensec), ft(a.Mtime, a.Mtimensec),
|
||||
ft(a.Ctime, a.Ctimensec))
|
||||
}
|
||||
|
||||
func (m *BackingMap) string() string {
|
||||
return fmt.Sprintf("{fd %d, flags 0x%x}", m.Fd, m.Flags)
|
||||
}
|
||||
|
||||
func (o *IoctlIn) string() string {
|
||||
return fmt.Sprintf("{Fh %d Flags %s Cmd 0x%x Arg 0x%x, insz %d outsz %d}",
|
||||
o.Fh,
|
||||
flagString(ioctlFlagNames, int64(o.Flags), ""),
|
||||
o.Cmd, o.Arg, o.InSize, o.OutSize)
|
||||
}
|
||||
|
||||
func (o *IoctlOut) string() string {
|
||||
return fmt.Sprintf("{Result %d Flags %s Iovs %d/%d",
|
||||
o.Result,
|
||||
flagString(ioctlFlagNames, int64(o.Flags), ""),
|
||||
o.InIovs, o.OutIovs)
|
||||
}
|
||||
44
vendor/github.com/hanwen/go-fuse/v2/fuse/print_darwin.go
generated
vendored
Normal file
44
vendor/github.com/hanwen/go-fuse/v2/fuse/print_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initFlagNames.set(CAP_NODE_RWLOCK, "NODE_RWLOCK")
|
||||
initFlagNames.set(CAP_RENAME_SWAP, "RENAME_SWAP")
|
||||
initFlagNames.set(CAP_RENAME_EXCL, "RENAME_EXCL")
|
||||
initFlagNames.set(CAP_ALLOCATE, "ALLOCATE")
|
||||
initFlagNames.set(CAP_EXCHANGE_DATA, "EXCHANGE_DATA")
|
||||
initFlagNames.set(CAP_XTIMES, "XTIMES")
|
||||
initFlagNames.set(CAP_VOL_RENAME, "VOL_RENAME")
|
||||
initFlagNames.set(CAP_CASE_INSENSITIVE, "CASE_INSENSITIVE")
|
||||
}
|
||||
|
||||
func (me *CreateIn) string() string {
|
||||
return fmt.Sprintf(
|
||||
"{0%o [%s]}", me.Mode,
|
||||
flagString(openFlagNames, int64(me.Flags), "O_RDONLY"))
|
||||
}
|
||||
|
||||
func (me *GetAttrIn) string() string { return "" }
|
||||
|
||||
func (me *MknodIn) string() string {
|
||||
return fmt.Sprintf("{0%o, %d}", me.Mode, me.Rdev)
|
||||
}
|
||||
|
||||
func (me *ReadIn) string() string {
|
||||
return fmt.Sprintf("{Fh %d [%d +%d) %s}",
|
||||
me.Fh, me.Offset, me.Size,
|
||||
flagString(readFlagNames, int64(me.ReadFlags), ""))
|
||||
}
|
||||
|
||||
func (me *WriteIn) string() string {
|
||||
return fmt.Sprintf("{Fh %d [%d +%d) %s}",
|
||||
me.Fh, me.Offset, me.Size,
|
||||
flagString(writeFlagNames, int64(me.WriteFlags), ""))
|
||||
}
|
||||
5
vendor/github.com/hanwen/go-fuse/v2/fuse/print_freebsd.go
generated
vendored
Normal file
5
vendor/github.com/hanwen/go-fuse/v2/fuse/print_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
package fuse
|
||||
|
||||
func init() {
|
||||
initFlagNames.set(CAP_NO_OPENDIR_SUPPORT, "NO_OPENDIR_SUPPORT")
|
||||
}
|
||||
92
vendor/github.com/hanwen/go-fuse/v2/fuse/print_linux.go
generated
vendored
Normal file
92
vendor/github.com/hanwen/go-fuse/v2/fuse/print_linux.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var statxFieldFlags = newFlagNames([]flagNameEntry{
|
||||
{unix.STATX_ATIME, "Atime"},
|
||||
{unix.STATX_BLOCKS, "blocks"},
|
||||
{unix.STATX_BTIME, "Btime"},
|
||||
{unix.STATX_CTIME, "Ctime"},
|
||||
{unix.STATX_GID, "Gid"},
|
||||
{unix.STATX_INO, "Ino"},
|
||||
{unix.STATX_MNT_ID, "Mntid"},
|
||||
{unix.STATX_MODE, "Mode"},
|
||||
{unix.STATX_MTIME, "Mtime"},
|
||||
{unix.STATX_NLINK, "Nlink"},
|
||||
{unix.STATX_SIZE, "Size"},
|
||||
{unix.STATX_TYPE, "Type"},
|
||||
{unix.STATX_UID, "Uid"},
|
||||
})
|
||||
|
||||
func init() {
|
||||
// syscall.O_LARGEFILE is 0x0 on x86_64, but the kernel
|
||||
// supplies 0x8000 anyway, except on mips64el, where 0x8000 is
|
||||
// used for O_DIRECT.
|
||||
if !strings.Contains(runtime.GOARCH, "mips64") {
|
||||
openFlagNames.set(0x8000, "LARGEFILE")
|
||||
}
|
||||
|
||||
openFlagNames.set(syscall.O_DIRECT, "DIRECT")
|
||||
openFlagNames.set(syscall_O_NOATIME, "NOATIME")
|
||||
openFlagNames.set(FMODE_EXEC, "EXEC")
|
||||
initFlagNames.set(CAP_NO_OPENDIR_SUPPORT, "NO_OPENDIR_SUPPORT")
|
||||
initFlagNames.set(CAP_EXPLICIT_INVAL_DATA, "EXPLICIT_INVAL_DATA")
|
||||
initFlagNames.set(CAP_MAP_ALIGNMENT, "MAP_ALIGNMENT")
|
||||
initFlagNames.set(CAP_SUBMOUNTS, "SUBMOUNTS")
|
||||
initFlagNames.set(CAP_HANDLE_KILLPRIV_V2, "HANDLE_KILLPRIV_V2")
|
||||
initFlagNames.set(CAP_SETXATTR_EXT, "SETXATTR_EXT")
|
||||
initFlagNames.set(CAP_INIT_EXT, "INIT_EXT")
|
||||
initFlagNames.set(CAP_INIT_RESERVED, "INIT_RESERVED")
|
||||
}
|
||||
|
||||
func (a *Statx) string() string {
|
||||
var ss []string
|
||||
if a.Mask&unix.STATX_MODE != 0 || a.Mask&unix.STATX_TYPE != 0 {
|
||||
ss = append(ss, fmt.Sprintf("M0%o", a.Mode))
|
||||
}
|
||||
if a.Mask&unix.STATX_SIZE != 0 {
|
||||
ss = append(ss, fmt.Sprintf("SZ=%d", a.Size))
|
||||
}
|
||||
if a.Mask&unix.STATX_NLINK != 0 {
|
||||
ss = append(ss, fmt.Sprintf("L=%d", a.Nlink))
|
||||
}
|
||||
if a.Mask&unix.STATX_UID != 0 || a.Mask&unix.STATX_GID != 0 {
|
||||
ss = append(ss, fmt.Sprintf("%d:%d", a.Uid, a.Gid))
|
||||
}
|
||||
if a.Mask&unix.STATX_INO != 0 {
|
||||
ss = append(ss, fmt.Sprintf("i%d", a.Ino))
|
||||
}
|
||||
if a.Mask&unix.STATX_ATIME != 0 {
|
||||
ss = append(ss, fmt.Sprintf("A %f", a.Atime.Seconds()))
|
||||
}
|
||||
if a.Mask&unix.STATX_BTIME != 0 {
|
||||
ss = append(ss, fmt.Sprintf("B %f", a.Btime.Seconds()))
|
||||
}
|
||||
if a.Mask&unix.STATX_CTIME != 0 {
|
||||
ss = append(ss, fmt.Sprintf("C %f", a.Ctime.Seconds()))
|
||||
}
|
||||
if a.Mask&unix.STATX_MTIME != 0 {
|
||||
ss = append(ss, fmt.Sprintf("M %f", a.Mtime.Seconds()))
|
||||
}
|
||||
if a.Mask&unix.STATX_BLOCKS != 0 {
|
||||
ss = append(ss, fmt.Sprintf("%d*%d", a.Blocks, a.Blksize))
|
||||
}
|
||||
|
||||
return "{" + strings.Join(ss, " ") + "}"
|
||||
}
|
||||
|
||||
func (in *StatxIn) string() string {
|
||||
return fmt.Sprintf("{Fh %d %s 0x%x %s}", in.Fh, flagString(getAttrFlagNames, int64(in.GetattrFlags), ""),
|
||||
in.SxFlags, flagString(statxFieldFlags, int64(in.SxMask), ""))
|
||||
}
|
||||
35
vendor/github.com/hanwen/go-fuse/v2/fuse/print_unix.go
generated
vendored
Normal file
35
vendor/github.com/hanwen/go-fuse/v2/fuse/print_unix.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
//go:build !darwin
|
||||
|
||||
package fuse
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (in *CreateIn) string() string {
|
||||
return fmt.Sprintf(
|
||||
"{0%o [%s] (0%o)}", in.Mode,
|
||||
flagString(openFlagNames, int64(in.Flags), "O_RDONLY"), in.Umask)
|
||||
}
|
||||
|
||||
func (in *GetAttrIn) string() string {
|
||||
return fmt.Sprintf("{Fh %d %s}", in.Fh_, flagString(getAttrFlagNames, int64(in.Flags_), ""))
|
||||
}
|
||||
|
||||
func (in *MknodIn) string() string {
|
||||
return fmt.Sprintf("{0%o (0%o), %d}", in.Mode, in.Umask, in.Rdev)
|
||||
}
|
||||
|
||||
func (in *ReadIn) string() string {
|
||||
return fmt.Sprintf("{Fh %d [%d +%d) %s L %d %s}",
|
||||
in.Fh, in.Offset, in.Size,
|
||||
flagString(readFlagNames, int64(in.ReadFlags), ""),
|
||||
in.LockOwner,
|
||||
flagString(openFlagNames, int64(in.Flags), "RDONLY"))
|
||||
}
|
||||
|
||||
func (in *WriteIn) string() string {
|
||||
return fmt.Sprintf("{Fh %d [%d +%d) %s L %d %s}",
|
||||
in.Fh, in.Offset, in.Size,
|
||||
flagString(writeFlagNames, int64(in.WriteFlags), ""),
|
||||
in.LockOwner,
|
||||
flagString(openFlagNames, int64(in.Flags), "RDONLY"))
|
||||
}
|
||||
128
vendor/github.com/hanwen/go-fuse/v2/fuse/protocol-server.go
generated
vendored
Normal file
128
vendor/github.com/hanwen/go-fuse/v2/fuse/protocol-server.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// protocolServer bridges from the FUSE datatypes to a RawFileSystem
|
||||
type protocolServer struct {
|
||||
fileSystem RawFileSystem
|
||||
|
||||
interruptMu sync.Mutex
|
||||
reqInflight []*request
|
||||
connectionDead bool
|
||||
|
||||
latencies LatencyMap
|
||||
|
||||
kernelSettings InitIn
|
||||
|
||||
opts *MountOptions
|
||||
|
||||
// in-flight notify-retrieve queries
|
||||
retrieveMu sync.Mutex
|
||||
retrieveNext uint64
|
||||
retrieveTab map[uint64]*retrieveCacheRequest // notifyUnique -> retrieve request
|
||||
}
|
||||
|
||||
func (ms *protocolServer) handleRequest(h *operationHandler, req *request) {
|
||||
ms.addInflight(req)
|
||||
defer ms.dropInflight(req)
|
||||
|
||||
if req.status.Ok() && ms.opts.Debug {
|
||||
ms.opts.Logger.Println(req.InputDebug())
|
||||
}
|
||||
|
||||
if req.inHeader().NodeId == pollHackInode ||
|
||||
req.inHeader().NodeId == FUSE_ROOT_ID && h.FileNames > 0 && req.filename() == pollHackName {
|
||||
doPollHackLookup(ms, req)
|
||||
} else if req.status.Ok() && h.Func == nil {
|
||||
ms.opts.Logger.Printf("Unimplemented opcode %v", operationName(req.inHeader().Opcode))
|
||||
req.status = ENOSYS
|
||||
} else if req.status.Ok() {
|
||||
h.Func(ms, req)
|
||||
}
|
||||
|
||||
// Forget/NotifyReply do not wait for reply from filesystem server.
|
||||
switch req.inHeader().Opcode {
|
||||
case _OP_FORGET, _OP_BATCH_FORGET, _OP_NOTIFY_REPLY:
|
||||
req.suppressReply = true
|
||||
case _OP_INTERRUPT:
|
||||
// ? what other status can interrupt generate?
|
||||
if req.status.Ok() {
|
||||
req.suppressReply = true
|
||||
}
|
||||
}
|
||||
if req.status == EINTR {
|
||||
ms.interruptMu.Lock()
|
||||
dead := ms.connectionDead
|
||||
ms.interruptMu.Unlock()
|
||||
if dead {
|
||||
req.suppressReply = true
|
||||
}
|
||||
}
|
||||
if req.suppressReply {
|
||||
return
|
||||
}
|
||||
if req.fdData != nil && ms.opts.DisableSplice {
|
||||
req.outPayload, req.status = req.fdData.Bytes(req.outPayload)
|
||||
req.fdData = nil
|
||||
}
|
||||
|
||||
req.serializeHeader(req.outPayloadSize())
|
||||
|
||||
if ms.opts.Debug {
|
||||
ms.opts.Logger.Println(req.OutputDebug())
|
||||
}
|
||||
}
|
||||
|
||||
func (ms *protocolServer) addInflight(req *request) {
|
||||
ms.interruptMu.Lock()
|
||||
defer ms.interruptMu.Unlock()
|
||||
req.inflightIndex = len(ms.reqInflight)
|
||||
ms.reqInflight = append(ms.reqInflight, req)
|
||||
}
|
||||
|
||||
func (ms *protocolServer) dropInflight(req *request) {
|
||||
ms.interruptMu.Lock()
|
||||
defer ms.interruptMu.Unlock()
|
||||
this := req.inflightIndex
|
||||
last := len(ms.reqInflight) - 1
|
||||
if last != this {
|
||||
ms.reqInflight[this] = ms.reqInflight[last]
|
||||
ms.reqInflight[this].inflightIndex = this
|
||||
}
|
||||
ms.reqInflight = ms.reqInflight[:last]
|
||||
}
|
||||
|
||||
func (ms *protocolServer) interruptRequest(unique uint64) Status {
|
||||
ms.interruptMu.Lock()
|
||||
defer ms.interruptMu.Unlock()
|
||||
|
||||
// This is slow, but this operation is rare.
|
||||
for _, inflight := range ms.reqInflight {
|
||||
if unique == inflight.inHeader().Unique && !inflight.interrupted {
|
||||
close(inflight.cancel)
|
||||
inflight.interrupted = true
|
||||
return OK
|
||||
}
|
||||
}
|
||||
|
||||
return EAGAIN
|
||||
}
|
||||
|
||||
func (ms *protocolServer) cancelAll() {
|
||||
ms.interruptMu.Lock()
|
||||
defer ms.interruptMu.Unlock()
|
||||
ms.connectionDead = true
|
||||
for _, req := range ms.reqInflight {
|
||||
if !req.interrupted {
|
||||
close(req.cancel)
|
||||
req.interrupted = true
|
||||
}
|
||||
}
|
||||
// Leave ms.reqInflight alone, or dropInflight will barf.
|
||||
}
|
||||
75
vendor/github.com/hanwen/go-fuse/v2/fuse/read.go
generated
vendored
Normal file
75
vendor/github.com/hanwen/go-fuse/v2/fuse/read.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ReadResultData is the read return for returning bytes directly.
|
||||
type readResultData struct {
|
||||
// Raw bytes for the read.
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (r *readResultData) Size() int {
|
||||
return len(r.Data)
|
||||
}
|
||||
|
||||
func (r *readResultData) Done() {
|
||||
}
|
||||
|
||||
func (r *readResultData) Bytes(buf []byte) ([]byte, Status) {
|
||||
return r.Data, OK
|
||||
}
|
||||
|
||||
func ReadResultData(b []byte) ReadResult {
|
||||
return &readResultData{b}
|
||||
}
|
||||
|
||||
func ReadResultFd(fd uintptr, off int64, sz int) ReadResult {
|
||||
return &readResultFd{fd, off, sz}
|
||||
}
|
||||
|
||||
// ReadResultFd is the read return for zero-copy file data.
|
||||
type readResultFd struct {
|
||||
// Splice from the following file.
|
||||
Fd uintptr
|
||||
|
||||
// Offset within Fd, or -1 to use current offset.
|
||||
Off int64
|
||||
|
||||
// Size of data to be loaded. Actual data available may be
|
||||
// less at the EOF.
|
||||
Sz int
|
||||
}
|
||||
|
||||
// Reads raw bytes from file descriptor if necessary, using the passed
|
||||
// buffer as storage.
|
||||
func (r *readResultFd) Bytes(buf []byte) ([]byte, Status) {
|
||||
sz := r.Sz
|
||||
if len(buf) < sz {
|
||||
sz = len(buf)
|
||||
}
|
||||
|
||||
n, err := syscall.Pread(int(r.Fd), buf[:sz], r.Off)
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
|
||||
if n < 0 {
|
||||
n = 0
|
||||
}
|
||||
|
||||
return buf[:n], ToStatus(err)
|
||||
}
|
||||
|
||||
func (r *readResultFd) Size() int {
|
||||
return r.Sz
|
||||
}
|
||||
|
||||
func (r *readResultFd) Done() {
|
||||
}
|
||||
303
vendor/github.com/hanwen/go-fuse/v2/fuse/request.go
generated
vendored
Normal file
303
vendor/github.com/hanwen/go-fuse/v2/fuse/request.go
generated
vendored
Normal file
@@ -0,0 +1,303 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var sizeOfOutHeader = unsafe.Sizeof(OutHeader{})
|
||||
var zeroOutBuf [outputHeaderSize]byte
|
||||
|
||||
type request struct {
|
||||
inflightIndex int
|
||||
|
||||
cancel chan struct{}
|
||||
|
||||
suppressReply bool
|
||||
|
||||
// written under Server.interruptMu
|
||||
interrupted bool
|
||||
|
||||
// inHeader + opcode specific data
|
||||
inputBuf []byte
|
||||
|
||||
// outHeader + opcode specific data.
|
||||
outputBuf []byte
|
||||
|
||||
// Unstructured input (filenames, data for WRITE call)
|
||||
inPayload []byte
|
||||
|
||||
// Output data.
|
||||
status Status
|
||||
|
||||
// Unstructured output. Only one of these is non-nil.
|
||||
outPayload []byte
|
||||
fdData *readResultFd
|
||||
|
||||
// In case of read, keep read result here so we can call
|
||||
// Done() on it.
|
||||
readResult ReadResult
|
||||
|
||||
// Start timestamp for timing info.
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
// requestAlloc holds the request, plus I/O buffers, which are
|
||||
// reused across requests.
|
||||
type requestAlloc struct {
|
||||
request
|
||||
|
||||
// Request storage. For large inputs and outputs, use data
|
||||
// obtained through bufferpool.
|
||||
bufferPoolInputBuf []byte
|
||||
bufferPoolOutputBuf []byte
|
||||
|
||||
// For small pieces of data, we use the following inlines
|
||||
// arrays:
|
||||
//
|
||||
// Output header and structured data.
|
||||
outBuf [outputHeaderSize]byte
|
||||
|
||||
// Input, if small enough to fit here.
|
||||
smallInputBuf [128]byte
|
||||
}
|
||||
|
||||
func (r *request) inHeader() *InHeader {
|
||||
return (*InHeader)(r.inData())
|
||||
}
|
||||
|
||||
func (r *request) outHeader() *OutHeader {
|
||||
return (*OutHeader)(unsafe.Pointer(&r.outputBuf[0]))
|
||||
}
|
||||
|
||||
// TODO - benchmark to see if this is necessary?
|
||||
func (r *request) clear() {
|
||||
r.suppressReply = false
|
||||
r.inputBuf = nil
|
||||
r.outputBuf = nil
|
||||
r.inPayload = nil
|
||||
r.status = OK
|
||||
r.outPayload = nil
|
||||
r.fdData = nil
|
||||
r.startTime = time.Time{}
|
||||
r.readResult = nil
|
||||
}
|
||||
|
||||
func asType(ptr unsafe.Pointer, typ interface{}) interface{} {
|
||||
return reflect.NewAt(reflect.ValueOf(typ).Type(), ptr).Interface()
|
||||
}
|
||||
|
||||
func typSize(typ interface{}) uintptr {
|
||||
return reflect.ValueOf(typ).Type().Size()
|
||||
}
|
||||
|
||||
func (r *request) InputDebug() string {
|
||||
val := ""
|
||||
|
||||
hdr := r.inHeader()
|
||||
h := getHandler(hdr.Opcode)
|
||||
if h != nil && h.InType != nil {
|
||||
val = Print(asType(r.inData(), h.InType))
|
||||
}
|
||||
|
||||
names := ""
|
||||
if h.FileNames == 1 {
|
||||
names = fmt.Sprintf(" %q", r.filename())
|
||||
} else if h.FileNames == 2 {
|
||||
n1, n2 := r.filenames()
|
||||
names = fmt.Sprintf(" %q %q", n1, n2)
|
||||
} else if l := len(r.inPayload); l > 0 {
|
||||
dots := ""
|
||||
if l > 8 {
|
||||
l = 8
|
||||
dots = "..."
|
||||
}
|
||||
|
||||
names = fmt.Sprintf("%q%s %db", r.inPayload[:l], dots, len(r.inPayload))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("rx %d: %s n%d %s%s p%d",
|
||||
hdr.Unique, operationName(hdr.Opcode), hdr.NodeId,
|
||||
val, names, hdr.Caller.Pid)
|
||||
}
|
||||
|
||||
func (r *request) OutputDebug() string {
|
||||
var dataStr string
|
||||
h := getHandler(r.inHeader().Opcode)
|
||||
if h != nil && h.OutType != nil && len(r.outputBuf) > int(sizeOfOutHeader) {
|
||||
dataStr = Print(asType(r.outData(), h.OutType))
|
||||
}
|
||||
|
||||
max := 1024
|
||||
if len(dataStr) > max {
|
||||
dataStr = dataStr[:max] + " ...trimmed"
|
||||
}
|
||||
|
||||
flatStr := ""
|
||||
if r.outPayloadSize() > 0 {
|
||||
if h != nil && h.FileNameOut {
|
||||
s := strings.TrimRight(string(r.outPayload), "\x00")
|
||||
flatStr = fmt.Sprintf(" %q", s)
|
||||
} else {
|
||||
spl := ""
|
||||
if r.fdData != nil {
|
||||
spl = " (fd data)"
|
||||
} else {
|
||||
l := len(r.outPayload)
|
||||
s := ""
|
||||
if l > 8 {
|
||||
l = 8
|
||||
s = "..."
|
||||
}
|
||||
spl = fmt.Sprintf(" %q%s", r.outPayload[:l], s)
|
||||
}
|
||||
flatStr = fmt.Sprintf(" %db data%s", r.outPayloadSize(), spl)
|
||||
}
|
||||
}
|
||||
|
||||
extraStr := dataStr + flatStr
|
||||
if extraStr != "" {
|
||||
extraStr = ", " + extraStr
|
||||
}
|
||||
return fmt.Sprintf("tx %d: %v%s",
|
||||
r.inHeader().Unique, r.status, extraStr)
|
||||
}
|
||||
|
||||
// setInput returns true if it takes ownership of the argument, false if not.
|
||||
func (r *requestAlloc) setInput(input []byte) bool {
|
||||
if len(input) < len(r.smallInputBuf) {
|
||||
copy(r.smallInputBuf[:], input)
|
||||
r.inputBuf = r.smallInputBuf[:len(input)]
|
||||
return false
|
||||
}
|
||||
r.inputBuf = input
|
||||
r.bufferPoolInputBuf = input[:cap(input)]
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *request) inData() unsafe.Pointer {
|
||||
return unsafe.Pointer(&r.inputBuf[0])
|
||||
}
|
||||
|
||||
// note: outSize is without OutHeader
|
||||
func parseRequest(in []byte, kernelSettings *InitIn) (h *operationHandler, inSize, outSize, outPayloadSize int, errno Status) {
|
||||
inSize = int(unsafe.Sizeof(InHeader{}))
|
||||
if len(in) < inSize {
|
||||
errno = EIO
|
||||
return
|
||||
}
|
||||
inData := unsafe.Pointer(&in[0])
|
||||
hdr := (*InHeader)(inData)
|
||||
h = getHandler(hdr.Opcode)
|
||||
if h == nil {
|
||||
log.Printf("Unknown opcode %d", hdr.Opcode)
|
||||
errno = ENOSYS
|
||||
return
|
||||
}
|
||||
if h.InputSize > 0 {
|
||||
inSize = int(h.InputSize)
|
||||
}
|
||||
if kernelSettings != nil && hdr.Opcode == _OP_RENAME && kernelSettings.supportsRenameSwap() {
|
||||
inSize = int(unsafe.Sizeof(RenameIn{}))
|
||||
}
|
||||
if hdr.Opcode == _OP_INIT && inSize > len(in) {
|
||||
// Minor version 36 extended the size of InitIn struct
|
||||
inSize = len(in)
|
||||
}
|
||||
if len(in) < inSize {
|
||||
log.Printf("Short read for %v: %q", h.Name, in)
|
||||
errno = EIO
|
||||
return
|
||||
}
|
||||
|
||||
switch hdr.Opcode {
|
||||
case _OP_READDIR, _OP_READDIRPLUS, _OP_READ:
|
||||
outPayloadSize = int(((*ReadIn)(inData)).Size)
|
||||
case _OP_GETXATTR, _OP_LISTXATTR:
|
||||
outPayloadSize = int(((*GetXAttrIn)(inData)).Size)
|
||||
case _OP_IOCTL:
|
||||
outPayloadSize = int(((*IoctlIn)(inData)).OutSize)
|
||||
}
|
||||
|
||||
outSize = int(h.OutputSize)
|
||||
return
|
||||
}
|
||||
|
||||
func (r *request) outData() unsafe.Pointer {
|
||||
return unsafe.Pointer(&r.outputBuf[sizeOfOutHeader])
|
||||
}
|
||||
|
||||
func (r *request) filename() string {
|
||||
return string(r.inPayload[:len(r.inPayload)-1])
|
||||
}
|
||||
|
||||
func (r *request) filenames() (string, string) {
|
||||
i1 := bytes.IndexByte(r.inPayload, 0)
|
||||
s1 := string(r.inPayload[:i1])
|
||||
s2 := string(r.inPayload[i1+1 : len(r.inPayload)-1])
|
||||
return s1, s2
|
||||
}
|
||||
|
||||
// serializeHeader serializes the response header. The header points
|
||||
// to an internal buffer of the receiver.
|
||||
func (r *request) serializeHeader(outPayloadSize int) {
|
||||
var dataLength uintptr
|
||||
|
||||
h := getHandler(r.inHeader().Opcode)
|
||||
if h != nil {
|
||||
dataLength = h.OutputSize
|
||||
}
|
||||
if r.status > OK {
|
||||
// only do this for positive status; negative status
|
||||
// is used for notification.
|
||||
dataLength = 0
|
||||
outPayloadSize = 0
|
||||
}
|
||||
|
||||
// [GET|LIST]XATTR is two opcodes in one: get/list xattr size (return
|
||||
// structured GetXAttrOut, no flat data) and get/list xattr data
|
||||
// (return no structured data, but only flat data)
|
||||
if r.inHeader().Opcode == _OP_GETXATTR || r.inHeader().Opcode == _OP_LISTXATTR {
|
||||
if (*GetXAttrIn)(r.inData()).Size != 0 {
|
||||
dataLength = 0
|
||||
}
|
||||
}
|
||||
|
||||
// The InitOut structure has 24 bytes (ie. TimeGran and
|
||||
// further fields not available) in fuse version <= 22.
|
||||
// https://john-millikin.com/the-fuse-protocol#FUSE_INIT
|
||||
if r.inHeader().Opcode == _OP_INIT {
|
||||
out := (*InitOut)(r.outData())
|
||||
if out.Minor <= 22 {
|
||||
dataLength = 24
|
||||
}
|
||||
}
|
||||
|
||||
o := r.outHeader()
|
||||
o.Unique = r.inHeader().Unique
|
||||
o.Status = int32(-r.status)
|
||||
o.Length = uint32(
|
||||
int(sizeOfOutHeader) + int(dataLength) + outPayloadSize)
|
||||
|
||||
r.outputBuf = r.outputBuf[:dataLength+sizeOfOutHeader]
|
||||
if r.outPayload != nil {
|
||||
r.outPayload = r.outPayload[:outPayloadSize]
|
||||
}
|
||||
}
|
||||
|
||||
func (r *request) outPayloadSize() int {
|
||||
if r.fdData != nil {
|
||||
return r.fdData.Size()
|
||||
}
|
||||
return len(r.outPayload)
|
||||
}
|
||||
13
vendor/github.com/hanwen/go-fuse/v2/fuse/request_darwin.go
generated
vendored
Normal file
13
vendor/github.com/hanwen/go-fuse/v2/fuse/request_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuse
|
||||
|
||||
const outputHeaderSize = 200
|
||||
|
||||
const (
|
||||
_FUSE_KERNEL_VERSION = 7
|
||||
_MINIMUM_MINOR_VERSION = 12
|
||||
_OUR_MINOR_VERSION = 19
|
||||
)
|
||||
9
vendor/github.com/hanwen/go-fuse/v2/fuse/request_freebsd.go
generated
vendored
Normal file
9
vendor/github.com/hanwen/go-fuse/v2/fuse/request_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package fuse
|
||||
|
||||
const outputHeaderSize = 160
|
||||
|
||||
const (
|
||||
_FUSE_KERNEL_VERSION = 7
|
||||
_MINIMUM_MINOR_VERSION = 12
|
||||
_OUR_MINOR_VERSION = 28
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user