| 465 | } |
| 466 | |
| 467 | bool SFTPClient::getFile(const std::string& path, io::BaseStream& output, int64_t expected_size /*= -1*/) { |
| 468 | /** |
| 469 | * SFTP servers should not set the mode of an existing file on open |
| 470 | * (see https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13, Page 33 |
| 471 | * "The 'attrs' field is ignored if an existing file is opened." |
| 472 | * Unfortunately this is a later SFTP version specification than implemented by most servers.) |
| 473 | * But because this is the intuitively correct behaviour (especially when opening a file for read only), |
| 474 | * most servers (OpenSSH for example) implement it this way. |
| 475 | * mina-sshd, the server we use for testing, however did not until recently, |
| 476 | * causing all files we read to be set to 0000. |
| 477 | * The fix to make it behave correctly has been merged back to master, but not yet released: |
| 478 | * https://github.com/apache/mina-sshd/commit/19adb39e4706929b6e5a1b2df056a2b2a29fac4d |
| 479 | * If we encounter real servers that behave like this, a workaround would be to stat before opening the file |
| 480 | * and "re-setting" the mode we read earlier on open. |
| 481 | * An another option would be to patch libssh2 to not send permissions in attrs when opening a file for read only. |
| 482 | */ |
| 483 | LIBSSH2_SFTP_HANDLE *file_handle = libssh2_sftp_open(sftp_session_, path.c_str(), LIBSSH2_FXF_READ, 0 /*mode*/); |
| 484 | if (file_handle == nullptr) { |
| 485 | int ssh_errno = libssh2_session_last_errno(ssh_session_); |
| 486 | /* We can only get the sftp error in this case if the ssh error is a protocol error */ |
| 487 | if (ssh_errno == LIBSSH2_ERROR_SFTP_PROTOCOL) { |
| 488 | last_error_.setLibssh2Error(libssh2_sftp_last_error(sftp_session_)); |
| 489 | logger_->log_error("Failed to open remote file \"%s\", error: %s", path.c_str(), sftp_strerror(last_error_)); |
| 490 | } else { |
| 491 | last_error_.setSftpError(SFTPError::IoError); |
| 492 | char *err_msg = nullptr; |
| 493 | libssh2_session_last_error(ssh_session_, &err_msg, nullptr, 0); |
| 494 | logger_->log_error("Failed to open remote file \"%s\" due to an underlying SSH error: %s", path.c_str(), err_msg); |
| 495 | } |
| 496 | return false; |
| 497 | } |
| 498 | const auto guard = gsl::finally([&file_handle]() { |
| 499 | libssh2_sftp_close(file_handle); |
| 500 | }); |
| 501 | |
| 502 | const size_t buf_size = expected_size < 0 ? MAX_BUFFER_SIZE : std::min<size_t>(expected_size, MAX_BUFFER_SIZE); |
| 503 | std::vector<uint8_t> buf(buf_size); |
| 504 | uint64_t total_read = 0U; |
| 505 | do { |
| 506 | ssize_t read_ret = libssh2_sftp_read(file_handle, reinterpret_cast<char*>(buf.data()), buf.size()); |
| 507 | if (read_ret < 0) { |
| 508 | last_error_.setSftpError(SFTPError::IoError); |
| 509 | logger_->log_error("Failed to read remote file \"%s\"", path.c_str()); |
| 510 | return false; |
| 511 | } else if (read_ret == 0) { |
| 512 | logger_->log_trace("EOF while reading remote file \"%s\"", path.c_str()); |
| 513 | break; |
| 514 | } |
| 515 | logger_->log_trace("Read %d bytes from remote file \"%s\"", read_ret, path.c_str()); |
| 516 | total_read += read_ret; |
| 517 | int remaining = read_ret; |
| 518 | while (remaining > 0) { |
| 519 | int write_ret = output.write(buf.data() + (read_ret - remaining), remaining); |
| 520 | if (write_ret < 0) { |
| 521 | last_error_.setLibssh2Error(LIBSSH2_FX_OK); |
| 522 | logger_->log_error("Failed to write output"); |
| 523 | return false; |
| 524 | } |
no test coverage detected