2018-10-30 17:45:30 +08:00
|
|
|
package ssh
|
|
|
|
|
|
|
|
import (
|
2018-11-13 17:42:29 +08:00
|
|
|
"bytes"
|
2018-10-30 17:45:30 +08:00
|
|
|
"errors"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Upload 上传本地文件 local 到sftp远程目录 remote like rsync
|
|
|
|
// rsync -av src/ dst ./src/* --> /root/dst/*
|
|
|
|
// rsync -av src/ dst/ ./src/* --> /root/dst/*
|
|
|
|
// rsync -av src dst ./src/* --> /root/dst/src/*
|
|
|
|
// rsync -av src dst/ ./src/* --> /root/dst/src/*
|
|
|
|
func (c *Client) Upload(local string, remote string) (err error) {
|
|
|
|
// var localDir, localFile, remoteDir, remoteFile string
|
|
|
|
|
|
|
|
info, err := os.Stat(local)
|
|
|
|
if err != nil {
|
2018-11-13 17:42:29 +08:00
|
|
|
return errors.New("sftp: 跳过上传 Upload(\"" + local + "\") ,本地文件不存在或格式错误!")
|
2018-10-30 17:45:30 +08:00
|
|
|
}
|
|
|
|
if info.IsDir() {
|
2018-11-13 17:42:29 +08:00
|
|
|
log.Println("sftp: UploadDir", local)
|
2018-10-30 17:45:30 +08:00
|
|
|
return c.UploadDir(local, remote)
|
|
|
|
}
|
|
|
|
return c.UploadFile(local, remote)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Download 下载sftp远程文件 remote 到本地 local like rsync
|
|
|
|
func (c *Client) Download(remote string, local string) (err error) {
|
|
|
|
if c.IsNotExist(strings.TrimSuffix(remote, "/")) {
|
2018-11-13 17:42:29 +08:00
|
|
|
return errors.New("sftp: 远程文件不存在,跳过文件下载 \"" + remote + "\" ")
|
2018-10-30 17:45:30 +08:00
|
|
|
}
|
|
|
|
if c.IsDir(remote) {
|
|
|
|
// return errors.New("检测到远程是文件不是目录 \"" + remote + "\" 跳过下载")
|
|
|
|
return c.downloadDir(remote, local)
|
|
|
|
|
|
|
|
}
|
|
|
|
return c.downloadFile(remote, local)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// downloadFile a file from the remote server like cp
|
|
|
|
func (c *Client) downloadFile(remoteFile, local string) error {
|
|
|
|
// remoteFile = strings.TrimSuffix(remoteFile, "/")
|
|
|
|
if !c.IsFile(remoteFile) {
|
2018-11-13 17:42:29 +08:00
|
|
|
return errors.New("sftp: 文件不存在或不是文件, 跳过目录下载 downloadFile(" + remoteFile + ")")
|
2018-10-30 17:45:30 +08:00
|
|
|
}
|
|
|
|
var localFile string
|
|
|
|
if local[len(local)-1] == '/' {
|
|
|
|
localFile = filepath.Join(local, filepath.Base(remoteFile))
|
|
|
|
} else {
|
|
|
|
localFile = local
|
|
|
|
}
|
2019-03-12 17:12:42 +08:00
|
|
|
localFile = filepath.ToSlash(localFile)
|
2018-11-13 17:42:29 +08:00
|
|
|
if c.Size(remoteFile) > 1000 {
|
|
|
|
rsum := c.Md5File(remoteFile)
|
2022-02-25 11:10:16 +08:00
|
|
|
ioutil.WriteFile(localFile+".md5", []byte(rsum), 0755)
|
2018-11-13 17:42:29 +08:00
|
|
|
if FileExist(localFile) {
|
|
|
|
if rsum != "" {
|
|
|
|
lsum, _ := Md5File(localFile)
|
|
|
|
if lsum == rsum {
|
2018-12-19 14:33:31 +08:00
|
|
|
log.Println("sftp: 文件与本地一致,跳过下载!", localFile)
|
2018-11-13 17:42:29 +08:00
|
|
|
return nil
|
|
|
|
}
|
2018-12-19 14:33:31 +08:00
|
|
|
log.Println("sftp: 正在下载 ", localFile)
|
2018-11-13 17:42:29 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-10-30 17:45:30 +08:00
|
|
|
if err := os.MkdirAll(filepath.Dir(localFile), os.ModePerm); err != nil {
|
2018-11-13 17:42:29 +08:00
|
|
|
// log.Println(err)
|
2018-10-30 17:45:30 +08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
r, err := c.SFTPClient.Open(remoteFile)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer r.Close()
|
|
|
|
|
|
|
|
l, err := os.Create(localFile)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer l.Close()
|
|
|
|
|
|
|
|
_, err = io.Copy(l, r)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// downloadDir from remote dir to local dir like rsync
|
|
|
|
// rsync -av src/ dst ./src/* --> /root/dst/*
|
|
|
|
// rsync -av src/ dst/ ./src/* --> /root/dst/*
|
|
|
|
// rsync -av src dst ./src/* --> /root/dst/src/*
|
|
|
|
// rsync -av src dst/ ./src/* --> /root/dst/src/*
|
|
|
|
func (c *Client) downloadDir(remote, local string) error {
|
|
|
|
var localDir, remoteDir string
|
|
|
|
|
|
|
|
if !c.IsDir(remote) {
|
2018-11-13 17:42:29 +08:00
|
|
|
return errors.New("sftp: 目录不存在或不是目录, 跳过 downloadDir(" + remote + ")")
|
2018-10-30 17:45:30 +08:00
|
|
|
}
|
|
|
|
remoteDir = remote
|
|
|
|
if remote[len(remote)-1] == '/' {
|
|
|
|
localDir = local
|
|
|
|
} else {
|
|
|
|
localDir = path.Join(local, path.Base(remote))
|
|
|
|
}
|
|
|
|
|
|
|
|
walker := c.SFTPClient.Walk(remoteDir)
|
|
|
|
|
|
|
|
for walker.Step() {
|
|
|
|
if err := walker.Err(); err != nil {
|
2018-11-13 17:42:29 +08:00
|
|
|
log.Println(err)
|
2018-10-30 17:45:30 +08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
info := walker.Stat()
|
|
|
|
|
|
|
|
relPath, err := filepath.Rel(remoteDir, walker.Path())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
localPath := filepath.ToSlash(filepath.Join(localDir, relPath))
|
|
|
|
|
|
|
|
// if we have something at the download path delete it if it is a directory
|
|
|
|
// and the remote is a file and vice a versa
|
|
|
|
localInfo, err := os.Stat(localPath)
|
|
|
|
if os.IsExist(err) {
|
|
|
|
if localInfo.IsDir() {
|
|
|
|
if info.IsDir() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
err = os.RemoveAll(localPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else if info.IsDir() {
|
|
|
|
err = os.Remove(localPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if info.IsDir() {
|
|
|
|
err = os.MkdirAll(localPath, os.ModePerm)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
c.downloadFile(walker.Path(), localPath)
|
|
|
|
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
//UploadFile 上传本地文件 localFile 到sftp远程目录 remote
|
|
|
|
func (c *Client) UploadFile(localFile, remote string) error {
|
|
|
|
// localFile = strings.TrimSuffix(localFile, "/")
|
2018-11-13 17:42:29 +08:00
|
|
|
// localFile = filepath.ToSlash(localFile)
|
2018-10-30 17:45:30 +08:00
|
|
|
info, err := os.Stat(localFile)
|
|
|
|
if err != nil || info.IsDir() {
|
2018-11-13 17:42:29 +08:00
|
|
|
return errors.New("sftp: 本地文件不存在,或是不是文件 UploadFile(\"" + localFile + "\") 跳过上传")
|
2018-10-30 17:45:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
l, err := os.Open(localFile)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer l.Close()
|
|
|
|
|
|
|
|
var remoteFile, remoteDir string
|
|
|
|
if remote[len(remote)-1] == '/' {
|
2018-11-13 17:42:29 +08:00
|
|
|
remoteFile = filepath.ToSlash(filepath.Join(remote, filepath.Base(localFile)))
|
2018-10-30 17:45:30 +08:00
|
|
|
remoteDir = remote
|
|
|
|
} else {
|
|
|
|
remoteFile = remote
|
2018-11-13 17:42:29 +08:00
|
|
|
remoteDir = filepath.ToSlash(filepath.Dir(remoteFile))
|
|
|
|
}
|
|
|
|
log.Println("sftp: UploadFile", localFile, remoteFile)
|
|
|
|
if info.Size() > 1000 {
|
|
|
|
// 1. 检测远程是否存在
|
|
|
|
rsum := c.Md5File(remoteFile)
|
|
|
|
if rsum != "" {
|
|
|
|
lsum, _ := Md5File(localFile)
|
|
|
|
if lsum == rsum {
|
|
|
|
log.Println("sftp: 文件与本地一致,跳过上传!", localFile)
|
|
|
|
return nil
|
|
|
|
}
|
2018-12-19 14:33:31 +08:00
|
|
|
log.Println("sftp: 正在上传 ", localFile)
|
2018-11-13 17:42:29 +08:00
|
|
|
}
|
2018-10-30 17:45:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 目录不存在,则创建 remoteDir
|
|
|
|
if _, err := c.SFTPClient.Stat(remoteDir); err != nil {
|
2018-11-13 17:42:29 +08:00
|
|
|
log.Println("sftp: Mkdir all", remoteDir)
|
2018-10-30 17:45:30 +08:00
|
|
|
c.MkdirAll(remoteDir)
|
|
|
|
}
|
|
|
|
|
|
|
|
r, err := c.SFTPClient.Create(remoteFile)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = io.Copy(r, l)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// UploadDir files without checking diff status
|
|
|
|
func (c *Client) UploadDir(localDir string, remoteDir string) (err error) {
|
|
|
|
// defer func() {
|
|
|
|
// if err != nil {
|
|
|
|
// err = errors.New("UploadDir " + err.Error())
|
|
|
|
// }
|
|
|
|
// }()
|
|
|
|
// 本地输入检测,必须是目录
|
2018-11-13 17:42:29 +08:00
|
|
|
// localDir = filepath.ToSlash(localDir)
|
2018-10-30 17:45:30 +08:00
|
|
|
info, err := os.Stat(localDir)
|
|
|
|
if err != nil || !info.IsDir() {
|
2018-11-13 17:42:29 +08:00
|
|
|
return errors.New("sftp: 本地目录不存在或不是目录 UploadDir(\"" + localDir + "\") 跳过上传")
|
2018-10-30 17:45:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 模仿 rsync localDir不以'/'结尾,则创建尾目录
|
|
|
|
if localDir[len(localDir)-1] != '/' {
|
2018-11-13 17:42:29 +08:00
|
|
|
remoteDir = filepath.ToSlash(filepath.Join(remoteDir, filepath.Base(localDir)))
|
2018-10-30 17:45:30 +08:00
|
|
|
}
|
2018-11-13 17:42:29 +08:00
|
|
|
log.Println("sftp: UploadDir", localDir, remoteDir)
|
2018-10-30 17:45:30 +08:00
|
|
|
|
|
|
|
rootDst := strings.TrimSuffix(remoteDir, "/")
|
|
|
|
if c.IsFile(rootDst) {
|
|
|
|
c.SFTPClient.Remove(rootDst)
|
|
|
|
}
|
|
|
|
|
|
|
|
walkFunc := func(path string, info os.FileInfo, err error) error {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Calculate the final destination using the
|
|
|
|
// base source and root destination
|
|
|
|
relSrc, err := filepath.Rel(localDir, path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
finalDst := filepath.Join(rootDst, relSrc)
|
|
|
|
|
|
|
|
// In Windows, Join uses backslashes which we don't want to get
|
|
|
|
// to the sftp server
|
|
|
|
finalDst = filepath.ToSlash(finalDst)
|
|
|
|
|
|
|
|
// Skip the creation of the target destination directory since
|
|
|
|
// it should exist and we might not even own it
|
|
|
|
if finalDst == remoteDir {
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if info.IsDir() {
|
2018-11-13 17:42:29 +08:00
|
|
|
err := c.MkdirAll(finalDst)
|
|
|
|
if err != nil {
|
|
|
|
log.Println("sftp: MkdirAll", err)
|
|
|
|
}
|
|
|
|
// log.Println("MkdirAll", finalDst)
|
2018-10-30 17:45:30 +08:00
|
|
|
// err = c.SFTPClient.Mkdir(finalDst)
|
2018-11-13 17:42:29 +08:00
|
|
|
// log.Println(err)
|
2018-10-30 17:45:30 +08:00
|
|
|
// if err := c.SFTPClient.Mkdir(finalDst); err != nil {
|
|
|
|
// // Do not consider it an error if the directory existed
|
|
|
|
// remoteFi, fiErr := c.SFTPClient.Lstat(finalDst)
|
|
|
|
// if fiErr != nil || !remoteFi.IsDir() {
|
|
|
|
// return err
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// return err
|
|
|
|
} else {
|
|
|
|
// f, err := os.Open(path)
|
|
|
|
// if err != nil {
|
|
|
|
// return err
|
|
|
|
// }
|
|
|
|
// defer f.Close()
|
|
|
|
return c.UploadFile(path, finalDst)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
return filepath.Walk(localDir, walkFunc)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove a file from the remote server
|
|
|
|
func (c *Client) Remove(path string) error {
|
|
|
|
return c.SFTPClient.Remove(path)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveDirectory Remove a directory from the remote server
|
|
|
|
func (c *Client) RemoveDirectory(path string) error {
|
|
|
|
return c.SFTPClient.RemoveDirectory(path)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadAll Read a remote file and return the contents.
|
|
|
|
func (c *Client) ReadAll(filepath string) ([]byte, error) {
|
|
|
|
file, err := c.SFTPClient.Open(filepath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
return ioutil.ReadAll(file)
|
|
|
|
}
|
|
|
|
|
|
|
|
//FileExist 文件是否存在
|
|
|
|
func (c *Client) FileExist(filepath string) (bool, error) {
|
|
|
|
if _, err := c.SFTPClient.Stat(filepath); err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) RemoveFile(remoteFile string) error {
|
|
|
|
return c.SFTPClient.Remove(remoteFile)
|
|
|
|
}
|
|
|
|
func (c *Client) RemoveDir(remoteDir string) error {
|
|
|
|
remoteFiles, err := c.SFTPClient.ReadDir(remoteDir)
|
|
|
|
if err != nil {
|
2018-11-13 17:42:29 +08:00
|
|
|
log.Printf("sftp: remove remote dir: %s err: %v\n", remoteDir, err)
|
2018-10-30 17:45:30 +08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, file := range remoteFiles {
|
|
|
|
subRemovePath := path.Join(remoteDir, file.Name())
|
|
|
|
if file.IsDir() {
|
|
|
|
c.RemoveDir(subRemovePath)
|
|
|
|
} else {
|
|
|
|
c.RemoveFile(subRemovePath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.SFTPClient.RemoveDirectory(remoteDir) //must empty dir to remove
|
2018-11-13 17:42:29 +08:00
|
|
|
log.Printf("sftp: remove remote dir: %s ok\n", remoteDir)
|
2018-10-30 17:45:30 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
//RemoveAll 递归删除目录,文件
|
|
|
|
func (c *Client) RemoveAll(remoteDir string) error {
|
|
|
|
c.RemoveDir(remoteDir)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
//MkdirAll 创建目录,递归
|
|
|
|
func (c *Client) MkdirAll(dirpath string) error {
|
2022-02-25 11:10:16 +08:00
|
|
|
_, err := c.SFTPClient.Stat(dirpath)
|
2018-10-30 17:45:30 +08:00
|
|
|
if err != nil {
|
|
|
|
if err.Error() == "file does not exist" {
|
2022-02-25 11:10:16 +08:00
|
|
|
e := c.MkdirAll(filepath.ToSlash(filepath.Dir(dirpath)))
|
|
|
|
if e != nil {
|
|
|
|
return e
|
2018-10-30 17:45:30 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2022-02-25 11:10:16 +08:00
|
|
|
if err != nil && err.Error() == "file does not exist" {
|
|
|
|
err = c.SFTPClient.Mkdir(dirpath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-10-30 17:45:30 +08:00
|
|
|
}
|
2022-02-25 11:10:16 +08:00
|
|
|
return err
|
2018-10-30 17:45:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) Mkdir(path string, fi os.FileInfo) error {
|
|
|
|
log.Printf("[DEBUG] sftp: creating dir %s", path)
|
|
|
|
|
|
|
|
if err := c.SFTPClient.Mkdir(path); err != nil {
|
|
|
|
// Do not consider it an error if the directory existed
|
|
|
|
remoteFi, fiErr := c.SFTPClient.Lstat(path)
|
|
|
|
if fiErr != nil || !remoteFi.IsDir() {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mode := fi.Mode().Perm()
|
|
|
|
if err := c.SFTPClient.Chmod(path, mode); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
//IsDir 检查远程是否是个目录
|
|
|
|
func (c *Client) IsDir(path string) bool {
|
|
|
|
// 检查远程是文件还是目录
|
|
|
|
info, err := c.SFTPClient.Stat(path)
|
|
|
|
if err == nil && info.IsDir() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2018-11-13 17:42:29 +08:00
|
|
|
//Size 获取文件大小
|
|
|
|
func (c *Client) Size(path string) int64 {
|
|
|
|
info, err := c.SFTPClient.Stat(path)
|
|
|
|
if err != nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return info.Size()
|
|
|
|
}
|
|
|
|
|
2018-10-30 17:45:30 +08:00
|
|
|
//IsFile 检查远程是否是个文件
|
|
|
|
func (c *Client) IsFile(path string) bool {
|
|
|
|
info, err := c.SFTPClient.Stat(path)
|
|
|
|
if err == nil && !info.IsDir() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
//IsNotExist 检查远程是文件是否不存在
|
|
|
|
func (c *Client) IsNotExist(path string) bool {
|
|
|
|
_, err := c.SFTPClient.Stat(path)
|
|
|
|
return err != nil
|
|
|
|
}
|
|
|
|
|
|
|
|
//IsExist 检查远程是文件是否存在
|
|
|
|
func (c *Client) IsExist(path string) bool {
|
|
|
|
|
|
|
|
_, err := c.SFTPClient.Stat(path)
|
|
|
|
return err == nil
|
|
|
|
}
|
2018-11-13 17:42:29 +08:00
|
|
|
|
|
|
|
//Md5File 检查远程是文件是否存在
|
|
|
|
func (c *Client) Md5File(path string) string {
|
|
|
|
if c.IsNotExist(path) {
|
|
|
|
return ""
|
|
|
|
}
|
2018-12-19 14:33:31 +08:00
|
|
|
b, err := c.Output("md5sum " + path)
|
2018-11-13 17:42:29 +08:00
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return string(bytes.Split(b, []byte{' '})[0])
|
|
|
|
|
|
|
|
}
|