SFTP reading is completely broken for me in 1.4.0 (though it has been for a while). The last release that works for me is 1.2.7.
Some revisions cause a read with a buffer larger than 65-130k to block. Other revisions cause reads to return 0 prematurely indicating the end of the file (usually around the 200k mark). Recent revisions do the latter. Using my usual test script (attached) and git bisect I've narrowed down the offending commits. Apparently, premature EOF is caused by 03ca9020756a4e16f0294e5b35e9826ee6af2364 [1] and blocking is caused by 90b4b4073f34919aa72deff61a5c9bc188c47c95 [2]. It's difficult to know how accurate these are as there were several overlapping, interacting bugs and attempts to fix them that kept changing the SFTP behaviour. This is all on Windows 7 64-bit. To try the script yourself, run it as: libssh2_bigread ip_address username password path If you run it with something like /dev/zero as the path, the expected behaviour is that it will try reading from that path with increasing buffer sizes up to about 8MB. Daniel, liuzl: we went through this process in September and you guys made some fixes which I though had solved it. I've changed both the Windows and Linux machines I'm testing on since then so I don't know if that's why the problems are back. I don't understand enough about the SFTP layer to suggest a fix but, as usual, I'll happily test any patches. Alex [1] http://git.libssh2.org/?p=libssh2.git;a=commit;h=03ca9020756a4e16f0294e5b35e9826ee6af2364;js=1 [2] http://git.libssh2.org/?p=libssh2.git;a=commit;h=90b4b4073f34919aa72deff61a5c9bc188c47c95;js=1
/* * The sample code has default values for host name, user name, password * and path to copy, but you can specify them on the command line like: * * "sftp 192.168.0.1 user password /tmp/secrets -p|-i|-k" */ //#define TEST_READ_SIZE 6543210 #define TEST_READ_SIZE 654 #include <libssh2_config.h> #include <libssh2.h> #include <libssh2_sftp.h> #ifdef HAVE_WINSOCK2_H # include <winsock2.h> #endif #ifdef HAVE_NETINET_IN_H # include <netinet/in.h> #endif #ifdef HAVE_SYS_SOCKET_H # include <sys/socket.h> #endif # ifdef HAVE_UNISTD_H #include <unistd.h> #endif #ifdef HAVE_ARPA_INET_H # include <arpa/inet.h> #endif #ifdef HAVE_SYS_TIME_H # include <sys/time.h> #endif #include <sys/types.h> #include <fcntl.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <ctype.h> const char *username="username"; const char *password="password"; const char *sftppath="/tmp/TEST"; static void kbd_callback(const char *name, int name_len, const char *instruction, int instruction_len, int num_prompts, const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, void **abstract) { (void)name; (void)name_len; (void)instruction; (void)instruction_len; if (num_prompts == 1) { responses[0].text = strdup(password); responses[0].length = strlen(password); } (void)prompts; (void)abstract; } /* kbd_callback */ int main(int argc, char *argv[]) { unsigned long hostaddr; int sock, i, auth_pw = 0; struct sockaddr_in sin; const char *fingerprint; char *userauthlist; LIBSSH2_SESSION *session; int rc; LIBSSH2_SFTP *sftp_session; LIBSSH2_SFTP_HANDLE *sftp_handle; char *mem; #ifdef WIN32 WSADATA wsadata; WSAStartup(MAKEWORD(2,0), &wsadata); #endif if (argc > 1) { hostaddr = inet_addr(argv[1]); } else { hostaddr = htonl(0x7F000001); } if(argc > 2) { username = argv[2]; } if(argc > 3) { password = argv[3]; } if(argc > 4) { sftppath = argv[4]; } /* * The application code is responsible for creating the socket * and establishing the connection */ sock = socket(AF_INET, SOCK_STREAM, 0); sin.sin_family = AF_INET; sin.sin_port = htons(22); sin.sin_addr.s_addr = hostaddr; if (connect(sock, (struct sockaddr*)(&sin), sizeof(struct sockaddr_in)) != 0) { fprintf(stderr, "failed to connect!\n"); return -1; } /* Create a session instance */ session = libssh2_session_init(); if(!session) return -1; /* Since we have set non-blocking, tell libssh2 we are blocking */ libssh2_session_set_blocking(session, 1); /* ... start it up. This will trade welcome banners, exchange keys, * and setup crypto, compression, and MAC layers */ rc = libssh2_session_startup(session, sock); if(rc) { fprintf(stderr, "Failure establishing SSH session: %d\n", rc); return -1; } /* At this point we havn't yet authenticated. The first thing to do * is check the hostkey's fingerprint against our known hosts Your app * may have it hard coded, may go to a file, may present it to the * user, that's your call */ fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5); fprintf(stderr, "Fingerprint: "); for(i = 0; i < 16; i++) { fprintf(stderr, "%02X ", (unsigned char)fingerprint[i]); } fprintf(stderr, "\n"); /* check what authentication methods are available */ userauthlist = libssh2_userauth_list(session, username, strlen(username)); printf("Authentication methods: %s\n", userauthlist); if (strstr(userauthlist, "password") != NULL) { auth_pw |= 1; } if (strstr(userauthlist, "keyboard-interactive") != NULL) { auth_pw |= 2; } /* if we got an 4. argument we set this option if supported */ if(argc > 5) { if ((auth_pw & 1) && !strcasecmp(argv[5], "-p")) { auth_pw = 1; } if ((auth_pw & 2) && !strcasecmp(argv[5], "-i")) { auth_pw = 2; } } if (auth_pw & 1) { /* We could authenticate via password */ if (libssh2_userauth_password(session, username, password)) { fprintf(stderr, "Authentication by password failed.\n"); goto shutdown; } } else if (auth_pw & 2) { /* Or via keyboard-interactive */ if (libssh2_userauth_keyboard_interactive(session, username, &kbd_callback) ) { printf("\tAuthentication by keyboard-interactive failed!\n"); goto shutdown; } else { printf("\tAuthentication by keyboard-interactive succeeded.\n"); } } else { printf("No supported authentication methods found!\n"); goto shutdown; } fprintf(stderr, "libssh2_sftp_init()!\n"); sftp_session = libssh2_sftp_init(session); if (!sftp_session) { fprintf(stderr, "Unable to init SFTP session\n"); goto shutdown; } //libssh2_trace(session, (~0) & ~LIBSSH2_TRACE_TRANS); fprintf(stderr, "libssh2_sftp_open()!\n"); /* Request a file via SFTP */ sftp_handle = libssh2_sftp_open(sftp_session, sftppath, LIBSSH2_FXF_READ, 0); if (!sftp_handle) { fprintf( stderr, "Unable to open file with SFTP: %d\n", libssh2_sftp_last_error(sftp_session)); goto shutdown; } fprintf(stderr, "libssh2_sftp_open() is done, now receive data!\n"); //for (int i = 6970001; i < 60000000; i += 1) for (int i = 1; i < 10000000; i *= 2) { fprintf(stderr, "trying buffer size %d\n", i); libssh2_sftp_seek(sftp_handle, 0); mem = (char *)malloc(i); int bytes_read = 0; do { rc = libssh2_sftp_read(sftp_handle, mem, i - bytes_read); if (rc < 0) { char *msg; fprintf(stderr, "READ FAILED!\n"); libssh2_session_last_error(session, &msg, NULL, 0); fprintf(stderr, "%s\n", msg); fprintf(stderr, "%s\n", libssh2_version(0)); break; } bytes_read += rc; } while (rc != 0 && bytes_read < i); free(mem); if (bytes_read < i) { fprintf(stderr, "READ TOO OPTIMISTIC!\n"); fprintf( stderr, "it thinks it's finished but only read %d bytes\n", bytes_read); fprintf(stderr, "%s\n", libssh2_version(0)); } else if (bytes_read == i) { fprintf(stdout, "READ SUCCEEDED!\n"); } else if (bytes_read > i) { fprintf(stderr, "WTF?! BUFFER OVERFLOW!!\n"); fprintf( stderr, "claims to have read %d bytes\n", bytes_read); fprintf(stderr, "%s\n", libssh2_version(0)); } } libssh2_sftp_close(sftp_handle); libssh2_sftp_shutdown(sftp_session); shutdown: libssh2_session_disconnect(session, "Normal Shutdown, Thank you for playing"); libssh2_session_free(session); #ifdef WIN32 closesocket(sock); #else close(sock); #endif fprintf(stderr, "all done\n"); return 0; }
_______________________________________________ libssh2-devel http://cool.haxx.se/cgi-bin/mailman/listinfo/libssh2-devel