/* * Copyright (C) 2011-2018 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ #ifdef TARGET_WINDOWS #include #include #endif #include "FileItem.h" #include "NFSDirectory.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" #include "utils/XTimeUtils.h" #include "utils/log.h" #ifdef TARGET_WINDOWS #include #endif using namespace XFILE; #include #include #include #if defined(TARGET_WINDOWS) #define S_IFLNK 0120000 #define S_ISBLK(m) (0) #define S_ISSOCK(m) (0) #define S_ISLNK(m) ((m & S_IFLNK) != 0) #define S_ISCHR(m) ((m & _S_IFCHR) != 0) #define S_ISDIR(m) ((m & _S_IFDIR) != 0) #define S_ISFIFO(m) ((m & _S_IFIFO) != 0) #define S_ISREG(m) ((m & _S_IFREG) != 0) #endif CNFSDirectory::CNFSDirectory(void) { gNfsConnection.AddActiveConnection(); } CNFSDirectory::~CNFSDirectory(void) { gNfsConnection.AddIdleConnection(); } bool CNFSDirectory::GetDirectoryFromExportList(const std::string& strPath, CFileItemList &items) { CURL url(strPath); std::string nonConstStrPath(strPath); std::list exportList=gNfsConnection.GetExportList(url); for (const std::string& it : exportList) { const std::string& currentExport(it); URIUtils::RemoveSlashAtEnd(nonConstStrPath); CFileItemPtr pItem(new CFileItem(currentExport)); std::string path(nonConstStrPath + currentExport); URIUtils::AddSlashAtEnd(path); pItem->SetPath(path); pItem->m_dateTime = 0; pItem->m_bIsFolder = true; items.Add(pItem); } return exportList.empty() ? false : true; } bool CNFSDirectory::GetServerList(CFileItemList &items) { struct nfs_server_list *srvrs; struct nfs_server_list *srv; bool ret = false; srvrs = nfs_find_local_servers(); for (srv=srvrs; srv; srv = srv->next) { std::string currentExport(srv->addr); CFileItemPtr pItem(new CFileItem(currentExport)); std::string path("nfs://" + currentExport); URIUtils::AddSlashAtEnd(path); pItem->m_dateTime=0; pItem->SetPath(path); pItem->m_bIsFolder = true; items.Add(pItem); ret = true; //added at least one entry } free_nfs_srvr_list(srvrs); return ret; } bool CNFSDirectory::ResolveSymlink( const std::string &dirName, struct nfsdirent *dirent, CURL &resolvedUrl) { std::unique_lock lock(gNfsConnection); int ret = 0; bool retVal = true; std::string fullpath = dirName; char resolvedLink[MAX_PATH]; URIUtils::AddSlashAtEnd(fullpath); fullpath.append(dirent->name); resolvedUrl.Reset(); resolvedUrl.SetPort(2049); resolvedUrl.SetProtocol("nfs"); resolvedUrl.SetHostName(gNfsConnection.GetConnectedIp()); ret = nfs_readlink(gNfsConnection.GetNfsContext(), fullpath.c_str(), resolvedLink, MAX_PATH); if(ret == 0) { nfs_stat_64 tmpBuffer = {}; fullpath = dirName; URIUtils::AddSlashAtEnd(fullpath); fullpath.append(resolvedLink); //special case - if link target is absolute it could be even another export //intervolume symlinks baby ... if(resolvedLink[0] == '/') { //use the special stat function for using an extra context //because we are inside of a dir traversal //and just can't change the global nfs context here //without destroying something... fullpath = resolvedLink; resolvedUrl.SetFileName(fullpath); ret = gNfsConnection.stat(resolvedUrl, &tmpBuffer); } else { ret = nfs_stat64(gNfsConnection.GetNfsContext(), fullpath.c_str(), &tmpBuffer); resolvedUrl.SetFileName(gNfsConnection.GetConnectedExport() + fullpath); } if (ret != 0) { CLog::Log(LOGERROR, "NFS: Failed to stat({}) on link resolve {}", fullpath, nfs_get_error(gNfsConnection.GetNfsContext())); retVal = false; } else { dirent->inode = tmpBuffer.nfs_ino; dirent->mode = tmpBuffer.nfs_mode; dirent->size = tmpBuffer.nfs_size; dirent->atime.tv_sec = tmpBuffer.nfs_atime; dirent->mtime.tv_sec = tmpBuffer.nfs_mtime; dirent->ctime.tv_sec = tmpBuffer.nfs_ctime; //map stat mode to nf3type if (S_ISBLK(tmpBuffer.nfs_mode)) { dirent->type = NF3BLK; } else if (S_ISCHR(tmpBuffer.nfs_mode)) { dirent->type = NF3CHR; } else if (S_ISDIR(tmpBuffer.nfs_mode)) { dirent->type = NF3DIR; } else if (S_ISFIFO(tmpBuffer.nfs_mode)) { dirent->type = NF3FIFO; } else if (S_ISREG(tmpBuffer.nfs_mode)) { dirent->type = NF3REG; } else if (S_ISLNK(tmpBuffer.nfs_mode)) { dirent->type = NF3LNK; } else if (S_ISSOCK(tmpBuffer.nfs_mode)) { dirent->type = NF3SOCK; } } } else { CLog::Log(LOGERROR, "Failed to readlink({}) {}", fullpath, nfs_get_error(gNfsConnection.GetNfsContext())); retVal = false; } return retVal; } bool CNFSDirectory::GetDirectory(const CURL& url, CFileItemList &items) { // We accept nfs://server/path[/file]]]] int ret = 0; KODI::TIME::FileTime fileTime, localTime; std::unique_lock lock(gNfsConnection); std::string strDirName=""; std::string myStrPath(url.Get()); URIUtils::AddSlashAtEnd(myStrPath); //be sure the dir ends with a slash if(!gNfsConnection.Connect(url,strDirName)) { //connect has failed - so try to get the exported filesystems if no path is given to the url if(url.GetShareName().empty()) { if(url.GetHostName().empty()) { return GetServerList(items); } else { return GetDirectoryFromExportList(myStrPath, items); } } else { return false; } } struct nfsdir *nfsdir = NULL; struct nfsdirent *nfsdirent = NULL; ret = nfs_opendir(gNfsConnection.GetNfsContext(), strDirName.c_str(), &nfsdir); if(ret != 0) { CLog::Log(LOGERROR, "Failed to open({}) {}", strDirName, nfs_get_error(gNfsConnection.GetNfsContext())); return false; } lock.unlock(); while((nfsdirent = nfs_readdir(gNfsConnection.GetNfsContext(), nfsdir)) != NULL) { struct nfsdirent tmpDirent = *nfsdirent; std::string strName = tmpDirent.name; std::string path(myStrPath + strName); int64_t iSize = 0; bool bIsDir = false; int64_t lTimeDate = 0; //reslove symlinks if(tmpDirent.type == NF3LNK) { CURL linkUrl; //resolve symlink changes tmpDirent and strName if(!ResolveSymlink(strDirName,&tmpDirent,linkUrl)) { continue; } path = linkUrl.Get(); } iSize = tmpDirent.size; bIsDir = tmpDirent.type == NF3DIR; lTimeDate = tmpDirent.mtime.tv_sec; if (!StringUtils::EqualsNoCase(strName,".") && !StringUtils::EqualsNoCase(strName,"..") && !StringUtils::EqualsNoCase(strName,"lost+found")) { if(lTimeDate == 0) // if modification date is missing, use create date { lTimeDate = tmpDirent.ctime.tv_sec; } long long ll = lTimeDate & 0xffffffff; ll *= 10000000ll; ll += 116444736000000000ll; fileTime.lowDateTime = (DWORD)(ll & 0xffffffff); fileTime.highDateTime = (DWORD)(ll >> 32); KODI::TIME::FileTimeToLocalFileTime(&fileTime, &localTime); CFileItemPtr pItem(new CFileItem(tmpDirent.name)); pItem->m_dateTime=localTime; pItem->m_dwSize = iSize; if (bIsDir) { URIUtils::AddSlashAtEnd(path); pItem->m_bIsFolder = true; } else { pItem->m_bIsFolder = false; } if (strName[0] == '.') { pItem->SetProperty("file:hidden", true); } pItem->SetPath(path); items.Add(pItem); } } lock.lock(); nfs_closedir(gNfsConnection.GetNfsContext(), nfsdir);//close the dir lock.unlock(); return true; } bool CNFSDirectory::Create(const CURL& url2) { int ret = 0; bool success=true; std::unique_lock lock(gNfsConnection); std::string folderName(url2.Get()); URIUtils::RemoveSlashAtEnd(folderName);//mkdir fails if a slash is at the end!!! CURL url(folderName); folderName = ""; if(!gNfsConnection.Connect(url,folderName)) return false; ret = nfs_mkdir(gNfsConnection.GetNfsContext(), folderName.c_str()); success = (ret == 0 || -EEXIST == ret); if(!success) CLog::Log(LOGERROR, "NFS: Failed to create({}) {}", folderName, nfs_get_error(gNfsConnection.GetNfsContext())); return success; } bool CNFSDirectory::Remove(const CURL& url2) { int ret = 0; std::unique_lock lock(gNfsConnection); std::string folderName(url2.Get()); URIUtils::RemoveSlashAtEnd(folderName);//rmdir fails if a slash is at the end!!! CURL url(folderName); folderName = ""; if(!gNfsConnection.Connect(url,folderName)) return false; ret = nfs_rmdir(gNfsConnection.GetNfsContext(), folderName.c_str()); if(ret != 0 && errno != ENOENT) { CLog::Log(LOGERROR, "{} - Error( {} )", __FUNCTION__, nfs_get_error(gNfsConnection.GetNfsContext())); return false; } return true; } bool CNFSDirectory::Exists(const CURL& url2) { int ret = 0; std::unique_lock lock(gNfsConnection); std::string folderName(url2.Get()); URIUtils::RemoveSlashAtEnd(folderName);//remove slash at end or URIUtils::GetFileName won't return what we want... CURL url(folderName); folderName = ""; if(!gNfsConnection.Connect(url,folderName)) return false; nfs_stat_64 info; ret = nfs_stat64(gNfsConnection.GetNfsContext(), folderName.c_str(), &info); if (ret != 0) { return false; } return S_ISDIR(info.nfs_mode) ? true : false; }