2 * Copyright (C) 2011-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
16 #include "NFSDirectory.h"
17 #include "utils/StringUtils.h"
18 #include "utils/URIUtils.h"
19 #include "utils/XTimeUtils.h"
20 #include "utils/log.h"
26 using namespace XFILE
;
28 #include <nfsc/libnfs.h>
29 #include <nfsc/libnfs-raw-nfs.h>
31 #if defined(TARGET_WINDOWS)
32 #define S_IFLNK 0120000
33 #define S_ISBLK(m) (0)
34 #define S_ISSOCK(m) (0)
35 #define S_ISLNK(m) ((m & S_IFLNK) != 0)
36 #define S_ISCHR(m) ((m & _S_IFCHR) != 0)
37 #define S_ISDIR(m) ((m & _S_IFDIR) != 0)
38 #define S_ISFIFO(m) ((m & _S_IFIFO) != 0)
39 #define S_ISREG(m) ((m & _S_IFREG) != 0)
42 CNFSDirectory::CNFSDirectory(void)
44 gNfsConnection
.AddActiveConnection();
47 CNFSDirectory::~CNFSDirectory(void)
49 gNfsConnection
.AddIdleConnection();
52 bool CNFSDirectory::GetDirectoryFromExportList(const std::string
& strPath
, CFileItemList
&items
)
55 std::string
nonConstStrPath(strPath
);
56 std::list
<std::string
> exportList
=gNfsConnection
.GetExportList(url
);
58 for (const std::string
& it
: exportList
)
60 const std::string
& currentExport(it
);
61 URIUtils::RemoveSlashAtEnd(nonConstStrPath
);
63 CFileItemPtr
pItem(new CFileItem(currentExport
));
64 std::string
path(nonConstStrPath
+ currentExport
);
65 URIUtils::AddSlashAtEnd(path
);
67 pItem
->m_dateTime
= 0;
69 pItem
->m_bIsFolder
= true;
73 return exportList
.empty() ? false : true;
76 bool CNFSDirectory::GetServerList(CFileItemList
&items
)
78 struct nfs_server_list
*srvrs
;
79 struct nfs_server_list
*srv
;
82 srvrs
= nfs_find_local_servers();
84 for (srv
=srvrs
; srv
; srv
= srv
->next
)
86 std::string
currentExport(srv
->addr
);
88 CFileItemPtr
pItem(new CFileItem(currentExport
));
89 std::string
path("nfs://" + currentExport
);
90 URIUtils::AddSlashAtEnd(path
);
94 pItem
->m_bIsFolder
= true;
96 ret
= true; //added at least one entry
98 free_nfs_srvr_list(srvrs
);
103 bool CNFSDirectory::ResolveSymlink( const std::string
&dirName
, struct nfsdirent
*dirent
, CURL
&resolvedUrl
)
105 std::unique_lock
<CCriticalSection
> lock(gNfsConnection
);
108 std::string fullpath
= dirName
;
109 char resolvedLink
[MAX_PATH
];
111 URIUtils::AddSlashAtEnd(fullpath
);
112 fullpath
.append(dirent
->name
);
115 resolvedUrl
.SetPort(2049);
116 resolvedUrl
.SetProtocol("nfs");
117 resolvedUrl
.SetHostName(gNfsConnection
.GetConnectedIp());
119 ret
= nfs_readlink(gNfsConnection
.GetNfsContext(), fullpath
.c_str(), resolvedLink
, MAX_PATH
);
123 nfs_stat_64 tmpBuffer
= {};
125 URIUtils::AddSlashAtEnd(fullpath
);
126 fullpath
.append(resolvedLink
);
128 //special case - if link target is absolute it could be even another export
129 //intervolume symlinks baby ...
130 if(resolvedLink
[0] == '/')
132 //use the special stat function for using an extra context
133 //because we are inside of a dir traversal
134 //and just can't change the global nfs context here
135 //without destroying something...
136 fullpath
= resolvedLink
;
137 resolvedUrl
.SetFileName(fullpath
);
138 ret
= gNfsConnection
.stat(resolvedUrl
, &tmpBuffer
);
142 ret
= nfs_stat64(gNfsConnection
.GetNfsContext(), fullpath
.c_str(), &tmpBuffer
);
143 resolvedUrl
.SetFileName(gNfsConnection
.GetConnectedExport() + fullpath
);
148 CLog::Log(LOGERROR
, "NFS: Failed to stat({}) on link resolve {}", fullpath
,
149 nfs_get_error(gNfsConnection
.GetNfsContext()));
154 dirent
->inode
= tmpBuffer
.nfs_ino
;
155 dirent
->mode
= tmpBuffer
.nfs_mode
;
156 dirent
->size
= tmpBuffer
.nfs_size
;
157 dirent
->atime
.tv_sec
= tmpBuffer
.nfs_atime
;
158 dirent
->mtime
.tv_sec
= tmpBuffer
.nfs_mtime
;
159 dirent
->ctime
.tv_sec
= tmpBuffer
.nfs_ctime
;
161 //map stat mode to nf3type
162 if (S_ISBLK(tmpBuffer
.nfs_mode
))
164 dirent
->type
= NF3BLK
;
166 else if (S_ISCHR(tmpBuffer
.nfs_mode
))
168 dirent
->type
= NF3CHR
;
170 else if (S_ISDIR(tmpBuffer
.nfs_mode
))
172 dirent
->type
= NF3DIR
;
174 else if (S_ISFIFO(tmpBuffer
.nfs_mode
))
176 dirent
->type
= NF3FIFO
;
178 else if (S_ISREG(tmpBuffer
.nfs_mode
))
180 dirent
->type
= NF3REG
;
182 else if (S_ISLNK(tmpBuffer
.nfs_mode
))
184 dirent
->type
= NF3LNK
;
186 else if (S_ISSOCK(tmpBuffer
.nfs_mode
))
188 dirent
->type
= NF3SOCK
;
194 CLog::Log(LOGERROR
, "Failed to readlink({}) {}", fullpath
,
195 nfs_get_error(gNfsConnection
.GetNfsContext()));
201 bool CNFSDirectory::GetDirectory(const CURL
& url
, CFileItemList
&items
)
203 // We accept nfs://server/path[/file]]]]
205 KODI::TIME::FileTime fileTime
, localTime
;
206 std::unique_lock
<CCriticalSection
> lock(gNfsConnection
);
207 std::string strDirName
="";
208 std::string
myStrPath(url
.Get());
209 URIUtils::AddSlashAtEnd(myStrPath
); //be sure the dir ends with a slash
211 if(!gNfsConnection
.Connect(url
,strDirName
))
213 //connect has failed - so try to get the exported filesystems if no path is given to the url
214 if(url
.GetShareName().empty())
216 if(url
.GetHostName().empty())
218 return GetServerList(items
);
222 return GetDirectoryFromExportList(myStrPath
, items
);
231 struct nfsdir
*nfsdir
= NULL
;
232 struct nfsdirent
*nfsdirent
= NULL
;
234 ret
= nfs_opendir(gNfsConnection
.GetNfsContext(), strDirName
.c_str(), &nfsdir
);
238 CLog::Log(LOGERROR
, "Failed to open({}) {}", strDirName
,
239 nfs_get_error(gNfsConnection
.GetNfsContext()));
244 while((nfsdirent
= nfs_readdir(gNfsConnection
.GetNfsContext(), nfsdir
)) != NULL
)
246 struct nfsdirent tmpDirent
= *nfsdirent
;
247 std::string strName
= tmpDirent
.name
;
248 std::string
path(myStrPath
+ strName
);
251 int64_t lTimeDate
= 0;
254 if(tmpDirent
.type
== NF3LNK
)
257 //resolve symlink changes tmpDirent and strName
258 if(!ResolveSymlink(strDirName
,&tmpDirent
,linkUrl
))
263 path
= linkUrl
.Get();
266 iSize
= tmpDirent
.size
;
267 bIsDir
= tmpDirent
.type
== NF3DIR
;
268 lTimeDate
= tmpDirent
.mtime
.tv_sec
;
270 if (!StringUtils::EqualsNoCase(strName
,".") && !StringUtils::EqualsNoCase(strName
,"..")
271 && !StringUtils::EqualsNoCase(strName
,"lost+found"))
273 if(lTimeDate
== 0) // if modification date is missing, use create date
275 lTimeDate
= tmpDirent
.ctime
.tv_sec
;
278 long long ll
= lTimeDate
& 0xffffffff;
280 ll
+= 116444736000000000ll;
281 fileTime
.lowDateTime
= (DWORD
)(ll
& 0xffffffff);
282 fileTime
.highDateTime
= (DWORD
)(ll
>> 32);
283 KODI::TIME::FileTimeToLocalFileTime(&fileTime
, &localTime
);
285 CFileItemPtr
pItem(new CFileItem(tmpDirent
.name
));
286 pItem
->m_dateTime
=localTime
;
287 pItem
->m_dwSize
= iSize
;
291 URIUtils::AddSlashAtEnd(path
);
292 pItem
->m_bIsFolder
= true;
296 pItem
->m_bIsFolder
= false;
299 if (strName
[0] == '.')
301 pItem
->SetProperty("file:hidden", true);
303 pItem
->SetPath(path
);
309 nfs_closedir(gNfsConnection
.GetNfsContext(), nfsdir
);//close the dir
314 bool CNFSDirectory::Create(const CURL
& url2
)
319 std::unique_lock
<CCriticalSection
> lock(gNfsConnection
);
320 std::string
folderName(url2
.Get());
321 URIUtils::RemoveSlashAtEnd(folderName
);//mkdir fails if a slash is at the end!!!
322 CURL
url(folderName
);
325 if(!gNfsConnection
.Connect(url
,folderName
))
328 ret
= nfs_mkdir(gNfsConnection
.GetNfsContext(), folderName
.c_str());
330 success
= (ret
== 0 || -EEXIST
== ret
);
332 CLog::Log(LOGERROR
, "NFS: Failed to create({}) {}", folderName
,
333 nfs_get_error(gNfsConnection
.GetNfsContext()));
337 bool CNFSDirectory::Remove(const CURL
& url2
)
341 std::unique_lock
<CCriticalSection
> lock(gNfsConnection
);
342 std::string
folderName(url2
.Get());
343 URIUtils::RemoveSlashAtEnd(folderName
);//rmdir fails if a slash is at the end!!!
344 CURL
url(folderName
);
347 if(!gNfsConnection
.Connect(url
,folderName
))
350 ret
= nfs_rmdir(gNfsConnection
.GetNfsContext(), folderName
.c_str());
352 if(ret
!= 0 && errno
!= ENOENT
)
354 CLog::Log(LOGERROR
, "{} - Error( {} )", __FUNCTION__
,
355 nfs_get_error(gNfsConnection
.GetNfsContext()));
361 bool CNFSDirectory::Exists(const CURL
& url2
)
365 std::unique_lock
<CCriticalSection
> lock(gNfsConnection
);
366 std::string
folderName(url2
.Get());
367 URIUtils::RemoveSlashAtEnd(folderName
);//remove slash at end or URIUtils::GetFileName won't return what we want...
368 CURL
url(folderName
);
371 if(!gNfsConnection
.Connect(url
,folderName
))
375 ret
= nfs_stat64(gNfsConnection
.GetNfsContext(), folderName
.c_str(), &info
);
381 return S_ISDIR(info
.nfs_mode
) ? true : false;