James Kuszmaul | cf32412 | 2023-01-14 14:07:17 -0800 | [diff] [blame^] | 1 | // Copyright (c) FIRST and other WPILib contributors. |
| 2 | // Open Source Software; you can modify and/or share it under the terms of |
| 3 | // the WPILib BSD license file in the root directory of this project. |
| 4 | |
| 5 | #include "Sftp.h" |
| 6 | |
| 7 | #include <fmt/format.h> |
| 8 | |
| 9 | using namespace sftp; |
| 10 | |
| 11 | Attributes::Attributes(sftp_attributes&& attr) |
| 12 | : name{attr->name}, flags{attr->flags}, type{attr->type}, size{attr->size} { |
| 13 | sftp_attributes_free(attr); |
| 14 | } |
| 15 | |
| 16 | static std::string GetError(sftp_session sftp) { |
| 17 | switch (sftp_get_error(sftp)) { |
| 18 | case SSH_FX_EOF: |
| 19 | return "end of file"; |
| 20 | case SSH_FX_NO_SUCH_FILE: |
| 21 | return "no such file"; |
| 22 | case SSH_FX_PERMISSION_DENIED: |
| 23 | return "permission denied"; |
| 24 | case SSH_FX_FAILURE: |
| 25 | return "SFTP failure"; |
| 26 | case SSH_FX_BAD_MESSAGE: |
| 27 | return "SFTP bad message"; |
| 28 | case SSH_FX_NO_CONNECTION: |
| 29 | return "SFTP no connection"; |
| 30 | case SSH_FX_CONNECTION_LOST: |
| 31 | return "SFTP connection lost"; |
| 32 | case SSH_FX_OP_UNSUPPORTED: |
| 33 | return "SFTP operation unsupported"; |
| 34 | case SSH_FX_INVALID_HANDLE: |
| 35 | return "SFTP invalid handle"; |
| 36 | case SSH_FX_NO_SUCH_PATH: |
| 37 | return "no such path"; |
| 38 | case SSH_FX_FILE_ALREADY_EXISTS: |
| 39 | return "file already exists"; |
| 40 | case SSH_FX_WRITE_PROTECT: |
| 41 | return "write protected filesystem"; |
| 42 | case SSH_FX_NO_MEDIA: |
| 43 | return "no media inserted"; |
| 44 | default: |
| 45 | return ssh_get_error(sftp->session); |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | Exception::Exception(sftp_session sftp) |
| 50 | : runtime_error{GetError(sftp)}, err{sftp_get_error(sftp)} {} |
| 51 | |
| 52 | File::~File() { |
| 53 | if (m_handle) { |
| 54 | sftp_close(m_handle); |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | Attributes File::Stat() const { |
| 59 | sftp_attributes attr = sftp_fstat(m_handle); |
| 60 | if (!attr) { |
| 61 | throw Exception{m_handle->sftp}; |
| 62 | } |
| 63 | return Attributes{std::move(attr)}; |
| 64 | } |
| 65 | |
| 66 | size_t File::Read(void* buf, uint32_t count) { |
| 67 | auto rv = sftp_read(m_handle, buf, count); |
| 68 | if (rv < 0) { |
| 69 | throw Exception{m_handle->sftp}; |
| 70 | } |
| 71 | return rv; |
| 72 | } |
| 73 | |
| 74 | File::AsyncId File::AsyncReadBegin(uint32_t len) const { |
| 75 | int rv = sftp_async_read_begin(m_handle, len); |
| 76 | if (rv < 0) { |
| 77 | throw Exception{m_handle->sftp}; |
| 78 | } |
| 79 | return rv; |
| 80 | } |
| 81 | |
| 82 | size_t File::AsyncRead(void* data, uint32_t len, AsyncId id) { |
| 83 | auto rv = sftp_async_read(m_handle, data, len, id); |
| 84 | if (rv == SSH_ERROR) { |
| 85 | throw Exception{ssh_get_error(m_handle->sftp->session)}; |
| 86 | } |
| 87 | if (rv == SSH_AGAIN) { |
| 88 | return 0; |
| 89 | } |
| 90 | return rv; |
| 91 | } |
| 92 | |
| 93 | size_t File::Write(std::span<const uint8_t> data) { |
| 94 | auto rv = sftp_write(m_handle, data.data(), data.size()); |
| 95 | if (rv < 0) { |
| 96 | throw Exception{m_handle->sftp}; |
| 97 | } |
| 98 | return rv; |
| 99 | } |
| 100 | |
| 101 | void File::Seek(uint64_t offset) { |
| 102 | if (sftp_seek64(m_handle, offset) < 0) { |
| 103 | throw Exception{m_handle->sftp}; |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | uint64_t File::Tell() const { |
| 108 | return sftp_tell64(m_handle); |
| 109 | } |
| 110 | |
| 111 | void File::Rewind() { |
| 112 | sftp_rewind(m_handle); |
| 113 | } |
| 114 | |
| 115 | void File::Sync() { |
| 116 | if (sftp_fsync(m_handle) < 0) { |
| 117 | throw Exception{m_handle->sftp}; |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | Session::Session(std::string_view host, int port, std::string_view user, |
| 122 | std::string_view pass) |
| 123 | : m_host{host}, m_port{port}, m_username{user}, m_password{pass} { |
| 124 | // Create a new SSH session. |
| 125 | m_session = ssh_new(); |
| 126 | if (!m_session) { |
| 127 | throw Exception{"The SSH session could not be allocated."}; |
| 128 | } |
| 129 | |
| 130 | // Set the host, user, and port. |
| 131 | ssh_options_set(m_session, SSH_OPTIONS_HOST, m_host.c_str()); |
| 132 | ssh_options_set(m_session, SSH_OPTIONS_USER, m_username.c_str()); |
| 133 | ssh_options_set(m_session, SSH_OPTIONS_PORT, &m_port); |
| 134 | |
| 135 | // Set timeout to 3 seconds. |
| 136 | int64_t timeout = 3L; |
| 137 | ssh_options_set(m_session, SSH_OPTIONS_TIMEOUT, &timeout); |
| 138 | |
| 139 | // Set other miscellaneous options. |
| 140 | ssh_options_set(m_session, SSH_OPTIONS_STRICTHOSTKEYCHECK, "no"); |
| 141 | } |
| 142 | |
| 143 | Session::~Session() { |
| 144 | if (m_sftp) { |
| 145 | sftp_free(m_sftp); |
| 146 | } |
| 147 | if (m_session) { |
| 148 | ssh_free(m_session); |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | void Session::Connect() { |
| 153 | // Connect to the server. |
| 154 | int rc = ssh_connect(m_session); |
| 155 | if (rc != SSH_OK) { |
| 156 | throw Exception{ssh_get_error(m_session)}; |
| 157 | } |
| 158 | |
| 159 | // Authenticate with password. |
| 160 | rc = ssh_userauth_password(m_session, nullptr, m_password.c_str()); |
| 161 | if (rc != SSH_AUTH_SUCCESS) { |
| 162 | throw Exception{ssh_get_error(m_session)}; |
| 163 | } |
| 164 | |
| 165 | // Allocate the SFTP session. |
| 166 | m_sftp = sftp_new(m_session); |
| 167 | if (!m_sftp) { |
| 168 | throw Exception{ssh_get_error(m_session)}; |
| 169 | } |
| 170 | |
| 171 | // Initialize. |
| 172 | rc = sftp_init(m_sftp); |
| 173 | if (rc != SSH_OK) { |
| 174 | sftp_free(m_sftp); |
| 175 | m_sftp = nullptr; |
| 176 | throw Exception{ssh_get_error(m_session)}; |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | void Session::Disconnect() { |
| 181 | if (m_sftp) { |
| 182 | sftp_free(m_sftp); |
| 183 | m_sftp = nullptr; |
| 184 | } |
| 185 | ssh_disconnect(m_session); |
| 186 | } |
| 187 | |
| 188 | std::vector<Attributes> Session::ReadDir(const std::string& path) { |
| 189 | sftp_dir dir = sftp_opendir(m_sftp, path.c_str()); |
| 190 | if (!dir) { |
| 191 | throw Exception{m_sftp}; |
| 192 | } |
| 193 | |
| 194 | std::vector<Attributes> rv; |
| 195 | while (sftp_attributes attr = sftp_readdir(m_sftp, dir)) { |
| 196 | rv.emplace_back(std::move(attr)); |
| 197 | } |
| 198 | |
| 199 | sftp_closedir(dir); |
| 200 | return rv; |
| 201 | } |
| 202 | |
| 203 | void Session::Unlink(const std::string& filename) { |
| 204 | if (sftp_unlink(m_sftp, filename.c_str()) < 0) { |
| 205 | throw Exception{m_sftp}; |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | File Session::Open(const std::string& filename, int accesstype, mode_t mode) { |
| 210 | sftp_file f = sftp_open(m_sftp, filename.c_str(), accesstype, mode); |
| 211 | if (!f) { |
| 212 | throw Exception{m_sftp}; |
| 213 | } |
| 214 | return File{std::move(f)}; |
| 215 | } |