Merge pull request #4314 from MartijnKaijser/beta1
[vuplus_xbmc] / xbmc / filesystem / NFSDirectory.cpp
1 /*
2  *      Copyright (C) 2011-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "system.h"
22
23 #ifdef HAS_FILESYSTEM_NFS
24 #include "DllLibNfs.h"
25
26 #ifdef TARGET_WINDOWS
27 #include <fcntl.h>
28 #include <sys\stat.h>
29 #endif
30
31 #include "NFSDirectory.h"
32 #include "FileItem.h"
33 #include "utils/log.h"
34 #include "utils/URIUtils.h"
35 #include "utils/StringUtils.h"
36 #include "threads/SingleLock.h"
37 using namespace XFILE;
38 using namespace std;
39 #include <limits.h>
40 #include <nfsc/libnfs-raw-mount.h>
41 #include <nfsc/libnfs-raw-nfs.h>
42
43 CNFSDirectory::CNFSDirectory(void)
44 {
45   gNfsConnection.AddActiveConnection();
46 }
47
48 CNFSDirectory::~CNFSDirectory(void)
49 {
50   gNfsConnection.AddIdleConnection();
51 }
52
53 bool CNFSDirectory::GetDirectoryFromExportList(const std::string& strPath, CFileItemList &items)
54 {
55   CURL url(strPath);
56   std::string nonConstStrPath(strPath);
57   std::list<std::string> exportList=gNfsConnection.GetExportList(url);
58   std::list<std::string>::iterator it;
59   
60   for(it=exportList.begin();it!=exportList.end();it++)
61   {
62       std::string currentExport(*it);     
63       URIUtils::RemoveSlashAtEnd(nonConstStrPath);
64            
65       CFileItemPtr pItem(new CFileItem(currentExport));
66       std::string path(nonConstStrPath + currentExport);
67       URIUtils::AddSlashAtEnd(path);
68       pItem->SetPath(path);
69       pItem->m_dateTime=0;
70
71       pItem->m_bIsFolder = true;
72       items.Add(pItem);
73   }
74   
75   return exportList.empty()? false : true;
76 }
77
78 bool CNFSDirectory::GetServerList(CFileItemList &items)
79 {
80   struct nfs_server_list *srvrs;
81   struct nfs_server_list *srv;
82   bool ret = false;
83
84   if(!gNfsConnection.HandleDyLoad())
85   {
86     return false;
87   }
88
89   srvrs = gNfsConnection.GetImpl()->nfs_find_local_servers();   
90
91   for (srv=srvrs; srv; srv = srv->next) 
92   {
93       CStdString currentExport(srv->addr);
94
95       CFileItemPtr pItem(new CFileItem(currentExport));
96       CStdString path("nfs://" + currentExport);
97       URIUtils::AddSlashAtEnd(path);
98       pItem->m_dateTime=0;
99
100       pItem->SetPath(path);
101       pItem->m_bIsFolder = true;
102       items.Add(pItem);
103       ret = true; //added at least one entry
104   }
105   gNfsConnection.GetImpl()->free_nfs_srvr_list(srvrs);
106
107   return ret;
108 }
109
110 bool CNFSDirectory::ResolveSymlink( const CStdString &dirName, struct nfsdirent *dirent, CURL &resolvedUrl)
111 {
112   CSingleLock lock(gNfsConnection); 
113   int ret = 0;  
114   bool retVal = true;
115   CStdString fullpath = dirName;
116   char resolvedLink[MAX_PATH];
117   
118   URIUtils::AddSlashAtEnd(fullpath);
119   fullpath.append(dirent->name);
120   
121   resolvedUrl.Reset();
122   resolvedUrl.SetPort(2049);
123   resolvedUrl.SetProtocol("nfs");
124   resolvedUrl.SetHostName(gNfsConnection.GetConnectedIp()); 
125   
126   ret = gNfsConnection.GetImpl()->nfs_readlink(gNfsConnection.GetNfsContext(), fullpath.c_str(), resolvedLink, MAX_PATH);    
127   
128   if(ret == 0)
129   {
130     NFSSTAT tmpBuffer = {0};
131     fullpath = dirName;
132     URIUtils::AddSlashAtEnd(fullpath);
133     fullpath.append(resolvedLink);
134   
135     //special case - if link target is absolute it could be even another export
136     //intervolume symlinks baby ...
137     if(resolvedLink[0] == '/')
138     {    
139       //use the special stat function for using an extra context
140       //because we are inside of a dir traversation
141       //and just can't change the global nfs context here
142       //without destroying something...
143       fullpath = resolvedLink;
144       resolvedUrl.SetFileName(fullpath);            
145       ret = gNfsConnection.stat(resolvedUrl, &tmpBuffer);
146     }
147     else
148     {
149       ret = gNfsConnection.GetImpl()->nfs_stat(gNfsConnection.GetNfsContext(), fullpath.c_str(), &tmpBuffer);
150       resolvedUrl.SetFileName(gNfsConnection.GetConnectedExport() + fullpath);      
151     }
152
153     if (ret != 0) 
154     {
155       CLog::Log(LOGERROR, "NFS: Failed to stat(%s) on link resolve %s\n", fullpath.c_str(), gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
156       retVal = false;;
157     }
158     else
159     {  
160       dirent->inode = tmpBuffer.st_ino;
161       dirent->mode = tmpBuffer.st_mode;
162       dirent->size = tmpBuffer.st_size;
163       dirent->atime.tv_sec = tmpBuffer.st_atime;
164       dirent->mtime.tv_sec = tmpBuffer.st_mtime;
165       dirent->ctime.tv_sec = tmpBuffer.st_ctime;
166       
167       //map stat mode to nf3type
168       if(S_ISBLK(tmpBuffer.st_mode)){ dirent->type = NF3BLK; }
169       else if(S_ISCHR(tmpBuffer.st_mode)){ dirent->type = NF3CHR; }
170       else if(S_ISDIR(tmpBuffer.st_mode)){ dirent->type = NF3DIR; }
171       else if(S_ISFIFO(tmpBuffer.st_mode)){ dirent->type = NF3FIFO; }
172       else if(S_ISREG(tmpBuffer.st_mode)){ dirent->type = NF3REG; }      
173       else if(S_ISLNK(tmpBuffer.st_mode)){ dirent->type = NF3LNK; }      
174       else if(S_ISSOCK(tmpBuffer.st_mode)){ dirent->type = NF3SOCK; }            
175     }
176   }
177   else
178   {
179     CLog::Log(LOGERROR, "Failed to readlink(%s) %s\n", fullpath.c_str(), gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
180     retVal = false;
181   }
182   return retVal;
183 }
184
185 bool CNFSDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items)
186 {
187   // We accept nfs://server/path[/file]]]]
188   int ret = 0;
189   FILETIME fileTime, localTime;    
190   CSingleLock lock(gNfsConnection); 
191   CURL url(strPath);
192   CStdString strDirName="";
193   std::string myStrPath(strPath);
194   URIUtils::AddSlashAtEnd(myStrPath); //be sure the dir ends with a slash
195    
196   if(!gNfsConnection.Connect(url,strDirName))
197   {
198     //connect has failed - so try to get the exported filesystms if no path is given to the url
199     if(url.GetShareName().Equals(""))
200     {
201       if(url.GetHostName().Equals(""))
202       {
203         return GetServerList(items);
204       }
205       else 
206       {
207         return GetDirectoryFromExportList(myStrPath, items); 
208       }
209     }
210     else
211     {
212       return false;
213     }    
214   }
215       
216   struct nfsdir *nfsdir = NULL;
217   struct nfsdirent *nfsdirent = NULL;
218
219   ret = gNfsConnection.GetImpl()->nfs_opendir(gNfsConnection.GetNfsContext(), strDirName.c_str(), &nfsdir);
220
221   if(ret != 0)
222   {
223     CLog::Log(LOGERROR, "Failed to open(%s) %s\n", strDirName.c_str(), gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
224     return false;
225   }
226   lock.Leave();
227   
228   while((nfsdirent = gNfsConnection.GetImpl()->nfs_readdir(gNfsConnection.GetNfsContext(), nfsdir)) != NULL) 
229   {
230     std::string strName = nfsdirent->name;
231     std::string path(myStrPath + strName);    
232     int64_t iSize = 0;
233     bool bIsDir = false;
234     int64_t lTimeDate = 0;
235
236     //reslove symlinks
237     if(nfsdirent->type == NF3LNK)
238     {
239       CURL linkUrl;
240       //resolve symlink changes nfsdirent and strName
241       if(!ResolveSymlink(strDirName,nfsdirent,linkUrl))
242       { 
243         continue;
244       }
245       
246       path = linkUrl.Get();
247     }
248     
249     iSize = nfsdirent->size;
250     bIsDir = nfsdirent->type == NF3DIR;
251     lTimeDate = nfsdirent->mtime.tv_sec;
252
253     if (!StringUtils::EqualsNoCase(strName,".") && !StringUtils::EqualsNoCase(strName,"..")
254         && !StringUtils::EqualsNoCase(strName,"lost+found"))
255     {
256       if(lTimeDate == 0) // if modification date is missing, use create date
257       {
258         lTimeDate = nfsdirent->ctime.tv_sec;
259       }
260
261       LONGLONG ll = Int32x32To64(lTimeDate & 0xffffffff, 10000000) + 116444736000000000ll;
262       fileTime.dwLowDateTime = (DWORD) (ll & 0xffffffff);
263       fileTime.dwHighDateTime = (DWORD)(ll >> 32);
264       FileTimeToLocalFileTime(&fileTime, &localTime);
265
266       CFileItemPtr pItem(new CFileItem(nfsdirent->name));
267       pItem->m_dateTime=localTime;   
268       pItem->m_dwSize = iSize;        
269       
270       if (bIsDir)
271       {
272         URIUtils::AddSlashAtEnd(path);
273         pItem->m_bIsFolder = true;
274       }
275       else
276       {
277         pItem->m_bIsFolder = false;
278       }
279
280       if (strName[0] == '.')
281       {
282         pItem->SetProperty("file:hidden", true);
283       }
284       pItem->SetPath(path);
285       items.Add(pItem);
286     }
287   }
288
289   lock.Enter();
290   gNfsConnection.GetImpl()->nfs_closedir(gNfsConnection.GetNfsContext(), nfsdir);//close the dir
291   lock.Leave();
292   return true;
293 }
294
295 bool CNFSDirectory::Create(const char* strPath)
296 {
297   int ret = 0;
298   bool success=true;
299   
300   CSingleLock lock(gNfsConnection);
301   CStdString folderName(strPath);
302   URIUtils::RemoveSlashAtEnd(folderName);//mkdir fails if a slash is at the end!!! 
303   CURL url(folderName); 
304   folderName = "";
305   
306   if(!gNfsConnection.Connect(url,folderName))
307     return false;
308   
309   ret = gNfsConnection.GetImpl()->nfs_mkdir(gNfsConnection.GetNfsContext(), folderName.c_str());
310
311   success = (ret == 0 || -EEXIST == ret);
312   if(!success)
313     CLog::Log(LOGERROR, "NFS: Failed to create(%s) %s\n", folderName.c_str(), gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
314   return success;
315 }
316
317 bool CNFSDirectory::Remove(const char* strPath)
318 {
319   int ret = 0;
320
321   CSingleLock lock(gNfsConnection);
322   CStdString folderName(strPath);
323   URIUtils::RemoveSlashAtEnd(folderName);//rmdir fails if a slash is at the end!!!   
324   CURL url(folderName);
325   folderName = "";
326   
327   if(!gNfsConnection.Connect(url,folderName))
328     return false;
329   
330   ret = gNfsConnection.GetImpl()->nfs_rmdir(gNfsConnection.GetNfsContext(), folderName.c_str());
331
332   if(ret != 0 && errno != ENOENT)
333   {
334     CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
335     return false;
336   }
337   return true;
338 }
339
340 bool CNFSDirectory::Exists(const char* strPath)
341 {
342   int ret = 0;
343
344   CSingleLock lock(gNfsConnection); 
345   CStdString folderName(strPath);  
346   URIUtils::RemoveSlashAtEnd(folderName);//remove slash at end or URIUtils::GetFileName won't return what we want...
347   CURL url(folderName);
348   folderName = "";
349   
350   if(!gNfsConnection.Connect(url,folderName))
351     return false;
352   
353   NFSSTAT info;
354   ret = gNfsConnection.GetImpl()->nfs_stat(gNfsConnection.GetNfsContext(), folderName.c_str(), &info);
355   
356   if (ret != 0)
357   {
358     return false;
359   }
360   return S_ISDIR(info.st_mode) ? true : false;
361 }
362
363 #endif