1 /*
2  *  Copyright (C) 2014-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "PosixFile.h"
10 
11 #include "URL.h"
12 #include "filesystem/File.h"
13 #include "utils/AliasShortcutUtils.h"
14 #include "utils/log.h"
15 
16 #include <algorithm>
17 #include <assert.h>
18 #include <errno.h>
19 #include <limits.h>
20 #include <string>
21 
22 #include <fcntl.h>
23 #include <sys/ioctl.h>
24 #include <sys/stat.h>
25 
26 #if defined(HAVE_STATX) // use statx if available to get file birth date
27 #include <sys/sysmacros.h>
28 #endif
29 #include <unistd.h>
30 
31 using namespace XFILE;
32 
~CPosixFile()33 CPosixFile::~CPosixFile()
34 {
35   if (m_fd >= 0)
36     close(m_fd);
37 }
38 
39 // local helper
getFilename(const CURL & url)40 static std::string getFilename(const CURL& url)
41 {
42   std::string filename(url.GetFileName());
43   if (IsAliasShortcut(filename, false))
44     TranslateAliasShortcut(filename);
45 
46   return filename;
47 }
48 
49 
Open(const CURL & url)50 bool CPosixFile::Open(const CURL& url)
51 {
52   if (m_fd >= 0)
53     return false;
54 
55   const std::string filename(getFilename(url));
56   if (filename.empty())
57     return false;
58 
59   m_fd = open(filename.c_str(), O_RDONLY, S_IRUSR | S_IRGRP | S_IROTH);
60   m_filePos = 0;
61 
62   return m_fd != -1;
63 }
64 
OpenForWrite(const CURL & url,bool bOverWrite)65 bool CPosixFile::OpenForWrite(const CURL& url, bool bOverWrite /* = false*/ )
66 {
67   if (m_fd >= 0)
68     return false;
69 
70   const std::string filename(getFilename(url));
71   if (filename.empty())
72     return false;
73 
74   m_fd = open(filename.c_str(), O_RDWR | O_CREAT | (bOverWrite ? O_TRUNC : 0), S_IWUSR | S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH);
75   if (m_fd < 0)
76     return false;
77 
78   m_filePos = 0;
79   m_allowWrite = true;
80 
81   return true;
82 }
83 
Close()84 void CPosixFile::Close()
85 {
86   if (m_fd >= 0)
87   {
88     close(m_fd);
89     m_fd = -1;
90     m_filePos = -1;
91     m_lastDropPos = -1;
92     m_allowWrite = false;
93   }
94 }
95 
96 
Read(void * lpBuf,size_t uiBufSize)97 ssize_t CPosixFile::Read(void* lpBuf, size_t uiBufSize)
98 {
99   if (m_fd < 0)
100     return -1;
101 
102   assert(lpBuf != NULL || uiBufSize == 0);
103   if (lpBuf == NULL && uiBufSize != 0)
104     return -1;
105 
106   if (uiBufSize > SSIZE_MAX)
107     uiBufSize = SSIZE_MAX;
108 
109   const ssize_t res = read(m_fd, lpBuf, uiBufSize);
110   if (res < 0)
111   {
112     Seek(0, SEEK_CUR); // force update file position
113     return -1;
114   }
115 
116   if (m_filePos >= 0)
117   {
118     m_filePos += res; // if m_filePos was known - update it
119 #if defined(HAVE_POSIX_FADVISE)
120     // Drop the cache between then last drop and 16 MB behind where we
121     // are now, to make sure the file doesn't displace everything else.
122     // However, never throw out the first 16 MB of the file, as it might
123     // be the header etc., and never ask the OS to drop in chunks of
124     // less than 1 MB.
125     const int64_t end_drop = m_filePos - 16 * 1024 * 1024;
126     if (end_drop >= 17 * 1024 * 1024)
127     {
128       const int64_t start_drop = std::max<int64_t>(m_lastDropPos, 16 * 1024 * 1024);
129       if (end_drop - start_drop >= 1 * 1024 * 1024 &&
130           posix_fadvise(m_fd, start_drop, end_drop - start_drop, POSIX_FADV_DONTNEED) == 0)
131         m_lastDropPos = end_drop;
132     }
133 #endif
134   }
135 
136   return res;
137 }
138 
Write(const void * lpBuf,size_t uiBufSize)139 ssize_t CPosixFile::Write(const void* lpBuf, size_t uiBufSize)
140 {
141   if (m_fd < 0)
142     return -1;
143 
144   assert(lpBuf != NULL || uiBufSize == 0);
145   if ((lpBuf == NULL && uiBufSize != 0) || !m_allowWrite)
146     return -1;
147 
148   if (uiBufSize > SSIZE_MAX)
149     uiBufSize = SSIZE_MAX;
150 
151   const ssize_t res = write(m_fd, lpBuf, uiBufSize);
152   if (res < 0)
153   {
154     Seek(0, SEEK_CUR); // force update file position
155     return -1;
156   }
157 
158   if (m_filePos >= 0)
159     m_filePos += res; // if m_filePos was known - update it
160 
161   return res;
162 }
163 
Seek(int64_t iFilePosition,int iWhence)164 int64_t CPosixFile::Seek(int64_t iFilePosition, int iWhence /* = SEEK_SET*/)
165 {
166   if (m_fd < 0)
167     return -1;
168 
169 #ifdef TARGET_ANDROID
170   //! @todo properly support with detection in configure
171   //! Android special case: Android doesn't substitute off64_t for off_t and similar functions
172   m_filePos = lseek64(m_fd, (off64_t)iFilePosition, iWhence);
173 #else  // !TARGET_ANDROID
174   const off_t filePosOffT = (off_t) iFilePosition;
175   // check for parameter overflow
176   if (sizeof(int64_t) != sizeof(off_t) && iFilePosition != filePosOffT)
177     return -1;
178 
179   m_filePos = lseek(m_fd, filePosOffT, iWhence);
180 #endif // !TARGET_ANDROID
181 
182   return m_filePos;
183 }
184 
Truncate(int64_t size)185 int CPosixFile::Truncate(int64_t size)
186 {
187   if (m_fd < 0)
188     return -1;
189 
190   const off_t sizeOffT = (off_t) size;
191   // check for parameter overflow
192   if (sizeof(int64_t) != sizeof(off_t) && size != sizeOffT)
193     return -1;
194 
195   return ftruncate(m_fd, sizeOffT);
196 }
197 
GetPosition()198 int64_t CPosixFile::GetPosition()
199 {
200   if (m_fd < 0)
201     return -1;
202 
203   if (m_filePos < 0)
204     m_filePos = lseek(m_fd, 0, SEEK_CUR);
205 
206   return m_filePos;
207 }
208 
GetLength()209 int64_t CPosixFile::GetLength()
210 {
211   if (m_fd < 0)
212     return -1;
213 
214   struct stat64 st;
215   if (fstat64(m_fd, &st) != 0)
216     return -1;
217 
218   return st.st_size;
219 }
220 
Flush()221 void CPosixFile::Flush()
222 {
223   if (m_fd >= 0)
224     fsync(m_fd);
225 }
226 
IoControl(EIoControl request,void * param)227 int CPosixFile::IoControl(EIoControl request, void* param)
228 {
229   if (m_fd < 0)
230     return -1;
231 
232   if (request == IOCTRL_NATIVE)
233   {
234     if(!param)
235       return -1;
236     return ioctl(m_fd, ((SNativeIoControl*)param)->request, ((SNativeIoControl*)param)->param);
237   }
238   else if (request == IOCTRL_SEEK_POSSIBLE)
239   {
240     if (GetPosition() < 0)
241       return -1; // current position is unknown, can't test seeking
242     else if (m_filePos > 0)
243     {
244       const int64_t orgPos = m_filePos;
245       // try to seek one byte back
246       const bool seekPossible = (Seek(orgPos - 1, SEEK_SET) == (orgPos - 1));
247       // restore file position
248       if (Seek(orgPos, SEEK_SET) != orgPos)
249         return 0; // seeking is not possible
250 
251       return seekPossible ? 1 : 0;
252     }
253     else
254     { // m_filePos == 0
255       // try to seek one byte forward
256       const bool seekPossible = (Seek(1, SEEK_SET) == 1);
257       // restore file position
258       if (Seek(0, SEEK_SET) != 0)
259         return 0; // seeking is not possible
260 
261       if (seekPossible)
262         return 1;
263 
264       if (GetLength() <= 0)
265         return -1; // size of file is zero or can be zero, can't test seeking
266       else
267         return 0; // size of file is 1 byte or more and seeking not possible
268     }
269   }
270 
271   return -1;
272 }
273 
274 
Delete(const CURL & url)275 bool CPosixFile::Delete(const CURL& url)
276 {
277   const std::string filename(getFilename(url));
278   if (filename.empty())
279     return false;
280 
281   if (unlink(filename.c_str()) == 0)
282     return true;
283 
284   if (errno == EACCES || errno == EPERM)
285     CLog::LogF(LOGWARNING, "Can't access file \"%s\"", filename.c_str());
286 
287   return false;
288 }
289 
Rename(const CURL & url,const CURL & urlnew)290 bool CPosixFile::Rename(const CURL& url, const CURL& urlnew)
291 {
292   const std::string name(getFilename(url)), newName(getFilename(urlnew));
293   if (name.empty() || newName.empty())
294     return false;
295 
296   if (name == newName)
297     return true;
298 
299   if (rename(name.c_str(), newName.c_str()) == 0)
300     return true;
301 
302   if (errno == EACCES || errno == EPERM)
303     CLog::LogF(LOGWARNING, "Can't access file \"%s\" for rename to \"%s\"", name.c_str(), newName.c_str());
304 
305   // rename across mount points - need to copy/delete
306   if (errno == EXDEV)
307   {
308     CLog::LogF(LOGDEBUG, "Source file \"%s\" and target file \"%s\" are located on different filesystems, copy&delete will be used instead of rename", name.c_str(), newName.c_str());
309     if (XFILE::CFile::Copy(name, newName))
310     {
311       if (XFILE::CFile::Delete(name))
312         return true;
313       else
314         XFILE::CFile::Delete(newName);
315     }
316   }
317 
318   return false;
319 }
320 
Exists(const CURL & url)321 bool CPosixFile::Exists(const CURL& url)
322 {
323   const std::string filename(getFilename(url));
324   if (filename.empty())
325     return false;
326 
327   struct stat64 st;
328   return stat64(filename.c_str(), &st) == 0 && !S_ISDIR(st.st_mode);
329 }
330 
Stat(const CURL & url,struct __stat64 * buffer)331 int CPosixFile::Stat(const CURL& url, struct __stat64* buffer)
332 {
333   assert(buffer != NULL);
334   const std::string filename(getFilename(url));
335   if (filename.empty() || !buffer)
336     return -1;
337 
338 // Use statx to get file creation date (btime) which isn't available with just stat. This fills the
339 // buffer with the same data as the Windows implementation. Useful for the music library so that
340 // tags can be updated without changing the date they were added to the library (as m/ctime does)
341 
342 #if defined(HAVE_STATX)
343   int dirfd = AT_FDCWD;
344   int flags = AT_STATX_SYNC_AS_STAT;
345   unsigned int mask = STATX_BASIC_STATS | STATX_BTIME;
346   struct statx stxbuf = {};
347   long ret = 0;
348   ret = statx(dirfd, filename.c_str(), flags, mask, &stxbuf);
349   if (ret == 0)
350   {
351     buffer->st_mtime = stxbuf.stx_mtime.tv_sec; // modification time
352     if (stxbuf.stx_btime.tv_sec != 0)
353       buffer->st_ctime = stxbuf.stx_btime.tv_sec; // birth (creation) time
354     else
355       buffer->st_ctime = stxbuf.stx_ctime.tv_sec; // change time (of metadata or file)
356     // fill everything else we might need (statx buffer is slightly different to stat buffer so
357     // can't just return the statx buffer) Note we might not need all this but lets fill it for
358     // completeness
359     buffer->st_atime = stxbuf.stx_atime.tv_sec;
360     buffer->st_size = stxbuf.stx_size;
361     buffer->st_blksize = stxbuf.stx_blksize;
362     buffer->st_blocks = stxbuf.stx_blocks;
363     buffer->st_ino = stxbuf.stx_ino;
364     buffer->st_nlink = stxbuf.stx_nlink;
365     buffer->st_uid = stxbuf.stx_uid;
366     buffer->st_gid = stxbuf.stx_gid;
367     buffer->st_mode = stxbuf.stx_mode;
368     buffer->st_rdev = makedev(stxbuf.stx_rdev_major, stxbuf.stx_rdev_minor);
369     buffer->st_dev = makedev(stxbuf.stx_dev_major, stxbuf.stx_dev_minor);
370   }
371   return ret;
372 #else
373   return stat64(filename.c_str(), buffer);
374 #endif
375 }
376 
Stat(struct __stat64 * buffer)377 int CPosixFile::Stat(struct __stat64* buffer)
378 {
379   assert(buffer != NULL);
380   if (m_fd < 0 || !buffer)
381     return -1;
382 
383   return fstat64(m_fd, buffer);
384 }
385