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