2022-10-18 23:15:21 +08:00
|
|
|
package sftp
|
|
|
|
|
|
|
|
import (
|
2024-01-06 20:04:19 +08:00
|
|
|
"fmt"
|
2024-01-07 19:49:09 +08:00
|
|
|
"io"
|
2024-01-06 20:04:19 +08:00
|
|
|
"io/fs"
|
|
|
|
"os"
|
2022-10-18 23:15:21 +08:00
|
|
|
"path"
|
|
|
|
"path/filepath"
|
2025-01-01 23:08:40 +08:00
|
|
|
"time"
|
2024-01-07 19:49:09 +08:00
|
|
|
|
|
|
|
"golang.org/x/sys/windows"
|
2022-10-18 23:15:21 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
func (s *Server) toLocalPath(p string) string {
|
|
|
|
if s.workDir != "" && !path.IsAbs(p) {
|
|
|
|
p = path.Join(s.workDir, p)
|
|
|
|
}
|
|
|
|
|
|
|
|
lp := filepath.FromSlash(p)
|
|
|
|
|
2024-01-10 20:32:51 +08:00
|
|
|
if path.IsAbs(p) { // starts with '/'
|
|
|
|
if len(p) == 1 && s.winRoot {
|
|
|
|
return `\\.\` // for openfile
|
|
|
|
}
|
|
|
|
|
2022-10-18 23:15:21 +08:00
|
|
|
tmp := lp
|
|
|
|
for len(tmp) > 0 && tmp[0] == '\\' {
|
|
|
|
tmp = tmp[1:]
|
|
|
|
}
|
|
|
|
|
|
|
|
if filepath.IsAbs(tmp) {
|
|
|
|
// If the FromSlash without any starting slashes is absolute,
|
|
|
|
// then we have a filepath encoded with a prefix '/'.
|
|
|
|
// e.g. "/C:/Windows" to "C:\\Windows"
|
|
|
|
return tmp
|
|
|
|
}
|
|
|
|
|
|
|
|
tmp += "\\"
|
|
|
|
|
|
|
|
if filepath.IsAbs(tmp) {
|
|
|
|
// If the FromSlash without any starting slashes but with extra end slash is absolute,
|
|
|
|
// then we have a filepath encoded with a prefix '/' and a dropped '/' at the end.
|
|
|
|
// e.g. "/C:" to "C:\\"
|
|
|
|
return tmp
|
|
|
|
}
|
2024-01-10 20:32:51 +08:00
|
|
|
|
|
|
|
if s.winRoot {
|
|
|
|
// Make it so that "/Windows" is not found, and "/c:/Windows" has to be used
|
|
|
|
return `\\.\` + tmp
|
|
|
|
}
|
2022-10-18 23:15:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return lp
|
|
|
|
}
|
2024-01-06 20:04:19 +08:00
|
|
|
|
|
|
|
func bitsToDrives(bitmap uint32) []string {
|
2024-01-07 19:49:09 +08:00
|
|
|
var drive rune = 'a'
|
2024-01-06 20:04:19 +08:00
|
|
|
var drives []string
|
|
|
|
|
2025-01-03 03:15:18 +08:00
|
|
|
for bitmap != 0 && drive <= 'z' {
|
2024-01-06 20:04:19 +08:00
|
|
|
if bitmap&1 == 1 {
|
2024-01-07 19:49:09 +08:00
|
|
|
drives = append(drives, string(drive)+":")
|
2024-01-06 20:04:19 +08:00
|
|
|
}
|
|
|
|
drive++
|
|
|
|
bitmap >>= 1
|
|
|
|
}
|
|
|
|
|
|
|
|
return drives
|
|
|
|
}
|
|
|
|
|
|
|
|
func getDrives() ([]string, error) {
|
2024-01-07 19:49:09 +08:00
|
|
|
mask, err := windows.GetLogicalDrives()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("GetLogicalDrives: %w", err)
|
2024-01-06 20:04:19 +08:00
|
|
|
}
|
2024-01-07 19:49:09 +08:00
|
|
|
return bitsToDrives(mask), nil
|
2024-01-06 20:04:19 +08:00
|
|
|
}
|
|
|
|
|
2024-01-07 19:49:09 +08:00
|
|
|
type driveInfo struct {
|
|
|
|
fs.FileInfo
|
2024-01-06 20:04:19 +08:00
|
|
|
name string
|
|
|
|
}
|
|
|
|
|
2024-01-07 19:49:09 +08:00
|
|
|
func (i *driveInfo) Name() string {
|
|
|
|
return i.name // since the Name() returned from a os.Stat("C:\\") is "\\"
|
2024-01-06 20:04:19 +08:00
|
|
|
}
|
|
|
|
|
2024-01-07 19:49:09 +08:00
|
|
|
type winRoot struct {
|
2024-01-10 04:53:27 +08:00
|
|
|
drives []string
|
2024-01-06 20:04:19 +08:00
|
|
|
}
|
|
|
|
|
2024-01-10 04:53:27 +08:00
|
|
|
func newWinRoot() (*winRoot, error) {
|
2024-01-06 20:04:19 +08:00
|
|
|
drives, err := getDrives()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-01-10 04:53:27 +08:00
|
|
|
return &winRoot{
|
|
|
|
drives: drives,
|
|
|
|
}, nil
|
|
|
|
}
|
2024-01-07 19:49:09 +08:00
|
|
|
|
2024-01-10 04:53:27 +08:00
|
|
|
func (f *winRoot) Readdir(n int) ([]os.FileInfo, error) {
|
|
|
|
drives := f.drives
|
2024-01-10 20:32:51 +08:00
|
|
|
if n > 0 && len(drives) > n {
|
|
|
|
drives = drives[:n]
|
|
|
|
}
|
|
|
|
f.drives = f.drives[len(drives):]
|
|
|
|
if len(drives) == 0 {
|
|
|
|
return nil, io.EOF
|
2024-01-06 20:04:19 +08:00
|
|
|
}
|
2024-01-07 19:49:09 +08:00
|
|
|
|
|
|
|
var infos []os.FileInfo
|
2024-01-10 04:53:27 +08:00
|
|
|
for _, drive := range drives {
|
2024-01-10 20:32:51 +08:00
|
|
|
fi, err := os.Stat(drive + `\`)
|
2024-01-07 19:49:09 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
di := &driveInfo{
|
|
|
|
FileInfo: fi,
|
|
|
|
name: drive,
|
|
|
|
}
|
|
|
|
infos = append(infos, di)
|
|
|
|
}
|
|
|
|
|
2024-01-06 20:04:19 +08:00
|
|
|
return infos, nil
|
|
|
|
}
|
|
|
|
|
2024-01-11 17:16:37 +08:00
|
|
|
func (f *winRoot) Stat() (os.FileInfo, error) {
|
2025-01-03 03:15:18 +08:00
|
|
|
return rootFileInfo, nil
|
2024-01-11 17:16:37 +08:00
|
|
|
}
|
|
|
|
func (f *winRoot) ReadAt(b []byte, off int64) (int, error) {
|
|
|
|
return 0, os.ErrPermission
|
|
|
|
}
|
|
|
|
func (f *winRoot) WriteAt(b []byte, off int64) (int, error) {
|
|
|
|
return 0, os.ErrPermission
|
|
|
|
}
|
|
|
|
func (f *winRoot) Name() string {
|
|
|
|
return "/"
|
|
|
|
}
|
|
|
|
func (f *winRoot) Truncate(int64) error {
|
|
|
|
return os.ErrPermission
|
|
|
|
}
|
|
|
|
func (f *winRoot) Chmod(mode fs.FileMode) error {
|
|
|
|
return os.ErrPermission
|
|
|
|
}
|
|
|
|
func (f *winRoot) Chown(uid, gid int) error {
|
|
|
|
return os.ErrPermission
|
|
|
|
}
|
|
|
|
func (f *winRoot) Close() error {
|
2025-01-03 03:15:18 +08:00
|
|
|
f.drives = nil
|
2024-01-11 17:16:37 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-01-10 20:32:51 +08:00
|
|
|
func (s *Server) openfile(path string, flag int, mode fs.FileMode) (file, error) {
|
|
|
|
if path == `\\.\` && s.winRoot {
|
2024-01-10 04:53:27 +08:00
|
|
|
return newWinRoot()
|
2024-01-06 20:04:19 +08:00
|
|
|
}
|
|
|
|
return os.OpenFile(path, flag, mode)
|
|
|
|
}
|
2025-01-01 23:08:40 +08:00
|
|
|
|
|
|
|
type winRootFileInfo struct {
|
2025-01-03 03:15:18 +08:00
|
|
|
name string
|
|
|
|
modTime time.Time
|
2025-01-01 23:08:40 +08:00
|
|
|
}
|
|
|
|
|
2025-01-03 03:15:18 +08:00
|
|
|
func (w *winRootFileInfo) Name() string { return w.name }
|
|
|
|
func (w *winRootFileInfo) Size() int64 { return 0 }
|
|
|
|
func (w *winRootFileInfo) Mode() fs.FileMode { return fs.ModeDir | 0555 } // read+execute for all
|
|
|
|
func (w *winRootFileInfo) ModTime() time.Time { return w.modTime }
|
|
|
|
func (w *winRootFileInfo) IsDir() bool { return true }
|
|
|
|
func (w *winRootFileInfo) Sys() interface{} { return nil }
|
2025-01-01 23:08:40 +08:00
|
|
|
|
|
|
|
// Create a new root FileInfo
|
2025-01-03 03:15:18 +08:00
|
|
|
var rootFileInfo = &winRootFileInfo{
|
|
|
|
name: "/",
|
|
|
|
modTime: time.Now(),
|
2025-01-01 23:08:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) lstat(name string) (os.FileInfo, error) {
|
|
|
|
if name == `\\.\` && s.winRoot {
|
|
|
|
return rootFileInfo, nil
|
|
|
|
}
|
|
|
|
return os.Lstat(name)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) stat(name string) (os.FileInfo, error) {
|
|
|
|
if name == `\\.\` && s.winRoot {
|
|
|
|
return rootFileInfo, nil
|
|
|
|
}
|
|
|
|
return os.Stat(name)
|
|
|
|
}
|