How To Wiki
Advertisement

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;
}
Advertisement