| package sftp |
| |
| import ( |
| "fmt" |
| "io" |
| "io/fs" |
| "os" |
| "path" |
| "path/filepath" |
| "time" |
| |
| "golang.org/x/sys/windows" |
| ) |
| |
| func (s *Server) toLocalPath(p string) string { |
| if s.workDir != "" && !path.IsAbs(p) { |
| p = path.Join(s.workDir, p) |
| } |
| |
| lp := filepath.FromSlash(p) |
| |
| if path.IsAbs(p) { // starts with '/' |
| if len(p) == 1 && s.winRoot { |
| return `\\.\` // for openfile |
| } |
| |
| 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 |
| } |
| |
| if s.winRoot { |
| // Make it so that "/Windows" is not found, and "/c:/Windows" has to be used |
| return `\\.\` + tmp |
| } |
| } |
| |
| return lp |
| } |
| |
| func bitsToDrives(bitmap uint32) []string { |
| var drive rune = 'a' |
| var drives []string |
| |
| for bitmap != 0 && drive <= 'z' { |
| if bitmap&1 == 1 { |
| drives = append(drives, string(drive)+":") |
| } |
| drive++ |
| bitmap >>= 1 |
| } |
| |
| return drives |
| } |
| |
| func getDrives() ([]string, error) { |
| mask, err := windows.GetLogicalDrives() |
| if err != nil { |
| return nil, fmt.Errorf("GetLogicalDrives: %w", err) |
| } |
| return bitsToDrives(mask), nil |
| } |
| |
| type driveInfo struct { |
| fs.FileInfo |
| name string |
| } |
| |
| func (i *driveInfo) Name() string { |
| return i.name // since the Name() returned from a os.Stat("C:\\") is "\\" |
| } |
| |
| type winRoot struct { |
| drives []string |
| } |
| |
| func newWinRoot() (*winRoot, error) { |
| drives, err := getDrives() |
| if err != nil { |
| return nil, err |
| } |
| return &winRoot{ |
| drives: drives, |
| }, nil |
| } |
| |
| func (f *winRoot) Readdir(n int) ([]os.FileInfo, error) { |
| drives := f.drives |
| if n > 0 && len(drives) > n { |
| drives = drives[:n] |
| } |
| f.drives = f.drives[len(drives):] |
| if len(drives) == 0 { |
| return nil, io.EOF |
| } |
| |
| var infos []os.FileInfo |
| for _, drive := range drives { |
| fi, err := os.Stat(drive + `\`) |
| if err != nil { |
| return nil, err |
| } |
| |
| di := &driveInfo{ |
| FileInfo: fi, |
| name: drive, |
| } |
| infos = append(infos, di) |
| } |
| |
| return infos, nil |
| } |
| |
| func (f *winRoot) Stat() (os.FileInfo, error) { |
| return rootFileInfo, nil |
| } |
| 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 { |
| f.drives = nil |
| return nil |
| } |
| |
| func (s *Server) openfile(path string, flag int, mode fs.FileMode) (file, error) { |
| if path == `\\.\` && s.winRoot { |
| return newWinRoot() |
| } |
| return os.OpenFile(path, flag, mode) |
| } |
| |
| type winRootFileInfo struct { |
| name string |
| modTime time.Time |
| } |
| |
| 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 } |
| |
| // Create a new root FileInfo |
| var rootFileInfo = &winRootFileInfo{ |
| name: "/", |
| modTime: time.Now(), |
| } |
| |
| 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) |
| } |