1 /** @file fileinfo.c  File information.
2 
3 @authors Copyright (c) 2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 
5 @par License
6 
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
9 
10 1. Redistributions of source code must retain the above copyright notice, this
11    list of conditions and the following disclaimer.
12 2. Redistributions in binary form must reproduce the above copyright notice,
13    this list of conditions and the following disclaimer in the documentation
14    and/or other materials provided with the distribution.
15 
16 <small>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</small>
26 */
27 
28 #include "the_Foundation/fileinfo.h"
29 #include "the_Foundation/file.h"
30 #include "the_Foundation/time.h"
31 #include "the_Foundation/path.h"
32 
33 #include <stdlib.h>
34 #if defined (iPlatformWindows) || defined (iPlatformMsys)
35 #   define iHaveMsdirent 1
36 #   include "platform/win32/wide.h"
37 #   include "platform/win32/msdirent.h"
38 
39 #   if defined (iPlatformMsys)
40 #       define R_OK 0
41 
access(const char * path,int mode)42 static int access(const char *path, int mode) {
43     const iString str = iStringLiteral(path);
44     iBlock *wpath = toUtf16_String(&str);
45     const DWORD attr = GetFileAttributesW(data_Block(wpath));
46     delete_Block(wpath);
47     iUnused(mode);
48     return (attr == INVALID_FILE_ATTRIBUTES ? -1 : 0);
49 }
50 #   endif /* defined (iPlatformMsys) */
51 
52 #else
53 #   include <sys/stat.h>
54 #   include <sys/types.h>
55 #   if defined (iHaveSysDirent)
56 #       include <sys/dirent.h>
57 #   elif !defined (iPlatformHaiku)
58 #       include <sys/dir.h>
59 #   endif
60 #   include <unistd.h>
61 #   include <dirent.h>
62 #endif
63 
64 #if defined (iPlatformLinux) || defined (iPlatformMsys) || defined (iPlatformCygwin) || defined (iPlatformHaiku)
65 #   define st_mtimespec st_mtim
66 #endif
67 
68 enum FileInfoFlags {
69     exists_FileInfoFlag     = iBit(1),
70     directory_FileInfoFlag  = iBit(2),
71 };
72 
73 struct Impl_FileInfo {
74     iObject object;
75     iString *path;
76     iTime lastModified;
77     size_t size;
78     uint32_t flags;
79 };
80 
81 iDefineClass(FileInfo)
82 iDefineObjectConstructionArgs(FileInfo, (const iString *path), path)
83 
newCStr_FileInfo(const char * path)84 iFileInfo *newCStr_FileInfo(const char *path) {
85     iString str; initCStr_String(&str, path);
86     iFileInfo *info = new_FileInfo(&str);
87     deinit_String(&str);
88     return info;
89 }
90 
new_FileInfo_(void)91 iFileInfo *new_FileInfo_(void) {
92     iFileInfo *d = iNew(FileInfo);
93     d->path = new_String();
94     iZap(d->lastModified);
95     d->size = 0;
96     d->flags = 0;
97     return d;
98 }
99 
init_FileInfo(iFileInfo * d,const iString * path)100 void init_FileInfo(iFileInfo *d, const iString *path) {
101     d->path = copy_String(path);
102     d->flags = 0;
103     struct stat st;
104     if (!stat(cstr_String(d->path), &st)) {
105 #if defined (iPlatformWindows)
106         d->lastModified.ts = (struct timespec){ .tv_sec = st.st_mtime };
107 #else
108         d->lastModified.ts = st.st_mtimespec;
109 #endif
110         d->size = st.st_size;
111         d->flags |= exists_FileInfoFlag;
112         if (st.st_mode & S_IFDIR) d->flags |= directory_FileInfoFlag;
113     }
114     else {
115         iZap(d->lastModified);
116         d->size = iInvalidSize;
117     }
118 }
119 
initDirEntry_FileInfo_(iFileInfo * d,const iString * dirPath,struct dirent * ent)120 static iBool initDirEntry_FileInfo_(iFileInfo *d, const iString *dirPath, struct dirent *ent) {
121     iString entryName;
122 #if defined (iPlatformApple)
123     initLocalCStrN_String(&entryName, ent->d_name, ent->d_namlen);
124 #elif defined (iHaveMsdirent)
125     initCStrN_String(&entryName, ent->d_name, ent->d_namlen); /* UTF-8 name */
126 #else
127     initLocalCStr_String(&entryName, ent->d_name);
128 #endif
129     /* Check for ignored entries. */
130     if (!cmp_String(&entryName, "..") || !cmp_String(&entryName, ".")) {
131         deinit_String(&entryName);
132         return iFalse;
133     }
134     iString *full = concat_Path(dirPath, &entryName);
135     clean_Path(full);
136     set_String(d->path, full);
137     delete_String(full);
138     deinit_String(&entryName);
139 
140     d->flags = exists_FileInfoFlag;
141 #if defined (iPlatformHaiku)
142     struct stat s;
143     stat(ent->d_name, &s);
144     if (S_ISDIR(s.st_mode)) {
145 #else
146     if (ent->d_type == DT_DIR) {
147 #endif
148         d->flags |= directory_FileInfoFlag;
149         d->size = 0;
150     }
151     else {
152         d->size = iInvalidSize; // Unknown at this time.
153     }
154     return iTrue;
155 }
156 
157 void deinit_FileInfo(iFileInfo *d) {
158     delete_String(d->path);
159 }
160 
161 iBool exists_FileInfo(const iFileInfo *d) {
162     return (d->flags & exists_FileInfoFlag) != 0;
163 }
164 
165 const iString *path_FileInfo(const iFileInfo *d) {
166     return d->path;
167 }
168 
169 size_t size_FileInfo(const iFileInfo *d) {
170     if (d->size == iInvalidSize) {
171         iConstCast(iFileInfo *, d)->size = fileSize_FileInfo(d->path);
172     }
173     return d->size;
174 }
175 
176 iBool isDirectory_FileInfo(const iFileInfo *d) {
177     return (d->flags & directory_FileInfoFlag) != 0;
178 }
179 
180 iTime lastModified_FileInfo(const iFileInfo *d) {
181     if (!isValid_Time(&d->lastModified)) {
182         struct stat st;
183         if (!stat(cstr_String(d->path), &st)) {
184 #if defined (iPlatformWindows)
185             iConstCast(iFileInfo *, d)->lastModified.ts = (struct timespec){
186                 .tv_sec = st.st_mtime };
187 #else
188             iConstCast(iFileInfo *, d)->lastModified.ts = st.st_mtimespec;
189 #endif
190         }
191     }
192     return d->lastModified;
193 }
194 
195 iDirFileInfo *directoryContents_FileInfo(const iFileInfo *fileInfo) {
196     iDirFileInfo *d = iNew(DirFileInfo);
197     initInfo_DirFileInfo(d, fileInfo);
198     return d;
199 }
200 
201 iFile *open_FileInfo(const iFileInfo *d, int modeFlags) {
202     iFile *f = new_File(path_FileInfo(d));
203     open_File(f, modeFlags);
204     return f;
205 }
206 
207 iBool fileExists_FileInfo(const iString *path) {
208     return fileExistsCStr_FileInfo(cstr_String(path));
209 }
210 
211 iBool fileExistsCStr_FileInfo(const char *path) {
212     return !access(path, R_OK);
213 }
214 
215 size_t fileSize_FileInfo(const iString *path) {
216     return fileSizeCStr_FileInfo(cstr_String(path));
217 }
218 
219 size_t fileSizeCStr_FileInfo(const char *path) {
220     size_t size = iInvalidSize;
221 #if defined (iHaveMsdirent)
222     iBeginCollect();
223     const wchar_t *wpath = toWide_CStr_(path);
224     HANDLE f = CreateFileW(wpath,
225                            FILE_READ_ATTRIBUTES,
226                            FILE_SHARE_READ,
227                            NULL,
228                            OPEN_EXISTING,
229                            0,
230                            NULL);
231     if (f != INVALID_HANDLE_VALUE) {
232         BY_HANDLE_FILE_INFORMATION info;
233         GetFileInformationByHandle(f, &info);
234         size = (size_t) info.nFileSizeLow | (((size_t) info.nFileSizeHigh) << 32);
235         CloseHandle(f);
236     }
237     else {
238         if (GetFileAttributesW(wpath) & FILE_ATTRIBUTE_DIRECTORY) {
239             size = 0;
240         }
241     }
242     iEndCollect();
243 #else
244     struct stat st;
245     if (!stat(path, &st)) {
246         size = (size_t) st.st_size;
247     }
248 #endif
249     return size;
250 }
251 
252 /*-------------------------------------------------------------------------------------*/
253 
254 struct Impl_DirFileInfo {
255     iObject object;
256     const iFileInfo *dirInfo;
257     DIR *fd;
258     iFileInfo *entry;
259 };
260 
261 iDefineClass(DirFileInfo)
262 iDefineObjectConstructionArgs(DirFileInfo, (const iString *path), path)
263 
264 iDirFileInfo *newCStr_DirFileInfo(const char *path) {
265     iString str; initCStr_String(&str, path);
266     iDirFileInfo *d = new_DirFileInfo(&str);
267     deinit_String(&str);
268     return d;
269 }
270 
271 void init_DirFileInfo(iDirFileInfo *d, const iString *path) {
272     iFileInfo *fileInfo = new_FileInfo(path);
273     initInfo_DirFileInfo(d, fileInfo);
274     iRelease(fileInfo);
275     d->entry = NULL;
276 }
277 
278 void initInfo_DirFileInfo(iDirFileInfo *d, const iFileInfo *dir) {
279     if (isDirectory_FileInfo(dir)) {
280         d->fd = opendir(cstr_String(path_FileInfo(dir)));
281         d->dirInfo = ref_Object(dir);
282     }
283     else {
284         d->fd = NULL;
285         d->dirInfo = NULL;
286     }
287     d->entry = NULL;
288 }
289 
290 void deinit_DirFileInfo(iDirFileInfo *d) {
291     if (d->fd) {
292         closedir(d->fd);
293         d->fd = NULL;
294     }
295     iRelease(d->entry);
296     deref_Object(d->dirInfo);
297 }
298 
299 static iBool readNextEntry_DirFileInfo_(iDirFileInfo *d) {
300     iReleasePtr(&d->entry);
301     if (!d->fd) {
302         return iFalse;
303     }
304     for (;;) {
305         struct dirent *result = NULL;
306 #if defined (iPlatformApple)
307         struct dirent ent;
308         readdir_r(d->fd, &ent, &result);
309 #else
310         result = readdir(d->fd);
311 #endif
312         if (result) {
313             iReleasePtr(&d->entry);
314             d->entry = new_FileInfo_();
315             if (!initDirEntry_FileInfo_(d->entry, path_FileInfo(d->dirInfo), result)) {
316                 continue;
317             }
318             return iTrue;
319         }
320         return iFalse;
321     }
322 }
323 
324 void init_DirFileInfoIterator(iDirFileInfoIterator *d, iDirFileInfo *info) {
325     d->dir = info;
326     next_DirFileInfoIterator(d);
327 }
328 
329 void next_DirFileInfoIterator(iDirFileInfoIterator *d) {
330     if (readNextEntry_DirFileInfo_(d->dir)) {
331         d->value = d->dir->entry;
332     }
333     else {
334         d->value = NULL;
335     }
336 }
337