1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specic language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "MediaProviderWrapper.h"
18 #include "libfuse_jni/ReaddirHelper.h"
19
20 #include <android-base/logging.h>
21 #include <android-base/properties.h>
22 #include <jni.h>
23 #include <nativehelper/scoped_local_ref.h>
24 #include <nativehelper/scoped_primitive_array.h>
25 #include <nativehelper/scoped_utf_chars.h>
26
27 #include <pthread.h>
28
29 #include <mutex>
30 #include <unordered_map>
31
32 namespace mediaprovider {
33 namespace fuse {
34 using std::string;
35
36 namespace {
37
38 constexpr const char* kPropRedactionEnabled = "persist.sys.fuse.redaction-enabled";
39
40 constexpr uid_t ROOT_UID = 0;
41 constexpr uid_t SHELL_UID = 2000;
42
43 // These need to stay in sync with MediaProvider.java's DIRECTORY_ACCESS_FOR_* constants.
44 enum DirectoryAccessRequestType {
45 kReadDirectoryRequest = 1,
46 kWriteDirectoryRequest = 2,
47 kCreateDirectoryRequest = 3,
48 kDeleteDirectoryRequest = 4,
49 };
50
51 /** Private helper functions **/
52
shouldBypassMediaProvider(uid_t uid)53 inline bool shouldBypassMediaProvider(uid_t uid) {
54 return uid == SHELL_UID || uid == ROOT_UID;
55 }
56
CheckForJniException(JNIEnv * env)57 static bool CheckForJniException(JNIEnv* env) {
58 if (env->ExceptionCheck()) {
59 env->ExceptionDescribe();
60 env->ExceptionClear();
61 return true;
62 }
63 return false;
64 }
65
66 /**
67 * Auxiliary for caching class fields
68 */
CacheField(JNIEnv * env,jclass clazz,const char field_name[],const char type[])69 static jfieldID CacheField(JNIEnv* env, jclass clazz, const char field_name[], const char type[]) {
70 jfieldID fid;
71 string actual_field_name(field_name);
72 fid = env->GetFieldID(clazz, actual_field_name.c_str(), type);
73 if (!fid) {
74 LOG(FATAL) << "Error caching field: " << field_name << type;
75 }
76 return fid;
77 }
78
insertFileInternal(JNIEnv * env,jobject media_provider_object,jmethodID mid_insert_file,const string & path,uid_t uid)79 int insertFileInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_insert_file,
80 const string& path, uid_t uid) {
81 ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
82 int res = env->CallIntMethod(media_provider_object, mid_insert_file, j_path.get(), uid);
83
84 if (CheckForJniException(env)) {
85 return EFAULT;
86 }
87 return res;
88 }
89
deleteFileInternal(JNIEnv * env,jobject media_provider_object,jmethodID mid_delete_file,const string & path,uid_t uid)90 int deleteFileInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_delete_file,
91 const string& path, uid_t uid) {
92 ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
93 int res = env->CallIntMethod(media_provider_object, mid_delete_file, j_path.get(), uid);
94
95 if (CheckForJniException(env)) {
96 return EFAULT;
97 }
98 return res;
99 }
100
isDirAccessAllowedInternal(JNIEnv * env,jobject media_provider_object,jmethodID mid_is_diraccess_allowed,const string & path,uid_t uid,int accessType)101 int isDirAccessAllowedInternal(JNIEnv* env, jobject media_provider_object,
102 jmethodID mid_is_diraccess_allowed, const string& path, uid_t uid,
103 int accessType) {
104 ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
105 int res = env->CallIntMethod(media_provider_object, mid_is_diraccess_allowed, j_path.get(), uid,
106 accessType);
107
108 if (CheckForJniException(env)) {
109 return EFAULT;
110 }
111 return res;
112 }
113
isUidAllowedAccessToDataOrObbPathInternal(JNIEnv * env,jobject media_provider_object,jmethodID mid_is_uid_allowed_path_access_,uid_t uid,const string & path)114 bool isUidAllowedAccessToDataOrObbPathInternal(JNIEnv* env, jobject media_provider_object,
115 jmethodID mid_is_uid_allowed_path_access_, uid_t uid,
116 const string& path) {
117 ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
118 bool res = env->CallBooleanMethod(media_provider_object, mid_is_uid_allowed_path_access_, uid,
119 j_path.get());
120
121 if (CheckForJniException(env)) {
122 return false;
123 }
124 return res;
125 }
126
getFilesInDirectoryInternal(JNIEnv * env,jobject media_provider_object,jmethodID mid_get_files_in_dir,uid_t uid,const string & path)127 std::vector<std::shared_ptr<DirectoryEntry>> getFilesInDirectoryInternal(
128 JNIEnv* env, jobject media_provider_object, jmethodID mid_get_files_in_dir, uid_t uid,
129 const string& path) {
130 std::vector<std::shared_ptr<DirectoryEntry>> directory_entries;
131 ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
132
133 ScopedLocalRef<jobjectArray> files_list(
134 env, static_cast<jobjectArray>(env->CallObjectMethod(
135 media_provider_object, mid_get_files_in_dir, j_path.get(), uid)));
136
137 if (CheckForJniException(env)) {
138 directory_entries.push_back(std::make_shared<DirectoryEntry>("", EFAULT));
139 return directory_entries;
140 }
141
142 int de_count = env->GetArrayLength(files_list.get());
143 if (de_count == 1) {
144 ScopedLocalRef<jstring> j_d_name(env,
145 (jstring)env->GetObjectArrayElement(files_list.get(), 0));
146 ScopedUtfChars d_name(env, j_d_name.get());
147 if (d_name.c_str() == nullptr) {
148 LOG(ERROR) << "Error reading file name returned from MediaProvider at index " << 0;
149 directory_entries.push_back(std::make_shared<DirectoryEntry>("", EFAULT));
150 return directory_entries;
151 } else if (d_name.c_str()[0] == '\0') {
152 // Calling package has no storage permissions.
153 directory_entries.push_back(std::make_shared<DirectoryEntry>("", EPERM));
154 return directory_entries;
155 }
156 }
157
158 for (int i = 0; i < de_count; i++) {
159 ScopedLocalRef<jstring> j_d_name(env,
160 (jstring)env->GetObjectArrayElement(files_list.get(), i));
161 ScopedUtfChars d_name(env, j_d_name.get());
162
163 if (d_name.c_str() == nullptr) {
164 LOG(ERROR) << "Error reading file name returned from MediaProvider at index " << i;
165 directory_entries.resize(0);
166 directory_entries.push_back(std::make_shared<DirectoryEntry>("", EFAULT));
167 break;
168 }
169 directory_entries.push_back(std::make_shared<DirectoryEntry>(d_name.c_str(), DT_REG));
170 }
171 return directory_entries;
172 }
173
renameInternal(JNIEnv * env,jobject media_provider_object,jmethodID mid_rename,const string & old_path,const string & new_path,uid_t uid)174 int renameInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_rename,
175 const string& old_path, const string& new_path, uid_t uid) {
176 ScopedLocalRef<jstring> j_old_path(env, env->NewStringUTF(old_path.c_str()));
177 ScopedLocalRef<jstring> j_new_path(env, env->NewStringUTF(new_path.c_str()));
178 int res = env->CallIntMethod(media_provider_object, mid_rename, j_old_path.get(),
179 j_new_path.get(), uid);
180
181 if (CheckForJniException(env)) {
182 return EFAULT;
183 }
184 return res;
185 }
186
onFileCreatedInternal(JNIEnv * env,jobject media_provider_object,jmethodID mid_on_file_created,const string & path)187 void onFileCreatedInternal(JNIEnv* env, jobject media_provider_object,
188 jmethodID mid_on_file_created, const string& path) {
189 ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
190
191 env->CallVoidMethod(media_provider_object, mid_on_file_created, j_path.get());
192 CheckForJniException(env);
193 return;
194 }
195
196 } // namespace
197 /*****************************************************************************************/
198 /******************************* Public API Implementation *******************************/
199 /*****************************************************************************************/
200
201 JavaVM* MediaProviderWrapper::gJavaVm = nullptr;
202 pthread_key_t MediaProviderWrapper::gJniEnvKey;
203
OneTimeInit(JavaVM * vm)204 void MediaProviderWrapper::OneTimeInit(JavaVM* vm) {
205 gJavaVm = vm;
206 CHECK(gJavaVm != nullptr);
207
208 pthread_key_create(&MediaProviderWrapper::gJniEnvKey,
209 MediaProviderWrapper::DetachThreadFunction);
210 }
211
MediaProviderWrapper(JNIEnv * env,jobject media_provider)212 MediaProviderWrapper::MediaProviderWrapper(JNIEnv* env, jobject media_provider) {
213 if (!media_provider) {
214 LOG(FATAL) << "MediaProvider is null!";
215 }
216
217 media_provider_object_ = reinterpret_cast<jobject>(env->NewGlobalRef(media_provider));
218 media_provider_class_ = env->FindClass("com/android/providers/media/MediaProvider");
219 if (!media_provider_class_) {
220 LOG(FATAL) << "Could not find class MediaProvider";
221 }
222 media_provider_class_ = reinterpret_cast<jclass>(env->NewGlobalRef(media_provider_class_));
223
224 // Cache methods - Before calling a method, make sure you cache it here
225 mid_insert_file_ = CacheMethod(env, "insertFileIfNecessary", "(Ljava/lang/String;I)I");
226 mid_delete_file_ = CacheMethod(env, "deleteFile", "(Ljava/lang/String;I)I");
227 mid_on_file_open_ = CacheMethod(env, "onFileOpen",
228 "(Ljava/lang/String;Ljava/lang/String;IIIZZZ)Lcom/android/"
229 "providers/media/FileOpenResult;");
230 mid_is_diraccess_allowed_ = CacheMethod(env, "isDirAccessAllowed", "(Ljava/lang/String;II)I");
231 mid_get_files_in_dir_ =
232 CacheMethod(env, "getFilesInDirectory", "(Ljava/lang/String;I)[Ljava/lang/String;");
233 mid_rename_ = CacheMethod(env, "rename", "(Ljava/lang/String;Ljava/lang/String;I)I");
234 mid_is_uid_allowed_access_to_data_or_obb_path_ =
235 CacheMethod(env, "isUidAllowedAccessToDataOrObbPath", "(ILjava/lang/String;)Z");
236 mid_on_file_created_ = CacheMethod(env, "onFileCreated", "(Ljava/lang/String;)V");
237 mid_should_allow_lookup_ = CacheMethod(env, "shouldAllowLookup", "(II)Z");
238 mid_is_app_clone_user_ = CacheMethod(env, "isAppCloneUser", "(I)Z");
239 mid_transform_ = CacheMethod(env, "transform", "(Ljava/lang/String;Ljava/lang/String;IIIII)Z");
240 mid_file_lookup_ =
241 CacheMethod(env, "onFileLookup",
242 "(Ljava/lang/String;II)Lcom/android/providers/media/FileLookupResult;");
243
244 // FileLookupResult
245 file_lookup_result_class_ = env->FindClass("com/android/providers/media/FileLookupResult");
246 if (!file_lookup_result_class_) {
247 LOG(FATAL) << "Could not find class FileLookupResult";
248 }
249 file_lookup_result_class_ =
250 reinterpret_cast<jclass>(env->NewGlobalRef(file_lookup_result_class_));
251 fid_file_lookup_transforms_ = CacheField(env, file_lookup_result_class_, "transforms", "I");
252 fid_file_lookup_transforms_reason_ =
253 CacheField(env, file_lookup_result_class_, "transformsReason", "I");
254 fid_file_lookup_uid_ = CacheField(env, file_lookup_result_class_, "uid", "I");
255 fid_file_lookup_transforms_complete_ =
256 CacheField(env, file_lookup_result_class_, "transformsComplete", "Z");
257 fid_file_lookup_transforms_supported_ =
258 CacheField(env, file_lookup_result_class_, "transformsSupported", "Z");
259 fid_file_lookup_io_path_ =
260 CacheField(env, file_lookup_result_class_, "ioPath", "Ljava/lang/String;");
261
262 // FileOpenResult
263 file_open_result_class_ = env->FindClass("com/android/providers/media/FileOpenResult");
264 if (!file_open_result_class_) {
265 LOG(FATAL) << "Could not find class FileOpenResult";
266 }
267 file_open_result_class_ = reinterpret_cast<jclass>(env->NewGlobalRef(file_open_result_class_));
268 fid_file_open_status_ = CacheField(env, file_open_result_class_, "status", "I");
269 fid_file_open_uid_ = CacheField(env, file_open_result_class_, "uid", "I");
270 fid_file_open_transforms_uid_ = CacheField(env, file_open_result_class_, "transformsUid", "I");
271 fid_file_open_redaction_ranges_ =
272 CacheField(env, file_open_result_class_, "redactionRanges", "[J");
273 fid_file_open_fd_ = CacheField(env, file_open_result_class_, "nativeFd", "I");
274 }
275
~MediaProviderWrapper()276 MediaProviderWrapper::~MediaProviderWrapper() {
277 JNIEnv* env = MaybeAttachCurrentThread();
278 env->DeleteGlobalRef(media_provider_object_);
279 env->DeleteGlobalRef(media_provider_class_);
280 }
281
InsertFile(const string & path,uid_t uid)282 int MediaProviderWrapper::InsertFile(const string& path, uid_t uid) {
283 if (uid == ROOT_UID) {
284 return 0;
285 }
286
287 JNIEnv* env = MaybeAttachCurrentThread();
288 return insertFileInternal(env, media_provider_object_, mid_insert_file_, path, uid);
289 }
290
DeleteFile(const string & path,uid_t uid)291 int MediaProviderWrapper::DeleteFile(const string& path, uid_t uid) {
292 if (uid == ROOT_UID) {
293 int res = unlink(path.c_str());
294 return res;
295 }
296
297 JNIEnv* env = MaybeAttachCurrentThread();
298 return deleteFileInternal(env, media_provider_object_, mid_delete_file_, path, uid);
299 }
300
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)301 std::unique_ptr<FileOpenResult> MediaProviderWrapper::OnFileOpen(const string& path,
302 const string& io_path, uid_t uid,
303 pid_t tid, int transforms_reason,
304 bool for_write, bool redact,
305 bool log_transforms_metrics) {
306 JNIEnv* env = MaybeAttachCurrentThread();
307 if (shouldBypassMediaProvider(uid)) {
308 return std::make_unique<FileOpenResult>(0, uid, /* transforms_uid */ 0, /* nativeFd */ -1,
309 new RedactionInfo());
310 }
311
312 ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
313 ScopedLocalRef<jstring> j_io_path(env, env->NewStringUTF(io_path.c_str()));
314 ScopedLocalRef<jobject> j_res_file_open_object(
315 env, env->CallObjectMethod(media_provider_object_, mid_on_file_open_, j_path.get(),
316 j_io_path.get(), uid, tid, transforms_reason, for_write,
317 redact, log_transforms_metrics));
318
319 if (CheckForJniException(env)) {
320 return nullptr;
321 }
322
323 const int status = env->GetIntField(j_res_file_open_object.get(), fid_file_open_status_);
324 const int original_uid = env->GetIntField(j_res_file_open_object.get(), fid_file_open_uid_);
325 const int transforms_uid =
326 env->GetIntField(j_res_file_open_object.get(), fid_file_open_transforms_uid_);
327 const int fd = env->GetIntField(j_res_file_open_object.get(), fid_file_open_fd_);
328
329 if (redact) {
330 ScopedLocalRef<jlongArray> redaction_ranges_local_ref(
331 env, static_cast<jlongArray>(env->GetObjectField(j_res_file_open_object.get(),
332 fid_file_open_redaction_ranges_)));
333 ScopedLongArrayRO redaction_ranges(env, redaction_ranges_local_ref.get());
334
335 std::unique_ptr<RedactionInfo> ri;
336 if (redaction_ranges.size() % 2) {
337 LOG(ERROR) << "Error while calculating redaction ranges: array length is uneven";
338 } else if (redaction_ranges.size() > 0) {
339 ri = std::make_unique<RedactionInfo>(redaction_ranges.size() / 2,
340 redaction_ranges.get());
341 } else {
342 // No ranges to redact
343 ri = std::make_unique<RedactionInfo>();
344 }
345 return std::make_unique<FileOpenResult>(status, original_uid, transforms_uid, fd,
346 ri.release());
347 } else {
348 return std::make_unique<FileOpenResult>(status, original_uid, transforms_uid, fd,
349 new RedactionInfo());
350 }
351 }
352
IsCreatingDirAllowed(const string & path,uid_t uid)353 int MediaProviderWrapper::IsCreatingDirAllowed(const string& path, uid_t uid) {
354 if (shouldBypassMediaProvider(uid)) {
355 return 0;
356 }
357
358 JNIEnv* env = MaybeAttachCurrentThread();
359 return isDirAccessAllowedInternal(env, media_provider_object_, mid_is_diraccess_allowed_, path,
360 uid, kCreateDirectoryRequest);
361 }
362
IsDeletingDirAllowed(const string & path,uid_t uid)363 int MediaProviderWrapper::IsDeletingDirAllowed(const string& path, uid_t uid) {
364 if (shouldBypassMediaProvider(uid)) {
365 return 0;
366 }
367
368 JNIEnv* env = MaybeAttachCurrentThread();
369 return isDirAccessAllowedInternal(env, media_provider_object_, mid_is_diraccess_allowed_, path,
370 uid, kDeleteDirectoryRequest);
371 }
372
GetDirectoryEntries(uid_t uid,const string & path,DIR * dirp)373 std::vector<std::shared_ptr<DirectoryEntry>> MediaProviderWrapper::GetDirectoryEntries(
374 uid_t uid, const string& path, DIR* dirp) {
375 // Default value in case JNI thread was being terminated
376 std::vector<std::shared_ptr<DirectoryEntry>> res;
377 if (shouldBypassMediaProvider(uid)) {
378 addDirectoryEntriesFromLowerFs(dirp, /* filter */ nullptr, &res);
379 return res;
380 }
381
382 JNIEnv* env = MaybeAttachCurrentThread();
383 res = getFilesInDirectoryInternal(env, media_provider_object_, mid_get_files_in_dir_, uid, path);
384
385 const int res_size = res.size();
386 if (res_size && res[0]->d_name[0] == '/') {
387 // Path is unknown to MediaProvider, get files and directories from lower file system.
388 res.resize(0);
389 addDirectoryEntriesFromLowerFs(dirp, /* filter */ nullptr, &res);
390 } else if (res_size == 0 || !res[0]->d_name.empty()) {
391 // add directory names from lower file system.
392 addDirectoryEntriesFromLowerFs(dirp, /* filter */ &isDirectory, &res);
393 }
394 return res;
395 }
396
IsOpendirAllowed(const string & path,uid_t uid,bool forWrite)397 int MediaProviderWrapper::IsOpendirAllowed(const string& path, uid_t uid, bool forWrite) {
398 if (shouldBypassMediaProvider(uid)) {
399 return 0;
400 }
401
402 JNIEnv* env = MaybeAttachCurrentThread();
403 return isDirAccessAllowedInternal(env, media_provider_object_, mid_is_diraccess_allowed_, path,
404 uid,
405 forWrite ? kWriteDirectoryRequest : kReadDirectoryRequest);
406 }
407
isUidAllowedAccessToDataOrObbPath(uid_t uid,const string & path)408 bool MediaProviderWrapper::isUidAllowedAccessToDataOrObbPath(uid_t uid, const string& path) {
409 if (shouldBypassMediaProvider(uid)) {
410 return true;
411 }
412
413 JNIEnv* env = MaybeAttachCurrentThread();
414 return isUidAllowedAccessToDataOrObbPathInternal(
415 env, media_provider_object_, mid_is_uid_allowed_access_to_data_or_obb_path_, uid, path);
416 }
417
Rename(const string & old_path,const string & new_path,uid_t uid)418 int MediaProviderWrapper::Rename(const string& old_path, const string& new_path, uid_t uid) {
419 // Rename from SHELL_UID should go through MediaProvider to update database rows, so only bypass
420 // MediaProvider for ROOT_UID.
421 if (uid == ROOT_UID) {
422 int res = rename(old_path.c_str(), new_path.c_str());
423 if (res != 0) res = -errno;
424 return res;
425 }
426
427 JNIEnv* env = MaybeAttachCurrentThread();
428 return renameInternal(env, media_provider_object_, mid_rename_, old_path, new_path, uid);
429 }
430
OnFileCreated(const string & path)431 void MediaProviderWrapper::OnFileCreated(const string& path) {
432 JNIEnv* env = MaybeAttachCurrentThread();
433
434 return onFileCreatedInternal(env, media_provider_object_, mid_on_file_created_, path);
435 }
436
ShouldAllowLookup(uid_t uid,int path_user_id)437 bool MediaProviderWrapper::ShouldAllowLookup(uid_t uid, int path_user_id) {
438 JNIEnv* env = MaybeAttachCurrentThread();
439
440 bool res = env->CallBooleanMethod(media_provider_object_, mid_should_allow_lookup_, uid,
441 path_user_id);
442
443 if (CheckForJniException(env)) {
444 return false;
445 }
446 return res;
447 }
448
IsAppCloneUser(uid_t userId)449 bool MediaProviderWrapper::IsAppCloneUser(uid_t userId) {
450 JNIEnv* env = MaybeAttachCurrentThread();
451
452 bool res = env->CallBooleanMethod(media_provider_object_, mid_is_app_clone_user_, userId);
453
454 if (CheckForJniException(env)) {
455 return false;
456 }
457 return res;
458 }
459
FileLookup(const std::string & path,uid_t uid,pid_t tid)460 std::unique_ptr<FileLookupResult> MediaProviderWrapper::FileLookup(const std::string& path,
461 uid_t uid, pid_t tid) {
462 JNIEnv* env = MaybeAttachCurrentThread();
463
464 ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
465
466 ScopedLocalRef<jobject> j_res_file_lookup_object(
467 env, env->CallObjectMethod(media_provider_object_, mid_file_lookup_, j_path.get(), uid,
468 tid));
469
470 if (CheckForJniException(env)) {
471 return nullptr;
472 }
473
474 int transforms = env->GetIntField(j_res_file_lookup_object.get(), fid_file_lookup_transforms_);
475 int transforms_reason =
476 env->GetIntField(j_res_file_lookup_object.get(), fid_file_lookup_transforms_reason_);
477 int original_uid = env->GetIntField(j_res_file_lookup_object.get(), fid_file_lookup_uid_);
478 bool transforms_complete = env->GetBooleanField(j_res_file_lookup_object.get(),
479 fid_file_lookup_transforms_complete_);
480 bool transforms_supported = env->GetBooleanField(j_res_file_lookup_object.get(),
481 fid_file_lookup_transforms_supported_);
482 ScopedLocalRef<jstring> j_io_path(
483 env,
484 (jstring)env->GetObjectField(j_res_file_lookup_object.get(), fid_file_lookup_io_path_));
485 ScopedUtfChars j_io_path_utf(env, j_io_path.get());
486
487 std::unique_ptr<FileLookupResult> file_lookup_result = std::make_unique<FileLookupResult>(
488 transforms, transforms_reason, original_uid, transforms_complete, transforms_supported,
489 string(j_io_path_utf.c_str()));
490 return file_lookup_result;
491 }
492
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)493 bool MediaProviderWrapper::Transform(const std::string& src, const std::string& dst, int transforms,
494 int transforms_reason, uid_t read_uid, uid_t open_uid,
495 uid_t transforms_uid) {
496 JNIEnv* env = MaybeAttachCurrentThread();
497
498 ScopedLocalRef<jstring> j_src(env, env->NewStringUTF(src.c_str()));
499 ScopedLocalRef<jstring> j_dst(env, env->NewStringUTF(dst.c_str()));
500 bool res = env->CallBooleanMethod(media_provider_object_, mid_transform_, j_src.get(),
501 j_dst.get(), transforms, transforms_reason, read_uid,
502 open_uid, transforms_uid);
503
504 if (CheckForJniException(env)) {
505 return false;
506 }
507
508 return res;
509 }
510
511 /*****************************************************************************************/
512 /******************************** Private member functions *******************************/
513 /*****************************************************************************************/
514
515 /**
516 * Finds MediaProvider method and adds it to methods map so it can be quickly called later.
517 */
CacheMethod(JNIEnv * env,const char method_name[],const char signature[])518 jmethodID MediaProviderWrapper::CacheMethod(JNIEnv* env, const char method_name[],
519 const char signature[]) {
520 jmethodID mid;
521 string actual_method_name(method_name);
522 actual_method_name.append("ForFuse");
523 mid = env->GetMethodID(media_provider_class_, actual_method_name.c_str(), signature);
524
525 if (!mid) {
526 LOG(FATAL) << "Error caching method: " << method_name << signature;
527 }
528 return mid;
529 }
530
DetachThreadFunction(void * unused)531 void MediaProviderWrapper::DetachThreadFunction(void* unused) {
532 int detach = gJavaVm->DetachCurrentThread();
533 CHECK_EQ(detach, 0);
534 }
535
MaybeAttachCurrentThread()536 JNIEnv* MediaProviderWrapper::MaybeAttachCurrentThread() {
537 // We could use pthread_getspecific here as that's likely quicker but
538 // that would result in wrong behaviour for threads that don't need to
539 // be attached (e.g, those that were created in managed code).
540 JNIEnv* env = nullptr;
541 if (gJavaVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_4) == JNI_OK) {
542 return env;
543 }
544
545 // This thread is currently unattached, so it must not have any TLS
546 // value. Note that we don't really care about the actual value we store
547 // in TLS -- we only care about the value destructor being called, which
548 // will happen as long as the key is not null.
549 CHECK(pthread_getspecific(gJniEnvKey) == nullptr);
550 CHECK_EQ(gJavaVm->AttachCurrentThread(&env, nullptr), 0);
551 CHECK(env != nullptr);
552
553 pthread_setspecific(gJniEnvKey, env);
554 return env;
555 }
556
557 } // namespace fuse
558 } // namespace mediaprovider
559