首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >libssh2 SFTP流水线多个文件: sftp_read()内部错误

libssh2 SFTP流水线多个文件: sftp_read()内部错误
EN

Stack Overflow用户
提问于 2019-04-17 13:41:56
回答 1查看 978关注 0票数 8

我试图重现一个问题,当我们的一些客户端在使用SFTP协议下载时可能会接收错误的文件内容。

基于我们的SFTP服务器(CrushFTP)日志,它们可能在一个会话中打开多个文件,然后使用一些管道来下载这些文件。我不知道他们使用的是哪种库,因为他们使用的是一些SAAS提供者。

我正在尝试用libssh2再现这种行为,但是在第一个打开的文件返回LIBSSH2_ERROR_EAGAIN (甚至在本地主机连接到OpenSSH )之后,在第二个打开的文件上异步调用libssh2_sftp_read时,会得到libssh2_sftp_read。

在浏览SSH文件传输协议IETF草案时,我可以看到协议允许同时打开多个文件并使用多个SSH_FXP_READ请求请求它们的内容,而无需等待响应。

下面是我用于测试的代码(SSCCE,但相当长的-C相当冗长)-用gcc sftp_multifile.c -lssh2 -Wall -g -o sftp_multifile编译并用以下方法进行测试:

代码语言:javascript
复制
./sftp_multifile localhost 22 testusername testpassword /usr/share/dict/words /usr/share/doc/words/readme.txt

connect try: ai_family=10 ai_socktype=1 ai_protocol=6 addr=::1 port=22
opening: /usr/share/dict/words
opening: /usr/share/doc/words/readme.txt
reading: /usr/share/dict/words from 10134400 to 140736927200896
read result: -37
reading: /usr/share/doc/words/readme.txt from 10135536 to 140736927201920
read result: -31
Bad read result: -31
Read error: /usr/share/doc/words/readme.txt: sftp_read() internal error

我是在代码中出错了,还是libssh2只是不支持对多个打开的文件执行流水线libssh2_sftp_read,还是应该向维护人员报告libssh2中的一个bug?

代码语言:javascript
复制
#include <libssh2.h>
#include <libssh2_sftp.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#include <arpa/inet.h>

#define CHUNK_SIZE (1024)

int sftp_connect(char const* hostname, char const* port)
{
    struct addrinfo* addrinfo_result, *addrinfo_current;
    int sfd, s;
    struct addrinfo addrinfo_hints = {0};
    addrinfo_hints.ai_family = AF_UNSPEC;
    addrinfo_hints.ai_socktype = SOCK_STREAM;
    s = getaddrinfo(hostname, port, &addrinfo_hints, &addrinfo_result);
    if (s != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
        exit(EXIT_FAILURE);
    }
    
    for (addrinfo_current = addrinfo_result; addrinfo_current != NULL; addrinfo_current = addrinfo_current->ai_next) {
        char s[INET6_ADDRSTRLEN];
        sfd = socket(addrinfo_current->ai_family, addrinfo_current->ai_socktype, addrinfo_current->ai_protocol);
        if (sfd == -1) continue;
        {
            void* addr;
            in_port_t port;
            switch(addrinfo_current->ai_family) {
                case AF_INET: {
                    struct sockaddr_in* sockaddr = (struct sockaddr_in*)addrinfo_current->ai_addr;
                    addr = &(sockaddr->sin_addr);
                    port = ntohs(sockaddr->sin_port);
                    break;
                }
                case AF_INET6: {
                    struct sockaddr_in6* sockaddr = (struct sockaddr_in6*)addrinfo_current->ai_addr;
                    addr = &(sockaddr->sin6_addr);
                    port = ntohs(sockaddr->sin6_port);
                    break;
                }
                default:
                    fprintf(stderr, "unknown family: %d\n", addrinfo_current->ai_family);
                    exit(EXIT_FAILURE);
            }
            inet_ntop(addrinfo_current->ai_family, addr, s, INET6_ADDRSTRLEN);
            fprintf(stderr, "connect try: ai_family=%d ai_socktype=%d ai_protocol=%d addr=%s port=%d\n", addrinfo_current->ai_family, addrinfo_current->ai_socktype, addrinfo_current->ai_protocol, s, port);
        }
        if (connect(sfd, addrinfo_current->ai_addr, addrinfo_current->ai_addrlen) != 0) {
            fprintf(stderr, "connect: %s\n", strerror(errno));
            close(sfd);
            continue;
        }
        break;
    }
    if (addrinfo_current == NULL) {
        fprintf(stderr, "connection failed\n");
        exit(EXIT_FAILURE);
    }
    freeaddrinfo(addrinfo_result);
    return sfd;
}

