summaryrefslogtreecommitdiff
path: root/libs/samba/src/main/cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/samba/src/main/cpp')
-rw-r--r--libs/samba/src/main/cpp/jni.cpp10
-rw-r--r--libs/samba/src/main/cpp/jni.hpp43
-rw-r--r--libs/samba/src/main/cpp/samba.cpp128
3 files changed, 173 insertions, 8 deletions
diff --git a/libs/samba/src/main/cpp/jni.cpp b/libs/samba/src/main/cpp/jni.cpp
index be13df7..30f03a9 100644
--- a/libs/samba/src/main/cpp/jni.cpp
+++ b/libs/samba/src/main/cpp/jni.cpp
@@ -97,4 +97,14 @@ LocalRef<jstring> UTF8ToString(JNIEnv* env, const std::string& str) {
return {env, env->NewStringUTF(str.c_str())};
}
+LocalRef<jobjectArray> CreateArray(JNIEnv* env, const Ref<jclass>& element_class, std::vector<LocalRef<jobject>> objects) {
+ auto ret = LocalRef<jobjectArray>(env, env->NewObjectArray(static_cast<jsize>(objects.size()), element_class.get(), nullptr));
+ ABORT_IF_NULL(env, ret);
+ jsize i = 0;
+ for (auto&& obj : objects) {
+ env->SetObjectArrayElement(ret.get(), i++, obj.release());
+ }
+ return ret;
+}
+
} // namespace jni
diff --git a/libs/samba/src/main/cpp/jni.hpp b/libs/samba/src/main/cpp/jni.hpp
index 90ca011..450fa71 100644
--- a/libs/samba/src/main/cpp/jni.hpp
+++ b/libs/samba/src/main/cpp/jni.hpp
@@ -3,6 +3,7 @@
#include <jni.h>
#include <string>
+#include <vector>
#define ABORT_IF_NOT_OK(x) (::jni::internal::_abort_if_not_ok(__FILE__, __LINE__, (x)))
#define ABORT_IF_NULL(env, x) (::jni::internal::_abort_if_null(__FILE__, __LINE__, (env), (x)))
@@ -21,8 +22,8 @@ class Ref {
public:
constexpr Ref() : env_(nullptr), ptr_(0) {}
Ref(const Ref<T>&) = delete;
- Ref<T>& operator=(const Ref<T>&) = delete;
+ [[nodiscard]] JNIEnv* env() const { return env_; }
[[nodiscard]] T get() const { return ptr_; }
[[nodiscard]] T release() {
auto ret = release_to_local();
@@ -44,7 +45,7 @@ class Ref {
virtual T release_to_local() = 0;
virtual void del() = 0;
- JNIEnv* const env_;
+ JNIEnv* env_;
T ptr_;
};
@@ -54,6 +55,10 @@ class LocalRef : public Ref<T> {
LocalRef(JNIEnv* env, T ptr): Ref<T>(env, ptr) {}
~LocalRef() override { free(); }
+ LocalRef(LocalRef<T> &&ref) noexcept : Ref<T>(ref.env_, ref.release()) {}
+
+ LocalRef& operator=(const Ref<T>& other) = delete;
+
protected:
T release_to_local() override { return this->ptr_; }
void del() override { free(); }
@@ -71,6 +76,8 @@ class ParamRef : public Ref<T> {
ParamRef(JNIEnv* env, T ptr) : Ref<T>(env, ptr) {}
~ParamRef() override = default;
+ ParamRef& operator=(const Ref<T>& other) = delete;
+
protected:
T release_to_local() override {
if (this->ptr_)
@@ -83,11 +90,18 @@ class ParamRef : public Ref<T> {
template<class T>
class GlobalRef : public Ref<T> {
public:
- GlobalRef(JNIEnv* env, T ptr) : Ref<T>(env, ptr ? env->NewGlobalRef(ptr) : 0) {}
- explicit GlobalRef(const Ref<T>& other) : Ref<T>(other.env_, other ? other.env_->NewGlobalRef(other.ptr_) : 0) {}
+ GlobalRef(JNIEnv* env, T ptr) : Ref<T>(env, ptr ? static_cast<T>(env->NewGlobalRef(static_cast<jobject>(ptr))) : 0) {}
+ explicit GlobalRef(const Ref<T>& other) : Ref<T>(other.env(), other ? static_cast<T>(other.env()->NewGlobalRef(static_cast<jobject>(other.get()))) : 0) {}
~GlobalRef() override { free(); }
+ GlobalRef& operator=(const Ref<T>& other) {
+ free();
+ this->env_ = other.env();
+ this->ptr_ = other ? static_cast<T>(this->env_->NewGlobalRef(static_cast<jobject>(other.get()))) : 0;
+ return *this;
+ }
+
protected:
T release_to_local() override {
if (this->ptr_) {
@@ -117,19 +131,32 @@ LocalRef<jclass> FindClass(JNIEnv *env, const char *name);
LocalRef<jthrowable> ExceptionOccurred(JNIEnv* env);
-template<typename Out, typename In>
-LocalRef<Out> CallObjectMethod(JNIEnv* env, const Ref<In>& object, jmethodID method) {
- return {env, static_cast<Out>(env->CallObjectMethod(object.get(), method))};
+template<typename Out, typename In, typename... Params>
+LocalRef<Out> CallObjectMethod(JNIEnv* env, const Ref<In>& object, jmethodID method, Params... params) {
+ return {env, static_cast<Out>(env->CallObjectMethod(object.get(), method, params...))};
+}
+
+template<typename Out, typename... Params>
+LocalRef<Out> CallStaticObjectMethod(JNIEnv* env, const Ref<jclass>& clazz, jmethodID method, Params... params) {
+ return {env, static_cast<Out>(env->CallStaticObjectMethod(clazz.get(), method, params...))};
}
std::string StringToUTF8(JNIEnv* env, const Ref<jstring>& str);
LocalRef<jstring> UTF8ToString(JNIEnv* env, const std::string& str);
+LocalRef<jobjectArray> CreateArray(JNIEnv* env, const Ref<jclass>& element_class, std::vector<LocalRef<jobject>> objects);
+
namespace internal {
template<typename T>
-void _abort_if_null(const char* file, int line, JNIEnv* env, const jni::Ref<T>& ref) {
+inline void _abort_if_null(const char* file, int line, JNIEnv* env, const jni::Ref<T>& ref) {
+ if (ref) [[likely]] return;
+
+ _abort_with_exception(file, line, env);
+}
+
+inline void _abort_if_null(const char* file, int line, JNIEnv* env, jmethodID ref) {
if (ref) [[likely]] return;
_abort_with_exception(file, line, env);
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<jobject> CreateDirEntry(JNIEnv* env, const std::string& name, const smb2_stat_64& stat);
+
class Dir {
public:
Dir(std::shared_ptr<smb2_context> 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<jobjectArray> List(JNIEnv* env, const jni::Ref<jclass>& dir_entry_clazz) {
+ smb2_rewinddir(context_.get(), dir_);
+ std::vector<jni::LocalRef<jobject>> 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<smb2_context> context_;
smb2dir* const dir_;
@@ -81,6 +97,50 @@ class Context {
return ptr ? std::make_unique<Dir>(context_, ptr) : nullptr;
}
+ [[nodiscard]] jni::LocalRef<jobject> 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<std::string> 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<char> 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<jlong>(reinterpret_cast<Context*>(ptr)->OpenDir(jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))).release());
}
+jobject nativeContextEntry(JNIEnv* env, jclass clazz, jlong ptr, jstring path) {
+ return reinterpret_cast<Context*>(ptr)->Entry(env, jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))).release();
+}
+
+jboolean nativeContextMakeDir(JNIEnv* env, jclass clazz, jlong ptr, jstring path) {
+ return reinterpret_cast<Context*>(ptr)->MakeDir(jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))) ? JNI_TRUE : JNI_FALSE;
+}
+
+jboolean nativeContextRemoveDir(JNIEnv* env, jclass clazz, jlong ptr, jstring path) {
+ return reinterpret_cast<Context*>(ptr)->RemoveDir(jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))) ? JNI_TRUE : JNI_FALSE;
+}
+
+jboolean nativeContextUnlink(JNIEnv* env, jclass clazz, jlong ptr, jstring path) {
+ return reinterpret_cast<Context*>(ptr)->Unlink(jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))) ? JNI_TRUE : JNI_FALSE;
+}
+
+jstring nativeContextReadLink(JNIEnv* env, jclass clazz, jlong ptr, jstring path) {
+ auto ret = reinterpret_cast<Context*>(ptr)->ReadLink(jni::StringToUTF8(env, jni::ParamRef<jstring>(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<Url*>(ptr);
}
@@ -133,9 +216,20 @@ void nativeDirDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
delete reinterpret_cast<Dir*>(ptr);
}
+jni::GlobalRef<jclass> g_DirEntryClass(nullptr, nullptr);
+
+jobjectArray nativeDirList(JNIEnv* env, jclass clazz, jlong ptr) {
+ return reinterpret_cast<Dir*>(ptr)->List(env, g_DirEntryClass).release();
+}
+
+jni::GlobalRef<jclass> 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<void*>(&nativeContextNew) },
{ "nativeContextDestroy", "(J)V", reinterpret_cast<void*>(&nativeContextDestroy) },
@@ -143,14 +237,48 @@ void RegisterSamba(JNIEnv* env) {
{ "nativeContextConnect", "(JJLjava/lang/String;Ljava/lang/String;)Z", reinterpret_cast<void*>(&nativeContextConnect) },
{ "nativeContextGetError", "(J)Ljava/lang/String;", reinterpret_cast<void*>(&nativeContextGetError) },
{ "nativeContextOpenDir", "(JLjava/lang/String;)J", reinterpret_cast<void*>(&nativeContextOpenDir) },
+ { "nativeContextEntry", "(JLjava/lang/String;)Lorg/the_jk/cleversync/io/samba/NativeSamba$DirEntry;", reinterpret_cast<void*>(&nativeContextEntry) },
+ { "nativeContextMakeDir", "(JLjava/lang/String;)Z", reinterpret_cast<void*>(&nativeContextMakeDir) },
+ { "nativeContextRemoveDir", "(JLjava/lang/String;)Z", reinterpret_cast<void*>(&nativeContextRemoveDir) },
+ { "nativeContextUnlink", "(JLjava/lang/String;)Z", reinterpret_cast<void*>(&nativeContextUnlink) },
+ { "nativeContextReadLink", "(JLjava/lang/String;)Ljava/lang/String;", reinterpret_cast<void*>(&nativeContextReadLink) },
{ "nativeUrlDestroy", "(J)V", reinterpret_cast<void*>(&nativeUrlDestroy) },
{ "nativeUrlPath", "(J)Ljava/lang/String;", reinterpret_cast<void*>(&nativeUrlPath) },
{ "nativeDirDestroy", "(J)V", reinterpret_cast<void*>(&nativeDirDestroy) },
+ { "nativeDirList", "(J)[Lorg/the_jk/cleversync/io/samba/NativeSamba$DirEntry;", reinterpret_cast<void*>(&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<jobject> 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<jlong>(stat.smb2_size);
+ auto last_modified = static_cast<jlong>(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<jobject>(env, g_NativeSambaClass, g_CreateDirEntry, j_name.get(), type, size, last_modified);
}
} // namespace