From d372cdcea3b3a0ba4b49180695c4e6b0e2d074a5 Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Mon, 19 Aug 2024 00:34:03 +0200 Subject: Increase the samba implemetation With the exception of openDir, largely untested. --- libs/samba/src/main/cpp/samba.cpp | 128 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) (limited to 'libs/samba/src/main/cpp/samba.cpp') diff --git a/libs/samba/src/main/cpp/samba.cpp b/libs/samba/src/main/cpp/samba.cpp index e3b689d..7b83eb4 100644 --- a/libs/samba/src/main/cpp/samba.cpp +++ b/libs/samba/src/main/cpp/samba.cpp @@ -11,6 +11,8 @@ namespace { +jni::LocalRef CreateDirEntry(JNIEnv* env, const std::string& name, const smb2_stat_64& stat); + class Dir { public: Dir(std::shared_ptr context, smb2dir* dir) : context_(std::move(context)), dir_(dir) { @@ -24,6 +26,20 @@ class Dir { Dir(const Dir&) = delete; Dir& operator=(const Dir&) = delete; + jni::LocalRef List(JNIEnv* env, const jni::Ref& dir_entry_clazz) { + smb2_rewinddir(context_.get(), dir_); + std::vector> tmp; + while (auto* ent = smb2_readdir(context_.get(), dir_)) { + // Skip . and .. entries. + if (ent->name[0] == '.' && (ent->name[1] == '\0' || (ent->name[1] == '.' && ent->name[2] == '\0'))) continue; + auto obj = CreateDirEntry(env, ent->name, ent->st); + if (obj) { + tmp.push_back(std::move(obj)); + } + } + return jni::CreateArray(env, dir_entry_clazz, std::move(tmp)); + } + private: std::shared_ptr context_; smb2dir* const dir_; @@ -81,6 +97,50 @@ class Context { return ptr ? std::make_unique(context_, ptr) : nullptr; } + [[nodiscard]] jni::LocalRef Entry(JNIEnv* env, const std::string& path) { + struct smb2_stat_64 stat; // NOLINT(*-pro-type-member-init) + auto ret = smb2_stat(context_.get(), path.c_str(), &stat); + if (ret) return {env, nullptr}; + auto slash = path.find_last_of('/'); + return CreateDirEntry(env, slash == std::string::npos ? path : path.substr(slash + 1), stat); + } + + bool MakeDir(const std::string& path) { + return smb2_mkdir(context_.get(), path.c_str()) == 0; + } + + bool RemoveDir(const std::string& path) { + return smb2_rmdir(context_.get(), path.c_str()) == 0; + } + + bool Unlink(const std::string& path) { + return smb2_unlink(context_.get(), path.c_str()) == 0; + } + + std::optional ReadLink(const std::string& path) { + // Good to start with a fairly small size as current implementation + // of smb2_readlink uses strncpy, which pads the whole unused buffer + // with zeros. + uint32_t bufsize = 256; + std::vector buf; + while (true) { + buf.resize(bufsize); + auto ret = smb2_readlink(context_.get(), path.c_str(), buf.data(), bufsize); + if (ret != 0) + return std::nullopt; + // smb2_readlink uses strncpy, so if actual path was larger than bufsize + // there will be no terminating zero. + auto it = std::find(buf.begin(), buf.end(), '\0'); + if (it != buf.end()) + return std::string(buf.begin(), it); + const auto previous = bufsize; + bufsize *= 2; + // Check for bufsize (a uint32_t) overflow. + if (bufsize <= previous) + return std::nullopt; + } + } + private: struct ContextDeleter { void operator()(smb2_context* context) { @@ -121,6 +181,29 @@ jlong nativeContextOpenDir(JNIEnv* env, jclass clazz, jlong ptr, jstring path) { return reinterpret_cast(reinterpret_cast(ptr)->OpenDir(jni::StringToUTF8(env, jni::ParamRef(env, path))).release()); } +jobject nativeContextEntry(JNIEnv* env, jclass clazz, jlong ptr, jstring path) { + return reinterpret_cast(ptr)->Entry(env, jni::StringToUTF8(env, jni::ParamRef(env, path))).release(); +} + +jboolean nativeContextMakeDir(JNIEnv* env, jclass clazz, jlong ptr, jstring path) { + return reinterpret_cast(ptr)->MakeDir(jni::StringToUTF8(env, jni::ParamRef(env, path))) ? JNI_TRUE : JNI_FALSE; +} + +jboolean nativeContextRemoveDir(JNIEnv* env, jclass clazz, jlong ptr, jstring path) { + return reinterpret_cast(ptr)->RemoveDir(jni::StringToUTF8(env, jni::ParamRef(env, path))) ? JNI_TRUE : JNI_FALSE; +} + +jboolean nativeContextUnlink(JNIEnv* env, jclass clazz, jlong ptr, jstring path) { + return reinterpret_cast(ptr)->Unlink(jni::StringToUTF8(env, jni::ParamRef(env, path))) ? JNI_TRUE : JNI_FALSE; +} + +jstring nativeContextReadLink(JNIEnv* env, jclass clazz, jlong ptr, jstring path) { + auto ret = reinterpret_cast(ptr)->ReadLink(jni::StringToUTF8(env, jni::ParamRef(env, path))); + if (ret.has_value()) + return jni::UTF8ToString(env, ret.value()).release(); + return nullptr; +} + void nativeUrlDestroy(JNIEnv* env, jclass clazz, jlong ptr) { delete reinterpret_cast(ptr); } @@ -133,9 +216,20 @@ void nativeDirDestroy(JNIEnv* env, jclass clazz, jlong ptr) { delete reinterpret_cast(ptr); } +jni::GlobalRef g_DirEntryClass(nullptr, nullptr); + +jobjectArray nativeDirList(JNIEnv* env, jclass clazz, jlong ptr) { + return reinterpret_cast(ptr)->List(env, g_DirEntryClass).release(); +} + +jni::GlobalRef g_NativeSambaClass(nullptr, nullptr); +jmethodID g_CreateDirEntry; + void RegisterSamba(JNIEnv* env) { auto clazz = jni::FindClass(env, "org/the_jk/cleversync/io/samba/NativeSamba"); ABORT_IF_NULL(env, clazz); + auto dir_entry_clazz = jni::FindClass(env, "org/the_jk/cleversync/io/samba/NativeSamba$DirEntry"); + ABORT_IF_NULL(env, dir_entry_clazz); static const JNINativeMethod methods[] = { { "nativeContextNew", "(I)J", reinterpret_cast(&nativeContextNew) }, { "nativeContextDestroy", "(J)V", reinterpret_cast(&nativeContextDestroy) }, @@ -143,14 +237,48 @@ void RegisterSamba(JNIEnv* env) { { "nativeContextConnect", "(JJLjava/lang/String;Ljava/lang/String;)Z", reinterpret_cast(&nativeContextConnect) }, { "nativeContextGetError", "(J)Ljava/lang/String;", reinterpret_cast(&nativeContextGetError) }, { "nativeContextOpenDir", "(JLjava/lang/String;)J", reinterpret_cast(&nativeContextOpenDir) }, + { "nativeContextEntry", "(JLjava/lang/String;)Lorg/the_jk/cleversync/io/samba/NativeSamba$DirEntry;", reinterpret_cast(&nativeContextEntry) }, + { "nativeContextMakeDir", "(JLjava/lang/String;)Z", reinterpret_cast(&nativeContextMakeDir) }, + { "nativeContextRemoveDir", "(JLjava/lang/String;)Z", reinterpret_cast(&nativeContextRemoveDir) }, + { "nativeContextUnlink", "(JLjava/lang/String;)Z", reinterpret_cast(&nativeContextUnlink) }, + { "nativeContextReadLink", "(JLjava/lang/String;)Ljava/lang/String;", reinterpret_cast(&nativeContextReadLink) }, { "nativeUrlDestroy", "(J)V", reinterpret_cast(&nativeUrlDestroy) }, { "nativeUrlPath", "(J)Ljava/lang/String;", reinterpret_cast(&nativeUrlPath) }, { "nativeDirDestroy", "(J)V", reinterpret_cast(&nativeDirDestroy) }, + { "nativeDirList", "(J)[Lorg/the_jk/cleversync/io/samba/NativeSamba$DirEntry;", reinterpret_cast(&nativeDirList) }, }; 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/samba/NativeSamba$DirEntry;"); + ABORT_IF_NULL(env, g_CreateDirEntry); + g_NativeSambaClass = clazz; + g_DirEntryClass = dir_entry_clazz; +} + +jni::LocalRef CreateDirEntry(JNIEnv* env, const std::string& name, const smb2_stat_64& stat) { + auto j_name = jni::UTF8ToString(env, name); + // Kotlin size casts Long to ULong + auto size = static_cast(stat.smb2_size); + auto last_modified = static_cast(stat.smb2_mtime); + jint type; + switch (stat.smb2_type) { + case SMB2_TYPE_DIRECTORY: + type = 0; + break; + case SMB2_TYPE_FILE: + type = 1; + break; + case SMB2_TYPE_LINK: + type = 2; + break; + default: + return {env, nullptr}; + } + + return jni::CallStaticObjectMethod(env, g_NativeSambaClass, g_CreateDirEntry, j_name.get(), type, size, last_modified); } } // namespace -- cgit v1.2.3-70-g09d2