fix: Bring support for symlink on regular files on NAS (#11383)

fixes #11203
This commit is contained in:
Harshavardhana 2021-02-20 00:30:12 -08:00 committed by GitHub
parent 85d2187c20
commit 8cad407e0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 155 additions and 23 deletions

View file

@ -56,6 +56,24 @@ func readDirFn(dirPath string, filter func(name string, typ os.FileMode) error)
return osErrToFileErr(err)
}
for _, fi := range fis {
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
fi, err = os.Stat(pathJoin(dirPath, fi.Name()))
if err != nil {
// It got deleted in the meantime, not found
// or returns too many symlinks ignore this
// file/directory.
if osIsNotExist(err) || isSysErrPathNotFound(err) ||
isSysErrTooManySymlinks(err) {
continue
}
return err
}
// Ignore symlinked directories.
if fi.IsDir() {
continue
}
}
if err = filter(fi.Name(), fi.Mode()); err == errDoneForNow {
// filtering requested to return by caller.
return nil
@ -97,11 +115,26 @@ func readDirN(dirPath string, count int) (entries []string, err error) {
}
}
for _, fi := range fis {
// Not need to follow symlink.
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
continue
fi, err = os.Stat(pathJoin(dirPath, fi.Name()))
if err != nil {
// It got deleted in the meantime, not found
// or returns too many symlinks ignore this
// file/directory.
if osIsNotExist(err) || isSysErrPathNotFound(err) ||
isSysErrTooManySymlinks(err) {
continue
}
return err
}
// Ignore symlinked directories.
if fi.IsDir() {
continue
}
}
if fi.Mode().IsDir() {
if fi.IsDir() {
// Append SlashSeparator instead of "\" so that sorting is achieved as expected.
entries = append(entries, fi.Name()+SlashSeparator)
} else if fi.Mode().IsRegular() {

View file

@ -149,7 +149,8 @@ func setupTestReadDirSymlink(t *testing.T) (testResults []result) {
}
// Add to entries.
entries = append(entries, name1)
// Symlinks are ignored.
// Symlinks are preserved for regular files
entries = append(entries, name2)
}
if err := os.MkdirAll(filepath.Join(dir, "mydir"), 0777); err != nil {
t.Fatalf("Unable to create \"mydir\", %s", err)

View file

@ -123,8 +123,29 @@ func readDirFn(dirPath string, fn func(name string, typ os.FileMode) error) erro
if len(name) == 0 || bytes.Equal(name, []byte{'.'}) || bytes.Equal(name, []byte{'.', '.'}) {
continue
}
if typ&os.ModeSymlink == os.ModeSymlink {
continue
// Fallback for filesystems (like old XFS) that don't
// support Dirent.Type and have DT_UNKNOWN (0) there
// instead.
if typ == unexpectedFileMode || typ&os.ModeSymlink == os.ModeSymlink {
fi, err := os.Stat(pathJoin(dirPath, string(name)))
if err != nil {
// It got deleted in the meantime, not found
// or returns too many symlinks ignore this
// file/directory.
if osIsNotExist(err) || isSysErrPathNotFound(err) ||
isSysErrTooManySymlinks(err) {
continue
}
return err
}
// Ignore symlinked directories.
if typ&os.ModeSymlink == os.ModeSymlink && fi.IsDir() {
continue
}
typ = fi.Mode() & os.ModeType
}
if err = fn(string(name), typ); err == errDoneForNow {
// fn() requested to return by caller.
@ -176,11 +197,12 @@ func readDirN(dirPath string, count int) (entries []string, err error) {
if len(name) == 0 || bytes.Equal(name, []byte{'.'}) || bytes.Equal(name, []byte{'.', '.'}) {
continue
}
// Fallback for filesystems (like old XFS) that don't
// support Dirent.Type and have DT_UNKNOWN (0) there
// instead.
if typ == unexpectedFileMode {
fi, err := os.Lstat(pathJoin(dirPath, string(name)))
if typ == unexpectedFileMode || typ&os.ModeSymlink == os.ModeSymlink {
fi, err := os.Stat(pathJoin(dirPath, string(name)))
if err != nil {
// It got deleted in the meantime, not found
// or returns too many symlinks ignore this
@ -191,22 +213,30 @@ func readDirN(dirPath string, count int) (entries []string, err error) {
}
return nil, err
}
// Ignore symlinked directories.
if typ&os.ModeSymlink == os.ModeSymlink && fi.IsDir() {
continue
}
typ = fi.Mode() & os.ModeType
}
if typ&os.ModeSymlink == os.ModeSymlink {
continue
}
var nameStr string
if typ.IsRegular() {
entries = append(entries, string(name))
nameStr = string(name)
} else if typ.IsDir() {
// Use temp buffer to append a slash to avoid string concat.
tmp = tmp[:len(name)+1]
copy(tmp, name)
tmp[len(tmp)-1] = '/' // SlashSeparator
entries = append(entries, string(tmp))
nameStr = string(tmp)
}
count--
entries = append(entries, nameStr)
}
return
}

View file

@ -42,6 +42,15 @@ func readDirFn(dirPath string, filter func(name string, typ os.FileMode) error)
}
defer f.Close()
// Check if file or dir. This is the quickest way.
// Do not remove this check, on windows syscall.FindNextFile
// would throw an exception if Fd() points to a file
// instead of a directory, we need to quickly fail
// in such situations - this workadound is expected.
if _, err = f.Seek(0, io.SeekStart); err == nil {
return errFileNotFound
}
data := &syscall.Win32finddata{}
for {
e := syscall.FindNextFile(syscall.Handle(f.Fd()), data)
@ -63,13 +72,33 @@ func readDirFn(dirPath string, filter func(name string, typ os.FileMode) error)
if name == "" || name == "." || name == ".." { // Useless names
continue
}
if data.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
continue
}
var typ os.FileMode = 0 // regular file
if data.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
switch {
case data.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0:
// Reparse point is a symlink
fi, err := os.Stat(pathJoin(dirPath, string(name)))
if err != nil {
// It got deleted in the meantime, not found
// or returns too many symlinks ignore this
// file/directory.
if osIsNotExist(err) || isSysErrPathNotFound(err) ||
isSysErrTooManySymlinks(err) {
continue
}
return err
}
if fi.IsDir() {
// Ignore symlinked directories.
continue
}
typ = fi.Mode()
case data.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0:
typ = os.ModeDir
}
if e = filter(name, typ); e == errDoneForNow {
// filtering requested to return by caller.
return nil
@ -88,10 +117,14 @@ func readDirN(dirPath string, count int) (entries []string, err error) {
defer f.Close()
// Check if file or dir. This is the quickest way.
_, err = f.Seek(0, io.SeekStart)
if err == nil {
// Do not remove this check, on windows syscall.FindNextFile
// would throw an exception if Fd() points to a file
// instead of a directory, we need to quickly fail
// in such situations - this workadound is expected.
if _, err = f.Seek(0, io.SeekStart); err == nil {
return nil, errFileNotFound
}
data := &syscall.Win32finddata{}
handle := syscall.Handle(f.Fd())
@ -113,15 +146,33 @@ func readDirN(dirPath string, count int) (entries []string, err error) {
if name == "" || name == "." || name == ".." { // Useless names
continue
}
switch {
case data.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0:
continue
// Reparse point is a symlink
fi, err := os.Stat(pathJoin(dirPath, string(name)))
if err != nil {
// It got deleted in the meantime, not found
// or returns too many symlinks ignore this
// file/directory.
if osIsNotExist(err) || isSysErrPathNotFound(err) ||
isSysErrTooManySymlinks(err) {
continue
}
return nil, err
}
if fi.IsDir() {
// directory symlinks are ignored.
continue
}
case data.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0:
entries = append(entries, name+SlashSeparator)
default:
entries = append(entries, name)
name = name + SlashSeparator
}
count--
entries = append(entries, name)
}
return entries, nil

View file

@ -79,6 +79,23 @@ export MINIO_NOTIFY_WEBHOOK_QUEUE_DIR_1=/tmp/webhk
> NOTE: Please check the docs for the corresponding ENV setting. Alternatively, We can obtain other ENVs in the form `mc admin config set alias/ <sub-sys> --env`
## Symlink support
NAS gateway implementation allows symlinks on regular files,
### Behavior
- For reads symlink resolves to file symlink points to.
- For deletes
- Delete of symlink deletes the symlink but not the real file to which the symlink points.
- Delete of actual file automatically makes symlink'ed file invisible, dangling symlinks won't be visible.
#### Caveats
- Disallows follow of directory symlinks to avoid security issues, and leaving them as is on namespace makes them very inconsistent.
- Dangling symlinks are ignored automatically.
*Directory symlinks is not and will not be supported as there are no safe ways to handle them.*
## Explore Further
- [`mc` command-line interface](https://docs.min.io/docs/minio-client-quickstart-guide)
- [`aws` command-line interface](https://docs.min.io/docs/aws-cli-with-minio)