This is a modified version of FileSmb.cpp for Boxee versions 0.9.6.4578 thru 0.9.11.5591, which will compile under Gentoo. If you are using any of these versions you should be able to replace the file with this one. See Howto install Boxee in Gentoo Linux for details.
- xbmc/FileSystem/FileSmb.cpp
/* * Copyright (C) 2005-2008 Team XBMC * http://www.xbmc.org * * This Program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This Program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with XBMC; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ // FileSmb.cpp: implementation of the CFileSMB class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "FileSmb.h" #include "GUIPassword.h" #include "SMBDirectory.h" #include "Util.h" #ifndef _LINUX #include "Win32Exception.h" #include "lib/libsmb/xbLibSmb.h" #else #include "../lib/libsmb/libsmbclient.h" #endif #include "../utils/Network.h" #include "Settings.h" #include "Application.h" using namespace XFILE; using namespace DIRECTORY; #define SMB_READ_CACHE_SIZE (96*1024) void xb_smbc_log(const char* msg) { CLog::Log(LOGINFO, "%s%s", "smb: ", msg); } void xb_smbc_auth(const char *srv, const char *shr, char *wg, int wglen, char *un, int unlen, char *pw, int pwlen) { return ; } smbc_get_cached_srv_fn orig_cache; SMBCSRV* xb_smbc_cache(SMBCCTX* c, const char* server, const char* share, const char* workgroup, const char* username) { return orig_cache(c, server, share, workgroup, username); } CSMB::CSMB() { m_context = NULL; } CSMB::~CSMB() { Deinit(); } void CSMB::Deinit() { CSingleLock lock(*this); CLog::Log(LOGINFO,"SMB: DEinitializing package"); /* samba goes loco if deinited while it has some files opened */ if (m_context) { try { smbc_set_context(NULL); smbc_free_context(m_context, 1); } #ifndef _LINUX catch(win32_exception e) { e.writelog(__FUNCTION__); } #else catch(...) { CLog::Log(LOGERROR,"exception on CSMB::Deinit. errno: %d", errno); } #endif m_context = NULL; } } void CSMB::Init() { CSingleLock lock(*this); static bool bInitialized = false; if (!bInitialized) { CLog::Log(LOGINFO,"SMB: initializing package"); smbc_init(xb_smbc_auth, 0); bInitialized = true; } if (!m_context) { #ifdef _WIN32 // set the log function set_log_callback(xb_smbc_log); #endif // setup our context m_context = smbc_new_context(); #ifdef _LINUX // smbc_setDebug(m_context, 0); // smbc_setFunctionAuthData(m_context, xb_smbc_auth); // orig_cache = smbc_getFunctionGetCachedServer(m_context); // smbc_setFunctionGetCachedServer(m_context, xb_smbc_cache); // smbc_setOptionOneSharePerServer(m_context, false); // smbc_setOptionBrowseMaxLmbCount(m_context, 0); // smbc_setTimeout(m_context, g_advancedSettings.m_sambaclienttimeout * 1000); // Create ~/.smb/smb.conf char smb_conf[MAX_PATH]; sprintf(smb_conf, "%s/.smb", getenv("HOME")); mkdir(smb_conf, 0755); sprintf(smb_conf, "%s/.smb/smb.conf", getenv("HOME")); FILE* f = fopen(smb_conf, "w"); if (f != NULL) { fprintf(f, "[global]\n"); // if a wins-server is set, we have to change name resolve order to if ( g_guiSettings.GetString("smb.winsserver").length() > 0 && !g_guiSettings.GetString("smb.winsserver").Equals("0.0.0.0") ) { fprintf(f, " wins server = %s\n", g_guiSettings.GetString("smb.winsserver").c_str()); fprintf(f, " name resolve order = bcast wins host\n"); } else fprintf(f, "name resolve order = bcast host\n"); if (g_advancedSettings.m_sambadoscodepage.length() > 0) fprintf(f, "dos charset = %s\n", g_advancedSettings.m_sambadoscodepage.c_str()); else fprintf(f, "dos charset = CP850\n"); fprintf(f, "lock directory = /tmp\n"); fprintf(f, "socket options = SO_KEEPALIVE TCP_NODELAY IPTOS_LOWDELAY\n"); fprintf(f, "workgroup = %s\n", g_guiSettings.GetString("smb.workgroup").c_str()); fclose(f); } // smbc_setWorkgroup(m_context, strdup(g_guiSettings.GetString("smb.workgroup").c_str())); #endif // initialize samba and do some hacking into the settings if (smbc_init_context(m_context)) { /* setup old interface to use this context */ smbc_set_context(m_context); #ifndef _LINUX // if a wins-server is set, we have to change name resolve order to if ( g_guiSettings.GetString("smb.winsserver").length() > 0 && !g_guiSettings.GetString("smb.winsserver").Equals("0.0.0.0") ) { lp_do_parameter( -1, "wins server", g_guiSettings.GetString("smb.winsserver").c_str()); lp_do_parameter( -1, "name resolve order", "bcast wins host"); } else lp_do_parameter( -1, "name resolve order", "bcast host"); if (g_advancedSettings.m_sambadoscodepage.length() > 0) lp_do_parameter( -1, "dos charset", g_advancedSettings.m_sambadoscodepage.c_str()); else lp_do_parameter( -1, "dos charset", "CP850"); #endif } else { smbc_free_context(m_context, 1); m_context = NULL; } } } void CSMB::Purge() { #ifndef _LINUX CSingleLock lock(*this); smbc_purge(); #endif } /* * For each new connection samba creates a new session * But this is not what we want, we just want to have one session at the time * This means that we have to call smbc_purge() if samba created a new session * Samba will create a new session when: * - connecting to another server * - connecting to another share on the same server (share, not a different folder!) * * We try to avoid lot's of purge commands because it slow samba down. */ void CSMB::PurgeEx(const CURL& url) { CSingleLock lock(*this); CStdString strShare = url.GetFileName().substr(0, url.GetFileName().Find('/')); #ifndef _LINUX if (m_strLastShare.length() > 0 && (m_strLastShare != strShare || m_strLastHost != url.GetHostName())) smbc_purge(); #endif m_strLastShare = strShare; m_strLastHost = url.GetHostName(); } CStdString CSMB::URLEncode(const CURL &url) { /* due to smb wanting encoded urls we have to build it manually */ CStdString flat = "smb://"; if(url.GetDomain().length() > 0) { flat += URLEncode(url.GetDomain()); flat += ";"; } /* samba messes up of password is set but no username is set. don't know why yet */ /* probably the url parser that goes crazy */ if(url.GetUserName().length() > 0 /* || url.GetPassWord().length() > 0 */) { flat += URLEncode(url.GetUserName()); flat += ":"; flat += URLEncode(url.GetPassWord()); flat += "@"; } else if( !url.GetHostName().IsEmpty() && !g_guiSettings.GetString("smb.username").IsEmpty() ) { /* okey this is abit uggly to do this here, as we don't really only url encode */ /* but it's the simplest place to do so */ flat += URLEncode(g_guiSettings.GetString("smb.username")); flat += ":"; flat += URLEncode(g_guiSettings.GetString("smb.password")); flat += "@"; } flat += URLEncode(url.GetHostName()); /* okey sadly since a slash is an invalid name we have to tokenize */ std::vector<CStdString> parts; std::vector<CStdString>::iterator it; CUtil::Tokenize(url.GetFileName(), parts, "/"); for( it = parts.begin(); it != parts.end(); it++ ) { flat += "/"; flat += URLEncode((*it)); } /* okey options should go here, thou current samba doesn't support any */ return flat; } CStdString CSMB::URLEncode(const CStdString &value) { int buffer_len = value.length()*3+1; char* buffer = (char*)malloc(buffer_len); memset(buffer,0,buffer_len); char *strSrc = strdup(value.c_str()); // urlencode requires char* and not const char *. just to be on the safe side #ifdef _WIN32 // smbc_urlencode(buffer, strSrc, buffer_len); #else // SMBC_urlencode(buffer, strSrc, buffer_len); #endif free(strSrc); CStdString encoded = buffer; free(buffer); return encoded; } CStdString CSMB::URLDecode(const CStdString &value) { int buffer_len = value.length()+2; // decode is shorter char* buffer = (char*)malloc(buffer_len); memset(buffer,0,buffer_len); char *strSrc = strdup(value.c_str()); // urlencode requires char* and not const char *. just to be on the safe side #ifdef _WIN32 // smbc_urldecode(buffer, strSrc, buffer_len-1); #else // SMBC_urldecode(buffer, strSrc, buffer_len-1); #endif free(strSrc); CStdString decoded = buffer; free(buffer); return decoded; } #ifndef _LINUX DWORD CSMB::ConvertUnixToNT(int error) { DWORD nt_error; if (error == ENODEV || error == ENETUNREACH || error == WSAETIMEDOUT) nt_error = NT_STATUS_INVALID_COMPUTER_NAME; else if(error == WSAECONNREFUSED || error == WSAECONNABORTED) nt_error = NT_STATUS_CONNECTION_REFUSED; else nt_error = map_nt_error_from_unix(error); return nt_error; } #endif CSMB smb; CFileSMB::CFileSMB() { smb.Init(); m_fd = -1; m_fileSize = 0; m_nBufferSize = 0; m_buffer = new BYTE[SMB_READ_CACHE_SIZE]; } CFileSMB::~CFileSMB() { Close(); if (m_buffer) delete [] m_buffer; } __int64 CFileSMB::GetPosition() { if (m_fd == -1) return 0; CSingleLock lock(smb); smb.Init(); __int64 pos = smbc_lseek(m_fd, 0, SEEK_CUR); if ( pos < 0 ) return 0; return pos; } __int64 CFileSMB::GetLength() { if (m_fd == -1) return 0; return m_fileSize; } bool CFileSMB::Open(const CURL& url, bool bBinary) { m_bBinary = bBinary; CStdString strPath; url.GetURL(strPath); Close(); if (!g_application.IsPathAvailable(strPath, true)) return false; // smb share unavailable // we can't open files like smb://file.f or smb://server/file.f // if a file matches the if below return false, it can't exist on a samba share. if (!IsValidFile(url.GetFileName())) { CLog::Log(LOGNOTICE,"FileSmb->Open: Bad URL : '%s'",url.GetFileName().c_str()); return false; } m_url = url; CSingleLock lock(smb); smb.Init(); CStdString strFileName; m_fd = OpenFile(url, strFileName); if (m_fd == -1) { // write error to logfile #ifndef _LINUX int nt_error = smb.ConvertUnixToNT(errno); CLog::Log(LOGINFO, "FileSmb->Open: Unable to open file : '%s'\nunix_err:'%x' nt_err : '%x' error : '%s'", strFileName.c_str(), errno, nt_error, get_friendly_nt_error_msg(nt_error)); #else CLog::Log(LOGINFO, "FileSmb->Open: Unable to open file : '%s'\nunix_err:'%x' error : '%s'", strFileName.c_str(), errno, strerror(errno)); #endif return false; } #ifndef _LINUX struct __stat64 tmpBuffer = {0}; #else struct stat tmpBuffer; #endif if (smbc_fstat(m_fd, &tmpBuffer) < 0) { smbc_close(m_fd); m_fd = -1; return false; } m_fileSize = tmpBuffer.st_size; __int64 ret = smbc_lseek(m_fd, 0, SEEK_SET); if ( ret < 0 ) { smbc_close(m_fd); m_fd = -1; return false; } // We've successfully opened the file! return true; } int CFileSMB::OpenFile(const CURL &url, CStdString& strAuth) { if (!g_application.IsPathAvailable(url.GetShareName(), true)) return -1; int fd = -1; CSingleLock lock(smb); smb.Init(); /* original auth name */ strAuth = smb.URLEncode(url); CStdString strPath = g_passwordManager.GetSMBAuthFilename(strAuth); fd = smbc_open(strPath.c_str(), O_RDONLY, 0); // file open failed, try to open the directory to force authentication #ifndef _LINUX if (fd < 0 && smb.ConvertUnixToNT(errno) == NT_STATUS_ACCESS_DENIED) #else if (fd < 0 && errno == EACCES) #endif { CURL urlshare(url); /* just replace the filename with the sharename */ urlshare.SetFileName(url.GetShareName()); CSMBDirectory smbDir; smbDir.SetAllowPrompting(false); fd = smbDir.Open(urlshare); // directory open worked, try opening the file again if (fd >= 0) { // close current directory filehandle // dont need to purge since its the same server and share smbc_closedir(fd); // set up new filehandle (as CFileSMB::Open does) strPath = g_passwordManager.GetSMBAuthFilename(strPath); fd = smbc_open(strPath.c_str(), O_RDONLY, 0); } } if (fd >= 0) strAuth = strPath; return fd; } bool CFileSMB::Exists(const CURL& url) { // if a file matches the if below return false, it can't exist on a samba share. if (url.GetFileName().Find('/') < 0 || url.GetFileName().at(0) == '.' || url.GetFileName().Find("/.") >= 0) return false; CSingleLock lock(smb); smb.Init(); CStdString strFileName = smb.URLEncode(url); strFileName = g_passwordManager.GetSMBAuthFilename(strFileName); #ifndef _LINUX struct __stat64 info; #else struct stat info; #endif int iResult = smbc_stat(strFileName, &info); if (iResult < 0) return false; return true; } int CFileSMB::Stat(struct __stat64* buffer) { if (m_fd == -1) return -1; CSingleLock lock(smb); smb.Init(); #ifndef _LINUX struct __stat64 tmpBuffer = {0}; #else struct stat tmpBuffer = {0}; #endif int iResult = smbc_fstat(m_fd, &tmpBuffer); buffer->st_dev = tmpBuffer.st_dev; buffer->st_ino = tmpBuffer.st_ino; buffer->st_mode = tmpBuffer.st_mode; buffer->st_nlink = tmpBuffer.st_nlink; buffer->st_uid = tmpBuffer.st_uid; buffer->st_gid = tmpBuffer.st_gid; buffer->st_rdev = tmpBuffer.st_rdev; buffer->st_size = tmpBuffer.st_size; buffer->st_atime = tmpBuffer.st_atime; buffer->st_mtime = tmpBuffer.st_mtime; buffer->st_ctime = tmpBuffer.st_ctime; return iResult; } int CFileSMB::Stat(const CURL& url, struct __stat64* buffer) { CSingleLock lock(smb); smb.Init(); CStdString strFileName = smb.URLEncode(url); strFileName = g_passwordManager.GetSMBAuthFilename(strFileName); #ifndef _LINUX struct __stat64 tmpBuffer = {0}; #else struct stat tmpBuffer = {0}; #endif int iResult = smbc_stat(strFileName, &tmpBuffer); buffer->st_dev = tmpBuffer.st_dev; buffer->st_ino = tmpBuffer.st_ino; buffer->st_mode = tmpBuffer.st_mode; buffer->st_nlink = tmpBuffer.st_nlink; buffer->st_uid = tmpBuffer.st_uid; buffer->st_gid = tmpBuffer.st_gid; buffer->st_rdev = tmpBuffer.st_rdev; buffer->st_size = tmpBuffer.st_size; buffer->st_atime = tmpBuffer.st_atime; buffer->st_mtime = tmpBuffer.st_mtime; buffer->st_ctime = tmpBuffer.st_ctime; return iResult; } bool CFileSMB::FillBuffer() { if (m_fd == -1) return false; CSingleLock lock(smb); smb.Init(); while (m_nBufferSize < SMB_READ_CACHE_SIZE) { int nToRead = SMB_READ_CACHE_SIZE - m_nBufferSize; if (nToRead > 65000) // not 64K on purpose. 64k confuses some smb servers. nToRead = 65000; int bytesRead = smbc_read(m_fd, m_buffer + m_nBufferSize, nToRead); if (bytesRead < 0) { #ifndef _LINUX CLog::Log(LOGERROR, "SMB: %s - Error( %s )", __FUNCTION__, get_friendly_nt_error_msg(smb.ConvertUnixToNT(errno))); #else CLog::Log(LOGERROR, "SMB: %s -fd = %d, Error( %s )", __FUNCTION__, m_fd, strerror(errno)); #endif return false; } if (bytesRead <= 0) break; m_nBufferSize += bytesRead; } return true; } unsigned int CFileSMB::Read(void *lpBuf, __int64 uiBufSize) { if (m_fd == -1) return 0; if (uiBufSize > m_nBufferSize) { if (!FillBuffer()) { Close(); return 0; } } if (m_nBufferSize == 0) return 0; int nToCopy = (uiBufSize < m_nBufferSize)?uiBufSize:m_nBufferSize; memmove(lpBuf, m_buffer, nToCopy); if (nToCopy < m_nBufferSize) memmove(m_buffer, m_buffer + nToCopy, m_nBufferSize - nToCopy); m_nBufferSize -= nToCopy; return (unsigned int)nToCopy; } __int64 CFileSMB::Seek(__int64 iFilePosition, int iWhence) { if (m_fd == -1) return -1; if(iWhence == SEEK_POSSIBLE) return 1; CSingleLock lock(smb); smb.Init(); INT64 pos = smbc_lseek(m_fd, iFilePosition, iWhence); if ( pos < 0 ) { #ifndef _LINUX CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, get_friendly_nt_error_msg(smb.ConvertUnixToNT(errno))); #else CLog::Log(LOGERROR, "SMB: %s - Error( %s )", __FUNCTION__, strerror(errno)); #endif return -1; } m_nBufferSize = 0; return (__int64)pos; } void CFileSMB::Close() { if (m_fd != -1) { CSingleLock lock(smb); smbc_close(m_fd); } m_fd = -1; m_fileSize = 0; m_nBufferSize = 0; } int CFileSMB::Write(const void* lpBuf, __int64 uiBufSize) { if (m_fd == -1) return -1; DWORD dwNumberOfBytesWritten = 0; // lpBuf can be safely casted to void* since xmbc_write will only read from it. CSingleLock lock(smb); smb.Init(); dwNumberOfBytesWritten = smbc_write(m_fd, (void*)lpBuf, (DWORD)uiBufSize); return (int)dwNumberOfBytesWritten; } bool CFileSMB::Delete(const CURL& url) { CStdString strFile = g_passwordManager.GetSMBAuthFilename(smb.URLEncode(url)); CSingleLock lock(smb); smb.Init(); int result = smbc_unlink(strFile.c_str()); if(result != 0) #ifndef _LINUX CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, get_friendly_nt_error_msg(smb.ConvertUnixToNT(errno))); #else CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, strerror(errno)); #endif return (result == 0); } bool CFileSMB::Rename(const CURL& url, const CURL& urlnew) { CStdString strFile = g_passwordManager.GetSMBAuthFilename(smb.URLEncode(url)); CStdString strFileNew = g_passwordManager.GetSMBAuthFilename(smb.URLEncode(urlnew)); CSingleLock lock(smb); smb.Init(); int result = smbc_rename(strFile.c_str(), strFileNew.c_str()); if(result != 0) #ifndef _LINUX CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, get_friendly_nt_error_msg(smb.ConvertUnixToNT(errno))); #else CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, strerror(errno)); #endif return (result == 0); } bool CFileSMB::OpenForWrite(const CURL& url, bool bBinary, bool bOverWrite) { m_bBinary = bBinary; m_fileSize = 0; CSingleLock lock(smb); smb.Init(); Close(); // we can't open files like smb://file.f or smb://server/file.f // if a file matches the if below return false, it can't exist on a samba share. if (!IsValidFile(url.GetFileName())) return false; CStdString strFileName = smb.URLEncode(url); strFileName = g_passwordManager.GetSMBAuthFilename(strFileName); if (bOverWrite) { CLog::Log(LOGWARNING, "FileSmb::OpenForWrite() called with overwriting enabled! - %s", strFileName.c_str()); m_fd = smbc_creat(strFileName.c_str(), 0); } else { m_fd = smbc_open(strFileName.c_str(), O_RDWR, 0); } if (m_fd == -1) { // write error to logfile #ifndef _LINUX int nt_error = map_nt_error_from_unix(errno); CLog::Log(LOGERROR, "FileSmb->Open: Unable to open file : '%s'\nunix_err:'%x' nt_err : '%x' error : '%s'", strFileName.c_str(), errno, nt_error, get_friendly_nt_error_msg(nt_error)); #else CLog::Log(LOGERROR, "FileSmb->Open: Unable to open file : '%s'\nunix_err:'%x' error : '%s'", strFileName.c_str(), errno, strerror(errno)); #endif return false; } // We've successfully opened the file! return true; } bool CFileSMB::IsValidFile(const CStdString& strFileName) { if (strFileName.Find('/') == -1 || /* doesn't have sharename */ strFileName.Right(2) == "/." || /* not current folder */ strFileName.Right(3) == "/..") /* not parent folder */ return false; return true; }