1 /*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #define FUSE_USE_VERSION 34
17
18 #include "fuse_manager/fuse_manager.h"
19
20 #include <atomic>
21 #include <cassert>
22 #include <cerrno>
23 #include <cstddef>
24 #include <cstdio>
25 #include <cstdlib>
26 #include <cstring>
27 #include <exception>
28 #include <filesystem>
29 #include <fcntl.h>
30 #include <iostream>
31 #include <map>
32 #include <mutex>
33 #include <pthread.h>
34 #include <shared_mutex>
35 #include <stdexcept>
36 #include <string>
37 #include <thread>
38 #include <unistd.h>
39
40 #include <fuse.h>
41 #include <fuse_i.h>
42 #include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
43
44 #include "datetime_ex.h"
45 #include "dfs_error.h"
46 #include "directory_ex.h"
47 #include "dk_database.h"
48 #include "dk_asset_read_session.h"
49 #include "drive_kit.h"
50 #include "meta_file.h"
51 #include "sdk_helper.h"
52 #include "utils_log.h"
53
54 namespace OHOS {
55 namespace FileManagement {
56 namespace CloudFile {
57 using namespace std;
58
59 static const string LOCAL_PATH_PREFIX = "/mnt/hmdfs/";
60 static const string LOCAL_PATH_SUFFIX = "/account/device_view/local";
61 static const string FUSE_CACHE_PATH_PREFIX = "/data/service/el2/";
62 static const string FUSE_CACHE_PATH_SUFFIX = "/hmdfs/fuse";
63 static const string PHOTOS_BUNDLE_NAME = "com.ohos.photos";
64 static const unsigned int OID_USER_DATA_RW = 1008;
65 static const unsigned int STAT_NLINK_REG = 1;
66 static const unsigned int STAT_NLINK_DIR = 2;
67 static const unsigned int STAT_MODE_REG = 0770;
68 static const unsigned int STAT_MODE_DIR = 0771;
69
70 struct CloudInode {
71 shared_ptr<MetaBase> mBase{nullptr};
72 shared_ptr<MetaFile> mFile{nullptr};
73 string path;
74 fuse_ino_t parent{0};
75 atomic<int> refCount{0};
76 shared_ptr<DriveKit::DKAssetReadSession> readSession{nullptr};
77 atomic<int> sessionRefCount{0};
78 std::shared_mutex sessionLock;
79 };
80
81 struct FuseData {
82 int userId;
83 shared_ptr<CloudInode> rootNode{nullptr};
84 /* store CloudInode by path */
85 map<string, shared_ptr<CloudInode>> inodeCache;
86 std::shared_mutex cacheLock;
87 shared_ptr<DriveKit::DKDatabase> database;
88 struct fuse_session *se;
89 };
90
GetDatabase(struct FuseData * data)91 static shared_ptr<DriveKit::DKDatabase> GetDatabase(struct FuseData *data)
92 {
93 if (!data->database) {
94 auto driveKit = DriveKit::DriveKitNative::GetInstance(data->userId);
95 if (driveKit == nullptr) {
96 LOGE("sdk helper get drive kit instance fail");
97 return nullptr;
98 }
99
100 auto container = driveKit->GetDefaultContainer(PHOTOS_BUNDLE_NAME);
101 if (container == nullptr) {
102 LOGE("sdk helper get drive kit container fail");
103 return nullptr;
104 }
105
106 data->database = container->GetPrivateDatabase();
107 if (data->database == nullptr) {
108 LOGE("sdk helper get drive kit database fail");
109 return nullptr;
110 }
111 }
112 return data->database;
113 }
114
FindNode(struct FuseData * data,string path)115 static shared_ptr<CloudInode> FindNode(struct FuseData *data, string path)
116 {
117 shared_ptr<CloudInode> ret;
118 std::shared_lock<std::shared_mutex> rLock(data->cacheLock, std::defer_lock);
119 rLock.lock();
120 ret = data->inodeCache[path];
121 rLock.unlock();
122 return ret;
123 }
124
GetRootInode(struct FuseData * data,fuse_ino_t ino)125 static shared_ptr<CloudInode> GetRootInode(struct FuseData *data, fuse_ino_t ino)
126 {
127 std::unique_lock<std::shared_mutex> wLock(data->cacheLock, std::defer_lock);
128 shared_ptr<CloudInode> ret;
129
130 wLock.lock();
131 if (!data->rootNode) {
132 data->rootNode = make_shared<CloudInode>();
133 data->rootNode->path = "/";
134 data->rootNode->refCount = 1;
135 data->rootNode->mBase = make_shared<MetaBase>();
136 data->rootNode->mBase->mode = S_IFDIR;
137 data->rootNode->mBase->mtime = static_cast<uint64_t>(GetSecondsSince1970ToNow());
138 LOGD("create rootNode");
139 }
140 data->rootNode->mFile = MetaFileMgr::GetInstance().GetMetaFile(data->userId, "/");
141 ret = data->rootNode;
142 wLock.unlock();
143
144 return ret;
145 }
146
GetCloudInode(struct FuseData * data,fuse_ino_t ino)147 static shared_ptr<CloudInode> GetCloudInode(struct FuseData *data, fuse_ino_t ino)
148 {
149 if (ino == FUSE_ROOT_ID) {
150 return GetRootInode(data, ino);
151 } else {
152 struct CloudInode *inoPtr = reinterpret_cast<struct CloudInode *>(ino);
153 return FindNode(data, inoPtr->path);
154 }
155 }
156
CloudPath(struct FuseData * data,fuse_ino_t ino)157 static string CloudPath(struct FuseData *data, fuse_ino_t ino)
158 {
159 return GetCloudInode(data, ino)->path;
160 }
161
GetMetaAttr(shared_ptr<CloudInode> ino,struct stat * stbuf)162 static void GetMetaAttr(shared_ptr<CloudInode> ino, struct stat *stbuf)
163 {
164 stbuf->st_ino = reinterpret_cast<fuse_ino_t>(ino.get());
165 stbuf->st_uid = OID_USER_DATA_RW;
166 stbuf->st_gid = OID_USER_DATA_RW;
167 stbuf->st_mtime = static_cast<int64_t>(ino->mBase->mtime);
168 if (ino->mBase->mode & S_IFDIR) {
169 stbuf->st_mode = S_IFDIR | STAT_MODE_DIR;
170 stbuf->st_nlink = STAT_NLINK_DIR;
171 LOGD("directory, ino:%s", ino->path.c_str());
172 } else {
173 stbuf->st_mode = S_IFREG | STAT_MODE_REG;
174 stbuf->st_nlink = STAT_NLINK_REG;
175 stbuf->st_size = static_cast<decltype(stbuf->st_size)>(ino->mBase->size);
176 LOGD("regular file, ino:%s, size: %lld", ino->path.c_str(), (long long)stbuf->st_size);
177 }
178 }
179
CloudDoLookup(fuse_req_t req,fuse_ino_t parent,const char * name,struct fuse_entry_param * e)180 static int CloudDoLookup(fuse_req_t req, fuse_ino_t parent, const char *name,
181 struct fuse_entry_param *e)
182 {
183 int err = 0;
184 shared_ptr<CloudInode> child;
185 bool create = false;
186 struct FuseData *data = static_cast<struct FuseData *>(fuse_req_userdata(req));
187 string childName = (parent == FUSE_ROOT_ID) ? CloudPath(data, parent) + name :
188 CloudPath(data, parent) + "/" + name;
189 std::unique_lock<std::shared_mutex> wLock(data->cacheLock, std::defer_lock);
190
191 LOGD("parent: %{private}s, name: %s", CloudPath(data, parent).c_str(), name);
192
193 child = FindNode(data, childName);
194 if (!child) {
195 child = make_shared<CloudInode>();
196 create = true;
197 LOGD("new child %s", child->path.c_str());
198 }
199 child->mBase = make_shared<MetaBase>(name);
200 child->path = childName;
201 child->refCount++;
202 err = GetCloudInode(data, parent)->mFile->DoLookup(*(child->mBase));
203 if (err) {
204 LOGE("lookup %s error, err: %{public}d", childName.c_str(), err);
205 return err;
206 }
207 if (child->mBase->mode & S_IFDIR) {
208 child->mFile = MetaFileMgr::GetInstance().GetMetaFile(data->userId,
209 childName);
210 }
211 child->parent = parent;
212 if (create) {
213 wLock.lock();
214 data->inodeCache[child->path] = child;
215 wLock.unlock();
216 }
217 LOGD("lookup success, child: %{private}s, refCount: %lld", child->path.c_str(),
218 static_cast<long long>(child->refCount));
219 GetMetaAttr(child, &e->attr);
220 e->ino = reinterpret_cast<fuse_ino_t>(child.get());
221 return 0;
222 }
223
CloudLookup(fuse_req_t req,fuse_ino_t parent,const char * name)224 static void CloudLookup(fuse_req_t req, fuse_ino_t parent,
225 const char *name)
226 {
227 struct fuse_entry_param e;
228 int err;
229
230 err = CloudDoLookup(req, parent, name, &e);
231 if (err) {
232 fuse_reply_err(req, err);
233 } else {
234 fuse_reply_entry(req, &e);
235 }
236 }
237
PutNode(struct FuseData * data,shared_ptr<CloudInode> node,uint64_t num)238 static void PutNode(struct FuseData *data, shared_ptr<CloudInode> node, uint64_t num)
239 {
240 std::unique_lock<std::shared_mutex> wLock(data->cacheLock, std::defer_lock);
241 node->refCount -= num;
242 LOGD("%s, put num: %lld, current refCount: %d", node->path.c_str(), (long long)num, node->refCount.load());
243 if (node->refCount == 0) {
244 LOGD("node released: %s", node->path.c_str());
245 wLock.lock();
246 data->inodeCache.erase(node->path);
247 wLock.unlock();
248 }
249 }
250
CloudForget(fuse_req_t req,fuse_ino_t ino,uint64_t nlookup)251 static void CloudForget(fuse_req_t req, fuse_ino_t ino,
252 uint64_t nlookup)
253 {
254 struct FuseData *data = static_cast<struct FuseData *>(fuse_req_userdata(req));
255 shared_ptr<CloudInode> node = GetCloudInode(data, ino);
256 LOGD("forget %s, nlookup: %lld", node->path.c_str(), (long long)nlookup);
257 PutNode(data, node, nlookup);
258 fuse_reply_none(req);
259 }
260
CloudGetAttr(fuse_req_t req,fuse_ino_t ino,struct fuse_file_info * fi)261 static void CloudGetAttr(fuse_req_t req, fuse_ino_t ino,
262 struct fuse_file_info *fi)
263 {
264 struct stat buf;
265 struct FuseData *data = static_cast<struct FuseData *>(fuse_req_userdata(req));
266 (void) fi;
267
268 LOGD("getattr, %s", CloudPath(data, ino).c_str());
269 GetMetaAttr(GetCloudInode(data, ino), &buf);
270
271 fuse_reply_attr(req, &buf, 0);
272 }
273
GetAssetKey(int fileType)274 static string GetAssetKey(int fileType)
275 {
276 switch (fileType) {
277 case FILE_TYPE_CONTENT:
278 return "content";
279 case FILE_TYPE_THUMBNAIL:
280 return "thumbnail";
281 case FILE_TYPE_LCD:
282 return "lcd";
283 default:
284 LOGE("bad fileType %{public}d", fileType);
285 return "";
286 }
287 }
288
GetLocalPath(int32_t userId)289 static string GetLocalPath(int32_t userId)
290 {
291 return LOCAL_PATH_PREFIX + to_string(userId) + LOCAL_PATH_SUFFIX;
292 }
293
GetFuseCachePath(int32_t userId)294 static string GetFuseCachePath(int32_t userId)
295 {
296 return FUSE_CACHE_PATH_PREFIX + to_string(userId) + FUSE_CACHE_PATH_SUFFIX;
297 }
298
GetAssetPath(shared_ptr<CloudInode> cInode,struct FuseData * data)299 static string GetAssetPath(shared_ptr<CloudInode> cInode, struct FuseData *data)
300 {
301 string path;
302 filesystem::path parentPath;
303 switch (cInode->mBase->fileType) {
304 case FILE_TYPE_THUMBNAIL:
305 case FILE_TYPE_LCD:
306 path = GetLocalPath(data->userId) + cInode->path;
307 break;
308 default:
309 path = GetFuseCachePath(data->userId) + cInode->path;
310 break;
311 }
312 parentPath = filesystem::path(path).parent_path();
313 ForceCreateDirectory(parentPath.string());
314 LOGD("fileType: %d, create dir: %s, relative path: %s",
315 cInode->mBase->fileType, parentPath.string().c_str(), cInode->path.c_str());
316 return path;
317 }
318
CloudOpen(fuse_req_t req,fuse_ino_t ino,struct fuse_file_info * fi)319 static void CloudOpen(fuse_req_t req, fuse_ino_t ino,
320 struct fuse_file_info *fi)
321 {
322 struct FuseData *data = static_cast<struct FuseData *>(fuse_req_userdata(req));
323 shared_ptr<CloudInode> cInode = GetCloudInode(data, ino);
324 string recordId = MetaFileMgr::GetInstance().CloudIdToRecordId(cInode->mBase->cloudId);
325 shared_ptr<DriveKit::DKDatabase> database = GetDatabase(data);
326 std::unique_lock<std::shared_mutex> wSesLock(cInode->sessionLock, std::defer_lock);
327
328 LOGD("open %s", CloudPath(data, ino).c_str());
329 if (!database) {
330 fuse_reply_err(req, EPERM);
331 return;
332 }
333 wSesLock.lock();
334 if (!cInode->readSession) {
335 /*
336 * 'recordType' is fixed to "fileType" now
337 * 'assetKey' is one of "content"/"lcd"/"thumbnail"
338 */
339 LOGD("recordId: %s", recordId.c_str());
340 cInode->readSession = database->NewAssetReadSession("fileType",
341 recordId,
342 GetAssetKey(cInode->mBase->fileType),
343 GetAssetPath(cInode, data));
344 }
345
346 if (!cInode->readSession) {
347 fuse_reply_err(req, EPERM);
348 } else {
349 cInode->sessionRefCount++;
350 LOGD("open success, sessionRefCount: %d", cInode->sessionRefCount.load());
351 fuse_reply_open(req, fi);
352 }
353 wSesLock.unlock();
354 }
355
CloudRelease(fuse_req_t req,fuse_ino_t ino,struct fuse_file_info * fi)356 static void CloudRelease(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
357 {
358 struct FuseData *data = static_cast<struct FuseData *>(fuse_req_userdata(req));
359 shared_ptr<CloudInode> cInode = GetCloudInode(data, ino);
360 std::unique_lock<std::shared_mutex> wSesLock(cInode->sessionLock, std::defer_lock);
361
362 wSesLock.lock();
363 LOGD("%s, sessionRefCount: %d", CloudPath(data, ino).c_str(), cInode->sessionRefCount.load());
364 cInode->sessionRefCount--;
365 if (cInode->sessionRefCount == 0) {
366 bool needRemain = false;
367 if (cInode->mBase->fileType != FILE_TYPE_CONTENT) {
368 needRemain = true;
369 }
370 bool res = cInode->readSession->Close(needRemain);
371 if (!res) {
372 LOGE("close error, needRemain: %d", needRemain);
373 }
374 if (needRemain && res) {
375 GetCloudInode(data, cInode->parent)->mFile->DoRemove(*(cInode->mBase));
376 LOGD("remove from dentryfile");
377 }
378 cInode->readSession = nullptr;
379 LOGD("readSession released");
380 }
381 wSesLock.unlock();
382
383 fuse_reply_err(req, 0);
384 }
385
CloudReadDir(fuse_req_t req,fuse_ino_t ino,size_t size,off_t off,struct fuse_file_info * fi)386 static void CloudReadDir(fuse_req_t req, fuse_ino_t ino, size_t size,
387 off_t off, struct fuse_file_info *fi)
388 {
389 struct FuseData *data = static_cast<struct FuseData *>(fuse_req_userdata(req));
390 LOGE("readdir %s, not support", CloudPath(data, ino).c_str());
391 fuse_reply_err(req, ENOENT);
392 }
393
CloudForgetMulti(fuse_req_t req,size_t count,struct fuse_forget_data * forgets)394 static void CloudForgetMulti(fuse_req_t req, size_t count,
395 struct fuse_forget_data *forgets)
396 {
397 struct FuseData *data = static_cast<struct FuseData *>(fuse_req_userdata(req));
398 LOGD("forget_multi");
399 for (size_t i = 0; i < count; i++) {
400 shared_ptr<CloudInode> node = GetCloudInode(data, forgets[i].ino);
401 LOGD("forget (i=%zu) %s, nlookup: %lld", i, node->path.c_str(), (long long)forgets[i].nlookup);
402 PutNode(data, node, forgets[i].nlookup);
403 }
404 fuse_reply_none(req);
405 }
406
CloudRead(fuse_req_t req,fuse_ino_t ino,size_t size,off_t off,struct fuse_file_info * fi)407 static void CloudRead(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
408 struct fuse_file_info *fi)
409 {
410 int64_t readSize;
411 DriveKit::DKError dkError;
412 shared_ptr<char> buf = nullptr;
413 struct FuseData *data = static_cast<struct FuseData *>(fuse_req_userdata(req));
414 shared_ptr<CloudInode> cInode = GetCloudInode(data, ino);
415
416 LOGD("%s, size=%zd, off=%lu", CloudPath(data, ino).c_str(), size, (unsigned long)off);
417
418 buf.reset(new char[size], [](char* ptr) {
419 delete[] ptr;
420 });
421 if (!buf) {
422 fuse_reply_err(req, ENOMEM);
423 return;
424 }
425
426 if (!cInode->readSession) {
427 fuse_reply_err(req, EPERM);
428 return;
429 }
430
431 readSize = cInode->readSession->PRead(off, size, buf.get(), dkError);
432 if (dkError.HasError()) {
433 LOGE("read error");
434 fuse_reply_err(req, EIO);
435 return;
436 }
437
438 LOGD("read %s success, %lld bytes", CloudPath(data, ino).c_str(), static_cast<long long>(readSize));
439 fuse_reply_buf(req, buf.get(), readSize);
440 }
441
442 static const struct fuse_lowlevel_ops cloudFuseOps = {
443 .lookup = CloudLookup,
444 .forget = CloudForget,
445 .getattr = CloudGetAttr,
446 .open = CloudOpen,
447 .read = CloudRead,
448 .release = CloudRelease,
449 .readdir = CloudReadDir,
450 .forget_multi = CloudForgetMulti,
451 };
452
StartFuse(int32_t userId,int32_t devFd,const string & path)453 int32_t FuseManager::StartFuse(int32_t userId, int32_t devFd, const string &path)
454 {
455 struct fuse_loop_config config;
456 struct fuse_args args = FUSE_ARGS_INIT(0, nullptr);
457 struct FuseData data;
458 struct fuse_session *se = nullptr;
459 int ret;
460
461 if (fuse_opt_add_arg(&args, path.c_str())) {
462 LOGE("Mount path invalid");
463 return -EINVAL;
464 }
465
466 se = fuse_session_new(&args, &cloudFuseOps,
467 sizeof(cloudFuseOps), &data);
468 if (se == nullptr) {
469 LOGE("fuse_session_new error");
470 return -EINVAL;
471 }
472
473 data.userId = userId;
474 data.se = se;
475
476 LOGI("fuse_session_new success, userId: %{public}d", userId);
477 se->fd = devFd;
478 se->mountpoint = strdup(path.c_str());
479 sessions_[userId] = se;
480
481 fuse_daemonize(true);
482 config.max_idle_threads = 1;
483 ret = fuse_session_loop_mt(se, &config);
484
485 fuse_session_unmount(se);
486 LOGI("fuse_session_unmount");
487 if (se->mountpoint) {
488 free(se->mountpoint);
489 se->mountpoint = nullptr;
490 }
491
492 fuse_session_destroy(se);
493 close(devFd);
494 return ret;
495 }
496
GetInstance()497 FuseManager &FuseManager::GetInstance()
498 {
499 static FuseManager instance_;
500 return instance_;
501 }
502
503 } // namespace CloudFile
504 } // namespace FileManagement
505 } // namespace OHOS
506