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 #define MLOG_TAG "FileInotify"
16 #include "medialibrary_inotify.h"
17 
18 #include <string>
19 #include <thread>
20 
21 #include "unistd.h"
22 #include "media_log.h"
23 #include "media_file_uri.h"
24 #include "media_file_utils.h"
25 #include "medialibrary_bundle_manager.h"
26 #include "medialibrary_object_utils.h"
27 #include "medialibrary_errno.h"
28 #include "medialibrary_uripermission_operations.h"
29 #include "permission_utils.h"
30 #include "dfx_utils.h"
31 
32 using namespace std;
33 namespace OHOS {
34 namespace Media {
35 std::shared_ptr<MediaLibraryInotify> MediaLibraryInotify::instance_ = nullptr;
36 std::mutex MediaLibraryInotify::mutex_;
37 const int32_t MAX_WATCH_LIST = 1000;
38 const int32_t SINGLE_BUNDLE_LIST = 200;
39 const int32_t MAX_AGING_WATCH_LIST = 100;
40 
GetInstance()41 shared_ptr<MediaLibraryInotify> MediaLibraryInotify::GetInstance()
42 {
43     if (instance_ != nullptr) {
44         return instance_;
45     }
46     lock_guard<mutex> lock(mutex_);
47     if (instance_ == nullptr) {
48         instance_ = make_shared<MediaLibraryInotify>();
49         if (instance_ == nullptr) {
50             MEDIA_ERR_LOG("GetInstance nullptr");
51             return instance_;
52         }
53         instance_->Init();
54     }
55     return instance_;
56 }
57 
ConvertMediaPath(const std::string & path)58 static string ConvertMediaPath(const std::string &path)
59 {
60     // if input path is /storage/media/local/xxx, convert to /storage/cloud/xxx
61     string mediaPath = path;
62     string localPath = "/storage/media/local/";
63     string cloudPath = "/storage/cloud/";
64     if (mediaPath.find(localPath) != string::npos) {
65         mediaPath.replace(mediaPath.find(localPath), localPath.length(), cloudPath);
66     }
67     return mediaPath;
68 }
69 
WatchCallBack()70 void MediaLibraryInotify::WatchCallBack()
71 {
72     string name("MediaLibraryInotify");
73     pthread_setname_np(pthread_self(), name.c_str());
74     const int32_t READ_LEN = 255;
75     char data[READ_LEN] = {0};
76     while (isWatching_) {
77         int32_t len = read(inotifyFd_, data, READ_LEN);
78         if (len < static_cast<int32_t>(sizeof(struct inotify_event))) {
79             MEDIA_ERR_LOG("read event error, len: %{public}d, fd: %{public}d, error: %{public}d", len, inotifyFd_,
80                 errno);
81             Restart();
82             return;
83         }
84         int32_t index = 0;
85         while (index < len) {
86             struct inotify_event *event = reinterpret_cast<struct inotify_event *>(data + index);
87             index += static_cast<int32_t>(sizeof(struct inotify_event)) +
88 		    static_cast<int32_t>(event->len);
89             unique_lock<mutex> lock(mutex_);
90             if (watchList_.count(event->wd) == 0) {
91                 continue;
92             }
93             auto &item = watchList_.at(event->wd);
94             auto eventMask = event->mask;
95             auto &meetEvent = item.meetEvent_;
96             meetEvent = (eventMask & IN_MODIFY) ? (meetEvent | IN_MODIFY) : meetEvent;
97             meetEvent = (eventMask & IN_CLOSE_WRITE) ? (meetEvent | IN_CLOSE_WRITE) : meetEvent;
98             meetEvent = (eventMask & IN_CLOSE_NOWRITE) ? (meetEvent | IN_CLOSE_NOWRITE) : meetEvent;
99             if (((meetEvent & IN_CLOSE_WRITE) && (meetEvent & IN_MODIFY)) ||
100                 ((meetEvent & IN_CLOSE_NOWRITE) && (meetEvent & IN_MODIFY))) {
101                 MEDIA_INFO_LOG("path:%s, meetEvent:%x file_id:%s", item.path_.c_str(),
102                     meetEvent, item.uri_.c_str());
103                 string id = MediaFileUtils::GetIdFromUri(item.uri_);
104                 string itemPath = ConvertMediaPath(item.path_);
105                 string bundleName = item.bundleName_;
106                 MediaFileUri itemUri(item.uri_);
107                 MediaLibraryApi itemApi = item.api_;
108                 Remove(event->wd);
109                 lock.unlock();
110                 MediaLibraryObjectUtils::ScanFileAsync(itemPath, id, itemApi);
111                 UriPermissionOperations::DeleteBundlePermission(id, bundleName, itemUri.GetTableName());
112             }
113         }
114     }
115     isWatching_ = false;
116 }
117 
Restart()118 void MediaLibraryInotify::Restart()
119 {
120     for (auto iter = watchList_.begin(); iter != watchList_.end(); iter++) {
121         if (inotify_rm_watch(inotifyFd_, iter->first) != 0) {
122             MEDIA_ERR_LOG("rm watch fd: %{public}d, fail: %{public}d", iter->first, errno);
123         }
124     }
125     isWatching_ = false;
126     watchList_.clear();
127     inotifyFd_ = 0;
128     Init();
129 }
130 
DoAging()131 void MediaLibraryInotify::DoAging()
132 {
133     lock_guard<mutex> lock(mutex_);
134     if (watchList_.size() > MAX_AGING_WATCH_LIST) {
135         MEDIA_DEBUG_LOG("watch list clear");
136         watchList_.clear();
137     }
138 }
139 
DoStop()140 void MediaLibraryInotify::DoStop()
141 {
142     lock_guard<mutex> lock(mutex_);
143     for (auto iter = watchList_.begin(); iter != watchList_.end(); iter++) {
144         if (inotify_rm_watch(inotifyFd_, iter->first) != 0) {
145             MEDIA_ERR_LOG("rm watch fd: %{public}d, fail: %{public}d", iter->first, errno);
146         }
147     }
148     isWatching_ = false;
149     watchList_.clear();
150     inotifyFd_ = 0;
151     MEDIA_INFO_LOG("stop success");
152 }
153 
RemoveByFileUri(const string & uri,MediaLibraryApi api)154 int32_t MediaLibraryInotify::RemoveByFileUri(const string &uri, MediaLibraryApi api)
155 {
156     lock_guard<mutex> lock(mutex_);
157     int32_t wd = -1;
158     for (auto iter = watchList_.begin(); iter != watchList_.end(); iter++) {
159         if (iter->second.uri_ == uri && iter->second.api_ == api) {
160             wd = iter->first;
161             MEDIA_DEBUG_LOG("remove uri:%{public}s wd:%{public}d path:%{public}s",
162                 iter->second.uri_.c_str(), wd, iter->second.path_.c_str());
163             break;
164         }
165     }
166     if (wd < 0) {
167         MEDIA_DEBUG_LOG("remove uri:%s fail", uri.c_str());
168         return E_FAIL;
169     }
170     return Remove(wd);
171 }
172 
Remove(int wd)173 int32_t MediaLibraryInotify::Remove(int wd)
174 {
175     watchList_.erase(wd);
176     if (inotify_rm_watch(inotifyFd_, wd) != 0) {
177         MEDIA_ERR_LOG("rm watch fd:%{public}d fail:%{public}d", wd, errno);
178         return E_FAIL;
179     }
180     return E_SUCCESS;
181 }
182 
Init()183 int32_t MediaLibraryInotify::Init()
184 {
185     if (inotifyFd_ <= 0) {
186         inotifyFd_ = inotify_init();
187         if (inotifyFd_ < 0) {
188             MEDIA_ERR_LOG("add AddWatchList fail fd: %{public}d, error: %{public}d", inotifyFd_, errno);
189             return E_FAIL;
190         }
191         MEDIA_INFO_LOG("init inotifyFd_: %{public}d success", inotifyFd_);
192     }
193     return E_SUCCESS;
194 }
195 
GetBundleCount(const std::string & bundleName)196 int32_t MediaLibraryInotify::GetBundleCount(const std::string &bundleName)
197 {
198     int count = 0;
199     for (auto &pair : watchList_) {
200         if (bundleName.compare(pair.second.bundleName_) == 0) {
201             count++;
202         }
203     }
204     MEDIA_DEBUG_LOG("MediaLibraryInotify GetBundleCount count:%{public}d", count);
205     return count;
206 }
207 
BuildDfxInfo()208 const string MediaLibraryInotify::BuildDfxInfo()
209 {
210     unordered_map<string, WatchBundleInfo> bundleInfoMap;
211     for (auto &pair : watchList_) {
212         string bundleName = pair.second.bundleName_;
213         if (bundleInfoMap.find(bundleName) != bundleInfoMap.end()) {
214             auto it = bundleInfoMap.find(bundleName);
215             int32_t count = ++it->second.count;
216             int64_t firstTime = pair.second.currentTime_ < it->second.firstEntryTime ?
217                 pair.second.currentTime_ : it->second.firstEntryTime;
218             string firstUri = pair.second.currentTime_ < it->second.firstEntryTime ?
219                 pair.second.uri_ : it->second.firstUri;
220             WatchBundleInfo watchBundleInfo(count, firstTime, firstUri, bundleName);
221             it->second = watchBundleInfo;
222         } else {
223             WatchBundleInfo watchBundleInfo(1, pair.second.currentTime_, pair.second.uri_, bundleName);
224             bundleInfoMap.emplace(bundleName, watchBundleInfo);
225         }
226     }
227     string dfxInfo;
228     for (const auto &pair : bundleInfoMap) {
229         dfxInfo.append(pair.second.Dump());
230         dfxInfo.append(";");
231     }
232     return dfxInfo;
233 }
234 
AddWatchList(const string & path,const string & uri,MediaLibraryApi api)235 int32_t MediaLibraryInotify::AddWatchList(const string &path, const string &uri, MediaLibraryApi api)
236 {
237     lock_guard<mutex> lock(mutex_);
238     string bundleName = MediaLibraryBundleManager::GetInstance()->GetClientBundleName();
239     if (watchList_.size() >= MAX_WATCH_LIST || GetBundleCount(bundleName) >= SINGLE_BUNDLE_LIST) {
240         MEDIA_ERR_LOG("watch list full, add uri:%{public}s fail, bundleName:%{public}s, info:%{public}s",
241             uri.c_str(), bundleName.c_str(), BuildDfxInfo().c_str());
242         return E_FAIL;
243     }
244     int32_t wd = inotify_add_watch(inotifyFd_, path.c_str(), IN_CLOSE | IN_MODIFY);
245     if (wd > 0) {
246         int64_t currentTime = MediaFileUtils::UTCTimeSeconds();
247         struct WatchInfo item(path, uri, bundleName, api, currentTime);
248         MEDIA_INFO_LOG("inotify emplace path:%{public}s, bundleName:%{public}s, watchSize:%{public}d,",
249             DfxUtils::GetSafePath(path).c_str(), bundleName.c_str(), static_cast<int32_t>(watchList_.size()));
250         watchList_.emplace(wd, item);
251     }
252     if (!isWatching_.load()) {
253         isWatching_ = true;
254         thread(&MediaLibraryInotify::WatchCallBack, this).detach();
255     }
256     return E_SUCCESS;
257 }
258 } // namespace Media
259 } // namespace OHOS
260