void retrieve_files(int socket, LIBSSH2_SESSION* session, LIBSSH2_SFTP *sftp_session, int filec, char *filev[])
{
    LIBSSH2_SFTP_HANDLE* sftp_handles[filec];
    int finished[filec];
    int toread = filec;
    for(int i=0; i<filec; i++) {
        fprintf(stderr, "opening: %s\n", filev[i]);
        sftp_handles[i] = libssh2_sftp_open(sftp_session, filev[i], LIBSSH2_FXF_READ, 0);
        if (!sftp_handles[i]) {
            char* errmsg;
            libssh2_session_last_error(session, &errmsg, NULL, 0);
            fprintf(stderr, "Failure opening remote file: %s: %s\n", filev[i], errmsg);
            exit(EXIT_FAILURE);
        }
        finished[i]=0;
    }
    char buffer[CHUNK_SIZE];
    libssh2_session_set_blocking(session, 0);
    while (toread) {
        for(int i=0; i<filec; i++) {
            if (finished[i]) {
                continue;
            } else {
                int read_result;
                do {
                    fprintf(stderr, "reading: %s from %ld to %ld\n", filev[i], (long)sftp_handles[i], (long)(buffer+CHUNK_SIZE*i));
                    read_result = libssh2_sftp_read(sftp_handles[i], buffer, CHUNK_SIZE);
                    fprintf(stderr, "read result: %d\n", read_result);
                    if (read_result > 0) {
                        printf("%s: ", filev[i]);
                        fwrite(buffer, sizeof(char), read_result, stdout);
                        printf("\n");
                    } else {
                        break;
                    }
                } while (1);
                if (read_result == LIBSSH2_ERROR_EAGAIN) {
                    continue;
                } else if (read_result == 0) {
                    finished[i] = 1;
                    toread--;
                } else {
                    fprintf(stderr, "Bad read result: %d\n", read_result);
                    char* errmsg;
                    libssh2_session_last_error(session, &errmsg, NULL, 0);
                    fprintf(stderr, "Read error: %s: %s\n", filev[i], errmsg);
                    exit(EXIT_FAILURE);
                }
            }
        }
        if (toread) {
            struct pollfd fds[1] = {0};
            fds[0].fd = socket;
            fds[0].events = POLLIN;
            poll(fds, 1, 3000);
        }
    }
}

int main(int argc, char *argv[])
{
    LIBSSH2_SESSION *session;
    LIBSSH2_SFTP *sftp_session;
    int rc, sock;
    
    if (argc<6) {
        fprintf(stderr, "Usage: %s hostname port username password file1 [file2] [file3...]\n", argv[0]);
        return EXIT_FAILURE;
    }
    
    sock = sftp_connect(argv[1], argv[2]);
    session = libssh2_session_init();
    if (!session) {
        return EXIT_FAILURE;
    }
    rc = libssh2_session_handshake(session, sock);
    if (rc) {
        char* errmsg;
        libssh2_session_last_error(session, &errmsg, NULL, 0);
        fprintf(stderr, "Failure establishing SSH session: %s\n", errmsg);
        return EXIT_FAILURE;
    }
    rc = libssh2_userauth_password(session, argv[3], argv[4]);
    if (rc) {
        char* errmsg;
        libssh2_session_last_error(session, &errmsg, NULL, 0);
        fprintf(stderr, "%s\n", errmsg);
        return EXIT_FAILURE;
    }
    
    sftp_session = libssh2_sftp_init(session);
    if (sftp_session == NULL) {
        char* errmsg;
        libssh2_session_last_error(session, &errmsg, NULL, 0);
        fprintf(stderr, "Unable to init SFTP session: %s\n", errmsg);
        return EXIT_FAILURE;
    }

    retrieve_files(sock, session, sftp_session, argc-5, argv+5);

    libssh2_session_set_blocking(session, 1);
    libssh2_sftp_shutdown(sftp_session);
    libssh2_session_disconnect(session, "");
    libssh2_session_free(session);
    libssh2_exit();
    return EXIT_SUCCESS;
}
EN

