pkg / sftp

SFTP support for the go.crypto/ssh package
BSD 2-Clause "Simplified" License
1.5k stars 379 forks source link

On write new file it hangs #539

Closed IgnacioGarcia-uala closed 1 year ago

IgnacioGarcia-uala commented 1 year ago

Hi im having a issue while trying to create and write a new file, the file creation is successfully but when i'm trying to write it hangs and dont write anything, when proccess throw timeout error the file stay locked.

The code is:

    destFile, err := sc.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
    if err != nil {
        return err
    }
    defer destFile.Close()

    channel := make(chan error)
    go func() {
        _, err = destFile.Write(content) // hangs here
        channel <- err
    }()

    select {
    case <-time.After(5 * time.Second):
        os.Exit(-1) // force close
        return errors.New("time out")
    case err := <-channel:
        return err
    }

The file is less than 1mb

Any idea? Thanks

puellanivis commented 1 year ago

So, there’s the possibility that the channel <- err blocks, because the time out has happened. In this specific case, I recommend setting a buffer on the channel := make(chan error, 1) in order that the goroutine can make that send without blocking indefinitely (and thus causing a goroutine leak).

I’m not sure what could be going on here. 🤔 The code doesn’t seem wrong at all. What happens if you try to write it without the timeout?

IgnacioGarcia-uala commented 1 year ago

The channel with the timeout is there to force the end of the process so it hangs, i tried with files less than 6kb and it was not resolved after 90s. If the code has nothing wrong, is it possible that there is a problem on the server side?

drakkan commented 1 year ago

please provide a complate runnable reproducer where we have to change at most the file path and the credentials for the SFTP server instead of a code snippet. Thank you

IgnacioGarcia-uala commented 1 year ago

Here is a sample with client creation, i let commented the "TODO"

package main

import (
    "errors"
    "fmt"
    "os"
    "time"

    "github.com/pkg/sftp"
    "golang.org/x/crypto/ssh"
)

func main() {
    err := sendFiles()
    fmt.Println(err)
}

func sendFiles() error {
    key, err := ssh.ParseRawPrivateKeyWithPassphrase([]byte("TODO certificate"), []byte("TODO pass phrase"))
    if err != nil {
        return err
    }
    signer, err := ssh.NewSignerFromKey(key)
    if err != nil {
        return err
    }

    auth := []ssh.AuthMethod{ssh.PublicKeys(signer)}
    cfg := ssh.ClientConfig{
        User:            "TODO user",
        Auth:            auth,
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    }

    addr := fmt.Sprintf("%s:%v", "TODO host", "TODO port")
    conn, err := ssh.Dial("tcp", addr, &cfg)
    if err != nil {
        return err
    }

    client, err := sftp.NewClient(conn)
    if err != nil {
        return err
    }

    name := fmt.Sprintf("%s/%s", os.Getenv("VM_DEST_PATH"), "TODO file_name.txt")
    destFile, err := client.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
    if err != nil {
        return err
    }
    defer destFile.Close()

    channel := make(chan error)
    go func() {
        _, err = destFile.Write([]byte("TODO file content")) // hangs here
        channel <- err
    }()

    select {
    case <-time.After(5 * time.Second):
        os.Exit(-1) // force close
        return errors.New("time out")
    case err := <-channel:
        return err
    }
}
drakkan commented 1 year ago

Your example works for me with both OpenSSH and SFTPGo, try checking the server side logs. Is your server something we can easily test ourselves? (without downloading a proprietary sw, installing a trial license, etc.)

IgnacioGarcia-uala commented 1 year ago

Im not the owner of the server and i not have access to logs or configuration, i will contact the owner to have more information

drakkan commented 1 year ago

Im not the owner of the server and i not have access to logs or configuration, i will contact the owner to have more information

You could also try to set some client options, for example UseFstat could help with IBM sterling server. Disabling concurrent reads/writes should not make difference but you could try while waiting for the response from the server owner

IgnacioGarcia-uala commented 1 year ago

The server logs show how the file is created and x bytes writted, but apparently that bytes stays in buffer. I have modify the writer to test this and it hang on writer.Flush()

writer := bufio.NewWriter(destFile)
b, err := writer.WriteString(string(content))
if err != nil {
    channel <- err
}
log.Printf("bytes: %v", writer.Buffered())

err = writer.Flush() // hangs here
if err != nil {
    channel <- err
}

channel <- nil

The client used is https://www.bitvise.com/ssh-server

drakkan commented 1 year ago

strange, I installed bitwise server locally on a VM (v9.27) and I have no issues with it, also your example works fine. I have no idea sorry

IgnacioGarcia-uala commented 1 year ago

Update: We had to reject this approach and carry out the solution in another way that does not involve the use of the server. The server owner could not find the fault.

Close Issue