mirror of
https://github.com/labring/sealos.git
synced 2026-10-29 17:25:58 +08:00
feat(develop): sshcmd vendor
This commit is contained in:
-202
@@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/wonderivan/logger"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
//Cmd is exec on os ,no return
|
||||
func Cmd(name string, arg ...string) {
|
||||
logger.Info("[os]exec cmd is : ", name, arg)
|
||||
cmd := exec.Command(name, arg[:]...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdout = os.Stdout
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
logger.Error("[os]os call error.", err)
|
||||
}
|
||||
}
|
||||
|
||||
//CmdToString is exec on os , return result
|
||||
func CmdToString(name string, arg ...string) string {
|
||||
logger.Info("[os]exec cmd is : ", name, arg)
|
||||
cmd := exec.Command(name, arg[:]...)
|
||||
cmd.Stdin = os.Stdin
|
||||
var b bytes.Buffer
|
||||
cmd.Stdout = &b
|
||||
cmd.Stderr = &b
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
logger.Error("[os]os call error.", err)
|
||||
return ""
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/wonderivan/logger"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const oneMBByte = 1024 * 1024
|
||||
|
||||
//WatchFileSize is
|
||||
func LoggerFileSize(filename string, size int) {
|
||||
t := time.NewTicker(3 * time.Second) //every 3s check file
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
length := CmdToString("/bin/sh", "-c", "ls -l "+filename+" | awk '{print $5}'", "")
|
||||
length = strings.Replace(length, "\n", "", -1)
|
||||
length = strings.Replace(length, "\r", "", -1)
|
||||
lengthByte, _ := strconv.Atoi(length)
|
||||
if lengthByte == size {
|
||||
t.Stop()
|
||||
}
|
||||
lengthFloat := float64(lengthByte)
|
||||
value, _ := strconv.ParseFloat(fmt.Sprintf("%.2f", lengthFloat/oneMBByte), 64)
|
||||
logger.Alert("[os][%s]transfer total size is: %.2f%s", filename, value, "MB")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//RemoteFilExist is
|
||||
func IsFilExist(filepath string) bool {
|
||||
// if remote file is
|
||||
// ls -l | grep aa | wc -l
|
||||
fileName := path.Base(filepath) // aa
|
||||
fileDirName := path.Dir(filepath)
|
||||
fileCommand := fmt.Sprintf("ls -l %s | grep %s | wc -l", fileDirName, fileName)
|
||||
data := strings.Replace(CmdToString("/bin/sh", "-c", fileCommand), "\r", "", -1)
|
||||
data = strings.Replace(data, "\n", "", -1)
|
||||
count, err := strconv.Atoi(strings.TrimSpace(data))
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Error("[os][%s]RemoteFilExist:%s", filepath, err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
panic(1)
|
||||
}
|
||||
if count == 0 {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
package md5sum
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/wonderivan/logger"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func FromLocal(localPath string) string {
|
||||
cmd := fmt.Sprintf("md5sum %s | cut -d\" \" -f1", localPath)
|
||||
c := exec.Command("sh", "-c", cmd)
|
||||
out, err := c.CombinedOutput()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
md5 := string(out)
|
||||
md5 = strings.ReplaceAll(md5, "\n", "")
|
||||
md5 = strings.ReplaceAll(md5, "\r", "")
|
||||
|
||||
return md5
|
||||
}
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
package sshutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/wonderivan/logger"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
/**
|
||||
这里主要是做连接ssh操作的
|
||||
*/
|
||||
func (ss *SSH) Connect(host string) (*ssh.Session, error) {
|
||||
auth := []ssh.AuthMethod{ss.sshAuthMethod(ss.Password, ss.PkFile)}
|
||||
config := ssh.Config{
|
||||
Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc"},
|
||||
}
|
||||
DefaultTimeout := time.Duration(1) * time.Minute
|
||||
if ss.Timeout == nil {
|
||||
ss.Timeout = &DefaultTimeout
|
||||
}
|
||||
clientConfig := &ssh.ClientConfig{
|
||||
User: ss.User,
|
||||
Auth: auth,
|
||||
Timeout: *ss.Timeout,
|
||||
Config: config,
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
addr := ss.addrReformat(host)
|
||||
client, err := ssh.Dial("tcp", addr, clientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
modes := ssh.TerminalModes{
|
||||
ssh.ECHO: 0, // disable echoing
|
||||
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
|
||||
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
|
||||
}
|
||||
|
||||
if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (ss *SSH) sshAuthMethod(passwd, pkFile string) ssh.AuthMethod {
|
||||
var am ssh.AuthMethod
|
||||
if passwd != "" {
|
||||
am = ssh.Password(passwd)
|
||||
} else {
|
||||
pkData := ss.readFile(pkFile)
|
||||
pk, err := ssh.ParsePrivateKey([]byte(pkData))
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
am = ssh.PublicKeys(pk)
|
||||
}
|
||||
return am
|
||||
}
|
||||
|
||||
func (ss *SSH) readFile(name string) string {
|
||||
content, err := ioutil.ReadFile(name)
|
||||
if err != nil {
|
||||
logger.Error("[globals] read file err is : %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return string(content)
|
||||
}
|
||||
|
||||
func (ss *SSH) addrReformat(host string) string {
|
||||
if strings.Index(host, ":") == -1 {
|
||||
host = fmt.Sprintf("%s:22", host)
|
||||
}
|
||||
return host
|
||||
}
|
||||
-123
@@ -1,123 +0,0 @@
|
||||
package sshutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cuisongliu/sshcmd/pkg/md5sum"
|
||||
"github.com/pkg/sftp"
|
||||
"github.com/wonderivan/logger"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
//Copy is
|
||||
func (ss *SSH) CopyForMD5(host, localFilePath, remoteFilePath, md5 string) bool {
|
||||
//如果有md5则可以验证
|
||||
//如果没有md5则拿到本地数据后验证
|
||||
if md5 == "" {
|
||||
md5 = md5sum.FromLocal(localFilePath)
|
||||
}
|
||||
logger.Debug("[ssh]source file md5 value is %s", md5)
|
||||
ss.Copy(host, localFilePath, remoteFilePath)
|
||||
remoteMD5 := ss.Md5Sum(host, remoteFilePath)
|
||||
logger.Debug("[ssh]host: %s , remote md5: %s", host, remoteMD5)
|
||||
remoteMD5 = strings.TrimSpace(remoteMD5)
|
||||
md5 = strings.TrimSpace(md5)
|
||||
if remoteMD5 == md5 {
|
||||
logger.Info("[ssh]md5 validate true")
|
||||
return true
|
||||
}
|
||||
logger.Error("[ssh]md5 validate false")
|
||||
return false
|
||||
}
|
||||
func (ss *SSH) Md5Sum(host, remoteFilePath string) string {
|
||||
cmd := fmt.Sprintf("md5sum %s | cut -d\" \" -f1", remoteFilePath)
|
||||
remoteMD5 := ss.CmdToString(host, cmd, "")
|
||||
return remoteMD5
|
||||
}
|
||||
|
||||
//Copy is
|
||||
func (ss *SSH) Copy(host, localFilePath, remoteFilePath string) {
|
||||
sftpClient, err := ss.sftpConnect(host)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Error("[ssh][%s]scpCopy: %s", host, err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
panic(1)
|
||||
}
|
||||
defer sftpClient.Close()
|
||||
srcFile, err := os.Open(localFilePath)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Error("[ssh][%s]scpCopy: %s", host, err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
panic(1)
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
dstFile, err := sftpClient.Create(remoteFilePath)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Error("[ssh][%s]scpCopy: %s", host, err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
panic(1)
|
||||
}
|
||||
defer dstFile.Close()
|
||||
buf := make([]byte, 100*oneMBByte) //100mb
|
||||
totalMB := 0
|
||||
for {
|
||||
n, _ := srcFile.Read(buf)
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
length, _ := dstFile.Write(buf[0:n])
|
||||
totalMB += length / oneMBByte
|
||||
logger.Alert("[ssh][%s]transfer total size is: %d%s", host, totalMB, "MB")
|
||||
}
|
||||
}
|
||||
|
||||
//SftpConnect is
|
||||
func (ss *SSH) sftpConnect(host string) (*sftp.Client, error) {
|
||||
var (
|
||||
auth []ssh.AuthMethod
|
||||
addr string
|
||||
clientConfig *ssh.ClientConfig
|
||||
sshClient *ssh.Client
|
||||
sftpClient *sftp.Client
|
||||
err error
|
||||
)
|
||||
// get auth method
|
||||
auth = make([]ssh.AuthMethod, 0)
|
||||
auth = append(auth, ss.sshAuthMethod(ss.Password, ss.PkFile))
|
||||
|
||||
clientConfig = &ssh.ClientConfig{
|
||||
User: ss.User,
|
||||
Auth: auth,
|
||||
Timeout: 30 * time.Second,
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// connet to ssh
|
||||
addr = ss.addrReformat(host)
|
||||
|
||||
if sshClient, err = ssh.Dial("tcp", addr, clientConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create sftp client
|
||||
if sftpClient, err = sftp.NewClient(sshClient); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sftpClient, nil
|
||||
}
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
package sshutil
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/wonderivan/logger"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//Cmd is in host exec cmd
|
||||
func (ss *SSH) Cmd(host string, cmd string) []byte {
|
||||
logger.Info("[ssh][%s]exec cmd is : %s", host, cmd)
|
||||
session, err := ss.Connect(host)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Error("[ssh][%s]Error create ssh session failed,%s", host, err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
panic(1)
|
||||
}
|
||||
defer session.Close()
|
||||
b, err := session.CombinedOutput(cmd)
|
||||
logger.Debug("[ssh][%s]command result is: %s", host, string(b))
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Error("[ssh][%s]Error exec command failed: %s", host, err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
panic(1)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (ss *SSH) CmdAsync(host string, cmd string) error {
|
||||
logger.Info("[ssh][%s]exec cmd is : %s", host, cmd)
|
||||
session, err := ss.Connect(host)
|
||||
if err != nil {
|
||||
logger.Error("[ssh][%s]Error create ssh session failed,%s", host, err)
|
||||
return err
|
||||
}
|
||||
defer session.Close()
|
||||
stdout, err := session.StdoutPipe()
|
||||
if err != nil {
|
||||
logger.Error("[ssh][%s]Unable to request StdoutPipe(): %s", host, err)
|
||||
return err
|
||||
}
|
||||
if err := session.Start(cmd); err != nil {
|
||||
logger.Error("[ssh][%s]Unable to execute command: %s", host, err)
|
||||
return err
|
||||
}
|
||||
done := make(chan bool, 1)
|
||||
go func() {
|
||||
for {
|
||||
r := bufio.NewReader(stdout)
|
||||
line, _, err := r.ReadLine()
|
||||
if line == nil || err != nil {
|
||||
done <- true
|
||||
} else {
|
||||
logger.Info("[ssh][%s]: %s", host, line)
|
||||
}
|
||||
}
|
||||
}()
|
||||
<-done
|
||||
return nil
|
||||
}
|
||||
|
||||
//CmdToString is in host exec cmd and replace to spilt str
|
||||
func (ss *SSH) CmdToString(host, cmd, spilt string) string {
|
||||
data := ss.Cmd(host, cmd)
|
||||
if data != nil {
|
||||
str := string(data)
|
||||
str = strings.ReplaceAll(str, "\r\n", spilt)
|
||||
return str
|
||||
}
|
||||
return ""
|
||||
}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
package sshutil
|
||||
|
||||
import "time"
|
||||
|
||||
type SSH struct {
|
||||
User string
|
||||
Password string
|
||||
PkFile string
|
||||
Timeout *time.Duration
|
||||
}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
package sshutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/wonderivan/logger"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const oneMBByte = 1024 * 1024
|
||||
|
||||
//WatchFileSize is
|
||||
func (ss *SSH) LoggerFileSize(host, filename string, size int) {
|
||||
t := time.NewTicker(3 * time.Second) //every 3s check file
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
length := ss.CmdToString(host, "ls -l "+filename+" | awk '{print $5}'", "")
|
||||
length = strings.Replace(length, "\n", "", -1)
|
||||
length = strings.Replace(length, "\r", "", -1)
|
||||
lengthByte, _ := strconv.Atoi(length)
|
||||
if lengthByte == size {
|
||||
t.Stop()
|
||||
}
|
||||
lengthFloat := float64(lengthByte)
|
||||
value, _ := strconv.ParseFloat(fmt.Sprintf("%.2f", lengthFloat/oneMBByte), 64)
|
||||
logger.Alert("[ssh][%s]transfer total size is: %.2f%s", host, value, "MB")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//RemoteFilExist is
|
||||
func (ss *SSH) IsFilExist(host, remoteFilePath string) bool {
|
||||
// if remote file is
|
||||
// ls -l | grep aa | wc -l
|
||||
remoteFileName := path.Base(remoteFilePath) // aa
|
||||
remoteFileDirName := path.Dir(remoteFilePath)
|
||||
//it's bug: if file is aa.bak, `ls -l | grep aa | wc -l` is 1 ,should use `ll aa 2>/dev/null |wc -l`
|
||||
//remoteFileCommand := fmt.Sprintf("ls -l %s| grep %s | grep -v grep |wc -l", remoteFileDirName, remoteFileName)
|
||||
remoteFileCommand := fmt.Sprintf("ls -l %s/%s 2>/dev/null |wc -l", remoteFileDirName, remoteFileName)
|
||||
|
||||
data := ss.CmdToString(host, remoteFileCommand, " ")
|
||||
count, err := strconv.Atoi(strings.TrimSpace(data))
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Error("[ssh][%s]RemoteFilExist:%s", host, err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
panic(1)
|
||||
}
|
||||
if count == 0 {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -4,25 +4,25 @@ go_import_path: github.com/pkg/sftp
|
||||
# current and previous stable releases, plus tip
|
||||
# remember to exclude previous and tip for macs below
|
||||
go:
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
- tip
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
env:
|
||||
global:
|
||||
- GO111MODULE=on
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- os: osx
|
||||
go: 1.10.x
|
||||
go: 1.12.x
|
||||
- os: osx
|
||||
go: tip
|
||||
|
||||
env:
|
||||
global:
|
||||
- GO111MODULE=on
|
||||
|
||||
addons:
|
||||
ssh_known_hosts:
|
||||
- bitbucket.org
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
integration:
|
||||
go test -integration -v
|
||||
go test -testserver -v
|
||||
go test -integration -testserver -v
|
||||
|
||||
integration_w_race:
|
||||
go test -race -integration -v
|
||||
go test -race -testserver -v
|
||||
go test -race -integration -testserver -v
|
||||
|
||||
|
||||
+31
-28
@@ -10,11 +10,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ssh_FILEXFER_ATTR_SIZE = 0x00000001
|
||||
ssh_FILEXFER_ATTR_UIDGID = 0x00000002
|
||||
ssh_FILEXFER_ATTR_PERMISSIONS = 0x00000004
|
||||
ssh_FILEXFER_ATTR_ACMODTIME = 0x00000008
|
||||
ssh_FILEXFER_ATTR_EXTENDED = 0x80000000
|
||||
sshFileXferAttrSize = 0x00000001
|
||||
sshFileXferAttrUIDGID = 0x00000002
|
||||
sshFileXferAttrPermissions = 0x00000004
|
||||
sshFileXferAttrACmodTime = 0x00000008
|
||||
sshFileXferAttrExtented = 0x80000000
|
||||
|
||||
sshFileXferAttrAll = sshFileXferAttrSize | sshFileXferAttrUIDGID | sshFileXferAttrPermissions |
|
||||
sshFileXferAttrACmodTime | sshFileXferAttrExtented
|
||||
)
|
||||
|
||||
// fileInfo is an artificial type designed to satisfy os.FileInfo.
|
||||
@@ -77,9 +80,9 @@ func fileInfoFromStat(st *FileStat, name string) os.FileInfo {
|
||||
func fileStatFromInfo(fi os.FileInfo) (uint32, FileStat) {
|
||||
mtime := fi.ModTime().Unix()
|
||||
atime := mtime
|
||||
var flags uint32 = ssh_FILEXFER_ATTR_SIZE |
|
||||
ssh_FILEXFER_ATTR_PERMISSIONS |
|
||||
ssh_FILEXFER_ATTR_ACMODTIME
|
||||
var flags uint32 = sshFileXferAttrSize |
|
||||
sshFileXferAttrPermissions |
|
||||
sshFileXferAttrACmodTime
|
||||
|
||||
fileStat := FileStat{
|
||||
Size: uint64(fi.Size()),
|
||||
@@ -101,31 +104,31 @@ func unmarshalAttrs(b []byte) (*FileStat, []byte) {
|
||||
|
||||
func getFileStat(flags uint32, b []byte) (*FileStat, []byte) {
|
||||
var fs FileStat
|
||||
if flags&ssh_FILEXFER_ATTR_SIZE == ssh_FILEXFER_ATTR_SIZE {
|
||||
fs.Size, b = unmarshalUint64(b)
|
||||
if flags&sshFileXferAttrSize == sshFileXferAttrSize {
|
||||
fs.Size, b, _ = unmarshalUint64Safe(b)
|
||||
}
|
||||
if flags&ssh_FILEXFER_ATTR_UIDGID == ssh_FILEXFER_ATTR_UIDGID {
|
||||
fs.UID, b = unmarshalUint32(b)
|
||||
if flags&sshFileXferAttrUIDGID == sshFileXferAttrUIDGID {
|
||||
fs.UID, b, _ = unmarshalUint32Safe(b)
|
||||
}
|
||||
if flags&ssh_FILEXFER_ATTR_UIDGID == ssh_FILEXFER_ATTR_UIDGID {
|
||||
fs.GID, b = unmarshalUint32(b)
|
||||
if flags&sshFileXferAttrUIDGID == sshFileXferAttrUIDGID {
|
||||
fs.GID, b, _ = unmarshalUint32Safe(b)
|
||||
}
|
||||
if flags&ssh_FILEXFER_ATTR_PERMISSIONS == ssh_FILEXFER_ATTR_PERMISSIONS {
|
||||
fs.Mode, b = unmarshalUint32(b)
|
||||
if flags&sshFileXferAttrPermissions == sshFileXferAttrPermissions {
|
||||
fs.Mode, b, _ = unmarshalUint32Safe(b)
|
||||
}
|
||||
if flags&ssh_FILEXFER_ATTR_ACMODTIME == ssh_FILEXFER_ATTR_ACMODTIME {
|
||||
fs.Atime, b = unmarshalUint32(b)
|
||||
fs.Mtime, b = unmarshalUint32(b)
|
||||
if flags&sshFileXferAttrACmodTime == sshFileXferAttrACmodTime {
|
||||
fs.Atime, b, _ = unmarshalUint32Safe(b)
|
||||
fs.Mtime, b, _ = unmarshalUint32Safe(b)
|
||||
}
|
||||
if flags&ssh_FILEXFER_ATTR_EXTENDED == ssh_FILEXFER_ATTR_EXTENDED {
|
||||
if flags&sshFileXferAttrExtented == sshFileXferAttrExtented {
|
||||
var count uint32
|
||||
count, b = unmarshalUint32(b)
|
||||
count, b, _ = unmarshalUint32Safe(b)
|
||||
ext := make([]StatExtended, count)
|
||||
for i := uint32(0); i < count; i++ {
|
||||
var typ string
|
||||
var data string
|
||||
typ, b = unmarshalString(b)
|
||||
data, b = unmarshalString(b)
|
||||
typ, b, _ = unmarshalStringSafe(b)
|
||||
data, b, _ = unmarshalStringSafe(b)
|
||||
ext[i] = StatExtended{typ, data}
|
||||
}
|
||||
fs.Extended = ext
|
||||
@@ -152,17 +155,17 @@ func marshalFileInfo(b []byte, fi os.FileInfo) []byte {
|
||||
flags, fileStat := fileStatFromInfo(fi)
|
||||
|
||||
b = marshalUint32(b, flags)
|
||||
if flags&ssh_FILEXFER_ATTR_SIZE != 0 {
|
||||
if flags&sshFileXferAttrSize != 0 {
|
||||
b = marshalUint64(b, fileStat.Size)
|
||||
}
|
||||
if flags&ssh_FILEXFER_ATTR_UIDGID != 0 {
|
||||
if flags&sshFileXferAttrUIDGID != 0 {
|
||||
b = marshalUint32(b, fileStat.UID)
|
||||
b = marshalUint32(b, fileStat.GID)
|
||||
}
|
||||
if flags&ssh_FILEXFER_ATTR_PERMISSIONS != 0 {
|
||||
if flags&sshFileXferAttrPermissions != 0 {
|
||||
b = marshalUint32(b, fileStat.Mode)
|
||||
}
|
||||
if flags&ssh_FILEXFER_ATTR_ACMODTIME != 0 {
|
||||
if flags&sshFileXferAttrACmodTime != 0 {
|
||||
b = marshalUint32(b, fileStat.Atime)
|
||||
b = marshalUint32(b, fileStat.Mtime)
|
||||
}
|
||||
@@ -173,7 +176,7 @@ func marshalFileInfo(b []byte, fi os.FileInfo) []byte {
|
||||
// toFileMode converts sftp filemode bits to the os.FileMode specification
|
||||
func toFileMode(mode uint32) os.FileMode {
|
||||
var fm = os.FileMode(mode & 0777)
|
||||
switch mode & syscall.S_IFMT {
|
||||
switch mode & S_IFMT {
|
||||
case syscall.S_IFBLK:
|
||||
fm |= os.ModeDevice
|
||||
case syscall.S_IFCHR:
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
func fileStatFromInfoOs(fi os.FileInfo, flags *uint32, fileStat *FileStat) {
|
||||
if statt, ok := fi.Sys().(*syscall.Stat_t); ok {
|
||||
*flags |= ssh_FILEXFER_ATTR_UIDGID
|
||||
*flags |= sshFileXferAttrUIDGID
|
||||
fileStat.UID = statt.Uid
|
||||
fileStat.GID = statt.Gid
|
||||
}
|
||||
|
||||
+126
-61
@@ -15,12 +15,18 @@ import (
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// InternalInconsistency indicates the packets sent and the data queued to be
|
||||
// written to the file don't match up. It is an unusual error and usually is
|
||||
// caused by bad behavior server side or connection issues. The error is
|
||||
// limited in scope to the call where it happened, the client object is still
|
||||
// OK to use as long as the connection is still open.
|
||||
var InternalInconsistency = errors.New("internal inconsistency")
|
||||
var (
|
||||
// ErrInternalInconsistency indicates the packets sent and the data queued to be
|
||||
// written to the file don't match up. It is an unusual error and usually is
|
||||
// caused by bad behavior server side or connection issues. The error is
|
||||
// limited in scope to the call where it happened, the client object is still
|
||||
// OK to use as long as the connection is still open.
|
||||
ErrInternalInconsistency = errors.New("internal inconsistency")
|
||||
// InternalInconsistency alias for ErrInternalInconsistency.
|
||||
//
|
||||
// Deprecated: please use ErrInternalInconsistency
|
||||
InternalInconsistency = ErrInternalInconsistency
|
||||
)
|
||||
|
||||
// A ClientOption is a function which applies configuration to a Client.
|
||||
type ClientOption func(*Client) error
|
||||
@@ -45,6 +51,30 @@ func MaxPacketChecked(size int) ClientOption {
|
||||
}
|
||||
}
|
||||
|
||||
// UseFstat sets whether to use Fstat or Stat when File.WriteTo is called
|
||||
// (usually when copying files).
|
||||
// Some servers limit the amount of open files and calling Stat after opening
|
||||
// the file will throw an error From the server. Setting this flag will call
|
||||
// Fstat instead of Stat which is suppose to be called on an open file handle.
|
||||
//
|
||||
// It has been found that that with IBM Sterling SFTP servers which have
|
||||
// "extractability" level set to 1 which means only 1 file can be opened at
|
||||
// any given time.
|
||||
//
|
||||
// If the server you are working with still has an issue with both Stat and
|
||||
// Fstat calls you can always open a file and read it until the end.
|
||||
//
|
||||
// Another reason to read the file until its end and Fstat doesn't work is
|
||||
// that in some servers, reading a full file will automatically delete the
|
||||
// file as some of these mainframes map the file to a message in a queue.
|
||||
// Once the file has been read it will get deleted.
|
||||
func UseFstat(value bool) ClientOption {
|
||||
return func(c *Client) error {
|
||||
c.useFstat = value
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MaxPacketUnchecked sets the maximum size of the payload, measured in bytes.
|
||||
// It accepts sizes larger than the 32768 bytes all servers should support.
|
||||
// Only use a setting higher than 32768 if your application always connects to
|
||||
@@ -155,12 +185,17 @@ type Client struct {
|
||||
maxPacket int // max packet size read or written.
|
||||
nextid uint32
|
||||
maxConcurrentRequests int
|
||||
useFstat bool
|
||||
}
|
||||
|
||||
// Create creates the named file mode 0666 (before umask), truncating it if it
|
||||
// already exists. If successful, methods on the returned File can be used for
|
||||
// I/O; the associated file descriptor has mode O_RDWR. If you need more
|
||||
// control over the flags/mode used to open the file see client.OpenFile.
|
||||
//
|
||||
// Note that some SFTP servers (eg. AWS Transfer) do not support opening files
|
||||
// read/write at the same time. For those services you will need to use
|
||||
// `client.OpenFile(os.O_WRONLY|os.O_CREATE|os.O_TRUNC)`.
|
||||
func (c *Client) Create(path string) (*File, error) {
|
||||
return c.open(path, flags(os.O_RDWR|os.O_CREATE|os.O_TRUNC))
|
||||
}
|
||||
@@ -183,8 +218,8 @@ func (c *Client) recvVersion() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if typ != ssh_FXP_VERSION {
|
||||
return &unexpectedPacketErr{ssh_FXP_VERSION, typ}
|
||||
if typ != sshFxpVersion {
|
||||
return &unexpectedPacketErr{sshFxpVersion, typ}
|
||||
}
|
||||
|
||||
version, _ := unmarshalUint32(data)
|
||||
@@ -222,7 +257,7 @@ func (c *Client) ReadDir(p string) ([]os.FileInfo, error) {
|
||||
break
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_NAME:
|
||||
case sshFxpName:
|
||||
sid, data := unmarshalUint32(data)
|
||||
if sid != id {
|
||||
return nil, &unexpectedIDErr{id, sid}
|
||||
@@ -239,7 +274,7 @@ func (c *Client) ReadDir(p string) ([]os.FileInfo, error) {
|
||||
}
|
||||
attrs = append(attrs, fileInfoFromStat(attr, path.Base(filename)))
|
||||
}
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
// TODO(dfc) scope warning!
|
||||
err = normaliseError(unmarshalStatus(id, data))
|
||||
done = true
|
||||
@@ -263,14 +298,14 @@ func (c *Client) opendir(path string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_HANDLE:
|
||||
case sshFxpHandle:
|
||||
sid, data := unmarshalUint32(data)
|
||||
if sid != id {
|
||||
return "", &unexpectedIDErr{id, sid}
|
||||
}
|
||||
handle, _ := unmarshalString(data)
|
||||
return handle, nil
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
return "", normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return "", unimplementedPacketErr(typ)
|
||||
@@ -289,14 +324,14 @@ func (c *Client) Stat(p string) (os.FileInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_ATTRS:
|
||||
case sshFxpAttrs:
|
||||
sid, data := unmarshalUint32(data)
|
||||
if sid != id {
|
||||
return nil, &unexpectedIDErr{id, sid}
|
||||
}
|
||||
attr, _ := unmarshalAttrs(data)
|
||||
return fileInfoFromStat(attr, path.Base(p)), nil
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
return nil, normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return nil, unimplementedPacketErr(typ)
|
||||
@@ -315,14 +350,14 @@ func (c *Client) Lstat(p string) (os.FileInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_ATTRS:
|
||||
case sshFxpAttrs:
|
||||
sid, data := unmarshalUint32(data)
|
||||
if sid != id {
|
||||
return nil, &unexpectedIDErr{id, sid}
|
||||
}
|
||||
attr, _ := unmarshalAttrs(data)
|
||||
return fileInfoFromStat(attr, path.Base(p)), nil
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
return nil, normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return nil, unimplementedPacketErr(typ)
|
||||
@@ -340,7 +375,7 @@ func (c *Client) ReadLink(p string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_NAME:
|
||||
case sshFxpName:
|
||||
sid, data := unmarshalUint32(data)
|
||||
if sid != id {
|
||||
return "", &unexpectedIDErr{id, sid}
|
||||
@@ -351,13 +386,32 @@ func (c *Client) ReadLink(p string) (string, error) {
|
||||
}
|
||||
filename, _ := unmarshalString(data) // ignore dummy attributes
|
||||
return filename, nil
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
return "", normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return "", unimplementedPacketErr(typ)
|
||||
}
|
||||
}
|
||||
|
||||
// Link creates a hard link at 'newname', pointing at the same inode as 'oldname'
|
||||
func (c *Client) Link(oldname, newname string) error {
|
||||
id := c.nextID()
|
||||
typ, data, err := c.sendPacket(sshFxpHardlinkPacket{
|
||||
ID: id,
|
||||
Oldpath: oldname,
|
||||
Newpath: newname,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch typ {
|
||||
case sshFxpStatus:
|
||||
return normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return unimplementedPacketErr(typ)
|
||||
}
|
||||
}
|
||||
|
||||
// Symlink creates a symbolic link at 'newname', pointing at target 'oldname'
|
||||
func (c *Client) Symlink(oldname, newname string) error {
|
||||
id := c.nextID()
|
||||
@@ -370,7 +424,7 @@ func (c *Client) Symlink(oldname, newname string) error {
|
||||
return err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
return normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return unimplementedPacketErr(typ)
|
||||
@@ -390,7 +444,7 @@ func (c *Client) setstat(path string, flags uint32, attrs interface{}) error {
|
||||
return err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
return normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return unimplementedPacketErr(typ)
|
||||
@@ -404,7 +458,7 @@ func (c *Client) Chtimes(path string, atime time.Time, mtime time.Time) error {
|
||||
Mtime uint32
|
||||
}
|
||||
attrs := times{uint32(atime.Unix()), uint32(mtime.Unix())}
|
||||
return c.setstat(path, ssh_FILEXFER_ATTR_ACMODTIME, attrs)
|
||||
return c.setstat(path, sshFileXferAttrACmodTime, attrs)
|
||||
}
|
||||
|
||||
// Chown changes the user and group owners of the named file.
|
||||
@@ -414,12 +468,12 @@ func (c *Client) Chown(path string, uid, gid int) error {
|
||||
GID uint32
|
||||
}
|
||||
attrs := owner{uint32(uid), uint32(gid)}
|
||||
return c.setstat(path, ssh_FILEXFER_ATTR_UIDGID, attrs)
|
||||
return c.setstat(path, sshFileXferAttrUIDGID, attrs)
|
||||
}
|
||||
|
||||
// Chmod changes the permissions of the named file.
|
||||
func (c *Client) Chmod(path string, mode os.FileMode) error {
|
||||
return c.setstat(path, ssh_FILEXFER_ATTR_PERMISSIONS, uint32(mode))
|
||||
return c.setstat(path, sshFileXferAttrPermissions, uint32(mode))
|
||||
}
|
||||
|
||||
// Truncate sets the size of the named file. Although it may be safely assumed
|
||||
@@ -427,7 +481,7 @@ func (c *Client) Chmod(path string, mode os.FileMode) error {
|
||||
// the SFTP protocol does not specify what behavior the server should do when setting
|
||||
// size greater than the current size.
|
||||
func (c *Client) Truncate(path string, size int64) error {
|
||||
return c.setstat(path, ssh_FILEXFER_ATTR_SIZE, uint64(size))
|
||||
return c.setstat(path, sshFileXferAttrSize, uint64(size))
|
||||
}
|
||||
|
||||
// Open opens the named file for reading. If successful, methods on the
|
||||
@@ -455,14 +509,14 @@ func (c *Client) open(path string, pflags uint32) (*File, error) {
|
||||
return nil, err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_HANDLE:
|
||||
case sshFxpHandle:
|
||||
sid, data := unmarshalUint32(data)
|
||||
if sid != id {
|
||||
return nil, &unexpectedIDErr{id, sid}
|
||||
}
|
||||
handle, _ := unmarshalString(data)
|
||||
return &File{c: c, path: path, handle: handle}, nil
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
return nil, normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return nil, unimplementedPacketErr(typ)
|
||||
@@ -482,7 +536,7 @@ func (c *Client) close(handle string) error {
|
||||
return err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
return normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return unimplementedPacketErr(typ)
|
||||
@@ -499,14 +553,14 @@ func (c *Client) fstat(handle string) (*FileStat, error) {
|
||||
return nil, err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_ATTRS:
|
||||
case sshFxpAttrs:
|
||||
sid, data := unmarshalUint32(data)
|
||||
if sid != id {
|
||||
return nil, &unexpectedIDErr{id, sid}
|
||||
}
|
||||
attr, _ := unmarshalAttrs(data)
|
||||
return attr, nil
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
return nil, normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return nil, unimplementedPacketErr(typ)
|
||||
@@ -530,7 +584,7 @@ func (c *Client) StatVFS(path string) (*StatVFS, error) {
|
||||
|
||||
switch typ {
|
||||
// server responded with valid data
|
||||
case ssh_FXP_EXTENDED_REPLY:
|
||||
case sshFxpExtendedReply:
|
||||
var response StatVFS
|
||||
err = binary.Read(bytes.NewReader(data), binary.BigEndian, &response)
|
||||
if err != nil {
|
||||
@@ -540,8 +594,8 @@ func (c *Client) StatVFS(path string) (*StatVFS, error) {
|
||||
return &response, nil
|
||||
|
||||
// the resquest failed
|
||||
case ssh_FXP_STATUS:
|
||||
return nil, errors.New(fxp(ssh_FXP_STATUS).String())
|
||||
case sshFxpStatus:
|
||||
return nil, errors.New(fxp(sshFxpStatus).String())
|
||||
|
||||
default:
|
||||
return nil, unimplementedPacketErr(typ)
|
||||
@@ -562,7 +616,7 @@ func (c *Client) Remove(path string) error {
|
||||
switch err.Code {
|
||||
// some servers, *cough* osx *cough*, return EPERM, not ENODIR.
|
||||
// serv-u returns ssh_FX_FILE_IS_A_DIRECTORY
|
||||
case ssh_FX_PERMISSION_DENIED, ssh_FX_FAILURE, ssh_FX_FILE_IS_A_DIRECTORY:
|
||||
case sshFxPermissionDenied, sshFxFailure, sshFxFileIsADirectory:
|
||||
return c.RemoveDirectory(path)
|
||||
}
|
||||
}
|
||||
@@ -579,7 +633,7 @@ func (c *Client) removeFile(path string) error {
|
||||
return err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
return normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return unimplementedPacketErr(typ)
|
||||
@@ -597,7 +651,7 @@ func (c *Client) RemoveDirectory(path string) error {
|
||||
return err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
return normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return unimplementedPacketErr(typ)
|
||||
@@ -616,7 +670,7 @@ func (c *Client) Rename(oldname, newname string) error {
|
||||
return err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
return normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return unimplementedPacketErr(typ)
|
||||
@@ -636,7 +690,7 @@ func (c *Client) PosixRename(oldname, newname string) error {
|
||||
return err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
return normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return unimplementedPacketErr(typ)
|
||||
@@ -653,7 +707,7 @@ func (c *Client) realpath(path string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_NAME:
|
||||
case sshFxpName:
|
||||
sid, data := unmarshalUint32(data)
|
||||
if sid != id {
|
||||
return "", &unexpectedIDErr{id, sid}
|
||||
@@ -664,7 +718,7 @@ func (c *Client) realpath(path string) (string, error) {
|
||||
}
|
||||
filename, _ := unmarshalString(data) // ignore attributes
|
||||
return filename, nil
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
return "", normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return "", unimplementedPacketErr(typ)
|
||||
@@ -690,7 +744,7 @@ func (c *Client) Mkdir(path string) error {
|
||||
return err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
return normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return unimplementedPacketErr(typ)
|
||||
@@ -845,14 +899,14 @@ func (f *File) Read(b []byte) (int, error) {
|
||||
}
|
||||
delete(reqs, reqID)
|
||||
switch res.typ {
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
if firstErr.err == nil || req.offset < firstErr.offset {
|
||||
firstErr = offsetErr{
|
||||
offset: req.offset,
|
||||
err: normaliseError(unmarshalStatus(reqID, res.data)),
|
||||
}
|
||||
}
|
||||
case ssh_FXP_DATA:
|
||||
case sshFxpData:
|
||||
l, data := unmarshalUint32(data)
|
||||
n := copy(req.b, data[:l])
|
||||
read += n
|
||||
@@ -884,15 +938,26 @@ func (f *File) Read(b []byte) (int, error) {
|
||||
// maximise throughput for transferring the entire file (especially
|
||||
// over high latency links).
|
||||
func (f *File) WriteTo(w io.Writer) (int64, error) {
|
||||
fi, err := f.c.Stat(f.path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
var fileSize uint64
|
||||
if f.c.useFstat {
|
||||
fileStat, err := f.c.fstat(f.handle)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
fileSize = fileStat.Size
|
||||
|
||||
} else {
|
||||
fi, err := f.c.Stat(f.path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
fileSize = uint64(fi.Size())
|
||||
}
|
||||
|
||||
inFlight := 0
|
||||
desiredInFlight := 1
|
||||
offset := f.offset
|
||||
writeOffset := offset
|
||||
fileSize := uint64(fi.Size())
|
||||
// see comment on same line in Read() above
|
||||
ch := make(chan result, f.c.maxConcurrentRequests+1)
|
||||
type inflightRead struct {
|
||||
@@ -934,7 +999,7 @@ func (f *File) WriteTo(w io.Writer) (int64, error) {
|
||||
|
||||
if inFlight == 0 {
|
||||
if firstErr.err == nil && len(pendingWrites) > 0 {
|
||||
return copied, InternalInconsistency
|
||||
return copied, ErrInternalInconsistency
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -952,11 +1017,11 @@ func (f *File) WriteTo(w io.Writer) (int64, error) {
|
||||
}
|
||||
delete(reqs, reqID)
|
||||
switch res.typ {
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
if firstErr.err == nil || req.offset < firstErr.offset {
|
||||
firstErr = offsetErr{offset: req.offset, err: normaliseError(unmarshalStatus(reqID, res.data))}
|
||||
}
|
||||
case ssh_FXP_DATA:
|
||||
case sshFxpData:
|
||||
l, data := unmarshalUint32(data)
|
||||
if req.offset == writeOffset {
|
||||
nbytes, err := w.Write(data)
|
||||
@@ -1071,7 +1136,7 @@ func (f *File) Write(b []byte) (int, error) {
|
||||
continue
|
||||
}
|
||||
switch res.typ {
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
id, _ := unmarshalUint32(res.data)
|
||||
err := normaliseError(unmarshalStatus(id, res.data))
|
||||
if err != nil && firstErr == nil {
|
||||
@@ -1139,7 +1204,7 @@ func (f *File) ReadFrom(r io.Reader) (int64, error) {
|
||||
continue
|
||||
}
|
||||
switch res.typ {
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
id, _ := unmarshalUint32(res.data)
|
||||
err := normaliseError(unmarshalStatus(id, res.data))
|
||||
if err != nil && firstErr == nil {
|
||||
@@ -1218,11 +1283,11 @@ func normaliseError(err error) error {
|
||||
switch err := err.(type) {
|
||||
case *StatusError:
|
||||
switch err.Code {
|
||||
case ssh_FX_EOF:
|
||||
case sshFxEOF:
|
||||
return io.EOF
|
||||
case ssh_FX_NO_SUCH_FILE:
|
||||
case sshFxNoSuchFile:
|
||||
return os.ErrNotExist
|
||||
case ssh_FX_OK:
|
||||
case sshFxOk:
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
@@ -1260,24 +1325,24 @@ func flags(f int) uint32 {
|
||||
var out uint32
|
||||
switch f & os.O_WRONLY {
|
||||
case os.O_WRONLY:
|
||||
out |= ssh_FXF_WRITE
|
||||
out |= sshFxfWrite
|
||||
case os.O_RDONLY:
|
||||
out |= ssh_FXF_READ
|
||||
out |= sshFxfRead
|
||||
}
|
||||
if f&os.O_RDWR == os.O_RDWR {
|
||||
out |= ssh_FXF_READ | ssh_FXF_WRITE
|
||||
out |= sshFxfRead | sshFxfWrite
|
||||
}
|
||||
if f&os.O_APPEND == os.O_APPEND {
|
||||
out |= ssh_FXF_APPEND
|
||||
out |= sshFxfAppend
|
||||
}
|
||||
if f&os.O_CREATE == os.O_CREATE {
|
||||
out |= ssh_FXF_CREAT
|
||||
out |= sshFxfCreat
|
||||
}
|
||||
if f&os.O_TRUNC == os.O_TRUNC {
|
||||
out |= ssh_FXF_TRUNC
|
||||
out |= sshFxfTrunc
|
||||
}
|
||||
if f&os.O_EXCL == os.O_EXCL {
|
||||
out |= ssh_FXF_EXCL
|
||||
out |= sshFxfExcl
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
+11
-11
@@ -39,7 +39,7 @@ func newPktMgr(sender packetSender) *packetManager {
|
||||
}
|
||||
|
||||
//// packet ordering
|
||||
func (s *packetManager) newOrderId() uint32 {
|
||||
func (s *packetManager) newOrderID() uint32 {
|
||||
s.packetCount++
|
||||
return s.packetCount
|
||||
}
|
||||
@@ -50,10 +50,10 @@ type orderedRequest struct {
|
||||
}
|
||||
|
||||
func (s *packetManager) newOrderedRequest(p requestPacket) orderedRequest {
|
||||
return orderedRequest{requestPacket: p, orderid: s.newOrderId()}
|
||||
return orderedRequest{requestPacket: p, orderid: s.newOrderID()}
|
||||
}
|
||||
func (p orderedRequest) orderId() uint32 { return p.orderid }
|
||||
func (p orderedRequest) setOrderId(oid uint32) { p.orderid = oid }
|
||||
func (p orderedRequest) orderID() uint32 { return p.orderid }
|
||||
func (p orderedRequest) setOrderID(oid uint32) { p.orderid = oid }
|
||||
|
||||
type orderedResponse struct {
|
||||
responsePacket
|
||||
@@ -64,18 +64,18 @@ func (s *packetManager) newOrderedResponse(p responsePacket, id uint32,
|
||||
) orderedResponse {
|
||||
return orderedResponse{responsePacket: p, orderid: id}
|
||||
}
|
||||
func (p orderedResponse) orderId() uint32 { return p.orderid }
|
||||
func (p orderedResponse) setOrderId(oid uint32) { p.orderid = oid }
|
||||
func (p orderedResponse) orderID() uint32 { return p.orderid }
|
||||
func (p orderedResponse) setOrderID(oid uint32) { p.orderid = oid }
|
||||
|
||||
type orderedPacket interface {
|
||||
id() uint32
|
||||
orderId() uint32
|
||||
orderID() uint32
|
||||
}
|
||||
type orderedPackets []orderedPacket
|
||||
|
||||
func (o orderedPackets) Sort() {
|
||||
sort.Slice(o, func(i, j int) bool {
|
||||
return o[i].orderId() < o[j].orderId()
|
||||
return o[i].orderID() < o[j].orderID()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -145,11 +145,11 @@ func (s *packetManager) controller() {
|
||||
for {
|
||||
select {
|
||||
case pkt := <-s.requests:
|
||||
debug("incoming id (oid): %v (%v)", pkt.id(), pkt.orderId())
|
||||
debug("incoming id (oid): %v (%v)", pkt.id(), pkt.orderID())
|
||||
s.incoming = append(s.incoming, pkt)
|
||||
s.incoming.Sort()
|
||||
case pkt := <-s.responses:
|
||||
debug("outgoing id (oid): %v (%v)", pkt.id(), pkt.orderId())
|
||||
debug("outgoing id (oid): %v (%v)", pkt.id(), pkt.orderID())
|
||||
s.outgoing = append(s.outgoing, pkt)
|
||||
s.outgoing.Sort()
|
||||
case <-s.fini:
|
||||
@@ -171,7 +171,7 @@ func (s *packetManager) maybeSendPackets() {
|
||||
in := s.incoming[0]
|
||||
// debug("incoming: %v", ids(s.incoming))
|
||||
// debug("outgoing: %v", ids(s.outgoing))
|
||||
if in.orderId() == out.orderId() {
|
||||
if in.orderID() == out.orderID() {
|
||||
debug("Sending packet: %v", out.id())
|
||||
s.sender.sendPacket(out.(encoding.BinaryMarshaler))
|
||||
// pop off heads
|
||||
|
||||
+23
-21
@@ -49,8 +49,9 @@ func (p sshFxpOpendirPacket) getPath() string { return p.Path }
|
||||
func (p sshFxpOpenPacket) getPath() string { return p.Path }
|
||||
|
||||
func (p sshFxpExtendedPacketPosixRename) getPath() string { return p.Oldpath }
|
||||
func (p sshFxpExtendedPacketHardlink) getPath() string { return p.Oldpath }
|
||||
|
||||
// hasHandle
|
||||
// getHandle
|
||||
func (p sshFxpFstatPacket) getHandle() string { return p.Handle }
|
||||
func (p sshFxpFsetstatPacket) getHandle() string { return p.Handle }
|
||||
func (p sshFxpReadPacket) getHandle() string { return p.Handle }
|
||||
@@ -68,6 +69,7 @@ func (p sshFxpRmdirPacket) notReadOnly() {}
|
||||
func (p sshFxpRenamePacket) notReadOnly() {}
|
||||
func (p sshFxpSymlinkPacket) notReadOnly() {}
|
||||
func (p sshFxpExtendedPacketPosixRename) notReadOnly() {}
|
||||
func (p sshFxpExtendedPacketHardlink) notReadOnly() {}
|
||||
|
||||
// some packets with ID are missing id()
|
||||
func (p sshFxpDataPacket) id() uint32 { return p.ID }
|
||||
@@ -82,45 +84,45 @@ func (p sshFxVersionPacket) id() uint32 { return 0 }
|
||||
func makePacket(p rxPacket) (requestPacket, error) {
|
||||
var pkt requestPacket
|
||||
switch p.pktType {
|
||||
case ssh_FXP_INIT:
|
||||
case sshFxpInit:
|
||||
pkt = &sshFxInitPacket{}
|
||||
case ssh_FXP_LSTAT:
|
||||
case sshFxpLstat:
|
||||
pkt = &sshFxpLstatPacket{}
|
||||
case ssh_FXP_OPEN:
|
||||
case sshFxpOpen:
|
||||
pkt = &sshFxpOpenPacket{}
|
||||
case ssh_FXP_CLOSE:
|
||||
case sshFxpClose:
|
||||
pkt = &sshFxpClosePacket{}
|
||||
case ssh_FXP_READ:
|
||||
case sshFxpRead:
|
||||
pkt = &sshFxpReadPacket{}
|
||||
case ssh_FXP_WRITE:
|
||||
case sshFxpWrite:
|
||||
pkt = &sshFxpWritePacket{}
|
||||
case ssh_FXP_FSTAT:
|
||||
case sshFxpFstat:
|
||||
pkt = &sshFxpFstatPacket{}
|
||||
case ssh_FXP_SETSTAT:
|
||||
case sshFxpSetstat:
|
||||
pkt = &sshFxpSetstatPacket{}
|
||||
case ssh_FXP_FSETSTAT:
|
||||
case sshFxpFsetstat:
|
||||
pkt = &sshFxpFsetstatPacket{}
|
||||
case ssh_FXP_OPENDIR:
|
||||
case sshFxpOpendir:
|
||||
pkt = &sshFxpOpendirPacket{}
|
||||
case ssh_FXP_READDIR:
|
||||
case sshFxpReaddir:
|
||||
pkt = &sshFxpReaddirPacket{}
|
||||
case ssh_FXP_REMOVE:
|
||||
case sshFxpRemove:
|
||||
pkt = &sshFxpRemovePacket{}
|
||||
case ssh_FXP_MKDIR:
|
||||
case sshFxpMkdir:
|
||||
pkt = &sshFxpMkdirPacket{}
|
||||
case ssh_FXP_RMDIR:
|
||||
case sshFxpRmdir:
|
||||
pkt = &sshFxpRmdirPacket{}
|
||||
case ssh_FXP_REALPATH:
|
||||
case sshFxpRealpath:
|
||||
pkt = &sshFxpRealpathPacket{}
|
||||
case ssh_FXP_STAT:
|
||||
case sshFxpStat:
|
||||
pkt = &sshFxpStatPacket{}
|
||||
case ssh_FXP_RENAME:
|
||||
case sshFxpRename:
|
||||
pkt = &sshFxpRenamePacket{}
|
||||
case ssh_FXP_READLINK:
|
||||
case sshFxpReadlink:
|
||||
pkt = &sshFxpReadlinkPacket{}
|
||||
case ssh_FXP_SYMLINK:
|
||||
case sshFxpSymlink:
|
||||
pkt = &sshFxpSymlinkPacket{}
|
||||
case ssh_FXP_EXTENDED:
|
||||
case sshFxpExtended:
|
||||
pkt = &sshFxpExtendedPacket{}
|
||||
default:
|
||||
return nil, errors.Errorf("unhandled packet type: %s", p.pktType)
|
||||
|
||||
+94
-31
@@ -13,11 +13,13 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
errLongPacket = errors.New("packet too long")
|
||||
errShortPacket = errors.New("packet too short")
|
||||
errUnknownExtendedPacket = errors.New("unknown extended packet")
|
||||
)
|
||||
|
||||
const (
|
||||
maxMsgLength = 256 * 1024
|
||||
debugDumpTxPacket = false
|
||||
debugDumpRxPacket = false
|
||||
debugDumpTxPacketBytes = false
|
||||
@@ -143,6 +145,10 @@ func recvPacket(r io.Reader) (uint8, []byte, error) {
|
||||
return 0, nil, err
|
||||
}
|
||||
l, _ := unmarshalUint32(b)
|
||||
if l > maxMsgLength {
|
||||
debug("recv packet %d bytes too long", l)
|
||||
return 0, nil, errLongPacket
|
||||
}
|
||||
b = make([]byte, l)
|
||||
if _, err := io.ReadFull(r, b); err != nil {
|
||||
debug("recv packet %d bytes: err %v", l, err)
|
||||
@@ -189,7 +195,7 @@ func (p sshFxInitPacket) MarshalBinary() ([]byte, error) {
|
||||
}
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_INIT)
|
||||
b = append(b, sshFxpInit)
|
||||
b = marshalUint32(b, p.Version)
|
||||
for _, e := range p.Extensions {
|
||||
b = marshalString(b, e.Name)
|
||||
@@ -216,9 +222,11 @@ func (p *sshFxInitPacket) UnmarshalBinary(b []byte) error {
|
||||
|
||||
type sshFxVersionPacket struct {
|
||||
Version uint32
|
||||
Extensions []struct {
|
||||
Name, Data string
|
||||
}
|
||||
Extensions []sshExtensionPair
|
||||
}
|
||||
|
||||
type sshExtensionPair struct {
|
||||
Name, Data string
|
||||
}
|
||||
|
||||
func (p sshFxVersionPacket) MarshalBinary() ([]byte, error) {
|
||||
@@ -228,7 +236,7 @@ func (p sshFxVersionPacket) MarshalBinary() ([]byte, error) {
|
||||
}
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_VERSION)
|
||||
b = append(b, sshFxpVersion)
|
||||
b = marshalUint32(b, p.Version)
|
||||
for _, e := range p.Extensions {
|
||||
b = marshalString(b, e.Name)
|
||||
@@ -266,7 +274,7 @@ type sshFxpReaddirPacket struct {
|
||||
func (p sshFxpReaddirPacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpReaddirPacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIDString(ssh_FXP_READDIR, p.ID, p.Handle)
|
||||
return marshalIDString(sshFxpReaddir, p.ID, p.Handle)
|
||||
}
|
||||
|
||||
func (p *sshFxpReaddirPacket) UnmarshalBinary(b []byte) error {
|
||||
@@ -281,7 +289,7 @@ type sshFxpOpendirPacket struct {
|
||||
func (p sshFxpOpendirPacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpOpendirPacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIDString(ssh_FXP_OPENDIR, p.ID, p.Path)
|
||||
return marshalIDString(sshFxpOpendir, p.ID, p.Path)
|
||||
}
|
||||
|
||||
func (p *sshFxpOpendirPacket) UnmarshalBinary(b []byte) error {
|
||||
@@ -296,7 +304,7 @@ type sshFxpLstatPacket struct {
|
||||
func (p sshFxpLstatPacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpLstatPacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIDString(ssh_FXP_LSTAT, p.ID, p.Path)
|
||||
return marshalIDString(sshFxpLstat, p.ID, p.Path)
|
||||
}
|
||||
|
||||
func (p *sshFxpLstatPacket) UnmarshalBinary(b []byte) error {
|
||||
@@ -311,7 +319,7 @@ type sshFxpStatPacket struct {
|
||||
func (p sshFxpStatPacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpStatPacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIDString(ssh_FXP_STAT, p.ID, p.Path)
|
||||
return marshalIDString(sshFxpStat, p.ID, p.Path)
|
||||
}
|
||||
|
||||
func (p *sshFxpStatPacket) UnmarshalBinary(b []byte) error {
|
||||
@@ -326,7 +334,7 @@ type sshFxpFstatPacket struct {
|
||||
func (p sshFxpFstatPacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpFstatPacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIDString(ssh_FXP_FSTAT, p.ID, p.Handle)
|
||||
return marshalIDString(sshFxpFstat, p.ID, p.Handle)
|
||||
}
|
||||
|
||||
func (p *sshFxpFstatPacket) UnmarshalBinary(b []byte) error {
|
||||
@@ -341,7 +349,7 @@ type sshFxpClosePacket struct {
|
||||
func (p sshFxpClosePacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpClosePacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIDString(ssh_FXP_CLOSE, p.ID, p.Handle)
|
||||
return marshalIDString(sshFxpClose, p.ID, p.Handle)
|
||||
}
|
||||
|
||||
func (p *sshFxpClosePacket) UnmarshalBinary(b []byte) error {
|
||||
@@ -356,7 +364,7 @@ type sshFxpRemovePacket struct {
|
||||
func (p sshFxpRemovePacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpRemovePacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIDString(ssh_FXP_REMOVE, p.ID, p.Filename)
|
||||
return marshalIDString(sshFxpRemove, p.ID, p.Filename)
|
||||
}
|
||||
|
||||
func (p *sshFxpRemovePacket) UnmarshalBinary(b []byte) error {
|
||||
@@ -371,7 +379,7 @@ type sshFxpRmdirPacket struct {
|
||||
func (p sshFxpRmdirPacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpRmdirPacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIDString(ssh_FXP_RMDIR, p.ID, p.Path)
|
||||
return marshalIDString(sshFxpRmdir, p.ID, p.Path)
|
||||
}
|
||||
|
||||
func (p *sshFxpRmdirPacket) UnmarshalBinary(b []byte) error {
|
||||
@@ -392,7 +400,7 @@ func (p sshFxpSymlinkPacket) MarshalBinary() ([]byte, error) {
|
||||
4 + len(p.Linkpath)
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_SYMLINK)
|
||||
b = append(b, sshFxpSymlink)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Targetpath)
|
||||
b = marshalString(b, p.Linkpath)
|
||||
@@ -411,6 +419,30 @@ func (p *sshFxpSymlinkPacket) UnmarshalBinary(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type sshFxpHardlinkPacket struct {
|
||||
ID uint32
|
||||
Oldpath string
|
||||
Newpath string
|
||||
}
|
||||
|
||||
func (p sshFxpHardlinkPacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpHardlinkPacket) MarshalBinary() ([]byte, error) {
|
||||
const ext = "hardlink@openssh.com"
|
||||
l := 1 + 4 + // type(byte) + uint32
|
||||
4 + len(ext) +
|
||||
4 + len(p.Oldpath) +
|
||||
4 + len(p.Newpath)
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, sshFxpExtended)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, ext)
|
||||
b = marshalString(b, p.Oldpath)
|
||||
b = marshalString(b, p.Newpath)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type sshFxpReadlinkPacket struct {
|
||||
ID uint32
|
||||
Path string
|
||||
@@ -419,7 +451,7 @@ type sshFxpReadlinkPacket struct {
|
||||
func (p sshFxpReadlinkPacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpReadlinkPacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIDString(ssh_FXP_READLINK, p.ID, p.Path)
|
||||
return marshalIDString(sshFxpReadlink, p.ID, p.Path)
|
||||
}
|
||||
|
||||
func (p *sshFxpReadlinkPacket) UnmarshalBinary(b []byte) error {
|
||||
@@ -434,7 +466,7 @@ type sshFxpRealpathPacket struct {
|
||||
func (p sshFxpRealpathPacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpRealpathPacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIDString(ssh_FXP_REALPATH, p.ID, p.Path)
|
||||
return marshalIDString(sshFxpRealpath, p.ID, p.Path)
|
||||
}
|
||||
|
||||
func (p *sshFxpRealpathPacket) UnmarshalBinary(b []byte) error {
|
||||
@@ -464,7 +496,7 @@ type sshFxpNamePacket struct {
|
||||
|
||||
func (p sshFxpNamePacket) MarshalBinary() ([]byte, error) {
|
||||
b := []byte{}
|
||||
b = append(b, ssh_FXP_NAME)
|
||||
b = append(b, sshFxpName)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalUint32(b, uint32(len(p.NameAttrs)))
|
||||
for _, na := range p.NameAttrs {
|
||||
@@ -493,7 +525,7 @@ func (p sshFxpOpenPacket) MarshalBinary() ([]byte, error) {
|
||||
4 + 4
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_OPEN)
|
||||
b = append(b, sshFxpOpen)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Path)
|
||||
b = marshalUint32(b, p.Pflags)
|
||||
@@ -530,7 +562,7 @@ func (p sshFxpReadPacket) MarshalBinary() ([]byte, error) {
|
||||
8 + 4 // uint64 + uint32
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_READ)
|
||||
b = append(b, sshFxpRead)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Handle)
|
||||
b = marshalUint64(b, p.Offset)
|
||||
@@ -566,7 +598,7 @@ func (p sshFxpRenamePacket) MarshalBinary() ([]byte, error) {
|
||||
4 + len(p.Newpath)
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_RENAME)
|
||||
b = append(b, sshFxpRename)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Oldpath)
|
||||
b = marshalString(b, p.Newpath)
|
||||
@@ -601,7 +633,7 @@ func (p sshFxpPosixRenamePacket) MarshalBinary() ([]byte, error) {
|
||||
4 + len(p.Newpath)
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_EXTENDED)
|
||||
b = append(b, sshFxpExtended)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, ext)
|
||||
b = marshalString(b, p.Oldpath)
|
||||
@@ -626,7 +658,7 @@ func (p sshFxpWritePacket) MarshalBinary() ([]byte, error) {
|
||||
len(p.Data)
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_WRITE)
|
||||
b = append(b, sshFxpWrite)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Handle)
|
||||
b = marshalUint64(b, p.Offset)
|
||||
@@ -667,7 +699,7 @@ func (p sshFxpMkdirPacket) MarshalBinary() ([]byte, error) {
|
||||
4 // uint32
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_MKDIR)
|
||||
b = append(b, sshFxpMkdir)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Path)
|
||||
b = marshalUint32(b, p.Flags)
|
||||
@@ -709,7 +741,7 @@ func (p sshFxpSetstatPacket) MarshalBinary() ([]byte, error) {
|
||||
4 // uint32 + uint64
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_SETSTAT)
|
||||
b = append(b, sshFxpSetstat)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Path)
|
||||
b = marshalUint32(b, p.Flags)
|
||||
@@ -723,7 +755,7 @@ func (p sshFxpFsetstatPacket) MarshalBinary() ([]byte, error) {
|
||||
4 // uint32 + uint64
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_FSETSTAT)
|
||||
b = append(b, sshFxpFsetstat)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Handle)
|
||||
b = marshalUint32(b, p.Flags)
|
||||
@@ -763,7 +795,7 @@ type sshFxpHandlePacket struct {
|
||||
}
|
||||
|
||||
func (p sshFxpHandlePacket) MarshalBinary() ([]byte, error) {
|
||||
b := []byte{ssh_FXP_HANDLE}
|
||||
b := []byte{sshFxpHandle}
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Handle)
|
||||
return b, nil
|
||||
@@ -775,7 +807,7 @@ type sshFxpStatusPacket struct {
|
||||
}
|
||||
|
||||
func (p sshFxpStatusPacket) MarshalBinary() ([]byte, error) {
|
||||
b := []byte{ssh_FXP_STATUS}
|
||||
b := []byte{sshFxpStatus}
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalStatus(b, p.StatusError)
|
||||
return b, nil
|
||||
@@ -788,7 +820,7 @@ type sshFxpDataPacket struct {
|
||||
}
|
||||
|
||||
func (p sshFxpDataPacket) MarshalBinary() ([]byte, error) {
|
||||
b := []byte{ssh_FXP_DATA}
|
||||
b := []byte{sshFxpData}
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalUint32(b, p.Length)
|
||||
b = append(b, p.Data[:p.Length]...)
|
||||
@@ -823,7 +855,7 @@ func (p sshFxpStatvfsPacket) MarshalBinary() ([]byte, error) {
|
||||
len("statvfs@openssh.com")
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_EXTENDED)
|
||||
b = append(b, sshFxpExtended)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, "statvfs@openssh.com")
|
||||
b = marshalString(b, p.Path)
|
||||
@@ -856,10 +888,10 @@ func (p *StatVFS) FreeSpace() uint64 {
|
||||
return p.Frsize * p.Bfree
|
||||
}
|
||||
|
||||
// Convert to ssh_FXP_EXTENDED_REPLY packet binary format
|
||||
// MarshalBinary converts to ssh_FXP_EXTENDED_REPLY packet binary format
|
||||
func (p *StatVFS) MarshalBinary() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
buf.Write([]byte{ssh_FXP_EXTENDED_REPLY})
|
||||
buf.Write([]byte{sshFxpExtendedReply})
|
||||
err := binary.Write(&buf, binary.BigEndian, p)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
@@ -903,6 +935,8 @@ func (p *sshFxpExtendedPacket) UnmarshalBinary(b []byte) error {
|
||||
p.SpecificPacket = &sshFxpExtendedPacketStatVFS{}
|
||||
case "posix-rename@openssh.com":
|
||||
p.SpecificPacket = &sshFxpExtendedPacketPosixRename{}
|
||||
case "hardlink@openssh.com":
|
||||
p.SpecificPacket = &sshFxpExtendedPacketHardlink{}
|
||||
default:
|
||||
return errors.Wrapf(errUnknownExtendedPacket, "packet type %v", p.SpecificPacket)
|
||||
}
|
||||
@@ -957,3 +991,32 @@ func (p sshFxpExtendedPacketPosixRename) respond(s *Server) responsePacket {
|
||||
err := os.Rename(p.Oldpath, p.Newpath)
|
||||
return statusFromError(p, err)
|
||||
}
|
||||
|
||||
type sshFxpExtendedPacketHardlink struct {
|
||||
ID uint32
|
||||
ExtendedRequest string
|
||||
Oldpath string
|
||||
Newpath string
|
||||
}
|
||||
|
||||
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL
|
||||
func (p sshFxpExtendedPacketHardlink) id() uint32 { return p.ID }
|
||||
func (p sshFxpExtendedPacketHardlink) readonly() bool { return true }
|
||||
func (p *sshFxpExtendedPacketHardlink) UnmarshalBinary(b []byte) error {
|
||||
var err error
|
||||
if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
} else if p.ExtendedRequest, b, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
} else if p.Oldpath, b, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
} else if p.Newpath, _, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p sshFxpExtendedPacketHardlink) respond(s *Server) responsePacket {
|
||||
err := os.Link(p.Oldpath, p.Newpath)
|
||||
return statusFromError(p, err)
|
||||
}
|
||||
|
||||
+14
-14
@@ -5,7 +5,7 @@ package sftp
|
||||
// request and AttrFlags() and Attributes() when working with SetStat requests.
|
||||
import "os"
|
||||
|
||||
// File Open and Write Flags. Correlate directly with with os.OpenFile flags
|
||||
// FileOpenFlags defines Open and Write Flags. Correlate directly with with os.OpenFile flags
|
||||
// (https://golang.org/pkg/os/#pkg-constants).
|
||||
type FileOpenFlags struct {
|
||||
Read, Write, Append, Creat, Trunc, Excl bool
|
||||
@@ -13,12 +13,12 @@ type FileOpenFlags struct {
|
||||
|
||||
func newFileOpenFlags(flags uint32) FileOpenFlags {
|
||||
return FileOpenFlags{
|
||||
Read: flags&ssh_FXF_READ != 0,
|
||||
Write: flags&ssh_FXF_WRITE != 0,
|
||||
Append: flags&ssh_FXF_APPEND != 0,
|
||||
Creat: flags&ssh_FXF_CREAT != 0,
|
||||
Trunc: flags&ssh_FXF_TRUNC != 0,
|
||||
Excl: flags&ssh_FXF_EXCL != 0,
|
||||
Read: flags&sshFxfRead != 0,
|
||||
Write: flags&sshFxfWrite != 0,
|
||||
Append: flags&sshFxfAppend != 0,
|
||||
Creat: flags&sshFxfCreat != 0,
|
||||
Trunc: flags&sshFxfTrunc != 0,
|
||||
Excl: flags&sshFxfExcl != 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func (r *Request) Pflags() FileOpenFlags {
|
||||
return newFileOpenFlags(r.Flags)
|
||||
}
|
||||
|
||||
// Flags that indicate whether SFTP file attributes were passed. When a flag is
|
||||
// FileAttrFlags that indicate whether SFTP file attributes were passed. When a flag is
|
||||
// true the corresponding attribute should be available from the FileStat
|
||||
// object returned by Attributes method. Used with SetStat.
|
||||
type FileAttrFlags struct {
|
||||
@@ -37,14 +37,14 @@ type FileAttrFlags struct {
|
||||
|
||||
func newFileAttrFlags(flags uint32) FileAttrFlags {
|
||||
return FileAttrFlags{
|
||||
Size: (flags & ssh_FILEXFER_ATTR_SIZE) != 0,
|
||||
UidGid: (flags & ssh_FILEXFER_ATTR_UIDGID) != 0,
|
||||
Permissions: (flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0,
|
||||
Acmodtime: (flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0,
|
||||
Size: (flags & sshFileXferAttrSize) != 0,
|
||||
UidGid: (flags & sshFileXferAttrUIDGID) != 0,
|
||||
Permissions: (flags & sshFileXferAttrPermissions) != 0,
|
||||
Acmodtime: (flags & sshFileXferAttrACmodTime) != 0,
|
||||
}
|
||||
}
|
||||
|
||||
// FileAttrFlags returns a FileAttrFlags boolean struct based on the
|
||||
// AttrFlags returns a FileAttrFlags boolean struct based on the
|
||||
// bitmap/uint32 file attribute flags from the SFTP packaet.
|
||||
func (r *Request) AttrFlags() FileAttrFlags {
|
||||
return newFileAttrFlags(r.Flags)
|
||||
@@ -55,7 +55,7 @@ func (a FileStat) FileMode() os.FileMode {
|
||||
return os.FileMode(a.Mode)
|
||||
}
|
||||
|
||||
// Attributres parses file attributes byte blob and return them in a
|
||||
// Attributes parses file attributes byte blob and return them in a
|
||||
// FileStat object.
|
||||
func (r *Request) Attributes() *FileStat {
|
||||
fs, _ := getFileStat(r.Flags, r.Attrs)
|
||||
|
||||
+32
-20
@@ -1,40 +1,52 @@
|
||||
package sftp
|
||||
|
||||
type fxerr uint32
|
||||
|
||||
// Error types that match the SFTP's SSH_FXP_STATUS codes. Gives you more
|
||||
// direct control of the errors being sent vs. letting the library work them
|
||||
// out from the standard os/io errors.
|
||||
|
||||
type fxerr uint32
|
||||
|
||||
const (
|
||||
ErrSshFxOk = fxerr(ssh_FX_OK)
|
||||
ErrSshFxEof = fxerr(ssh_FX_EOF)
|
||||
ErrSshFxNoSuchFile = fxerr(ssh_FX_NO_SUCH_FILE)
|
||||
ErrSshFxPermissionDenied = fxerr(ssh_FX_PERMISSION_DENIED)
|
||||
ErrSshFxFailure = fxerr(ssh_FX_FAILURE)
|
||||
ErrSshFxBadMessage = fxerr(ssh_FX_BAD_MESSAGE)
|
||||
ErrSshFxNoConnection = fxerr(ssh_FX_NO_CONNECTION)
|
||||
ErrSshFxConnectionLost = fxerr(ssh_FX_CONNECTION_LOST)
|
||||
ErrSshFxOpUnsupported = fxerr(ssh_FX_OP_UNSUPPORTED)
|
||||
ErrSSHFxOk = fxerr(sshFxOk)
|
||||
ErrSSHFxEOF = fxerr(sshFxEOF)
|
||||
ErrSSHFxNoSuchFile = fxerr(sshFxNoSuchFile)
|
||||
ErrSSHFxPermissionDenied = fxerr(sshFxPermissionDenied)
|
||||
ErrSSHFxFailure = fxerr(sshFxFailure)
|
||||
ErrSSHFxBadMessage = fxerr(sshFxBadMessage)
|
||||
ErrSSHFxNoConnection = fxerr(sshFxNoConnection)
|
||||
ErrSSHFxConnectionLost = fxerr(sshFxConnectionLost)
|
||||
ErrSSHFxOpUnsupported = fxerr(sshFxOPUnsupported)
|
||||
)
|
||||
|
||||
// Deprecated error types, these are aliases for the new ones, please use the new ones directly
|
||||
const (
|
||||
ErrSshFxOk = ErrSSHFxOk
|
||||
ErrSshFxEof = ErrSSHFxEOF
|
||||
ErrSshFxNoSuchFile = ErrSSHFxNoSuchFile
|
||||
ErrSshFxPermissionDenied = ErrSSHFxPermissionDenied
|
||||
ErrSshFxFailure = ErrSSHFxFailure
|
||||
ErrSshFxBadMessage = ErrSSHFxBadMessage
|
||||
ErrSshFxNoConnection = ErrSSHFxNoConnection
|
||||
ErrSshFxConnectionLost = ErrSSHFxConnectionLost
|
||||
ErrSshFxOpUnsupported = ErrSSHFxOpUnsupported
|
||||
)
|
||||
|
||||
func (e fxerr) Error() string {
|
||||
switch e {
|
||||
case ErrSshFxOk:
|
||||
case ErrSSHFxOk:
|
||||
return "OK"
|
||||
case ErrSshFxEof:
|
||||
case ErrSSHFxEOF:
|
||||
return "EOF"
|
||||
case ErrSshFxNoSuchFile:
|
||||
case ErrSSHFxNoSuchFile:
|
||||
return "No Such File"
|
||||
case ErrSshFxPermissionDenied:
|
||||
case ErrSSHFxPermissionDenied:
|
||||
return "Permission Denied"
|
||||
case ErrSshFxBadMessage:
|
||||
case ErrSSHFxBadMessage:
|
||||
return "Bad Message"
|
||||
case ErrSshFxNoConnection:
|
||||
case ErrSSHFxNoConnection:
|
||||
return "No Connection"
|
||||
case ErrSshFxConnectionLost:
|
||||
case ErrSSHFxConnectionLost:
|
||||
return "Connection Lost"
|
||||
case ErrSshFxOpUnsupported:
|
||||
case ErrSSHFxOpUnsupported:
|
||||
return "Operation Unsupported"
|
||||
default:
|
||||
return "Failure"
|
||||
|
||||
+53
-13
@@ -11,6 +11,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -90,18 +91,51 @@ func (fs *root) Filecmd(r *Request) error {
|
||||
file.name = r.Target
|
||||
fs.files[r.Target] = file
|
||||
delete(fs.files, r.Filepath)
|
||||
|
||||
if file.IsDir() {
|
||||
for path, file := range fs.files {
|
||||
if strings.HasPrefix(path, r.Filepath+"/") {
|
||||
file.name = r.Target + path[len(r.Filepath):]
|
||||
fs.files[r.Target+path[len(r.Filepath):]] = file
|
||||
delete(fs.files, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
case "Rmdir", "Remove":
|
||||
_, err := fs.fetch(filepath.Dir(r.Filepath))
|
||||
file, err := fs.fetch(filepath.Dir(r.Filepath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if file.IsDir() {
|
||||
for path := range fs.files {
|
||||
if strings.HasPrefix(path, r.Filepath+"/") {
|
||||
return &os.PathError{
|
||||
Op: "remove",
|
||||
Path: r.Filepath + "/",
|
||||
Err: fmt.Errorf("directory is not empty"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete(fs.files, r.Filepath)
|
||||
|
||||
case "Mkdir":
|
||||
_, err := fs.fetch(filepath.Dir(r.Filepath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fs.files[r.Filepath] = newMemFile(r.Filepath, true)
|
||||
case "Link":
|
||||
file, err := fs.fetch(r.Filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if file.IsDir() {
|
||||
return fmt.Errorf("hard link not allowed for directory")
|
||||
}
|
||||
fs.files[r.Target] = file
|
||||
case "Symlink":
|
||||
_, err := fs.fetch(r.Filepath)
|
||||
if err != nil {
|
||||
@@ -147,15 +181,15 @@ func (fs *root) Filelist(r *Request) (ListerAt, error) {
|
||||
if !file.IsDir() {
|
||||
return nil, syscall.ENOTDIR
|
||||
}
|
||||
ordered_names := []string{}
|
||||
for fn, _ := range fs.files {
|
||||
orderedNames := []string{}
|
||||
for fn := range fs.files {
|
||||
if filepath.Dir(fn) == r.Filepath {
|
||||
ordered_names = append(ordered_names, fn)
|
||||
orderedNames = append(orderedNames, fn)
|
||||
}
|
||||
}
|
||||
sort.Strings(ordered_names)
|
||||
list := make([]os.FileInfo, len(ordered_names))
|
||||
for i, fn := range ordered_names {
|
||||
sort.Strings(orderedNames)
|
||||
list := make([]os.FileInfo, len(orderedNames))
|
||||
for i, fn := range orderedNames {
|
||||
list[i] = fs.files[fn]
|
||||
}
|
||||
return listerat(list), nil
|
||||
@@ -199,13 +233,15 @@ func (fs *root) fetch(path string) (*memFile, error) {
|
||||
|
||||
// Implements os.FileInfo, Reader and Writer interfaces.
|
||||
// These are the 3 interfaces necessary for the Handlers.
|
||||
// Implements the optional interface TransferError.
|
||||
type memFile struct {
|
||||
name string
|
||||
modtime time.Time
|
||||
symlink string
|
||||
isdir bool
|
||||
content []byte
|
||||
contentLock sync.RWMutex
|
||||
name string
|
||||
modtime time.Time
|
||||
symlink string
|
||||
isdir bool
|
||||
content []byte
|
||||
transferError error
|
||||
contentLock sync.RWMutex
|
||||
}
|
||||
|
||||
// factory to make sure modtime is set
|
||||
@@ -265,3 +301,7 @@ func (f *memFile) WriteAt(p []byte, off int64) (int, error) {
|
||||
copy(f.content[off:], p)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (f *memFile) TransferError(err error) {
|
||||
f.transferError = err
|
||||
}
|
||||
|
||||
+10
-1
@@ -25,6 +25,8 @@ type FileReader interface {
|
||||
// The request server code will call Close() on the returned io.WriterAt
|
||||
// ojbect if an io.Closer type assertion succeeds.
|
||||
// Note in cases of an error, the error text will be sent to the client.
|
||||
// Note when receiving an Append flag it is important to not open files using
|
||||
// O_APPEND if you plan to use WriteAt, as they conflict.
|
||||
// Called for Methods: Put, Open
|
||||
type FileWriter interface {
|
||||
Filewrite(*Request) (io.WriterAt, error)
|
||||
@@ -32,7 +34,7 @@ type FileWriter interface {
|
||||
|
||||
// FileCmder should return an error
|
||||
// Note in cases of an error, the error text will be sent to the client.
|
||||
// Called for Methods: Setstat, Rename, Rmdir, Mkdir, Symlink, Remove
|
||||
// Called for Methods: Setstat, Rename, Rmdir, Mkdir, Link, Symlink, Remove
|
||||
type FileCmder interface {
|
||||
Filecmd(*Request) error
|
||||
}
|
||||
@@ -53,3 +55,10 @@ type FileLister interface {
|
||||
type ListerAt interface {
|
||||
ListAt([]os.FileInfo, int64) (int, error)
|
||||
}
|
||||
|
||||
// TransferError is an optional interface that readerAt and writerAt
|
||||
// can implement to be notified about the error causing Serve() to exit
|
||||
// with the request still open
|
||||
type TransferError interface {
|
||||
TransferError(err error)
|
||||
}
|
||||
|
||||
+28
-8
@@ -116,11 +116,7 @@ func (rs *RequestServer) Serve() error {
|
||||
if err != nil {
|
||||
switch errors.Cause(err) {
|
||||
case errUnknownExtendedPacket:
|
||||
if err := rs.serverConn.sendError(pkt, ErrSshFxOpUnsupported); err != nil {
|
||||
debug("failed to send err packet: %v", err)
|
||||
rs.conn.Close() // shuts down recvPacket
|
||||
break
|
||||
}
|
||||
// do nothing
|
||||
default:
|
||||
debug("makePacket err: %v", err)
|
||||
rs.conn.Close() // shuts down recvPacket
|
||||
@@ -137,6 +133,20 @@ func (rs *RequestServer) Serve() error {
|
||||
// make sure all open requests are properly closed
|
||||
// (eg. possible on dropped connections, client crashes, etc.)
|
||||
for handle, req := range rs.openRequests {
|
||||
if err != nil {
|
||||
req.state.RLock()
|
||||
writer := req.state.writerAt
|
||||
reader := req.state.readerAt
|
||||
req.state.RUnlock()
|
||||
if t, ok := writer.(TransferError); ok {
|
||||
debug("notify error: %v to writer: %v\n", err, writer)
|
||||
t.TransferError(err)
|
||||
}
|
||||
if t, ok := reader.(TransferError); ok {
|
||||
debug("notify error: %v to reader: %v\n", err, reader)
|
||||
t.TransferError(err)
|
||||
}
|
||||
}
|
||||
delete(rs.openRequests, handle)
|
||||
req.close()
|
||||
}
|
||||
@@ -148,10 +158,16 @@ func (rs *RequestServer) packetWorker(
|
||||
ctx context.Context, pktChan chan orderedRequest,
|
||||
) error {
|
||||
for pkt := range pktChan {
|
||||
if epkt, ok := pkt.requestPacket.(*sshFxpExtendedPacket); ok {
|
||||
if epkt.SpecificPacket != nil {
|
||||
pkt.requestPacket = epkt.SpecificPacket
|
||||
}
|
||||
}
|
||||
|
||||
var rpkt responsePacket
|
||||
switch pkt := pkt.requestPacket.(type) {
|
||||
case *sshFxInitPacket:
|
||||
rpkt = sshFxVersionPacket{Version: sftpProtocolVersion}
|
||||
rpkt = sshFxVersionPacket{Version: sftpProtocolVersion, Extensions: sftpExtensions}
|
||||
case *sshFxpClosePacket:
|
||||
handle := pkt.getHandle()
|
||||
rpkt = statusFromError(pkt, rs.closeRequest(handle))
|
||||
@@ -174,6 +190,10 @@ func (rs *RequestServer) packetWorker(
|
||||
request = NewRequest("Stat", request.Filepath)
|
||||
rpkt = request.call(rs.Handlers, pkt)
|
||||
}
|
||||
case *sshFxpExtendedPacketPosixRename:
|
||||
request := NewRequest("Rename", pkt.Oldpath)
|
||||
request.Target = pkt.Newpath
|
||||
rpkt = request.call(rs.Handlers, pkt)
|
||||
case hasHandle:
|
||||
handle := pkt.getHandle()
|
||||
request, ok := rs.getRequest(handle)
|
||||
@@ -187,11 +207,11 @@ func (rs *RequestServer) packetWorker(
|
||||
rpkt = request.call(rs.Handlers, pkt)
|
||||
request.close()
|
||||
default:
|
||||
return errors.Errorf("unexpected packet type %T", pkt)
|
||||
rpkt = statusFromError(pkt, ErrSSHFxOpUnsupported)
|
||||
}
|
||||
|
||||
rs.pktMgr.readyPacket(
|
||||
rs.pktMgr.newOrderedResponse(rpkt, pkt.orderId()))
|
||||
rs.pktMgr.newOrderedResponse(rpkt, pkt.orderID()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+2
-2
@@ -14,10 +14,10 @@ func fakeFileInfoSys() interface{} {
|
||||
func testOsSys(sys interface{}) error {
|
||||
fstat := sys.(*FileStat)
|
||||
if fstat.UID != uint32(65534) {
|
||||
return errors.New("Uid failed to match.")
|
||||
return errors.New("Uid failed to match")
|
||||
}
|
||||
if fstat.GID != uint32(65534) {
|
||||
return errors.New("Gid failed to match:")
|
||||
return errors.New("Gid failed to match")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+6
-2
@@ -18,7 +18,7 @@ var MaxFilelist int64 = 100
|
||||
// Request contains the data and state for the incoming service request.
|
||||
type Request struct {
|
||||
// Get, Put, Setstat, Stat, Rename, Remove
|
||||
// Rmdir, Mkdir, List, Readlink, Symlink
|
||||
// Rmdir, Mkdir, List, Readlink, Link, Symlink
|
||||
Method string
|
||||
Filepath string
|
||||
Flags uint32
|
||||
@@ -56,6 +56,8 @@ func requestFromPacket(ctx context.Context, pkt hasPath) *Request {
|
||||
request.Target = cleanPath(p.Newpath)
|
||||
case *sshFxpSymlinkPacket:
|
||||
request.Target = cleanPath(p.Linkpath)
|
||||
case *sshFxpExtendedPacketHardlink:
|
||||
request.Target = cleanPath(p.Newpath)
|
||||
}
|
||||
return request
|
||||
}
|
||||
@@ -158,7 +160,7 @@ func (r *Request) call(handlers Handlers, pkt requestPacket) responsePacket {
|
||||
return fileget(handlers.FileGet, r, pkt)
|
||||
case "Put":
|
||||
return fileput(handlers.FilePut, r, pkt)
|
||||
case "Setstat", "Rename", "Rmdir", "Mkdir", "Symlink", "Remove":
|
||||
case "Setstat", "Rename", "Rmdir", "Mkdir", "Link", "Symlink", "Remove":
|
||||
return filecmd(handlers.FileCmd, r, pkt)
|
||||
case "List":
|
||||
return filelist(handlers.FileList, r, pkt)
|
||||
@@ -378,6 +380,8 @@ func requestMethod(p requestPacket) (method string) {
|
||||
method = "Readlink"
|
||||
case *sshFxpMkdirPacket:
|
||||
method = "Mkdir"
|
||||
case *sshFxpExtendedPacketHardlink:
|
||||
method = "Link"
|
||||
}
|
||||
return method
|
||||
}
|
||||
|
||||
+55
-45
@@ -18,6 +18,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// SftpServerWorkerCount defines the number of workers for the SFTP server
|
||||
SftpServerWorkerCount = 8
|
||||
)
|
||||
|
||||
@@ -141,7 +142,7 @@ func (svr *Server) sftpServerWorker(pktChan chan orderedRequest) error {
|
||||
if !readonly && svr.readOnly {
|
||||
svr.sendPacket(orderedResponse{
|
||||
responsePacket: statusFromError(pkt, syscall.EPERM),
|
||||
orderid: pkt.orderId()})
|
||||
orderid: pkt.orderID()})
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -156,7 +157,10 @@ func handlePacket(s *Server, p orderedRequest) error {
|
||||
var rpkt responsePacket
|
||||
switch p := p.requestPacket.(type) {
|
||||
case *sshFxInitPacket:
|
||||
rpkt = sshFxVersionPacket{Version: sftpProtocolVersion}
|
||||
rpkt = sshFxVersionPacket{
|
||||
Version: sftpProtocolVersion,
|
||||
Extensions: sftpExtensions,
|
||||
}
|
||||
case *sshFxpStatPacket:
|
||||
// stat the requested file
|
||||
info, err := os.Stat(p.Path)
|
||||
@@ -246,7 +250,7 @@ func handlePacket(s *Server, p orderedRequest) error {
|
||||
rpkt = sshFxpOpenPacket{
|
||||
ID: p.ID,
|
||||
Path: p.Path,
|
||||
Pflags: ssh_FXF_READ,
|
||||
Pflags: sshFxfRead,
|
||||
}.respond(s)
|
||||
}
|
||||
case *sshFxpReadPacket:
|
||||
@@ -276,13 +280,19 @@ func handlePacket(s *Server, p orderedRequest) error {
|
||||
_, err = f.WriteAt(p.Data, int64(p.Offset))
|
||||
}
|
||||
rpkt = statusFromError(p, err)
|
||||
case *sshFxpExtendedPacket:
|
||||
if p.SpecificPacket == nil {
|
||||
rpkt = statusFromError(p, ErrSSHFxOpUnsupported)
|
||||
} else {
|
||||
rpkt = p.respond(s)
|
||||
}
|
||||
case serverRespondablePacket:
|
||||
rpkt = p.respond(s)
|
||||
default:
|
||||
return errors.Errorf("unexpected packet type %T", p)
|
||||
}
|
||||
|
||||
s.pktMgr.readyPacket(s.pktMgr.newOrderedResponse(rpkt, p.orderId()))
|
||||
s.pktMgr.readyPacket(s.pktMgr.newOrderedResponse(rpkt, p.orderID()))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -315,11 +325,11 @@ func (svr *Server) Serve() error {
|
||||
if err != nil {
|
||||
switch errors.Cause(err) {
|
||||
case errUnknownExtendedPacket:
|
||||
if err := svr.serverConn.sendError(pkt, ErrSshFxOpUnsupported); err != nil {
|
||||
debug("failed to send err packet: %v", err)
|
||||
svr.conn.Close() // shuts down recvPacket
|
||||
break
|
||||
}
|
||||
//if err := svr.serverConn.sendError(pkt, ErrSshFxOpUnsupported); err != nil {
|
||||
// debug("failed to send err packet: %v", err)
|
||||
// svr.conn.Close() // shuts down recvPacket
|
||||
// break
|
||||
//}
|
||||
default:
|
||||
debug("makePacket err: %v", err)
|
||||
svr.conn.Close() // shuts down recvPacket
|
||||
@@ -354,7 +364,7 @@ type sshFxpStatResponse struct {
|
||||
}
|
||||
|
||||
func (p sshFxpStatResponse) MarshalBinary() ([]byte, error) {
|
||||
b := []byte{ssh_FXP_ATTRS}
|
||||
b := []byte{sshFxpAttrs}
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalFileInfo(b, p.info)
|
||||
return b, nil
|
||||
@@ -363,7 +373,7 @@ func (p sshFxpStatResponse) MarshalBinary() ([]byte, error) {
|
||||
var emptyFileStat = []interface{}{uint32(0)}
|
||||
|
||||
func (p sshFxpOpenPacket) readonly() bool {
|
||||
return !p.hasPflags(ssh_FXF_WRITE)
|
||||
return !p.hasPflags(sshFxfWrite)
|
||||
}
|
||||
|
||||
func (p sshFxpOpenPacket) hasPflags(flags ...uint32) bool {
|
||||
@@ -377,27 +387,27 @@ func (p sshFxpOpenPacket) hasPflags(flags ...uint32) bool {
|
||||
|
||||
func (p sshFxpOpenPacket) respond(svr *Server) responsePacket {
|
||||
var osFlags int
|
||||
if p.hasPflags(ssh_FXF_READ, ssh_FXF_WRITE) {
|
||||
if p.hasPflags(sshFxfRead, sshFxfWrite) {
|
||||
osFlags |= os.O_RDWR
|
||||
} else if p.hasPflags(ssh_FXF_WRITE) {
|
||||
} else if p.hasPflags(sshFxfWrite) {
|
||||
osFlags |= os.O_WRONLY
|
||||
} else if p.hasPflags(ssh_FXF_READ) {
|
||||
} else if p.hasPflags(sshFxfRead) {
|
||||
osFlags |= os.O_RDONLY
|
||||
} else {
|
||||
// how are they opening?
|
||||
return statusFromError(p, syscall.EINVAL)
|
||||
}
|
||||
|
||||
if p.hasPflags(ssh_FXF_APPEND) {
|
||||
osFlags |= os.O_APPEND
|
||||
}
|
||||
if p.hasPflags(ssh_FXF_CREAT) {
|
||||
// Don't use O_APPEND flag as it conflicts with WriteAt.
|
||||
// The sshFxfAppend flag is a no-op here as the client sends the offsets.
|
||||
|
||||
if p.hasPflags(sshFxfCreat) {
|
||||
osFlags |= os.O_CREATE
|
||||
}
|
||||
if p.hasPflags(ssh_FXF_TRUNC) {
|
||||
if p.hasPflags(sshFxfTrunc) {
|
||||
osFlags |= os.O_TRUNC
|
||||
}
|
||||
if p.hasPflags(ssh_FXF_EXCL) {
|
||||
if p.hasPflags(sshFxfExcl) {
|
||||
osFlags |= os.O_EXCL
|
||||
}
|
||||
|
||||
@@ -439,19 +449,19 @@ func (p sshFxpSetstatPacket) respond(svr *Server) responsePacket {
|
||||
var err error
|
||||
|
||||
debug("setstat name \"%s\"", p.Path)
|
||||
if (p.Flags & ssh_FILEXFER_ATTR_SIZE) != 0 {
|
||||
if (p.Flags & sshFileXferAttrSize) != 0 {
|
||||
var size uint64
|
||||
if size, b, err = unmarshalUint64Safe(b); err == nil {
|
||||
err = os.Truncate(p.Path, int64(size))
|
||||
}
|
||||
}
|
||||
if (p.Flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0 {
|
||||
if (p.Flags & sshFileXferAttrPermissions) != 0 {
|
||||
var mode uint32
|
||||
if mode, b, err = unmarshalUint32Safe(b); err == nil {
|
||||
err = os.Chmod(p.Path, os.FileMode(mode))
|
||||
}
|
||||
}
|
||||
if (p.Flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0 {
|
||||
if (p.Flags & sshFileXferAttrACmodTime) != 0 {
|
||||
var atime uint32
|
||||
var mtime uint32
|
||||
if atime, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
@@ -462,7 +472,7 @@ func (p sshFxpSetstatPacket) respond(svr *Server) responsePacket {
|
||||
err = os.Chtimes(p.Path, atimeT, mtimeT)
|
||||
}
|
||||
}
|
||||
if (p.Flags & ssh_FILEXFER_ATTR_UIDGID) != 0 {
|
||||
if (p.Flags & sshFileXferAttrUIDGID) != 0 {
|
||||
var uid uint32
|
||||
var gid uint32
|
||||
if uid, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
@@ -486,19 +496,19 @@ func (p sshFxpFsetstatPacket) respond(svr *Server) responsePacket {
|
||||
var err error
|
||||
|
||||
debug("fsetstat name \"%s\"", f.Name())
|
||||
if (p.Flags & ssh_FILEXFER_ATTR_SIZE) != 0 {
|
||||
if (p.Flags & sshFileXferAttrSize) != 0 {
|
||||
var size uint64
|
||||
if size, b, err = unmarshalUint64Safe(b); err == nil {
|
||||
err = f.Truncate(int64(size))
|
||||
}
|
||||
}
|
||||
if (p.Flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0 {
|
||||
if (p.Flags & sshFileXferAttrPermissions) != 0 {
|
||||
var mode uint32
|
||||
if mode, b, err = unmarshalUint32Safe(b); err == nil {
|
||||
err = f.Chmod(os.FileMode(mode))
|
||||
}
|
||||
}
|
||||
if (p.Flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0 {
|
||||
if (p.Flags & sshFileXferAttrACmodTime) != 0 {
|
||||
var atime uint32
|
||||
var mtime uint32
|
||||
if atime, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
@@ -509,7 +519,7 @@ func (p sshFxpFsetstatPacket) respond(svr *Server) responsePacket {
|
||||
err = os.Chtimes(f.Name(), atimeT, mtimeT)
|
||||
}
|
||||
}
|
||||
if (p.Flags & ssh_FILEXFER_ATTR_UIDGID) != 0 {
|
||||
if (p.Flags & sshFileXferAttrUIDGID) != 0 {
|
||||
var uid uint32
|
||||
var gid uint32
|
||||
if uid, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
@@ -526,30 +536,30 @@ func (p sshFxpFsetstatPacket) respond(svr *Server) responsePacket {
|
||||
func translateErrno(errno syscall.Errno) uint32 {
|
||||
switch errno {
|
||||
case 0:
|
||||
return ssh_FX_OK
|
||||
return sshFxOk
|
||||
case syscall.ENOENT:
|
||||
return ssh_FX_NO_SUCH_FILE
|
||||
return sshFxNoSuchFile
|
||||
case syscall.EPERM:
|
||||
return ssh_FX_PERMISSION_DENIED
|
||||
return sshFxPermissionDenied
|
||||
}
|
||||
|
||||
return ssh_FX_FAILURE
|
||||
return sshFxFailure
|
||||
}
|
||||
|
||||
func statusFromError(p ider, err error) sshFxpStatusPacket {
|
||||
ret := sshFxpStatusPacket{
|
||||
ID: p.id(),
|
||||
StatusError: StatusError{
|
||||
// ssh_FX_OK = 0
|
||||
// ssh_FX_EOF = 1
|
||||
// ssh_FX_NO_SUCH_FILE = 2 ENOENT
|
||||
// ssh_FX_PERMISSION_DENIED = 3
|
||||
// ssh_FX_FAILURE = 4
|
||||
// ssh_FX_BAD_MESSAGE = 5
|
||||
// ssh_FX_NO_CONNECTION = 6
|
||||
// ssh_FX_CONNECTION_LOST = 7
|
||||
// ssh_FX_OP_UNSUPPORTED = 8
|
||||
Code: ssh_FX_OK,
|
||||
// sshFXOk = 0
|
||||
// sshFXEOF = 1
|
||||
// sshFXNoSuchFile = 2 ENOENT
|
||||
// sshFXPermissionDenied = 3
|
||||
// sshFXFailure = 4
|
||||
// sshFXBadMessage = 5
|
||||
// sshFXNoConnection = 6
|
||||
// sshFXConnectionLost = 7
|
||||
// sshFXOPUnsupported = 8
|
||||
Code: sshFxOk,
|
||||
},
|
||||
}
|
||||
if err == nil {
|
||||
@@ -557,7 +567,7 @@ func statusFromError(p ider, err error) sshFxpStatusPacket {
|
||||
}
|
||||
|
||||
debug("statusFromError: error is %T %#v", err, err)
|
||||
ret.StatusError.Code = ssh_FX_FAILURE
|
||||
ret.StatusError.Code = sshFxFailure
|
||||
ret.StatusError.msg = err.Error()
|
||||
|
||||
switch e := err.(type) {
|
||||
@@ -573,9 +583,9 @@ func statusFromError(p ider, err error) sshFxpStatusPacket {
|
||||
default:
|
||||
switch e {
|
||||
case io.EOF:
|
||||
ret.StatusError.Code = ssh_FX_EOF
|
||||
ret.StatusError.Code = sshFxEOF
|
||||
case os.ErrNotExist:
|
||||
ret.StatusError.Code = ssh_FX_NO_SUCH_FILE
|
||||
ret.StatusError.Code = sshFxNoSuchFile
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+144
-102
@@ -9,139 +9,148 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ssh_FXP_INIT = 1
|
||||
ssh_FXP_VERSION = 2
|
||||
ssh_FXP_OPEN = 3
|
||||
ssh_FXP_CLOSE = 4
|
||||
ssh_FXP_READ = 5
|
||||
ssh_FXP_WRITE = 6
|
||||
ssh_FXP_LSTAT = 7
|
||||
ssh_FXP_FSTAT = 8
|
||||
ssh_FXP_SETSTAT = 9
|
||||
ssh_FXP_FSETSTAT = 10
|
||||
ssh_FXP_OPENDIR = 11
|
||||
ssh_FXP_READDIR = 12
|
||||
ssh_FXP_REMOVE = 13
|
||||
ssh_FXP_MKDIR = 14
|
||||
ssh_FXP_RMDIR = 15
|
||||
ssh_FXP_REALPATH = 16
|
||||
ssh_FXP_STAT = 17
|
||||
ssh_FXP_RENAME = 18
|
||||
ssh_FXP_READLINK = 19
|
||||
ssh_FXP_SYMLINK = 20
|
||||
ssh_FXP_STATUS = 101
|
||||
ssh_FXP_HANDLE = 102
|
||||
ssh_FXP_DATA = 103
|
||||
ssh_FXP_NAME = 104
|
||||
ssh_FXP_ATTRS = 105
|
||||
ssh_FXP_EXTENDED = 200
|
||||
ssh_FXP_EXTENDED_REPLY = 201
|
||||
sshFxpInit = 1
|
||||
sshFxpVersion = 2
|
||||
sshFxpOpen = 3
|
||||
sshFxpClose = 4
|
||||
sshFxpRead = 5
|
||||
sshFxpWrite = 6
|
||||
sshFxpLstat = 7
|
||||
sshFxpFstat = 8
|
||||
sshFxpSetstat = 9
|
||||
sshFxpFsetstat = 10
|
||||
sshFxpOpendir = 11
|
||||
sshFxpReaddir = 12
|
||||
sshFxpRemove = 13
|
||||
sshFxpMkdir = 14
|
||||
sshFxpRmdir = 15
|
||||
sshFxpRealpath = 16
|
||||
sshFxpStat = 17
|
||||
sshFxpRename = 18
|
||||
sshFxpReadlink = 19
|
||||
sshFxpSymlink = 20
|
||||
sshFxpStatus = 101
|
||||
sshFxpHandle = 102
|
||||
sshFxpData = 103
|
||||
sshFxpName = 104
|
||||
sshFxpAttrs = 105
|
||||
sshFxpExtended = 200
|
||||
sshFxpExtendedReply = 201
|
||||
)
|
||||
|
||||
const (
|
||||
ssh_FX_OK = 0
|
||||
ssh_FX_EOF = 1
|
||||
ssh_FX_NO_SUCH_FILE = 2
|
||||
ssh_FX_PERMISSION_DENIED = 3
|
||||
ssh_FX_FAILURE = 4
|
||||
ssh_FX_BAD_MESSAGE = 5
|
||||
ssh_FX_NO_CONNECTION = 6
|
||||
ssh_FX_CONNECTION_LOST = 7
|
||||
ssh_FX_OP_UNSUPPORTED = 8
|
||||
sshFxOk = 0
|
||||
sshFxEOF = 1
|
||||
sshFxNoSuchFile = 2
|
||||
sshFxPermissionDenied = 3
|
||||
sshFxFailure = 4
|
||||
sshFxBadMessage = 5
|
||||
sshFxNoConnection = 6
|
||||
sshFxConnectionLost = 7
|
||||
sshFxOPUnsupported = 8
|
||||
|
||||
// see draft-ietf-secsh-filexfer-13
|
||||
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.1
|
||||
ssh_FX_INVALID_HANDLE = 9
|
||||
ssh_FX_NO_SUCH_PATH = 10
|
||||
ssh_FX_FILE_ALREADY_EXISTS = 11
|
||||
ssh_FX_WRITE_PROTECT = 12
|
||||
ssh_FX_NO_MEDIA = 13
|
||||
ssh_FX_NO_SPACE_ON_FILESYSTEM = 14
|
||||
ssh_FX_QUOTA_EXCEEDED = 15
|
||||
ssh_FX_UNKNOWN_PRINCIPAL = 16
|
||||
ssh_FX_LOCK_CONFLICT = 17
|
||||
ssh_FX_DIR_NOT_EMPTY = 18
|
||||
ssh_FX_NOT_A_DIRECTORY = 19
|
||||
ssh_FX_INVALID_FILENAME = 20
|
||||
ssh_FX_LINK_LOOP = 21
|
||||
ssh_FX_CANNOT_DELETE = 22
|
||||
ssh_FX_INVALID_PARAMETER = 23
|
||||
ssh_FX_FILE_IS_A_DIRECTORY = 24
|
||||
ssh_FX_BYTE_RANGE_LOCK_CONFLICT = 25
|
||||
ssh_FX_BYTE_RANGE_LOCK_REFUSED = 26
|
||||
ssh_FX_DELETE_PENDING = 27
|
||||
ssh_FX_FILE_CORRUPT = 28
|
||||
ssh_FX_OWNER_INVALID = 29
|
||||
ssh_FX_GROUP_INVALID = 30
|
||||
ssh_FX_NO_MATCHING_BYTE_RANGE_LOCK = 31
|
||||
sshFxInvalidHandle = 9
|
||||
sshFxNoSuchPath = 10
|
||||
sshFxFileAlreadyExists = 11
|
||||
sshFxWriteProtect = 12
|
||||
sshFxNoMedia = 13
|
||||
sshFxNoSpaceOnFilesystem = 14
|
||||
sshFxQuotaExceeded = 15
|
||||
sshFxUnlnownPrincipal = 16
|
||||
sshFxLockConflict = 17
|
||||
sshFxDitNotEmpty = 18
|
||||
sshFxNotADirectory = 19
|
||||
sshFxInvalidFilename = 20
|
||||
sshFxLinkLoop = 21
|
||||
sshFxCannotDelete = 22
|
||||
sshFxInvalidParameter = 23
|
||||
sshFxFileIsADirectory = 24
|
||||
sshFxByteRangeLockConflict = 25
|
||||
sshFxByteRangeLockRefused = 26
|
||||
sshFxDeletePending = 27
|
||||
sshFxFileCorrupt = 28
|
||||
sshFxOwnerInvalid = 29
|
||||
sshFxGroupInvalid = 30
|
||||
sshFxNoMatchingByteRangeLock = 31
|
||||
)
|
||||
|
||||
const (
|
||||
ssh_FXF_READ = 0x00000001
|
||||
ssh_FXF_WRITE = 0x00000002
|
||||
ssh_FXF_APPEND = 0x00000004
|
||||
ssh_FXF_CREAT = 0x00000008
|
||||
ssh_FXF_TRUNC = 0x00000010
|
||||
ssh_FXF_EXCL = 0x00000020
|
||||
sshFxfRead = 0x00000001
|
||||
sshFxfWrite = 0x00000002
|
||||
sshFxfAppend = 0x00000004
|
||||
sshFxfCreat = 0x00000008
|
||||
sshFxfTrunc = 0x00000010
|
||||
sshFxfExcl = 0x00000020
|
||||
)
|
||||
|
||||
var (
|
||||
// supportedSFTPExtensions defines the supported extensions
|
||||
supportedSFTPExtensions = []sshExtensionPair{
|
||||
{"hardlink@openssh.com", "1"},
|
||||
{"posix-rename@openssh.com", "1"},
|
||||
}
|
||||
sftpExtensions = supportedSFTPExtensions
|
||||
)
|
||||
|
||||
type fxp uint8
|
||||
|
||||
func (f fxp) String() string {
|
||||
switch f {
|
||||
case ssh_FXP_INIT:
|
||||
case sshFxpInit:
|
||||
return "SSH_FXP_INIT"
|
||||
case ssh_FXP_VERSION:
|
||||
case sshFxpVersion:
|
||||
return "SSH_FXP_VERSION"
|
||||
case ssh_FXP_OPEN:
|
||||
case sshFxpOpen:
|
||||
return "SSH_FXP_OPEN"
|
||||
case ssh_FXP_CLOSE:
|
||||
case sshFxpClose:
|
||||
return "SSH_FXP_CLOSE"
|
||||
case ssh_FXP_READ:
|
||||
case sshFxpRead:
|
||||
return "SSH_FXP_READ"
|
||||
case ssh_FXP_WRITE:
|
||||
case sshFxpWrite:
|
||||
return "SSH_FXP_WRITE"
|
||||
case ssh_FXP_LSTAT:
|
||||
case sshFxpLstat:
|
||||
return "SSH_FXP_LSTAT"
|
||||
case ssh_FXP_FSTAT:
|
||||
case sshFxpFstat:
|
||||
return "SSH_FXP_FSTAT"
|
||||
case ssh_FXP_SETSTAT:
|
||||
case sshFxpSetstat:
|
||||
return "SSH_FXP_SETSTAT"
|
||||
case ssh_FXP_FSETSTAT:
|
||||
case sshFxpFsetstat:
|
||||
return "SSH_FXP_FSETSTAT"
|
||||
case ssh_FXP_OPENDIR:
|
||||
case sshFxpOpendir:
|
||||
return "SSH_FXP_OPENDIR"
|
||||
case ssh_FXP_READDIR:
|
||||
case sshFxpReaddir:
|
||||
return "SSH_FXP_READDIR"
|
||||
case ssh_FXP_REMOVE:
|
||||
case sshFxpRemove:
|
||||
return "SSH_FXP_REMOVE"
|
||||
case ssh_FXP_MKDIR:
|
||||
case sshFxpMkdir:
|
||||
return "SSH_FXP_MKDIR"
|
||||
case ssh_FXP_RMDIR:
|
||||
case sshFxpRmdir:
|
||||
return "SSH_FXP_RMDIR"
|
||||
case ssh_FXP_REALPATH:
|
||||
case sshFxpRealpath:
|
||||
return "SSH_FXP_REALPATH"
|
||||
case ssh_FXP_STAT:
|
||||
case sshFxpStat:
|
||||
return "SSH_FXP_STAT"
|
||||
case ssh_FXP_RENAME:
|
||||
case sshFxpRename:
|
||||
return "SSH_FXP_RENAME"
|
||||
case ssh_FXP_READLINK:
|
||||
case sshFxpReadlink:
|
||||
return "SSH_FXP_READLINK"
|
||||
case ssh_FXP_SYMLINK:
|
||||
case sshFxpSymlink:
|
||||
return "SSH_FXP_SYMLINK"
|
||||
case ssh_FXP_STATUS:
|
||||
case sshFxpStatus:
|
||||
return "SSH_FXP_STATUS"
|
||||
case ssh_FXP_HANDLE:
|
||||
case sshFxpHandle:
|
||||
return "SSH_FXP_HANDLE"
|
||||
case ssh_FXP_DATA:
|
||||
case sshFxpData:
|
||||
return "SSH_FXP_DATA"
|
||||
case ssh_FXP_NAME:
|
||||
case sshFxpName:
|
||||
return "SSH_FXP_NAME"
|
||||
case ssh_FXP_ATTRS:
|
||||
case sshFxpAttrs:
|
||||
return "SSH_FXP_ATTRS"
|
||||
case ssh_FXP_EXTENDED:
|
||||
case sshFxpExtended:
|
||||
return "SSH_FXP_EXTENDED"
|
||||
case ssh_FXP_EXTENDED_REPLY:
|
||||
case sshFxpExtendedReply:
|
||||
return "SSH_FXP_EXTENDED_REPLY"
|
||||
default:
|
||||
return "unknown"
|
||||
@@ -152,23 +161,23 @@ type fx uint8
|
||||
|
||||
func (f fx) String() string {
|
||||
switch f {
|
||||
case ssh_FX_OK:
|
||||
case sshFxOk:
|
||||
return "SSH_FX_OK"
|
||||
case ssh_FX_EOF:
|
||||
case sshFxEOF:
|
||||
return "SSH_FX_EOF"
|
||||
case ssh_FX_NO_SUCH_FILE:
|
||||
case sshFxNoSuchFile:
|
||||
return "SSH_FX_NO_SUCH_FILE"
|
||||
case ssh_FX_PERMISSION_DENIED:
|
||||
case sshFxPermissionDenied:
|
||||
return "SSH_FX_PERMISSION_DENIED"
|
||||
case ssh_FX_FAILURE:
|
||||
case sshFxFailure:
|
||||
return "SSH_FX_FAILURE"
|
||||
case ssh_FX_BAD_MESSAGE:
|
||||
case sshFxBadMessage:
|
||||
return "SSH_FX_BAD_MESSAGE"
|
||||
case ssh_FX_NO_CONNECTION:
|
||||
case sshFxNoConnection:
|
||||
return "SSH_FX_NO_CONNECTION"
|
||||
case ssh_FX_CONNECTION_LOST:
|
||||
case sshFxConnectionLost:
|
||||
return "SSH_FX_CONNECTION_LOST"
|
||||
case ssh_FX_OP_UNSUPPORTED:
|
||||
case sshFxOPUnsupported:
|
||||
return "SSH_FX_OP_UNSUPPORTED"
|
||||
default:
|
||||
return "unknown"
|
||||
@@ -214,4 +223,37 @@ type StatusError struct {
|
||||
msg, lang string
|
||||
}
|
||||
|
||||
func (s *StatusError) Error() string { return fmt.Sprintf("sftp: %q (%v)", s.msg, fx(s.Code)) }
|
||||
func (s *StatusError) Error() string {
|
||||
return fmt.Sprintf("sftp: %q (%v)", s.msg, fx(s.Code))
|
||||
}
|
||||
|
||||
// FxCode returns the error code typed to match against the exported codes
|
||||
func (s *StatusError) FxCode() fxerr {
|
||||
return fxerr(s.Code)
|
||||
}
|
||||
|
||||
func getSupportedExtensionByName(extensionName string) (sshExtensionPair, error) {
|
||||
for _, supportedExtension := range supportedSFTPExtensions {
|
||||
if supportedExtension.Name == extensionName {
|
||||
return supportedExtension, nil
|
||||
}
|
||||
}
|
||||
return sshExtensionPair{}, fmt.Errorf("Unsupported extension: %v", extensionName)
|
||||
}
|
||||
|
||||
// SetSFTPExtensions allows to customize the supported server extensions.
|
||||
// See the variable supportedSFTPExtensions for supported extensions.
|
||||
// This method accepts a slice of sshExtensionPair names for example 'hardlink@openssh.com'.
|
||||
// If an invalid extension is given an error will be returned and nothing will be changed
|
||||
func SetSFTPExtensions(extensions ...string) error {
|
||||
tempExtensions := []sshExtensionPair{}
|
||||
for _, extension := range extensions {
|
||||
sftpExtension, err := getSupportedExtensionByName(extension)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tempExtensions = append(tempExtensions, sftpExtension)
|
||||
}
|
||||
sftpExtensions = tempExtensions
|
||||
return nil
|
||||
}
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// +build plan9 windows js,wasm
|
||||
|
||||
// Go defines S_IFMT on windows, plan9 and js/wasm as 0x1f000 instead of
|
||||
// 0xf000. None of the the other S_IFxyz values include the "1" (in 0x1f000)
|
||||
// which prevents them from matching the bitmask.
|
||||
|
||||
package sftp
|
||||
|
||||
const S_IFMT = 0xf000
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// +build !plan9,!windows
|
||||
// +build !js !wasm
|
||||
|
||||
package sftp
|
||||
|
||||
import "syscall"
|
||||
|
||||
const S_IFMT = syscall.S_IFMT
|
||||
Reference in New Issue
Block a user