回答 1

Stack Overflow用户

发布于 2019-05-02 03:51:04

有意思的。我在拱门上用libssh2 1.8.1-1。我将您的代码粘贴到sftp_multifile.c中,创建了一个Makefile

代码语言:javascript
复制
sftp_multifile: sftp_multifile.c
    gcc -g -O -Wall $< -o $@ -lssh2

并将args写入55728938.<count> (不打算在这里发布;-)。只有一个文件,我得到

代码语言:javascript
复制
$ ./sftp_multifile $(cat 55728938.one)
connect try: ai_family=10 ai_socktype=1 ai_protocol=6 addr=::1 port=22
opening: path/text1
reading: path/text1 from 94221524604816 to 140729241245632
read result: -37
reading: path/text1 from 94221524604816 to 140729241245632
read result: 21
reading: path/text1 from 94221524604816 to 140729241245632
read result: -37
reading: path/text1 from 94221524604816 to 140729241245632
read result: 0
path/text1: So much depends
upon

..。在我看来不错。但有两个或更多的文件,

代码语言:javascript
复制
$ ./sftp_multifile $(cat 55728938.two)
connect try: ai_family=10 ai_socktype=1 ai_protocol=6 addr=::1 port=22
opening: path/text1
opening: path/text2
reading: path/text1 from 94473513555856 to 140727557764112
read result: -37
reading: path/text2 from 94473513556256 to 140727557765136
read result: -31
Bad read result: -31
Read error: path/text2: sftp_read() internal error
代码语言:javascript
复制
$ ./sftp_multifile $(cat 55728938.three)
connect try: ai_family=10 ai_socktype=1 ai_protocol=6 addr=::1 port=22
opening: path/text1
opening: path/text2
opening: path/text3
reading: path/text1 from 94652164568976 to 140727538608784
read result: -37
reading: path/text2 from 94652164569376 to 140727538609808
read result: -31
Bad read result: -31
Read error: path/text2: sftp_read() internal error

这和你看到的是一致的。由于打包的libssh2不是用调试符号构建的,所以gdb没有多大帮助。我建议把这个和libssh2邮件列表联系起来。

编辑以添加

删除行libssh2_session_set_blocking(session, 0);似乎解决了问题。

代码语言:javascript
复制
$ make && ./sftp_multifile $(cat 55728938.four)
gcc -g -O -Wall sftp_multifile.c -o sftp_multifile -lssh2
connect try: ai_family=10 ai_socktype=1 ai_protocol=6 addr=::1 port=22
opening: /home/pi/stackoverflow/text1
opening: /home/pi/stackoverflow/text2
opening: /home/pi/stackoverflow/text3
opening: /home/pi/stackoverflow/text4
4 of 4
reading: /home/pi/stackoverflow/text1 from 5453288 to 2129740008
read result: 21
/home/pi/stackoverflow/text1: So much depends
upon

reading: /home/pi/stackoverflow/text1 from 5453288 to 2129740008
read result: 0
3 of 4
reading: /home/pi/stackoverflow/text2 from 5453640 to 2129741032
read result: 19
/home/pi/stackoverflow/text2: a red wheel
barrow

reading: /home/pi/stackoverflow/text2 from 5453640 to 2129741032
read result: 0
2 of 4
reading: /home/pi/stackoverflow/text3 from 5455408 to 2129742056
read result: 23
/home/pi/stackoverflow/text3: glazed with rain
water

reading: /home/pi/stackoverflow/text3 from 5455408 to 2129742056
read result: 0
1 of 4
reading: /home/pi/stackoverflow/text4 from 5455760 to 2129743080
read result: 27
/home/pi/stackoverflow/text4: beside the white
chickens.

reading: /home/pi/stackoverflow/text4 from 5455760 to 2129743080
read result: 0

所有四个文件,现在和记录。在Arch (libssh2-1.8)和Raspbian (libssh-1.7)上的结果是相同的。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/55728938

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档