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