mirror of
https://github.com/pterodactyl/wings.git
synced 2025-12-11 03:02:08 -06:00
ufs: improve error handling
Signed-off-by: Matthew Penner <me@matthewp.io>
This commit is contained in:
parent
25966e7838
commit
407b783aa5
@ -7,6 +7,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
iofs "io/fs"
|
iofs "io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
@ -48,9 +49,9 @@ type PathError = iofs.PathError
|
|||||||
// SyscallError records an error from a specific system call.
|
// SyscallError records an error from a specific system call.
|
||||||
type SyscallError = os.SyscallError
|
type SyscallError = os.SyscallError
|
||||||
|
|
||||||
// NewSyscallError returns, as an error, a new SyscallError
|
// NewSyscallError returns, as an error, a new [*os.SyscallError] with the
|
||||||
// with the given system call name and error details.
|
// given system call name and error details. As a convenience, if err is nil,
|
||||||
// As a convenience, if err is nil, NewSyscallError returns nil.
|
// [NewSyscallError] returns nil.
|
||||||
func NewSyscallError(syscall string, err error) error {
|
func NewSyscallError(syscall string, err error) error {
|
||||||
return os.NewSyscallError(syscall, err)
|
return os.NewSyscallError(syscall, err)
|
||||||
}
|
}
|
||||||
@ -61,62 +62,122 @@ func convertErrorType(err error) error {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var pErr *PathError
|
var pErr *PathError
|
||||||
switch {
|
if errors.As(err, &pErr) {
|
||||||
case errors.As(err, &pErr):
|
if errno, ok := pErr.Err.(syscall.Errno); ok {
|
||||||
switch {
|
return errnoToPathError(errno, pErr.Op, pErr.Path)
|
||||||
// File exists
|
}
|
||||||
case errors.Is(pErr.Err, unix.EEXIST):
|
return pErr
|
||||||
return &PathError{
|
}
|
||||||
Op: pErr.Op,
|
|
||||||
Path: pErr.Path,
|
// If the error wasn't already a path error and is a errno, wrap it with
|
||||||
Err: ErrExist,
|
// details that we can use to know there is something wrong with our
|
||||||
}
|
// error wrapping somewhere.
|
||||||
// Is a directory
|
var errno syscall.Errno
|
||||||
case errors.Is(pErr.Err, unix.EISDIR):
|
if errors.As(err, &errno) {
|
||||||
return &PathError{
|
return &PathError{
|
||||||
Op: pErr.Op,
|
Op: "!(UNKNOWN)",
|
||||||
Path: pErr.Path,
|
Path: "!(UNKNOWN)",
|
||||||
Err: ErrIsDirectory,
|
Err: err,
|
||||||
}
|
|
||||||
// Not a directory
|
|
||||||
case errors.Is(pErr.Err, unix.ENOTDIR):
|
|
||||||
return &PathError{
|
|
||||||
Op: pErr.Op,
|
|
||||||
Path: pErr.Path,
|
|
||||||
Err: ErrNotDirectory,
|
|
||||||
}
|
|
||||||
// No such file or directory
|
|
||||||
case errors.Is(pErr.Err, unix.ENOENT):
|
|
||||||
return &PathError{
|
|
||||||
Op: pErr.Op,
|
|
||||||
Path: pErr.Path,
|
|
||||||
Err: ErrNotExist,
|
|
||||||
}
|
|
||||||
// Operation not permitted
|
|
||||||
case errors.Is(pErr.Err, unix.EPERM):
|
|
||||||
return &PathError{
|
|
||||||
Op: pErr.Op,
|
|
||||||
Path: pErr.Path,
|
|
||||||
Err: ErrPermission,
|
|
||||||
}
|
|
||||||
// Invalid cross-device link
|
|
||||||
case errors.Is(pErr.Err, unix.EXDEV):
|
|
||||||
return &PathError{
|
|
||||||
Op: pErr.Op,
|
|
||||||
Path: pErr.Path,
|
|
||||||
Err: ErrBadPathResolution,
|
|
||||||
}
|
|
||||||
// Too many levels of symbolic links
|
|
||||||
case errors.Is(pErr.Err, unix.ELOOP):
|
|
||||||
return &PathError{
|
|
||||||
Op: pErr.Op,
|
|
||||||
Path: pErr.Path,
|
|
||||||
Err: ErrBadPathResolution,
|
|
||||||
}
|
|
||||||
// TODO: EROFS
|
|
||||||
// TODO: ENOTEMPTY
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensurePathError ensures that err is a PathError. The op and path arguments
|
||||||
|
// are only used of the error isn't already a PathError.
|
||||||
|
func ensurePathError(err error, op, path string) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the error is already a PathError.
|
||||||
|
var pErr *PathError
|
||||||
|
if errors.As(err, &pErr) {
|
||||||
|
// If underlying error is a errno, convert it.
|
||||||
|
//
|
||||||
|
// DO NOT USE `errors.As` or whatever here, the error will either be
|
||||||
|
// an errno, or it will be wrapped already.
|
||||||
|
if errno, ok := pErr.Err.(syscall.Errno); ok {
|
||||||
|
return errnoToPathError(errno, pErr.Op, pErr.Path)
|
||||||
|
}
|
||||||
|
// Return the PathError as-is without modification.
|
||||||
|
return pErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the error is directly an errno, convert it to a PathError.
|
||||||
|
var errno syscall.Errno
|
||||||
|
if errors.As(err, &errno) {
|
||||||
|
return errnoToPathError(errno, op, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise just wrap it as a PathError without any additional changes.
|
||||||
|
return &PathError{
|
||||||
|
Op: op,
|
||||||
|
Path: path,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// errnoToPathError converts an errno into a proper path error.
|
||||||
|
func errnoToPathError(err syscall.Errno, op, path string) error {
|
||||||
|
switch err {
|
||||||
|
// File exists
|
||||||
|
case unix.EEXIST:
|
||||||
|
return &PathError{
|
||||||
|
Op: op,
|
||||||
|
Path: path,
|
||||||
|
Err: ErrExist,
|
||||||
|
}
|
||||||
|
// Is a directory
|
||||||
|
case unix.EISDIR:
|
||||||
|
return &PathError{
|
||||||
|
Op: op,
|
||||||
|
Path: path,
|
||||||
|
Err: ErrIsDirectory,
|
||||||
|
}
|
||||||
|
// Not a directory
|
||||||
|
case unix.ENOTDIR:
|
||||||
|
return &PathError{
|
||||||
|
Op: op,
|
||||||
|
Path: path,
|
||||||
|
Err: ErrNotDirectory,
|
||||||
|
}
|
||||||
|
// No such file or directory
|
||||||
|
case unix.ENOENT:
|
||||||
|
return &PathError{
|
||||||
|
Op: op,
|
||||||
|
Path: path,
|
||||||
|
Err: ErrNotExist,
|
||||||
|
}
|
||||||
|
// Operation not permitted
|
||||||
|
case unix.EPERM:
|
||||||
|
return &PathError{
|
||||||
|
Op: op,
|
||||||
|
Path: path,
|
||||||
|
Err: ErrPermission,
|
||||||
|
}
|
||||||
|
// Invalid cross-device link
|
||||||
|
case unix.EXDEV:
|
||||||
|
return &PathError{
|
||||||
|
Op: op,
|
||||||
|
Path: path,
|
||||||
|
Err: ErrBadPathResolution,
|
||||||
|
}
|
||||||
|
// Too many levels of symbolic links
|
||||||
|
case unix.ELOOP:
|
||||||
|
return &PathError{
|
||||||
|
Op: op,
|
||||||
|
Path: path,
|
||||||
|
Err: ErrBadPathResolution,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return &PathError{
|
||||||
|
Op: op,
|
||||||
|
Path: path,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -7,8 +7,12 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Quota .
|
// Quota is a wrapper around [*UnixFS] that provides the ability to limit the
|
||||||
// TODO: document
|
// disk usage of the filesystem.
|
||||||
|
//
|
||||||
|
// NOTE: this is not a full complete quota filesystem, it provides utilities for
|
||||||
|
// tracking and checking the usage of the filesystem. The only operation that is
|
||||||
|
// automatically accounted against the quota are file deletions.
|
||||||
type Quota struct {
|
type Quota struct {
|
||||||
// fs is the underlying filesystem that runs the actual I/O operations.
|
// fs is the underlying filesystem that runs the actual I/O operations.
|
||||||
*UnixFS
|
*UnixFS
|
||||||
@ -28,8 +32,7 @@ type Quota struct {
|
|||||||
usage atomic.Int64
|
usage atomic.Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewQuota .
|
// NewQuota creates a new Quota filesystem using an existing UnixFS and a limit.
|
||||||
// TODO: document
|
|
||||||
func NewQuota(fs *UnixFS, limit int64) *Quota {
|
func NewQuota(fs *UnixFS, limit int64) *Quota {
|
||||||
qfs := Quota{UnixFS: fs}
|
qfs := Quota{UnixFS: fs}
|
||||||
qfs.limit.Store(limit)
|
qfs.limit.Store(limit)
|
||||||
@ -105,6 +108,9 @@ func (fs *Quota) CanFit(size int64) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove removes the named file or (empty) directory.
|
||||||
|
//
|
||||||
|
// If there is an error, it will be of type [*PathError].
|
||||||
func (fs *Quota) Remove(name string) error {
|
func (fs *Quota) Remove(name string) error {
|
||||||
// For information on why this interface is used here, check its
|
// For information on why this interface is used here, check its
|
||||||
// documentation.
|
// documentation.
|
||||||
@ -129,7 +135,7 @@ func (fs *Quota) Remove(name string) error {
|
|||||||
// it encounters. If the path does not exist, RemoveAll
|
// it encounters. If the path does not exist, RemoveAll
|
||||||
// returns nil (no error).
|
// returns nil (no error).
|
||||||
//
|
//
|
||||||
// If there is an error, it will be of type *PathError.
|
// If there is an error, it will be of type [*PathError].
|
||||||
func (fs *Quota) RemoveAll(name string) error {
|
func (fs *Quota) RemoveAll(name string) error {
|
||||||
name, err := fs.unsafePath(name)
|
name, err := fs.unsafePath(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -81,7 +81,16 @@ func (fs *UnixFS) Chmod(name string, mode FileMode) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return convertErrorType(unix.Fchmodat(dirfd, name, uint32(mode), 0))
|
return fs.fchmodat("chmod", dirfd, name, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chmodat is like Chmod but it takes a dirfd and name instead of a full path.
|
||||||
|
func (fs *UnixFS) Chmodat(dirfd int, name string, mode FileMode) error {
|
||||||
|
return fs.fchmodat("chmodat", dirfd, name, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *UnixFS) fchmodat(op string, dirfd int, name string, mode FileMode) error {
|
||||||
|
return ensurePathError(unix.Fchmodat(dirfd, name, uint32(mode), 0), op, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chown changes the numeric uid and gid of the named file.
|
// Chown changes the numeric uid and gid of the named file.
|
||||||
@ -93,7 +102,7 @@ func (fs *UnixFS) Chmod(name string, mode FileMode) error {
|
|||||||
// On Windows or Plan 9, Chown always returns the syscall.EWINDOWS or
|
// On Windows or Plan 9, Chown always returns the syscall.EWINDOWS or
|
||||||
// EPLAN9 error, wrapped in *PathError.
|
// EPLAN9 error, wrapped in *PathError.
|
||||||
func (fs *UnixFS) Chown(name string, uid, gid int) error {
|
func (fs *UnixFS) Chown(name string, uid, gid int) error {
|
||||||
return fs.fchown(name, uid, gid, 0)
|
return ensurePathError(fs.fchown(name, uid, gid, 0), "chown", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lchown changes the numeric uid and gid of the named file.
|
// Lchown changes the numeric uid and gid of the named file.
|
||||||
@ -106,7 +115,7 @@ func (fs *UnixFS) Chown(name string, uid, gid int) error {
|
|||||||
func (fs *UnixFS) Lchown(name string, uid, gid int) error {
|
func (fs *UnixFS) Lchown(name string, uid, gid int) error {
|
||||||
// With AT_SYMLINK_NOFOLLOW, Fchownat acts like Lchown but allows us to
|
// With AT_SYMLINK_NOFOLLOW, Fchownat acts like Lchown but allows us to
|
||||||
// pass a dirfd.
|
// pass a dirfd.
|
||||||
return fs.fchown(name, uid, gid, AT_SYMLINK_NOFOLLOW)
|
return ensurePathError(fs.fchown(name, uid, gid, AT_SYMLINK_NOFOLLOW), "lchown", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fchown is a re-usable Fchownat syscall used by Chown and Lchown.
|
// fchown is a re-usable Fchownat syscall used by Chown and Lchown.
|
||||||
@ -116,19 +125,19 @@ func (fs *UnixFS) fchown(name string, uid, gid, flags int) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return convertErrorType(unix.Fchownat(dirfd, name, uid, gid, flags))
|
return unix.Fchownat(dirfd, name, uid, gid, flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chownat is like Chown but allows passing an existing directory file
|
// Chownat is like Chown but allows passing an existing directory file
|
||||||
// descriptor rather than needing to resolve one.
|
// descriptor rather than needing to resolve one.
|
||||||
func (fs *UnixFS) Chownat(dirfd int, name string, uid, gid int) error {
|
func (fs *UnixFS) Chownat(dirfd int, name string, uid, gid int) error {
|
||||||
return convertErrorType(unix.Fchownat(dirfd, name, uid, gid, 0))
|
return ensurePathError(unix.Fchownat(dirfd, name, uid, gid, 0), "chownat", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lchownat is like Lchown but allows passing an existing directory file
|
// Lchownat is like Lchown but allows passing an existing directory file
|
||||||
// descriptor rather than needing to resolve one.
|
// descriptor rather than needing to resolve one.
|
||||||
func (fs *UnixFS) Lchownat(dirfd int, name string, uid, gid int) error {
|
func (fs *UnixFS) Lchownat(dirfd int, name string, uid, gid int) error {
|
||||||
return convertErrorType(unix.Fchownat(dirfd, name, uid, gid, AT_SYMLINK_NOFOLLOW))
|
return ensurePathError(unix.Fchownat(dirfd, name, uid, gid, AT_SYMLINK_NOFOLLOW), "lchownat", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chtimes changes the access and modification times of the named
|
// Chtimes changes the access and modification times of the named
|
||||||
@ -160,11 +169,9 @@ func (fs *UnixFS) Chtimesat(dirfd int, name string, atime, mtime time.Time) erro
|
|||||||
}
|
}
|
||||||
set(0, atime)
|
set(0, atime)
|
||||||
set(1, mtime)
|
set(1, mtime)
|
||||||
|
|
||||||
// This does support `AT_SYMLINK_NOFOLLOW` as well if needed.
|
// This does support `AT_SYMLINK_NOFOLLOW` as well if needed.
|
||||||
if err := unix.UtimesNanoAt(dirfd, name, utimes[0:], 0); err != nil {
|
return ensurePathError(unix.UtimesNanoAt(dirfd, name, utimes[0:], 0), "chtimes", name)
|
||||||
return convertErrorType(&PathError{Op: "chtimes", Path: name, Err: err})
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create creates or truncates the named file. If the file already exists,
|
// Create creates or truncates the named file. If the file already exists,
|
||||||
@ -188,11 +195,15 @@ func (fs *UnixFS) Mkdir(name string, mode FileMode) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return fs.Mkdirat(dirfd, name, mode)
|
return fs.mkdirat("mkdir", dirfd, name, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *UnixFS) Mkdirat(dirfd int, name string, mode FileMode) error {
|
func (fs *UnixFS) Mkdirat(dirfd int, name string, mode FileMode) error {
|
||||||
return convertErrorType(unix.Mkdirat(dirfd, name, uint32(mode)))
|
return fs.mkdirat("mkdirat", dirfd, name, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *UnixFS) mkdirat(op string, dirfd int, name string, mode FileMode) error {
|
||||||
|
return ensurePathError(unix.Mkdirat(dirfd, name, uint32(mode)), op, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MkdirAll creates a directory named path, along with any necessary
|
// MkdirAll creates a directory named path, along with any necessary
|
||||||
@ -313,7 +324,7 @@ func (fs *UnixFS) RemoveStat(name string) (FileInfo, error) {
|
|||||||
err = fs.unlinkat(dirfd, name, 0)
|
err = fs.unlinkat(dirfd, name, 0)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, convertErrorType(&PathError{Op: "remove", Path: name, Err: err})
|
return s, ensurePathError(err, "rename", name)
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
@ -362,7 +373,7 @@ func (fs *UnixFS) Remove(name string) error {
|
|||||||
if err1 != unix.ENOTDIR {
|
if err1 != unix.ENOTDIR {
|
||||||
err = err1
|
err = err1
|
||||||
}
|
}
|
||||||
return convertErrorType(&PathError{Op: "remove", Path: name, Err: err})
|
return ensurePathError(err, "remove", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveAll removes path and any children it contains.
|
// RemoveAll removes path and any children it contains.
|
||||||
@ -377,6 +388,7 @@ func (fs *UnixFS) RemoveAll(name string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// While removeAll internally checks this, I want to make sure we check it
|
// While removeAll internally checks this, I want to make sure we check it
|
||||||
// and return the proper error so our tests can ensure that this will never
|
// and return the proper error so our tests can ensure that this will never
|
||||||
// be a possibility.
|
// be a possibility.
|
||||||
@ -387,9 +399,29 @@ func (fs *UnixFS) RemoveAll(name string) error {
|
|||||||
Err: ErrBadPathResolution,
|
Err: ErrBadPathResolution,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fs.removeAll(name)
|
return fs.removeAll(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveContents recursively removes the contents of name.
|
||||||
|
//
|
||||||
|
// It removes everything it can but returns the first error
|
||||||
|
// it encounters. If the path does not exist, RemoveContents
|
||||||
|
// returns nil (no error).
|
||||||
|
//
|
||||||
|
// If there is an error, it will be of type [*PathError].
|
||||||
|
func (fs *UnixFS) RemoveContents(name string) error {
|
||||||
|
name, err := fs.unsafePath(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlike RemoveAll, we don't remove `name` itself, only it's contents.
|
||||||
|
// So there is no need to check for a name of `.` here.
|
||||||
|
|
||||||
|
return fs.removeContents(name)
|
||||||
|
}
|
||||||
|
|
||||||
func (fs *UnixFS) unlinkat(dirfd int, name string, flags int) error {
|
func (fs *UnixFS) unlinkat(dirfd int, name string, flags int) error {
|
||||||
return ignoringEINTR(func() error {
|
return ignoringEINTR(func() error {
|
||||||
return unix.Unlinkat(dirfd, name, flags)
|
return unix.Unlinkat(dirfd, name, flags)
|
||||||
@ -418,11 +450,11 @@ func (fs *UnixFS) Rename(oldpath, newpath string) error {
|
|||||||
// While unix.Renameat ends up throwing a "device or resource busy" error,
|
// While unix.Renameat ends up throwing a "device or resource busy" error,
|
||||||
// that doesn't mean we are protecting the system properly.
|
// that doesn't mean we are protecting the system properly.
|
||||||
if oldname == "." {
|
if oldname == "." {
|
||||||
return convertErrorType(&PathError{
|
return &PathError{
|
||||||
Op: "rename",
|
Op: "rename",
|
||||||
Path: oldname,
|
Path: oldname,
|
||||||
Err: ErrBadPathResolution,
|
Err: ErrBadPathResolution,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
// Stat the old target to return proper errors.
|
// Stat the old target to return proper errors.
|
||||||
if _, err := fs.Lstatat(olddirfd, oldname); err != nil {
|
if _, err := fs.Lstatat(olddirfd, oldname); err != nil {
|
||||||
@ -433,11 +465,11 @@ func (fs *UnixFS) Rename(oldpath, newpath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
closeFd2()
|
closeFd2()
|
||||||
if !errors.Is(err, ErrNotExist) {
|
if !errors.Is(err, ErrNotExist) {
|
||||||
return convertErrorType(err)
|
return err
|
||||||
}
|
}
|
||||||
var pathErr *PathError
|
var pathErr *PathError
|
||||||
if !errors.As(err, &pathErr) {
|
if !errors.As(err, &pathErr) {
|
||||||
return convertErrorType(err)
|
return err
|
||||||
}
|
}
|
||||||
if err := fs.MkdirAll(pathErr.Path, 0o755); err != nil {
|
if err := fs.MkdirAll(pathErr.Path, 0o755); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -455,25 +487,28 @@ func (fs *UnixFS) Rename(oldpath, newpath string) error {
|
|||||||
// While unix.Renameat ends up throwing a "device or resource busy" error,
|
// While unix.Renameat ends up throwing a "device or resource busy" error,
|
||||||
// that doesn't mean we are protecting the system properly.
|
// that doesn't mean we are protecting the system properly.
|
||||||
if newname == "." {
|
if newname == "." {
|
||||||
return convertErrorType(&PathError{
|
return &PathError{
|
||||||
Op: "rename",
|
Op: "rename",
|
||||||
Path: newname,
|
Path: newname,
|
||||||
Err: ErrBadPathResolution,
|
Err: ErrBadPathResolution,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
// Stat the new target to return proper errors.
|
// Stat the new target to return proper errors.
|
||||||
_, err = fs.Lstatat(newdirfd, newname)
|
_, err = fs.Lstatat(newdirfd, newname)
|
||||||
switch {
|
switch {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
return convertErrorType(&PathError{
|
return &PathError{
|
||||||
Op: "rename",
|
Op: "rename",
|
||||||
Path: newname,
|
Path: newname,
|
||||||
Err: ErrExist,
|
Err: ErrExist,
|
||||||
})
|
}
|
||||||
case !errors.Is(err, ErrNotExist):
|
case !errors.Is(err, ErrNotExist):
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return unix.Renameat(olddirfd, oldname, newdirfd, newname)
|
if err := unix.Renameat(olddirfd, oldname, newdirfd, newname); err != nil {
|
||||||
|
return &LinkError{Op: "rename", Old: oldpath, New: newpath, Err: err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stat returns a FileInfo describing the named file.
|
// Stat returns a FileInfo describing the named file.
|
||||||
@ -527,7 +562,7 @@ func (fs *UnixFS) _fstatat(op string, dirfd int, name string, flags int) (FileIn
|
|||||||
if err := ignoringEINTR(func() error {
|
if err := ignoringEINTR(func() error {
|
||||||
return unix.Fstatat(dirfd, name, &s.sys, flags)
|
return unix.Fstatat(dirfd, name, &s.sys, flags)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, &PathError{Op: op, Path: name, Err: err}
|
return nil, ensurePathError(err, op, name)
|
||||||
}
|
}
|
||||||
fillFileStatFromSys(&s, name)
|
fillFileStatFromSys(&s, name)
|
||||||
return &s, nil
|
return &s, nil
|
||||||
@ -563,23 +598,42 @@ func (fs *UnixFS) Touch(path string, flag int, mode FileMode) (File, error) {
|
|||||||
if flag&O_CREATE == 0 {
|
if flag&O_CREATE == 0 {
|
||||||
flag |= O_CREATE
|
flag |= O_CREATE
|
||||||
}
|
}
|
||||||
dirfd, name, closeFd, err := fs.safePath(path)
|
dirfd, name, closeFd, err, _ := fs.TouchPath(path)
|
||||||
defer closeFd()
|
defer closeFd()
|
||||||
if err == nil {
|
if err != nil {
|
||||||
return fs.OpenFileat(dirfd, name, flag, mode)
|
|
||||||
}
|
|
||||||
if !errors.Is(err, ErrNotExist) {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return fs.OpenFileat(dirfd, name, flag, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TouchPath is like SafePath except that it will create any missing directories
|
||||||
|
// in the path. Unlike SafePath, TouchPath returns an additional boolean which
|
||||||
|
// indicates whether the parent directories already existed, this is intended to
|
||||||
|
// be used as a way to know if the final destination could already exist.
|
||||||
|
func (fs *UnixFS) TouchPath(path string) (int, string, func(), error, bool) {
|
||||||
|
dirfd, name, closeFd, err := fs.safePath(path)
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return dirfd, name, closeFd, nil, true
|
||||||
|
case !errors.Is(err, ErrNotExist):
|
||||||
|
return dirfd, name, closeFd, err, false
|
||||||
|
}
|
||||||
|
|
||||||
var pathErr *PathError
|
var pathErr *PathError
|
||||||
if !errors.As(err, &pathErr) {
|
if !errors.As(err, &pathErr) {
|
||||||
return nil, err
|
return dirfd, name, closeFd, err, false
|
||||||
}
|
}
|
||||||
if err := fs.MkdirAll(pathErr.Path, 0o755); err != nil {
|
if err := fs.MkdirAll(pathErr.Path, 0o755); err != nil {
|
||||||
return nil, err
|
return dirfd, name, closeFd, err, false
|
||||||
}
|
}
|
||||||
// Try to open the file one more time after creating its parent directories.
|
|
||||||
return fs.OpenFile(path, flag, mode)
|
// Close the previous file descriptor since we are going to be opening
|
||||||
|
// a new one.
|
||||||
|
closeFd()
|
||||||
|
|
||||||
|
// Run safe path again now that the parent directories have been created.
|
||||||
|
dirfd, name, closeFd, err = fs.safePath(path)
|
||||||
|
return dirfd, name, closeFd, err, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// WalkDir walks the file tree rooted at root, calling fn for each file or
|
// WalkDir walks the file tree rooted at root, calling fn for each file or
|
||||||
@ -621,7 +675,7 @@ func (fs *UnixFS) openat(dirfd int, name string, flag int, mode FileMode) (int,
|
|||||||
if err == unix.EINTR {
|
if err == unix.EINTR {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return -1, convertErrorType(err)
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are using openat2, we don't need the additional security checks.
|
// If we are using openat2, we don't need the additional security checks.
|
||||||
@ -646,24 +700,29 @@ func (fs *UnixFS) openat(dirfd int, name string, flag int, mode FileMode) (int,
|
|||||||
if !errors.As(err, &pErr) {
|
if !errors.As(err, &pErr) {
|
||||||
return fd, fmt.Errorf("failed to evaluate symlink: %w", convertErrorType(err))
|
return fd, fmt.Errorf("failed to evaluate symlink: %w", convertErrorType(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the final path to whatever directory or path didn't exist while
|
||||||
|
// recursing any symlinks.
|
||||||
finalPath = pErr.Path
|
finalPath = pErr.Path
|
||||||
|
// Ensure the error is wrapped correctly.
|
||||||
|
err = convertErrorType(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the path is within our root.
|
// Check if the path is within our root.
|
||||||
if !fs.unsafeIsPathInsideOfBase(finalPath) {
|
if !fs.unsafeIsPathInsideOfBase(finalPath) {
|
||||||
return fd, convertErrorType(&PathError{
|
op := "openat"
|
||||||
Op: "openat",
|
if fs.useOpenat2 {
|
||||||
|
op = "openat2"
|
||||||
|
}
|
||||||
|
return fd, &PathError{
|
||||||
|
Op: op,
|
||||||
Path: name,
|
Path: name,
|
||||||
Err: ErrBadPathResolution,
|
Err: ErrBadPathResolution,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This handles any hanging errors.
|
// Return the file descriptor and any potential error.
|
||||||
if err != nil {
|
return fd, err
|
||||||
return fd, convertErrorType(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fd, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// _openat is a wrapper around unix.Openat. This method should never be directly
|
// _openat is a wrapper around unix.Openat. This method should never be directly
|
||||||
@ -681,11 +740,11 @@ func (fs *UnixFS) _openat(dirfd int, name string, flag int, mode uint32) (int, e
|
|||||||
case err == nil:
|
case err == nil:
|
||||||
return fd, nil
|
return fd, nil
|
||||||
case err == unix.EINTR:
|
case err == unix.EINTR:
|
||||||
return -1, err
|
return fd, err
|
||||||
case err == unix.EAGAIN:
|
case err == unix.EAGAIN:
|
||||||
return -1, err
|
return fd, err
|
||||||
default:
|
default:
|
||||||
return -1, &PathError{Op: "openat", Path: name, Err: err}
|
return fd, ensurePathError(err, "openat", name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -720,11 +779,11 @@ func (fs *UnixFS) _openat2(dirfd int, name string, flag, mode uint64) (int, erro
|
|||||||
case err == nil:
|
case err == nil:
|
||||||
return fd, nil
|
return fd, nil
|
||||||
case err == unix.EINTR:
|
case err == unix.EINTR:
|
||||||
return -1, err
|
return fd, err
|
||||||
case err == unix.EAGAIN:
|
case err == unix.EAGAIN:
|
||||||
return -1, err
|
return fd, err
|
||||||
default:
|
default:
|
||||||
return -1, &PathError{Op: "openat2", Path: name, Err: err}
|
return fd, ensurePathError(err, "openat2", name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -746,7 +805,7 @@ func (fs *UnixFS) safePath(path string) (dirfd int, file string, closeFd func(),
|
|||||||
// Open the base path. We use this as the sandbox root for any further
|
// Open the base path. We use this as the sandbox root for any further
|
||||||
// operations.
|
// operations.
|
||||||
var fsDirfd int
|
var fsDirfd int
|
||||||
fsDirfd, err = unix.Openat(AT_EMPTY_PATH, fs.basePath, O_DIRECTORY|O_RDONLY, 0)
|
fsDirfd, err = fs._openat(AT_EMPTY_PATH, fs.basePath, O_DIRECTORY|O_RDONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -772,7 +831,7 @@ func (fs *UnixFS) safePath(path string) (dirfd int, file string, closeFd func(),
|
|||||||
// An error occurred while opening the directory, but we already opened
|
// An error occurred while opening the directory, but we already opened
|
||||||
// the filesystem root, so we still need to ensure it gets closed.
|
// the filesystem root, so we still need to ensure it gets closed.
|
||||||
closeFd = func() { _ = unix.Close(fsDirfd) }
|
closeFd = func() { _ = unix.Close(fsDirfd) }
|
||||||
} else {
|
} else {
|
||||||
// Set closeFd to close the newly opened directory file descriptor.
|
// Set closeFd to close the newly opened directory file descriptor.
|
||||||
closeFd = func() {
|
closeFd = func() {
|
||||||
_ = unix.Close(dirfd)
|
_ = unix.Close(dirfd)
|
||||||
@ -780,22 +839,24 @@ func (fs *UnixFS) safePath(path string) (dirfd int, file string, closeFd func(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Return dirfd, name, the closeFd func, and err
|
// Return dirfd, name, the closeFd func, and err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// unsafePath prefixes the given path and prefixes it with the filesystem's
|
// unsafePath strips and joins the given path with the filesystem's base path,
|
||||||
// base path, cleaning the result. The path returned by this function may not
|
// cleaning the result. The cleaned path is then checked if it starts with the
|
||||||
// be inside the filesystem's base path, additional checks are required to
|
// filesystem's base path to obvious any obvious path traversal escapes. The
|
||||||
// safely use paths returned by this function.
|
// fully resolved path (if symlinks are followed) may not be within the
|
||||||
|
// filesystem's base path, additional checks are required to safely use paths
|
||||||
|
// returned by this function.
|
||||||
func (fs *UnixFS) unsafePath(path string) (string, error) {
|
func (fs *UnixFS) unsafePath(path string) (string, error) {
|
||||||
// Calling filepath.Clean on the joined directory will resolve it to the
|
// Calling filepath.Clean on the path will resolve it to it's absolute path,
|
||||||
// absolute path, removing any ../ type of resolution arguments, and leaving
|
// removing any path traversal arguments (such as ..), leaving us with an
|
||||||
// us with a direct path link.
|
// absolute path we can then use.
|
||||||
//
|
//
|
||||||
// This will also trim the existing root path off the beginning of the path
|
// This will also trim the filesystem's base path from the given path and
|
||||||
// passed to the function since that can get a bit messy.
|
// join the base path back on to ensure the path starts with the base path
|
||||||
|
// without appending it twice.
|
||||||
r := filepath.Clean(filepath.Join(fs.basePath, strings.TrimPrefix(path, fs.basePath)))
|
r := filepath.Clean(filepath.Join(fs.basePath, strings.TrimPrefix(path, fs.basePath)))
|
||||||
|
|
||||||
if fs.unsafeIsPathInsideOfBase(r) {
|
if fs.unsafeIsPathInsideOfBase(r) {
|
||||||
@ -822,6 +883,10 @@ func (fs *UnixFS) unsafePath(path string) (string, error) {
|
|||||||
|
|
||||||
// unsafeIsPathInsideOfBase checks if the given path is inside the filesystem's
|
// unsafeIsPathInsideOfBase checks if the given path is inside the filesystem's
|
||||||
// base path.
|
// base path.
|
||||||
|
//
|
||||||
|
// NOTE: this method doesn't clean the given path or attempt to join the
|
||||||
|
// filesystem's base path. This is purely a basic prefix check against the
|
||||||
|
// given path.
|
||||||
func (fs *UnixFS) unsafeIsPathInsideOfBase(path string) bool {
|
func (fs *UnixFS) unsafeIsPathInsideOfBase(path string) bool {
|
||||||
return strings.HasPrefix(
|
return strings.HasPrefix(
|
||||||
strings.TrimSuffix(path, "/")+"/",
|
strings.TrimSuffix(path, "/")+"/",
|
||||||
|
|||||||
@ -10,10 +10,6 @@
|
|||||||
|
|
||||||
package ufs
|
package ufs
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// mkdirAll is a recursive Mkdir implementation that properly handles symlinks.
|
// mkdirAll is a recursive Mkdir implementation that properly handles symlinks.
|
||||||
func (fs *UnixFS) mkdirAll(name string, mode FileMode) error {
|
func (fs *UnixFS) mkdirAll(name string, mode FileMode) error {
|
||||||
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
|
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
|
||||||
@ -30,7 +26,7 @@ func (fs *UnixFS) mkdirAll(name string, mode FileMode) error {
|
|||||||
if dir.IsDir() {
|
if dir.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return convertErrorType(&PathError{Op: "mkdir", Path: name, Err: unix.ENOTDIR})
|
return &PathError{Op: "mkdir", Path: name, Err: ErrNotDirectory}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slow path: make sure parent exists and then call Mkdir for path.
|
// Slow path: make sure parent exists and then call Mkdir for path.
|
||||||
|
|||||||
@ -52,60 +52,69 @@ func removeAll(fs unixFS, path string) error {
|
|||||||
parentDir, base := splitPath(path)
|
parentDir, base := splitPath(path)
|
||||||
|
|
||||||
parent, err := fs.Open(parentDir)
|
parent, err := fs.Open(parentDir)
|
||||||
if errors.Is(err, ErrNotExist) {
|
if err != nil {
|
||||||
|
if !errors.Is(err, ErrNotExist) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// If parent does not exist, base cannot exist. Fail silently
|
// If parent does not exist, base cannot exist. Fail silently
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer parent.Close()
|
defer parent.Close()
|
||||||
|
|
||||||
if err := removeAllFrom(fs, parent, base); err != nil {
|
if err := removeAllFrom(fs, parent, base); err != nil {
|
||||||
if pathErr, ok := err.(*PathError); ok {
|
if pathErr, ok := err.(*PathError); ok {
|
||||||
pathErr.Path = parentDir + string(os.PathSeparator) + pathErr.Path
|
pathErr.Path = parentDir + string(os.PathSeparator) + pathErr.Path
|
||||||
err = pathErr
|
err = convertErrorType(pathErr)
|
||||||
|
} else {
|
||||||
|
err = ensurePathError(err, "removeallfrom", base)
|
||||||
}
|
}
|
||||||
return convertErrorType(err)
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeAllFrom(fs unixFS, parent File, base string) error {
|
func (fs *UnixFS) removeContents(path string) error {
|
||||||
parentFd := int(parent.Fd())
|
return removeContents(fs, path)
|
||||||
// Simple case: if Unlink (aka remove) works, we're done.
|
}
|
||||||
err := fs.unlinkat(parentFd, base, 0)
|
|
||||||
if err == nil || errors.Is(err, ErrNotExist) {
|
func removeContents(fs unixFS, path string) error {
|
||||||
|
if path == "" {
|
||||||
|
// fail silently to retain compatibility with previous behavior
|
||||||
|
// of RemoveAll. See issue https://go.dev/issue/28830.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EISDIR means that we have a directory, and we need to
|
// RemoveAll recurses by deleting the path base from
|
||||||
// remove its contents.
|
// its parent directory
|
||||||
// EPERM or EACCES means that we don't have write permission on
|
parentDir, base := splitPath(path)
|
||||||
// the parent directory, but this entry might still be a directory
|
|
||||||
// whose contents need to be removed.
|
|
||||||
// Otherwise, just return the error.
|
|
||||||
if err != unix.EISDIR && err != unix.EPERM && err != unix.EACCES {
|
|
||||||
return &PathError{Op: "unlinkat", Path: base, Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is this a directory we need to recurse into?
|
parent, err := fs.Open(parentDir)
|
||||||
var statInfo unix.Stat_t
|
if err != nil {
|
||||||
statErr := ignoringEINTR(func() error {
|
if !errors.Is(err, ErrNotExist) {
|
||||||
return unix.Fstatat(parentFd, base, &statInfo, AT_SYMLINK_NOFOLLOW)
|
return err
|
||||||
})
|
|
||||||
if statErr != nil {
|
|
||||||
if errors.Is(statErr, ErrNotExist) {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return &PathError{Op: "fstatat", Path: base, Err: statErr}
|
// If parent does not exist, base cannot exist. Fail silently
|
||||||
}
|
return nil
|
||||||
if statInfo.Mode&unix.S_IFMT != unix.S_IFDIR {
|
|
||||||
// Not a directory; return the error from the unix.Unlinkat.
|
|
||||||
return &PathError{Op: "unlinkat", Path: base, Err: err}
|
|
||||||
}
|
}
|
||||||
|
defer parent.Close()
|
||||||
|
|
||||||
|
if err := removeContentsFrom(fs, parent, base); err != nil {
|
||||||
|
if pathErr, ok := err.(*PathError); ok {
|
||||||
|
pathErr.Path = parentDir + string(os.PathSeparator) + pathErr.Path
|
||||||
|
err = convertErrorType(pathErr)
|
||||||
|
} else {
|
||||||
|
err = ensurePathError(err, "removecontentsfrom", base)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeContentsFrom recursively removes all descendants of parent without
|
||||||
|
// removing parent itself. Parent must be a directory.
|
||||||
|
func removeContentsFrom(fs unixFS, parent File, base string) error {
|
||||||
|
parentFd := int(parent.Fd())
|
||||||
|
|
||||||
// Remove the directory's entries.
|
|
||||||
var recurseErr error
|
var recurseErr error
|
||||||
for {
|
for {
|
||||||
const reqSize = 1024
|
const reqSize = 1024
|
||||||
@ -168,6 +177,50 @@ func removeAllFrom(fs unixFS, parent File, base string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeAllFrom(fs unixFS, parent File, base string) error {
|
||||||
|
parentFd := int(parent.Fd())
|
||||||
|
|
||||||
|
// Simple case: if Unlink (aka remove) works, we're done.
|
||||||
|
err := fs.unlinkat(parentFd, base, 0)
|
||||||
|
if err == nil || errors.Is(err, ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EISDIR means that we have a directory, and we need to
|
||||||
|
// remove its contents.
|
||||||
|
// EPERM or EACCES means that we don't have write permission on
|
||||||
|
// the parent directory, but this entry might still be a directory
|
||||||
|
// whose contents need to be removed.
|
||||||
|
// Otherwise, just return the error.
|
||||||
|
if err != unix.EISDIR && err != unix.EPERM && err != unix.EACCES {
|
||||||
|
return &PathError{Op: "unlinkat", Path: base, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this a directory we need to recurse into?
|
||||||
|
var statInfo unix.Stat_t
|
||||||
|
statErr := ignoringEINTR(func() error {
|
||||||
|
return unix.Fstatat(parentFd, base, &statInfo, AT_SYMLINK_NOFOLLOW)
|
||||||
|
})
|
||||||
|
if statErr != nil {
|
||||||
|
if errors.Is(statErr, ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &PathError{Op: "fstatat", Path: base, Err: statErr}
|
||||||
|
}
|
||||||
|
if statInfo.Mode&unix.S_IFMT != unix.S_IFDIR {
|
||||||
|
// Not a directory; return the error from the unix.Unlinkat.
|
||||||
|
return &PathError{Op: "unlinkat", Path: base, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all contents will remove the contents of the directory.
|
||||||
|
//
|
||||||
|
// It was split out of this function to allow the deletion of the
|
||||||
|
// contents of a directory, without deleting the directory itself.
|
||||||
|
recurseErr := removeContentsFrom(fs, parent, base)
|
||||||
|
|
||||||
// Remove the directory itself.
|
// Remove the directory itself.
|
||||||
unlinkErr := fs.unlinkat(parentFd, base, AT_REMOVEDIR)
|
unlinkErr := fs.unlinkat(parentFd, base, AT_REMOVEDIR)
|
||||||
if unlinkErr == nil || errors.Is(unlinkErr, ErrNotExist) {
|
if unlinkErr == nil || errors.Is(unlinkErr, ErrNotExist) {
|
||||||
@ -177,7 +230,8 @@ func removeAllFrom(fs unixFS, parent File, base string) error {
|
|||||||
if recurseErr != nil {
|
if recurseErr != nil {
|
||||||
return recurseErr
|
return recurseErr
|
||||||
}
|
}
|
||||||
return &PathError{Op: "unlinkat", Path: base, Err: unlinkErr}
|
|
||||||
|
return ensurePathError(err, "unlinkat", base)
|
||||||
}
|
}
|
||||||
|
|
||||||
// openFdAt opens path relative to the directory in fd.
|
// openFdAt opens path relative to the directory in fd.
|
||||||
|
|||||||
@ -199,7 +199,7 @@ func (fs *UnixFS) modeTypeFromDirent(de *unix.Dirent, fd int, name string) (File
|
|||||||
func (fs *UnixFS) modeType(dirfd int, name string) (FileMode, error) {
|
func (fs *UnixFS) modeType(dirfd int, name string) (FileMode, error) {
|
||||||
fi, err := fs.Lstatat(dirfd, name)
|
fi, err := fs.Lstatat(dirfd, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("ufs: error finding mode type for %s during readDir: %w", name, convertErrorType(err))
|
return 0, fmt.Errorf("ufs: error finding mode type for %s during readDir: %w", name, err)
|
||||||
}
|
}
|
||||||
return fi.Mode() & ModeType, nil
|
return fi.Mode() & ModeType, nil
|
||||||
}
|
}
|
||||||
@ -227,7 +227,7 @@ func (fs *UnixFS) readDir(fd int, name, relative string, b []byte) ([]DirEntry,
|
|||||||
if err == unix.EINTR {
|
if err == unix.EINTR {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("ufs: error with getdents during readDir: %w", convertErrorType(err))
|
return nil, ensurePathError(err, "getdents", name)
|
||||||
}
|
}
|
||||||
if n <= 0 {
|
if n <= 0 {
|
||||||
// end of directory: normal exit
|
// end of directory: normal exit
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user