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)
64 smb.AddActiveConnection();
67 CSMBDirectory::~CSMBDirectory(void)
69 smb.AddIdleConnection();
72 bool CSMBDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items)
74 // We accept smb://[[[domain;]user[:password@]]server[/share[/path[/file]]]]
76 /* samba isn't thread safe with old interface, always lock */
77 CSingleLock lock(smb);
81 /* we need an url to do proper escaping */
84 //Separate roots for the authentication and the containing items to allow browsing to work correctly
85 CStdString strRoot = strPath;
88 lock.Leave(); // OpenDir is locked
89 int fd = OpenDir(url, strAuth);
93 URIUtils::AddSlashAtEnd(strRoot);
94 URIUtils::AddSlashAtEnd(strAuth);
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;
105 while ((dirEnt = smbc_readdir(fd)))
108 aDir.type = dirEnt->smbc_type;
109 aDir.name = dirEnt->name;
110 vecEntries.push_back(aDir);
115 for (size_t i=0; i<vecEntries.size(); i++)
117 CachedDirEntry aDir = vecEntries[i];
119 // We use UTF-8 internally, as does SMB
122 if (!strFile.Equals(".") && !strFile.Equals("..")
123 && !strFile.Equals("lost+found") && !strFile.empty()
124 && aDir.type != SMBC_PRINTER_SHARE && aDir.type != SMBC_IPC_SHARE)
128 int64_t lTimeDate = 0;
131 if(StringUtils::EndsWith(strFile, "$") && aDir.type == SMBC_FILE_SHARE )
134 // only stat files that can give proper responses
135 if ( aDir.type == SMBC_FILE ||
136 aDir.type == SMBC_DIR )
138 // set this here to if the stat should fail
139 bIsDir = (aDir.type == SMBC_DIR);
141 struct stat info = {0};
142 if ((m_flags & DIR_FLAG_NO_FILE_INFO)==0 && g_advancedSettings.m_sambastatfiles)
144 // make sure we use the authenticated path wich contains any default username
145 const CStdString strFullName = strAuth + smb.URLEncode(strFile);
149 if( smbc_stat(strFullName.c_str(), &info) == 0 )
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)
157 long longvalue = strtol(value, NULL, 16);
158 if (longvalue & SMBC_DOS_MODE_HIDDEN)
162 CLog::Log(LOGERROR, "Getting extended attributes for the share: '%s'\nunix_err:'%x' error: '%s'", CURL::GetRedacted(strFullName).c_str(), errno, strerror(errno));
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;
171 CLog::Log(LOGERROR, "%s - Failed to stat file %s", __FUNCTION__, CURL::GetRedacted(strFullName).c_str());
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);
185 CFileItemPtr pItem(new CFileItem(strFile));
186 CStdString path(strRoot);
188 // needed for network / workgroup browsing
189 // skip if root if we are given a server
190 if (aDir.type == SMBC_SERVER)
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);
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;
204 pItem->SetProperty("file:hidden", true);
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;
215 pItem->SetProperty("file:hidden", true);
224 int CSMBDirectory::Open(const CURL &url)
228 return OpenDir(url, strAuth);
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)
238 /* make a writeable copy */
241 CPasswordManager::GetInstance().AuthenticateURL(urlIn);
242 strAuth = smb.URLEncode(urlIn);
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) == '\\'))
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());
259 while (fd < 0) /* only to avoid goto in following code */
265 if (m_flags & DIR_FLAG_ALLOW_PROMPT)
266 RequireAuthentication(urlIn.Get());
270 if (errno == ENODEV || errno == ENOENT)
271 cError = StringUtils::Format(g_localizeStrings.Get(770).c_str(),errno);
273 cError = strerror(errno);
275 if (m_flags & DIR_FLAG_ALLOW_PROMPT)
276 SetErrorDialog(257, cError.c_str());
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));
289 bool CSMBDirectory::Create(const char* strPath)
292 CSingleLock lock(smb);
296 CPasswordManager::GetInstance().AuthenticateURL(url);
297 CStdString strFileName = smb.URLEncode(url);
299 int result = smbc_mkdir(strFileName.c_str(), 0);
300 success = (result == 0 || EEXIST == errno);
302 CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, strerror(errno));
307 bool CSMBDirectory::Remove(const char* strPath)
309 CSingleLock lock(smb);
313 CPasswordManager::GetInstance().AuthenticateURL(url);
314 CStdString strFileName = smb.URLEncode(url);
316 int result = smbc_rmdir(strFileName.c_str());
318 if(result != 0 && errno != ENOENT)
320 CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, strerror(errno));
327 bool CSMBDirectory::Exists(const char* strPath)
329 CSingleLock lock(smb);
333 CPasswordManager::GetInstance().AuthenticateURL(url);
334 CStdString strFileName = smb.URLEncode(url);
337 if (smbc_stat(strFileName.c_str(), &info) != 0)
340 return (info.st_mode & S_IFDIR) ? true : false;
343 CStdString CSMBDirectory::MountShare(const CStdString &smbPath, const CStdString &strType, const CStdString &strName,
344 const CStdString &strUser, const CStdString &strPass)
346 UnMountShare(strType, strName);
348 CStdString strMountPoint = GetMountPoint(strType, strName);
350 #if defined(TARGET_DARWIN)
351 // Create the directory.
352 strMountPoint = CURL::Decode(strMountPoint);
353 CreateDirectory(strMountPoint, NULL);
356 CStdString smbFullPath = "//";
357 if (smbFullPath.length() > 0)
359 smbFullPath += strUser;
360 if (strPass.length() > 0)
361 smbFullPath += ":" + strPass;
366 CStdString newPath = smbPath;
367 StringUtils::TrimLeft(newPath, "/");
368 smbFullPath += newPath;
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);
379 if (CUtil::Command(args))
380 return strMountPoint;
382 CUtil::SudoCommand("mkdir -p " + strMountPoint);
384 CStdString strCmd = "mount -t cifs " + smbPath + " " + strMountPoint +
385 " -o rw,nobrl,directio";
386 if (!strUser.empty())
387 strCmd += ",user=" + strUser + ",password=" + strPass;
391 if (CUtil::SudoCommand(strCmd))
392 return strMountPoint;
394 return StringUtils::EmptyString;
397 void CSMBDirectory::UnMountShare(const CStdString &strType, const CStdString &strName)
399 #if defined(TARGET_DARWIN)
401 CStdString strMountPoint(CURL::Decode(GetMountPoint(strType, strName)));
403 // Make the unmount command.
404 CStdStringArray args;
405 args.push_back("/sbin/umount");
406 args.push_back(strMountPoint);
409 CUtil::Command(args);
411 CStdString strCmd = "umount " + GetMountPoint(strType, strName);
412 CUtil::SudoCommand(strCmd);
416 CStdString CSMBDirectory::GetMountPoint(const CStdString &strType, const CStdString &strName)
418 CStdString strPath(CURL::Encode(strType + strName));
420 #if defined(TARGET_DARWIN)
421 CStdString str = getenv("HOME");
422 return str + "/" + XBMC_SMB_MOUNT_PATH + strPath;
424 return XBMC_SMB_MOUNT_PATH + strPath;