· 7 years ago · Nov 05, 2018, 07:02 AM
1-- A portable filesystem API using LuaJIT's FFI
2--
3local ffi = require("ffi")
4local table = require("table")
5require("string")
6-- Cache needed functions and locals
7local C, errno, string = ffi.C, ffi.errno, ffi.string
8local concat, insert = table.concat, table.insert
9
10-- "Standard" C99 functions
11ffi.cdef[[
12char *strerror(int errnum);
13]]
14
15local exists, listdir, mkdir, mtime, PATH_SEPERATOR
16if ffi.os == "Windows" then
17 ffi.cdef[[
18 struct _finddata_t {
19 unsigned attrib;
20 __time32_t time_create;
21 __time32_t time_access;
22 __time32_t time_write;
23 unsigned long size;
24 char name[260];
25 };
26 struct __stat64 {
27 unsigned int st_dev;
28 unsigned short st_ino;
29 unsigned short st_mode;
30 short st_nlink;
31 short st_uid;
32 short st_gid;
33 unsigned int st_rdev;
34 int64_t st_size;
35 long long st_atime;
36 long long st_mtime;
37 long long st_ctime;
38 };
39 int _access(const char *path, int mode);
40 intptr_t _findfirst(const char *filespec, struct _finddata_t *fileinfo);
41 int _findnext(intptr_t handle, struct _finddata_t *fileinfo);
42 int _findclose(intptr_t handle);
43 bool CreateDirectoryA(const char *path, void *lpSecurityAttributes);
44 int _stat64(const char *path, struct __stat64 *buffer);
45 ]]
46 local _finddata_t = ffi.typeof("struct _finddata_t")
47 local __stat64 = ffi.typeof("struct __stat64")
48 function exists(path)
49 assert(type(path) == "string", "path isn't a string")
50 return C._access(path, 0) == 0 -- Check existence
51 end
52 function listdir(path)
53 local data = _finddata_t()
54 local handle
55 local function nextDir()
56 if handle == nil then
57 local result = C._findfirst(path .. "/*", data)
58 if result == -1 then
59 error("error iterating over directory '" .. path .. "' (" .. string(C.strerror(errno())) .. ")")
60 else
61 handle = result
62 result = string(data.name)
63 if result == "." or result == ".." then
64 return nextDir()
65 else
66 return result
67 end
68 end
69 else
70 local result = C._findnext(handle, data)
71 if result ~= 0 then
72 if errno() == 2 then
73 -- We're done
74 C._findclose(handle)
75 data = nil
76 handle = nil
77 return nil
78 else
79 error("error iterating over directory '" .. path .. "' (" .. string(C.strerror(errno())) .. ")")
80 end
81 else
82 result = string(data.name)
83 if result == "." or result == ".." then
84 return nextDir()
85 else
86 return result
87 end
88 end
89 end
90 end
91 return nextDir
92 end
93 function mkdir(path)
94 assert(type(path) == "string", "path isn't a string")
95 if not C.CreateDirectoryA(path, nil) then
96 error("unable to create directory '" .. path .. "' (" .. string(C.strerror(errno())) .. ")")
97 end
98 end
99 function mtime(path)
100 local buffer = __stat64()
101 if C._stat64(path, buffer) == -1 then
102 error("error getting modification time for '" .. path .. "' (" .. string(C.strerror(errno())) .. ")")
103 end
104 return tonumber(buffer.st_mtime)
105 end
106 PATH_SEPERATOR = "\\"
107elseif ffi.os == "Linux" or ffi.os == "OSX" then
108 ffi.cdef[[
109 struct dirent {
110 unsigned long int d_ino;
111 long int d_off;
112 unsigned short d_reclen;
113 unsigned char d_type;
114 char name[256];
115 };
116 typedef struct __dirstream DIR;
117 int access(const char *path, int amode);
118 DIR *opendir(const char *name);
119 struct dirent *readdir(DIR *dirp);
120 int closedir(DIR *dirp);
121 int mkdir(const char *path, int mode);
122 typedef size_t time_t;
123 ]]
124 local stat_func
125 if ffi.os == "Linux" then
126 ffi.cdef[[
127 long syscall(int number, ...);
128 ]]
129 local stat_syscall_num
130 if ffi.arch == "x64" then
131 ffi.cdef[[
132 struct stat {
133 unsigned long st_dev;
134 unsigned long st_ino;
135 unsigned long st_nlink;
136 unsigned int st_mode;
137 unsigned int st_uid;
138 unsigned int st_gid;
139 unsigned int __pad0;
140 unsigned long st_rdev;
141 long st_size;
142 long st_blksize;
143 long st_blocks;
144 unsigned long st_atime;
145 unsigned long st_atime_nsec;
146 unsigned long st_mtime;
147 unsigned long st_mtime_nsec;
148 unsigned long st_ctime;
149 unsigned long st_ctime_nsec;
150 long __unused[3];
151 };
152 ]]
153 stat_syscall_num = 4
154 elseif ffi.arch == "x86" then
155 ffi.cdef[[
156 struct stat {
157 unsigned long long st_dev;
158 unsigned char __pad0[4];
159 unsigned long __st_ino;
160 unsigned int st_mode;
161 unsigned int st_nlink;
162 unsigned long st_uid;
163 unsigned long st_gid;
164 unsigned long long st_rdev;
165 unsigned char __pad3[4];
166 long long st_size;
167 unsigned long st_blksize;
168 unsigned long long st_blocks;
169 unsigned long st_atime;
170 unsigned long st_atime_nsec;
171 unsigned long st_mtime;
172 unsigned int st_mtime_nsec;
173 unsigned long st_ctime;
174 unsigned long st_ctime_nsec;
175 unsigned long long st_ino;
176 };
177 ]]
178 stat_syscall_num = ffi.abi("64bit") and 106 or 195
179 elseif ffi.arch == "arm" then
180 if ffi.abi("64bit") then
181 ffi.cdef[[
182 struct stat {
183 unsigned long st_dev;
184 unsigned long st_ino;
185 unsigned int st_mode;
186 unsigned int st_nlink;
187 unsigned int st_uid;
188 unsigned int st_gid;
189 unsigned long st_rdev;
190 unsigned long __pad1;
191 long st_size;
192 int st_blksize;
193 int __pad2;
194 long st_blocks;
195 long st_atime;
196 unsigned long st_atime_nsec;
197 long st_mtime;
198 unsigned long st_mtime_nsec;
199 long st_ctime;
200 unsigned long st_ctime_nsec;
201 unsigned int __unused4;
202 unsigned int __unused5;
203 };
204 ]]
205 stat_syscall_num = 106
206 else
207 ffi.cdef[[
208 struct stat {
209 unsigned long long st_dev;
210 unsigned char __pad0[4];
211 unsigned long __st_ino;
212 unsigned int st_mode;
213 unsigned int st_nlink;
214 unsigned long st_uid;
215 unsigned long st_gid;
216 unsigned long long st_rdev;
217 unsigned char __pad3[4];
218 long long st_size;
219 unsigned long st_blksize;
220 unsigned long long st_blocks;
221 unsigned long st_atime;
222 unsigned long st_atime_nsec;
223 unsigned long st_mtime;
224 unsigned int st_mtime_nsec;
225 unsigned long st_ctime;
226 unsigned long st_ctime_nsec;
227 unsigned long long st_ino;
228 };
229 ]]
230 stat_syscall_num = 195
231 end
232 elseif ffi.arch == "ppc" or ffi.arch == "ppcspe" then
233 ffi.cdef[[
234 struct stat {
235 unsigned long long st_dev;
236 unsigned long long st_ino;
237 unsigned int st_mode;
238 unsigned int st_nlink;
239 unsigned int st_uid;
240 unsigned int st_gid;
241 unsigned long long st_rdev;
242 unsigned long long __pad1;
243 long long st_size;
244 int st_blksize;
245 int __pad2;
246 long long st_blocks;
247 int st_atime;
248 unsigned int st_atime_nsec;
249 int st_mtime;
250 unsigned int st_mtime_nsec;
251 int st_ctime;
252 unsigned int st_ctime_nsec;
253 unsigned int __unused4;
254 unsigned int __unused5;
255 };
256 ]]
257 stat_syscall_num = ffi.abi("64bit") and 106 or 195
258 elseif ffi.arch == "mips" or ffi.arch == "mipsel" then
259 ffi.cdef[[
260 struct stat {
261 unsigned long st_dev;
262 unsigned long __st_pad0[3];
263 unsigned long long st_ino;
264 mode_t st_mode;
265 nlink_t st_nlink;
266 uid_t st_uid;
267 gid_t st_gid;
268 unsigned long st_rdev;
269 unsigned long __st_pad1[3];
270 long long st_size;
271 time_t st_atime;
272 unsigned long st_atime_nsec;
273 time_t st_mtime;
274 unsigned long st_mtime_nsec;
275 time_t st_ctime;
276 unsigned long st_ctime_nsec;
277 unsigned long st_blksize;
278 unsigned long __st_pad2;
279 long long st_blocks;
280 long __st_padding4[14];
281 };
282 ]]
283 stat_syscall_num = ffi.abi("64bit") and 4106 or 4213
284 end
285 if stat_syscall_num then
286 stat_func = function(path, buffer)
287 return C.syscall(stat_syscall_num, path, buffer)
288 end
289 else
290 stat_func = function(path, buffer)
291 error("unsupported architecture (" .. ffi.arch .. ")")
292 end
293 end
294 elseif ffi.os == "OSX" then
295 ffi.cdef[[
296 struct timespec {
297 time_t tv_sec;
298 long tv_nsec;
299 };
300 struct stat {
301 uint32_t st_dev;
302 uint16_t st_mode;
303 uint16_t st_nlink;
304 uint64_t st_ino;
305 uint32_t st_uid;
306 uint32_t st_gid;
307 uint32_t st_rdev;
308 struct timespec st_atimespec;
309 struct timespec st_mtimespec;
310 struct timespec st_ctimespec;
311 struct timespec st_birthtimespec;
312 int64_t st_size;
313 int64_t st_blocks;
314 int32_t st_blksize;
315 uint32_t st_flags;
316 uint32_t st_gen;
317 int32_t st_lspare;
318 int64_t st_qspare[2];
319 };
320 int stat64(const char *path, struct stat *buf);
321 ]]
322 stat_func = C.stat64
323 end
324 local stat = ffi.typeof("struct stat")
325 function exists(path)
326 assert(type(path) == "string", "path isn't a string")
327 return C.access(path, 0) == 0 -- Check existence
328 end
329 function listdir(path)
330 local dir = C.opendir(path)
331 if dir == nil then
332 error("error opening directory '" .. dir .. "' (" .. string(C.strerror(errno())) .. ")")
333 end
334 local function nextDir()
335 local entry = C.readdir(dir)
336 if entry ~= nil then
337 local result = string(entry.name)
338 if result == "." or result == ".." then
339 return nextDir()
340 else
341 return result
342 end
343 else
344 C.closedir(dir)
345 dir = nil
346 return nil
347 end
348 end
349 return nextDir
350 end
351 function mkdir(path, mode)
352 assert(type(path) == "string", "path isn't a string")
353 if C.mkdir(path, tonumber(mode or "755", 8)) ~= 0 then
354 error("Unable to create directory " .. path .. ": " .. string(C.strerror(errno())))
355 end
356 end
357 if ffi.os == "Linux" then
358 function mtime(path)
359 local buffer = stat()
360 if stat_func(path, buffer) == -1 then
361 error("error getting modification time for '" .. path .. "' (" .. string(C.strerror(errno())) .. ")")
362 end
363 return tonumber(buffer.st_mtime)
364 end
365 elseif ffi.os == "OSX" then
366 function mtime(path)
367 local buffer = stat()
368 if stat_func(path, buffer) == -1 then
369 error("error getting modification time for '" .. path .. "' (" .. string(C.strerror(errno())) .. ")")
370 end
371 return tonumber(buffer.st_mtimespec.tv_sec)
372 end
373 end
374 PATH_SEPERATOR = "/"
375else
376 error("unsupported operating system (" .. ffi.os .. ")")
377end
378
379local function join(...)
380 local parts = {}
381 for i = 1, select("#", ...) do
382 insert(parts, select(i, ...))
383 end
384 return concat(parts, PATH_SEPERATOR)
385end
386
387local function splitPath(path)
388 assert(type(path) == "string", "path isn't a string")
389 local parts = {}
390 local lastIndex = 0
391 for i = 1, path:len() do
392 local c = path:sub(i, i)
393 if c == "/" or c == "\\" then
394 insert(parts, path:sub(lastIndex, i - 1))
395 lastIndex = i + 1
396 end
397 end
398 insert(parts, path:sub(lastIndex))
399 return parts
400end
401
402local function mkdirs(path)
403 local parts = splitPath(path)
404 local currentPath = parts[1]
405 for i = 2, #parts do
406 if not exists(currentPath) then
407 mkdir(currentPath)
408 end
409 -- Note: This isn't suboptimal, since we really do need the intermediate results
410 currentPath = currentPath .. PATH_SEPERATOR .. parts[i]
411 end
412 if not exists(path) then
413 mkdir(path)
414 end
415end
416
417return {
418 exists = exists,
419 join = join,
420 mkdir = mkdir,
421 mkdirs = mkdirs,
422 splitPath = splitPath,
423 listdir = listdir,
424 mtime = mtime,
425 PATH_SEPERATOR = PATH_SEPERATOR
426}