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