[cstdstring] removal of Trim/TrimLeft/TrimRight
[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 #ifdef TARGET_POSIX
65   smb.AddActiveConnection();
66 #endif
67 }
68
69 CSMBDirectory::~CSMBDirectory(void)
70 {
71 #ifdef TARGET_POSIX
72   smb.AddIdleConnection();
73 #endif
74 }
75
76 bool CSMBDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items)
77 {
78   // We accept smb://[[[domain;]user[:password@]]server[/share[/path[/file]]]]
79
80   /* samba isn't thread safe with old interface, always lock */
81   CSingleLock lock(smb);
82
83   smb.Init();
84
85   /* we need an url to do proper escaping */
86   CURL url(strPath);
87
88   //Separate roots for the authentication and the containing items to allow browsing to work correctly
89   CStdString strRoot = strPath;
90   CStdString strAuth;
91
92   lock.Leave(); // OpenDir is locked
93   int fd = OpenDir(url, strAuth);
94   if (fd < 0)
95     return false;
96
97   URIUtils::AddSlashAtEnd(strRoot);
98   URIUtils::AddSlashAtEnd(strAuth);
99
100   CStdString strFile;
101
102   // need to keep the samba lock for as short as possible.
103   // so we first cache all directory entries and then go over them again asking for stat
104   // "stat" is locked each time. that way the lock is freed between stat requests
105   vector<CachedDirEntry> vecEntries;
106   struct smbc_dirent* dirEnt;
107
108   lock.Enter();
109   while ((dirEnt = smbc_readdir(fd)))
110   {
111     CachedDirEntry aDir;
112     aDir.type = dirEnt->smbc_type;
113     aDir.name = dirEnt->name;
114     vecEntries.push_back(aDir);
115   }
116   smbc_closedir(fd);
117   lock.Leave();
118
119   for (size_t i=0; i<vecEntries.size(); i++)
120   {
121     CachedDirEntry aDir = vecEntries[i];
122
123     // We use UTF-8 internally, as does SMB
124     strFile = aDir.name;
125
126     if (!strFile.Equals(".") && !strFile.Equals("..")
127       && !strFile.Equals("lost+found") && !strFile.empty()
128       && aDir.type != SMBC_PRINTER_SHARE && aDir.type != SMBC_IPC_SHARE)
129     {
130      int64_t iSize = 0;
131       bool bIsDir = true;
132       int64_t lTimeDate = 0;
133       bool hidden = false;
134
135       if(StringUtils::EndsWith(strFile, "$") && aDir.type == SMBC_FILE_SHARE )
136         continue;
137
138       // only stat files that can give proper responses
139       if ( aDir.type == SMBC_FILE ||
140            aDir.type == SMBC_DIR )
141       {
142         // set this here to if the stat should fail
143         bIsDir = (aDir.type == SMBC_DIR);
144
145 #ifdef TARGET_WINDOWS
146         struct __stat64 info = {0};
147 #else
148         struct stat info = {0};
149 #endif
150         if ((m_flags & DIR_FLAG_NO_FILE_INFO)==0 && g_advancedSettings.m_sambastatfiles)
151         {
152           // make sure we use the authenticated path wich contains any default username
153           const CStdString strFullName = strAuth + smb.URLEncode(strFile);
154
155           lock.Enter();
156
157           if( smbc_stat(strFullName.c_str(), &info) == 0 )
158           {
159
160 #ifdef TARGET_WINDOWS
161             if ((info.st_mode & S_IXOTH))
162               hidden = true;
163 #else
164             char value[20];
165             // We poll for extended attributes which symbolizes bits but split up into a string. Where 0x02 is hidden and 0x12 is hidden directory.
166             // 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
167             if (smbc_getxattr(strFullName, "system.dos_attr.mode", value, sizeof(value)) > 0)
168             {
169               long longvalue = strtol(value, NULL, 16);
170               if (longvalue & SMBC_DOS_MODE_HIDDEN)
171                 hidden = true;
172             }
173             else
174               CLog::Log(LOGERROR, "Getting extended attributes for the share: '%s'\nunix_err:'%x' error: '%s'", CURL::GetRedacted(strFullName).c_str(), errno, strerror(errno));
175 #endif
176
177             bIsDir = (info.st_mode & S_IFDIR) ? true : false;
178             lTimeDate = info.st_mtime;
179             if(lTimeDate == 0) // if modification date is missing, use create date
180               lTimeDate = info.st_ctime;
181             iSize = info.st_size;
182           }
183           else
184             CLog::Log(LOGERROR, "%s - Failed to stat file %s", __FUNCTION__, CURL::GetRedacted(strFullName).c_str());
185
186           lock.Leave();
187         }
188       }
189
190       FILETIME fileTime, localTime;
191       LONGLONG ll = Int32x32To64(lTimeDate & 0xffffffff, 10000000) + 116444736000000000ll;
192       fileTime.dwLowDateTime = (DWORD) (ll & 0xffffffff);
193       fileTime.dwHighDateTime = (DWORD)(ll >> 32);
194       FileTimeToLocalFileTime(&fileTime, &localTime);
195
196       if (bIsDir)
197       {
198         CFileItemPtr pItem(new CFileItem(strFile));
199         CStdString path(strRoot);
200
201         // needed for network / workgroup browsing
202         // skip if root if we are given a server
203         if (aDir.type == SMBC_SERVER)
204         {
205           /* create url with same options, user, pass.. but no filename or host*/
206           CURL rooturl(strRoot);
207           rooturl.SetFileName("");
208           rooturl.SetHostName("");
209           path = smb.URLEncode(rooturl);
210         }
211         path = URIUtils::AddFileToFolder(path,aDir.name);
212         URIUtils::AddSlashAtEnd(path);
213         pItem->SetPath(path);
214         pItem->m_bIsFolder = true;
215         pItem->m_dateTime=localTime;
216         if (hidden)
217           pItem->SetProperty("file:hidden", true);
218         items.Add(pItem);
219       }
220       else
221       {
222         CFileItemPtr pItem(new CFileItem(strFile));
223         pItem->SetPath(strRoot + aDir.name);
224         pItem->m_bIsFolder = false;
225         pItem->m_dwSize = iSize;
226         pItem->m_dateTime=localTime;
227         if (hidden)
228           pItem->SetProperty("file:hidden", true);
229         items.Add(pItem);
230       }
231     }
232   }
233
234   return true;
235 }
236
237 int CSMBDirectory::Open(const CURL &url)
238 {
239   smb.Init();
240   CStdString strAuth;
241   return OpenDir(url, strAuth);
242 }
243
244 /// \brief Checks authentication against SAMBA share and prompts for username and password if needed
245 /// \param strAuth The SMB style path
246 /// \return SMB file descriptor
247 int CSMBDirectory::OpenDir(const CURL& url, CStdString& strAuth)
248 {
249   int fd = -1;
250 #ifdef TARGET_WINDOWS
251   int nt_error;
252 #endif
253
254   /* make a writeable copy */
255   CURL urlIn(url);
256
257   CPasswordManager::GetInstance().AuthenticateURL(urlIn);
258   strAuth = smb.URLEncode(urlIn);
259
260   // remove the / or \ at the end. the samba library does not strip them off
261   // don't do this for smb:// !!
262   std::string s = strAuth;
263   int len = s.length();
264   if (len > 1 && s.at(len - 2) != '/' &&
265       (s.at(len - 1) == '/' || s.at(len - 1) == '\\'))
266   {
267     s.erase(len - 1, 1);
268   }
269
270   CLog::Log(LOGDEBUG, "%s - Using authentication url %s", __FUNCTION__, CURL::GetRedacted(s).c_str());
271   { CSingleLock lock(smb);
272     fd = smbc_opendir(s.c_str());
273   }
274
275   while (fd < 0) /* only to avoid goto in following code */
276   {
277     CStdString cError;
278
279 #ifdef TARGET_WINDOWS
280     nt_error = smb.ConvertUnixToNT(errno);
281
282     // if we have an 'invalid handle' error we don't display the error
283     // because most of the time this means there is no cdrom in the server's
284     // cdrom drive.
285     if (nt_error == NT_STATUS_INVALID_HANDLE)
286       break;
287
288     if (nt_error == NT_STATUS_ACCESS_DENIED)
289     {
290       if (m_flags & DIR_FLAG_ALLOW_PROMPT)
291         RequireAuthentication(urlIn.Get());
292       break;
293     }
294
295     if (nt_error == NT_STATUS_OBJECT_NAME_NOT_FOUND)
296       cError = StringUtils::Format(g_localizeStrings.Get(770).c_str(),nt_error);
297     else
298       cError = get_friendly_nt_error_msg(nt_error);
299
300 #else
301
302     if (errno == EACCES)
303     {
304       if (m_flags & DIR_FLAG_ALLOW_PROMPT)
305         RequireAuthentication(urlIn.Get());
306       break;
307     }
308
309     if (errno == ENODEV || errno == ENOENT)
310       cError = StringUtils::Format(g_localizeStrings.Get(770).c_str(),errno);
311     else
312       cError = strerror(errno);
313
314 #endif
315
316     if (m_flags & DIR_FLAG_ALLOW_PROMPT)
317       SetErrorDialog(257, cError.c_str());
318     break;
319   }
320
321   if (fd < 0)
322   {
323     // write error to logfile
324 #ifdef TARGET_WINDOWS
325     CLog::Log(LOGERROR, "SMBDirectory->GetDirectory: Unable to open directory : '%s'\nunix_err:'%x' nt_err : '%x' error : '%s'", CURL::GetRedacted(strAuth).c_str(), errno, nt_error, get_friendly_nt_error_msg(nt_error));
326 #else
327     CLog::Log(LOGERROR, "SMBDirectory->GetDirectory: Unable to open directory : '%s'\nunix_err:'%x' error : '%s'", CURL::GetRedacted(strAuth).c_str(), errno, strerror(errno));
328 #endif
329   }
330
331   return fd;
332 }
333
334 bool CSMBDirectory::Create(const char* strPath)
335 {
336   bool success = true;
337   CSingleLock lock(smb);
338   smb.Init();
339
340   CURL url(strPath);
341   CPasswordManager::GetInstance().AuthenticateURL(url);
342   CStdString strFileName = smb.URLEncode(url);
343
344   int result = smbc_mkdir(strFileName.c_str(), 0);
345   success = (result == 0 || EEXIST == errno);
346   if(!success)
347 #ifdef TARGET_WINDOWS
348     CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, get_friendly_nt_error_msg(smb.ConvertUnixToNT(errno)));
349 #else
350     CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, strerror(errno));
351 #endif
352
353   return success;
354 }
355
356 bool CSMBDirectory::Remove(const char* strPath)
357 {
358   CSingleLock lock(smb);
359   smb.Init();
360
361   CURL url(strPath);
362   CPasswordManager::GetInstance().AuthenticateURL(url);
363   CStdString strFileName = smb.URLEncode(url);
364
365   int result = smbc_rmdir(strFileName.c_str());
366
367   if(result != 0 && errno != ENOENT)
368   {
369 #ifdef TARGET_WINDOWS
370     CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, get_friendly_nt_error_msg(smb.ConvertUnixToNT(errno)));
371 #else
372     CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, strerror(errno));
373 #endif
374     return false;
375   }
376
377   return true;
378 }
379
380 bool CSMBDirectory::Exists(const char* strPath)
381 {
382   CSingleLock lock(smb);
383   smb.Init();
384
385   CURL url(strPath);
386   CPasswordManager::GetInstance().AuthenticateURL(url);
387   CStdString strFileName = smb.URLEncode(url);
388
389 #ifdef TARGET_WINDOWS
390   SMB_STRUCT_STAT info;
391 #else
392   struct stat info;
393 #endif
394   if (smbc_stat(strFileName.c_str(), &info) != 0)
395     return false;
396
397   return (info.st_mode & S_IFDIR) ? true : false;
398 }
399
400 CStdString CSMBDirectory::MountShare(const CStdString &smbPath, const CStdString &strType, const CStdString &strName,
401     const CStdString &strUser, const CStdString &strPass)
402 {
403 #ifdef TARGET_POSIX
404   UnMountShare(strType, strName);
405
406   CStdString strMountPoint = GetMountPoint(strType, strName);
407
408 #if defined(TARGET_DARWIN)
409   // Create the directory.
410   CURL::Decode(strMountPoint);
411   CreateDirectory(strMountPoint, NULL);
412
413   // Massage the path.
414   CStdString smbFullPath = "//";
415   if (smbFullPath.length() > 0)
416   {
417     smbFullPath += strUser;
418     if (strPass.length() > 0)
419       smbFullPath += ":" + strPass;
420
421     smbFullPath += "@";
422   }
423
424   CStdString newPath = smbPath;
425   StringUtils::TrimLeft(newPath, "/");
426   smbFullPath += newPath;
427
428   // Make the mount command.
429   CStdStringArray args;
430   args.push_back("/sbin/mount_smbfs");
431   args.push_back("-o");
432   args.push_back("nobrowse");
433   args.push_back(smbFullPath);
434   args.push_back(strMountPoint);
435
436   // Execute it.
437   if (CUtil::Command(args))
438     return strMountPoint;
439 #else
440   CUtil::SudoCommand("mkdir -p " + strMountPoint);
441
442   CStdString strCmd = "mount -t cifs " + smbPath + " " + strMountPoint +
443     " -o rw,nobrl,directio";
444   if (!strUser.empty())
445     strCmd += ",user=" + strUser + ",password=" + strPass;
446   else
447     strCmd += ",guest";
448
449   if (CUtil::SudoCommand(strCmd))
450     return strMountPoint;
451 #endif
452 #endif
453   return StringUtils::EmptyString;
454 }
455
456 void CSMBDirectory::UnMountShare(const CStdString &strType, const CStdString &strName)
457 {
458 #if defined(TARGET_DARWIN)
459   // Decode the path.
460   CStdString strMountPoint = GetMountPoint(strType, strName);
461   CURL::Decode(strMountPoint);
462
463   // Make the unmount command.
464   CStdStringArray args;
465   args.push_back("/sbin/umount");
466   args.push_back(strMountPoint);
467
468   // Execute command.
469   CUtil::Command(args);
470 #elif defined(TARGET_POSIX)
471   CStdString strCmd = "umount " + GetMountPoint(strType, strName);
472   CUtil::SudoCommand(strCmd);
473 #endif
474 }
475
476 CStdString CSMBDirectory::GetMountPoint(const CStdString &strType, const CStdString &strName)
477 {
478   CStdString strPath = strType + strName;
479   CURL::Encode(strPath);
480
481 #if defined(TARGET_DARWIN)
482   CStdString str = getenv("HOME");
483   return str + "/" + XBMC_SMB_MOUNT_PATH + strPath;
484 #else
485   return XBMC_SMB_MOUNT_PATH + strPath;
486 #endif
487 }