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