You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
578 lines
24 KiB
578 lines
24 KiB
/*
|
|
* Copyright (C) 2019 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specic language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "MediaProviderWrapper.h"
|
|
#include "libfuse_jni/ReaddirHelper.h"
|
|
|
|
#include <android-base/logging.h>
|
|
#include <android-base/properties.h>
|
|
#include <jni.h>
|
|
#include <nativehelper/scoped_local_ref.h>
|
|
#include <nativehelper/scoped_primitive_array.h>
|
|
#include <nativehelper/scoped_utf_chars.h>
|
|
|
|
#include <pthread.h>
|
|
|
|
#include <mutex>
|
|
#include <unordered_map>
|
|
|
|
namespace mediaprovider {
|
|
namespace fuse {
|
|
using android::base::GetBoolProperty;
|
|
using std::string;
|
|
|
|
namespace {
|
|
|
|
constexpr const char* kPropRedactionEnabled = "persist.sys.fuse.redaction-enabled";
|
|
|
|
constexpr uid_t ROOT_UID = 0;
|
|
constexpr uid_t SHELL_UID = 2000;
|
|
|
|
/** Private helper functions **/
|
|
|
|
inline bool shouldBypassMediaProvider(uid_t uid) {
|
|
return uid == SHELL_UID || uid == ROOT_UID;
|
|
}
|
|
|
|
static bool CheckForJniException(JNIEnv* env) {
|
|
if (env->ExceptionCheck()) {
|
|
env->ExceptionDescribe();
|
|
env->ExceptionClear();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Auxiliary for caching class fields
|
|
*/
|
|
static jfieldID CacheField(JNIEnv* env, jclass clazz, const char field_name[], const char type[]) {
|
|
jfieldID fid;
|
|
string actual_field_name(field_name);
|
|
fid = env->GetFieldID(clazz, actual_field_name.c_str(), type);
|
|
if (!fid) {
|
|
LOG(FATAL) << "Error caching field: " << field_name << type;
|
|
}
|
|
return fid;
|
|
}
|
|
|
|
int insertFileInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_insert_file,
|
|
const string& path, uid_t uid) {
|
|
ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
|
|
int res = env->CallIntMethod(media_provider_object, mid_insert_file, j_path.get(), uid);
|
|
|
|
if (CheckForJniException(env)) {
|
|
return EFAULT;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
int deleteFileInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_delete_file,
|
|
const string& path, uid_t uid) {
|
|
ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
|
|
int res = env->CallIntMethod(media_provider_object, mid_delete_file, j_path.get(), uid);
|
|
|
|
if (CheckForJniException(env)) {
|
|
return EFAULT;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
int isMkdirOrRmdirAllowedInternal(JNIEnv* env, jobject media_provider_object,
|
|
jmethodID mid_is_mkdir_or_rmdir_allowed, const string& path,
|
|
uid_t uid, bool forCreate) {
|
|
ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
|
|
int res = env->CallIntMethod(media_provider_object, mid_is_mkdir_or_rmdir_allowed, j_path.get(),
|
|
uid, forCreate);
|
|
|
|
if (CheckForJniException(env)) {
|
|
return EFAULT;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
int isOpendirAllowedInternal(JNIEnv* env, jobject media_provider_object,
|
|
jmethodID mid_is_opendir_allowed, const string& path, uid_t uid,
|
|
bool forWrite) {
|
|
ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
|
|
int res = env->CallIntMethod(media_provider_object, mid_is_opendir_allowed, j_path.get(), uid,
|
|
forWrite);
|
|
|
|
if (CheckForJniException(env)) {
|
|
return EFAULT;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool isUidAllowedAccessToDataOrObbPathInternal(JNIEnv* env, jobject media_provider_object,
|
|
jmethodID mid_is_uid_allowed_path_access_, uid_t uid,
|
|
const string& path) {
|
|
ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
|
|
bool res = env->CallBooleanMethod(media_provider_object, mid_is_uid_allowed_path_access_, uid,
|
|
j_path.get());
|
|
|
|
if (CheckForJniException(env)) {
|
|
return false;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
std::vector<std::shared_ptr<DirectoryEntry>> getFilesInDirectoryInternal(
|
|
JNIEnv* env, jobject media_provider_object, jmethodID mid_get_files_in_dir, uid_t uid,
|
|
const string& path) {
|
|
std::vector<std::shared_ptr<DirectoryEntry>> directory_entries;
|
|
ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
|
|
|
|
ScopedLocalRef<jobjectArray> files_list(
|
|
env, static_cast<jobjectArray>(env->CallObjectMethod(
|
|
media_provider_object, mid_get_files_in_dir, j_path.get(), uid)));
|
|
|
|
if (CheckForJniException(env)) {
|
|
directory_entries.push_back(std::make_shared<DirectoryEntry>("", EFAULT));
|
|
return directory_entries;
|
|
}
|
|
|
|
int de_count = env->GetArrayLength(files_list.get());
|
|
if (de_count == 1) {
|
|
ScopedLocalRef<jstring> j_d_name(env,
|
|
(jstring)env->GetObjectArrayElement(files_list.get(), 0));
|
|
ScopedUtfChars d_name(env, j_d_name.get());
|
|
if (d_name.c_str() == nullptr) {
|
|
LOG(ERROR) << "Error reading file name returned from MediaProvider at index " << 0;
|
|
directory_entries.push_back(std::make_shared<DirectoryEntry>("", EFAULT));
|
|
return directory_entries;
|
|
} else if (d_name.c_str()[0] == '\0') {
|
|
// Calling package has no storage permissions.
|
|
directory_entries.push_back(std::make_shared<DirectoryEntry>("", EPERM));
|
|
return directory_entries;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < de_count; i++) {
|
|
ScopedLocalRef<jstring> j_d_name(env,
|
|
(jstring)env->GetObjectArrayElement(files_list.get(), i));
|
|
ScopedUtfChars d_name(env, j_d_name.get());
|
|
|
|
if (d_name.c_str() == nullptr) {
|
|
LOG(ERROR) << "Error reading file name returned from MediaProvider at index " << i;
|
|
directory_entries.resize(0);
|
|
directory_entries.push_back(std::make_shared<DirectoryEntry>("", EFAULT));
|
|
break;
|
|
}
|
|
directory_entries.push_back(std::make_shared<DirectoryEntry>(d_name.c_str(), DT_REG));
|
|
}
|
|
return directory_entries;
|
|
}
|
|
|
|
int renameInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_rename,
|
|
const string& old_path, const string& new_path, uid_t uid) {
|
|
ScopedLocalRef<jstring> j_old_path(env, env->NewStringUTF(old_path.c_str()));
|
|
ScopedLocalRef<jstring> j_new_path(env, env->NewStringUTF(new_path.c_str()));
|
|
int res = env->CallIntMethod(media_provider_object, mid_rename, j_old_path.get(),
|
|
j_new_path.get(), uid);
|
|
|
|
if (CheckForJniException(env)) {
|
|
return EFAULT;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void onFileCreatedInternal(JNIEnv* env, jobject media_provider_object,
|
|
jmethodID mid_on_file_created, const string& path) {
|
|
ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
|
|
|
|
env->CallVoidMethod(media_provider_object, mid_on_file_created, j_path.get());
|
|
CheckForJniException(env);
|
|
return;
|
|
}
|
|
|
|
} // namespace
|
|
/*****************************************************************************************/
|
|
/******************************* Public API Implementation *******************************/
|
|
/*****************************************************************************************/
|
|
|
|
JavaVM* MediaProviderWrapper::gJavaVm = nullptr;
|
|
pthread_key_t MediaProviderWrapper::gJniEnvKey;
|
|
|
|
void MediaProviderWrapper::OneTimeInit(JavaVM* vm) {
|
|
gJavaVm = vm;
|
|
CHECK(gJavaVm != nullptr);
|
|
|
|
pthread_key_create(&MediaProviderWrapper::gJniEnvKey,
|
|
MediaProviderWrapper::DetachThreadFunction);
|
|
}
|
|
|
|
MediaProviderWrapper::MediaProviderWrapper(JNIEnv* env, jobject media_provider) {
|
|
if (!media_provider) {
|
|
LOG(FATAL) << "MediaProvider is null!";
|
|
}
|
|
|
|
media_provider_object_ = reinterpret_cast<jobject>(env->NewGlobalRef(media_provider));
|
|
media_provider_class_ = env->FindClass("com/android/providers/media/MediaProvider");
|
|
if (!media_provider_class_) {
|
|
LOG(FATAL) << "Could not find class MediaProvider";
|
|
}
|
|
media_provider_class_ = reinterpret_cast<jclass>(env->NewGlobalRef(media_provider_class_));
|
|
|
|
// Cache methods - Before calling a method, make sure you cache it here
|
|
mid_insert_file_ = CacheMethod(env, "insertFileIfNecessary", "(Ljava/lang/String;I)I",
|
|
/*is_static*/ false);
|
|
mid_delete_file_ = CacheMethod(env, "deleteFile", "(Ljava/lang/String;I)I", /*is_static*/ false);
|
|
mid_on_file_open_ = CacheMethod(env, "onFileOpen",
|
|
"(Ljava/lang/String;Ljava/lang/String;IIIZZZ)Lcom/android/"
|
|
"providers/media/FileOpenResult;",
|
|
/*is_static*/ false);
|
|
mid_is_mkdir_or_rmdir_allowed_ = CacheMethod(env, "isDirectoryCreationOrDeletionAllowed",
|
|
"(Ljava/lang/String;IZ)I", /*is_static*/ false);
|
|
mid_is_opendir_allowed_ = CacheMethod(env, "isOpendirAllowed", "(Ljava/lang/String;IZ)I",
|
|
/*is_static*/ false);
|
|
mid_get_files_in_dir_ =
|
|
CacheMethod(env, "getFilesInDirectory", "(Ljava/lang/String;I)[Ljava/lang/String;",
|
|
/*is_static*/ false);
|
|
mid_rename_ = CacheMethod(env, "rename", "(Ljava/lang/String;Ljava/lang/String;I)I",
|
|
/*is_static*/ false);
|
|
mid_is_uid_allowed_access_to_data_or_obb_path_ =
|
|
CacheMethod(env, "isUidAllowedAccessToDataOrObbPath", "(ILjava/lang/String;)Z",
|
|
/*is_static*/ false);
|
|
mid_on_file_created_ = CacheMethod(env, "onFileCreated", "(Ljava/lang/String;)V",
|
|
/*is_static*/ false);
|
|
mid_should_allow_lookup_ = CacheMethod(env, "shouldAllowLookup", "(II)Z",
|
|
/*is_static*/ false);
|
|
mid_is_app_clone_user_ = CacheMethod(env, "isAppCloneUser", "(I)Z",
|
|
/*is_static*/ false);
|
|
mid_transform_ = CacheMethod(env, "transform", "(Ljava/lang/String;Ljava/lang/String;IIIII)Z",
|
|
/*is_static*/ false);
|
|
mid_file_lookup_ =
|
|
CacheMethod(env, "onFileLookup",
|
|
"(Ljava/lang/String;II)Lcom/android/providers/media/FileLookupResult;",
|
|
/*is_static*/ false);
|
|
|
|
// FileLookupResult
|
|
file_lookup_result_class_ = env->FindClass("com/android/providers/media/FileLookupResult");
|
|
if (!file_lookup_result_class_) {
|
|
LOG(FATAL) << "Could not find class FileLookupResult";
|
|
}
|
|
file_lookup_result_class_ =
|
|
reinterpret_cast<jclass>(env->NewGlobalRef(file_lookup_result_class_));
|
|
fid_file_lookup_transforms_ = CacheField(env, file_lookup_result_class_, "transforms", "I");
|
|
fid_file_lookup_transforms_reason_ =
|
|
CacheField(env, file_lookup_result_class_, "transformsReason", "I");
|
|
fid_file_lookup_uid_ = CacheField(env, file_lookup_result_class_, "uid", "I");
|
|
fid_file_lookup_transforms_complete_ =
|
|
CacheField(env, file_lookup_result_class_, "transformsComplete", "Z");
|
|
fid_file_lookup_transforms_supported_ =
|
|
CacheField(env, file_lookup_result_class_, "transformsSupported", "Z");
|
|
fid_file_lookup_io_path_ =
|
|
CacheField(env, file_lookup_result_class_, "ioPath", "Ljava/lang/String;");
|
|
|
|
// FileOpenResult
|
|
file_open_result_class_ = env->FindClass("com/android/providers/media/FileOpenResult");
|
|
if (!file_open_result_class_) {
|
|
LOG(FATAL) << "Could not find class FileOpenResult";
|
|
}
|
|
file_open_result_class_ = reinterpret_cast<jclass>(env->NewGlobalRef(file_open_result_class_));
|
|
fid_file_open_status_ = CacheField(env, file_open_result_class_, "status", "I");
|
|
fid_file_open_uid_ = CacheField(env, file_open_result_class_, "uid", "I");
|
|
fid_file_open_transforms_uid_ = CacheField(env, file_open_result_class_, "transformsUid", "I");
|
|
fid_file_open_redaction_ranges_ =
|
|
CacheField(env, file_open_result_class_, "redactionRanges", "[J");
|
|
}
|
|
|
|
MediaProviderWrapper::~MediaProviderWrapper() {
|
|
JNIEnv* env = MaybeAttachCurrentThread();
|
|
env->DeleteGlobalRef(media_provider_object_);
|
|
env->DeleteGlobalRef(media_provider_class_);
|
|
}
|
|
|
|
int MediaProviderWrapper::InsertFile(const string& path, uid_t uid) {
|
|
if (uid == ROOT_UID) {
|
|
return 0;
|
|
}
|
|
|
|
JNIEnv* env = MaybeAttachCurrentThread();
|
|
return insertFileInternal(env, media_provider_object_, mid_insert_file_, path, uid);
|
|
}
|
|
|
|
int MediaProviderWrapper::DeleteFile(const string& path, uid_t uid) {
|
|
if (uid == ROOT_UID) {
|
|
int res = unlink(path.c_str());
|
|
return res;
|
|
}
|
|
|
|
JNIEnv* env = MaybeAttachCurrentThread();
|
|
return deleteFileInternal(env, media_provider_object_, mid_delete_file_, path, uid);
|
|
}
|
|
|
|
std::unique_ptr<FileOpenResult> MediaProviderWrapper::OnFileOpen(const string& path,
|
|
const string& io_path, uid_t uid,
|
|
pid_t tid, int transforms_reason,
|
|
bool for_write, bool redact,
|
|
bool log_transforms_metrics) {
|
|
JNIEnv* env = MaybeAttachCurrentThread();
|
|
if (shouldBypassMediaProvider(uid)) {
|
|
return std::make_unique<FileOpenResult>(0, uid, 0 /* transforms_uid */, new RedactionInfo());
|
|
}
|
|
|
|
ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
|
|
ScopedLocalRef<jstring> j_io_path(env, env->NewStringUTF(io_path.c_str()));
|
|
ScopedLocalRef<jobject> j_res_file_open_object(
|
|
env, env->CallObjectMethod(media_provider_object_, mid_on_file_open_, j_path.get(),
|
|
j_io_path.get(), uid, tid, transforms_reason, for_write,
|
|
redact, log_transforms_metrics));
|
|
|
|
if (CheckForJniException(env)) {
|
|
return nullptr;
|
|
}
|
|
|
|
int status = env->GetIntField(j_res_file_open_object.get(), fid_file_open_status_);
|
|
int original_uid = env->GetIntField(j_res_file_open_object.get(), fid_file_open_uid_);
|
|
int transforms_uid =
|
|
env->GetIntField(j_res_file_open_object.get(), fid_file_open_transforms_uid_);
|
|
|
|
if (redact) {
|
|
ScopedLocalRef<jlongArray> redaction_ranges_local_ref(
|
|
env, static_cast<jlongArray>(env->GetObjectField(j_res_file_open_object.get(),
|
|
fid_file_open_redaction_ranges_)));
|
|
ScopedLongArrayRO redaction_ranges(env, redaction_ranges_local_ref.get());
|
|
|
|
std::unique_ptr<RedactionInfo> ri;
|
|
if (redaction_ranges.size() % 2) {
|
|
LOG(ERROR) << "Error while calculating redaction ranges: array length is uneven";
|
|
} else if (redaction_ranges.size() > 0) {
|
|
ri = std::make_unique<RedactionInfo>(redaction_ranges.size() / 2,
|
|
redaction_ranges.get());
|
|
} else {
|
|
// No ranges to redact
|
|
ri = std::make_unique<RedactionInfo>();
|
|
}
|
|
return std::make_unique<FileOpenResult>(status, original_uid, transforms_uid, ri.release());
|
|
} else {
|
|
return std::make_unique<FileOpenResult>(status, original_uid, transforms_uid,
|
|
new RedactionInfo());
|
|
}
|
|
}
|
|
|
|
int MediaProviderWrapper::IsCreatingDirAllowed(const string& path, uid_t uid) {
|
|
if (shouldBypassMediaProvider(uid)) {
|
|
return 0;
|
|
}
|
|
|
|
JNIEnv* env = MaybeAttachCurrentThread();
|
|
return isMkdirOrRmdirAllowedInternal(env, media_provider_object_,
|
|
mid_is_mkdir_or_rmdir_allowed_, path, uid,
|
|
/*forCreate*/ true);
|
|
}
|
|
|
|
int MediaProviderWrapper::IsDeletingDirAllowed(const string& path, uid_t uid) {
|
|
if (shouldBypassMediaProvider(uid)) {
|
|
return 0;
|
|
}
|
|
|
|
JNIEnv* env = MaybeAttachCurrentThread();
|
|
return isMkdirOrRmdirAllowedInternal(env, media_provider_object_,
|
|
mid_is_mkdir_or_rmdir_allowed_, path, uid,
|
|
/*forCreate*/ false);
|
|
}
|
|
|
|
std::vector<std::shared_ptr<DirectoryEntry>> MediaProviderWrapper::GetDirectoryEntries(
|
|
uid_t uid, const string& path, DIR* dirp) {
|
|
// Default value in case JNI thread was being terminated
|
|
std::vector<std::shared_ptr<DirectoryEntry>> res;
|
|
if (shouldBypassMediaProvider(uid)) {
|
|
addDirectoryEntriesFromLowerFs(dirp, /* filter */ nullptr, &res);
|
|
return res;
|
|
}
|
|
|
|
JNIEnv* env = MaybeAttachCurrentThread();
|
|
res = getFilesInDirectoryInternal(env, media_provider_object_, mid_get_files_in_dir_, uid, path);
|
|
|
|
const int res_size = res.size();
|
|
if (res_size && res[0]->d_name[0] == '/') {
|
|
// Path is unknown to MediaProvider, get files and directories from lower file system.
|
|
res.resize(0);
|
|
addDirectoryEntriesFromLowerFs(dirp, /* filter */ nullptr, &res);
|
|
} else if (res_size == 0 || !res[0]->d_name.empty()) {
|
|
// add directory names from lower file system.
|
|
addDirectoryEntriesFromLowerFs(dirp, /* filter */ &isDirectory, &res);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
int MediaProviderWrapper::IsOpendirAllowed(const string& path, uid_t uid, bool forWrite) {
|
|
if (shouldBypassMediaProvider(uid)) {
|
|
return 0;
|
|
}
|
|
|
|
JNIEnv* env = MaybeAttachCurrentThread();
|
|
return isOpendirAllowedInternal(env, media_provider_object_, mid_is_opendir_allowed_, path, uid,
|
|
forWrite);
|
|
}
|
|
|
|
bool MediaProviderWrapper::isUidAllowedAccessToDataOrObbPath(uid_t uid, const string& path) {
|
|
if (shouldBypassMediaProvider(uid)) {
|
|
return true;
|
|
}
|
|
|
|
JNIEnv* env = MaybeAttachCurrentThread();
|
|
return isUidAllowedAccessToDataOrObbPathInternal(
|
|
env, media_provider_object_, mid_is_uid_allowed_access_to_data_or_obb_path_, uid, path);
|
|
}
|
|
|
|
int MediaProviderWrapper::Rename(const string& old_path, const string& new_path, uid_t uid) {
|
|
// Rename from SHELL_UID should go through MediaProvider to update database rows, so only bypass
|
|
// MediaProvider for ROOT_UID.
|
|
if (uid == ROOT_UID) {
|
|
int res = rename(old_path.c_str(), new_path.c_str());
|
|
if (res != 0) res = -errno;
|
|
return res;
|
|
}
|
|
|
|
JNIEnv* env = MaybeAttachCurrentThread();
|
|
return renameInternal(env, media_provider_object_, mid_rename_, old_path, new_path, uid);
|
|
}
|
|
|
|
void MediaProviderWrapper::OnFileCreated(const string& path) {
|
|
JNIEnv* env = MaybeAttachCurrentThread();
|
|
|
|
return onFileCreatedInternal(env, media_provider_object_, mid_on_file_created_, path);
|
|
}
|
|
|
|
bool MediaProviderWrapper::ShouldAllowLookup(uid_t uid, int path_user_id) {
|
|
JNIEnv* env = MaybeAttachCurrentThread();
|
|
|
|
bool res = env->CallBooleanMethod(media_provider_object_, mid_should_allow_lookup_, uid,
|
|
path_user_id);
|
|
|
|
if (CheckForJniException(env)) {
|
|
return false;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool MediaProviderWrapper::IsAppCloneUser(uid_t userId) {
|
|
JNIEnv* env = MaybeAttachCurrentThread();
|
|
|
|
bool res = env->CallBooleanMethod(media_provider_object_, mid_is_app_clone_user_, userId);
|
|
|
|
if (CheckForJniException(env)) {
|
|
return false;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
std::unique_ptr<FileLookupResult> MediaProviderWrapper::FileLookup(const std::string& path,
|
|
uid_t uid, pid_t tid) {
|
|
JNIEnv* env = MaybeAttachCurrentThread();
|
|
|
|
ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
|
|
|
|
ScopedLocalRef<jobject> j_res_file_lookup_object(
|
|
env, env->CallObjectMethod(media_provider_object_, mid_file_lookup_, j_path.get(), uid,
|
|
tid));
|
|
|
|
if (CheckForJniException(env)) {
|
|
return nullptr;
|
|
}
|
|
|
|
int transforms = env->GetIntField(j_res_file_lookup_object.get(), fid_file_lookup_transforms_);
|
|
int transforms_reason =
|
|
env->GetIntField(j_res_file_lookup_object.get(), fid_file_lookup_transforms_reason_);
|
|
int original_uid = env->GetIntField(j_res_file_lookup_object.get(), fid_file_lookup_uid_);
|
|
bool transforms_complete = env->GetBooleanField(j_res_file_lookup_object.get(),
|
|
fid_file_lookup_transforms_complete_);
|
|
bool transforms_supported = env->GetBooleanField(j_res_file_lookup_object.get(),
|
|
fid_file_lookup_transforms_supported_);
|
|
ScopedLocalRef<jstring> j_io_path(
|
|
env,
|
|
(jstring)env->GetObjectField(j_res_file_lookup_object.get(), fid_file_lookup_io_path_));
|
|
ScopedUtfChars j_io_path_utf(env, j_io_path.get());
|
|
|
|
std::unique_ptr<FileLookupResult> file_lookup_result = std::make_unique<FileLookupResult>(
|
|
transforms, transforms_reason, original_uid, transforms_complete, transforms_supported,
|
|
string(j_io_path_utf.c_str()));
|
|
return file_lookup_result;
|
|
}
|
|
|
|
bool MediaProviderWrapper::Transform(const std::string& src, const std::string& dst, int transforms,
|
|
int transforms_reason, uid_t read_uid, uid_t open_uid,
|
|
uid_t transforms_uid) {
|
|
JNIEnv* env = MaybeAttachCurrentThread();
|
|
|
|
ScopedLocalRef<jstring> j_src(env, env->NewStringUTF(src.c_str()));
|
|
ScopedLocalRef<jstring> j_dst(env, env->NewStringUTF(dst.c_str()));
|
|
bool res = env->CallBooleanMethod(media_provider_object_, mid_transform_, j_src.get(),
|
|
j_dst.get(), transforms, transforms_reason, read_uid,
|
|
open_uid, transforms_uid);
|
|
|
|
if (CheckForJniException(env)) {
|
|
return false;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/*****************************************************************************************/
|
|
/******************************** Private member functions *******************************/
|
|
/*****************************************************************************************/
|
|
|
|
/**
|
|
* Finds MediaProvider method and adds it to methods map so it can be quickly called later.
|
|
*/
|
|
jmethodID MediaProviderWrapper::CacheMethod(JNIEnv* env, const char method_name[],
|
|
const char signature[], bool is_static) {
|
|
jmethodID mid;
|
|
string actual_method_name(method_name);
|
|
actual_method_name.append("ForFuse");
|
|
if (is_static) {
|
|
mid = env->GetStaticMethodID(media_provider_class_, actual_method_name.c_str(), signature);
|
|
} else {
|
|
mid = env->GetMethodID(media_provider_class_, actual_method_name.c_str(), signature);
|
|
}
|
|
if (!mid) {
|
|
LOG(FATAL) << "Error caching method: " << method_name << signature;
|
|
}
|
|
return mid;
|
|
}
|
|
|
|
void MediaProviderWrapper::DetachThreadFunction(void* unused) {
|
|
int detach = gJavaVm->DetachCurrentThread();
|
|
CHECK_EQ(detach, 0);
|
|
}
|
|
|
|
JNIEnv* MediaProviderWrapper::MaybeAttachCurrentThread() {
|
|
// We could use pthread_getspecific here as that's likely quicker but
|
|
// that would result in wrong behaviour for threads that don't need to
|
|
// be attached (e.g, those that were created in managed code).
|
|
JNIEnv* env = nullptr;
|
|
if (gJavaVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_4) == JNI_OK) {
|
|
return env;
|
|
}
|
|
|
|
// This thread is currently unattached, so it must not have any TLS
|
|
// value. Note that we don't really care about the actual value we store
|
|
// in TLS -- we only care about the value destructor being called, which
|
|
// will happen as long as the key is not null.
|
|
CHECK(pthread_getspecific(gJniEnvKey) == nullptr);
|
|
CHECK_EQ(gJavaVm->AttachCurrentThread(&env, nullptr), 0);
|
|
CHECK(env != nullptr);
|
|
|
|
pthread_setspecific(gJniEnvKey, env);
|
|
return env;
|
|
}
|
|
|
|
} // namespace fuse
|
|
} // namespace mediaprovider
|