diff options
Diffstat (limited to 'libs/sftp/src/main/cpp')
| l--------- | libs/sftp/src/main/cpp/jni.cpp | 1 | ||||
| l--------- | libs/sftp/src/main/cpp/jni.hpp | 1 | ||||
| -rw-r--r-- | libs/sftp/src/main/cpp/sftp.cpp | 688 |
3 files changed, 690 insertions, 0 deletions
diff --git a/libs/sftp/src/main/cpp/jni.cpp b/libs/sftp/src/main/cpp/jni.cpp new file mode 120000 index 0000000..4aa7f0b --- /dev/null +++ b/libs/sftp/src/main/cpp/jni.cpp @@ -0,0 +1 @@ +../../../../samba/src/main/cpp/jni.cpp
\ No newline at end of file diff --git a/libs/sftp/src/main/cpp/jni.hpp b/libs/sftp/src/main/cpp/jni.hpp new file mode 120000 index 0000000..ea14057 --- /dev/null +++ b/libs/sftp/src/main/cpp/jni.hpp @@ -0,0 +1 @@ +../../../../samba/src/main/cpp/jni.hpp
\ No newline at end of file diff --git a/libs/sftp/src/main/cpp/sftp.cpp b/libs/sftp/src/main/cpp/sftp.cpp new file mode 100644 index 0000000..0feafb1 --- /dev/null +++ b/libs/sftp/src/main/cpp/sftp.cpp @@ -0,0 +1,688 @@ +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <unistd.h> + +#include <memory> +#include <optional> +#include <string> +#include <utility> +#include <vector> + +#include "jni.hpp" +#include "libssh2.h" +#include "libssh2_sftp.h" + +namespace { + +class SftpOwner { + public: + SftpOwner(std::shared_ptr<LIBSSH2_SESSION> ssh_session, LIBSSH2_SFTP* sftp) + : ssh_session_(std::move(ssh_session)), sftp_(sftp) {} + + ~SftpOwner() { + libssh2_sftp_shutdown(sftp_); + } + + SftpOwner(const SftpOwner&) = delete; + SftpOwner& operator=(const SftpOwner&) = delete; + + [[nodiscard]] LIBSSH2_SFTP* get() const { return sftp_; } + + private: + std::shared_ptr<LIBSSH2_SESSION> ssh_session_; + LIBSSH2_SFTP* sftp_; +}; + +class SftpHandle { + public: + SftpHandle(std::shared_ptr<SftpOwner> sftp, LIBSSH2_SFTP_HANDLE* handle) + : sftp_(std::move(sftp)), handle_(handle) {} + + ~SftpHandle() { + libssh2_sftp_close_handle(handle_); + } + + SftpHandle(const SftpHandle&) = delete; + SftpHandle& operator=(const SftpHandle&) = delete; + + protected: + [[nodiscard]] LIBSSH2_SFTP_HANDLE* get() const { return handle_; } + + private: + std::shared_ptr<SftpOwner> sftp_; + LIBSSH2_SFTP_HANDLE* handle_; +}; + +jni::LocalRef<jobject> CreateDirEntry(JNIEnv* env, const char* name, + const LIBSSH2_SFTP_ATTRIBUTES& attrs); + +class Dir : SftpHandle { + public: + Dir(std::shared_ptr<SftpOwner> sftp, LIBSSH2_SFTP_HANDLE *handle) + : SftpHandle(std::move(sftp), handle) {} + + jni::LocalRef<jobjectArray> List(JNIEnv* env, const jni::Ref<jclass>& entry_clazz) { + std::vector<jni::LocalRef<jobject>> tmp; + size_t bufsize = 1024; + auto buf = std::make_unique<char[]>(bufsize); + LIBSSH2_SFTP_ATTRIBUTES attrs; + while (true) { + auto size = bufsize / 2; + auto ret = libssh2_sftp_readdir_ex(get(), buf.get(), size, buf.get() + size, size, &attrs); + if (ret == 0) + break; + if (ret > 0) { + // Skip . and .. entries. + if (buf[0] == '.' && (ret == 1 || (buf[1] == '.' && ret == 2))) + continue; + auto obj = CreateDirEntry(env, buf.get(), attrs); + if (obj) + tmp.push_back(std::move(obj)); + } else if (ret == LIBSSH2_ERROR_BUFFER_TOO_SMALL) { + auto newsize = bufsize * 2; + // Protect against overflow + if (newsize <= bufsize) + break; + buf = std::make_unique<char[]>(newsize); + } else { + // Error + break; + } + } + return jni::CreateArray(env, entry_clazz, std::move(tmp)); + } +}; + +class File : SftpHandle { + public: + File(std::shared_ptr<SftpOwner> sftp, LIBSSH2_SFTP_HANDLE *handle) + : SftpHandle(std::move(sftp), handle) {} + + int32_t Read(uint8_t* data, int32_t size) { + if (size <= 0) return 0; + return libssh2_sftp_read(get(), reinterpret_cast<char*>(data), size); + } + + void Seek(int64_t offset) { + libssh2_sftp_seek64(get(), offset); + } + + int32_t Write(const uint8_t* data, int32_t size) { + if (size <= 0) return 0; + return libssh2_sftp_write(get(), reinterpret_cast<const char*>(data), size); + } +}; + +class SftpSession { + public: + explicit SftpSession(std::shared_ptr<SftpOwner> sftp) + : sftp_(std::move(sftp)) {} + + ~SftpSession() = default; + + SftpSession(const SftpSession &) = delete; + + SftpSession &operator=(const SftpSession &) = delete; + + std::string GetLastError() { + switch (libssh2_sftp_last_error(sftp_->get())) { + case LIBSSH2_FX_OK: + return ""; + case LIBSSH2_FX_EOF: + return "End of file"; + case LIBSSH2_FX_NO_SUCH_FILE: + return "No such file"; + case LIBSSH2_FX_PERMISSION_DENIED: + return "Permission denied"; + case LIBSSH2_FX_FAILURE: + return "Failure"; + case LIBSSH2_FX_BAD_MESSAGE: + return "Bad message"; + case LIBSSH2_FX_NO_CONNECTION: + return "No connection"; + case LIBSSH2_FX_CONNECTION_LOST: + return "Connection lost"; + case LIBSSH2_FX_OP_UNSUPPORTED: + return "Operation unsupported"; + case LIBSSH2_FX_INVALID_HANDLE: + return "Invalid handle"; + case LIBSSH2_FX_NO_SUCH_PATH: + return "No such path"; + case LIBSSH2_FX_FILE_ALREADY_EXISTS: + return "File already exists"; + case LIBSSH2_FX_WRITE_PROTECT: + return "Write protected"; + case LIBSSH2_FX_NO_MEDIA: + return "No media"; + case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM: + return "No space on filesystem"; + case LIBSSH2_FX_QUOTA_EXCEEDED: + return "Quota exceeded"; + case LIBSSH2_FX_UNKNOWN_PRINCIPAL: + return "Unknown principal"; + case LIBSSH2_FX_LOCK_CONFLICT: + return "Lock conflict"; + case LIBSSH2_FX_DIR_NOT_EMPTY: + return "Directory is not empty"; + case LIBSSH2_FX_NOT_A_DIRECTORY: + return "Not a directory"; + case LIBSSH2_FX_INVALID_FILENAME: + return "Invalid filename"; + case LIBSSH2_FX_LINK_LOOP: + return "Link loop"; + default: + return "Unknown error"; + } + } + + std::unique_ptr<Dir> OpenDir(const std::string &path) { + auto* handle = libssh2_sftp_open_ex(sftp_->get(), path.data(), path.size(), + LIBSSH2_FXF_READ, + 0700, + LIBSSH2_SFTP_OPENDIR); + if (!handle) return nullptr; + return std::make_unique<Dir>(sftp_, handle); + } + + bool MakeDir(const std::string &path, int32_t mode) { + return libssh2_sftp_mkdir_ex(sftp_->get(), path.data(), path.size(), mode) == 0; + } + + bool RemoveDir(const std::string &path) { + return libssh2_sftp_rmdir_ex(sftp_->get(), path.data(), path.size()) == 0; + } + + std::unique_ptr<File> OpenFile(const std::string &path, int32_t mode) { + unsigned long flags = 0; + switch (mode) { + case 0: // READ(0), + flags |= LIBSSH2_FXF_READ; + break; + case 1: // WRITE_CREATE_TRUNCATE(1), + flags |= LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC; + break; + default: + return nullptr; + } + auto* handle = libssh2_sftp_open_ex(sftp_->get(), path.data(), path.size(), + flags, + 0700, + LIBSSH2_SFTP_OPENFILE); + if (!handle) return nullptr; + return std::make_unique<File>(sftp_, handle); + } + + bool Unlink(const std::string &path) { + return libssh2_sftp_unlink_ex(sftp_->get(), path.data(), path.size()) == 0; + } + + bool Symlink(const std::string &target, const std::string &path) { + // symlink fails is path already exists, so remove any existing entry first. + libssh2_sftp_unlink_ex(sftp_->get(), path.data(), path.size()); + // The argument order does not seem to match the documentation, so this + // might change? + return libssh2_sftp_symlink_ex(sftp_->get(), target.data(), target.size(), + const_cast<char*>(path.data()), path.size(), + LIBSSH2_SFTP_SYMLINK) == 0; + } + + std::optional<std::string> Readlink(const std::string &path) { + unsigned int bufsize = 8192; + std::vector<char> buf; + while (true) { + buf.resize(bufsize); + auto ret = libssh2_sftp_symlink_ex(sftp_->get(), path.data(), path.size(), + buf.data(), buf.size(), LIBSSH2_SFTP_READLINK); + if (ret != LIBSSH2_ERROR_BUFFER_TOO_SMALL) { + if (ret >= 0) return std::string(buf.data(), ret); + return std::nullopt; + } + const auto previous = bufsize; + bufsize *= 2; + // Check for bufsize overflow. + if (bufsize <= previous) + return std::nullopt; + } + } + + jni::LocalRef<jobject> Stat(JNIEnv* env, const std::string &path, bool follow_link) { + LIBSSH2_SFTP_ATTRIBUTES attrs; + auto ret = libssh2_sftp_stat_ex(sftp_->get(), path.data(), path.size(), + follow_link ? LIBSSH2_SFTP_STAT : LIBSSH2_SFTP_LSTAT, + &attrs); + if (ret) return nullptr; + return CreateDirEntry(env, path.c_str(), attrs); + } + + private: + std::shared_ptr<SftpOwner> sftp_; +}; + +class unique_fd { + public: + constexpr unique_fd() + : fd_(-1) {} + explicit unique_fd(int fd) + : fd_(fd) {} + + ~unique_fd() { + if (fd_ != -1) close(fd_); + } + + unique_fd(const unique_fd&) = delete; + unique_fd& operator=(const unique_fd&) = delete; + unique_fd(unique_fd&& other) noexcept + : fd_(other.release()) {} + unique_fd& operator=(unique_fd&& other) noexcept { + reset(other.release()); + return *this; + } + + [[nodiscard]] int get() const { return fd_; } + + explicit operator bool() const { return fd_ != -1; } + + int release() { + int fd = fd_; + fd_ = -1; + return fd; + } + + void reset() { + reset(-1); + } + + void reset(int fd) { + if (fd_ != -1) close(fd_); + fd_ = fd; + } + + private: + int fd_; +}; + +class SshSession { + public: + explicit SshSession(LIBSSH2_SESSION* session) : session_(session, SessionDeleter{}) { + libssh2_session_set_blocking(session_.get(), 1); + } + ~SshSession() = default; + + SshSession(const SshSession&) = delete; + SshSession& operator=(const SshSession&) = delete; + + std::string GetLastError() { + char* ptr = nullptr; + int len = 0; + libssh2_session_last_error(session_.get(), &ptr, &len, /* want_buf */ 0); + return {ptr, static_cast<size_t>(len)}; + } + + bool Connect(const std::string& host, int32_t port) { + struct addrinfo hints{}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + hints.ai_flags = AI_ADDRCONFIG; + struct addrinfo* ptr = nullptr; + char tmp[10]; + snprintf(tmp, sizeof(tmp), "%d", static_cast<int>(port)); + int ret = getaddrinfo(host.c_str(), tmp, &hints, &ptr); + if (ret) { + libssh2_session_set_last_error(session_.get(), LIBSSH2_ERROR_SOCKET_NONE, gai_strerror(ret)); + return false; + } + auto* i = ptr; + for (; i; i = i->ai_next) { + unique_fd fd(socket(i->ai_family, i->ai_socktype, i->ai_protocol)); + if (fd) { + if (connect(fd.get(), i->ai_addr, i->ai_addrlen) == 0) { + fd_ = std::move(fd); + freeaddrinfo(ptr); + return true; + } + } + libssh2_session_set_last_error(session_.get(), LIBSSH2_ERROR_SOCKET_NONE, strerror(errno)); + } + freeaddrinfo(ptr); + return false; + } + + std::optional<std::vector<uint8_t>> Handshake() { + auto ret = libssh2_session_handshake(session_.get(), fd_.get()); + if (ret) + return std::nullopt; + auto* fingerprint = libssh2_hostkey_hash(session_.get(), LIBSSH2_HOSTKEY_HASH_SHA256); + if (!fingerprint) + return std::nullopt; + return std::vector<uint8_t>(fingerprint, fingerprint + 32); + } + + bool Authenticate(const std::string& username, const std::string& password) { + return libssh2_userauth_password_ex(session_.get(), + username.data(), username.size(), + password.data(), password.size(), + nullptr) == 0; + } + + bool Authenticate(const std::string& username, const std::vector<uint8_t>& public_key, + const std::vector<uint8_t>& private_key, const std::string& passphrase) { + return libssh2_userauth_publickey_frommemory( + session_.get(), username.data(), username.size(), + reinterpret_cast<const char*>(public_key.data()), public_key.size(), + reinterpret_cast<const char*>(private_key.data()), private_key.size(), + passphrase.c_str()) == 0; + } + + std::unique_ptr<SftpSession> NewSftpSession() { + auto* sftp_session = libssh2_sftp_init(session_.get()); + if (!sftp_session) return nullptr; + return std::make_unique<SftpSession>(std::make_shared<SftpOwner>(session_, sftp_session)); + } + + private: + struct SessionDeleter { + void operator()(LIBSSH2_SESSION* session) { + if (session) { + libssh2_session_disconnect(session, "Normal Shutdown"); + libssh2_session_free(session); + } + } + }; + + std::shared_ptr<LIBSSH2_SESSION> session_; + unique_fd fd_; +}; + +#pragma clang diagnostic push +#pragma ide diagnostic ignored "MemoryLeak" +jlong nativeSshSessionNew(JNIEnv*, jclass) { + auto* session = libssh2_session_init(); + if (!session) return 0; + return reinterpret_cast<jlong>(new SshSession(session)); +} +#pragma clang diagnostic pop + +void nativeSshSessionDestroy(JNIEnv*, jclass, jlong ptr) { + delete reinterpret_cast<SshSession*>(ptr); +} + +jstring nativeSshSessionGetLastError(JNIEnv* env, jclass, jlong ptr) { + return jni::UTF8ToString(env, reinterpret_cast<SshSession*>(ptr)->GetLastError()).release(); +} + +jboolean nativeSshSessionConnect(JNIEnv* env, jclass, jlong ptr, jstring host, jint port) { + return reinterpret_cast<SshSession*>(ptr)->Connect( + jni::StringToUTF8(env, jni::ParamRef<jstring>(env, host)), + static_cast<int32_t>(port)) ? JNI_TRUE : JNI_FALSE; +} + +jbyteArray nativeSshSessionHandshake(JNIEnv* env, jclass, jlong ptr) { + auto fingerprint = reinterpret_cast<SshSession*>(ptr)->Handshake(); + if (fingerprint.has_value()) { + return jni::VectorToByteArray(env, fingerprint.value()).release(); + } + return nullptr; +} + +jboolean nativeSshSessionAuthenticate(JNIEnv* env, jclass, jlong ptr, jstring j_username, + jstring password, jbyteArray public_key, + jbyteArray private_key) { + auto username = jni::StringToUTF8(env, jni::ParamRef<jstring>(env, j_username)); + if (public_key != nullptr && private_key != nullptr) { + return reinterpret_cast<SshSession*>(ptr)->Authenticate( + username, + jni::ByteArrayToVector(env, jni::ParamRef<jbyteArray>(env, public_key)), + jni::ByteArrayToVector(env, jni::ParamRef<jbyteArray>(env, private_key)), + password != nullptr ? jni::StringToUTF8(env, jni::ParamRef<jstring>(env, password)) : "") + ? JNI_TRUE : JNI_FALSE; + } + if (password != nullptr) { + return reinterpret_cast<SshSession *>(ptr)->Authenticate( + username, + jni::StringToUTF8(env, jni::ParamRef<jstring>(env, password))) + ? JNI_TRUE : JNI_FALSE; + } + return JNI_FALSE; +} + +jlong nativeSshSessionNewSftpSession(JNIEnv*, jclass, jlong ptr) { + return reinterpret_cast<jlong>(reinterpret_cast<SshSession*>(ptr)->NewSftpSession().release()); +} + +void nativeSftpSessionDestroy(JNIEnv*, jclass, jlong ptr) { + delete reinterpret_cast<SftpSession*>(ptr); +} + +jstring nativeSftpSessionGetLastError(JNIEnv* env, jclass, jlong ptr) { + return jni::UTF8ToString(env, reinterpret_cast<SftpSession*>(ptr)->GetLastError()).release(); +} + +jlong nativeSftpSessionOpenDir(JNIEnv* env, jclass, jlong ptr, jstring path) { + auto dir = reinterpret_cast<SftpSession*>(ptr)->OpenDir( + jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))); + if (!dir) return 0L; + return reinterpret_cast<jlong>(dir.release()); +} + +jboolean nativeSftpSessionMakeDir(JNIEnv* env, jclass, jlong ptr, jstring path, jint mode) { + return reinterpret_cast<SftpSession*>(ptr)->MakeDir( + jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path)), mode) + ? JNI_TRUE : JNI_FALSE; +} + +jboolean nativeSftpSessionRemoveDir(JNIEnv* env, jclass, jlong ptr, jstring path) { + return reinterpret_cast<SftpSession*>(ptr)->RemoveDir( + jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))) + ? JNI_TRUE : JNI_FALSE; +} + +jlong nativeSftpSessionOpenFile(JNIEnv* env, jclass, jlong ptr, jstring path, jint mode) { + auto file = reinterpret_cast<SftpSession*>(ptr)->OpenFile( + jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path)), mode); + if (!file) return 0L; + return reinterpret_cast<jlong>(file.release()); +} + +jboolean nativeSftpSessionUnlink(JNIEnv* env, jclass, jlong ptr, jstring path) { + return reinterpret_cast<SftpSession*>(ptr)->Unlink( + jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))) + ? JNI_TRUE : JNI_FALSE; +} + +jboolean nativeSftpSessionSymlink(JNIEnv* env, jclass, jlong ptr, jstring target, jstring path) { + return reinterpret_cast<SftpSession*>(ptr)->Symlink( + jni::StringToUTF8(env, jni::ParamRef<jstring>(env, target)), + jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))) + ? JNI_TRUE : JNI_FALSE; +} + +jstring nativeSftpSessionReadlink(JNIEnv* env, jclass, jlong ptr, jstring path) { + auto target = reinterpret_cast<SftpSession*>(ptr)->Readlink( + jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))); + if (target.has_value()) return jni::UTF8ToString(env, target.value()).release(); + return nullptr; +} + +jobject nativeSftpSessionStat(JNIEnv* env, jclass, jlong ptr, jstring path, jboolean follow_link) { + return reinterpret_cast<SftpSession*>(ptr)->Stat( + env, jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path)), follow_link).release(); +} + +void nativeDirDestroy(JNIEnv*, jclass, jlong ptr) { + delete reinterpret_cast<Dir*>(ptr); +} + +jni::GlobalRef<jclass> g_DirEntryClass(nullptr, nullptr); + +jobjectArray nativeDirList(JNIEnv* env, jclass, jlong ptr) { + return reinterpret_cast<Dir*>(ptr)->List(env, g_DirEntryClass).release(); +} + +void nativeFileDestroy(JNIEnv*, jclass, jlong ptr) { + delete reinterpret_cast<File*>(ptr); +} + +jint nativeFileRead(JNIEnv* env, jclass, jlong ptr, jbyteArray array, jint offset, jint length) { + jboolean is_copy = JNI_FALSE; + bool critical = true; + auto* data = reinterpret_cast<jbyte*>(env->GetPrimitiveArrayCritical(array, &is_copy)); + if (!data) { + critical = false; + data = env->GetByteArrayElements(array, &is_copy); + if (!data) return -1; + } + auto ret = reinterpret_cast<File *>(ptr)->Read(reinterpret_cast<uint8_t *>(data + offset), length); + if (critical) { + env->ReleasePrimitiveArrayCritical(array, data, JNI_COMMIT); + } else { + env->ReleaseByteArrayElements(array, data, JNI_COMMIT); + } + return ret; +} + +void nativeFileSeek(JNIEnv*, jclass, jlong ptr, jlong offset) { + reinterpret_cast<File*>(ptr)->Seek(offset); +} + +jint nativeFileWrite(JNIEnv* env, jclass, jlong ptr, jbyteArray array, jint offset, jint length) { + jboolean is_copy = JNI_FALSE; + bool critical = true; + auto* data = reinterpret_cast<jbyte*>(env->GetPrimitiveArrayCritical(array, &is_copy)); + if (!data) { + critical = false; + data = env->GetByteArrayElements(array, &is_copy); + if (!data) return -1; + } + auto ret = reinterpret_cast<File *>(ptr)->Write(reinterpret_cast<uint8_t *>(data + offset), length); + if (critical) { + env->ReleasePrimitiveArrayCritical(array, data, JNI_ABORT); + } else { + env->ReleaseByteArrayElements(array, data, JNI_ABORT); + } + return ret; +} + +jni::GlobalRef<jclass> g_NativeSftpClass(nullptr, nullptr); +jmethodID g_CreateDirEntry; + +void RegisterSftp(JNIEnv* env) { + auto clazz = jni::FindClass(env, "org/the_jk/cleversync/io/sftp/NativeSftp"); + ABORT_IF_NULL(env, clazz); + auto dir_entry_clazz = jni::FindClass(env, "org/the_jk/cleversync/io/sftp/NativeSftp$DirEntry"); + ABORT_IF_NULL(env, dir_entry_clazz); + static const JNINativeMethod methods[] = { + { "nativeSshSessionNew", "()J", reinterpret_cast<void*>(&nativeSshSessionNew) }, + { "nativeSshSessionDestroy", "(J)V", reinterpret_cast<void*>(&nativeSshSessionDestroy) }, + { "nativeSshSessionGetLastError", "(J)Ljava/lang/String;", reinterpret_cast<void*>(&nativeSshSessionGetLastError) }, + { "nativeSshSessionConnect", "(JLjava/lang/String;I)Z", reinterpret_cast<void*>(&nativeSshSessionConnect) }, + { "nativeSshSessionHandshake", "(J)[B", reinterpret_cast<void*>(&nativeSshSessionHandshake) }, + { "nativeSshSessionAuthenticate", "(JLjava/lang/String;Ljava/lang/String;[B[B)Z", reinterpret_cast<void*>(&nativeSshSessionAuthenticate) }, + { "nativeSshSessionNewSftpSession", "(J)J", reinterpret_cast<void*>(&nativeSshSessionNewSftpSession) }, + + { "nativeSftpSessionDestroy", "(J)V", reinterpret_cast<void*>(&nativeSftpSessionDestroy) }, + { "nativeSftpSessionGetLastError", "(J)Ljava/lang/String;", reinterpret_cast<void*>(&nativeSftpSessionGetLastError) }, + { "nativeSftpSessionOpenDir", "(JLjava/lang/String;)J", reinterpret_cast<void*>(&nativeSftpSessionOpenDir) }, + { "nativeSftpSessionMakeDir", "(JLjava/lang/String;I)Z", reinterpret_cast<void*>(&nativeSftpSessionMakeDir) }, + { "nativeSftpSessionRemoveDir", "(JLjava/lang/String;)Z", reinterpret_cast<void*>(&nativeSftpSessionRemoveDir) }, + { "nativeSftpSessionOpenFile", "(JLjava/lang/String;I)J", reinterpret_cast<void*>(&nativeSftpSessionOpenFile) }, + { "nativeSftpSessionUnlink", "(JLjava/lang/String;)Z", reinterpret_cast<void*>(&nativeSftpSessionUnlink) }, + { "nativeSftpSessionSymlink", "(JLjava/lang/String;Ljava/lang/String;)Z", reinterpret_cast<void*>(&nativeSftpSessionSymlink) }, + { "nativeSftpSessionReadlink", "(JLjava/lang/String;)Ljava/lang/String;", reinterpret_cast<void*>(&nativeSftpSessionReadlink) }, + { "nativeSftpSessionStat", "(JLjava/lang/String;Z)Lorg/the_jk/cleversync/io/sftp/NativeSftp$DirEntry;", reinterpret_cast<void*>(&nativeSftpSessionStat) }, + + { "nativeDirDestroy", "(J)V", reinterpret_cast<void*>(&nativeDirDestroy) }, + { "nativeDirList", "(J)[Lorg/the_jk/cleversync/io/sftp/NativeSftp$DirEntry;", reinterpret_cast<void*>(&nativeDirList) }, + + { "nativeFileDestroy", "(J)V", reinterpret_cast<void*>(&nativeFileDestroy) }, + { "nativeFileRead", "(J[BII)I", reinterpret_cast<void*>(&nativeFileRead) }, + { "nativeFileSeek", "(JJ)V", reinterpret_cast<void*>(&nativeFileSeek) }, + { "nativeFileWrite", "(J[BII)I", reinterpret_cast<void*>(&nativeFileWrite) }, + + }; + auto ret = env->RegisterNatives(clazz.get(), methods, sizeof(methods) / sizeof(methods[0])); + ABORT_IF_NOT_OK(ret); + + g_CreateDirEntry = env->GetStaticMethodID( + clazz.get(), "createDirEntry", + "(Ljava/lang/String;IJJ)Lorg/the_jk/cleversync/io/sftp/NativeSftp$DirEntry;"); + ABORT_IF_NULL(env, g_CreateDirEntry); + g_NativeSftpClass = clazz; + g_DirEntryClass = dir_entry_clazz; +} + +void UnregisterSftp() { + g_CreateDirEntry = nullptr; + g_NativeSftpClass.reset(); + g_DirEntryClass.reset(); +} + +jni::LocalRef<jobject> CreateDirEntry(JNIEnv* env, const char* name, + const LIBSSH2_SFTP_ATTRIBUTES& attrs) { + auto j_name = jni::UTF8ToString(env, name); + jlong size; + jlong last_modified; + jint type; + + if (attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) { + // Kotlin size casts Long to ULong + size = static_cast<jlong>(attrs.filesize); + } else { + size = 0L; + } + + if (attrs.flags & LIBSSH2_SFTP_ATTR_ACMODTIME) { + // Kotlin size casts Long to ULong + last_modified = static_cast<jlong>(attrs.mtime); + } else { + last_modified = 0L; + } + + if (attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) { + switch (attrs.permissions & LIBSSH2_SFTP_S_IFMT) { + case LIBSSH2_SFTP_S_IFREG: + type = 1; + break; + case LIBSSH2_SFTP_S_IFDIR: + type = 0; + break; + case LIBSSH2_SFTP_S_IFLNK: + type = 2; + break; + case LIBSSH2_SFTP_S_IFIFO: + case LIBSSH2_SFTP_S_IFCHR: + case LIBSSH2_SFTP_S_IFBLK: + case LIBSSH2_SFTP_S_IFSOCK: + default: + // Skip unknown or "weird" types + return nullptr; + } + } else { + // Skip unknown entries + return nullptr; + } + + return jni::CallStaticObjectMethod<jobject>(env, g_NativeSftpClass, g_CreateDirEntry, + j_name.get(), type, size, last_modified); +} + +} // namespace + +jint JNI_OnLoad(JavaVM *vm, void *) { + auto* env = jni::OnLoad(vm); + + // TODO: Check return + libssh2_init(0); + + RegisterSftp(env); + + return jni::JNI_VERSION; +} + +void JNI_OnUnload(JavaVM *, void *) { + // Not called on Android (or in general), but if it where, this would be the place to unregister. + UnregisterSftp(); + + libssh2_exit(); +} |
