3 * Copyright (C) 2005-2013 Team XBMC
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)
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.
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/>.
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.
30 * debugging is set to a max of 10 for release builds (see local.h)
35 #include "SMBDirectory.h"
37 #include "guilib/LocalizeStrings.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"
46 #include <libsmbclient.h>
48 #if defined(TARGET_DARWIN)
49 #define XBMC_SMB_MOUNT_PATH "Library/Application Support/XBMC/Mounts/"
51 #define XBMC_SMB_MOUNT_PATH "/media/xbmc/smb/"
60 using namespace XFILE;
63 CSMBDirectory::CSMBDirectory(void)
66 smb.AddActiveConnection();
70 CSMBDirectory::~CSMBDirectory(void)
73 smb.AddIdleConnection();
77 bool CSMBDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items)
79 // We accept smb://[[[domain;]user[:password@]]server[/share[/path[/file]]]]
81 /* samba isn't thread safe with old interface, always lock */
82 CSingleLock lock(smb);
86 /* we need an url to do proper escaping */
89 //Separate roots for the authentication and the containing items to allow browsing to work correctly
90 CStdString strRoot = strPath;
93 lock.Leave(); // OpenDir is locked
94 int fd = OpenDir(url, strAuth);
98 URIUtils::AddSlashAtEnd(strRoot);
99 URIUtils::AddSlashAtEnd(strAuth);
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;
110 while ((dirEnt = smbc_readdir(fd)))
113 aDir.type = dirEnt->smbc_type;
114 aDir.name = dirEnt->name;
115 vecEntries.push_back(aDir);
120 for (size_t i=0; i<vecEntries.size(); i++)
122 CachedDirEntry aDir = vecEntries[i];
124 // We use UTF-8 internally, as does SMB
127 if (!strFile.Equals(".") && !strFile.Equals("..")
128 && !strFile.Equals("lost+found") && !strFile.empty()
129 && aDir.type != SMBC_PRINTER_SHARE && aDir.type != SMBC_IPC_SHARE)
133 int64_t lTimeDate = 0;
136 if(StringUtils::EndsWith(strFile, "$") && aDir.type == SMBC_FILE_SHARE )
139 // only stat files that can give proper responses
140 if ( aDir.type == SMBC_FILE ||
141 aDir.type == SMBC_DIR )
143 // set this here to if the stat should fail
144 bIsDir = (aDir.type == SMBC_DIR);
146 #ifdef TARGET_WINDOWS
147 struct __stat64 info = {0};
149 struct stat info = {0};
151 if ((m_flags & DIR_FLAG_NO_FILE_INFO)==0 && g_advancedSettings.m_sambastatfiles)
153 // make sure we use the authenticated path wich contains any default username
154 CStdString strFullName = strAuth + smb.URLEncode(strFile);
158 if( smbc_stat(strFullName.c_str(), &info) == 0 )
161 #ifdef TARGET_WINDOWS
162 if ((info.st_mode & S_IXOTH))
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)
170 long longvalue = strtol(value, NULL, 16);
171 if (longvalue & SMBC_DOS_MODE_HIDDEN)
175 CLog::Log(LOGERROR, "Getting extended attributes for the share: '%s'\nunix_err:'%x' error: '%s'", strFullName.c_str(), errno, strerror(errno));
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;
185 CLog::Log(LOGERROR, "%s - Failed to stat file %s", __FUNCTION__, strFullName.c_str());
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);
199 CFileItemPtr pItem(new CFileItem(strFile));
200 CStdString path(strRoot);
202 // needed for network / workgroup browsing
203 // skip if root if we are given a server
204 if (aDir.type == SMBC_SERVER)
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);
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;
218 pItem->SetProperty("file:hidden", true);
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;
229 pItem->SetProperty("file:hidden", true);
238 int CSMBDirectory::Open(const CURL &url)
242 return OpenDir(url, strAuth);
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)
251 #ifdef TARGET_WINDOWS
255 /* make a writeable copy */
258 CPasswordManager::GetInstance().AuthenticateURL(urlIn);
259 strAuth = smb.URLEncode(urlIn);
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) == '\\'))
271 CLog::Log(LOGDEBUG, "%s - Using authentication url %s", __FUNCTION__, s.c_str());
272 { CSingleLock lock(smb);
273 fd = smbc_opendir(s.c_str());
276 while (fd < 0) /* only to avoid goto in following code */
280 #ifdef TARGET_WINDOWS
281 nt_error = smb.ConvertUnixToNT(errno);
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
286 if (nt_error == NT_STATUS_INVALID_HANDLE)
289 if (nt_error == NT_STATUS_ACCESS_DENIED)
291 if (m_flags & DIR_FLAG_ALLOW_PROMPT)
292 RequireAuthentication(urlIn.Get());
296 if (nt_error == NT_STATUS_OBJECT_NAME_NOT_FOUND)
297 cError.Format(g_localizeStrings.Get(770).c_str(),nt_error);
299 cError = get_friendly_nt_error_msg(nt_error);
305 if (m_flags & DIR_FLAG_ALLOW_PROMPT)
306 RequireAuthentication(urlIn.Get());
310 if (errno == ENODEV || errno == ENOENT)
311 cError.Format(g_localizeStrings.Get(770).c_str(),errno);
313 cError = strerror(errno);
317 if (m_flags & DIR_FLAG_ALLOW_PROMPT)
318 SetErrorDialog(257, cError.c_str());
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));
328 CLog::Log(LOGERROR, "SMBDirectory->GetDirectory: Unable to open directory : '%s'\nunix_err:'%x' error : '%s'", strAuth.c_str(), errno, strerror(errno));
335 bool CSMBDirectory::Create(const char* strPath)
338 CSingleLock lock(smb);
342 CPasswordManager::GetInstance().AuthenticateURL(url);
343 CStdString strFileName = smb.URLEncode(url);
345 int result = smbc_mkdir(strFileName.c_str(), 0);
346 success = (result == 0 || EEXIST == errno);
348 #ifdef TARGET_WINDOWS
349 CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, get_friendly_nt_error_msg(smb.ConvertUnixToNT(errno)));
351 CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, strerror(errno));
357 bool CSMBDirectory::Remove(const char* strPath)
359 CSingleLock lock(smb);
363 CPasswordManager::GetInstance().AuthenticateURL(url);
364 CStdString strFileName = smb.URLEncode(url);
366 int result = smbc_rmdir(strFileName.c_str());
368 if(result != 0 && errno != ENOENT)
370 #ifdef TARGET_WINDOWS
371 CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, get_friendly_nt_error_msg(smb.ConvertUnixToNT(errno)));
373 CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, strerror(errno));
381 bool CSMBDirectory::Exists(const char* strPath)
383 CSingleLock lock(smb);
387 CPasswordManager::GetInstance().AuthenticateURL(url);
388 CStdString strFileName = smb.URLEncode(url);
390 #ifdef TARGET_WINDOWS
391 SMB_STRUCT_STAT info;
395 if (smbc_stat(strFileName.c_str(), &info) != 0)
398 return (info.st_mode & S_IFDIR) ? true : false;
401 CStdString CSMBDirectory::MountShare(const CStdString &smbPath, const CStdString &strType, const CStdString &strName,
402 const CStdString &strUser, const CStdString &strPass)
405 UnMountShare(strType, strName);
407 CStdString strMountPoint = GetMountPoint(strType, strName);
409 #if defined(TARGET_DARWIN)
410 // Create the directory.
411 CURL::Decode(strMountPoint);
412 CreateDirectory(strMountPoint, NULL);
415 CStdString smbFullPath = "//";
416 if (smbFullPath.length() > 0)
418 smbFullPath += strUser;
419 if (strPass.length() > 0)
420 smbFullPath += ":" + strPass;
425 CStdString newPath = smbPath;
426 newPath.TrimLeft("/");
427 smbFullPath += newPath;
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);
438 if (CUtil::Command(args))
439 return strMountPoint;
441 CUtil::SudoCommand("mkdir -p " + strMountPoint);
443 CStdString strCmd = "mount -t cifs " + smbPath + " " + strMountPoint +
444 " -o rw,nobrl,directio";
445 if (!strUser.IsEmpty())
446 strCmd += ",user=" + strUser + ",password=" + strPass;
450 if (CUtil::SudoCommand(strCmd))
451 return strMountPoint;
454 return StringUtils::EmptyString;
457 void CSMBDirectory::UnMountShare(const CStdString &strType, const CStdString &strName)
459 #if defined(TARGET_DARWIN)
461 CStdString strMountPoint = GetMountPoint(strType, strName);
462 CURL::Decode(strMountPoint);
464 // Make the unmount command.
465 CStdStringArray args;
466 args.push_back("/sbin/umount");
467 args.push_back(strMountPoint);
470 CUtil::Command(args);
471 #elif defined(TARGET_POSIX)
472 CStdString strCmd = "umount " + GetMountPoint(strType, strName);
473 CUtil::SudoCommand(strCmd);
477 CStdString CSMBDirectory::GetMountPoint(const CStdString &strType, const CStdString &strName)
479 CStdString strPath = strType + strName;
480 CURL::Encode(strPath);
482 #if defined(TARGET_DARWIN)
483 CStdString str = getenv("HOME");
484 return str + "/" + XBMC_SMB_MOUNT_PATH + strPath;
486 return XBMC_SMB_MOUNT_PATH + strPath;