2 * Copyright (C) 2005-2013 Team XBMC
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)
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.
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/>.
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.
29 * debugging is set to a max of 10 for release builds (see local.h)
34 #include "SMBDirectory.h"
36 #include "guilib/LocalizeStrings.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"
45 #include <libsmbclient.h>
47 #if defined(TARGET_DARWIN)
48 #define XBMC_SMB_MOUNT_PATH "Library/Application Support/XBMC/Mounts/"
50 #define XBMC_SMB_MOUNT_PATH "/media/xbmc/smb/"
59 using namespace XFILE;
62 CSMBDirectory::CSMBDirectory(void)
65 smb.AddActiveConnection();
69 CSMBDirectory::~CSMBDirectory(void)
72 smb.AddIdleConnection();
76 bool CSMBDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items)
78 // We accept smb://[[[domain;]user[:password@]]server[/share[/path[/file]]]]
80 /* samba isn't thread safe with old interface, always lock */
81 CSingleLock lock(smb);
85 /* we need an url to do proper escaping */
88 //Separate roots for the authentication and the containing items to allow browsing to work correctly
89 CStdString strRoot = strPath;
92 lock.Leave(); // OpenDir is locked
93 int fd = OpenDir(url, strAuth);
97 URIUtils::AddSlashAtEnd(strRoot);
98 URIUtils::AddSlashAtEnd(strAuth);
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;
109 while ((dirEnt = smbc_readdir(fd)))
112 aDir.type = dirEnt->smbc_type;
113 aDir.name = dirEnt->name;
114 vecEntries.push_back(aDir);
119 for (size_t i=0; i<vecEntries.size(); i++)
121 CachedDirEntry aDir = vecEntries[i];
123 // We use UTF-8 internally, as does SMB
126 if (!strFile.Equals(".") && !strFile.Equals("..")
127 && !strFile.Equals("lost+found") && !strFile.empty()
128 && aDir.type != SMBC_PRINTER_SHARE && aDir.type != SMBC_IPC_SHARE)
132 int64_t lTimeDate = 0;
135 if(StringUtils::EndsWith(strFile, "$") && aDir.type == SMBC_FILE_SHARE )
138 // only stat files that can give proper responses
139 if ( aDir.type == SMBC_FILE ||
140 aDir.type == SMBC_DIR )
142 // set this here to if the stat should fail
143 bIsDir = (aDir.type == SMBC_DIR);
145 #ifdef TARGET_WINDOWS
146 struct __stat64 info = {0};
148 struct stat info = {0};
150 if ((m_flags & DIR_FLAG_NO_FILE_INFO)==0 && g_advancedSettings.m_sambastatfiles)
152 // make sure we use the authenticated path wich contains any default username
153 const CStdString strFullName = strAuth + smb.URLEncode(strFile);
157 if( smbc_stat(strFullName.c_str(), &info) == 0 )
160 #ifdef TARGET_WINDOWS
161 if ((info.st_mode & S_IXOTH))
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)
169 long longvalue = strtol(value, NULL, 16);
170 if (longvalue & SMBC_DOS_MODE_HIDDEN)
174 CLog::Log(LOGERROR, "Getting extended attributes for the share: '%s'\nunix_err:'%x' error: '%s'", CURL::GetRedacted(strFullName).c_str(), errno, strerror(errno));
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;
184 CLog::Log(LOGERROR, "%s - Failed to stat file %s", __FUNCTION__, CURL::GetRedacted(strFullName).c_str());
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);
198 CFileItemPtr pItem(new CFileItem(strFile));
199 CStdString path(strRoot);
201 // needed for network / workgroup browsing
202 // skip if root if we are given a server
203 if (aDir.type == SMBC_SERVER)
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);
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;
217 pItem->SetProperty("file:hidden", true);
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;
228 pItem->SetProperty("file:hidden", true);
237 int CSMBDirectory::Open(const CURL &url)
241 return OpenDir(url, strAuth);
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)
250 #ifdef TARGET_WINDOWS
254 /* make a writeable copy */
257 CPasswordManager::GetInstance().AuthenticateURL(urlIn);
258 strAuth = smb.URLEncode(urlIn);
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) == '\\'))
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());
275 while (fd < 0) /* only to avoid goto in following code */
279 #ifdef TARGET_WINDOWS
280 nt_error = smb.ConvertUnixToNT(errno);
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
285 if (nt_error == NT_STATUS_INVALID_HANDLE)
288 if (nt_error == NT_STATUS_ACCESS_DENIED)
290 if (m_flags & DIR_FLAG_ALLOW_PROMPT)
291 RequireAuthentication(urlIn.Get());
295 if (nt_error == NT_STATUS_OBJECT_NAME_NOT_FOUND)
296 cError = StringUtils::Format(g_localizeStrings.Get(770).c_str(),nt_error);
298 cError = get_friendly_nt_error_msg(nt_error);
304 if (m_flags & DIR_FLAG_ALLOW_PROMPT)
305 RequireAuthentication(urlIn.Get());
309 if (errno == ENODEV || errno == ENOENT)
310 cError = StringUtils::Format(g_localizeStrings.Get(770).c_str(),errno);
312 cError = strerror(errno);
316 if (m_flags & DIR_FLAG_ALLOW_PROMPT)
317 SetErrorDialog(257, cError.c_str());
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));
327 CLog::Log(LOGERROR, "SMBDirectory->GetDirectory: Unable to open directory : '%s'\nunix_err:'%x' error : '%s'", CURL::GetRedacted(strAuth).c_str(), errno, strerror(errno));
334 bool CSMBDirectory::Create(const char* strPath)
337 CSingleLock lock(smb);
341 CPasswordManager::GetInstance().AuthenticateURL(url);
342 CStdString strFileName = smb.URLEncode(url);
344 int result = smbc_mkdir(strFileName.c_str(), 0);
345 success = (result == 0 || EEXIST == errno);
347 #ifdef TARGET_WINDOWS
348 CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, get_friendly_nt_error_msg(smb.ConvertUnixToNT(errno)));
350 CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, strerror(errno));
356 bool CSMBDirectory::Remove(const char* strPath)
358 CSingleLock lock(smb);
362 CPasswordManager::GetInstance().AuthenticateURL(url);
363 CStdString strFileName = smb.URLEncode(url);
365 int result = smbc_rmdir(strFileName.c_str());
367 if(result != 0 && errno != ENOENT)
369 #ifdef TARGET_WINDOWS
370 CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, get_friendly_nt_error_msg(smb.ConvertUnixToNT(errno)));
372 CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, strerror(errno));
380 bool CSMBDirectory::Exists(const char* strPath)
382 CSingleLock lock(smb);
386 CPasswordManager::GetInstance().AuthenticateURL(url);
387 CStdString strFileName = smb.URLEncode(url);
389 #ifdef TARGET_WINDOWS
390 SMB_STRUCT_STAT info;
394 if (smbc_stat(strFileName.c_str(), &info) != 0)
397 return (info.st_mode & S_IFDIR) ? true : false;
400 CStdString CSMBDirectory::MountShare(const CStdString &smbPath, const CStdString &strType, const CStdString &strName,
401 const CStdString &strUser, const CStdString &strPass)
404 UnMountShare(strType, strName);
406 CStdString strMountPoint = GetMountPoint(strType, strName);
408 #if defined(TARGET_DARWIN)
409 // Create the directory.
410 CURL::Decode(strMountPoint);
411 CreateDirectory(strMountPoint, NULL);
414 CStdString smbFullPath = "//";
415 if (smbFullPath.length() > 0)
417 smbFullPath += strUser;
418 if (strPass.length() > 0)
419 smbFullPath += ":" + strPass;
424 CStdString newPath = smbPath;
425 StringUtils::TrimLeft(newPath, "/");
426 smbFullPath += newPath;
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);
437 if (CUtil::Command(args))
438 return strMountPoint;
440 CUtil::SudoCommand("mkdir -p " + strMountPoint);
442 CStdString strCmd = "mount -t cifs " + smbPath + " " + strMountPoint +
443 " -o rw,nobrl,directio";
444 if (!strUser.empty())
445 strCmd += ",user=" + strUser + ",password=" + strPass;
449 if (CUtil::SudoCommand(strCmd))
450 return strMountPoint;
453 return StringUtils::EmptyString;
456 void CSMBDirectory::UnMountShare(const CStdString &strType, const CStdString &strName)
458 #if defined(TARGET_DARWIN)
460 CStdString strMountPoint = GetMountPoint(strType, strName);
461 CURL::Decode(strMountPoint);
463 // Make the unmount command.
464 CStdStringArray args;
465 args.push_back("/sbin/umount");
466 args.push_back(strMountPoint);
469 CUtil::Command(args);
470 #elif defined(TARGET_POSIX)
471 CStdString strCmd = "umount " + GetMountPoint(strType, strName);
472 CUtil::SudoCommand(strCmd);
476 CStdString CSMBDirectory::GetMountPoint(const CStdString &strType, const CStdString &strName)
478 CStdString strPath = strType + strName;
479 CURL::Encode(strPath);
481 #if defined(TARGET_DARWIN)
482 CStdString str = getenv("HOME");
483 return str + "/" + XBMC_SMB_MOUNT_PATH + strPath;
485 return XBMC_SMB_MOUNT_PATH + strPath;