forked from TrueCloudLab/rclone
sftp: save the md5/sha1 command in use to the config file
This commit is contained in:
parent
3ea82032e7
commit
fa539b9d9b
1 changed files with 76 additions and 46 deletions
|
@ -36,7 +36,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
connectionsPerSecond = 10 // don't make more than this many ssh connections/s
|
connectionsPerSecond = 10 // don't make more than this many ssh connections/s
|
||||||
|
hashCommandNotSupported = "none"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -127,6 +128,16 @@ Home directory can be found in a shared folder called "home"
|
||||||
Default: true,
|
Default: true,
|
||||||
Help: "Set the modified time on the remote if set.",
|
Help: "Set the modified time on the remote if set.",
|
||||||
Advanced: true,
|
Advanced: true,
|
||||||
|
}, {
|
||||||
|
Name: "md5sum_command",
|
||||||
|
Default: "",
|
||||||
|
Help: "The command used to read md5 hashes. Leave blank for autodetect.",
|
||||||
|
Advanced: true,
|
||||||
|
}, {
|
||||||
|
Name: "sha1sum_command",
|
||||||
|
Default: "",
|
||||||
|
Help: "The command used to read sha1 hashes. Leave blank for autodetect.",
|
||||||
|
Advanced: true,
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
fs.Register(fsi)
|
fs.Register(fsi)
|
||||||
|
@ -146,14 +157,17 @@ type Options struct {
|
||||||
AskPassword bool `config:"ask_password"`
|
AskPassword bool `config:"ask_password"`
|
||||||
PathOverride string `config:"path_override"`
|
PathOverride string `config:"path_override"`
|
||||||
SetModTime bool `config:"set_modtime"`
|
SetModTime bool `config:"set_modtime"`
|
||||||
|
Md5sumCommand string `config:"md5sum_command"`
|
||||||
|
Sha1sumCommand string `config:"sha1sum_command"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fs stores the interface to the remote SFTP files
|
// Fs stores the interface to the remote SFTP files
|
||||||
type Fs struct {
|
type Fs struct {
|
||||||
name string
|
name string
|
||||||
root string
|
root string
|
||||||
opt Options // parsed options
|
opt Options // parsed options
|
||||||
features *fs.Features // optional features
|
m configmap.Mapper // config
|
||||||
|
features *fs.Features // optional features
|
||||||
config *ssh.ClientConfig
|
config *ssh.ClientConfig
|
||||||
url string
|
url string
|
||||||
mkdirLock *stringLock
|
mkdirLock *stringLock
|
||||||
|
@ -161,8 +175,6 @@ type Fs struct {
|
||||||
poolMu sync.Mutex
|
poolMu sync.Mutex
|
||||||
pool []*conn
|
pool []*conn
|
||||||
connLimit *rate.Limiter // for limiting number of connections per second
|
connLimit *rate.Limiter // for limiting number of connections per second
|
||||||
md5sum string // command to use for reading md5sum
|
|
||||||
sha1sum string // command to use for reading sha1sum
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Object is a remote SFTP file that has been stat'd (so it exists, but is not necessarily open for reading)
|
// Object is a remote SFTP file that has been stat'd (so it exists, but is not necessarily open for reading)
|
||||||
|
@ -423,16 +435,17 @@ func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
|
||||||
sshConfig.Auth = append(sshConfig.Auth, ssh.Password(clearpass))
|
sshConfig.Auth = append(sshConfig.Auth, ssh.Password(clearpass))
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewFsWithConnection(ctx, name, root, opt, sshConfig)
|
return NewFsWithConnection(ctx, name, root, m, opt, sshConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFsWithConnection creates a new Fs object from the name and root and a ssh.ClientConfig. It connects to
|
// NewFsWithConnection creates a new Fs object from the name and root and a ssh.ClientConfig. It connects to
|
||||||
// the host specified in the ssh.ClientConfig
|
// the host specified in the ssh.ClientConfig
|
||||||
func NewFsWithConnection(ctx context.Context, name string, root string, opt *Options, sshConfig *ssh.ClientConfig) (fs.Fs, error) {
|
func NewFsWithConnection(ctx context.Context, name string, root string, m configmap.Mapper, opt *Options, sshConfig *ssh.ClientConfig) (fs.Fs, error) {
|
||||||
f := &Fs{
|
f := &Fs{
|
||||||
name: name,
|
name: name,
|
||||||
root: root,
|
root: root,
|
||||||
opt: *opt,
|
opt: *opt,
|
||||||
|
m: m,
|
||||||
config: sshConfig,
|
config: sshConfig,
|
||||||
url: "sftp://" + opt.User + "@" + opt.Host + ":" + opt.Port + "/" + root,
|
url: "sftp://" + opt.User + "@" + opt.Host + ":" + opt.Port + "/" + root,
|
||||||
mkdirLock: newStringLock(),
|
mkdirLock: newStringLock(),
|
||||||
|
@ -758,45 +771,77 @@ func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hashes returns the supported hash types of the filesystem
|
// run runds cmd on the remote end returning standard output
|
||||||
func (f *Fs) Hashes() hash.Set {
|
func (f *Fs) run(cmd string) ([]byte, error) {
|
||||||
if f.cachedHashes != nil {
|
c, err := f.getSftpConnection()
|
||||||
return *f.cachedHashes
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "run: get SFTP connection")
|
||||||
|
}
|
||||||
|
defer f.putSftpConnection(&c, err)
|
||||||
|
|
||||||
|
session, err := c.sshClient.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "run: get SFTP sessiion")
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = session.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
session.Stdout = &stdout
|
||||||
|
session.Stderr = &stderr
|
||||||
|
|
||||||
|
err = session.Run(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to run %q: %s", cmd, stderr.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return stdout.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hashes returns the supported hash types of the filesystem
|
||||||
|
func (f *Fs) Hashes() hash.Set {
|
||||||
if f.opt.DisableHashCheck {
|
if f.opt.DisableHashCheck {
|
||||||
return hash.Set(hash.None)
|
return hash.Set(hash.None)
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := f.getSftpConnection()
|
if f.cachedHashes != nil {
|
||||||
if err != nil {
|
return *f.cachedHashes
|
||||||
fs.Errorf(f, "Couldn't get SSH connection to figure out Hashes: %v", err)
|
|
||||||
return hash.Set(hash.None)
|
|
||||||
}
|
}
|
||||||
defer f.putSftpConnection(&c, err)
|
|
||||||
|
|
||||||
// look for a hash command which works
|
// look for a hash command which works
|
||||||
checkHash := func(commands []string, expected string, hashCommand *string) bool {
|
checkHash := func(commands []string, expected string, hashCommand *string, changed *bool) bool {
|
||||||
|
if *hashCommand == hashCommandNotSupported {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if *hashCommand != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
*changed = true
|
||||||
for _, command := range commands {
|
for _, command := range commands {
|
||||||
session, err := c.sshClient.NewSession()
|
output, err := f.run(command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
output, _ := session.Output(command)
|
|
||||||
_ = session.Close()
|
|
||||||
output = bytes.TrimSpace(output)
|
output = bytes.TrimSpace(output)
|
||||||
fs.Debugf(f, "checking %q command: %q", command, output)
|
fs.Debugf(f, "checking %q command: %q", command, output)
|
||||||
ok := parseHash(output) == expected
|
if parseHash(output) == expected {
|
||||||
if ok {
|
|
||||||
*hashCommand = command
|
*hashCommand = command
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*hashCommand = hashCommandNotSupported
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
md5Works := checkHash([]string{"md5sum", "md5 -r"}, "d41d8cd98f00b204e9800998ecf8427e", &f.md5sum)
|
changed := false
|
||||||
sha1Works := checkHash([]string{"sha1sum", "sha1 -r"}, "da39a3ee5e6b4b0d3255bfef95601890afd80709", &f.sha1sum)
|
md5Works := checkHash([]string{"md5sum", "md5 -r"}, "d41d8cd98f00b204e9800998ecf8427e", &f.opt.Md5sumCommand, &changed)
|
||||||
|
sha1Works := checkHash([]string{"sha1sum", "sha1 -r"}, "da39a3ee5e6b4b0d3255bfef95601890afd80709", &f.opt.Sha1sumCommand, &changed)
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
f.m.Set("md5sum_command", f.opt.Md5sumCommand)
|
||||||
|
f.m.Set("sha1sum_command", f.opt.Sha1sumCommand)
|
||||||
|
}
|
||||||
|
|
||||||
set := hash.NewHashSet()
|
set := hash.NewHashSet()
|
||||||
if sha1Works {
|
if sha1Works {
|
||||||
|
@ -812,19 +857,6 @@ func (f *Fs) Hashes() hash.Set {
|
||||||
|
|
||||||
// About gets usage stats
|
// About gets usage stats
|
||||||
func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
|
func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
|
||||||
c, err := f.getSftpConnection()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "About get SFTP connection")
|
|
||||||
}
|
|
||||||
session, err := c.sshClient.NewSession()
|
|
||||||
f.putSftpConnection(&c, err)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "About put SFTP connection")
|
|
||||||
}
|
|
||||||
|
|
||||||
var stdout, stderr bytes.Buffer
|
|
||||||
session.Stdout = &stdout
|
|
||||||
session.Stderr = &stderr
|
|
||||||
escapedPath := shellEscape(f.root)
|
escapedPath := shellEscape(f.root)
|
||||||
if f.opt.PathOverride != "" {
|
if f.opt.PathOverride != "" {
|
||||||
escapedPath = shellEscape(path.Join(f.opt.PathOverride, f.root))
|
escapedPath = shellEscape(path.Join(f.opt.PathOverride, f.root))
|
||||||
|
@ -832,14 +864,12 @@ func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
|
||||||
if len(escapedPath) == 0 {
|
if len(escapedPath) == 0 {
|
||||||
escapedPath = "/"
|
escapedPath = "/"
|
||||||
}
|
}
|
||||||
err = session.Run("df -k " + escapedPath)
|
stdout, err := f.run("df -k " + escapedPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = session.Close()
|
return nil, errors.Wrap(err, "your remote may not support About")
|
||||||
return nil, errors.Wrap(err, "About invocation of df failed. Your remote may not support about.")
|
|
||||||
}
|
}
|
||||||
_ = session.Close()
|
|
||||||
|
|
||||||
usageTotal, usageUsed, usageAvail := parseUsage(stdout.Bytes())
|
usageTotal, usageUsed, usageAvail := parseUsage(stdout)
|
||||||
usage := &fs.Usage{}
|
usage := &fs.Usage{}
|
||||||
if usageTotal >= 0 {
|
if usageTotal >= 0 {
|
||||||
usage.Total = fs.NewUsageValue(usageTotal)
|
usage.Total = fs.NewUsageValue(usageTotal)
|
||||||
|
@ -874,26 +904,26 @@ func (o *Object) Remote() string {
|
||||||
// Hash returns the selected checksum of the file
|
// Hash returns the selected checksum of the file
|
||||||
// If no checksum is available it returns ""
|
// If no checksum is available it returns ""
|
||||||
func (o *Object) Hash(ctx context.Context, r hash.Type) (string, error) {
|
func (o *Object) Hash(ctx context.Context, r hash.Type) (string, error) {
|
||||||
_ = o.fs.Hashes()
|
|
||||||
if o.fs.opt.DisableHashCheck {
|
if o.fs.opt.DisableHashCheck {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
_ = o.fs.Hashes()
|
||||||
|
|
||||||
var hashCmd string
|
var hashCmd string
|
||||||
if r == hash.MD5 {
|
if r == hash.MD5 {
|
||||||
if o.md5sum != nil {
|
if o.md5sum != nil {
|
||||||
return *o.md5sum, nil
|
return *o.md5sum, nil
|
||||||
}
|
}
|
||||||
hashCmd = o.fs.md5sum
|
hashCmd = o.fs.opt.Md5sumCommand
|
||||||
} else if r == hash.SHA1 {
|
} else if r == hash.SHA1 {
|
||||||
if o.sha1sum != nil {
|
if o.sha1sum != nil {
|
||||||
return *o.sha1sum, nil
|
return *o.sha1sum, nil
|
||||||
}
|
}
|
||||||
hashCmd = o.fs.sha1sum
|
hashCmd = o.fs.opt.Sha1sumCommand
|
||||||
} else {
|
} else {
|
||||||
return "", hash.ErrUnsupported
|
return "", hash.ErrUnsupported
|
||||||
}
|
}
|
||||||
if hashCmd == "" {
|
if hashCmd == "" || hashCmd == hashCommandNotSupported {
|
||||||
return "", hash.ErrUnsupported
|
return "", hash.ErrUnsupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue