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 "FileItemList.h"
17 #include "NFSDirectory.h"
18 #include "utils/StringUtils.h"
19 #include "utils/URIUtils.h"
20 #include "utils/XTimeUtils.h"
21 #include "utils/log.h"
27 using namespace XFILE
;
29 #include <nfsc/libnfs.h>
30 #include <nfsc/libnfs-raw-nfs.h>
32 #if defined(TARGET_WINDOWS)
33 #define S_IFLNK 0120000
34 #define S_ISBLK(m) (0)
35 #define S_ISSOCK(m) (0)
36 #define S_ISLNK(m) ((m & S_IFLNK) != 0)
37 #define S_ISCHR(m) ((m & _S_IFCHR) != 0)
38 #define S_ISDIR(m) ((m & _S_IFDIR) != 0)
39 #define S_ISFIFO(m) ((m & _S_IFIFO) != 0)
40 #define S_ISREG(m) ((m & _S_IFREG) != 0)
43 CNFSDirectory::CNFSDirectory(void)
45 gNfsConnection
.AddActiveConnection();
48 CNFSDirectory::~CNFSDirectory(void)
50 gNfsConnection
.AddIdleConnection();
53 bool CNFSDirectory::GetDirectoryFromExportList(const std::string
& strPath
, CFileItemList
&items
)
56 std::string
nonConstStrPath(strPath
);
57 std::list
<std::string
> exportList
=gNfsConnection
.GetExportList(url
);
59 for (const std::string
& it
: exportList
)
61 const std::string
& currentExport(it
);
62 URIUtils::RemoveSlashAtEnd(nonConstStrPath
);
64 CFileItemPtr
pItem(new CFileItem(currentExport
));
65 std::string
path(nonConstStrPath
+ currentExport
);
66 URIUtils::AddSlashAtEnd(path
);
68 pItem
->m_dateTime
= 0;
70 pItem
->m_bIsFolder
= true;
74 return exportList
.empty() ? false : true;
77 bool CNFSDirectory::GetServerList(CFileItemList
&items
)
79 struct nfs_server_list
*srvrs
;
80 struct nfs_server_list
*srv
;
83 srvrs
= nfs_find_local_servers();
85 for (srv
=srvrs
; srv
; srv
= srv
->next
)
87 std::string
currentExport(srv
->addr
);
89 CFileItemPtr
pItem(new CFileItem(currentExport
));
90 std::string
path("nfs://" + currentExport
);
91 URIUtils::AddSlashAtEnd(path
);
95 pItem
->m_bIsFolder
= true;
97 ret
= true; //added at least one entry
99 free_nfs_srvr_list(srvrs
);
104 bool CNFSDirectory::ResolveSymlink( const std::string
&dirName
, struct nfsdirent
*dirent
, CURL
&resolvedUrl
)
106 std::unique_lock
<CCriticalSection
> lock(gNfsConnection
);
109 std::string fullpath
= dirName
;
110 char resolvedLink
[MAX_PATH
];
112 URIUtils::AddSlashAtEnd(fullpath
);
113 fullpath
.append(dirent
->name
);
116 resolvedUrl
.SetPort(2049);
117 resolvedUrl
.SetProtocol("nfs");
118 resolvedUrl
.SetHostName(gNfsConnection
.GetConnectedIp());
120 ret
= nfs_readlink(gNfsConnection
.GetNfsContext(), fullpath
.c_str(), resolvedLink
, MAX_PATH
);
124 nfs_stat_64 tmpBuffer
= {};
126 URIUtils::AddSlashAtEnd(fullpath
);
127 fullpath
.append(resolvedLink
);
129 //special case - if link target is absolute it could be even another export
130 //intervolume symlinks baby ...
131 if(resolvedLink
[0] == '/')
133 //use the special stat function for using an extra context
134 //because we are inside of a dir traversal
135 //and just can't change the global nfs context here
136 //without destroying something...
137 fullpath
= resolvedLink
;
138 resolvedUrl
.SetFileName(fullpath
);
139 ret
= gNfsConnection
.stat(resolvedUrl
, &tmpBuffer
);
143 ret
= nfs_stat64(gNfsConnection
.GetNfsContext(), fullpath
.c_str(), &tmpBuffer
);
144 resolvedUrl
.SetFileName(gNfsConnection
.GetConnectedExport() + fullpath
);
149 CLog::Log(LOGERROR
, "NFS: Failed to stat({}) on link resolve {}", fullpath
,
150 nfs_get_error(gNfsConnection
.GetNfsContext()));
155 dirent
->inode
= tmpBuffer
.nfs_ino
;
156 dirent
->mode
= tmpBuffer
.nfs_mode
;
157 dirent
->size
= tmpBuffer
.nfs_size
;
158 dirent
->atime
.tv_sec
= tmpBuffer
.nfs_atime
;
159 dirent
->mtime
.tv_sec
= tmpBuffer
.nfs_mtime
;
160 dirent
->ctime
.tv_sec
= tmpBuffer
.nfs_ctime
;
162 //map stat mode to nf3type
163 if (S_ISBLK(tmpBuffer
.nfs_mode
))
165 dirent
->type
= NF3BLK
;
167 else if (S_ISCHR(tmpBuffer
.nfs_mode
))
169 dirent
->type
= NF3CHR
;
171 else if (S_ISDIR(tmpBuffer
.nfs_mode
))
173 dirent
->type
= NF3DIR
;
175 else if (S_ISFIFO(tmpBuffer
.nfs_mode
))
177 dirent
->type
= NF3FIFO
;
179 else if (S_ISREG(tmpBuffer
.nfs_mode
))
181 dirent
->type
= NF3REG
;
183 else if (S_ISLNK(tmpBuffer
.nfs_mode
))
185 dirent
->type
= NF3LNK
;
187 else if (S_ISSOCK(tmpBuffer
.nfs_mode
))
189 dirent
->type
= NF3SOCK
;
195 CLog::Log(LOGERROR
, "Failed to readlink({}) {}", fullpath
,
196 nfs_get_error(gNfsConnection
.GetNfsContext()));
202 bool CNFSDirectory::GetDirectory(const CURL
& url
, CFileItemList
&items
)
204 // We accept nfs://server/path[/file]]]]
206 KODI::TIME::FileTime fileTime
, localTime
;
207 std::unique_lock
<CCriticalSection
> lock(gNfsConnection
);
208 std::string strDirName
="";
209 std::string
myStrPath(url
.Get());
210 URIUtils::AddSlashAtEnd(myStrPath
); //be sure the dir ends with a slash
212 if(!gNfsConnection
.Connect(url
,strDirName
))
214 //connect has failed - so try to get the exported filesystems if no path is given to the url
215 if(url
.GetShareName().empty())
217 if(url
.GetHostName().empty())
219 return GetServerList(items
);
223 return GetDirectoryFromExportList(myStrPath
, items
);
232 struct nfsdir
*nfsdir
= NULL
;
233 struct nfsdirent
*nfsdirent
= NULL
;
235 ret
= nfs_opendir(gNfsConnection
.GetNfsContext(), strDirName
.c_str(), &nfsdir
);
239 CLog::Log(LOGERROR
, "Failed to open({}) {}", strDirName
,
240 nfs_get_error(gNfsConnection
.GetNfsContext()));
245 while((nfsdirent
= nfs_readdir(gNfsConnection
.GetNfsContext(), nfsdir
)) != NULL
)
247 struct nfsdirent tmpDirent
= *nfsdirent
;
248 std::string strName
= tmpDirent
.name
;
249 std::string
path(myStrPath
+ strName
);
252 int64_t lTimeDate
= 0;
255 if(tmpDirent
.type
== NF3LNK
)
258 //resolve symlink changes tmpDirent and strName
259 if(!ResolveSymlink(strDirName
,&tmpDirent
,linkUrl
))
264 path
= linkUrl
.Get();
267 iSize
= tmpDirent
.size
;
268 bIsDir
= tmpDirent
.type
== NF3DIR
;
269 lTimeDate
= tmpDirent
.mtime
.tv_sec
;
271 if (!StringUtils::EqualsNoCase(strName
,".") && !StringUtils::EqualsNoCase(strName
,"..")
272 && !StringUtils::EqualsNoCase(strName
,"lost+found"))
274 if(lTimeDate
== 0) // if modification date is missing, use create date
276 lTimeDate
= tmpDirent
.ctime
.tv_sec
;
279 long long ll
= lTimeDate
& 0xffffffff;
281 ll
+= 116444736000000000ll;
282 fileTime
.lowDateTime
= (DWORD
)(ll
& 0xffffffff);
283 fileTime
.highDateTime
= (DWORD
)(ll
>> 32);
284 KODI::TIME::FileTimeToLocalFileTime(&fileTime
, &localTime
);
286 CFileItemPtr
pItem(new CFileItem(tmpDirent
.name
));
287 pItem
->m_dateTime
=localTime
;
288 pItem
->m_dwSize
= iSize
;
292 URIUtils::AddSlashAtEnd(path
);
293 pItem
->m_bIsFolder
= true;
297 pItem
->m_bIsFolder
= false;
300 if (strName
[0] == '.')
302 pItem
->SetProperty("file:hidden", true);
304 pItem
->SetPath(path
);
310 nfs_closedir(gNfsConnection
.GetNfsContext(), nfsdir
);//close the dir
315 bool CNFSDirectory::Create(const CURL
& url2
)
320 std::unique_lock
<CCriticalSection
> lock(gNfsConnection
);
321 std::string
folderName(url2
.Get());
322 URIUtils::RemoveSlashAtEnd(folderName
);//mkdir fails if a slash is at the end!!!
323 CURL
url(folderName
);
326 if(!gNfsConnection
.Connect(url
,folderName
))
329 ret
= nfs_mkdir(gNfsConnection
.GetNfsContext(), folderName
.c_str());
331 success
= (ret
== 0 || -EEXIST
== ret
);
333 CLog::Log(LOGERROR
, "NFS: Failed to create({}) {}", folderName
,
334 nfs_get_error(gNfsConnection
.GetNfsContext()));
338 bool CNFSDirectory::Remove(const CURL
& url2
)
342 std::unique_lock
<CCriticalSection
> lock(gNfsConnection
);
343 std::string
folderName(url2
.Get());
344 URIUtils::RemoveSlashAtEnd(folderName
);//rmdir fails if a slash is at the end!!!
345 CURL
url(folderName
);
348 if(!gNfsConnection
.Connect(url
,folderName
))
351 ret
= nfs_rmdir(gNfsConnection
.GetNfsContext(), folderName
.c_str());
353 if(ret
!= 0 && errno
!= ENOENT
)
355 CLog::Log(LOGERROR
, "{} - Error( {} )", __FUNCTION__
,
356 nfs_get_error(gNfsConnection
.GetNfsContext()));
362 bool CNFSDirectory::Exists(const CURL
& url2
)
366 std::unique_lock
<CCriticalSection
> lock(gNfsConnection
);
367 std::string
folderName(url2
.Get());
368 URIUtils::RemoveSlashAtEnd(folderName
);//remove slash at end or URIUtils::GetFileName won't return what we want...
369 CURL
url(folderName
);
372 if(!gNfsConnection
.Connect(url
,folderName
))
376 ret
= nfs_stat64(gNfsConnection
.GetNfsContext(), folderName
.c_str(), &info
);
382 return S_ISDIR(info
.nfs_mode
) ? true : false;