DVDCodecs: Amlogic: Handle conditions in which amcodec should be opened during Open()
[vuplus_xbmc] / xbmc / filesystem / NFSFile.cpp
1 /*
2  *      Copyright (C) 2011-2013 Team XBMC
3  *      http://xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
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/>.
18  *
19  */
20
21 // FileNFS.cpp: implementation of the CNFSFile class.
22 //
23 //////////////////////////////////////////////////////////////////////
24 #include "system.h"
25
26 #ifdef HAS_FILESYSTEM_NFS
27 #include "NFSFile.h"
28 #include "threads/SingleLock.h"
29 #include "utils/log.h"
30 #include "utils/URIUtils.h"
31 #include "utils/StringUtils.h"
32 #include "network/DNSNameCache.h"
33 #include "threads/SystemClock.h"
34
35 #include <nfsc/libnfs-raw-mount.h>
36
37 #ifdef TARGET_WINDOWS
38 #include <fcntl.h>
39 #include <sys\stat.h>
40 #endif
41
42 //KEEP_ALIVE_TIMEOUT is decremented every half a second
43 //360 * 0.5s == 180s == 3mins
44 //so when no read was done for 3mins and files are open
45 //do the nfs keep alive for the open files
46 #define KEEP_ALIVE_TIMEOUT 360
47
48 //6 mins (360s) cached context timeout
49 #define CONTEXT_TIMEOUT 360000
50
51 //return codes for getContextForExport
52 #define CONTEXT_INVALID  0    //getcontext failed
53 #define CONTEXT_NEW      1    //new context created
54 #define CONTEXT_CACHED   2    //context cached and therefore already mounted (no new mount needed)
55
56 using namespace XFILE;
57
58 CNfsConnection::CNfsConnection()
59 : m_pNfsContext(NULL)
60 , m_exportPath("")
61 , m_hostName("")
62 , m_resolvedHostName("")
63 , m_readChunkSize(0)
64 , m_writeChunkSize(0)
65 , m_OpenConnections(0)
66 , m_IdleTimeout(0)
67 , m_lastAccessedTime(0)
68 , m_pLibNfs(new DllLibNfs())
69 {
70 }
71
72 CNfsConnection::~CNfsConnection()
73 {
74   Deinit();
75   delete m_pLibNfs;
76 }
77
78 void CNfsConnection::resolveHost(const CURL &url)
79
80   //resolve if hostname has changed
81   CDNSNameCache::Lookup(url.GetHostName(), m_resolvedHostName);
82 }
83
84 std::list<std::string> CNfsConnection::GetExportList(const CURL &url)
85 {
86     std::list<std::string> retList;
87
88     if(HandleDyLoad())
89     {
90       struct exportnode *exportlist, *tmp;
91       exportlist = m_pLibNfs->mount_getexports(m_resolvedHostName);
92       tmp = exportlist;
93
94       for(tmp = exportlist; tmp!=NULL; tmp=tmp->ex_next)
95       {
96         std::string exportStr = std::string(tmp->ex_dir);
97         
98         retList.push_back(exportStr);
99       }      
100
101       gNfsConnection.GetImpl()->mount_free_export_list(exportlist);
102       retList.sort();
103       retList.reverse();
104     }
105     
106     return retList;
107 }
108
109 bool CNfsConnection::HandleDyLoad()
110 {
111   bool ret = true;
112   
113   if(!m_pLibNfs->IsLoaded())
114   {
115     if(!m_pLibNfs->Load())
116     {
117       CLog::Log(LOGERROR,"NFS: Error loading libnfs (%s).",__FUNCTION__);    
118       ret = false; //fatal
119     }    
120   }
121   return ret;
122 }
123
124 void CNfsConnection::clearMembers()
125 {
126     m_exportPath.clear();
127     m_hostName.clear();
128     m_exportList.clear();
129     m_writeChunkSize = 0;
130     m_readChunkSize = 0;  
131     m_pNfsContext = NULL;
132     m_KeepAliveTimeouts.clear();
133 }
134
135 void CNfsConnection::destroyOpenContexts()
136 {
137   CSingleLock lock(openContextLock);
138   for(tOpenContextMap::iterator it = m_openContextMap.begin();it!=m_openContextMap.end();it++)
139   {
140     m_pLibNfs->nfs_destroy_context(it->second.pContext);
141   }
142   m_openContextMap.clear();
143 }
144
145 void CNfsConnection::destroyContext(const CStdString &exportName)
146 {
147   CSingleLock lock(openContextLock);
148   tOpenContextMap::iterator it = m_openContextMap.find(exportName.c_str());
149   if (it != m_openContextMap.end()) 
150   {
151       m_pLibNfs->nfs_destroy_context(it->second.pContext);
152       m_openContextMap.erase(it);
153   }
154 }
155
156 struct nfs_context *CNfsConnection::getContextFromMap(const CStdString &exportname, bool forceCacheHit/* = false*/)
157 {
158   struct nfs_context *pRet = NULL;
159   CSingleLock lock(openContextLock);
160
161   tOpenContextMap::iterator it = m_openContextMap.find(exportname.c_str());
162   if(it != m_openContextMap.end())
163   {
164     //check if context has timed out already
165     uint64_t now = XbmcThreads::SystemClockMillis();
166     if((now - it->second.lastAccessedTime) < CONTEXT_TIMEOUT || forceCacheHit)
167     {
168       //its not timedout yet or caller wants the cached entry regardless of timeout
169       //refresh access time of that
170       //context and return it
171       if (!forceCacheHit) // only log it if this isn't the resetkeepalive on each read ;)
172         CLog::Log(LOGDEBUG, "NFS: Refreshing context for %s, old: %"PRId64", new: %"PRId64, exportname.c_str(), it->second.lastAccessedTime, now);
173       it->second.lastAccessedTime = now;
174       pRet = it->second.pContext;
175     }
176     else 
177     {
178       //context is timed out
179       //destroy it and return NULL
180       CLog::Log(LOGDEBUG, "NFS: Old context timed out - destroying it");
181       m_pLibNfs->nfs_destroy_context(it->second.pContext);
182       m_openContextMap.erase(it);
183     }
184   }
185   return pRet;
186 }
187
188 int CNfsConnection::getContextForExport(const CStdString &exportname)
189 {
190   int ret = CONTEXT_INVALID; 
191     
192   if(HandleDyLoad())
193   {
194     clearMembers();  
195     
196     m_pNfsContext = getContextFromMap(exportname);
197
198     if(!m_pNfsContext)
199     {
200       CLog::Log(LOGDEBUG,"NFS: Context for %s not open - get a new context.", exportname.c_str());
201       m_pNfsContext = m_pLibNfs->nfs_init_context();
202     
203       if(!m_pNfsContext) 
204       {
205         CLog::Log(LOGERROR,"NFS: Error initcontext in getContextForExport.");
206       }
207       else 
208       {
209         struct contextTimeout tmp;
210         CSingleLock lock(openContextLock);        
211         tmp.pContext = m_pNfsContext;
212         tmp.lastAccessedTime = XbmcThreads::SystemClockMillis();
213         m_openContextMap[exportname] = tmp; //add context to list of all contexts      
214         ret = CONTEXT_NEW;
215       }
216     }
217     else
218     {
219       ret = CONTEXT_CACHED;
220       CLog::Log(LOGDEBUG,"NFS: Using cached context.");
221     }
222     m_lastAccessedTime = XbmcThreads::SystemClockMillis(); //refresh last access time of m_pNfsContext
223   }
224   return ret;
225 }
226
227 bool CNfsConnection::splitUrlIntoExportAndPath(const CURL& url, CStdString &exportPath, CStdString &relativePath)
228 {
229   //refresh exportlist if empty or hostname change
230   if(m_exportList.empty() || !StringUtils::EqualsNoCase(url.GetHostName(), m_hostName))
231   {
232     m_exportList = GetExportList(url);
233   }
234
235   return splitUrlIntoExportAndPath(url, exportPath, relativePath, m_exportList);
236 }
237
238 bool CNfsConnection::splitUrlIntoExportAndPath(const CURL& url,CStdString &exportPath, CStdString &relativePath, std::list<std::string> &exportList)
239 {
240     bool ret = false;
241   
242     if(!exportList.empty())
243     {
244       relativePath = "";
245       exportPath = "";
246       
247       CStdString path = url.GetFileName();
248       
249       //GetFileName returns path without leading "/"
250       //but we need it because the export paths start with "/"
251       //and path.Find(*it) wouldn't work else
252       if(path[0] != '/')
253       {
254         path = "/" + path;
255       }
256       
257       std::list<std::string>::iterator it;
258       
259       for(it=exportList.begin();it!=exportList.end();it++)
260       {
261         //if path starts with the current export path
262         if(StringUtils::StartsWith(path, *it))
263         {
264           //its possible that StartsWith may not find the correct match first
265           //as an example, if /path/ & and /path/sub/ are exported, but
266           //the user specifies the path /path/subdir/ (from /path/ export).
267           //If the path is longer than the exportpath, make sure / is next.
268           if( (path.length() > (*it).length()) &&
269               (path[(*it).length()] != '/') && (*it) != "/")
270             continue;
271           exportPath = *it;
272           //handle special case where root is exported
273           //in that case we don't want to stripp off to
274           //much from the path
275           if( exportPath == path )
276             relativePath = "//";
277           else if( exportPath == "/" )
278             relativePath = "//" + path.substr(exportPath.length());
279           else
280             relativePath = "//" + path.substr(exportPath.length()+1);
281           ret = true;
282           break;          
283         }
284       }
285     }
286     return ret;
287 }
288
289 bool CNfsConnection::Connect(const CURL& url, CStdString &relativePath)
290 {
291   CSingleLock lock(*this);
292   bool ret = false;
293   int nfsRet = 0;
294   CStdString exportPath = "";
295
296   resolveHost(url);
297   ret = splitUrlIntoExportAndPath(url, exportPath, relativePath);
298   
299   if( (ret && (!exportPath.Equals(m_exportPath,true)  || 
300       !url.GetHostName().Equals(m_hostName,false)))    ||
301       (XbmcThreads::SystemClockMillis() - m_lastAccessedTime) > CONTEXT_TIMEOUT )
302   {
303     int contextRet = getContextForExport(url.GetHostName() + exportPath);
304     
305     if(contextRet == CONTEXT_INVALID)//we need a new context because sharename or hostname has changed
306     {
307       return false;
308     }
309     
310     if(contextRet == CONTEXT_NEW) //new context was created - we need to mount it
311     {
312       //we connect to the directory of the path. This will be the "root" path of this connection then.
313       //So all fileoperations are relative to this mountpoint...
314       nfsRet = m_pLibNfs->nfs_mount(m_pNfsContext, m_resolvedHostName.c_str(), exportPath.c_str());
315
316       if(nfsRet != 0) 
317       {
318         CLog::Log(LOGERROR,"NFS: Failed to mount nfs share: %s (%s)\n", exportPath.c_str(), m_pLibNfs->nfs_get_error(m_pNfsContext));
319         destroyContext(url.GetHostName() + exportPath);
320         return false;
321       }
322       CLog::Log(LOGDEBUG,"NFS: Connected to server %s and export %s\n", url.GetHostName().c_str(), exportPath.c_str());
323     }
324     m_exportPath = exportPath;
325     m_hostName = url.GetHostName();
326     //read chunksize only works after mount
327     m_readChunkSize = m_pLibNfs->nfs_get_readmax(m_pNfsContext);
328     m_writeChunkSize = m_pLibNfs->nfs_get_writemax(m_pNfsContext);
329
330     if(contextRet == CONTEXT_NEW)
331     {
332       CLog::Log(LOGDEBUG,"NFS: chunks: r/w %i/%i\n", (int)m_readChunkSize,(int)m_writeChunkSize);          
333     }
334   }
335   return ret; 
336 }
337
338 void CNfsConnection::Deinit()
339 {
340   if(m_pNfsContext && m_pLibNfs->IsLoaded())
341   {
342     destroyOpenContexts();
343     m_pNfsContext = NULL;
344     m_pLibNfs->Unload();    
345   }        
346   clearMembers();
347 }
348
349 /* This is called from CApplication::ProcessSlow() and is used to tell if nfs have been idle for too long */
350 void CNfsConnection::CheckIfIdle()
351 {
352   /* We check if there are open connections. This is done without a lock to not halt the mainthread. It should be thread safe as
353    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.  */
354   if (m_OpenConnections == 0 && m_pNfsContext != NULL)
355   { /* I've set the the maxiumum IDLE time to be 1 min and 30 sec. */
356     CSingleLock lock(*this);
357     if (m_OpenConnections == 0 /* check again - when locked */)
358     {
359       if (m_IdleTimeout > 0)
360       {
361         m_IdleTimeout--;
362       }
363       else
364       {
365         CLog::Log(LOGNOTICE, "NFS is idle. Closing the remaining connections.");
366         gNfsConnection.Deinit();
367       }
368     }
369   }
370   
371   if( m_pNfsContext != NULL )
372   {
373     CSingleLock lock(keepAliveLock);
374     //handle keep alive on opened files
375     for( tFileKeepAliveMap::iterator it = m_KeepAliveTimeouts.begin();it!=m_KeepAliveTimeouts.end();it++)
376     {
377       if(it->second.refreshCounter > 0)
378       {
379         it->second.refreshCounter--;
380       }
381       else
382       {
383         keepAlive(it->second.exportPath, it->first);
384         //reset timeout
385         resetKeepAlive(it->second.exportPath, it->first);
386       }
387     }
388   }
389 }
390
391 //remove file handle from keep alive list on file close
392 void CNfsConnection::removeFromKeepAliveList(struct nfsfh  *_pFileHandle)
393 {
394   CSingleLock lock(keepAliveLock);
395   m_KeepAliveTimeouts.erase(_pFileHandle);
396 }
397
398 //reset timeouts on read
399 void CNfsConnection::resetKeepAlive(std::string _exportPath, struct nfsfh  *_pFileHandle)
400 {
401   CSingleLock lock(keepAliveLock);
402   //refresh last access time of the context aswell
403   getContextFromMap(_exportPath, true);
404   //adds new keys - refreshs existing ones
405   m_KeepAliveTimeouts[_pFileHandle].exportPath = _exportPath;
406   m_KeepAliveTimeouts[_pFileHandle].refreshCounter = KEEP_ALIVE_TIMEOUT;
407 }
408
409 //keep alive the filehandles nfs connection
410 //by blindly doing a read 32bytes - seek back to where
411 //we were before
412 void CNfsConnection::keepAlive(std::string _exportPath, struct nfsfh  *_pFileHandle)
413 {
414   uint64_t offset = 0;
415   char buffer[32];
416   // this also refreshs the last accessed time for the context
417   // true forces a cachehit regardless the context is timedout
418   // on this call we are sure its not timedout even if the last accessed
419   // time suggests it.
420   struct nfs_context *pContext = getContextFromMap(_exportPath, true);
421   
422   if (!pContext)// this should normally never happen - paranoia
423     pContext = m_pNfsContext;
424   
425   CLog::Log(LOGNOTICE, "NFS: sending keep alive after %i s.",KEEP_ALIVE_TIMEOUT/2);
426   CSingleLock lock(*this);
427   m_pLibNfs->nfs_lseek(pContext, _pFileHandle, 0, SEEK_CUR, &offset);
428   m_pLibNfs->nfs_read(pContext, _pFileHandle, 32, buffer);
429   m_pLibNfs->nfs_lseek(pContext, _pFileHandle, offset, SEEK_SET, &offset);
430 }
431
432 int CNfsConnection::stat(const CURL &url, NFSSTAT *statbuff)
433 {
434   CSingleLock lock(*this);
435   int nfsRet = 0;
436   CStdString exportPath;
437   CStdString relativePath;
438   struct nfs_context *pTmpContext = NULL;
439   
440   if(!HandleDyLoad())
441   {
442     return -1;
443   }
444   
445   resolveHost(url);
446   
447   if(splitUrlIntoExportAndPath(url, exportPath, relativePath))
448   {    
449     pTmpContext = m_pLibNfs->nfs_init_context();
450     
451     if(pTmpContext)
452     {  
453       //we connect to the directory of the path. This will be the "root" path of this connection then.
454       //So all fileoperations are relative to this mountpoint...
455       nfsRet = m_pLibNfs->nfs_mount(pTmpContext, m_resolvedHostName.c_str(), exportPath.c_str());
456       
457       if(nfsRet == 0) 
458       {
459         nfsRet = m_pLibNfs->nfs_stat(pTmpContext, relativePath.c_str(), statbuff);      
460       }
461       else
462       {
463         CLog::Log(LOGERROR,"NFS: Failed to mount nfs share: %s (%s)\n", exportPath.c_str(), m_pLibNfs->nfs_get_error(m_pNfsContext));
464       }
465       
466       m_pLibNfs->nfs_destroy_context(pTmpContext);
467       CLog::Log(LOGDEBUG,"NFS: Connected to server %s and export %s in tmpContext\n", url.GetHostName().c_str(), exportPath.c_str());
468     }
469   }
470   return nfsRet;
471 }
472
473 /* The following two function is used to keep track on how many Opened files/directories there are.
474 needed for unloading the dylib*/
475 void CNfsConnection::AddActiveConnection()
476 {
477   CSingleLock lock(*this);
478   m_OpenConnections++;
479 }
480
481 void CNfsConnection::AddIdleConnection()
482 {
483   CSingleLock lock(*this);
484   m_OpenConnections--;
485   /* If we close a file we reset the idle timer so that we don't have any wierd behaviours if a user
486    leaves the movie paused for a long while and then press stop */
487   m_IdleTimeout = 180;
488 }
489
490 CNfsConnection gNfsConnection;
491
492 CNFSFile::CNFSFile()
493 : m_fileSize(0)
494 , m_pFileHandle(NULL)
495 , m_pNfsContext(NULL)
496 {
497   gNfsConnection.AddActiveConnection();
498 }
499
500 CNFSFile::~CNFSFile()
501 {
502   Close();
503   gNfsConnection.AddIdleConnection();
504 }
505
506 int64_t CNFSFile::GetPosition()
507 {
508   int ret = 0;
509   uint64_t offset = 0;
510   CSingleLock lock(gNfsConnection);
511   
512   if (gNfsConnection.GetNfsContext() == NULL || m_pFileHandle == NULL) return 0;
513   
514   ret = (int)gNfsConnection.GetImpl()->nfs_lseek(gNfsConnection.GetNfsContext(), m_pFileHandle, 0, SEEK_CUR, &offset);
515   
516   if (ret < 0) 
517   {
518     CLog::Log(LOGERROR, "NFS: Failed to lseek(%s)",gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
519   }
520   return offset;
521 }
522
523 int64_t CNFSFile::GetLength()
524 {
525   if (m_pFileHandle == NULL) return 0;
526   return m_fileSize;
527 }
528
529 bool CNFSFile::Open(const CURL& url)
530 {
531   int ret = 0;
532   Close();
533   // we can't open files like nfs://file.f or nfs://server/file.f
534   // if a file matches the if below return false, it can't exist on a nfs share.
535   if (!IsValidFile(url.GetFileName()))
536   {
537     CLog::Log(LOGNOTICE,"NFS: Bad URL : '%s'",url.GetFileName().c_str());
538     return false;
539   }
540   
541   CStdString filename = "";
542    
543   CSingleLock lock(gNfsConnection);
544   
545   if(!gNfsConnection.Connect(url, filename))
546     return false;
547   
548   m_pNfsContext = gNfsConnection.GetNfsContext(); 
549   m_exportPath = gNfsConnection.GetContextMapId();
550   
551   ret = gNfsConnection.GetImpl()->nfs_open(m_pNfsContext, filename.c_str(), O_RDONLY, &m_pFileHandle);
552   
553   if (ret != 0) 
554   {
555     CLog::Log(LOGINFO, "CNFSFile::Open: Unable to open file : '%s'  error : '%s'", url.GetFileName().c_str(), gNfsConnection.GetImpl()->nfs_get_error(m_pNfsContext));
556     m_pNfsContext = NULL;
557     m_exportPath.clear();
558     return false;
559   } 
560   
561   CLog::Log(LOGDEBUG,"CNFSFile::Open - opened %s",url.GetFileName().c_str());
562   m_url=url;
563   
564   struct __stat64 tmpBuffer;
565
566   if( Stat(&tmpBuffer) )
567   {
568     m_url.Reset();
569     Close();
570     return false;
571   }
572   
573   m_fileSize = tmpBuffer.st_size;//cache the size of this file
574   // We've successfully opened the file!
575   return true;
576 }
577
578
579 bool CNFSFile::Exists(const CURL& url)
580 {
581   return Stat(url,NULL) == 0;
582 }
583
584 int CNFSFile::Stat(struct __stat64* buffer)
585 {
586   return Stat(m_url,buffer);
587 }
588
589
590 int CNFSFile::Stat(const CURL& url, struct __stat64* buffer)
591 {
592   int ret = 0;
593   CSingleLock lock(gNfsConnection);
594   CStdString filename = "";
595   
596   if(!gNfsConnection.Connect(url,filename))
597     return -1;
598    
599
600   NFSSTAT tmpBuffer = {0};
601
602   ret = gNfsConnection.GetImpl()->nfs_stat(gNfsConnection.GetNfsContext(), filename.c_str(), &tmpBuffer);
603   
604   //if buffer == NULL we where called from Exists - in that case don't spam the log with errors
605   if (ret != 0 && buffer != NULL) 
606   {
607     CLog::Log(LOGERROR, "NFS: Failed to stat(%s) %s\n", url.GetFileName().c_str(), gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
608     ret = -1;
609   }
610   else
611   {  
612     if(buffer)
613     {
614 #if defined(TARGET_WINDOWS)// TODO get rid of this define after gotham
615       memcpy(buffer, &tmpBuffer, sizeof(struct __stat64));
616 #else
617       memset(buffer, 0, sizeof(struct __stat64));
618       buffer->st_dev = tmpBuffer.st_dev;
619       buffer->st_ino = tmpBuffer.st_ino;
620       buffer->st_mode = tmpBuffer.st_mode;
621       buffer->st_nlink = tmpBuffer.st_nlink;
622       buffer->st_uid = tmpBuffer.st_uid;
623       buffer->st_gid = tmpBuffer.st_gid;
624       buffer->st_rdev = tmpBuffer.st_rdev;
625       buffer->st_size = tmpBuffer.st_size;
626       buffer->st_atime = tmpBuffer.st_atime;
627       buffer->st_mtime = tmpBuffer.st_mtime;
628       buffer->st_ctime = tmpBuffer.st_ctime;
629 #endif
630     }
631   }
632   return ret;
633 }
634
635 unsigned int CNFSFile::Read(void *lpBuf, int64_t uiBufSize)
636 {
637   int numberOfBytesRead = 0;
638   CSingleLock lock(gNfsConnection);
639   
640   if (m_pFileHandle == NULL || m_pNfsContext == NULL ) return 0;
641
642   numberOfBytesRead = gNfsConnection.GetImpl()->nfs_read(m_pNfsContext, m_pFileHandle, uiBufSize, (char *)lpBuf);  
643
644   lock.Leave();//no need to keep the connection lock after that
645   
646   gNfsConnection.resetKeepAlive(m_exportPath, m_pFileHandle);//triggers keep alive timer reset for this filehandle
647   
648   //something went wrong ...
649   if (numberOfBytesRead < 0) 
650   {
651     CLog::Log(LOGERROR, "%s - Error( %d, %s )", __FUNCTION__, numberOfBytesRead, gNfsConnection.GetImpl()->nfs_get_error(m_pNfsContext));
652     return 0;
653   }
654   return (unsigned int)numberOfBytesRead;
655 }
656
657 int64_t CNFSFile::Seek(int64_t iFilePosition, int iWhence)
658 {
659   int ret = 0;
660   uint64_t offset = 0;
661
662   CSingleLock lock(gNfsConnection);  
663   if (m_pFileHandle == NULL || m_pNfsContext == NULL) return -1;
664   
665  
666   ret = (int)gNfsConnection.GetImpl()->nfs_lseek(m_pNfsContext, m_pFileHandle, iFilePosition, iWhence, &offset);
667   if (ret < 0) 
668   {
669     CLog::Log(LOGERROR, "%s - Error( seekpos: %"PRId64", whence: %i, fsize: %"PRId64", %s)", __FUNCTION__, iFilePosition, iWhence, m_fileSize, gNfsConnection.GetImpl()->nfs_get_error(m_pNfsContext));
670     return -1;
671   }
672   return (int64_t)offset;
673 }
674
675 int CNFSFile::Truncate(int64_t iSize)
676 {
677   int ret = 0;
678   
679   CSingleLock lock(gNfsConnection);  
680   if (m_pFileHandle == NULL || m_pNfsContext == NULL) return -1;
681   
682   
683   ret = (int)gNfsConnection.GetImpl()->nfs_ftruncate(m_pNfsContext, m_pFileHandle, iSize);
684   if (ret < 0) 
685   {
686     CLog::Log(LOGERROR, "%s - Error( ftruncate: %"PRId64", fsize: %"PRId64", %s)", __FUNCTION__, iSize, m_fileSize, gNfsConnection.GetImpl()->nfs_get_error(m_pNfsContext));
687     return -1;
688   }
689   return ret;
690 }
691
692 void CNFSFile::Close()
693 {
694   CSingleLock lock(gNfsConnection);
695   
696   if (m_pFileHandle != NULL && m_pNfsContext != NULL)
697   {
698     int ret = 0;
699     CLog::Log(LOGDEBUG,"CNFSFile::Close closing file %s", m_url.GetFileName().c_str());
700     // remove it from keep alive list before closing
701     // so keep alive code doens't process it anymore
702     gNfsConnection.removeFromKeepAliveList(m_pFileHandle);
703     ret = gNfsConnection.GetImpl()->nfs_close(m_pNfsContext, m_pFileHandle);
704         
705           if (ret < 0) 
706     {
707       CLog::Log(LOGERROR, "Failed to close(%s) - %s\n", m_url.GetFileName().c_str(), gNfsConnection.GetImpl()->nfs_get_error(m_pNfsContext));
708     }
709     m_pFileHandle = NULL;
710     m_pNfsContext = NULL;    
711     m_fileSize = 0;
712     m_exportPath.clear();
713   }
714 }
715
716 //this was a bitch!
717 //for nfs write to work we have to write chunked
718 //otherwise this could crash on big files
719 int CNFSFile::Write(const void* lpBuf, int64_t uiBufSize)
720 {
721   int numberOfBytesWritten = 0;
722   int writtenBytes = 0;
723   int64_t leftBytes = uiBufSize;
724   //clamp max write chunksize to 32kb - fixme - this might be superfluious with future libnfs versions
725   int64_t chunkSize = gNfsConnection.GetMaxWriteChunkSize() > 32768 ? 32768 : gNfsConnection.GetMaxWriteChunkSize();
726   
727   CSingleLock lock(gNfsConnection);
728   
729   if (m_pFileHandle == NULL || m_pNfsContext == NULL) return -1;
730   
731   //write as long as some bytes are left to be written
732   while( leftBytes )
733   {
734     //the last chunk could be smalle than chunksize
735     if(leftBytes < chunkSize)
736     {
737       chunkSize = leftBytes;//write last chunk with correct size
738     }
739     //write chunk
740     writtenBytes = gNfsConnection.GetImpl()->nfs_write(m_pNfsContext,
741                                   m_pFileHandle, 
742                                   chunkSize, 
743                                   (char *)lpBuf + numberOfBytesWritten);
744     //decrease left bytes
745     leftBytes-= writtenBytes;
746     //increase overall written bytes
747     numberOfBytesWritten += writtenBytes;
748         
749     //danger - something went wrong
750     if (writtenBytes < 0) 
751     {
752       CLog::Log(LOGERROR, "Failed to pwrite(%s) %s\n", m_url.GetFileName().c_str(), gNfsConnection.GetImpl()->nfs_get_error(m_pNfsContext));        
753       break;
754     }     
755   }
756   //return total number of written bytes
757   return numberOfBytesWritten;
758 }
759
760 bool CNFSFile::Delete(const CURL& url)
761 {
762   int ret = 0;
763   CSingleLock lock(gNfsConnection);
764   CStdString filename = "";
765   
766   if(!gNfsConnection.Connect(url, filename))
767     return false;
768   
769   
770   ret = gNfsConnection.GetImpl()->nfs_unlink(gNfsConnection.GetNfsContext(), filename.c_str());
771   
772   if(ret != 0)
773   {
774     CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
775   }
776   return (ret == 0);
777 }
778
779 bool CNFSFile::Rename(const CURL& url, const CURL& urlnew)
780 {
781   int ret = 0;
782   CSingleLock lock(gNfsConnection);
783   CStdString strFile = "";
784   
785   if(!gNfsConnection.Connect(url,strFile))
786     return false;
787   
788   CStdString strFileNew;
789   CStdString strDummy;
790   gNfsConnection.splitUrlIntoExportAndPath(urlnew, strDummy, strFileNew);
791   
792   ret = gNfsConnection.GetImpl()->nfs_rename(gNfsConnection.GetNfsContext() , strFile.c_str(), strFileNew.c_str());
793   
794   if(ret != 0)
795   {
796     CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
797   } 
798   return (ret == 0);
799 }
800
801 bool CNFSFile::OpenForWrite(const CURL& url, bool bOverWrite)
802
803   int ret = 0;
804   // we can't open files like nfs://file.f or nfs://server/file.f
805   // if a file matches the if below return false, it can't exist on a nfs share.
806   if (!IsValidFile(url.GetFileName())) return false;
807   
808   Close();
809   CSingleLock lock(gNfsConnection);
810   CStdString filename = "";
811   
812   if(!gNfsConnection.Connect(url,filename))
813     return false;
814   
815   m_pNfsContext = gNfsConnection.GetNfsContext();
816   m_exportPath = gNfsConnection.GetContextMapId();
817   
818   if (bOverWrite)
819   {
820     CLog::Log(LOGWARNING, "FileNFS::OpenForWrite() called with overwriting enabled! - %s", filename.c_str());
821     //create file with proper permissions
822     ret = gNfsConnection.GetImpl()->nfs_creat(m_pNfsContext, filename.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, &m_pFileHandle);    
823     //if file was created the file handle isn't valid ... so close it and open later
824     if(ret == 0)
825     {
826       gNfsConnection.GetImpl()->nfs_close(m_pNfsContext,m_pFileHandle);
827       m_pFileHandle = NULL;          
828     }
829   }
830
831   ret = gNfsConnection.GetImpl()->nfs_open(m_pNfsContext, filename.c_str(), O_RDWR, &m_pFileHandle);
832   
833   if (ret || m_pFileHandle == NULL)
834   {
835     // write error to logfile
836     CLog::Log(LOGERROR, "CNFSFile::Open: Unable to open file : '%s' error : '%s'", filename.c_str(), gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
837     m_pNfsContext = NULL;
838     m_exportPath.clear();
839     return false;
840   }
841   m_url=url;
842   
843   struct __stat64 tmpBuffer = {0};
844
845   //only stat if file was not created
846   if(!bOverWrite) 
847   {
848     if(Stat(&tmpBuffer))
849     {
850       m_url.Reset();
851       Close();
852       return false;
853     }
854     m_fileSize = tmpBuffer.st_size;//cache filesize of this file    
855   }
856   else//file was created - filesize is zero
857   {
858     m_fileSize = 0;    
859   }
860   
861   // We've successfully opened the file!
862   return true;
863 }
864
865 bool CNFSFile::IsValidFile(const CStdString& strFileName)
866 {
867   if (strFileName.find('/') == std::string::npos || /* doesn't have sharename */
868       StringUtils::EndsWith(strFileName, "/.") || /* not current folder */
869       StringUtils::EndsWith(strFileName, "/.."))  /* not parent folder */
870     return false;
871   return true;
872 }
873 #endif//HAS_FILESYSTEM_NFS
874