Merge pull request #45993 from thaJeztah/update_fsutils

vendor: github.com/containerd/continuity v0.4.1, tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb
This commit is contained in:
Akihiro Suda 2023-07-18 04:12:07 +09:00 committed by GitHub
commit 29eb0be134
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 354 additions and 113 deletions

View file

@ -27,7 +27,7 @@ require (
github.com/container-orchestrated-devices/container-device-interface v0.6.0
github.com/containerd/cgroups/v3 v3.0.2
github.com/containerd/containerd v1.6.21
github.com/containerd/continuity v0.3.0
github.com/containerd/continuity v0.4.1
github.com/containerd/fifo v1.1.0
github.com/containerd/typeurl/v2 v2.1.0
github.com/coreos/go-systemd/v22 v22.5.0
@ -173,7 +173,7 @@ require (
github.com/spdx/tools-golang v0.3.1-0.20230104082527-d6f58551be3f // indirect
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
github.com/tinylib/msgp v1.1.8 // indirect
github.com/tonistiigi/fsutil v0.0.0-20230105215944-fb433841cbfa // indirect
github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb // indirect
github.com/tonistiigi/go-actions-cache v0.0.0-20220404170428-0bdeb6e1eac7 // indirect
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.6 // indirect

View file

@ -384,8 +384,8 @@ github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cE
github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y=
github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ=
github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM=
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM=
github.com/containerd/continuity v0.4.1 h1:wQnVrjIyQ8vhU2sgOiL5T07jo+ouqc2bnKsv5/EqGhU=
github.com/containerd/continuity v0.4.1/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
@ -1397,8 +1397,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1
github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85/go.mod h1:a7cilN64dG941IOXfhJhlH0qB92hxJ9A1ewrdUmJ6xo=
github.com/tonistiigi/fsutil v0.0.0-20230105215944-fb433841cbfa h1:XOFp/3aBXlqmOFAg3r6e0qQjPnK5I970LilqX+Is1W8=
github.com/tonistiigi/fsutil v0.0.0-20230105215944-fb433841cbfa/go.mod h1:AvLEd1LEIl64G2Jpgwo7aVV5lGH0ePcKl0ygGIHNYl8=
github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb h1:uUe8rNyVXM8moActoBol6Xf6xX2GMr7SosR2EywMvGg=
github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb/go.mod h1:SxX/oNQ/ag6Vaoli547ipFK9J7BZn5JqJG0JE8lf8bA=
github.com/tonistiigi/go-actions-cache v0.0.0-20220404170428-0bdeb6e1eac7 h1:8eY6m1mjgyB8XySUR7WvebTM8D/Vs86jLJzD/Tw7zkc=
github.com/tonistiigi/go-actions-cache v0.0.0-20220404170428-0bdeb6e1eac7/go.mod h1:qqvyZqkfwkoJuPU/bw61bItaoO0SJ8YSW0vSVRRvsRg=
github.com/tonistiigi/go-archvariant v1.0.0 h1:5LC1eDWiBNflnTF1prCiX09yfNHIxDC/aukdhCdTyb0=

View file

@ -56,7 +56,7 @@ func WriteFile(r Driver, filename string, data []byte, perm os.FileMode) error {
return nil
}
// ReadDir works the same as ioutil.ReadDir with the Driver abstraction
// ReadDir works the same as os.ReadDir with the Driver abstraction
func ReadDir(r Driver, dirname string) ([]os.FileInfo, error) {
f, err := r.Open(dirname)
if err != nil {

View file

@ -18,7 +18,6 @@ package fs
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
@ -111,7 +110,7 @@ func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) er
}
}
fis, err := ioutil.ReadDir(src)
entries, err := os.ReadDir(src)
if err != nil {
return fmt.Errorf("failed to read %s: %w", src, err)
}
@ -124,18 +123,23 @@ func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) er
return fmt.Errorf("failed to copy xattrs: %w", err)
}
for _, fi := range fis {
source := filepath.Join(src, fi.Name())
target := filepath.Join(dst, fi.Name())
for _, entry := range entries {
source := filepath.Join(src, entry.Name())
target := filepath.Join(dst, entry.Name())
fileInfo, err := entry.Info()
if err != nil {
return fmt.Errorf("failed to get file info for %s: %w", entry.Name(), err)
}
switch {
case fi.IsDir():
case entry.IsDir():
if err := copyDirectory(target, source, inodes, o); err != nil {
return err
}
continue
case (fi.Mode() & os.ModeType) == 0:
link, err := getLinkSource(target, fi, inodes)
case (fileInfo.Mode() & os.ModeType) == 0:
link, err := getLinkSource(target, fileInfo, inodes)
if err != nil {
return fmt.Errorf("failed to get hardlink: %w", err)
}
@ -146,7 +150,7 @@ func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) er
} else if err := CopyFile(target, source); err != nil {
return fmt.Errorf("failed to copy files: %w", err)
}
case (fi.Mode() & os.ModeSymlink) == os.ModeSymlink:
case (fileInfo.Mode() & os.ModeSymlink) == os.ModeSymlink:
link, err := os.Readlink(source)
if err != nil {
return fmt.Errorf("failed to read link: %s: %w", source, err)
@ -154,18 +158,18 @@ func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) er
if err := os.Symlink(link, target); err != nil {
return fmt.Errorf("failed to create symlink: %s: %w", target, err)
}
case (fi.Mode() & os.ModeDevice) == os.ModeDevice,
(fi.Mode() & os.ModeNamedPipe) == os.ModeNamedPipe,
(fi.Mode() & os.ModeSocket) == os.ModeSocket:
if err := copyIrregular(target, fi); err != nil {
case (fileInfo.Mode() & os.ModeDevice) == os.ModeDevice,
(fileInfo.Mode() & os.ModeNamedPipe) == os.ModeNamedPipe,
(fileInfo.Mode() & os.ModeSocket) == os.ModeSocket:
if err := copyIrregular(target, fileInfo); err != nil {
return fmt.Errorf("failed to create irregular file: %w", err)
}
default:
logrus.Warnf("unsupported mode: %s: %s", source, fi.Mode())
logrus.Warnf("unsupported mode: %s: %s", source, fileInfo.Mode())
continue
}
if err := copyFileInfo(fi, source, target); err != nil {
if err := copyFileInfo(fileInfo, source, target); err != nil {
return fmt.Errorf("failed to copy file info: %w", err)
}
@ -180,6 +184,10 @@ func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) er
// CopyFile copies the source file to the target.
// The most efficient means of copying is used for the platform.
func CopyFile(target, source string) error {
return copyFile(target, source)
}
func openAndCopyFile(target, source string) error {
src, err := os.Open(source)
if err != nil {
return fmt.Errorf("failed to open source %s: %w", source, err)

View file

@ -0,0 +1,35 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fs
import (
"errors"
"fmt"
"golang.org/x/sys/unix"
)
func copyFile(target, source string) error {
if err := unix.Clonefile(source, target, unix.CLONE_NOFOLLOW); err != nil {
if !errors.Is(err, unix.ENOTSUP) && !errors.Is(err, unix.EXDEV) {
return fmt.Errorf("clonefile failed: %w", err)
}
return openAndCopyFile(target, source)
}
return nil
}

View file

@ -0,0 +1,22 @@
//go:build !darwin
// +build !darwin
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fs
var copyFile = openAndCopyFile

View file

@ -23,6 +23,7 @@ import (
"fmt"
"io"
"os"
"runtime"
"syscall"
"github.com/containerd/continuity/sysx"
@ -71,6 +72,10 @@ func copyFileContent(dst, src *os.File) error {
func copyXAttrs(dst, src string, excludes map[string]struct{}, errorHandler XAttrErrorHandler) error {
xattrKeys, err := sysx.LListxattr(src)
if err != nil {
if os.IsPermission(err) && runtime.GOOS == "darwin" {
// On darwin, character devices do not permit listing xattrs
return nil
}
e := fmt.Errorf("failed to list xattrs on %s: %w", src, err)
if errorHandler != nil {
e = errorHandler(dst, src, "", e)

View file

@ -49,7 +49,6 @@ func copyFileInfo(fi os.FileInfo, src, name string) error {
secInfo, err := windows.GetNamedSecurityInfo(
src, windows.SE_FILE_OBJECT,
windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION)
if err != nil {
return err
}
@ -68,7 +67,6 @@ func copyFileInfo(fi os.FileInfo, src, name string) error {
name, windows.SE_FILE_OBJECT,
windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION,
sid, nil, dacl, nil); err != nil {
return err
}
return nil

View file

@ -80,12 +80,13 @@ type ChangeFunc func(ChangeKind, string, os.FileInfo, error) error
//
// The change callback is called by the order of path names and
// should be appliable in that order.
// Due to this apply ordering, the following is true
// - Removed directory trees only create a single change for the root
// directory removed. Remaining changes are implied.
// - A directory which is modified to become a file will not have
// delete entries for sub-path items, their removal is implied
// by the removal of the parent directory.
//
// Due to this apply ordering, the following is true
// - Removed directory trees only create a single change for the root
// directory removed. Remaining changes are implied.
// - A directory which is modified to become a file will not have
// delete entries for sub-path items, their removal is implied
// by the removal of the parent directory.
//
// Opaque directories will not be treated specially and each file
// removed from the base directory will show up as a removal.

View file

@ -21,14 +21,13 @@ package fs
import (
"fmt"
"io/ioutil"
"os"
"syscall"
"unsafe"
)
func locateDummyIfEmpty(path string) (string, error) {
children, err := ioutil.ReadDir(path)
children, err := os.ReadDir(path)
if err != nil {
return "", err
}

View file

@ -28,10 +28,11 @@ import (
// blocksUnitSize is the unit used by `st_blocks` in `stat` in bytes.
// See https://man7.org/linux/man-pages/man2/stat.2.html
// st_blocks
// This field indicates the number of blocks allocated to the
// file, in 512-byte units. (This may be smaller than
// st_size/512 when the file has holes.)
//
// st_blocks
// This field indicates the number of blocks allocated to the
// file, in 512-byte units. (This may be smaller than
// st_size/512 when the file has holes.)
const blocksUnitSize = 512
type inode struct {
@ -48,7 +49,6 @@ func newInode(stat *syscall.Stat_t) inode {
}
func diskUsage(ctx context.Context, roots ...string) (Usage, error) {
var (
size int64
inodes = map[inode]struct{}{} // expensive!

View file

@ -26,9 +26,7 @@ import (
)
func diskUsage(ctx context.Context, roots ...string) (Usage, error) {
var (
size int64
)
var size int64
// TODO(stevvooe): Support inodes (or equivalent) for windows.
@ -57,9 +55,7 @@ func diskUsage(ctx context.Context, roots ...string) (Usage, error) {
}
func diffUsage(ctx context.Context, a, b string) (Usage, error) {
var (
size int64
)
var size int64
if err := Changes(ctx, a, b, func(kind ChangeKind, _ string, fi os.FileInfo, err error) error {
if err != nil {

View file

@ -25,9 +25,7 @@ import (
"path/filepath"
)
var (
errTooManyLinks = errors.New("too many links")
)
var errTooManyLinks = errors.New("too many links")
type currentPath struct {
path string

View file

@ -1,5 +1,5 @@
#syntax=docker/dockerfile:1
ARG GO_VERSION=1.18
ARG GO_VERSION=1.20
FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.1.0 AS xx

View file

@ -13,53 +13,83 @@ const (
seTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege"
)
func getUIDGID(fi os.FileInfo) (uid, gid int) {
return 0, 0
}
func getFileSecurityInfo(name string) (*windows.SID, *windows.ACL, error) {
secInfo, err := windows.GetNamedSecurityInfo(
name, windows.SE_FILE_OBJECT,
windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION)
if err != nil {
return nil, nil, errors.Wrap(err, "fetching security info")
}
sid, _, err := secInfo.Owner()
if err != nil {
return nil, nil, errors.Wrap(err, "fetching owner SID")
}
dacl, _, err := secInfo.DACL()
if err != nil {
return nil, nil, errors.Wrap(err, "fetching dacl")
}
return sid, dacl, nil
}
func (c *copier) copyFileInfo(fi os.FileInfo, src, name string) error {
if err := os.Chmod(name, fi.Mode()); err != nil {
return errors.Wrapf(err, "failed to chmod %s", name)
}
// Copy file ownership and ACL
// We need SeRestorePrivilege and SeTakeOwnershipPrivilege in order
// to restore security info on a file, especially if we're trying to
// apply security info which includes SIDs not necessarily present on
// the host.
privileges := []string{winio.SeRestorePrivilege, seTakeOwnershipPrivilege}
if err := winio.EnableProcessPrivileges(privileges); err != nil {
return err
}
defer winio.DisableProcessPrivileges(privileges)
secInfo, err := windows.GetNamedSecurityInfo(
src, windows.SE_FILE_OBJECT,
windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION)
sid, dacl, err := getFileSecurityInfo(src)
if err != nil {
return err
return errors.Wrap(err, "getting file info")
}
dacl, _, err := secInfo.DACL()
if err != nil {
return err
if c.chown != nil {
// Use the defined chowner.
usr := &User{SID: sid.String()}
if err := Chown(name, usr, c.chown); err != nil {
return errors.Wrapf(err, "failed to chown %s", name)
}
return nil
} else {
// Copy file ownership and ACL from the source file.
// We need SeRestorePrivilege and SeTakeOwnershipPrivilege in order
// to restore security info on a file, especially if we're trying to
// apply security info which includes SIDs not necessarily present on
// the host.
privileges := []string{winio.SeRestorePrivilege, seTakeOwnershipPrivilege}
if err := winio.EnableProcessPrivileges(privileges); err != nil {
return err
}
defer winio.DisableProcessPrivileges(privileges)
if err := windows.SetNamedSecurityInfo(
name, windows.SE_FILE_OBJECT,
windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION,
sid, nil, dacl, nil); err != nil {
return err
}
}
sid, _, err := secInfo.Owner()
if err != nil {
return err
}
if err := windows.SetNamedSecurityInfo(
name, windows.SE_FILE_OBJECT,
windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION,
sid, nil, dacl, nil); err != nil {
if err := c.copyFileTimestamp(fi, name); err != nil {
return err
}
return nil
}
func (c *copier) copyFileTimestamp(fi os.FileInfo, name string) error {
// TODO: copy windows specific metadata
if c.utime != nil {
return Utimes(name, c.utime)
}
if fi.Mode()&os.ModeSymlink == 0 {
if err := os.Chtimes(name, fi.ModTime(), fi.ModTime()); err != nil {
return errors.Wrap(err, "changing mtime")
}
}
return nil
}

View file

@ -28,6 +28,15 @@ func fixRootDirectory(p string) string {
}
func Utimes(p string, tm *time.Time) error {
info, err := os.Lstat(p)
if err != nil {
return errors.Wrap(err, "fetching file info")
}
if tm != nil && info.Mode()&os.ModeSymlink == 0 {
if err := os.Chtimes(p, *tm, *tm); err != nil {
return errors.Wrap(err, "changing times")
}
}
return nil
}
@ -39,22 +48,14 @@ func Chown(p string, old *User, fn Chowner) error {
if err != nil {
return errors.WithStack(err)
}
userSIDstring := user.SID
var userSIDstring string
if user != nil && user.SID != "" {
userSIDstring = user.SID
}
if userSIDstring == "" {
userSIDstring = containerAdministratorSidString
}
// Copy file ownership and ACL
// We need SeRestorePrivilege and SeTakeOwnershipPrivilege in order
// to restore security info on a file, especially if we're trying to
// apply security info which includes SIDs not necessarily present on
// the host.
privileges := []string{winio.SeRestorePrivilege, seTakeOwnershipPrivilege}
if err := winio.EnableProcessPrivileges(privileges); err != nil {
return err
}
defer winio.DisableProcessPrivileges(privileges)
sidPtr, err := syscall.UTF16PtrFromString(userSIDstring)
if err != nil {
@ -81,13 +82,22 @@ func Chown(p string, old *User, fn Chowner) error {
return fmt.Errorf("adding acls: %w", err)
}
if err := windows.SetNamedSecurityInfo(
p, windows.SE_FILE_OBJECT,
windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION,
userSID, nil, newAcl, nil); err != nil {
// Copy file ownership and ACL
// We need SeRestorePrivilege and SeTakeOwnershipPrivilege in order
// to restore security info on a file, especially if we're trying to
// apply security info which includes SIDs not necessarily present on
// the host.
privileges := []string{winio.SeRestorePrivilege, seTakeOwnershipPrivilege}
err = winio.RunWithPrivileges(privileges, func() error {
if err := windows.SetNamedSecurityInfo(
p, windows.SE_FILE_OBJECT,
windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION,
userSID, nil, newAcl, nil); err != nil {
return err
}
return err
}
return nil
})
return nil
return err
}

View file

@ -162,6 +162,10 @@ func (dw *DiskWriter) HandleChange(kind ChangeKind, p string, fi os.FileInfo, er
switch {
case fi.IsDir():
if err := os.Mkdir(newPath, fi.Mode()); 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[destPath] = statCopy.ModTime
@ -188,7 +192,6 @@ func (dw *DiskWriter) HandleChange(kind ChangeKind, p string, fi os.FileInfo, er
file.Close()
return err
}
break
}
if err := file.Close(); err != nil {
return errors.Wrapf(err, "failed to close %s", newPath)
@ -205,7 +208,8 @@ func (dw *DiskWriter) HandleChange(kind ChangeKind, p string, fi os.FileInfo, er
return errors.Wrapf(err, "failed to remove %s", destPath)
}
}
if err := os.Rename(newPath, destPath); err != nil {
if err := renameFile(newPath, destPath); err != nil {
return errors.Wrapf(err, "failed to rename %s to %s", newPath, destPath)
}
}

View file

@ -51,3 +51,10 @@ func handleTarTypeBlockCharFifo(path string, stat *types.Stat) error {
}
return nil
}
func renameFile(src, dst string) error {
if err := os.Rename(src, dst); err != nil {
return errors.Wrapf(err, "failed to rename %s to %s", src, dst)
}
return nil
}

View file

@ -1,8 +1,15 @@
//go:build windows
// +build windows
package fsutil
import (
"fmt"
iofs "io/fs"
"os"
"syscall"
"github.com/Microsoft/go-winio"
"github.com/pkg/errors"
"github.com/tonistiigi/fsutil/types"
)
@ -16,3 +23,75 @@ func rewriteMetadata(p string, stat *types.Stat) error {
func handleTarTypeBlockCharFifo(path string, stat *types.Stat) error {
return errors.New("Not implemented on windows")
}
func getFileHandle(path string, info iofs.FileInfo) (syscall.Handle, error) {
p, err := syscall.UTF16PtrFromString(path)
if err != nil {
return 0, errors.Wrap(err, "converting string to UTF-16")
}
attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
if info.Mode()&os.ModeSymlink != 0 {
// Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink.
// See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted
attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT
}
h, err := syscall.CreateFile(p, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
if err != nil {
return 0, errors.Wrap(err, "getting file handle")
}
return h, nil
}
func readlink(path string, info iofs.FileInfo) ([]byte, error) {
h, err := getFileHandle(path, info)
if err != nil {
return nil, errors.Wrap(err, "getting file handle")
}
defer syscall.CloseHandle(h)
rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
var bytesReturned uint32
err = syscall.DeviceIoControl(h, syscall.FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil)
if err != nil {
return nil, errors.Wrap(err, "sending I/O control command")
}
return rdbbuf[:bytesReturned], nil
}
func getReparsePoint(path string, info iofs.FileInfo) (*winio.ReparsePoint, error) {
target, err := readlink(path, info)
if err != nil {
return nil, errors.Wrap(err, "fetching link")
}
rp, err := winio.DecodeReparsePoint(target)
if err != nil {
return nil, errors.Wrap(err, "decoding reparse point")
}
return rp, nil
}
func renameFile(src, dst string) error {
info, err := os.Lstat(dst)
if err != nil {
if !os.IsNotExist(err) {
return errors.Wrap(err, "getting file info")
}
}
if info != nil && info.Mode()&os.ModeSymlink != 0 {
dstInfoRp, err := getReparsePoint(dst, info)
if err != nil {
return errors.Wrap(err, "getting reparse point")
}
if dstInfoRp.IsMountPoint {
return fmt.Errorf("%s is a mount point", dst)
}
if err := os.Remove(dst); err != nil {
return errors.Wrapf(err, "removing %s", dst)
}
}
if err := os.Rename(src, dst); err != nil {
return errors.Wrapf(err, "failed to rename %s to %s", src, dst)
}
return nil
}

View file

@ -1,5 +1,5 @@
variable "GO_VERSION" {
default = "1.18"
default = "1.20"
}
group "default" {

View file

@ -19,7 +19,7 @@ func FollowLinks(root string, paths []string) ([]string, error) {
}
res := make([]string, 0, len(r.resolved))
for r := range r.resolved {
res = append(res, r)
res = append(res, filepath.ToSlash(r))
}
sort.Strings(res)
return dedupePaths(res), nil
@ -31,6 +31,12 @@ type symlinkResolver struct {
}
func (r *symlinkResolver) append(p string) error {
if runtime.GOOS == "windows" && filepath.IsAbs(filepath.FromSlash(p)) {
absParts := strings.SplitN(p, ":", 2)
if len(absParts) == 2 {
p = absParts[1]
}
}
p = filepath.Join(".", p)
current := "."
for {
@ -41,7 +47,6 @@ func (r *symlinkResolver) append(p string) error {
if err != nil {
return err
}
p = ""
if len(parts) == 2 {
p = parts[1]
@ -76,7 +81,7 @@ func (r *symlinkResolver) readSymlink(p string, allowWildcard bool) ([]string, e
if allowWildcard && containsWildcards(base) {
fis, err := os.ReadDir(filepath.Dir(realPath))
if err != nil {
if errors.Is(err, os.ErrNotExist) {
if isNotFound(err) {
return nil, nil
}
return nil, errors.Wrap(err, "readdir")
@ -96,7 +101,7 @@ func (r *symlinkResolver) readSymlink(p string, allowWildcard bool) ([]string, e
fi, err := os.Lstat(realPath)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
if isNotFound(err) {
return nil, nil
}
return nil, errors.WithStack(err)
@ -139,7 +144,7 @@ func dedupePaths(in []string) []string {
if s == "." {
return nil
}
if strings.HasPrefix(s, last+string(filepath.Separator)) {
if strings.HasPrefix(s, last+"/") {
continue
}
out = append(out, s)

View file

@ -0,0 +1,14 @@
//go:build !windows
// +build !windows
package fsutil
import (
"os"
"github.com/pkg/errors"
)
func isNotFound(err error) bool {
return errors.Is(err, os.ErrNotExist)
}

View file

@ -0,0 +1,15 @@
package fsutil
import (
"os"
"github.com/pkg/errors"
"golang.org/x/sys/windows"
)
func isNotFound(err error) bool {
if errors.Is(err, os.ErrNotExist) || errors.Is(err, windows.ERROR_INVALID_NAME) {
return true
}
return false
}

View file

@ -2,6 +2,7 @@ package fsutil
import (
"context"
gofs "io/fs"
"os"
"path/filepath"
"strings"
@ -47,11 +48,11 @@ func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) err
if err != nil {
return errors.WithStack(&os.PathError{Op: "resolve", Path: root, Err: err})
}
fi, err := os.Stat(root)
rootFI, err := os.Stat(root)
if err != nil {
return errors.WithStack(err)
}
if !fi.IsDir() {
if !rootFI.IsDir() {
return errors.WithStack(&os.PathError{Op: "walk", Path: root, Err: syscall.ENOTDIR})
}
@ -126,7 +127,7 @@ func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) err
var parentDirs []visitedDir
seenFiles := make(map[uint64]string)
return filepath.Walk(root, func(path string, fi os.FileInfo, walkErr error) (retErr error) {
return filepath.WalkDir(root, func(path string, dirEntry gofs.DirEntry, walkErr error) (retErr error) {
defer func() {
if retErr != nil && isNotExist(retErr) {
retErr = filepath.SkipDir
@ -146,9 +147,10 @@ func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) err
var (
dir visitedDir
isDir bool
fi gofs.FileInfo
)
if fi != nil {
isDir = fi.IsDir()
if dirEntry != nil {
isDir = dirEntry.IsDir()
}
if includeMatcher != nil || excludeMatcher != nil {
@ -161,6 +163,11 @@ func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) err
}
if isDir {
fi, err = dirEntry.Info()
if err != nil {
return err
}
dir = visitedDir{
fi: fi,
path: path,
@ -268,6 +275,14 @@ func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) err
dir.calledFn = true
// The FileInfo might have already been read further up.
if fi == nil {
fi, err = dirEntry.Info()
if err != nil {
return err
}
}
stat, err := mkstat(origpath, path, fi, seenFiles)
if err != nil {
return err

8
vendor/modules.txt vendored
View file

@ -307,8 +307,8 @@ github.com/containerd/containerd/snapshots/proxy
github.com/containerd/containerd/sys
github.com/containerd/containerd/sys/reaper
github.com/containerd/containerd/version
# github.com/containerd/continuity v0.3.0
## explicit; go 1.17
# github.com/containerd/continuity v0.4.1
## explicit; go 1.19
github.com/containerd/continuity/devices
github.com/containerd/continuity/driver
github.com/containerd/continuity/fs
@ -969,8 +969,8 @@ github.com/syndtr/gocapability/capability
# github.com/tinylib/msgp v1.1.8
## explicit; go 1.15
github.com/tinylib/msgp/msgp
# github.com/tonistiigi/fsutil v0.0.0-20230105215944-fb433841cbfa
## explicit; go 1.18
# github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb
## explicit; go 1.19
github.com/tonistiigi/fsutil
github.com/tonistiigi/fsutil/copy
github.com/tonistiigi/fsutil/types