vendor: update buildkit to f449174742bf
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
+1
@@ -0,0 +1 @@
|
||||
bin/
|
||||
+23
-20
@@ -1,32 +1,35 @@
|
||||
run:
|
||||
timeout: 30m
|
||||
version: "2"
|
||||
|
||||
linters:
|
||||
default: none
|
||||
enable:
|
||||
- gofmt
|
||||
- depguard
|
||||
- govet
|
||||
- goimports
|
||||
- ineffassign
|
||||
- misspell
|
||||
- unused
|
||||
- staticcheck
|
||||
- typecheck
|
||||
disable-all: true
|
||||
- unused
|
||||
settings:
|
||||
depguard:
|
||||
rules:
|
||||
main:
|
||||
deny:
|
||||
- pkg: io/ioutil
|
||||
desc: The io/ioutil package has been deprecated.
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- .*\.pb\.go$
|
||||
|
||||
linters-settings:
|
||||
depguard:
|
||||
rules:
|
||||
main:
|
||||
deny:
|
||||
# The io/ioutil package has been deprecated.
|
||||
# https://go.dev/doc/go1.16#ioutil
|
||||
- pkg: "io/ioutil"
|
||||
desc: The io/ioutil package has been deprecated.
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- .*\.pb\.go$
|
||||
|
||||
issues:
|
||||
exclude-files:
|
||||
- ".*\\.pb\\.go$"
|
||||
|
||||
# show all
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
|
||||
+54
-3
@@ -1,11 +1,12 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.23
|
||||
ARG XX_VERSION=1.6.1
|
||||
ARG GO_VERSION=1.26
|
||||
ARG ALPINE_VERSION=3.23
|
||||
ARG XX_VERSION=1.9.0
|
||||
|
||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS base
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS base
|
||||
RUN apk add --no-cache git
|
||||
COPY --from=xx / /
|
||||
WORKDIR /src
|
||||
@@ -35,4 +36,54 @@ COPY --from=test /tmp/coverage.txt /coverage-root.txt
|
||||
FROM scratch AS test-noroot-coverage
|
||||
COPY --from=test-noroot /tmp/coverage.txt /coverage-noroot.txt
|
||||
|
||||
FROM base AS bench-base
|
||||
WORKDIR /app
|
||||
RUN --mount=type=bind,source=go.mod,target=/app/go.mod \
|
||||
--mount=type=bind,source=go.sum,target=/app/go.sum \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=type=cache,target=/go/pkg/mod <<EOT
|
||||
set -ex
|
||||
apk add --no-cache rsync
|
||||
go install tool github.com/jstemmer/go-junit-report/v2
|
||||
EOT
|
||||
|
||||
FROM bench-base AS bench
|
||||
WORKDIR /src
|
||||
ARG BENCH_FILE_SIZE
|
||||
RUN --mount=target=. \
|
||||
--mount=target=/go/pkg/mod,type=cache \
|
||||
--mount=target=/root/.cache,type=cache <<EOT
|
||||
set -ex
|
||||
set -o pipefail
|
||||
mkdir -p /tmp/bench-results
|
||||
CGO_ENABLED=0 xx-go test -benchmem -bench=. -run=^$ . 2>&1 | tee /tmp/fsutil.log
|
||||
go-junit-report -in /tmp/fsutil.log -out /tmp/bench-results/fsutil.junit.xml
|
||||
cd bench
|
||||
CGO_ENABLED=0 xx-go test -benchmem -bench=. -run=^$ . 2>&1 | tee /tmp/bench.log
|
||||
go-junit-report -in /tmp/bench.log -out /tmp/bench-results/bench.junit.xml
|
||||
EOT
|
||||
|
||||
FROM bench-base AS bench-noroot
|
||||
WORKDIR /src
|
||||
RUN mkdir -p /go/pkg && chmod 0777 /go/pkg
|
||||
USER 1000:1000
|
||||
ARG BENCH_FILE_SIZE
|
||||
RUN --mount=target=. \
|
||||
--mount=target=/tmp/.cache,type=cache <<EOT
|
||||
set -ex
|
||||
set -o pipefail
|
||||
mkdir -p /tmp/bench-results
|
||||
CGO_ENABLED=0 GOCACHE=/tmp/gocache xx-go test -bench=. -benchmem -run=^$ . 2>&1 | tee /tmp/fsutil.log
|
||||
go-junit-report -in /tmp/fsutil.log -out /tmp/bench-results/fsutil.junit.xml
|
||||
cd bench
|
||||
CGO_ENABLED=0 GOCACHE=/tmp/gocache xx-go test -bench=. -benchmem -run=^$ . 2>&1 | tee /tmp/bench.log
|
||||
go-junit-report -in /tmp/bench.log -out /tmp/bench-results/bench.junit.xml
|
||||
EOT
|
||||
|
||||
FROM scratch AS bench-root-results
|
||||
COPY --from=bench /tmp/bench-results /bench-root
|
||||
|
||||
FROM scratch AS bench-noroot-results
|
||||
COPY --from=bench-noroot /tmp/bench-results /bench-noroot
|
||||
|
||||
FROM build
|
||||
|
||||
+3
@@ -10,3 +10,6 @@ coverage:
|
||||
|
||||
github_checks:
|
||||
annotations: false
|
||||
|
||||
ignore:
|
||||
- "**/*.pb.go"
|
||||
|
||||
+2
-3
@@ -21,7 +21,7 @@ import (
|
||||
const defaultDirectoryMode = 0755
|
||||
|
||||
var bufferPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
buffer := make([]byte, 32*1024)
|
||||
return &buffer
|
||||
},
|
||||
@@ -738,8 +738,7 @@ func rel(basepath, targpath string) (string, error) {
|
||||
// filepath.Rel can't handle UUID paths in windows
|
||||
if runtime.GOOS == "windows" {
|
||||
pfx := basepath + `\`
|
||||
if strings.HasPrefix(targpath, pfx) {
|
||||
p := strings.TrimPrefix(targpath, pfx)
|
||||
if p, ok := strings.CutPrefix(targpath, pfx); ok {
|
||||
if p == "" {
|
||||
p = "."
|
||||
}
|
||||
|
||||
+66
-8
@@ -3,7 +3,10 @@ package fsutil
|
||||
import (
|
||||
"context"
|
||||
"hash"
|
||||
gofs "io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tonistiigi/fsutil/types"
|
||||
@@ -11,26 +14,41 @@ import (
|
||||
|
||||
type walkerFn func(ctx context.Context, pathC chan<- *currentPath) error
|
||||
|
||||
func Changes(ctx context.Context, a, b walkerFn, changeFn ChangeFunc) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type HandleChangeFn func(ChangeKind, string, os.FileInfo, error) error
|
||||
|
||||
type ContentHasher func(*types.Stat) (hash.Hash, error)
|
||||
|
||||
func getWalkerFn(root string) walkerFn {
|
||||
return getFSWalkerFn(func() (FS, error) {
|
||||
return NewFS(root)
|
||||
})
|
||||
}
|
||||
|
||||
func getRootWalkerFn(root Root) walkerFn {
|
||||
return getFSWalkerFn(func() (FS, error) {
|
||||
return NewRootFS(root), nil
|
||||
})
|
||||
}
|
||||
|
||||
func getFSWalkerFn(newFS func() (FS, error)) walkerFn {
|
||||
return func(ctx context.Context, pathC chan<- *currentPath) error {
|
||||
return errors.Wrap(Walk(ctx, root, nil, func(path string, f os.FileInfo, err error) error {
|
||||
fs, err := newFS()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to walk")
|
||||
}
|
||||
return errors.Wrap(fs.Walk(ctx, "/", func(path string, entry gofs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stat, ok := f.Sys().(*types.Stat)
|
||||
fi, err := entry.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stat, ok := fi.Sys().(*types.Stat)
|
||||
if !ok {
|
||||
return errors.Errorf("%T invalid file without stat information", f.Sys())
|
||||
return errors.Errorf("%T invalid file without stat information", fi.Sys())
|
||||
}
|
||||
|
||||
p := ¤tPath{
|
||||
path: path,
|
||||
stat: stat,
|
||||
@@ -46,6 +64,46 @@ func getWalkerFn(root string) walkerFn {
|
||||
}
|
||||
}
|
||||
|
||||
func mkrootstat(root Root, relpath string, fi os.FileInfo, inodemap map[uint64]string) (*types.Stat, error) {
|
||||
stat := &types.Stat{
|
||||
Path: filepath.FromSlash(filepath.ToSlash(relpath)),
|
||||
Mode: uint32(fi.Mode()),
|
||||
ModTime: fi.ModTime().UnixNano(),
|
||||
}
|
||||
|
||||
setUnixOpt(fi, stat, relpath, inodemap)
|
||||
|
||||
if !fi.IsDir() {
|
||||
stat.Size = fi.Size()
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
link, err := root.Readlink(relpath)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
stat.Linkname = link
|
||||
}
|
||||
}
|
||||
if fi.IsDir() || fi.Mode().IsRegular() {
|
||||
if err := loadRootXattr(root, relpath, stat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
permPart := stat.Mode & uint32(os.ModePerm)
|
||||
noPermPart := stat.Mode &^ uint32(os.ModePerm)
|
||||
// Add the x bit: make everything +x from windows
|
||||
permPart |= 0111
|
||||
permPart &= 0755
|
||||
stat.Mode = noPermPart | permPart
|
||||
}
|
||||
|
||||
// Clear the socket bit since archive/tar.FileInfoHeader does not handle it
|
||||
stat.Mode &^= uint32(os.ModeSocket)
|
||||
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
func emptyWalker(ctx context.Context, pathC chan<- *currentPath) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
+22
@@ -6,6 +6,10 @@ variable "DESTDIR" {
|
||||
default = "./bin"
|
||||
}
|
||||
|
||||
variable "BENCH_FILE_SIZE" {
|
||||
default = null
|
||||
}
|
||||
|
||||
target "_platforms" {
|
||||
platforms = [
|
||||
"darwin/amd64",
|
||||
@@ -60,6 +64,24 @@ target "test-noroot" {
|
||||
output = ["${DESTDIR}/coverage"]
|
||||
}
|
||||
|
||||
target "bench-root" {
|
||||
inherits = ["build"]
|
||||
target = "bench-root-results"
|
||||
output = ["${DESTDIR}/bench"]
|
||||
args = {
|
||||
BENCH_FILE_SIZE = BENCH_FILE_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
target "bench-noroot" {
|
||||
inherits = ["build"]
|
||||
target = "bench-noroot-results"
|
||||
output = ["${DESTDIR}/bench"]
|
||||
args = {
|
||||
BENCH_FILE_SIZE = BENCH_FILE_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
group "lint" {
|
||||
targets = ["lint-golangci", "lint-gopls"]
|
||||
}
|
||||
|
||||
+6
-6
@@ -326,10 +326,10 @@ func (fs *filterFS) Walk(ctx context.Context, target string, fn gofs.WalkDirFunc
|
||||
return ctx.Err()
|
||||
default:
|
||||
if fs.mapFn != nil {
|
||||
result := fs.mapFn(stat.Path, stat)
|
||||
if result == MapResultSkipDir {
|
||||
switch result := fs.mapFn(stat.Path, stat); result {
|
||||
case MapResultSkipDir:
|
||||
return filepath.SkipDir
|
||||
} else if result == MapResultExclude {
|
||||
case MapResultExclude:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -355,10 +355,10 @@ func (fs *filterFS) Walk(ctx context.Context, target string, fn gofs.WalkDirFunc
|
||||
default:
|
||||
}
|
||||
if fs.mapFn != nil {
|
||||
result := fs.mapFn(parentStat.Path, parentStat)
|
||||
if result == MapResultExclude {
|
||||
switch result := fs.mapFn(parentStat.Path, parentStat); result {
|
||||
case MapResultExclude:
|
||||
continue
|
||||
} else if result == MapResultSkipDir {
|
||||
case MapResultSkipDir:
|
||||
parentDirs[i].skipFn = true
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
+71
-5
@@ -40,6 +40,11 @@ func NewFS(root string) (FS, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewRootFS creates a new FS from a filesystem root.
|
||||
func NewRootFS(root Root) FS {
|
||||
return &rootFS{root: root}
|
||||
}
|
||||
|
||||
type fs struct {
|
||||
root string
|
||||
}
|
||||
@@ -90,6 +95,67 @@ func (fs *fs) Open(p string) (io.ReadCloser, error) {
|
||||
return rc, errors.WithStack(err)
|
||||
}
|
||||
|
||||
type rootFS struct {
|
||||
root Root
|
||||
}
|
||||
|
||||
func (fs *rootFS) Walk(ctx context.Context, target string, fn gofs.WalkDirFunc) error {
|
||||
seenFiles := make(map[uint64]string)
|
||||
target = cleanRootFSTarget(target)
|
||||
|
||||
return gofs.WalkDir(fs.root.FS(), target, func(path string, dirEntry gofs.DirEntry, walkErr error) (retErr error) {
|
||||
defer func() {
|
||||
if retErr != nil && isNotExist(retErr) {
|
||||
retErr = filepath.SkipDir
|
||||
}
|
||||
}()
|
||||
|
||||
path = filepath.FromSlash(path)
|
||||
if path == "." {
|
||||
return nil
|
||||
}
|
||||
|
||||
var entry gofs.DirEntry
|
||||
if dirEntry != nil {
|
||||
fi, err := fs.root.Lstat(path)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
stat, err := mkrootstat(fs.root, path, fi, seenFiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entry = &DirEntryInfo{Stat: stat}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
if err := fn(path, entry, walkErr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (fs *rootFS) Open(p string) (io.ReadCloser, error) {
|
||||
rc, err := fs.root.OpenFile(cleanRootPath(p), os.O_RDONLY, 0)
|
||||
return rc, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func cleanRootFSTarget(target string) string {
|
||||
target = cleanRootPath(target)
|
||||
for strings.HasPrefix(target, string(filepath.Separator)) {
|
||||
target = strings.TrimPrefix(target, string(filepath.Separator))
|
||||
}
|
||||
if target == "" || target == "." {
|
||||
return "."
|
||||
}
|
||||
return filepath.ToSlash(target)
|
||||
}
|
||||
|
||||
type Dir struct {
|
||||
Stat *types.Stat
|
||||
FS FS
|
||||
@@ -187,7 +253,7 @@ type StatInfo struct {
|
||||
}
|
||||
|
||||
func (s *StatInfo) Name() string {
|
||||
return filepath.Base(s.Stat.Path)
|
||||
return filepath.Base(s.Path)
|
||||
}
|
||||
|
||||
func (s *StatInfo) Size() int64 {
|
||||
@@ -206,7 +272,7 @@ func (s *StatInfo) IsDir() bool {
|
||||
return s.Mode().IsDir()
|
||||
}
|
||||
|
||||
func (s *StatInfo) Sys() interface{} {
|
||||
func (s *StatInfo) Sys() any {
|
||||
return s.Stat
|
||||
}
|
||||
|
||||
@@ -221,7 +287,7 @@ type DirEntryInfo struct {
|
||||
|
||||
func (s *DirEntryInfo) Name() string {
|
||||
if s.Stat != nil {
|
||||
return filepath.Base(s.Stat.Path)
|
||||
return filepath.Base(s.Path)
|
||||
}
|
||||
return s.entry.Name()
|
||||
}
|
||||
@@ -235,7 +301,7 @@ func (s *DirEntryInfo) IsDir() bool {
|
||||
|
||||
func (s *DirEntryInfo) Type() gofs.FileMode {
|
||||
if s.Stat != nil {
|
||||
return gofs.FileMode(s.Stat.Mode)
|
||||
return gofs.FileMode(s.Mode)
|
||||
}
|
||||
return s.entry.Type()
|
||||
}
|
||||
@@ -253,6 +319,6 @@ func (s *DirEntryInfo) Info() (gofs.FileInfo, error) {
|
||||
s.Stat = stat
|
||||
}
|
||||
|
||||
st := s.Stat.Clone()
|
||||
st := s.Clone()
|
||||
return &StatInfo{st}, nil
|
||||
}
|
||||
|
||||
+61
-42
@@ -6,45 +6,64 @@
|
||||
Incremental file directory sync tools in golang.
|
||||
|
||||
```
|
||||
BENCH_FILE_SIZE=10000 ./bench.test --test.bench .
|
||||
BenchmarkCopyWithTar10-4 2000 995242 ns/op
|
||||
BenchmarkCopyWithTar50-4 300 4710021 ns/op
|
||||
BenchmarkCopyWithTar200-4 100 16627260 ns/op
|
||||
BenchmarkCopyWithTar1000-4 20 60031459 ns/op
|
||||
BenchmarkCPA10-4 1000 1678367 ns/op
|
||||
BenchmarkCPA50-4 500 3690306 ns/op
|
||||
BenchmarkCPA200-4 200 9495066 ns/op
|
||||
BenchmarkCPA1000-4 50 29769289 ns/op
|
||||
BenchmarkDiffCopy10-4 2000 943889 ns/op
|
||||
BenchmarkDiffCopy50-4 500 3285950 ns/op
|
||||
BenchmarkDiffCopy200-4 200 8563792 ns/op
|
||||
BenchmarkDiffCopy1000-4 50 29511340 ns/op
|
||||
BenchmarkDiffCopyProto10-4 2000 944615 ns/op
|
||||
BenchmarkDiffCopyProto50-4 500 3334940 ns/op
|
||||
BenchmarkDiffCopyProto200-4 200 9420038 ns/op
|
||||
BenchmarkDiffCopyProto1000-4 50 30632429 ns/op
|
||||
BenchmarkIncrementalDiffCopy10-4 2000 691993 ns/op
|
||||
BenchmarkIncrementalDiffCopy50-4 1000 1304253 ns/op
|
||||
BenchmarkIncrementalDiffCopy200-4 500 3306519 ns/op
|
||||
BenchmarkIncrementalDiffCopy1000-4 200 10211343 ns/op
|
||||
BenchmarkIncrementalDiffCopy5000-4 20 55194427 ns/op
|
||||
BenchmarkIncrementalDiffCopy10000-4 20 91759289 ns/op
|
||||
BenchmarkIncrementalCopyWithTar10-4 2000 1020258 ns/op
|
||||
BenchmarkIncrementalCopyWithTar50-4 300 5348786 ns/op
|
||||
BenchmarkIncrementalCopyWithTar200-4 100 19495000 ns/op
|
||||
BenchmarkIncrementalCopyWithTar1000-4 20 70338507 ns/op
|
||||
BenchmarkIncrementalRsync10-4 30 45215754 ns/op
|
||||
BenchmarkIncrementalRsync50-4 30 45837260 ns/op
|
||||
BenchmarkIncrementalRsync200-4 30 48780614 ns/op
|
||||
BenchmarkIncrementalRsync1000-4 20 54801892 ns/op
|
||||
BenchmarkIncrementalRsync5000-4 20 84782542 ns/op
|
||||
BenchmarkIncrementalRsync10000-4 10 103355108 ns/op
|
||||
BenchmarkRsync10-4 30 46776470 ns/op
|
||||
BenchmarkRsync50-4 30 48601555 ns/op
|
||||
BenchmarkRsync200-4 20 59642691 ns/op
|
||||
BenchmarkRsync1000-4 20 101343010 ns/op
|
||||
BenchmarkGnuTar10-4 500 3171448 ns/op
|
||||
BenchmarkGnuTar50-4 300 5030296 ns/op
|
||||
BenchmarkGnuTar200-4 100 10464313 ns/op
|
||||
BenchmarkGnuTar1000-4 50 30375257 ns/op
|
||||
```
|
||||
BENCH_FILE_SIZE=10000 docker buildx bake bench-root
|
||||
...
|
||||
#17 0.303 + CGO_ENABLED=0 xx-go test -benchmem '-bench=.' '-run=^$' .
|
||||
#17 0.303 + tee /tmp/fsutil.log
|
||||
#17 1.527 BenchmarkWalker/depth_1_target-32 28356 42258 ns/op 9234 B/op 174 allocs/op
|
||||
#17 3.166 BenchmarkWalker/depth_1_doublestar_target-32 28647 42038 ns/op 9282 B/op 175 allocs/op
|
||||
#17 4.865 BenchmarkWalker/depth_2_star_target-32 1184 1009371 ns/op 200654 B/op 3971 allocs/op
|
||||
#17 6.342 BenchmarkWalker/depth_2_doublestar_target-32 1148 1007115 ns/op 195891 B/op 3908 allocs/op
|
||||
#17 9.339 BenchmarkWalker/depth_3_star_star_target-32 39 28146516 ns/op 5221363 B/op 100915 allocs/op
|
||||
#17 13.54 BenchmarkWalker/depth_3_doublestar_target-32 40 28496829 ns/op 5206464 B/op 100828 allocs/op
|
||||
#17 17.99 BenchmarkWalker/depth_4_star_star_star_target-32 26 48224854 ns/op 6571213 B/op 119421 allocs/op
|
||||
#17 25.32 BenchmarkWalker/depth_4_doublestar_target-32 24 45061931 ns/op 6488522 B/op 119315 allocs/op
|
||||
#17 28.67 BenchmarkWalker/depth_5_star_star_star_star_target-32 54 22124864 ns/op 2476377 B/op 42818 allocs/op
|
||||
#17 32.59 BenchmarkWalker/depth_5_doublestar_target-32 49 21479412 ns/op 2460690 B/op 42699 allocs/op
|
||||
#17 35.09 BenchmarkWalker/depth_6_star_star_star_star_star_target-32 28 38307776 ns/op 3998884 B/op 67772 allocs/op
|
||||
#17 38.05 BenchmarkWalker/depth_6_doublestar_target-32 31 38242074 ns/op 3980841 B/op 67634 allocs/op
|
||||
#17 42.92 BenchmarkWalker/depth_6_doublestar_exclude_star_star_doublestar-32 2925 393602 ns/op 47439 B/op 1006 allocs/op
|
||||
#17 45.99 + cd bench
|
||||
#17 45.99 + CGO_ENABLED=0 xx-go test -benchmem '-bench=.' '-run=^$' .
|
||||
#17 45.99 + tee /tmp/bench.log
|
||||
#17 46.84 BenchmarkCopyWithTar10-32 283 4291776 ns/op 906824 B/op 843 allocs/op
|
||||
#17 50.05 BenchmarkCopyWithTar50-32 50 24077499 ns/op 4999874 B/op 4514 allocs/op
|
||||
#17 54.00 BenchmarkCopyWithTar200-32 15 74350687 ns/op 18757006 B/op 15347 allocs/op
|
||||
#17 56.31 BenchmarkCopyWithTar1000-32 5 259188166 ns/op 72393427 B/op 55045 allocs/op
|
||||
#17 61.31 BenchmarkCPA10-32 339 3496169 ns/op 7102 B/op 77 allocs/op
|
||||
#17 64.68 BenchmarkCPA50-32 74 15808000 ns/op 7102 B/op 77 allocs/op
|
||||
#17 67.26 BenchmarkCPA200-32 26 45892546 ns/op 7101 B/op 77 allocs/op
|
||||
#17 70.08 BenchmarkCPA1000-32 8 147602854 ns/op 7103 B/op 77 allocs/op
|
||||
#17 72.60 BenchmarkDiffCopy10-32 392 3045072 ns/op 219789 B/op 1123 allocs/op
|
||||
#17 76.15 BenchmarkDiffCopy50-32 82 14024073 ns/op 1253831 B/op 5460 allocs/op
|
||||
#17 78.83 BenchmarkDiffCopy200-32 27 41259003 ns/op 4683591 B/op 18640 allocs/op
|
||||
#17 81.60 BenchmarkDiffCopy1000-32 8 125244042 ns/op 17285147 B/op 67649 allocs/op
|
||||
#17 83.90 BenchmarkDiffCopyProto10-32 412 2934103 ns/op 232184 B/op 1143 allocs/op
|
||||
#17 87.46 BenchmarkDiffCopyProto50-32 80 13809955 ns/op 1273311 B/op 5565 allocs/op
|
||||
#17 90.02 BenchmarkDiffCopyProto200-32 30 41169665 ns/op 4697476 B/op 18995 allocs/op
|
||||
#17 93.05 BenchmarkDiffCopyProto1000-32 8 127126319 ns/op 17334920 B/op 68883 allocs/op
|
||||
#17 95.37 BenchmarkIncrementalDiffCopy10-32 1540 779568 ns/op 119068 B/op 1015 allocs/op
|
||||
#17 97.84 BenchmarkIncrementalDiffCopy50-32 782 1513121 ns/op 455740 B/op 4285 allocs/op
|
||||
#17 99.97 BenchmarkIncrementalDiffCopy200-32 271 4248983 ns/op 1329875 B/op 13603 allocs/op
|
||||
#17 102.3 BenchmarkIncrementalDiffCopy1000-32 84 14027390 ns/op 4552790 B/op 46849 allocs/op
|
||||
#17 105.4 BenchmarkIncrementalDiffCopy5000-32 14 73269136 ns/op 24500669 B/op 266772 allocs/op
|
||||
#17 110.6 BenchmarkIncrementalDiffCopy10000-32 9 128400623 ns/op 43706410 B/op 472982 allocs/op
|
||||
#17 114.4 BenchmarkIncrementalCopyWithTar10-32 396 2946217 ns/op 915096 B/op 826 allocs/op
|
||||
#17 116.3 BenchmarkIncrementalCopyWithTar50-32 69 16767967 ns/op 5093048 B/op 4472 allocs/op
|
||||
#17 117.7 BenchmarkIncrementalCopyWithTar200-32 19 62520307 ns/op 19081658 B/op 15270 allocs/op
|
||||
#17 119.7 BenchmarkIncrementalCopyWithTar1000-32 5 239712851 ns/op 73113704 B/op 54938 allocs/op
|
||||
#17 122.8 BenchmarkIncrementalRsync10-32 26 43773727 ns/op 6608 B/op 69 allocs/op
|
||||
#17 124.1 BenchmarkIncrementalRsync50-32 25 45985011 ns/op 6608 B/op 69 allocs/op
|
||||
#17 125.6 BenchmarkIncrementalRsync200-32 22 49976819 ns/op 6608 B/op 69 allocs/op
|
||||
#17 127.3 BenchmarkIncrementalRsync1000-32 19 63618139 ns/op 6600 B/op 69 allocs/op
|
||||
#17 130.6 BenchmarkIncrementalRsync5000-32 8 132002745 ns/op 6608 B/op 69 allocs/op
|
||||
#17 136.3 BenchmarkIncrementalRsync10000-32 6 187247351 ns/op 6608 B/op 69 allocs/op
|
||||
#17 140.3 BenchmarkRsync10-32 25 46054741 ns/op 6606 B/op 69 allocs/op
|
||||
#17 141.6 BenchmarkRsync50-32 19 58922835 ns/op 6605 B/op 69 allocs/op
|
||||
#17 143.2 BenchmarkRsync200-32 13 91176938 ns/op 6606 B/op 69 allocs/op
|
||||
#17 145.5 BenchmarkRsync1000-32 6 198319527 ns/op 6606 B/op 69 allocs/op
|
||||
#17 147.6 BenchmarkGnuTar10-32 268 4489528 ns/op 14192 B/op 151 allocs/op
|
||||
#17 150.8 BenchmarkGnuTar50-32 54 20528041 ns/op 14192 B/op 151 allocs/op
|
||||
#17 152.9 BenchmarkGnuTar200-32 19 60394926 ns/op 14192 B/op 151 allocs/op
|
||||
#17 155.4 BenchmarkGnuTar1000-32 6 198630328 ns/op 14192 B/op 151 allocs/op
|
||||
```
|
||||
|
||||
+67
-6
@@ -64,13 +64,32 @@ type ReceiveOpt struct {
|
||||
MetadataOnly FilterFunc
|
||||
}
|
||||
|
||||
type receiveDiskWriter interface {
|
||||
HandleChange(ChangeKind, string, os.FileInfo, error) error
|
||||
Wait(context.Context) error
|
||||
}
|
||||
|
||||
func Receive(ctx context.Context, conn Stream, dest string, opt ReceiveOpt) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
r := &receiver{
|
||||
r := newReceiver(conn, opt)
|
||||
r.dest = dest
|
||||
return r.run(ctx)
|
||||
}
|
||||
|
||||
func ReceiveRoot(ctx context.Context, conn Stream, dest Root, opt ReceiveOpt) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
r := newReceiver(conn, opt)
|
||||
r.root = dest
|
||||
return r.run(ctx)
|
||||
}
|
||||
|
||||
func newReceiver(conn Stream, opt ReceiveOpt) *receiver {
|
||||
return &receiver{
|
||||
conn: &syncStream{Stream: conn},
|
||||
dest: dest,
|
||||
files: make(map[string]uint32),
|
||||
pipes: make(map[uint32]io.WriteCloser),
|
||||
notifyHashed: opt.NotifyHashed,
|
||||
@@ -81,11 +100,11 @@ func Receive(ctx context.Context, conn Stream, dest string, opt ReceiveOpt) erro
|
||||
differ: opt.Differ,
|
||||
metadataOnly: opt.MetadataOnly,
|
||||
}
|
||||
return r.run(ctx)
|
||||
}
|
||||
|
||||
type receiver struct {
|
||||
dest string
|
||||
root Root
|
||||
conn Stream
|
||||
files map[string]uint32
|
||||
pipes map[uint32]io.WriteCloser
|
||||
@@ -159,7 +178,7 @@ func (w *dynamicWalker) fill(ctx context.Context, pathC chan<- *currentPath) err
|
||||
func (r *receiver) run(ctx context.Context) error {
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
dw, err := NewDiskWriter(ctx, r.dest, DiskWriterOpt{
|
||||
dw, err := r.newDiskWriter(ctx, DiskWriterOpt{
|
||||
AsyncDataCb: r.asyncDataFunc,
|
||||
NotifyCb: r.notifyHashed,
|
||||
ContentHasher: r.contentHasher,
|
||||
@@ -179,12 +198,21 @@ func (r *receiver) run(ctx context.Context) error {
|
||||
g.Go(func() (retErr error) {
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
// If we're unwinding because the errgroup context was
|
||||
// cancelled by another goroutine's failure, report that root
|
||||
// cause instead of the bare "context canceled", which would
|
||||
// otherwise overwrite the real error on the sender side.
|
||||
if errors.Is(retErr, context.Canceled) {
|
||||
if cause := context.Cause(ctx); cause != nil {
|
||||
retErr = cause
|
||||
}
|
||||
}
|
||||
r.conn.SendMsg(&types.Packet{Type: types.PACKET_ERR, Data: []byte(retErr.Error())})
|
||||
}
|
||||
}()
|
||||
destWalker := emptyWalker
|
||||
if !r.merge {
|
||||
destWalker = getWalkerFn(r.dest)
|
||||
destWalker = r.destWalker()
|
||||
}
|
||||
err := doubleWalkDiff(ctx, dw.HandleChange, destWalker, w.fill, r.filter, r.differ)
|
||||
if err != nil {
|
||||
@@ -333,12 +361,45 @@ func (r *receiver) run(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.writeMetadata(metadataBuffer)
|
||||
}
|
||||
|
||||
func (r *receiver) newDiskWriter(ctx context.Context, opt DiskWriterOpt) (receiveDiskWriter, error) {
|
||||
if r.root != nil {
|
||||
return NewRootDiskWriter(ctx, r.root, opt)
|
||||
}
|
||||
return NewDiskWriter(ctx, r.dest, opt)
|
||||
}
|
||||
|
||||
func (r *receiver) destWalker() walkerFn {
|
||||
if r.root != nil {
|
||||
return getRootWalkerFn(r.root)
|
||||
}
|
||||
return getWalkerFn(r.dest)
|
||||
}
|
||||
|
||||
func (r *receiver) writeMetadata(metadataBuffer *buffer) error {
|
||||
if r.root != nil {
|
||||
// although we don't allow tranferring metadataPath, make sure there was no preexisting file/symlink
|
||||
_ = r.root.Remove(metadataPath)
|
||||
|
||||
f, err := r.root.OpenFile(metadataPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if _, err := metadataBuffer.WriteTo(f); err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
// although we don't allow tranferring metadataPath, make sure there was no preexisting file/symlink
|
||||
os.Remove(filepath.Join(r.dest, metadataPath))
|
||||
|
||||
f, err := os.OpenFile(filepath.Join(r.dest, metadataPath), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if _, err := metadataBuffer.WriteTo(f); err != nil {
|
||||
f.Close()
|
||||
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
gofs "io/fs"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Root interface {
|
||||
Close() error
|
||||
FS() gofs.FS
|
||||
Remove(string) error
|
||||
RemoveAll(string) error
|
||||
Lstat(string) (os.FileInfo, error)
|
||||
Stat(string) (os.FileInfo, error)
|
||||
Mkdir(string, os.FileMode) error
|
||||
Symlink(string, string) error
|
||||
Link(string, string) error
|
||||
OpenRoot(string) (*os.Root, error)
|
||||
OpenFile(string, int, os.FileMode) (*os.File, error)
|
||||
Readlink(string) (string, error)
|
||||
Rename(string, string) error
|
||||
Lchown(string, int, int) error
|
||||
Chmod(string, os.FileMode) error
|
||||
Chtimes(string, time.Time, time.Time) error
|
||||
RootXattr
|
||||
RootMknod
|
||||
RootLChtimes
|
||||
}
|
||||
|
||||
type RootXattr interface {
|
||||
LSetxattr(name, key string, value []byte, flags int) error
|
||||
}
|
||||
|
||||
type RootMknod interface {
|
||||
Mknod(name string, mode uint32, dev int) error
|
||||
}
|
||||
|
||||
type RootLChtimes interface {
|
||||
LChtimes(name string, mtime time.Time) error
|
||||
}
|
||||
|
||||
type root struct {
|
||||
*os.Root
|
||||
|
||||
mu sync.Mutex
|
||||
closed bool
|
||||
rootDirState
|
||||
}
|
||||
|
||||
func NewRoot(osroot *os.Root) Root {
|
||||
return &root{Root: osroot}
|
||||
}
|
||||
|
||||
func (r *root) Close() error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
if r.closed {
|
||||
r.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
r.closed = true
|
||||
rootDir := r.rootDir
|
||||
r.rootDir = nil
|
||||
osroot := r.Root
|
||||
r.Root = nil
|
||||
r.mu.Unlock()
|
||||
|
||||
var err error
|
||||
if rootDir != nil {
|
||||
err = rootDir.Close()
|
||||
}
|
||||
if osroot != nil {
|
||||
if err2 := osroot.Close(); err == nil {
|
||||
err = err2
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var _ Root = (*root)(nil)
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
//go:build linux || darwin || freebsd || netbsd || openbsd || dragonfly
|
||||
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type rootDirState struct {
|
||||
rootDirOnce sync.Once
|
||||
rootDir *os.File
|
||||
rootDirErr error
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
//go:build !linux && !darwin && !freebsd && !netbsd && !openbsd && !dragonfly
|
||||
|
||||
package fsutil
|
||||
|
||||
import "os"
|
||||
|
||||
type rootDirState struct {
|
||||
rootDir *os.File
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
//go:build !linux && !freebsd && !netbsd
|
||||
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func unsupportedRootOp(op, name string, err error) error {
|
||||
return errors.WithStack(&os.PathError{Op: op, Path: name, Err: err})
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
//go:build !linux && !darwin && !freebsd && !netbsd && !openbsd && !dragonfly && !windows
|
||||
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ RootLChtimes = (*root)(nil)
|
||||
|
||||
func (r *root) LChtimes(name string, mtime time.Time) error {
|
||||
return unsupportedRootOp("utimensat", name, syscall.ENOSYS)
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
//go:build windows
|
||||
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var _ RootLChtimes = (*root)(nil)
|
||||
|
||||
func (r *root) LChtimes(name string, mtime time.Time) error {
|
||||
fi, err := r.Lstat(name)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
return nil
|
||||
}
|
||||
if err := r.Chtimes(name, mtime, mtime); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
//go:build freebsd
|
||||
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var _ RootMknod = (*root)(nil)
|
||||
|
||||
func (r *root) Mknod(name string, mode uint32, dev int) error {
|
||||
parent, base, closeParent, err := r.openRootParent(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if closeParent {
|
||||
defer parent.Close()
|
||||
}
|
||||
|
||||
if err := unix.Mknodat(int(parent.Fd()), base, mode, uint64(dev)); err != nil {
|
||||
return errors.WithStack(&os.PathError{Op: "mknodat", Path: name, Err: err})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
//go:build linux || netbsd || openbsd || dragonfly
|
||||
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var _ RootMknod = (*root)(nil)
|
||||
|
||||
func (r *root) Mknod(name string, mode uint32, dev int) error {
|
||||
parent, base, closeParent, err := r.openRootParent(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if closeParent {
|
||||
defer parent.Close()
|
||||
}
|
||||
|
||||
if err := unix.Mknodat(int(parent.Fd()), base, mode, dev); err != nil {
|
||||
return errors.WithStack(&os.PathError{Op: "mknodat", Path: name, Err: err})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
//go:build !linux && !freebsd && !netbsd && !openbsd && !dragonfly
|
||||
|
||||
package fsutil
|
||||
|
||||
import "syscall"
|
||||
|
||||
var _ RootMknod = (*root)(nil)
|
||||
|
||||
func (r *root) Mknod(name string, mode uint32, dev int) error {
|
||||
return unsupportedRootOp("mknodat", name, syscall.ENOSYS)
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
//go:build linux || darwin || freebsd || netbsd || openbsd || dragonfly
|
||||
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var _ RootLChtimes = (*root)(nil)
|
||||
|
||||
func (r *root) LChtimes(name string, mtime time.Time) error {
|
||||
parent, base, closeParent, err := r.openRootParent(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if closeParent {
|
||||
defer parent.Close()
|
||||
}
|
||||
|
||||
ts := unix.NsecToTimespec(mtime.UnixNano())
|
||||
times := []unix.Timespec{ts, ts}
|
||||
if err := unix.UtimesNanoAt(int(parent.Fd()), base, times, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
||||
return errors.WithStack(&os.PathError{Op: "utimensat", Path: name, Err: err})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *root) openRootParent(name string) (*os.File, string, bool, error) {
|
||||
if r == nil {
|
||||
return nil, "", false, errors.New("nil root")
|
||||
}
|
||||
|
||||
// fast path for direct basename
|
||||
if !strings.ContainsRune(name, filepath.Separator) {
|
||||
if name == "" || name == "." || name == ".." {
|
||||
return nil, "", false, errors.WithStack(&os.PathError{Op: "openat", Path: name, Err: syscall.EINVAL})
|
||||
}
|
||||
parent, err := r.rootDirFile()
|
||||
if err != nil {
|
||||
return nil, "", false, errors.WithStack(err)
|
||||
}
|
||||
return parent, name, false, nil
|
||||
}
|
||||
|
||||
cleaned := filepath.Clean(name)
|
||||
base := filepath.Base(cleaned)
|
||||
if base == "." || base == ".." {
|
||||
return nil, "", false, errors.WithStack(&os.PathError{Op: "openat", Path: name, Err: syscall.EINVAL})
|
||||
}
|
||||
|
||||
dir := filepath.Dir(cleaned)
|
||||
if dir == "." {
|
||||
parent, err := r.rootDirFile()
|
||||
if err != nil {
|
||||
return nil, "", false, errors.WithStack(err)
|
||||
}
|
||||
return parent, base, false, nil
|
||||
}
|
||||
|
||||
parent, err := r.OpenFile(dir, os.O_RDONLY|unix.O_DIRECTORY, 0)
|
||||
if err != nil {
|
||||
return nil, "", false, errors.WithStack(err)
|
||||
}
|
||||
return parent, base, true, nil
|
||||
}
|
||||
|
||||
func (r *root) rootDirFile() (*os.File, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.closed {
|
||||
return nil, os.ErrClosed
|
||||
}
|
||||
if r.Root == nil {
|
||||
return nil, errors.New("nil root")
|
||||
}
|
||||
r.rootDirOnce.Do(func() {
|
||||
r.rootDir, r.rootDirErr = r.OpenFile(".", os.O_RDONLY|unix.O_DIRECTORY, 0)
|
||||
})
|
||||
if r.rootDirErr != nil {
|
||||
return nil, r.rootDirErr
|
||||
}
|
||||
return r.rootDir, nil
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
//go:build linux || freebsd || netbsd || openbsd || dragonfly
|
||||
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/tonistiigi/fsutil/types"
|
||||
)
|
||||
|
||||
func handleRootTarTypeBlockCharFifo(root RootMknod, path string, stat *types.Stat) error {
|
||||
mode := uint32(stat.Mode & 07777)
|
||||
if os.FileMode(stat.Mode)&os.ModeCharDevice != 0 {
|
||||
mode |= syscall.S_IFCHR
|
||||
} else if os.FileMode(stat.Mode)&os.ModeNamedPipe != 0 {
|
||||
mode |= syscall.S_IFIFO
|
||||
} else {
|
||||
mode |= syscall.S_IFBLK
|
||||
}
|
||||
|
||||
return root.Mknod(path, mode, int(mkdev(stat.Devmajor, stat.Devminor)))
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
//go:build !linux && !freebsd && !netbsd && !openbsd && !dragonfly
|
||||
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tonistiigi/fsutil/types"
|
||||
)
|
||||
|
||||
func handleRootTarTypeBlockCharFifo(RootMknod, string, *types.Stat) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
//go:build linux || darwin || freebsd || netbsd
|
||||
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var _ RootXattr = (*root)(nil)
|
||||
|
||||
func (r *root) LSetxattr(name, key string, value []byte, flags int) error {
|
||||
f, err := r.OpenFile(name, os.O_RDONLY|unix.O_NOFOLLOW|unix.O_NONBLOCK, 0)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := unix.Fsetxattr(int(f.Fd()), key, value, flags); err != nil {
|
||||
return errors.WithStack(&os.PathError{Op: "fsetxattr", Path: name, Err: err})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
//go:build linux || darwin || freebsd || netbsd
|
||||
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tonistiigi/fsutil/types"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const rootXattrBufferSize = 128
|
||||
|
||||
func loadRootXattr(root Root, path string, stat *types.Stat) error {
|
||||
f, err := root.OpenFile(path, os.O_RDONLY|unix.O_NOFOLLOW|unix.O_NONBLOCK, 0)
|
||||
if err != nil {
|
||||
if ignoreRootXattrOpenError(err) {
|
||||
return nil
|
||||
}
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
xattrs, err := rootListxattr(int(f.Fd()))
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.ENOTSUP) || errors.Is(err, unix.EOPNOTSUPP) || errors.Is(err, unix.ENOSYS) {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrapf(err, "failed to xattr %s", path)
|
||||
}
|
||||
if len(xattrs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
m := make(map[string][]byte)
|
||||
for _, key := range xattrs {
|
||||
if skipXattr(key) {
|
||||
continue
|
||||
}
|
||||
if v, err := rootGetxattr(int(f.Fd()), key); err == nil {
|
||||
m[key] = v
|
||||
}
|
||||
}
|
||||
if len(m) > 0 {
|
||||
stat.Xattrs = m
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ignoreRootXattrOpenError(err error) bool {
|
||||
return errors.Is(err, syscall.ELOOP) ||
|
||||
errors.Is(err, syscall.EACCES) ||
|
||||
errors.Is(err, syscall.EPERM) ||
|
||||
errors.Is(err, syscall.ENXIO)
|
||||
}
|
||||
|
||||
func rootListxattr(fd int) ([]string, error) {
|
||||
return rootListxattrWith(fd, rootFlistxattr, rootParseListxattr)
|
||||
}
|
||||
|
||||
func rootGetxattr(fd int, key string) ([]byte, error) {
|
||||
buf := make([]byte, rootXattrBufferSize)
|
||||
n, err := unix.Fgetxattr(fd, key, buf)
|
||||
for err == unix.ERANGE {
|
||||
n, err = unix.Fgetxattr(fd, key, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = make([]byte, n)
|
||||
n, err = unix.Fgetxattr(fd, key, buf)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf[:n], nil
|
||||
}
|
||||
|
||||
type rootListxattrFunc func(int, []byte) (int, error)
|
||||
type rootParseListxattrFunc func([]byte) []string
|
||||
|
||||
func rootListxattrWith(fd int, list rootListxattrFunc, parse rootParseListxattrFunc) ([]string, error) {
|
||||
buf := make([]byte, rootXattrBufferSize)
|
||||
n, err := list(fd, buf)
|
||||
for err == unix.ERANGE {
|
||||
n, err = list(fd, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = make([]byte, n)
|
||||
n, err = list(fd, buf)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parse(buf[:n]), nil
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
//go:build freebsd || netbsd
|
||||
|
||||
package fsutil
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func rootFlistxattr(fd int, buf []byte) (int, error) {
|
||||
return unix.FlistxattrNS(fd, unix.EXTATTR_NAMESPACE_USER, buf)
|
||||
}
|
||||
|
||||
func rootParseListxattr(buf []byte) []string {
|
||||
var xattrs []string
|
||||
for i := 0; i < len(buf); {
|
||||
next := i + 1 + int(buf[i])
|
||||
if next > len(buf) {
|
||||
break
|
||||
}
|
||||
if next > i+1 {
|
||||
xattrs = append(xattrs, "user."+string(buf[i+1:next]))
|
||||
}
|
||||
i = next
|
||||
}
|
||||
return xattrs
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
//go:build linux || darwin
|
||||
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func rootFlistxattr(fd int, buf []byte) (int, error) {
|
||||
return unix.Flistxattr(fd, buf)
|
||||
}
|
||||
|
||||
func rootParseListxattr(buf []byte) []string {
|
||||
parts := bytes.Split(bytes.TrimSuffix(buf, []byte{0}), []byte{0})
|
||||
var xattrs []string
|
||||
for _, part := range parts {
|
||||
if len(part) > 0 {
|
||||
xattrs = append(xattrs, string(part))
|
||||
}
|
||||
}
|
||||
return xattrs
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
//go:build !linux && !darwin && !freebsd && !netbsd
|
||||
|
||||
package fsutil
|
||||
|
||||
import "github.com/tonistiigi/fsutil/types"
|
||||
|
||||
func loadRootXattr(Root, string, *types.Stat) error {
|
||||
return nil
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
//go:build !linux && !darwin && !freebsd && !netbsd
|
||||
|
||||
package fsutil
|
||||
|
||||
import "syscall"
|
||||
|
||||
var _ RootXattr = (*root)(nil)
|
||||
|
||||
func (r *root) LSetxattr(name, key string, value []byte, flags int) error {
|
||||
return unsupportedRootOp("lsetxattr", name, syscall.ENOSYS)
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const rootCacheDefaultSize = 128
|
||||
|
||||
type rootCache struct {
|
||||
mu sync.Mutex
|
||||
maxSize int
|
||||
entries map[string]*rootCacheEntry
|
||||
lru *list.List
|
||||
closed bool
|
||||
}
|
||||
|
||||
type rootCacheEntry struct {
|
||||
path string
|
||||
root Root
|
||||
refs int
|
||||
evicted bool
|
||||
elem *list.Element
|
||||
closeErr error
|
||||
}
|
||||
|
||||
type rootLease struct {
|
||||
root Root
|
||||
base string
|
||||
entry *rootCacheEntry
|
||||
cache *rootCache
|
||||
release sync.Once
|
||||
}
|
||||
|
||||
func newRootCache(root Root, maxSize int) *rootCache {
|
||||
if maxSize <= 0 {
|
||||
maxSize = rootCacheDefaultSize
|
||||
}
|
||||
return &rootCache{
|
||||
maxSize: maxSize,
|
||||
entries: map[string]*rootCacheEntry{
|
||||
".": {path: ".", root: root},
|
||||
},
|
||||
lru: list.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *rootCache) get(path string) (*rootLease, error) {
|
||||
path = cleanRootPath(path)
|
||||
dir := filepath.Dir(path)
|
||||
base := filepath.Base(path)
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.closed {
|
||||
return nil, errors.WithStack(os.ErrClosed)
|
||||
}
|
||||
|
||||
entry, err := c.getDirLocked(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entry.refs++
|
||||
|
||||
return &rootLease{
|
||||
root: entry.root,
|
||||
base: base,
|
||||
entry: entry,
|
||||
cache: c,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *rootCache) Close() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.closed = true
|
||||
var err error
|
||||
for path, entry := range c.entries {
|
||||
if path == "." {
|
||||
continue
|
||||
}
|
||||
delete(c.entries, path)
|
||||
if entry.elem != nil {
|
||||
c.lru.Remove(entry.elem)
|
||||
entry.elem = nil
|
||||
}
|
||||
entry.evicted = true
|
||||
if entry.refs == 0 {
|
||||
if err2 := closeRootCacheEntry(entry); err == nil {
|
||||
err = err2
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *rootLease) Release() error {
|
||||
var err error
|
||||
l.release.Do(func() {
|
||||
l.cache.mu.Lock()
|
||||
defer l.cache.mu.Unlock()
|
||||
|
||||
l.entry.refs--
|
||||
if l.entry.refs == 0 && l.entry.evicted {
|
||||
err = closeRootCacheEntry(l.entry)
|
||||
}
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *rootCache) getDirLocked(dir string) (*rootCacheEntry, error) {
|
||||
entry := c.entries["."]
|
||||
if dir == "." {
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
path := "."
|
||||
for _, component := range rootCacheComponents(dir) {
|
||||
nextPath := component
|
||||
if path != "." {
|
||||
nextPath = filepath.Join(path, component)
|
||||
}
|
||||
|
||||
nextEntry, ok := c.entries[nextPath]
|
||||
if ok && !nextEntry.evicted {
|
||||
c.touchLocked(nextEntry)
|
||||
entry = nextEntry
|
||||
path = nextPath
|
||||
continue
|
||||
}
|
||||
|
||||
osroot, err := entry.root.OpenRoot(component)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
nextEntry = &rootCacheEntry{
|
||||
path: nextPath,
|
||||
root: NewRoot(osroot),
|
||||
}
|
||||
nextEntry.elem = c.lru.PushFront(nextEntry)
|
||||
c.entries[nextPath] = nextEntry
|
||||
entry = nextEntry
|
||||
path = nextPath
|
||||
c.evictLocked()
|
||||
}
|
||||
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (c *rootCache) touchLocked(entry *rootCacheEntry) {
|
||||
if entry.elem != nil {
|
||||
c.lru.MoveToFront(entry.elem)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *rootCache) evictLocked() {
|
||||
for len(c.entries)-1 > c.maxSize {
|
||||
elem := c.lru.Back()
|
||||
if elem == nil {
|
||||
return
|
||||
}
|
||||
entry := elem.Value.(*rootCacheEntry)
|
||||
c.lru.Remove(elem)
|
||||
entry.elem = nil
|
||||
entry.evicted = true
|
||||
delete(c.entries, entry.path)
|
||||
if entry.refs == 0 {
|
||||
entry.closeErr = closeRootCacheEntry(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func closeRootCacheEntry(entry *rootCacheEntry) error {
|
||||
if entry.closeErr != nil {
|
||||
return entry.closeErr
|
||||
}
|
||||
if err := entry.root.Close(); err != nil {
|
||||
entry.closeErr = errors.WithStack(err)
|
||||
}
|
||||
return entry.closeErr
|
||||
}
|
||||
|
||||
func rootCacheComponents(dir string) []string {
|
||||
if dir == "." {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(dir, string(filepath.Separator))
|
||||
}
|
||||
+339
@@ -0,0 +1,339 @@
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
gofs "io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tonistiigi/fsutil/types"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type RootDiskWriter struct {
|
||||
opt DiskWriterOpt
|
||||
dest Root
|
||||
rootStack *rootStack
|
||||
rootCache *rootCache
|
||||
|
||||
ctx context.Context
|
||||
cancel func()
|
||||
eg *errgroup.Group
|
||||
egCtx context.Context
|
||||
filter FilterFunc
|
||||
dirModTimes map[string]int64
|
||||
}
|
||||
|
||||
func NewRootDiskWriter(ctx context.Context, dest Root, opt DiskWriterOpt) (*RootDiskWriter, error) {
|
||||
if opt.SyncDataCb == nil && opt.AsyncDataCb == nil {
|
||||
return nil, errors.New("no data callback specified")
|
||||
}
|
||||
if opt.SyncDataCb != nil && opt.AsyncDataCb != nil {
|
||||
return nil, errors.New("can't specify both sync and async data callbacks")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
eg, egCtx := errgroup.WithContext(ctx)
|
||||
|
||||
return &RootDiskWriter{
|
||||
opt: opt,
|
||||
dest: dest,
|
||||
rootStack: newRootStack(dest),
|
||||
rootCache: newRootCache(dest, rootCacheDefaultSize),
|
||||
eg: eg,
|
||||
ctx: ctx,
|
||||
egCtx: egCtx,
|
||||
cancel: cancel,
|
||||
filter: opt.Filter,
|
||||
dirModTimes: map[string]int64{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (dw *RootDiskWriter) Wait(ctx context.Context) error {
|
||||
err := dw.eg.Wait()
|
||||
if closeErr := dw.rootCache.Close(); err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
if closeErr := dw.rootStack.Close(); err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gofs.WalkDir(dw.dest.FS(), ".", func(path string, d gofs.DirEntry, prevErr error) error {
|
||||
if prevErr != nil {
|
||||
return prevErr
|
||||
}
|
||||
if !d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if mtime, ok := dw.dirModTimes[path]; ok {
|
||||
return rootChtimes(dw.dest, filepath.FromSlash(path), mtime)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (dw *RootDiskWriter) HandleChange(kind ChangeKind, p string, fi os.FileInfo, err error) (retErr error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-dw.ctx.Done():
|
||||
return dw.ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
dw.cancel()
|
||||
}
|
||||
}()
|
||||
|
||||
destPath := cleanRootPath(p)
|
||||
destRoot, base, err := dw.rootStack.get(destPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if kind == ChangeKindDelete {
|
||||
if dw.filter != nil {
|
||||
var empty types.Stat
|
||||
if ok := dw.filter(p, &empty); !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// todo: no need to validate if diff is trusted but is it always?
|
||||
if err := destRoot.RemoveAll(base); err != nil {
|
||||
return errors.Wrapf(err, "failed to remove: %s", destPath)
|
||||
}
|
||||
if dw.opt.NotifyCb != nil {
|
||||
if err := dw.opt.NotifyCb(kind, p, nil, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
stat, ok := fi.Sys().(*types.Stat)
|
||||
if !ok {
|
||||
return errors.WithStack(&os.PathError{Path: p, Err: syscall.EBADMSG, Op: "change without stat info"})
|
||||
}
|
||||
|
||||
statCopy := stat.Clone()
|
||||
|
||||
if dw.filter != nil {
|
||||
if ok := dw.filter(p, statCopy); !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
rename := true
|
||||
oldFi, err := destRoot.Lstat(base)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
if kind != ChangeKindAdd {
|
||||
return errors.Wrap(err, "modify/rm")
|
||||
}
|
||||
rename = false
|
||||
} else {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
if oldFi != nil && fi.IsDir() && oldFi.IsDir() {
|
||||
if err := rewriteRootMetadata(destRoot, base, statCopy); err != nil {
|
||||
return errors.Wrapf(err, "error setting dir metadata for %s", destPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
newPath := base
|
||||
if rename {
|
||||
newPath = ".tmp." + nextSuffix()
|
||||
}
|
||||
|
||||
isRegularFile := false
|
||||
|
||||
switch {
|
||||
case fi.IsDir():
|
||||
if err := destRoot.Mkdir(newPath, fi.Mode().Perm()); err != nil {
|
||||
if errors.Is(err, syscall.EEXIST) {
|
||||
// we saw a race to create this directory, so try again
|
||||
return dw.HandleChange(kind, p, fi, nil)
|
||||
}
|
||||
return errors.Wrapf(err, "failed to create dir %s", newPath)
|
||||
}
|
||||
dw.dirModTimes[filepath.ToSlash(destPath)] = statCopy.ModTime
|
||||
case fi.Mode()&os.ModeDevice != 0 || fi.Mode()&os.ModeNamedPipe != 0:
|
||||
if err := handleRootTarTypeBlockCharFifo(destRoot, newPath, statCopy); err != nil {
|
||||
return errors.Wrapf(err, "failed to create device %s", newPath)
|
||||
}
|
||||
case fi.Mode()&os.ModeSymlink != 0:
|
||||
if err := destRoot.Symlink(statCopy.Linkname, newPath); err != nil {
|
||||
return errors.Wrapf(err, "failed to symlink %s", newPath)
|
||||
}
|
||||
case statCopy.Linkname != "":
|
||||
linkNewName := destPath
|
||||
if rename {
|
||||
linkNewName = filepath.Join(filepath.Dir(destPath), newPath)
|
||||
}
|
||||
if err := dw.dest.Link(statCopy.Linkname, linkNewName); err != nil {
|
||||
return errors.Wrapf(err, "failed to link %s to %s", newPath, statCopy.Linkname)
|
||||
}
|
||||
default:
|
||||
isRegularFile = true
|
||||
file, err := destRoot.OpenFile(newPath, os.O_CREATE|os.O_WRONLY, fi.Mode().Perm())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to create %s", newPath)
|
||||
}
|
||||
if dw.opt.SyncDataCb != nil {
|
||||
if err := dw.processChange(dw.ctx, ChangeKindAdd, p, fi, file); err != nil {
|
||||
file.Close()
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return errors.Wrapf(err, "failed to close %s", newPath)
|
||||
}
|
||||
}
|
||||
|
||||
if err := rewriteRootMetadata(destRoot, newPath, statCopy); err != nil {
|
||||
return errors.Wrapf(err, "error setting metadata for %s", newPath)
|
||||
}
|
||||
|
||||
if rename {
|
||||
if oldFi.IsDir() != fi.IsDir() {
|
||||
if err := destRoot.RemoveAll(base); err != nil {
|
||||
return errors.Wrapf(err, "failed to remove %s", destPath)
|
||||
}
|
||||
}
|
||||
|
||||
if err := destRoot.Rename(newPath, base); err != nil {
|
||||
return errors.Wrapf(err, "failed to rename %s to %s", newPath, destPath)
|
||||
}
|
||||
}
|
||||
|
||||
if isRegularFile {
|
||||
if dw.opt.AsyncDataCb != nil {
|
||||
dw.requestAsyncFileData(p, destPath, fi, statCopy)
|
||||
}
|
||||
} else {
|
||||
return dw.processChange(dw.ctx, kind, p, fi, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dw *RootDiskWriter) requestAsyncFileData(p, dest string, fi os.FileInfo, st *types.Stat) {
|
||||
// todo: limit worker threads
|
||||
dw.eg.Go(func() error {
|
||||
lease, err := dw.rootCache.get(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer lease.Release()
|
||||
|
||||
w := &rootLazyFileWriter{lease: lease}
|
||||
if err := dw.processChange(dw.egCtx, ChangeKindAdd, p, fi, w); err != nil {
|
||||
w.Close()
|
||||
return err
|
||||
}
|
||||
return rootChtimes(lease.root, lease.base, st.ModTime) // TODO: parent dirs
|
||||
})
|
||||
}
|
||||
|
||||
func (dw *RootDiskWriter) processChange(ctx context.Context, kind ChangeKind, p string, fi os.FileInfo, w io.WriteCloser) error {
|
||||
origw := w
|
||||
var hw *hashedWriter
|
||||
if dw.opt.NotifyCb != nil {
|
||||
var err error
|
||||
if hw, err = newHashWriter(dw.opt.ContentHasher, fi, w); err != nil {
|
||||
return err
|
||||
}
|
||||
w = hw
|
||||
}
|
||||
if origw != nil {
|
||||
fn := dw.opt.SyncDataCb
|
||||
if fn == nil && dw.opt.AsyncDataCb != nil {
|
||||
fn = dw.opt.AsyncDataCb
|
||||
}
|
||||
if err := fn(ctx, p, w); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if hw != nil {
|
||||
hw.Close()
|
||||
}
|
||||
}
|
||||
if hw != nil {
|
||||
return dw.opt.NotifyCb(kind, p, hw, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanRootPath(p string) string {
|
||||
if p == "" {
|
||||
return "."
|
||||
}
|
||||
return filepath.Clean(p)
|
||||
}
|
||||
|
||||
func rootChtimes(root Root, p string, un int64) error {
|
||||
t := time.Unix(0, un)
|
||||
if err := root.Chtimes(p, t, t); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type rootLazyFileWriter struct {
|
||||
lease *rootLease
|
||||
f *os.File
|
||||
fileMode *os.FileMode
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (lfw *rootLazyFileWriter) Write(dt []byte) (int, error) {
|
||||
if lfw.f == nil {
|
||||
file, err := lfw.lease.root.OpenFile(lfw.lease.base, os.O_WRONLY, 0)
|
||||
if os.IsPermission(err) {
|
||||
// retry after chmod
|
||||
fi, er := lfw.lease.root.Stat(lfw.lease.base)
|
||||
if er == nil {
|
||||
mode := fi.Mode()
|
||||
lfw.fileMode = &mode
|
||||
er = lfw.lease.root.Chmod(lfw.lease.base, mode|0222)
|
||||
if er == nil {
|
||||
file, err = lfw.lease.root.OpenFile(lfw.lease.base, os.O_WRONLY, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "failed to open %s", lfw.lease.base)
|
||||
}
|
||||
lfw.f = file
|
||||
}
|
||||
return lfw.f.Write(dt)
|
||||
}
|
||||
|
||||
func (lfw *rootLazyFileWriter) Close() error {
|
||||
if lfw.closed {
|
||||
return nil
|
||||
}
|
||||
lfw.closed = true
|
||||
|
||||
var err error
|
||||
if lfw.f != nil {
|
||||
err = lfw.f.Close()
|
||||
}
|
||||
if err == nil && lfw.fileMode != nil {
|
||||
err = lfw.lease.root.Chmod(lfw.lease.base, *lfw.fileMode)
|
||||
}
|
||||
return err
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
//go:build !windows
|
||||
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tonistiigi/fsutil/types"
|
||||
)
|
||||
|
||||
func rewriteRootMetadata(root Root, p string, stat *types.Stat) error {
|
||||
for key, value := range stat.Xattrs {
|
||||
root.LSetxattr(p, key, value, 0)
|
||||
}
|
||||
|
||||
if err := root.Lchown(p, int(stat.Uid), int(stat.Gid)); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if os.FileMode(stat.Mode)&os.ModeSymlink != 0 {
|
||||
return root.LChtimes(p, time.Unix(0, stat.ModTime))
|
||||
}
|
||||
if err := root.Chmod(p, os.FileMode(stat.Mode)); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := rootChtimes(root, p, stat.ModTime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
//go:build windows
|
||||
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/tonistiigi/fsutil/types"
|
||||
)
|
||||
|
||||
func rewriteRootMetadata(root Root, p string, stat *types.Stat) error {
|
||||
return root.LChtimes(p, time.Unix(0, stat.ModTime))
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type rootStack struct {
|
||||
items []rootStackItem
|
||||
}
|
||||
|
||||
type rootStackItem struct {
|
||||
path string
|
||||
root Root
|
||||
}
|
||||
|
||||
func newRootStack(root Root) *rootStack {
|
||||
return &rootStack{
|
||||
items: []rootStackItem{{path: ".", root: root}},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *rootStack) get(path string) (Root, string, error) {
|
||||
path = cleanRootPath(path)
|
||||
dir := filepath.Dir(path)
|
||||
base := filepath.Base(path)
|
||||
|
||||
for {
|
||||
top := s.items[len(s.items)-1]
|
||||
if top.path == dir {
|
||||
return top.root, base, nil
|
||||
}
|
||||
if rel, ok := rootStackRel(top.path, dir); ok {
|
||||
osroot, err := top.root.OpenRoot(rel)
|
||||
if err != nil {
|
||||
return nil, "", errors.WithStack(err)
|
||||
}
|
||||
root := NewRoot(osroot)
|
||||
s.items = append(s.items, rootStackItem{path: dir, root: root})
|
||||
return root, base, nil
|
||||
}
|
||||
if len(s.items) == 1 {
|
||||
return nil, "", errors.WithStack(&os.PathError{Op: "openroot", Path: dir, Err: os.ErrNotExist})
|
||||
}
|
||||
if err := s.pop(); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *rootStack) Close() error {
|
||||
var err error
|
||||
for len(s.items) > 1 {
|
||||
if err2 := s.pop(); err == nil {
|
||||
err = err2
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *rootStack) pop() error {
|
||||
i := len(s.items) - 1
|
||||
item := s.items[i]
|
||||
s.items = s.items[:i]
|
||||
return item.root.Close()
|
||||
}
|
||||
|
||||
// rootStackRel returns target relative to base when target is below base.
|
||||
func rootStackRel(base, target string) (string, bool) {
|
||||
if base == "." {
|
||||
if target == "." {
|
||||
return "", false
|
||||
}
|
||||
return target, true
|
||||
}
|
||||
rel, err := filepath.Rel(base, target)
|
||||
if err != nil || rel == "." || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
|
||||
return "", false
|
||||
}
|
||||
return rel, true
|
||||
}
|
||||
+5
-5
@@ -14,15 +14,15 @@ import (
|
||||
)
|
||||
|
||||
var bufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
buf := make([]byte, 32*1<<10)
|
||||
return &buf
|
||||
},
|
||||
}
|
||||
|
||||
type Stream interface {
|
||||
RecvMsg(interface{}) error
|
||||
SendMsg(m interface{}) error
|
||||
RecvMsg(any) error
|
||||
SendMsg(m any) error
|
||||
Context() context.Context
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ func (s *sender) run(ctx context.Context) error {
|
||||
return err
|
||||
})
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
for range 4 {
|
||||
g.Go(func() error {
|
||||
for h := range s.sendpipeline {
|
||||
select {
|
||||
@@ -210,7 +210,7 @@ type syncStream struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (ss *syncStream) SendMsg(m interface{}) error {
|
||||
func (ss *syncStream) SendMsg(m any) error {
|
||||
ss.mu.Lock()
|
||||
err := ss.Stream.SendMsg(m)
|
||||
ss.mu.Unlock()
|
||||
|
||||
+1
-4
@@ -43,10 +43,7 @@ func loadXattr(origpath string, stat *types.Stat) error {
|
||||
}
|
||||
|
||||
func skipXattr(key string) bool {
|
||||
if strings.HasPrefix(key, xattrApplePrefix) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return strings.HasPrefix(key, xattrApplePrefix)
|
||||
}
|
||||
|
||||
func setUnixOpt(fi os.FileInfo, stat *types.Stat, path string, seenFiles map[uint64]string) {
|
||||
|
||||
+41
-73
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v3.11.4
|
||||
// source: github.com/tonistiigi/fsutil/types/stat.proto
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -22,30 +23,27 @@ const (
|
||||
)
|
||||
|
||||
type Stat struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
|
||||
Mode uint32 `protobuf:"varint,2,opt,name=mode,proto3" json:"mode,omitempty"`
|
||||
Uid uint32 `protobuf:"varint,3,opt,name=uid,proto3" json:"uid,omitempty"`
|
||||
Gid uint32 `protobuf:"varint,4,opt,name=gid,proto3" json:"gid,omitempty"`
|
||||
Size int64 `protobuf:"varint,5,opt,name=size,proto3" json:"size,omitempty"`
|
||||
ModTime int64 `protobuf:"varint,6,opt,name=modTime,proto3" json:"modTime,omitempty"`
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
|
||||
Mode uint32 `protobuf:"varint,2,opt,name=mode,proto3" json:"mode,omitempty"`
|
||||
Uid uint32 `protobuf:"varint,3,opt,name=uid,proto3" json:"uid,omitempty"`
|
||||
Gid uint32 `protobuf:"varint,4,opt,name=gid,proto3" json:"gid,omitempty"`
|
||||
Size int64 `protobuf:"varint,5,opt,name=size,proto3" json:"size,omitempty"`
|
||||
ModTime int64 `protobuf:"varint,6,opt,name=modTime,proto3" json:"modTime,omitempty"`
|
||||
// int32 typeflag = 7;
|
||||
Linkname string `protobuf:"bytes,7,opt,name=linkname,proto3" json:"linkname,omitempty"`
|
||||
Devmajor int64 `protobuf:"varint,8,opt,name=devmajor,proto3" json:"devmajor,omitempty"`
|
||||
Devminor int64 `protobuf:"varint,9,opt,name=devminor,proto3" json:"devminor,omitempty"`
|
||||
Xattrs map[string][]byte `protobuf:"bytes,10,rep,name=xattrs,proto3" json:"xattrs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
Linkname string `protobuf:"bytes,7,opt,name=linkname,proto3" json:"linkname,omitempty"`
|
||||
Devmajor int64 `protobuf:"varint,8,opt,name=devmajor,proto3" json:"devmajor,omitempty"`
|
||||
Devminor int64 `protobuf:"varint,9,opt,name=devminor,proto3" json:"devminor,omitempty"`
|
||||
Xattrs map[string][]byte `protobuf:"bytes,10,rep,name=xattrs,proto3" json:"xattrs,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Stat) Reset() {
|
||||
*x = Stat{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_github_com_tonistiigi_fsutil_types_stat_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_github_com_tonistiigi_fsutil_types_stat_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Stat) String() string {
|
||||
@@ -56,7 +54,7 @@ func (*Stat) ProtoMessage() {}
|
||||
|
||||
func (x *Stat) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_github_com_tonistiigi_fsutil_types_stat_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -143,54 +141,39 @@ func (x *Stat) GetXattrs() map[string][]byte {
|
||||
|
||||
var File_github_com_tonistiigi_fsutil_types_stat_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_github_com_tonistiigi_fsutil_types_stat_proto_rawDesc = []byte{
|
||||
0x0a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x6f, 0x6e,
|
||||
0x69, 0x73, 0x74, 0x69, 0x69, 0x67, 0x69, 0x2f, 0x66, 0x73, 0x75, 0x74, 0x69, 0x6c, 0x2f, 0x74,
|
||||
0x79, 0x70, 0x65, 0x73, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
|
||||
0x0c, 0x66, 0x73, 0x75, 0x74, 0x69, 0x6c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x1a, 0x33, 0x67,
|
||||
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x74,
|
||||
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2f, 0x76, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x78, 0x74, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x22, 0xc7, 0x02, 0x0a, 0x04, 0x53, 0x74, 0x61, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70,
|
||||
0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12,
|
||||
0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x6d,
|
||||
0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d,
|
||||
0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x67, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01,
|
||||
0x28, 0x0d, 0x52, 0x03, 0x67, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18,
|
||||
0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d,
|
||||
0x6f, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d, 0x6f,
|
||||
0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x6e, 0x61, 0x6d,
|
||||
0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x6e, 0x61, 0x6d,
|
||||
0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x76, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x18, 0x08, 0x20,
|
||||
0x01, 0x28, 0x03, 0x52, 0x08, 0x64, 0x65, 0x76, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x1a, 0x0a,
|
||||
0x08, 0x64, 0x65, 0x76, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52,
|
||||
0x08, 0x64, 0x65, 0x76, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x36, 0x0a, 0x06, 0x78, 0x61, 0x74,
|
||||
0x74, 0x72, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x66, 0x73, 0x75, 0x74,
|
||||
0x69, 0x6c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x2e, 0x58, 0x61,
|
||||
0x74, 0x74, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x78, 0x61, 0x74, 0x74, 0x72,
|
||||
0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x58, 0x61, 0x74, 0x74, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
|
||||
0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
|
||||
0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x24, 0x5a, 0x22,
|
||||
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x6f, 0x6e, 0x69, 0x73,
|
||||
0x74, 0x69, 0x69, 0x67, 0x69, 0x2f, 0x66, 0x73, 0x75, 0x74, 0x69, 0x6c, 0x2f, 0x74, 0x79, 0x70,
|
||||
0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
const file_github_com_tonistiigi_fsutil_types_stat_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"-github.com/tonistiigi/fsutil/types/stat.proto\x12\ffsutil.types\x1a3github.com/planetscale/vtprotobuf/vtproto/ext.proto\"\xc7\x02\n" +
|
||||
"\x04Stat\x12\x12\n" +
|
||||
"\x04path\x18\x01 \x01(\tR\x04path\x12\x12\n" +
|
||||
"\x04mode\x18\x02 \x01(\rR\x04mode\x12\x10\n" +
|
||||
"\x03uid\x18\x03 \x01(\rR\x03uid\x12\x10\n" +
|
||||
"\x03gid\x18\x04 \x01(\rR\x03gid\x12\x12\n" +
|
||||
"\x04size\x18\x05 \x01(\x03R\x04size\x12\x18\n" +
|
||||
"\amodTime\x18\x06 \x01(\x03R\amodTime\x12\x1a\n" +
|
||||
"\blinkname\x18\a \x01(\tR\blinkname\x12\x1a\n" +
|
||||
"\bdevmajor\x18\b \x01(\x03R\bdevmajor\x12\x1a\n" +
|
||||
"\bdevminor\x18\t \x01(\x03R\bdevminor\x126\n" +
|
||||
"\x06xattrs\x18\n" +
|
||||
" \x03(\v2\x1e.fsutil.types.Stat.XattrsEntryR\x06xattrs\x1a9\n" +
|
||||
"\vXattrsEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\fR\x05value:\x028\x01B$Z\"github.com/tonistiigi/fsutil/typesb\x06proto3"
|
||||
|
||||
var (
|
||||
file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescOnce sync.Once
|
||||
file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescData = file_github_com_tonistiigi_fsutil_types_stat_proto_rawDesc
|
||||
file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescGZIP() []byte {
|
||||
file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescOnce.Do(func() {
|
||||
file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescData = protoimpl.X.CompressGZIP(file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescData)
|
||||
file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_github_com_tonistiigi_fsutil_types_stat_proto_rawDesc), len(file_github_com_tonistiigi_fsutil_types_stat_proto_rawDesc)))
|
||||
})
|
||||
return file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_github_com_tonistiigi_fsutil_types_stat_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_github_com_tonistiigi_fsutil_types_stat_proto_goTypes = []interface{}{
|
||||
var file_github_com_tonistiigi_fsutil_types_stat_proto_goTypes = []any{
|
||||
(*Stat)(nil), // 0: fsutil.types.Stat
|
||||
nil, // 1: fsutil.types.Stat.XattrsEntry
|
||||
}
|
||||
@@ -208,25 +191,11 @@ func file_github_com_tonistiigi_fsutil_types_stat_proto_init() {
|
||||
if File_github_com_tonistiigi_fsutil_types_stat_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_github_com_tonistiigi_fsutil_types_stat_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Stat); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_github_com_tonistiigi_fsutil_types_stat_proto_rawDesc,
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_github_com_tonistiigi_fsutil_types_stat_proto_rawDesc), len(file_github_com_tonistiigi_fsutil_types_stat_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
@@ -237,7 +206,6 @@ func file_github_com_tonistiigi_fsutil_types_stat_proto_init() {
|
||||
MessageInfos: file_github_com_tonistiigi_fsutil_types_stat_proto_msgTypes,
|
||||
}.Build()
|
||||
File_github_com_tonistiigi_fsutil_types_stat_proto = out.File
|
||||
file_github_com_tonistiigi_fsutil_types_stat_proto_rawDesc = nil
|
||||
file_github_com_tonistiigi_fsutil_types_stat_proto_goTypes = nil
|
||||
file_github_com_tonistiigi_fsutil_types_stat_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
+34
-64
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v3.11.4
|
||||
// source: github.com/tonistiigi/fsutil/types/wire.proto
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -77,23 +78,20 @@ func (Packet_PacketType) EnumDescriptor() ([]byte, []int) {
|
||||
}
|
||||
|
||||
type Packet struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type Packet_PacketType `protobuf:"varint,1,opt,name=type,proto3,enum=fsutil.types.Packet_PacketType" json:"type,omitempty"`
|
||||
Stat *Stat `protobuf:"bytes,2,opt,name=stat,proto3" json:"stat,omitempty"`
|
||||
ID uint32 `protobuf:"varint,3,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Type Packet_PacketType `protobuf:"varint,1,opt,name=type,proto3,enum=fsutil.types.Packet_PacketType" json:"type,omitempty"`
|
||||
Stat *Stat `protobuf:"bytes,2,opt,name=stat,proto3" json:"stat,omitempty"`
|
||||
ID uint32 `protobuf:"varint,3,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Packet) Reset() {
|
||||
*x = Packet{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_github_com_tonistiigi_fsutil_types_wire_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_github_com_tonistiigi_fsutil_types_wire_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Packet) String() string {
|
||||
@@ -104,7 +102,7 @@ func (*Packet) ProtoMessage() {}
|
||||
|
||||
func (x *Packet) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_github_com_tonistiigi_fsutil_types_wire_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -149,53 +147,40 @@ func (x *Packet) GetData() []byte {
|
||||
|
||||
var File_github_com_tonistiigi_fsutil_types_wire_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_github_com_tonistiigi_fsutil_types_wire_proto_rawDesc = []byte{
|
||||
0x0a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x6f, 0x6e,
|
||||
0x69, 0x73, 0x74, 0x69, 0x69, 0x67, 0x69, 0x2f, 0x66, 0x73, 0x75, 0x74, 0x69, 0x6c, 0x2f, 0x74,
|
||||
0x79, 0x70, 0x65, 0x73, 0x2f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
|
||||
0x0c, 0x66, 0x73, 0x75, 0x74, 0x69, 0x6c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x1a, 0x33, 0x67,
|
||||
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x74,
|
||||
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2f, 0x76, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x78, 0x74, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x1a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74,
|
||||
0x6f, 0x6e, 0x69, 0x73, 0x74, 0x69, 0x69, 0x67, 0x69, 0x2f, 0x66, 0x73, 0x75, 0x74, 0x69, 0x6c,
|
||||
0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x22, 0xef, 0x01, 0x0a, 0x06, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x33, 0x0a, 0x04,
|
||||
0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x66, 0x73, 0x75,
|
||||
0x74, 0x69, 0x6c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74,
|
||||
0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70,
|
||||
0x65, 0x12, 0x26, 0x0a, 0x04, 0x73, 0x74, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x12, 0x2e, 0x66, 0x73, 0x75, 0x74, 0x69, 0x6c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53,
|
||||
0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x49, 0x44, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74,
|
||||
0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x5e, 0x0a,
|
||||
0x0a, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x50,
|
||||
0x41, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a,
|
||||
0x50, 0x41, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b,
|
||||
0x50, 0x41, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x10, 0x02, 0x12, 0x0e, 0x0a,
|
||||
0x0a, 0x50, 0x41, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x46, 0x49, 0x4e, 0x10, 0x03, 0x12, 0x0e, 0x0a,
|
||||
0x0a, 0x50, 0x41, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x10, 0x04, 0x3a, 0x04, 0xa8,
|
||||
0xa6, 0x1f, 0x01, 0x42, 0x24, 0x5a, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
|
||||
0x6d, 0x2f, 0x74, 0x6f, 0x6e, 0x69, 0x73, 0x74, 0x69, 0x69, 0x67, 0x69, 0x2f, 0x66, 0x73, 0x75,
|
||||
0x74, 0x69, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x33,
|
||||
}
|
||||
const file_github_com_tonistiigi_fsutil_types_wire_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"-github.com/tonistiigi/fsutil/types/wire.proto\x12\ffsutil.types\x1a3github.com/planetscale/vtprotobuf/vtproto/ext.proto\x1a-github.com/tonistiigi/fsutil/types/stat.proto\"\xef\x01\n" +
|
||||
"\x06Packet\x123\n" +
|
||||
"\x04type\x18\x01 \x01(\x0e2\x1f.fsutil.types.Packet.PacketTypeR\x04type\x12&\n" +
|
||||
"\x04stat\x18\x02 \x01(\v2\x12.fsutil.types.StatR\x04stat\x12\x0e\n" +
|
||||
"\x02ID\x18\x03 \x01(\rR\x02ID\x12\x12\n" +
|
||||
"\x04data\x18\x04 \x01(\fR\x04data\"^\n" +
|
||||
"\n" +
|
||||
"PacketType\x12\x0f\n" +
|
||||
"\vPACKET_STAT\x10\x00\x12\x0e\n" +
|
||||
"\n" +
|
||||
"PACKET_REQ\x10\x01\x12\x0f\n" +
|
||||
"\vPACKET_DATA\x10\x02\x12\x0e\n" +
|
||||
"\n" +
|
||||
"PACKET_FIN\x10\x03\x12\x0e\n" +
|
||||
"\n" +
|
||||
"PACKET_ERR\x10\x04:\x04\xa8\xa6\x1f\x01B$Z\"github.com/tonistiigi/fsutil/typesb\x06proto3"
|
||||
|
||||
var (
|
||||
file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescOnce sync.Once
|
||||
file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescData = file_github_com_tonistiigi_fsutil_types_wire_proto_rawDesc
|
||||
file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescGZIP() []byte {
|
||||
file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescOnce.Do(func() {
|
||||
file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescData = protoimpl.X.CompressGZIP(file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescData)
|
||||
file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_github_com_tonistiigi_fsutil_types_wire_proto_rawDesc), len(file_github_com_tonistiigi_fsutil_types_wire_proto_rawDesc)))
|
||||
})
|
||||
return file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_github_com_tonistiigi_fsutil_types_wire_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_github_com_tonistiigi_fsutil_types_wire_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_github_com_tonistiigi_fsutil_types_wire_proto_goTypes = []interface{}{
|
||||
var file_github_com_tonistiigi_fsutil_types_wire_proto_goTypes = []any{
|
||||
(Packet_PacketType)(0), // 0: fsutil.types.Packet.PacketType
|
||||
(*Packet)(nil), // 1: fsutil.types.Packet
|
||||
(*Stat)(nil), // 2: fsutil.types.Stat
|
||||
@@ -216,25 +201,11 @@ func file_github_com_tonistiigi_fsutil_types_wire_proto_init() {
|
||||
return
|
||||
}
|
||||
file_github_com_tonistiigi_fsutil_types_stat_proto_init()
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_github_com_tonistiigi_fsutil_types_wire_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Packet); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_github_com_tonistiigi_fsutil_types_wire_proto_rawDesc,
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_github_com_tonistiigi_fsutil_types_wire_proto_rawDesc), len(file_github_com_tonistiigi_fsutil_types_wire_proto_rawDesc)),
|
||||
NumEnums: 1,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
@@ -246,7 +217,6 @@ func file_github_com_tonistiigi_fsutil_types_wire_proto_init() {
|
||||
MessageInfos: file_github_com_tonistiigi_fsutil_types_wire_proto_msgTypes,
|
||||
}.Build()
|
||||
File_github_com_tonistiigi_fsutil_types_wire_proto = out.File
|
||||
file_github_com_tonistiigi_fsutil_types_wire_proto_rawDesc = nil
|
||||
file_github_com_tonistiigi_fsutil_types_wire_proto_goTypes = nil
|
||||
file_github_com_tonistiigi_fsutil_types_wire_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
+2
-9
@@ -67,8 +67,8 @@ func (v *Validator) HandleChange(kind ChangeKind, p string, fi os.FileInfo, err
|
||||
|
||||
func ComparePath(p1, p2 string) int {
|
||||
// byte-by-byte comparison to be compatible with str<>str
|
||||
min := min(len(p1), len(p2))
|
||||
for i := 0; i < min; i++ {
|
||||
n := min(len(p1), len(p2))
|
||||
for i := range n {
|
||||
switch {
|
||||
case p1[i] == p2[i]:
|
||||
continue
|
||||
@@ -80,10 +80,3 @@ func ComparePath(p1, p2 string) int {
|
||||
}
|
||||
return len(p1) - len(p2)
|
||||
}
|
||||
|
||||
func min(x, y int) int {
|
||||
if x < y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user