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