1 /*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "AndroidStorageProvider.h"
10
11 #include "Util.h"
12 #include "filesystem/Directory.h"
13 #include "filesystem/File.h"
14 #include "guilib/LocalizeStrings.h"
15 #include "utils/RegExp.h"
16 #include "utils/StringUtils.h"
17 #include "utils/URIUtils.h"
18 #include "utils/log.h"
19
20 #include "platform/android/activity/XBMCApp.h"
21
22 #include <cstdio>
23 #include <cstdlib>
24 #include <cstring>
25 #include <map>
26
27 #include <androidjni/Context.h>
28 #include <androidjni/Environment.h>
29 #include <androidjni/StorageManager.h>
30 #include <androidjni/StorageVolume.h>
31
32 static const char * typeWL[] = { "vfat", "exfat", "sdcardfs", "fuse", "ntfs", "fat32", "ext3", "ext4", "esdfs", "cifs" };
33 static const char * mountWL[] = { "/mnt", "/Removable", "/storage" };
34 static const char * mountBL[] = {
35 "/mnt/secure",
36 "/mnt/shell",
37 "/mnt/asec",
38 "/mnt/obb",
39 "/mnt/media_rw/extSdCard",
40 "/mnt/media_rw/sdcard",
41 "/mnt/media_rw/usbdisk",
42 "/storage/emulated",
43 "/mnt/runtime"
44 };
45 static const char * deviceWL[] = {
46 "/dev/block/vold",
47 "/dev/fuse",
48 "/mnt/media_rw",
49 "//" // SMB
50 };
51
CreateInstance()52 IStorageProvider* IStorageProvider::CreateInstance()
53 {
54 return new CAndroidStorageProvider();
55 }
56
CAndroidStorageProvider()57 CAndroidStorageProvider::CAndroidStorageProvider()
58 {
59 PumpDriveChangeEvents(NULL);
60 }
61
unescape(const std::string & str)62 std::string CAndroidStorageProvider::unescape(const std::string& str)
63 {
64 std::string retString;
65 for (uint32_t i=0; i < str.length(); ++i)
66 {
67 if (str[i] != '\\')
68 retString += str[i];
69 else
70 {
71 i += 1;
72 if (str[i] == 'u') // unicode
73 {
74 //! @todo implement
75 }
76 else if (str[i] >= '0' && str[i] <= '7') // octal
77 {
78 std::string octString;
79 while (str[i] >= '0' && str[i] <= '7')
80 {
81 octString += str[i];
82 i += 1;
83 }
84 if (octString.length() != 0)
85 {
86 uint8_t val = 0;
87 for (int j=octString.length()-1; j>=0; --j)
88 {
89 val += ((uint8_t)(octString[j] - '0')) * (1 << ((octString.length() - (j+1)) * 3));
90 }
91 retString += (char)val;
92 i -= 1;
93 }
94 }
95 }
96 }
97 return retString;
98 }
99
GetLocalDrives(VECSOURCES & localDrives)100 void CAndroidStorageProvider::GetLocalDrives(VECSOURCES &localDrives)
101 {
102 CMediaSource share;
103
104 // external directory
105 std::string path;
106 if (CXBMCApp::GetExternalStorage(path) && !path.empty() && XFILE::CDirectory::Exists(path))
107 {
108 share.strPath = path;
109 share.strName = g_localizeStrings.Get(21456);
110 share.m_ignore = true;
111 localDrives.push_back(share);
112 }
113
114 // root directory
115 share.strPath = "/";
116 share.strName = g_localizeStrings.Get(21453);
117 localDrives.push_back(share);
118 }
119
GetRemovableDrives(VECSOURCES & removableDrives)120 void CAndroidStorageProvider::GetRemovableDrives(VECSOURCES &removableDrives)
121 {
122 if (CJNIBase::GetSDKVersion() >= 24)
123 {
124 bool inError = false;
125
126 CJNIStorageManager manager(CJNIContext::getSystemService("storage"));
127 if (xbmc_jnienv()->ExceptionCheck())
128 {
129 xbmc_jnienv()->ExceptionDescribe();
130 xbmc_jnienv()->ExceptionClear();
131 inError = true;
132 }
133
134 if (!inError)
135 {
136 CJNIStorageVolumes vols = manager.getStorageVolumes();
137 if (xbmc_jnienv()->ExceptionCheck())
138 {
139 xbmc_jnienv()->ExceptionDescribe();
140 xbmc_jnienv()->ExceptionClear();
141 inError = true;
142 }
143
144 if (!inError)
145 {
146 VECSOURCES droidDrives;
147
148 for (int i = 0; i < vols.size(); ++i)
149 {
150 CJNIStorageVolume vol = vols.get(i);
151 // CLog::Log(LOGDEBUG, "-- Volume: {}({}) -- {}", vol.getPath(), vol.getUserLabel(), vol.getState());
152
153 bool removable = vol.isRemovable();
154 if (xbmc_jnienv()->ExceptionCheck())
155 {
156 xbmc_jnienv()->ExceptionDescribe();
157 xbmc_jnienv()->ExceptionClear();
158 inError = true;
159 break;
160 }
161
162 std::string state = vol.getState();
163 if (xbmc_jnienv()->ExceptionCheck())
164 {
165 xbmc_jnienv()->ExceptionDescribe();
166 xbmc_jnienv()->ExceptionClear();
167 inError = true;
168 break;
169 }
170
171 if (removable && state == CJNIEnvironment::MEDIA_MOUNTED)
172 {
173 CMediaSource share;
174
175 share.strPath = vol.getPath();
176 if (xbmc_jnienv()->ExceptionCheck())
177 {
178 xbmc_jnienv()->ExceptionDescribe();
179 xbmc_jnienv()->ExceptionClear();
180 inError = true;
181 break;
182 }
183
184 share.strName = vol.getUserLabel();
185 if (xbmc_jnienv()->ExceptionCheck())
186 {
187 xbmc_jnienv()->ExceptionDescribe();
188 xbmc_jnienv()->ExceptionClear();
189 inError = true;
190 break;
191 }
192
193 StringUtils::Trim(share.strName);
194 if (share.strName.empty() || share.strName == "?" ||
195 StringUtils::EqualsNoCase(share.strName, "null"))
196 share.strName = URIUtils::GetFileName(share.strPath);
197
198 share.m_ignore = true;
199 droidDrives.emplace_back(share);
200 }
201 }
202
203 if (!inError)
204 {
205 removableDrives.insert(removableDrives.end(), droidDrives.begin(), droidDrives.end());
206 return;
207 }
208 }
209 }
210 }
211
212 // Try fallback for SDK < 24 or in case of error
213 for (const auto& mountStr : GetRemovableDrivesLinux())
214 {
215 // Reject unreadable
216 if (XFILE::CDirectory::Exists(mountStr))
217 {
218 CMediaSource share;
219 share.strPath = unescape(mountStr);
220 share.strName = URIUtils::GetFileName(mountStr);
221 share.m_ignore = true;
222 removableDrives.emplace_back(share);
223 }
224 }
225 }
226
GetRemovableDrivesLinux()227 std::set<std::string> CAndroidStorageProvider::GetRemovableDrivesLinux()
228 {
229 std::set<std::string> result;
230
231 // mounted usb disks
232 char* buf = NULL;
233 FILE* pipe;
234 CRegExp reMount;
235 reMount.RegComp("^(.+?)\\s+(.+?)\\s+(.+?)\\s+(.+?)\\s");
236
237 /* /proc/mounts is only guaranteed atomic for the current read
238 * operation, so we need to read it all at once.
239 */
240 if ((pipe = fopen("/proc/mounts", "r")))
241 {
242 char* new_buf;
243 size_t buf_len = 4096;
244
245 while ((new_buf = (char*)realloc(buf, buf_len * sizeof(char))))
246 {
247 size_t nread;
248
249 buf = new_buf;
250 nread = fread(buf, sizeof(char), buf_len, pipe);
251
252 if (nread == buf_len)
253 {
254 rewind(pipe);
255 buf_len *= 2;
256 }
257 else
258 {
259 buf[nread] = '\0';
260 if (!feof(pipe))
261 new_buf = NULL;
262 break;
263 }
264 }
265
266 if (!new_buf)
267 {
268 free(buf);
269 buf = NULL;
270 }
271 fclose(pipe);
272 }
273 else
274 CLog::Log(LOGERROR, "Cannot read mount points");
275
276 if (buf)
277 {
278 char* line;
279 char* saveptr = NULL;
280
281 line = strtok_r(buf, "\n", &saveptr);
282
283 while (line)
284 {
285 if (reMount.RegFind(line) != -1)
286 {
287 std::string deviceStr = reMount.GetReplaceString("\\1");
288 std::string mountStr = reMount.GetReplaceString("\\2");
289 std::string fsStr = reMount.GetReplaceString("\\3");
290 std::string optStr = reMount.GetReplaceString("\\4");
291
292 // Blacklist
293 bool bl_ok = true;
294
295 // What mount points are rejected
296 for (unsigned int i=0; i < ARRAY_SIZE(mountBL); ++i)
297 {
298 if (StringUtils::StartsWithNoCase(mountStr, mountBL[i]))
299 {
300 bl_ok = false;
301 break;
302 }
303 }
304
305 if (bl_ok)
306 {
307 // What filesystems are accepted
308 bool fsok = false;
309 for (unsigned int i=0; i < ARRAY_SIZE(typeWL); ++i)
310 {
311 if (StringUtils::StartsWithNoCase(fsStr, typeWL[i]))
312 {
313 fsok = true;
314 break;
315 }
316 }
317 // What devices are accepted
318 bool devok = false;
319 for (unsigned int i=0; i < ARRAY_SIZE(deviceWL); ++i)
320 {
321 if (StringUtils::StartsWithNoCase(deviceStr, deviceWL[i]))
322 {
323 devok = true;
324 break;
325 }
326 }
327
328 // What mount points are accepted
329 bool mountok = false;
330 for (unsigned int i=0; i < ARRAY_SIZE(mountWL); ++i)
331 {
332 if (StringUtils::StartsWithNoCase(mountStr, mountWL[i]))
333 {
334 mountok = true;
335 break;
336 }
337 }
338
339 if(devok && (fsok || mountok))
340 {
341 result.insert(mountStr);
342 }
343 }
344 }
345 line = strtok_r(NULL, "\n", &saveptr);
346 }
347 free(buf);
348 }
349 return result;
350 }
351
GetDiskUsage()352 std::vector<std::string> CAndroidStorageProvider::GetDiskUsage()
353 {
354 std::vector<std::string> result;
355
356 std::string usage;
357 // add header
358 CXBMCApp::GetStorageUsage("", usage);
359 result.push_back(usage);
360
361 usage.clear();
362 // add rootfs
363 if (CXBMCApp::GetStorageUsage("/", usage) && !usage.empty())
364 result.push_back(usage);
365
366 usage.clear();
367 // add external storage if available
368 std::string path;
369 if (CXBMCApp::GetExternalStorage(path) && !path.empty() &&
370 CXBMCApp::GetStorageUsage(path, usage) && !usage.empty())
371 result.push_back(usage);
372
373 // add removable storage
374 VECSOURCES drives;
375 GetRemovableDrives(drives);
376 for (unsigned int i = 0; i < drives.size(); i++)
377 {
378 usage.clear();
379 if (CXBMCApp::GetStorageUsage(drives[i].strPath, usage) && !usage.empty())
380 result.push_back(usage);
381 }
382
383 return result;
384 }
385
PumpDriveChangeEvents(IStorageEventsCallback * callback)386 bool CAndroidStorageProvider::PumpDriveChangeEvents(IStorageEventsCallback *callback)
387 {
388 VECSOURCES drives;
389 GetRemovableDrives(drives);
390 bool changed = m_removableDrives != drives;
391 m_removableDrives = std::move(drives);
392 return changed;
393 }
394