2 * Copyright (C) 2011-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/>.
21 // FileNFS.cpp: implementation of the CNFSFile class.
23 //////////////////////////////////////////////////////////////////////
26 #ifdef HAS_FILESYSTEM_NFS
27 #include "DllLibNfs.h"
29 #include "threads/SingleLock.h"
30 #include "utils/log.h"
31 #include "utils/URIUtils.h"
32 #include "utils/StringUtils.h"
33 #include "network/DNSNameCache.h"
34 #include "threads/SystemClock.h"
36 #include <nfsc/libnfs-raw-mount.h>
43 //KEEP_ALIVE_TIMEOUT is decremented every half a second
44 //360 * 0.5s == 180s == 3mins
45 //so when no read was done for 3mins and files are open
46 //do the nfs keep alive for the open files
47 #define KEEP_ALIVE_TIMEOUT 360
49 //6 mins (360s) cached context timeout
50 #define CONTEXT_TIMEOUT 360000
52 //return codes for getContextForExport
53 #define CONTEXT_INVALID 0 //getcontext failed
54 #define CONTEXT_NEW 1 //new context created
55 #define CONTEXT_CACHED 2 //context cached and therefore already mounted (no new mount needed)
57 using namespace XFILE;
59 CNfsConnection::CNfsConnection()
63 , m_resolvedHostName("")
66 , m_OpenConnections(0)
68 , m_lastAccessedTime(0)
69 , m_pLibNfs(new DllLibNfs())
73 CNfsConnection::~CNfsConnection()
79 void CNfsConnection::resolveHost(const CURL &url)
81 //resolve if hostname has changed
82 CDNSNameCache::Lookup(url.GetHostName(), m_resolvedHostName);
85 std::list<std::string> CNfsConnection::GetExportList(const CURL &url)
87 std::list<std::string> retList;
91 struct exportnode *exportlist, *tmp;
92 exportlist = m_pLibNfs->mount_getexports(m_resolvedHostName);
95 for(tmp = exportlist; tmp!=NULL; tmp=tmp->ex_next)
97 std::string exportStr = std::string(tmp->ex_dir);
98 URIUtils::AddSlashAtEnd(exportStr);
99 retList.push_back(exportStr);
102 gNfsConnection.GetImpl()->mount_free_export_list(exportlist);
108 bool CNfsConnection::HandleDyLoad()
112 if(!m_pLibNfs->IsLoaded())
114 if(!m_pLibNfs->Load())
116 CLog::Log(LOGERROR,"NFS: Error loading libnfs (%s).",__FUNCTION__);
123 void CNfsConnection::clearMembers()
125 m_exportPath.clear();
127 m_exportList.clear();
128 m_writeChunkSize = 0;
130 m_pNfsContext = NULL;
131 m_KeepAliveTimeouts.clear();
134 void CNfsConnection::destroyOpenContexts()
136 CSingleLock lock(openContextLock);
137 for(tOpenContextMap::iterator it = m_openContextMap.begin();it!=m_openContextMap.end();it++)
139 m_pLibNfs->nfs_destroy_context(it->second.pContext);
141 m_openContextMap.clear();
144 void CNfsConnection::destroyContext(const CStdString &exportName)
146 CSingleLock lock(openContextLock);
147 tOpenContextMap::iterator it = m_openContextMap.find(exportName.c_str());
148 if (it != m_openContextMap.end())
150 m_pLibNfs->nfs_destroy_context(it->second.pContext);
151 m_openContextMap.erase(it);
155 struct nfs_context *CNfsConnection::getContextFromMap(const CStdString &exportname, bool forceCacheHit/* = false*/)
157 struct nfs_context *pRet = NULL;
158 CSingleLock lock(openContextLock);
160 tOpenContextMap::iterator it = m_openContextMap.find(exportname.c_str());
161 if(it != m_openContextMap.end())
163 //check if context has timed out already
164 uint64_t now = XbmcThreads::SystemClockMillis();
165 if((now - it->second.lastAccessedTime) < CONTEXT_TIMEOUT || forceCacheHit)
167 //its not timedout yet or caller wants the cached entry regardless of timeout
168 //refresh access time of that
169 //context and return it
170 if (!forceCacheHit) // only log it if this isn't the resetkeepalive on each read ;)
171 CLog::Log(LOGDEBUG, "NFS: Refreshing context for %s, old: %"PRId64", new: %"PRId64, exportname.c_str(), it->second.lastAccessedTime, now);
172 it->second.lastAccessedTime = now;
173 pRet = it->second.pContext;
177 //context is timed out
178 //destroy it and return NULL
179 CLog::Log(LOGDEBUG, "NFS: Old context timed out - destroying it");
180 m_pLibNfs->nfs_destroy_context(it->second.pContext);
181 m_openContextMap.erase(it);
187 int CNfsConnection::getContextForExport(const CStdString &exportname)
189 int ret = CONTEXT_INVALID;
195 m_pNfsContext = getContextFromMap(exportname);
199 CLog::Log(LOGDEBUG,"NFS: Context for %s not open - get a new context.", exportname.c_str());
200 m_pNfsContext = m_pLibNfs->nfs_init_context();
204 CLog::Log(LOGERROR,"NFS: Error initcontext in getContextForExport.");
208 struct contextTimeout tmp;
209 CSingleLock lock(openContextLock);
210 tmp.pContext = m_pNfsContext;
211 tmp.lastAccessedTime = XbmcThreads::SystemClockMillis();
212 m_openContextMap[exportname] = tmp; //add context to list of all contexts
218 ret = CONTEXT_CACHED;
219 CLog::Log(LOGDEBUG,"NFS: Using cached context.");
221 m_lastAccessedTime = XbmcThreads::SystemClockMillis(); //refresh last access time of m_pNfsContext
226 bool CNfsConnection::splitUrlIntoExportAndPath(const CURL& url, CStdString &exportPath, CStdString &relativePath)
230 //refresh exportlist if empty or hostname change
231 if(m_exportList.empty() || !url.GetHostName().Equals(m_hostName,false))
233 m_exportList = GetExportList(url);
236 if(!m_exportList.empty())
241 CStdString path = url.GetFileName();
243 //GetFileName returns path without leading "/"
244 //but we need it because the export paths start with "/"
245 //and path.Find(*it) wouldn't work else
246 if(!path.empty() && path[0] != '/')
251 std::list<std::string>::iterator it;
253 for(it=m_exportList.begin();it!=m_exportList.end();it++)
255 //if path starts with the current export path
256 if(StringUtils::StartsWith(path, *it))
259 //handle special case where root is exported
260 //in that case we don't want to stripp off to
262 if( exportPath == "/" )
263 relativePath = "//" + path.substr(exportPath.length()-1);
265 relativePath = "//" + path.substr(exportPath.length());
274 bool CNfsConnection::Connect(const CURL& url, CStdString &relativePath)
276 CSingleLock lock(*this);
279 CStdString exportPath = "";
282 ret = splitUrlIntoExportAndPath(url, exportPath, relativePath);
284 if( (ret && (!exportPath.Equals(m_exportPath,true) ||
285 !url.GetHostName().Equals(m_hostName,false))) ||
286 (XbmcThreads::SystemClockMillis() - m_lastAccessedTime) > CONTEXT_TIMEOUT )
288 int contextRet = getContextForExport(url.GetHostName() + exportPath);
290 if(contextRet == CONTEXT_INVALID)//we need a new context because sharename or hostname has changed
295 if(contextRet == CONTEXT_NEW) //new context was created - we need to mount it
297 //we connect to the directory of the path. This will be the "root" path of this connection then.
298 //So all fileoperations are relative to this mountpoint...
299 nfsRet = m_pLibNfs->nfs_mount(m_pNfsContext, m_resolvedHostName.c_str(), exportPath.c_str());
303 CLog::Log(LOGERROR,"NFS: Failed to mount nfs share: %s (%s)\n", exportPath.c_str(), m_pLibNfs->nfs_get_error(m_pNfsContext));
304 destroyContext(url.GetHostName() + exportPath);
307 CLog::Log(LOGDEBUG,"NFS: Connected to server %s and export %s\n", url.GetHostName().c_str(), exportPath.c_str());
309 m_exportPath = exportPath;
310 m_hostName = url.GetHostName();
311 //read chunksize only works after mount
312 m_readChunkSize = m_pLibNfs->nfs_get_readmax(m_pNfsContext);
313 m_writeChunkSize = m_pLibNfs->nfs_get_writemax(m_pNfsContext);
315 if(contextRet == CONTEXT_NEW)
317 CLog::Log(LOGDEBUG,"NFS: chunks: r/w %i/%i\n", (int)m_readChunkSize,(int)m_writeChunkSize);
323 void CNfsConnection::Deinit()
325 if(m_pNfsContext && m_pLibNfs->IsLoaded())
327 destroyOpenContexts();
328 m_pNfsContext = NULL;
334 /* This is called from CApplication::ProcessSlow() and is used to tell if nfs have been idle for too long */
335 void CNfsConnection::CheckIfIdle()
337 /* We check if there are open connections. This is done without a lock to not halt the mainthread. It should be thread safe as
338 worst case scenario is that m_OpenConnections could read 0 and then changed to 1 if this happens it will enter the if wich will lead to another check, wich is locked. */
339 if (m_OpenConnections == 0 && m_pNfsContext != NULL)
340 { /* I've set the the maxiumum IDLE time to be 1 min and 30 sec. */
341 CSingleLock lock(*this);
342 if (m_OpenConnections == 0 /* check again - when locked */)
344 if (m_IdleTimeout > 0)
350 CLog::Log(LOGNOTICE, "NFS is idle. Closing the remaining connections.");
351 gNfsConnection.Deinit();
356 if( m_pNfsContext != NULL )
358 CSingleLock lock(keepAliveLock);
359 //handle keep alive on opened files
360 for( tFileKeepAliveMap::iterator it = m_KeepAliveTimeouts.begin();it!=m_KeepAliveTimeouts.end();it++)
362 if(it->second.refreshCounter > 0)
364 it->second.refreshCounter--;
368 keepAlive(it->second.exportPath, it->first);
370 resetKeepAlive(it->second.exportPath, it->first);
376 //remove file handle from keep alive list on file close
377 void CNfsConnection::removeFromKeepAliveList(struct nfsfh *_pFileHandle)
379 CSingleLock lock(keepAliveLock);
380 m_KeepAliveTimeouts.erase(_pFileHandle);
383 //reset timeouts on read
384 void CNfsConnection::resetKeepAlive(std::string _exportPath, struct nfsfh *_pFileHandle)
386 CSingleLock lock(keepAliveLock);
387 //refresh last access time of the context aswell
388 getContextFromMap(_exportPath, true);
389 //adds new keys - refreshs existing ones
390 m_KeepAliveTimeouts[_pFileHandle].exportPath = _exportPath;
391 m_KeepAliveTimeouts[_pFileHandle].refreshCounter = KEEP_ALIVE_TIMEOUT;
394 //keep alive the filehandles nfs connection
395 //by blindly doing a read 32bytes - seek back to where
397 void CNfsConnection::keepAlive(std::string _exportPath, struct nfsfh *_pFileHandle)
401 // this also refreshs the last accessed time for the context
402 // true forces a cachehit regardless the context is timedout
403 // on this call we are sure its not timedout even if the last accessed
405 struct nfs_context *pContext = getContextFromMap(_exportPath, true);
407 if (!pContext)// this should normally never happen - paranoia
408 pContext = m_pNfsContext;
410 CLog::Log(LOGNOTICE, "NFS: sending keep alive after %i s.",KEEP_ALIVE_TIMEOUT/2);
411 CSingleLock lock(*this);
412 m_pLibNfs->nfs_lseek(pContext, _pFileHandle, 0, SEEK_CUR, &offset);
413 m_pLibNfs->nfs_read(pContext, _pFileHandle, 32, buffer);
414 m_pLibNfs->nfs_lseek(pContext, _pFileHandle, offset, SEEK_SET, &offset);
417 int CNfsConnection::stat(const CURL &url, struct stat *statbuff)
419 CSingleLock lock(*this);
421 CStdString exportPath;
422 CStdString relativePath;
423 struct nfs_context *pTmpContext = NULL;
432 if(splitUrlIntoExportAndPath(url, exportPath, relativePath))
434 pTmpContext = m_pLibNfs->nfs_init_context();
438 //we connect to the directory of the path. This will be the "root" path of this connection then.
439 //So all fileoperations are relative to this mountpoint...
440 nfsRet = m_pLibNfs->nfs_mount(pTmpContext, m_resolvedHostName.c_str(), exportPath.c_str());
444 nfsRet = m_pLibNfs->nfs_stat(pTmpContext, relativePath.c_str(), statbuff);
448 CLog::Log(LOGERROR,"NFS: Failed to mount nfs share: %s (%s)\n", exportPath.c_str(), m_pLibNfs->nfs_get_error(m_pNfsContext));
451 m_pLibNfs->nfs_destroy_context(pTmpContext);
452 CLog::Log(LOGDEBUG,"NFS: Connected to server %s and export %s in tmpContext\n", url.GetHostName().c_str(), exportPath.c_str());
458 /* The following two function is used to keep track on how many Opened files/directories there are.
459 needed for unloading the dylib*/
460 void CNfsConnection::AddActiveConnection()
462 CSingleLock lock(*this);
466 void CNfsConnection::AddIdleConnection()
468 CSingleLock lock(*this);
470 /* If we close a file we reset the idle timer so that we don't have any wierd behaviours if a user
471 leaves the movie paused for a long while and then press stop */
475 CNfsConnection gNfsConnection;
479 , m_pFileHandle(NULL)
480 , m_pNfsContext(NULL)
482 gNfsConnection.AddActiveConnection();
485 CNFSFile::~CNFSFile()
488 gNfsConnection.AddIdleConnection();
491 int64_t CNFSFile::GetPosition()
495 CSingleLock lock(gNfsConnection);
497 if (gNfsConnection.GetNfsContext() == NULL || m_pFileHandle == NULL) return 0;
499 ret = (int)gNfsConnection.GetImpl()->nfs_lseek(gNfsConnection.GetNfsContext(), m_pFileHandle, 0, SEEK_CUR, &offset);
503 CLog::Log(LOGERROR, "NFS: Failed to lseek(%s)",gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
508 int64_t CNFSFile::GetLength()
510 if (m_pFileHandle == NULL) return 0;
514 bool CNFSFile::Open(const CURL& url)
518 // we can't open files like nfs://file.f or nfs://server/file.f
519 // if a file matches the if below return false, it can't exist on a nfs share.
520 if (!IsValidFile(url.GetFileName()))
522 CLog::Log(LOGNOTICE,"NFS: Bad URL : '%s'",url.GetFileName().c_str());
526 CStdString filename = "";
528 CSingleLock lock(gNfsConnection);
530 if(!gNfsConnection.Connect(url, filename))
533 m_pNfsContext = gNfsConnection.GetNfsContext();
534 m_exportPath = gNfsConnection.GetContextMapId();
536 ret = gNfsConnection.GetImpl()->nfs_open(m_pNfsContext, filename.c_str(), O_RDONLY, &m_pFileHandle);
540 CLog::Log(LOGINFO, "CNFSFile::Open: Unable to open file : '%s' error : '%s'", url.GetFileName().c_str(), gNfsConnection.GetImpl()->nfs_get_error(m_pNfsContext));
541 m_pNfsContext = NULL;
542 m_exportPath.clear();
546 CLog::Log(LOGDEBUG,"CNFSFile::Open - opened %s",url.GetFileName().c_str());
549 struct __stat64 tmpBuffer;
551 if( Stat(&tmpBuffer) )
558 m_fileSize = tmpBuffer.st_size;//cache the size of this file
559 // We've successfully opened the file!
564 bool CNFSFile::Exists(const CURL& url)
566 return Stat(url,NULL) == 0;
569 int CNFSFile::Stat(struct __stat64* buffer)
571 return Stat(m_url,buffer);
575 int CNFSFile::Stat(const CURL& url, struct __stat64* buffer)
578 CSingleLock lock(gNfsConnection);
579 CStdString filename = "";
581 if(!gNfsConnection.Connect(url,filename))
585 struct stat tmpBuffer = {0};
587 ret = gNfsConnection.GetImpl()->nfs_stat(gNfsConnection.GetNfsContext(), filename.c_str(), &tmpBuffer);
589 //if buffer == NULL we where called from Exists - in that case don't spam the log with errors
590 if (ret != 0 && buffer != NULL)
592 CLog::Log(LOGERROR, "NFS: Failed to stat(%s) %s\n", url.GetFileName().c_str(), gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
599 memset(buffer, 0, sizeof(struct __stat64));
600 buffer->st_dev = tmpBuffer.st_dev;
601 buffer->st_ino = tmpBuffer.st_ino;
602 buffer->st_mode = tmpBuffer.st_mode;
603 buffer->st_nlink = tmpBuffer.st_nlink;
604 buffer->st_uid = tmpBuffer.st_uid;
605 buffer->st_gid = tmpBuffer.st_gid;
606 buffer->st_rdev = tmpBuffer.st_rdev;
607 buffer->st_size = tmpBuffer.st_size;
608 buffer->st_atime = tmpBuffer.st_atime;
609 buffer->st_mtime = tmpBuffer.st_mtime;
610 buffer->st_ctime = tmpBuffer.st_ctime;
616 unsigned int CNFSFile::Read(void *lpBuf, int64_t uiBufSize)
618 int numberOfBytesRead = 0;
619 CSingleLock lock(gNfsConnection);
621 if (m_pFileHandle == NULL || m_pNfsContext == NULL ) return 0;
623 numberOfBytesRead = gNfsConnection.GetImpl()->nfs_read(m_pNfsContext, m_pFileHandle, uiBufSize, (char *)lpBuf);
625 lock.Leave();//no need to keep the connection lock after that
627 gNfsConnection.resetKeepAlive(m_exportPath, m_pFileHandle);//triggers keep alive timer reset for this filehandle
629 //something went wrong ...
630 if (numberOfBytesRead < 0)
632 CLog::Log(LOGERROR, "%s - Error( %d, %s )", __FUNCTION__, numberOfBytesRead, gNfsConnection.GetImpl()->nfs_get_error(m_pNfsContext));
635 return (unsigned int)numberOfBytesRead;
638 int64_t CNFSFile::Seek(int64_t iFilePosition, int iWhence)
643 CSingleLock lock(gNfsConnection);
644 if (m_pFileHandle == NULL || m_pNfsContext == NULL) return -1;
647 ret = (int)gNfsConnection.GetImpl()->nfs_lseek(m_pNfsContext, m_pFileHandle, iFilePosition, iWhence, &offset);
650 CLog::Log(LOGERROR, "%s - Error( seekpos: %"PRId64", whence: %i, fsize: %"PRId64", %s)", __FUNCTION__, iFilePosition, iWhence, m_fileSize, gNfsConnection.GetImpl()->nfs_get_error(m_pNfsContext));
653 return (int64_t)offset;
656 int CNFSFile::Truncate(int64_t iSize)
660 CSingleLock lock(gNfsConnection);
661 if (m_pFileHandle == NULL || m_pNfsContext == NULL) return -1;
664 ret = (int)gNfsConnection.GetImpl()->nfs_ftruncate(m_pNfsContext, m_pFileHandle, iSize);
667 CLog::Log(LOGERROR, "%s - Error( ftruncate: %"PRId64", fsize: %"PRId64", %s)", __FUNCTION__, iSize, m_fileSize, gNfsConnection.GetImpl()->nfs_get_error(m_pNfsContext));
673 void CNFSFile::Close()
675 CSingleLock lock(gNfsConnection);
677 if (m_pFileHandle != NULL && m_pNfsContext != NULL)
680 CLog::Log(LOGDEBUG,"CNFSFile::Close closing file %s", m_url.GetFileName().c_str());
681 // remove it from keep alive list before closing
682 // so keep alive code doens't process it anymore
683 gNfsConnection.removeFromKeepAliveList(m_pFileHandle);
684 ret = gNfsConnection.GetImpl()->nfs_close(m_pNfsContext, m_pFileHandle);
688 CLog::Log(LOGERROR, "Failed to close(%s) - %s\n", m_url.GetFileName().c_str(), gNfsConnection.GetImpl()->nfs_get_error(m_pNfsContext));
690 m_pFileHandle = NULL;
691 m_pNfsContext = NULL;
693 m_exportPath.clear();
698 //for nfs write to work we have to write chunked
699 //otherwise this could crash on big files
700 int CNFSFile::Write(const void* lpBuf, int64_t uiBufSize)
702 int numberOfBytesWritten = 0;
703 int writtenBytes = 0;
704 int64_t leftBytes = uiBufSize;
705 //clamp max write chunksize to 32kb - fixme - this might be superfluious with future libnfs versions
706 int64_t chunkSize = gNfsConnection.GetMaxWriteChunkSize() > 32768 ? 32768 : gNfsConnection.GetMaxWriteChunkSize();
708 CSingleLock lock(gNfsConnection);
710 if (m_pFileHandle == NULL || m_pNfsContext == NULL) return -1;
712 //write as long as some bytes are left to be written
715 //the last chunk could be smalle than chunksize
716 if(leftBytes < chunkSize)
718 chunkSize = leftBytes;//write last chunk with correct size
721 writtenBytes = gNfsConnection.GetImpl()->nfs_write(m_pNfsContext,
724 (char *)lpBuf + numberOfBytesWritten);
725 //decrease left bytes
726 leftBytes-= writtenBytes;
727 //increase overall written bytes
728 numberOfBytesWritten += writtenBytes;
730 //danger - something went wrong
731 if (writtenBytes < 0)
733 CLog::Log(LOGERROR, "Failed to pwrite(%s) %s\n", m_url.GetFileName().c_str(), gNfsConnection.GetImpl()->nfs_get_error(m_pNfsContext));
737 //return total number of written bytes
738 return numberOfBytesWritten;
741 bool CNFSFile::Delete(const CURL& url)
744 CSingleLock lock(gNfsConnection);
745 CStdString filename = "";
747 if(!gNfsConnection.Connect(url, filename))
751 ret = gNfsConnection.GetImpl()->nfs_unlink(gNfsConnection.GetNfsContext(), filename.c_str());
755 CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
760 bool CNFSFile::Rename(const CURL& url, const CURL& urlnew)
763 CSingleLock lock(gNfsConnection);
764 CStdString strFile = "";
766 if(!gNfsConnection.Connect(url,strFile))
769 CStdString strFileNew;
771 gNfsConnection.splitUrlIntoExportAndPath(urlnew, strDummy, strFileNew);
773 ret = gNfsConnection.GetImpl()->nfs_rename(gNfsConnection.GetNfsContext() , strFile.c_str(), strFileNew.c_str());
777 CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
782 bool CNFSFile::OpenForWrite(const CURL& url, bool bOverWrite)
785 // we can't open files like nfs://file.f or nfs://server/file.f
786 // if a file matches the if below return false, it can't exist on a nfs share.
787 if (!IsValidFile(url.GetFileName())) return false;
790 CSingleLock lock(gNfsConnection);
791 CStdString filename = "";
793 if(!gNfsConnection.Connect(url,filename))
796 m_pNfsContext = gNfsConnection.GetNfsContext();
797 m_exportPath = gNfsConnection.GetContextMapId();
801 CLog::Log(LOGWARNING, "FileNFS::OpenForWrite() called with overwriting enabled! - %s", filename.c_str());
802 //create file with proper permissions
803 ret = gNfsConnection.GetImpl()->nfs_creat(m_pNfsContext, filename.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, &m_pFileHandle);
804 //if file was created the file handle isn't valid ... so close it and open later
807 gNfsConnection.GetImpl()->nfs_close(m_pNfsContext,m_pFileHandle);
808 m_pFileHandle = NULL;
812 ret = gNfsConnection.GetImpl()->nfs_open(m_pNfsContext, filename.c_str(), O_RDWR, &m_pFileHandle);
814 if (ret || m_pFileHandle == NULL)
816 // write error to logfile
817 CLog::Log(LOGERROR, "CNFSFile::Open: Unable to open file : '%s' error : '%s'", filename.c_str(), gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
818 m_pNfsContext = NULL;
819 m_exportPath.clear();
824 struct __stat64 tmpBuffer = {0};
826 //only stat if file was not created
835 m_fileSize = tmpBuffer.st_size;//cache filesize of this file
837 else//file was created - filesize is zero
842 // We've successfully opened the file!
846 bool CNFSFile::IsValidFile(const CStdString& strFileName)
848 if (strFileName.find('/') == std::string::npos || /* doesn't have sharename */
849 StringUtils::EndsWith(strFileName, "/.") || /* not current folder */
850 StringUtils::EndsWith(strFileName, "/..")) /* not parent folder */
854 #endif//HAS_FILESYSTEM_NFS