Merge pull request #4314 from MartijnKaijser/beta1
[vuplus_xbmc] / xbmc / filesystem / SMBDirectory.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 /*
22 * know bugs:
23 * - when opening a server for the first time with ip adres and the second time
24 *   with server name, access to the server is denied.
25 * - when browsing entire network, user can't go back one step
26 *   share = smb://, user selects a workgroup, user selects a server.
27 *   doing ".." will go back to smb:// (entire network) and not to workgroup list.
28 *
29 * debugging is set to a max of 10 for release builds (see local.h)
30 */
31
32 #include "system.h"
33
34 #include "SMBDirectory.h"
35 #include "Util.h"
36 #include "guilib/LocalizeStrings.h"
37 #include "FileItem.h"
38 #include "settings/AdvancedSettings.h"
39 #include "utils/StringUtils.h"
40 #include "utils/log.h"
41 #include "utils/URIUtils.h"
42 #include "threads/SingleLock.h"
43 #include "PasswordManager.h"
44
45 #include <libsmbclient.h>
46
47 #if defined(TARGET_DARWIN)
48 #define XBMC_SMB_MOUNT_PATH "Library/Application Support/XBMC/Mounts/"
49 #else
50 #define XBMC_SMB_MOUNT_PATH "/media/xbmc/smb/"
51 #endif
52
53 struct CachedDirEntry
54 {
55   unsigned int type;
56   CStdString name;
57 };
58
59 using namespace XFILE;
60 using namespace std;
61
62 CSMBDirectory::CSMBDirectory(void)
63 {
64   smb.AddActiveConnection();
65 }
66
67 CSMBDirectory::~CSMBDirectory(void)
68 {
69   smb.AddIdleConnection();
70 }
71
72 bool CSMBDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items)
73 {
74   // We accept smb://[[[domain;]user[:password@]]server[/share[/path[/file]]]]
75
76   /* samba isn't thread safe with old interface, always lock */
77   CSingleLock lock(smb);
78
79   smb.Init();
80
81   /* we need an url to do proper escaping */
82   CURL url(strPath);
83
84   //Separate roots for the authentication and the containing items to allow browsing to work correctly
85   CStdString strRoot = strPath;
86   CStdString strAuth;
87
88   lock.Leave(); // OpenDir is locked
89   int fd = OpenDir(url, strAuth);
90   if (fd < 0)
91     return false;
92
93   URIUtils::AddSlashAtEnd(strRoot);
94   URIUtils::AddSlashAtEnd(strAuth);
95
96   CStdString strFile;
97
98   // need to keep the samba lock for as short as possible.
99   // so we first cache all directory entries and then go over them again asking for stat
100   // "stat" is locked each time. that way the lock is freed between stat requests
101   vector<CachedDirEntry> vecEntries;
102   struct smbc_dirent* dirEnt;
103
104   lock.Enter();
105   while ((dirEnt = smbc_readdir(fd)))
106   {
107     CachedDirEntry aDir;
108     aDir.type = dirEnt->smbc_type;
109     aDir.name = dirEnt->name;
110     vecEntries.push_back(aDir);
111   }
112   smbc_closedir(fd);
113   lock.Leave();
114
115   for (size_t i=0; i<vecEntries.size(); i++)
116   {
117     CachedDirEntry aDir = vecEntries[i];
118
119     // We use UTF-8 internally, as does SMB
120     strFile = aDir.name;
121
122     if (!strFile.Equals(".") && !strFile.Equals("..")
123       && !strFile.Equals("lost+found") && !strFile.empty()
124       && aDir.type != SMBC_PRINTER_SHARE && aDir.type != SMBC_IPC_SHARE)
125     {
126      int64_t iSize = 0;
127       bool bIsDir = true;
128       int64_t lTimeDate = 0;
129       bool hidden = false;
130
131       if(StringUtils::EndsWith(strFile, "$") && aDir.type == SMBC_FILE_SHARE )
132         continue;
133
134       // only stat files that can give proper responses
135       if ( aDir.type == SMBC_FILE ||
136            aDir.type == SMBC_DIR )
137       {
138         // set this here to if the stat should fail
139         bIsDir = (aDir.type == SMBC_DIR);
140
141         struct stat info = {0};
142         if ((m_flags & DIR_FLAG_NO_FILE_INFO)==0 && g_advancedSettings.m_sambastatfiles)
143         {
144           // make sure we use the authenticated path wich contains any default username
145           const CStdString strFullName = strAuth + smb.URLEncode(strFile);
146
147           lock.Enter();
148
149           if( smbc_stat(strFullName.c_str(), &info) == 0 )
150           {
151
152             char value[20];
153             // We poll for extended attributes which symbolizes bits but split up into a string. Where 0x02 is hidden and 0x12 is hidden directory.
154             // According to the libsmbclient.h it's supposed to return 0 if ok, or the length of the string. It seems always to return the length wich is 4
155             if (smbc_getxattr(strFullName, "system.dos_attr.mode", value, sizeof(value)) > 0)
156             {
157               long longvalue = strtol(value, NULL, 16);
158               if (longvalue & SMBC_DOS_MODE_HIDDEN)
159                 hidden = true;
160             }
161             else
162               CLog::Log(LOGERROR, "Getting extended attributes for the share: '%s'\nunix_err:'%x' error: '%s'", CURL::GetRedacted(strFullName).c_str(), errno, strerror(errno));
163
164             bIsDir = (info.st_mode & S_IFDIR) ? true : false;
165             lTimeDate = info.st_mtime;
166             if(lTimeDate == 0) // if modification date is missing, use create date
167               lTimeDate = info.st_ctime;
168             iSize = info.st_size;
169           }
170           else
171             CLog::Log(LOGERROR, "%s - Failed to stat file %s", __FUNCTION__, CURL::GetRedacted(strFullName).c_str());
172
173           lock.Leave();
174         }
175       }
176
177       FILETIME fileTime, localTime;
178       LONGLONG ll = Int32x32To64(lTimeDate & 0xffffffff, 10000000) + 116444736000000000ll;
179       fileTime.dwLowDateTime = (DWORD) (ll & 0xffffffff);
180       fileTime.dwHighDateTime = (DWORD)(ll >> 32);
181       FileTimeToLocalFileTime(&fileTime, &localTime);
182
183       if (bIsDir)
184       {
185         CFileItemPtr pItem(new CFileItem(strFile));
186         CStdString path(strRoot);
187
188         // needed for network / workgroup browsing
189         // skip if root if we are given a server
190         if (aDir.type == SMBC_SERVER)
191         {
192           /* create url with same options, user, pass.. but no filename or host*/
193           CURL rooturl(strRoot);
194           rooturl.SetFileName("");
195           rooturl.SetHostName("");
196           path = smb.URLEncode(rooturl);
197         }
198         path = URIUtils::AddFileToFolder(path,aDir.name);
199         URIUtils::AddSlashAtEnd(path);
200         pItem->SetPath(path);
201         pItem->m_bIsFolder = true;
202         pItem->m_dateTime=localTime;
203         if (hidden)
204           pItem->SetProperty("file:hidden", true);
205         items.Add(pItem);
206       }
207       else
208       {
209         CFileItemPtr pItem(new CFileItem(strFile));
210         pItem->SetPath(strRoot + aDir.name);
211         pItem->m_bIsFolder = false;
212         pItem->m_dwSize = iSize;
213         pItem->m_dateTime=localTime;
214         if (hidden)
215           pItem->SetProperty("file:hidden", true);
216         items.Add(pItem);
217       }
218     }
219   }
220
221   return true;
222 }
223
224 int CSMBDirectory::Open(const CURL &url)
225 {
226   smb.Init();
227   CStdString strAuth;
228   return OpenDir(url, strAuth);
229 }
230
231 /// \brief Checks authentication against SAMBA share and prompts for username and password if needed
232 /// \param strAuth The SMB style path
233 /// \return SMB file descriptor
234 int CSMBDirectory::OpenDir(const CURL& url, CStdString& strAuth)
235 {
236   int fd = -1;
237
238   /* make a writeable copy */
239   CURL urlIn(url);
240
241   CPasswordManager::GetInstance().AuthenticateURL(urlIn);
242   strAuth = smb.URLEncode(urlIn);
243
244   // remove the / or \ at the end. the samba library does not strip them off
245   // don't do this for smb:// !!
246   std::string s = strAuth;
247   int len = s.length();
248   if (len > 1 && s.at(len - 2) != '/' &&
249       (s.at(len - 1) == '/' || s.at(len - 1) == '\\'))
250   {
251     s.erase(len - 1, 1);
252   }
253
254   CLog::Log(LOGDEBUG, "%s - Using authentication url %s", __FUNCTION__, CURL::GetRedacted(s).c_str());
255   { CSingleLock lock(smb);
256     fd = smbc_opendir(s.c_str());
257   }
258
259   while (fd < 0) /* only to avoid goto in following code */
260   {
261     CStdString cError;
262
263     if (errno == EACCES)
264     {
265       if (m_flags & DIR_FLAG_ALLOW_PROMPT)
266         RequireAuthentication(urlIn.Get());
267       break;
268     }
269
270     if (errno == ENODEV || errno == ENOENT)
271       cError = StringUtils::Format(g_localizeStrings.Get(770).c_str(),errno);
272     else
273       cError = strerror(errno);
274
275     if (m_flags & DIR_FLAG_ALLOW_PROMPT)
276       SetErrorDialog(257, cError.c_str());
277     break;
278   }
279
280   if (fd < 0)
281   {
282     // write error to logfile
283     CLog::Log(LOGERROR, "SMBDirectory->GetDirectory: Unable to open directory : '%s'\nunix_err:'%x' error : '%s'", CURL::GetRedacted(strAuth).c_str(), errno, strerror(errno));
284   }
285
286   return fd;
287 }
288
289 bool CSMBDirectory::Create(const char* strPath)
290 {
291   bool success = true;
292   CSingleLock lock(smb);
293   smb.Init();
294
295   CURL url(strPath);
296   CPasswordManager::GetInstance().AuthenticateURL(url);
297   CStdString strFileName = smb.URLEncode(url);
298
299   int result = smbc_mkdir(strFileName.c_str(), 0);
300   success = (result == 0 || EEXIST == errno);
301   if(!success)
302     CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, strerror(errno));
303
304   return success;
305 }
306
307 bool CSMBDirectory::Remove(const char* strPath)
308 {
309   CSingleLock lock(smb);
310   smb.Init();
311
312   CURL url(strPath);
313   CPasswordManager::GetInstance().AuthenticateURL(url);
314   CStdString strFileName = smb.URLEncode(url);
315
316   int result = smbc_rmdir(strFileName.c_str());
317
318   if(result != 0 && errno != ENOENT)
319   {
320     CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, strerror(errno));
321     return false;
322   }
323
324   return true;
325 }
326
327 bool CSMBDirectory::Exists(const char* strPath)
328 {
329   CSingleLock lock(smb);
330   smb.Init();
331
332   CURL url(strPath);
333   CPasswordManager::GetInstance().AuthenticateURL(url);
334   CStdString strFileName = smb.URLEncode(url);
335
336   struct stat info;
337   if (smbc_stat(strFileName.c_str(), &info) != 0)
338     return false;
339
340   return (info.st_mode & S_IFDIR) ? true : false;
341 }
342
343 CStdString CSMBDirectory::MountShare(const CStdString &smbPath, const CStdString &strType, const CStdString &strName,
344     const CStdString &strUser, const CStdString &strPass)
345 {
346   UnMountShare(strType, strName);
347
348   CStdString strMountPoint = GetMountPoint(strType, strName);
349
350 #if defined(TARGET_DARWIN)
351   // Create the directory.
352   strMountPoint = CURL::Decode(strMountPoint);
353   CreateDirectory(strMountPoint, NULL);
354
355   // Massage the path.
356   CStdString smbFullPath = "//";
357   if (smbFullPath.length() > 0)
358   {
359     smbFullPath += strUser;
360     if (strPass.length() > 0)
361       smbFullPath += ":" + strPass;
362
363     smbFullPath += "@";
364   }
365
366   CStdString newPath = smbPath;
367   StringUtils::TrimLeft(newPath, "/");
368   smbFullPath += newPath;
369
370   // Make the mount command.
371   CStdStringArray args;
372   args.push_back("/sbin/mount_smbfs");
373   args.push_back("-o");
374   args.push_back("nobrowse");
375   args.push_back(smbFullPath);
376   args.push_back(strMountPoint);
377
378   // Execute it.
379   if (CUtil::Command(args))
380     return strMountPoint;
381 #else
382   CUtil::SudoCommand("mkdir -p " + strMountPoint);
383
384   CStdString strCmd = "mount -t cifs " + smbPath + " " + strMountPoint +
385     " -o rw,nobrl,directio";
386   if (!strUser.empty())
387     strCmd += ",user=" + strUser + ",password=" + strPass;
388   else
389     strCmd += ",guest";
390
391   if (CUtil::SudoCommand(strCmd))
392     return strMountPoint;
393 #endif
394   return StringUtils::EmptyString;
395 }
396
397 void CSMBDirectory::UnMountShare(const CStdString &strType, const CStdString &strName)
398 {
399 #if defined(TARGET_DARWIN)
400   // Decode the path.
401   CStdString strMountPoint(CURL::Decode(GetMountPoint(strType, strName)));
402
403   // Make the unmount command.
404   CStdStringArray args;
405   args.push_back("/sbin/umount");
406   args.push_back(strMountPoint);
407
408   // Execute command.
409   CUtil::Command(args);
410 #else
411   CStdString strCmd = "umount " + GetMountPoint(strType, strName);
412   CUtil::SudoCommand(strCmd);
413 #endif
414 }
415
416 CStdString CSMBDirectory::GetMountPoint(const CStdString &strType, const CStdString &strName)
417 {
418   CStdString strPath(CURL::Encode(strType + strName));
419
420 #if defined(TARGET_DARWIN)
421   CStdString str = getenv("HOME");
422   return str + "/" + XBMC_SMB_MOUNT_PATH + strPath;
423 #else
424   return XBMC_SMB_MOUNT_PATH + strPath;
425 #endif
426 }