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

fixes #11203
master
Harshavardhana 4 years ago committed by GitHub
parent 85d2187c20
commit 8cad407e0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 39
      cmd/os-readdir_other.go
  2. 3
      cmd/os-readdir_test.go
  3. 48
      cmd/os-readdir_unix.go
  4. 71
      cmd/os-readdir_windows.go
  5. 17
      docs/gateway/nas.md

@ -56,6 +56,24 @@ func readDirFn(dirPath string, filter func(name string, typ os.FileMode) error)
return osErrToFileErr(err) return osErrToFileErr(err)
} }
for _, fi := range fis { 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 { if err = filter(fi.Name(), fi.Mode()); err == errDoneForNow {
// filtering requested to return by caller. // filtering requested to return by caller.
return nil return nil
@ -97,11 +115,26 @@ func readDirN(dirPath string, count int) (entries []string, err error) {
} }
} }
for _, fi := range fis { for _, fi := range fis {
// Not need to follow symlink.
if fi.Mode()&os.ModeSymlink == os.ModeSymlink { 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. // Append SlashSeparator instead of "\" so that sorting is achieved as expected.
entries = append(entries, fi.Name()+SlashSeparator) entries = append(entries, fi.Name()+SlashSeparator)
} else if fi.Mode().IsRegular() { } else if fi.Mode().IsRegular() {

@ -149,7 +149,8 @@ func setupTestReadDirSymlink(t *testing.T) (testResults []result) {
} }
// Add to entries. // Add to entries.
entries = append(entries, name1) 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 { if err := os.MkdirAll(filepath.Join(dir, "mydir"), 0777); err != nil {
t.Fatalf("Unable to create \"mydir\", %s", err) t.Fatalf("Unable to create \"mydir\", %s", err)

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

@ -42,6 +42,15 @@ func readDirFn(dirPath string, filter func(name string, typ os.FileMode) error)
} }
defer f.Close() 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{} data := &syscall.Win32finddata{}
for { for {
e := syscall.FindNextFile(syscall.Handle(f.Fd()), data) 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 if name == "" || name == "." || name == ".." { // Useless names
continue continue
} }
if data.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
continue
}
var typ os.FileMode = 0 // regular file 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 typ = os.ModeDir
} }
if e = filter(name, typ); e == errDoneForNow { if e = filter(name, typ); e == errDoneForNow {
// filtering requested to return by caller. // filtering requested to return by caller.
return nil return nil
@ -88,10 +117,14 @@ func readDirN(dirPath string, count int) (entries []string, err error) {
defer f.Close() defer f.Close()
// Check if file or dir. This is the quickest way. // Check if file or dir. This is the quickest way.
_, err = f.Seek(0, io.SeekStart) // Do not remove this check, on windows syscall.FindNextFile
if err == nil { // 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 return nil, errFileNotFound
} }
data := &syscall.Win32finddata{} data := &syscall.Win32finddata{}
handle := syscall.Handle(f.Fd()) 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 if name == "" || name == "." || name == ".." { // Useless names
continue continue
} }
switch { switch {
case data.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0: 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: case data.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0:
entries = append(entries, name+SlashSeparator) name = name + SlashSeparator
default:
entries = append(entries, name)
} }
count-- count--
entries = append(entries, name)
} }
return entries, nil return entries, nil

@ -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` > 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 ## Explore Further
- [`mc` command-line interface](https://docs.min.io/docs/minio-client-quickstart-guide) - [`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) - [`aws` command-line interface](https://docs.min.io/docs/aws-cli-with-minio)

Loading…
Cancel
Save