Я пытаюсь воспроизвести проблему, когда некоторые из наших клиентов могут получить неправильное содержимое файла при загрузке по протоколу SFTP.
На основании журналов нашего SFTP-сервера (CrushFTP) они, вероятно, открывают несколько файлов водин сеанс, а затем использовать конвейерную загрузку для загрузки файлов.Я не знаю, какую библиотеку они используют, поскольку они используют для этого какого-либо поставщика SAAS.
Я пытаюсь воспроизвести поведение с помощью libssh2, но я получаю sftp_read() internal error
при асинхронном вызове libssh2_sftp_readна втором открытом файле после того, как первый вернул LIBSSH2_ERROR_EAGAIN - даже при локальном подключении к OpenSSH.
При просмотре Протокол передачи файлов SSH IETF Draft Я вижу, что протокол позволяет открывать несколько файловодновременно и запрашивая их содержимое с помощью нескольких запросов SSH_FXP_READ, не ожидая ответа.
Ниже приведен код, который я использую для тестирования (SSCCE, но довольно длинный - C довольно многословный) - скомпилирован с gcc sftp_multifile.c -lssh2 -Wall -g -o sftp_multifile
и протестирован с:
./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, о которой следует сообщить ее сопровождающему?
#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;
}