├── .gitignore ├── .luacheckrc ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── dist.ini ├── lfs.lua ├── lfs_ffi.lua ├── lfs_spec.lua ├── lock_unlock.lua ├── test_lock.lua ├── test_valgrind.lua ├── travis.sh └── valgrind.suppress /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Lua sources 2 | luac.out 3 | 4 | # luarocks build files 5 | *.src.rock 6 | *.zip 7 | *.tar.gz 8 | 9 | # Object files 10 | *.o 11 | *.os 12 | *.ko 13 | *.obj 14 | *.elf 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Libraries 21 | *.lib 22 | *.a 23 | *.la 24 | *.lo 25 | *.def 26 | *.exp 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | luafilesystem-* 43 | .luacheckcache 44 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | cache = true 2 | std = 'ngx_lua' 3 | files["*_spec.lua"].std = "+busted" 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: python 3 | 4 | matrix: 5 | include: 6 | - os: linux 7 | env: LUA="luajit 2.0" 8 | - os: linux 9 | env: LUA="luajit 2.1" 10 | - os: osx 11 | language: generic 12 | env: LUA="luajit 2.1" 13 | 14 | before_install: 15 | - pip install hererocks 16 | - hererocks lua_install -r^ --$LUA # install all stuff into ./lua_install 17 | - export PATH=$PATH:$PWD/lua_install/bin 18 | 19 | install: 20 | - luarocks install luacheck 21 | - luarocks install busted 22 | 23 | script: 24 | - luacheck --std max+busted *.lua 25 | - busted --verbose . || exit 1 26 | - sudo bash travis.sh 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 罗泽轩 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # luafilesystem 2 | 3 | [![Build Status](https://travis-ci.org/spacewander/luafilesystem.svg?branch=master)](https://travis-ci.org/spacewander/luafilesystem) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/52d2x1frvksf4u1h/branch/master?svg=true)](https://ci.appveyor.com/project/spacewander/luafilesystem/branch/master) 5 | 6 | 7 | Reimplement luafilesystem via LuaJIT FFI. 8 | 9 | ## Docs 10 | 11 | It should be compatible with vanilla luafilesystem but with unicode paths in windows: 12 | http://keplerproject.github.io/luafilesystem/manual.html#reference 13 | 14 | What you only need is replacing `require('lfs')` to `require('lfs_ffi')`.` 15 | 16 | On windows `lfs.dir` iterator will provide an extra return that can be used to get `mode` and `size` attributes in a much more performant way. 17 | 18 | This is the canonical way to iterate: 19 | 20 | ```Lua 21 | local sep = "/" 22 | for file,obj in lfs.dir(path) do 23 | if file ~= "." and file ~= ".." then 24 | local f = path..sep..file 25 | -- obj wont be nil in windows only 26 | local attr = obj and obj:attr() or lfs.attributes (f) 27 | assert (type(attr) == "table",f) 28 | -- do something with f and attr 29 | end 30 | end 31 | ``` 32 | 33 | ## Installation 34 | 35 | ```` 36 | cmake -DLUAJIT_DIR="path to luajit" ../luafilesystem 37 | make install 38 | ```` 39 | 40 | or just copy `lfs_ffi.lua` to lua folder 41 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 0.0.1.{build}-test 2 | shallow_clone: true 3 | matrix: 4 | fast_finish: true 5 | 6 | environment: 7 | matrix: 8 | - LUA: "luajit 2.1" 9 | 10 | 11 | platform: 12 | - x64 13 | - x86 14 | 15 | install: 16 | - set PATH=C:\Python35\scripts;%PATH% 17 | - pip install hererocks 18 | - hererocks env --%LUA% -rlatest 19 | - call env\bin\activate 20 | - luarocks install busted 21 | 22 | build_script: 23 | - echo 'Start to test' 24 | 25 | test_script: 26 | - busted --verbose . 27 | - lua test_lock.lua -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name=luafilesystem 2 | abstract=Reimplement luafilesystem via LuaJIT FFI 3 | author=spacewander 4 | is_original=yes 5 | license=mit 6 | lib_dir=. 7 | doc_dir=. 8 | exclude_files=*_spec.lua lock_unlock.lua test_lock.lua 9 | repo_link=https://github.com/spacewander/luafilesystem 10 | -------------------------------------------------------------------------------- /lfs.lua: -------------------------------------------------------------------------------- 1 | return require"lfs_ffi" -------------------------------------------------------------------------------- /lfs_ffi.lua: -------------------------------------------------------------------------------- 1 | local bit = require "bit" 2 | local ffi = require "ffi" 3 | 4 | 5 | local band = bit.band 6 | local rshift = bit.rshift 7 | local lib = ffi.C 8 | local ffi_str = ffi.string 9 | local concat = table.concat 10 | local has_table_new, new_tab = pcall(require, "table.new") 11 | if not has_table_new or type(new_tab) ~= "function" then 12 | new_tab = function () return {} end 13 | end 14 | 15 | 16 | local _M = { 17 | _VERSION = "0.1", 18 | } 19 | 20 | -- common utils/constants 21 | local IS_64_BIT = ffi.abi('64bit') 22 | local ERANGE = 'Result too large' 23 | 24 | if not pcall(ffi.typeof, "ssize_t") then 25 | -- LuaJIT 2.0 doesn't have ssize_t as a builtin type, let's define it 26 | ffi.cdef("typedef intptr_t ssize_t") 27 | end 28 | 29 | ffi.cdef([[ 30 | char* strerror(int errnum); 31 | ]]) 32 | 33 | local function errno() 34 | return ffi_str(lib.strerror(ffi.errno())) 35 | end 36 | 37 | local OS = ffi.os 38 | -- sys/syslimits.h 39 | local MAXPATH 40 | local MAXPATH_UNC = 32760 41 | local HAVE_WFINDFIRST = true 42 | local wchar_t 43 | local win_utf8_to_unicode 44 | local win_unicode_to_utf8 45 | if OS == 'Windows' then 46 | MAXPATH = MAXPATH_UNC --260 47 | ffi.cdef([[ 48 | typedef int mbstate_t; 49 | /* 50 | In VC2015, M$ change the definition of mbstate_t to this and breaks the ABI. 51 | */ 52 | typedef struct _Mbstatet 53 | { // state of a multibyte translation 54 | unsigned long _Wchar; 55 | unsigned short _Byte, _State; 56 | } _Mbstatet; 57 | typedef _Mbstatet mbstate_t; 58 | 59 | size_t mbrtowc(wchar_t* pwc, 60 | const char* s, 61 | size_t n, 62 | mbstate_t* ps); 63 | ]]) 64 | 65 | function wchar_t(s) 66 | local mbstate = ffi.new('mbstate_t[1]') 67 | local wcs = ffi.new('wchar_t[?]', #s + 1) 68 | local i = 0 69 | local offset = 0 70 | local len = #s 71 | while true do 72 | local processed = lib.mbrtowc( 73 | wcs + i, ffi.cast('const char *', s) + offset, len, mbstate) 74 | if processed <= 0 then break end 75 | i = i + 1 76 | offset = offset + processed 77 | len = len - processed 78 | end 79 | return wcs 80 | end 81 | 82 | elseif OS == 'Linux' then 83 | MAXPATH = 4096 84 | else 85 | MAXPATH = 1024 86 | end 87 | 88 | -- misc 89 | if OS == "Windows" then 90 | local utime_def 91 | if IS_64_BIT then 92 | utime_def = [[ 93 | typedef __int64 time_t; 94 | struct __utimebuf64 { 95 | time_t actime; 96 | time_t modtime; 97 | }; 98 | typedef struct __utimebuf64 utimebuf; 99 | int _utime64(unsigned char *file, utimebuf *times); 100 | ]] 101 | else 102 | utime_def = [[ 103 | typedef __int32 time_t; 104 | struct __utimebuf32 { 105 | time_t actime; 106 | time_t modtime; 107 | }; 108 | typedef struct __utimebuf32 utimebuf; 109 | int _utime632(unsigned char *file, utimebuf *times); 110 | ]] 111 | end 112 | 113 | ffi.cdef([[ 114 | char *_getcwd(char *buf, size_t size); 115 | wchar_t *_wgetcwd(wchar_t *buf, size_t size); 116 | int _chdir(const char *path); 117 | int _wchdir(const wchar_t *path); 118 | int _rmdir(const char *pathname); 119 | int _wrmdir(const wchar_t *pathname); 120 | int _mkdir(const char *pathname); 121 | int _wmkdir(const wchar_t *pathname); 122 | ]] .. utime_def .. [[ 123 | typedef wchar_t* LPTSTR; 124 | typedef unsigned char BOOLEAN; 125 | typedef unsigned long DWORD; 126 | BOOLEAN CreateSymbolicLinkW( 127 | LPTSTR lpSymlinkFileName, 128 | LPTSTR lpTargetFileName, 129 | DWORD dwFlags 130 | ); 131 | 132 | int _fileno(struct FILE *stream); 133 | int _setmode(int fd, int mode); 134 | ]]) 135 | 136 | ffi.cdef([[ 137 | 138 | size_t wcslen(const wchar_t *str); 139 | wchar_t *wcsncpy(wchar_t *strDest, const wchar_t *strSource, size_t count); 140 | 141 | int WideCharToMultiByte( 142 | unsigned int CodePage, 143 | DWORD dwFlags, 144 | const wchar_t* lpWideCharStr, 145 | int cchWideChar, 146 | char* lpMultiByteStr, 147 | int cbMultiByte, 148 | const char* lpDefaultChar, 149 | int* lpUsedDefaultChar); 150 | 151 | int MultiByteToWideChar( 152 | unsigned int CodePage, 153 | DWORD dwFlags, 154 | const char* lpMultiByteStr, 155 | int cbMultiByte, 156 | wchar_t* lpWideCharStr, 157 | int cchWideChar); 158 | 159 | ]]) 160 | ffi.cdef[[ 161 | 162 | uint32_t GetLastError(); 163 | uint32_t FormatMessageA( 164 | uint32_t dwFlags, 165 | const void* lpSource, 166 | uint32_t dwMessageId, 167 | uint32_t dwLanguageId, 168 | char* lpBuffer, 169 | uint32_t nSize, 170 | va_list *Arguments 171 | ); 172 | ]] 173 | -- Some helper functions 174 | local function error_win(lvl) 175 | local errcode = ffi.C.GetLastError() 176 | local str = ffi.new("char[?]",1024) 177 | local FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; 178 | local FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; 179 | local numout = ffi.C.FormatMessageA(bit.bor(FORMAT_MESSAGE_FROM_SYSTEM, 180 | FORMAT_MESSAGE_IGNORE_INSERTS), nil, errcode, 0, str, 1023, nil) 181 | if numout == 0 then 182 | error("Windows Error: (Error calling FormatMessage)", lvl) 183 | else 184 | error("Windows Error: "..ffi.string(str, numout), lvl) 185 | end 186 | end 187 | local CP_UTF8 = 65001 188 | local WC_ERR_INVALID_CHARS = 0x00000080 189 | local MB_ERR_INVALID_CHARS = 0x00000008 190 | function win_utf8_to_unicode(szUtf8) 191 | local dwFlags = _M.unicode_errors and MB_ERR_INVALID_CHARS or 0 192 | local nLenWchar = lib.MultiByteToWideChar(CP_UTF8, dwFlags, szUtf8, -1, nil, 0 ); 193 | if nLenWchar ==0 then error_win(2) end 194 | local szUnicode = ffi.new("wchar_t[?]",nLenWchar) 195 | nLenWchar = lib.MultiByteToWideChar(CP_UTF8, dwFlags, szUtf8, -1, szUnicode, nLenWchar); 196 | if nLenWchar ==0 then error_win(2) end 197 | return szUnicode, nLenWchar 198 | end 199 | _M.win_utf8_to_unicode = win_utf8_to_unicode 200 | function win_unicode_to_utf8( szUnicode) 201 | local dwFlags = _M.unicode_errors and WC_ERR_INVALID_CHARS or 0 202 | local nLen = lib.WideCharToMultiByte(CP_UTF8, dwFlags, szUnicode, -1, nil, 0, nil, nil); 203 | if nLen ==0 then error_win(2) end 204 | local str = ffi.new("char[?]",nLen) 205 | nLen = lib.WideCharToMultiByte(CP_UTF8, dwFlags, szUnicode, -1, str, nLen, nil, nil); 206 | if nLen ==0 then error_win(2) end 207 | return str 208 | end 209 | _M.win_unicode_to_utf8 = win_unicode_to_utf8 210 | local CP_ACP = 0 211 | function _M.win_utf8_to_acp(utf) 212 | local szUnicode = win_utf8_to_unicode(utf) 213 | local dwFlags = _M.unicode_errors and WC_ERR_INVALID_CHARS or 0 214 | local nLen = lib.WideCharToMultiByte(CP_ACP, dwFlags, szUnicode, -1, nil, 0, nil, nil); 215 | if nLen ==0 then error_win(2) end 216 | local str = ffi.new("char[?]",nLen) 217 | nLen = lib.WideCharToMultiByte(CP_ACP, dwFlags, szUnicode, -1, str, nLen, nil, nil); 218 | if nLen ==0 then error_win(2) end 219 | return ffi_str(str) 220 | end 221 | function _M.chdir(path) 222 | if _M.unicode then 223 | local uncstr = win_utf8_to_unicode(path) 224 | if lib._wchdir(uncstr) == 0 then return true end 225 | else 226 | if type(path) ~= 'string' then 227 | error('path should be a string') 228 | end 229 | if lib._chdir(path) == 0 then 230 | return true 231 | end 232 | end 233 | return nil, errno() 234 | end 235 | 236 | function _M.currentdir() 237 | if _M.unicode then 238 | local buf = ffi.new("wchar_t[?]",MAXPATH_UNC) 239 | if lib._wgetcwd(buf, MAXPATH_UNC) ~= nil then 240 | local buf_utf = win_unicode_to_utf8(buf) 241 | return ffi_str(buf_utf) 242 | end 243 | error("error in currentdir") 244 | else 245 | local size = MAXPATH 246 | while true do 247 | local buf = ffi.new("char[?]", size) 248 | if lib._getcwd(buf, size) ~= nil then 249 | return ffi_str(buf) 250 | end 251 | local err = errno() 252 | if err ~= ERANGE then 253 | return nil, err 254 | end 255 | size = size * 2 256 | end 257 | end 258 | end 259 | 260 | function _M.mkdir(path) 261 | if _M.unicode then 262 | local unc_str = win_utf8_to_unicode(path) 263 | if lib._wmkdir(unc_str) == 0 then 264 | return true 265 | end 266 | else 267 | if type(path) ~= 'string' then 268 | error('path should be a string') 269 | end 270 | if lib._mkdir(path) == 0 then 271 | return true 272 | end 273 | end 274 | return nil, errno() 275 | end 276 | 277 | function _M.rmdir(path) 278 | if _M.unicode then 279 | local unc_str = win_utf8_to_unicode(path) 280 | if lib._wrmdir(unc_str) == 0 then 281 | return true 282 | end 283 | else 284 | if type(path) ~= 'string' then 285 | error('path should be a string') 286 | end 287 | if lib._rmdir(path) == 0 then 288 | return true 289 | end 290 | end 291 | return nil, errno() 292 | end 293 | 294 | function _M.touch(path, actime, modtime) 295 | local buf 296 | 297 | if type(actime) == "number" then 298 | modtime = modtime or actime 299 | buf = ffi.new("utimebuf") 300 | buf.actime = actime 301 | buf.modtime = modtime 302 | end 303 | 304 | local p = ffi.new("unsigned char[?]", #path + 1) 305 | ffi.copy(p, path) 306 | local utime = IS_64_BIT and lib._utime64 or lib._utime32 307 | if utime(p, buf) == 0 then 308 | return true 309 | end 310 | return nil, errno() 311 | end 312 | 313 | function _M.setmode(file, mode) 314 | if io.type(file) ~= 'file' then 315 | error("setmode: invalid file") 316 | end 317 | if mode ~= nil and (mode ~= 'text' and mode ~= 'binary') then 318 | error('setmode: invalid mode') 319 | end 320 | mode = (mode == 'text') and 0x4000 or 0x8000 321 | local prev_mode = lib._setmode(lib._fileno(file), mode) 322 | if prev_mode == -1 then 323 | return nil, errno() 324 | end 325 | return true, (prev_mode == 0x4000) and 'text' or 'binary' 326 | end 327 | 328 | local function check_is_dir(path) 329 | return _M.attributes(path, 'mode') == 'directory' and 1 or 0 330 | end 331 | 332 | function _M.link(old, new) 333 | local is_dir = check_is_dir(old) 334 | if lib.CreateSymbolicLinkW( 335 | wchar_t(new), 336 | wchar_t(old), is_dir) ~= 0 then 337 | return true 338 | end 339 | return nil, errno() 340 | end 341 | 342 | local findfirst 343 | local findnext 344 | local wfindfirst 345 | local wfindnext 346 | if IS_64_BIT then 347 | ffi.cdef([[ 348 | typedef struct _finddata64_t { 349 | uint64_t attrib; 350 | uint64_t time_create; 351 | uint64_t time_access; 352 | uint64_t time_write; 353 | uint64_t size; 354 | char name[]] .. MAXPATH ..[[]; 355 | } _finddata_t; 356 | intptr_t _findfirst64(const char *filespec, _finddata_t *fileinfo); 357 | int _findnext64(intptr_t handle, _finddata_t *fileinfo); 358 | int _findclose(intptr_t handle); 359 | typedef struct _wfinddata_t { //is _wfinddata64_t 360 | uint64_t attrib; 361 | uint64_t time_create; 362 | uint64_t time_access; 363 | uint64_t time_write; 364 | uint64_t size; 365 | wchar_t name[]] .. MAXPATH ..[[]; 366 | } _wfinddata_t; 367 | intptr_t _wfindfirst64(const wchar_t *filespec, struct _wfinddata_t *fileinfo); 368 | int _wfindnext64(intptr_t handle,struct _wfinddata_t *fileinfo); 369 | 370 | ]]) 371 | findfirst = lib._findfirst64 372 | findnext = lib._findnext64 373 | wfindfirst = lib._wfindfirst64 374 | wfindnext = lib._wfindnext64 375 | else 376 | ffi.cdef([[ 377 | typedef struct _finddata32_t { 378 | uint32_t attrib; 379 | uint32_t time_create; 380 | uint32_t time_access; 381 | uint32_t time_write; 382 | uint32_t size; 383 | char name[]] .. MAXPATH ..[[]; 384 | } _finddata_t; 385 | intptr_t _findfirst32(const char* filespec, _finddata_t* fileinfo); 386 | int _findnext32(intptr_t handle, _finddata_t *fileinfo); 387 | 388 | intptr_t _findfirst(const char* filespec, _finddata_t* fileinfo); 389 | int _findnext(intptr_t handle, _finddata_t *fileinfo); 390 | 391 | typedef struct _wfinddata_t { 392 | uint32_t attrib; 393 | uint32_t time_create; 394 | uint32_t time_access; 395 | uint32_t time_write; 396 | uint32_t size; 397 | wchar_t name[]] .. MAXPATH ..[[]; 398 | } _wfinddata_t; 399 | intptr_t _wfindfirst( 400 | const wchar_t *filespec, 401 | struct _wfinddata_t *fileinfo 402 | ); 403 | intptr_t _wfindfirst32( 404 | const wchar_t *filespec, 405 | struct _wfinddata_t *fileinfo 406 | ); 407 | 408 | int _wfindnext( 409 | intptr_t handle, 410 | struct _wfinddata_t *fileinfo 411 | ); 412 | int _wfindnext32( 413 | intptr_t handle, 414 | struct _wfinddata_t *fileinfo 415 | ); 416 | int _findclose(intptr_t handle); 417 | ]]) 418 | local ok 419 | ok,findfirst = pcall(function() return lib._findfirst32 end) 420 | if not ok then findfirst = lib._findfirst end 421 | ok,findnext = pcall(function() return lib._findnext32 end) 422 | if not ok then findnext = lib._findnext end 423 | ok,wfindfirst = pcall(function() return lib._wfindfirst end) 424 | if not ok then ok,wfindfirst = pcall(function() return lib._wfindfirst32 end) end 425 | if not ok then HAVE_WFINDFIRST = false end 426 | ok,wfindnext = pcall(function() return lib._wfindnext end) 427 | if not ok then ok,wfindnext = pcall(function() return lib._wfindnext32 end) end 428 | end 429 | 430 | local function findclose(dentry) 431 | if dentry and dentry.handle ~= -1 then 432 | lib._findclose(dentry.handle) 433 | dentry.handle = -1 434 | end 435 | end 436 | 437 | local dir_type = ffi.metatype("struct {intptr_t handle;}", { 438 | __gc = findclose 439 | }) 440 | 441 | local function close(dir) 442 | findclose(dir._dentry) 443 | dir.closed = true 444 | end 445 | 446 | local dir_attrs = { 447 | _A_ARCH = 0x20, 448 | _A_HIDDEN = 0x02, 449 | _A_NORMAL = 0x00, 450 | _A_RDONLY = 0x01, 451 | _A_SUBDIR = 0x10, 452 | _A_SYSTEM = 0x04 453 | } 454 | 455 | local function dir_attr(t,attr) 456 | if not (type(attr)=="string") then --error("dir_attr must have a string") end 457 | return {size = t.size, mode = (bit.band(t.attrib,dir_attrs._A_SUBDIR)~=0) and "directory" or "file"} 458 | end 459 | if attr=="mode" then 460 | if bit.band(t.attrib,dir_attrs._A_SUBDIR)~=0 then 461 | return "directory" 462 | else 463 | return "file" 464 | end 465 | elseif attr=="size" then 466 | return t.size 467 | end 468 | end 469 | 470 | local dentry_type = ffi.metatype("_finddata_t", 471 | {__index = { 472 | attr = dir_attr 473 | } 474 | }) 475 | 476 | local function iterator(dir) 477 | if dir.closed ~= false then error("closed directory") end 478 | if not dir._dentry then 479 | dir.entry = ffi.new(dentry_type) 480 | dir._dentry = ffi.new(dir_type) 481 | dir._dentry.handle = findfirst(dir._pattern, dir.entry) 482 | if dir._dentry.handle == -1 then 483 | dir.closed = true 484 | return nil, errno() 485 | end 486 | return ffi_str(dir.entry.name), dir.entry 487 | end 488 | 489 | if findnext(dir._dentry.handle, dir.entry) == 0 then 490 | return ffi_str(dir.entry.name), dir.entry 491 | end 492 | close(dir) 493 | return nil 494 | end 495 | 496 | local wdentry_type = ffi.metatype("_wfinddata_t", 497 | {__index = { 498 | attr = dir_attr 499 | }, 500 | }) 501 | 502 | local function witerator(dir) 503 | if dir.closed ~= false then error("closed directory") end 504 | if not dir._dentry then 505 | dir.entry = ffi.new(wdentry_type) 506 | dir._dentry = ffi.new(dir_type) 507 | local szPattern = win_utf8_to_unicode(dir._pattern); 508 | dir._dentry.handle = wfindfirst(szPattern, dir.entry) 509 | if dir._dentry.handle == -1 then 510 | dir.closed = true 511 | return nil, errno() 512 | end 513 | local szName = win_unicode_to_utf8(dir.entry.name)--, -1, szName, 512); 514 | return ffi_str(szName),dir.entry 515 | end 516 | 517 | if wfindnext(dir._dentry.handle, dir.entry) == 0 then 518 | local szName = win_unicode_to_utf8(dir.entry.name)--, -1, szName, 512); 519 | return ffi_str(szName),dir.entry 520 | end 521 | close(dir) 522 | return nil 523 | end 524 | 525 | local dirmeta = {__index = { 526 | next = iterator, 527 | close = close, 528 | }} 529 | 530 | function _M.sdir(path) 531 | if #path > MAXPATH - 2 then 532 | error('path too long: ' .. path) 533 | end 534 | local dir_obj = setmetatable({ 535 | _pattern = path..'/*', 536 | closed = false, 537 | }, dirmeta) 538 | return iterator, dir_obj 539 | end 540 | 541 | local wdirmeta = {__index = { 542 | next = witerator, 543 | close = close, 544 | }} 545 | 546 | function _M.wdir(path) 547 | if #path > MAXPATH - 2 then 548 | error('path too long: ' .. path) 549 | end 550 | local dir_obj = setmetatable({ 551 | _pattern = path..'/*', 552 | closed = false, 553 | }, wdirmeta) 554 | return witerator, dir_obj 555 | end 556 | 557 | function _M.dir(path) 558 | if _M.unicode then 559 | return _M.wdir(path) 560 | else 561 | return _M.sdir(path) 562 | end 563 | end 564 | 565 | ffi.cdef([[ 566 | int _fileno(struct FILE *stream); 567 | int fseek(struct FILE *stream, long offset, int origin); 568 | long ftell(struct FILE *stream); 569 | int _locking(int fd, int mode, long nbytes); 570 | ]]) 571 | 572 | local mode_ltype_map = { 573 | r = 2, -- LK_NBLCK 574 | w = 2, -- LK_NBLCK 575 | u = 0, -- LK_UNLCK 576 | } 577 | local SEEK_SET = 0 578 | local SEEK_END = 2 579 | 580 | local function lock(fh, mode, start, len) 581 | local lkmode = mode_ltype_map[mode] 582 | if not len or len <= 0 then 583 | if lib.fseek(fh, 0, SEEK_END) ~= 0 then 584 | return nil, errno() 585 | end 586 | len = lib.ftell(fh) 587 | end 588 | if not start or start <= 0 then 589 | start = 0 590 | end 591 | if lib.fseek(fh, start, SEEK_SET) ~= 0 then 592 | return nil, errno() 593 | end 594 | local fd = lib._fileno(fh) 595 | if lib._locking(fd, lkmode, len) == -1 then 596 | return nil, errno() 597 | end 598 | return true 599 | end 600 | 601 | function _M.lock(filehandle, mode, start, length) 602 | if mode ~= 'r' and mode ~= 'w' then 603 | error("lock: invalid mode") 604 | end 605 | if io.type(filehandle) ~= 'file' then 606 | error("lock: invalid file") 607 | end 608 | local ok, err = lock(filehandle, mode, start, length) 609 | if not ok then 610 | return nil, err 611 | end 612 | return true 613 | end 614 | 615 | function _M.unlock(filehandle, start, length) 616 | if io.type(filehandle) ~= 'file' then 617 | error("unlock: invalid file") 618 | end 619 | local ok, err = lock(filehandle, 'u', start, length) 620 | if not ok then 621 | return nil, err 622 | end 623 | return true 624 | end 625 | else 626 | ffi.cdef([[ 627 | char *getcwd(char *buf, size_t size); 628 | int chdir(const char *path); 629 | int rmdir(const char *pathname); 630 | typedef unsigned int mode_t; 631 | int mkdir(const char *pathname, mode_t mode); 632 | typedef size_t time_t; 633 | struct utimebuf { 634 | time_t actime; 635 | time_t modtime; 636 | }; 637 | int utime(const char *file, const struct utimebuf *times); 638 | int link(const char *oldpath, const char *newpath); 639 | int symlink(const char *oldpath, const char *newpath); 640 | ]]) 641 | 642 | function _M.chdir(path) 643 | if lib.chdir(path) == 0 then 644 | return true 645 | end 646 | return nil, errno() 647 | end 648 | 649 | function _M.currentdir() 650 | local size = MAXPATH 651 | while true do 652 | local buf = ffi.new("char[?]", size) 653 | if lib.getcwd(buf, size) ~= nil then 654 | return ffi_str(buf) 655 | end 656 | local err = errno() 657 | if err ~= ERANGE then 658 | return nil, err 659 | end 660 | size = size * 2 661 | end 662 | end 663 | 664 | function _M.mkdir(path, mode) 665 | if lib.mkdir(path, mode or 509) == 0 then 666 | return true 667 | end 668 | return nil, errno() 669 | end 670 | 671 | function _M.rmdir(path) 672 | if lib.rmdir(path) == 0 then 673 | return true 674 | end 675 | return nil, errno() 676 | end 677 | 678 | function _M.touch(path, actime, modtime) 679 | local buf 680 | 681 | if type(actime) == "number" then 682 | modtime = modtime or actime 683 | buf = ffi.new("struct utimebuf") 684 | buf.actime = actime 685 | buf.modtime = modtime 686 | end 687 | 688 | local p = ffi.new("unsigned char[?]", #path + 1) 689 | ffi.copy(p, path) 690 | 691 | if lib.utime(p, buf) == 0 then 692 | return true 693 | end 694 | return nil, errno() 695 | end 696 | 697 | function _M.setmode() 698 | return true, "binary" 699 | end 700 | 701 | function _M.link(old, new, symlink) 702 | local f = symlink and lib.symlink or lib.link 703 | if f(old, new) == 0 then 704 | return true 705 | end 706 | return nil, errno() 707 | end 708 | 709 | local dirent_def 710 | if OS == 'OSX' or OS == 'BSD' then 711 | dirent_def = [[ 712 | /* _DARWIN_FEATURE_64_BIT_INODE is NOT defined here? */ 713 | struct dirent { 714 | uint32_t d_ino; 715 | uint16_t d_reclen; 716 | uint8_t d_type; 717 | uint8_t d_namlen; 718 | char d_name[256]; 719 | }; 720 | ]] 721 | else 722 | dirent_def = [[ 723 | struct dirent { 724 | int64_t d_ino; 725 | size_t d_off; 726 | unsigned short d_reclen; 727 | unsigned char d_type; 728 | char d_name[256]; 729 | }; 730 | ]] 731 | end 732 | ffi.cdef(dirent_def .. [[ 733 | typedef struct __dirstream DIR; 734 | DIR *opendir(const char *name); 735 | struct dirent *readdir(DIR *dirp); 736 | int closedir(DIR *dirp); 737 | ]]) 738 | 739 | local function close(dir) 740 | if dir._dentry ~= nil then 741 | lib.closedir(dir._dentry) 742 | dir._dentry = nil 743 | dir.closed = true 744 | end 745 | end 746 | 747 | local function iterator(dir) 748 | if dir.closed ~= false then error("closed directory") end 749 | 750 | local entry = lib.readdir(dir._dentry) 751 | if entry ~= nil then 752 | return ffi_str(entry.d_name) 753 | else 754 | close(dir) 755 | return nil 756 | end 757 | end 758 | 759 | local dir_obj_type = ffi.metatype([[ 760 | struct { 761 | DIR *_dentry; 762 | bool closed; 763 | } 764 | ]], 765 | {__index = { 766 | next = iterator, 767 | close = close, 768 | }, __gc = close 769 | }) 770 | 771 | function _M.dir(path) 772 | local dentry = lib.opendir(path) 773 | if dentry == nil then 774 | error("cannot open "..path.." : "..errno()) 775 | end 776 | local dir_obj = ffi.new(dir_obj_type) 777 | dir_obj._dentry = dentry 778 | dir_obj.closed = false; 779 | return iterator, dir_obj 780 | end 781 | 782 | local SEEK_SET = 0 783 | local F_SETLK = (OS == 'Linux') and 6 or 8 784 | local mode_ltype_map 785 | local flock_def 786 | if OS == 'Linux' then 787 | flock_def = [[ 788 | struct flock { 789 | short int l_type; 790 | short int l_whence; 791 | int64_t l_start; 792 | int64_t l_len; 793 | int l_pid; 794 | }; 795 | ]] 796 | mode_ltype_map = { 797 | r = 0, -- F_RDLCK 798 | w = 1, -- F_WRLCK 799 | u = 2, -- F_UNLCK 800 | } 801 | else 802 | flock_def = [[ 803 | struct flock { 804 | int64_t l_start; 805 | int64_t l_len; 806 | int32_t l_pid; 807 | short l_type; 808 | short l_whence; 809 | }; 810 | ]] 811 | mode_ltype_map = { 812 | r = 1, -- F_RDLCK 813 | u = 2, -- F_UNLCK 814 | w = 3, -- F_WRLCK 815 | } 816 | end 817 | 818 | ffi.cdef(flock_def..[[ 819 | int fileno(struct FILE *stream); 820 | int fcntl(int fd, int cmd, ... /* arg */ ); 821 | int unlink(const char *path); 822 | ]]) 823 | 824 | local function lock(fd, mode, start, len) 825 | local flock = ffi.new('struct flock') 826 | flock.l_type = mode_ltype_map[mode] 827 | flock.l_whence = SEEK_SET 828 | flock.l_start = start or 0 829 | flock.l_len = len or 0 830 | if lib.fcntl(fd, F_SETLK, flock) == -1 then 831 | return nil, errno() 832 | end 833 | return true 834 | end 835 | 836 | function _M.lock(filehandle, mode, start, length) 837 | if mode ~= 'r' and mode ~= 'w' then 838 | error("lock: invalid mode") 839 | end 840 | if io.type(filehandle) ~= 'file' then 841 | error("lock: invalid file") 842 | end 843 | local fd = lib.fileno(filehandle) 844 | local ok, err = lock(fd, mode, start, length) 845 | if not ok then 846 | return nil, err 847 | end 848 | return true 849 | end 850 | 851 | function _M.unlock(filehandle, start, length) 852 | if io.type(filehandle) ~= 'file' then 853 | error("unlock: invalid file") 854 | end 855 | local fd = lib.fileno(filehandle) 856 | local ok, err = lock(fd, 'u', start, length) 857 | if not ok then 858 | return nil, err 859 | end 860 | return true 861 | end 862 | end 863 | 864 | -- lock related 865 | local dir_lock_struct 866 | local create_lockfile 867 | local delete_lockfile 868 | 869 | if OS == 'Windows' then 870 | ffi.cdef([[ 871 | typedef const wchar_t* LPCWSTR; 872 | typedef struct _SECURITY_ATTRIBUTES { 873 | DWORD nLength; 874 | void *lpSecurityDescriptor; 875 | int bInheritHandle; 876 | } SECURITY_ATTRIBUTES; 877 | typedef SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES; 878 | void *CreateFileW( 879 | LPCWSTR lpFileName, 880 | DWORD dwDesiredAccess, 881 | DWORD dwShareMode, 882 | LPSECURITY_ATTRIBUTES lpSecurityAttributes, 883 | DWORD dwCreationDisposition, 884 | DWORD dwFlagsAndAttributes, 885 | void *hTemplateFile 886 | ); 887 | 888 | int CloseHandle(void *hObject); 889 | ]]) 890 | 891 | local GENERIC_WRITE = 0x40000000 892 | local CREATE_NEW = 1 893 | local FILE_NORMAL_DELETE_ON_CLOSE = 0x04000080 894 | 895 | dir_lock_struct = 'struct {void *lockname;}' 896 | 897 | function create_lockfile(dir_lock, _, lockname) 898 | lockname = wchar_t(lockname) 899 | dir_lock.lockname = lib.CreateFileW(lockname, GENERIC_WRITE, 0, nil, CREATE_NEW, 900 | FILE_NORMAL_DELETE_ON_CLOSE, nil) 901 | return dir_lock.lockname ~= ffi.cast('void*', -1) 902 | end 903 | 904 | function delete_lockfile(dir_lock) 905 | return lib.CloseHandle(dir_lock.lockname) 906 | end 907 | else 908 | dir_lock_struct = 'struct {char *lockname;}' 909 | function create_lockfile(dir_lock, path, lockname) 910 | dir_lock.lockname = ffi.new('char[?]', #lockname + 1) 911 | ffi.copy(dir_lock.lockname, lockname) 912 | return lib.symlink(path, lockname) == 0 913 | end 914 | 915 | function delete_lockfile(dir_lock) 916 | return lib.unlink(dir_lock.lockname) 917 | end 918 | end 919 | 920 | local function unlock_dir(dir_lock) 921 | if dir_lock.lockname ~= nil then 922 | dir_lock:delete_lockfile() 923 | dir_lock.lockname = nil 924 | end 925 | return true 926 | end 927 | 928 | local dir_lock_type = ffi.metatype(dir_lock_struct, 929 | {__gc = unlock_dir, 930 | __index = { 931 | free = unlock_dir, 932 | create_lockfile = create_lockfile, 933 | delete_lockfile = delete_lockfile, 934 | }} 935 | ) 936 | 937 | function _M.lock_dir(path, _) 938 | -- It's interesting that the lock_dir from vanilla lfs just ignores second paramter. 939 | -- So, I follow this behavior too :) 940 | local dir_lock = ffi.new(dir_lock_type) 941 | local lockname = path .. '/lockfile.lfs' 942 | if not dir_lock:create_lockfile(path, lockname) then 943 | return nil, errno() 944 | end 945 | return dir_lock 946 | end 947 | 948 | -- stat related 949 | local stat_func 950 | local lstat_func 951 | if OS == 'Linux' then 952 | ffi.cdef([[ 953 | long syscall(int number, ...); 954 | ]]) 955 | local ARCH = ffi.arch 956 | -- Taken from justincormack/ljsyscall 957 | local stat_syscall_num 958 | local lstat_syscall_num 959 | if ARCH == 'x64' then 960 | ffi.cdef([[ 961 | typedef struct { 962 | unsigned long st_dev; 963 | unsigned long st_ino; 964 | unsigned long st_nlink; 965 | unsigned int st_mode; 966 | unsigned int st_uid; 967 | unsigned int st_gid; 968 | unsigned int __pad0; 969 | unsigned long st_rdev; 970 | long st_size; 971 | long st_blksize; 972 | long st_blocks; 973 | unsigned long st_atime; 974 | unsigned long st_atime_nsec; 975 | unsigned long st_mtime; 976 | unsigned long st_mtime_nsec; 977 | unsigned long st_ctime; 978 | unsigned long st_ctime_nsec; 979 | long __unused[3]; 980 | } lfs_stat; 981 | ]]) 982 | stat_syscall_num = 4 983 | lstat_syscall_num = 6 984 | elseif ARCH == 'x86' then 985 | ffi.cdef([[ 986 | typedef struct { 987 | unsigned long long st_dev; 988 | unsigned char __pad0[4]; 989 | unsigned long __st_ino; 990 | unsigned int st_mode; 991 | unsigned int st_nlink; 992 | unsigned long st_uid; 993 | unsigned long st_gid; 994 | unsigned long long st_rdev; 995 | unsigned char __pad3[4]; 996 | long long st_size; 997 | unsigned long st_blksize; 998 | unsigned long long st_blocks; 999 | unsigned long st_atime; 1000 | unsigned long st_atime_nsec; 1001 | unsigned long st_mtime; 1002 | unsigned int st_mtime_nsec; 1003 | unsigned long st_ctime; 1004 | unsigned long st_ctime_nsec; 1005 | unsigned long long st_ino; 1006 | } lfs_stat; 1007 | ]]) 1008 | stat_syscall_num = IS_64_BIT and 106 or 195 1009 | lstat_syscall_num = IS_64_BIT and 107 or 196 1010 | elseif ARCH == 'arm' then 1011 | if IS_64_BIT then 1012 | ffi.cdef([[ 1013 | typedef struct { 1014 | unsigned long st_dev; 1015 | unsigned long st_ino; 1016 | unsigned int st_mode; 1017 | unsigned int st_nlink; 1018 | unsigned int st_uid; 1019 | unsigned int st_gid; 1020 | unsigned long st_rdev; 1021 | unsigned long __pad1; 1022 | long st_size; 1023 | int st_blksize; 1024 | int __pad2; 1025 | long st_blocks; 1026 | long st_atime; 1027 | unsigned long st_atime_nsec; 1028 | long st_mtime; 1029 | unsigned long st_mtime_nsec; 1030 | long st_ctime; 1031 | unsigned long st_ctime_nsec; 1032 | unsigned int __unused4; 1033 | unsigned int __unused5; 1034 | } lfs_stat; 1035 | ]]) 1036 | stat_syscall_num = 106 1037 | lstat_syscall_num = 107 1038 | else 1039 | ffi.cdef([[ 1040 | typedef struct { 1041 | unsigned long long st_dev; 1042 | unsigned char __pad0[4]; 1043 | unsigned long __st_ino; 1044 | unsigned int st_mode; 1045 | unsigned int st_nlink; 1046 | unsigned long st_uid; 1047 | unsigned long st_gid; 1048 | unsigned long long st_rdev; 1049 | unsigned char __pad3[4]; 1050 | long long st_size; 1051 | unsigned long st_blksize; 1052 | unsigned long long st_blocks; 1053 | unsigned long st_atime; 1054 | unsigned long st_atime_nsec; 1055 | unsigned long st_mtime; 1056 | unsigned int st_mtime_nsec; 1057 | unsigned long st_ctime; 1058 | unsigned long st_ctime_nsec; 1059 | unsigned long long st_ino; 1060 | } lfs_stat; 1061 | ]]) 1062 | stat_syscall_num = 195 1063 | lstat_syscall_num = 196 1064 | end 1065 | elseif ARCH == 'ppc' or ARCH == 'ppcspe' then 1066 | ffi.cdef([[ 1067 | typedef struct { 1068 | unsigned long long st_dev; 1069 | unsigned long long st_ino; 1070 | unsigned int st_mode; 1071 | unsigned int st_nlink; 1072 | unsigned int st_uid; 1073 | unsigned int st_gid; 1074 | unsigned long long st_rdev; 1075 | unsigned long long __pad1; 1076 | long long st_size; 1077 | int st_blksize; 1078 | int __pad2; 1079 | long long st_blocks; 1080 | int st_atime; 1081 | unsigned int st_atime_nsec; 1082 | int st_mtime; 1083 | unsigned int st_mtime_nsec; 1084 | int st_ctime; 1085 | unsigned int st_ctime_nsec; 1086 | unsigned int __unused4; 1087 | unsigned int __unused5; 1088 | } lfs_stat; 1089 | ]]) 1090 | stat_syscall_num = IS_64_BIT and 106 or 195 1091 | lstat_syscall_num = IS_64_BIT and 107 or 196 1092 | elseif ARCH == 'mips' or ARCH == 'mipsel' then 1093 | ffi.cdef([[ 1094 | typedef struct { 1095 | unsigned long st_dev; 1096 | unsigned long __st_pad0[3]; 1097 | unsigned long long st_ino; 1098 | mode_t st_mode; 1099 | nlink_t st_nlink; 1100 | uid_t st_uid; 1101 | gid_t st_gid; 1102 | unsigned long st_rdev; 1103 | unsigned long __st_pad1[3]; 1104 | long long st_size; 1105 | time_t st_atime; 1106 | unsigned long st_atime_nsec; 1107 | time_t st_mtime; 1108 | unsigned long st_mtime_nsec; 1109 | time_t st_ctime; 1110 | unsigned long st_ctime_nsec; 1111 | unsigned long st_blksize; 1112 | unsigned long __st_pad2; 1113 | long long st_blocks; 1114 | long __st_padding4[14]; 1115 | } lfs_stat; 1116 | ]]) 1117 | stat_syscall_num = IS_64_BIT and 4106 or 4213 1118 | lstat_syscall_num = IS_64_BIT and 4107 or 4214 1119 | end 1120 | 1121 | if ARCH == 'arm64' then 1122 | 1123 | -- On arm64 stat and lstat do not exist as syscall but can be used like this 1124 | 1125 | ffi.cdef([[ 1126 | typedef struct lfs_stat { 1127 | unsigned long st_dev; 1128 | unsigned long st_ino; 1129 | unsigned int st_mode; 1130 | unsigned int st_nlink; 1131 | unsigned int st_uid; 1132 | unsigned int st_gid; 1133 | unsigned long st_rdev; 1134 | unsigned long __pad1; 1135 | long st_size; 1136 | int st_blksize; 1137 | int __pad2; 1138 | long st_blocks; 1139 | long st_atime; 1140 | unsigned long st_atime_nsec; 1141 | long st_mtime; 1142 | unsigned long st_mtime_nsec; 1143 | long st_ctime; 1144 | unsigned long st_ctime_nsec; 1145 | unsigned int __unused4; 1146 | unsigned int __unused5; 1147 | } lfs_stat; 1148 | 1149 | int stat(const char *pathname, 1150 | struct lfs_stat *statbuf); 1151 | 1152 | int lstat(const char *pathname, 1153 | struct lfs_stat *statbuf); 1154 | 1155 | ]]) 1156 | 1157 | stat_func = function(filepath, buf) 1158 | return lib.stat(filepath, buf) 1159 | end 1160 | lstat_func = function(filepath, buf) 1161 | return lib.lstat(filepath, buf) 1162 | end 1163 | 1164 | elseif stat_syscall_num then 1165 | stat_func = function(filepath, buf) 1166 | return lib.syscall(stat_syscall_num, filepath, buf) 1167 | end 1168 | lstat_func = function(filepath, buf) 1169 | return lib.syscall(lstat_syscall_num, filepath, buf) 1170 | end 1171 | else 1172 | ffi.cdef('typedef struct {} lfs_stat;') 1173 | stat_func = function() error("TODO support other Linux architectures") end 1174 | lstat_func = stat_func 1175 | end 1176 | elseif OS == 'Windows' then 1177 | ffi.cdef([[ 1178 | typedef __int64 __time64_t; 1179 | typedef struct { 1180 | unsigned int st_dev; 1181 | unsigned short st_ino; 1182 | unsigned short st_mode; 1183 | short st_nlink; 1184 | short st_uid; 1185 | short st_gid; 1186 | unsigned int st_rdev; 1187 | __int64 st_size; 1188 | __time64_t st_atime; 1189 | __time64_t st_mtime; 1190 | __time64_t st_ctime; 1191 | } lfs_stat; 1192 | 1193 | int _stat64(const char *path, lfs_stat *buffer); 1194 | int _wstat64(const wchar_t *path, lfs_stat *buffer); 1195 | ]]) 1196 | 1197 | stat_func = function(filepath, buf) 1198 | if _M.unicode then 1199 | local szfp = win_utf8_to_unicode(filepath); 1200 | return lib._wstat64(szfp, buf) 1201 | else 1202 | return lib._stat64(filepath, buf) 1203 | end 1204 | end 1205 | lstat_func = stat_func 1206 | elseif OS == 'OSX' then 1207 | ffi.cdef([[ 1208 | struct lfs_timespec { 1209 | time_t tv_sec; 1210 | long tv_nsec; 1211 | }; 1212 | typedef struct { 1213 | uint32_t st_dev; 1214 | uint16_t st_mode; 1215 | uint16_t st_nlink; 1216 | uint64_t st_ino; 1217 | uint32_t st_uid; 1218 | uint32_t st_gid; 1219 | uint32_t st_rdev; 1220 | struct lfs_timespec st_atimespec; 1221 | struct lfs_timespec st_mtimespec; 1222 | struct lfs_timespec st_ctimespec; 1223 | struct lfs_timespec st_birthtimespec; 1224 | int64_t st_size; 1225 | int64_t st_blocks; 1226 | int32_t st_blksize; 1227 | uint32_t st_flags; 1228 | uint32_t st_gen; 1229 | int32_t st_lspare; 1230 | int64_t st_qspare[2]; 1231 | } lfs_stat; 1232 | int stat64(const char *path, lfs_stat *buf); 1233 | int lstat64(const char *path, lfs_stat *buf); 1234 | ]]) 1235 | stat_func = lib.stat64 1236 | lstat_func = lib.lstat64 1237 | elseif OS == 'BSD' then 1238 | ffi.cdef([[ 1239 | struct lfs_timespec { 1240 | time_t tv_sec; 1241 | long tv_nsec; 1242 | }; 1243 | typedef struct { 1244 | uint32_t st_dev; 1245 | uint32_t st_ino; 1246 | uint16_t st_mode; 1247 | uint16_t st_nlink; 1248 | uint32_t st_uid; 1249 | uint32_t st_gid; 1250 | uint32_t st_rdev; 1251 | struct lfs_timespec st_atimespec; 1252 | struct lfs_timespec st_mtimespec; 1253 | struct lfs_timespec st_ctimespec; 1254 | int64_t st_size; 1255 | int64_t st_blocks; 1256 | int32_t st_blksize; 1257 | uint32_t st_flags; 1258 | uint32_t st_gen; 1259 | int32_t st_lspare; 1260 | struct lfs_timespec st_birthtimespec; 1261 | } lfs_stat; 1262 | int stat(const char *path, lfs_stat *buf); 1263 | int lstat(const char *path, lfs_stat *buf); 1264 | ]]) 1265 | stat_func = lib.stat 1266 | lstat_func = lib.lstat 1267 | else 1268 | ffi.cdef('typedef struct {} lfs_stat;') 1269 | stat_func = function() error('TODO: support other posix system') end 1270 | lstat_func = stat_func 1271 | end 1272 | 1273 | local STAT = { 1274 | FMT = 0xF000, 1275 | FSOCK = 0xC000, 1276 | FLNK = 0xA000, 1277 | FREG = 0x8000, 1278 | FBLK = 0x6000, 1279 | FDIR = 0x4000, 1280 | FCHR = 0x2000, 1281 | FIFO = 0x1000, 1282 | } 1283 | 1284 | local ftype_name_map = { 1285 | [STAT.FREG] = 'file', 1286 | [STAT.FDIR] = 'directory', 1287 | [STAT.FLNK] = 'link', 1288 | [STAT.FSOCK] = 'socket', 1289 | [STAT.FCHR] = 'char device', 1290 | [STAT.FBLK] = "block device", 1291 | [STAT.FIFO] = "named pipe", 1292 | } 1293 | 1294 | local function mode_to_ftype(mode) 1295 | local ftype = band(mode, STAT.FMT) 1296 | return ftype_name_map[ftype] or 'other' 1297 | end 1298 | 1299 | local function mode_to_perm(mode) 1300 | local perm_bits = band(mode, tonumber(777, 8)) 1301 | local perm = new_tab(9, 0) 1302 | local i = 9 1303 | while i > 0 do 1304 | local perm_bit = band(perm_bits, 7) 1305 | perm[i] = (band(perm_bit, 1) > 0 and 'x' or '-') 1306 | perm[i-1] = (band(perm_bit, 2) > 0 and 'w' or '-') 1307 | perm[i-2] = (band(perm_bit, 4) > 0 and 'r' or '-') 1308 | i = i - 3 1309 | perm_bits = rshift(perm_bits, 3) 1310 | end 1311 | return concat(perm) 1312 | end 1313 | 1314 | local function time_or_timespec(time, timespec) 1315 | local t = tonumber(time) 1316 | if not t and timespec then 1317 | t = tonumber(timespec.tv_sec) 1318 | end 1319 | return t 1320 | end 1321 | 1322 | local attr_handlers = { 1323 | access = function(st) return time_or_timespec(st.st_atime, st.st_atimespec) end, 1324 | blksize = function(st) return tonumber(st.st_blksize) end, 1325 | blocks = function(st) return tonumber(st.st_blocks) end, 1326 | change = function(st) return time_or_timespec(st.st_ctime, st.st_ctimespec) end, 1327 | dev = function(st) return tonumber(st.st_dev) end, 1328 | gid = function(st) return tonumber(st.st_gid) end, 1329 | ino = function(st) return tonumber(st.st_ino) end, 1330 | mode = function(st) return mode_to_ftype(st.st_mode) end, 1331 | modification = function(st) return time_or_timespec(st.st_mtime, st.st_mtimespec) end, 1332 | nlink = function(st) return tonumber(st.st_nlink) end, 1333 | permissions = function(st) return mode_to_perm(st.st_mode) end, 1334 | rdev = function(st) return tonumber(st.st_rdev) end, 1335 | size = function(st) return tonumber(st.st_size) end, 1336 | uid = function(st) return tonumber(st.st_uid) end, 1337 | } 1338 | local mt = { 1339 | __index = function(self, attr_name) 1340 | local func = attr_handlers[attr_name] 1341 | return func and func(self) 1342 | end 1343 | } 1344 | local stat_type = ffi.metatype('lfs_stat', mt) 1345 | 1346 | -- Add target field for symlinkattributes, which is the absolute path of linked target 1347 | local get_link_target_path 1348 | if OS == 'Windows' then 1349 | local ENOSYS = 40 1350 | function get_link_target_path() 1351 | return nil, "could not obtain link target: Function not implemented ",ENOSYS 1352 | end 1353 | else 1354 | 1355 | ffi.cdef('ssize_t readlink(const char *path, char *buf, size_t bufsize);') 1356 | local EINVAL = 22 1357 | function get_link_target_path(link_path, statbuf) 1358 | local size = statbuf.st_size 1359 | size = size == 0 and MAXPATH or size 1360 | local buf = ffi.new('char[?]', size + 1) 1361 | local read = lib.readlink(link_path, buf, size) 1362 | if read == -1 then 1363 | return nil, "could not obtain link target: "..errno(), ffi.errno() 1364 | end 1365 | if read > size then 1366 | return nil, "not enought size for readlink: "..errno(), ffi.errno() 1367 | end 1368 | buf[size] = 0 1369 | return ffi_str(buf) 1370 | end 1371 | end 1372 | 1373 | local buf = ffi.new(stat_type) 1374 | local function attributes(filepath, attr, follow_symlink) 1375 | local func = follow_symlink and stat_func or lstat_func 1376 | if func(filepath, buf) == -1 then 1377 | return nil, string.format("cannot obtain information from file '%s' : %s",tostring(filepath),errno()), ffi.errno() 1378 | end 1379 | 1380 | local atype = type(attr) 1381 | if atype == 'string' then 1382 | local value, err, errn 1383 | if attr == 'target' and not follow_symlink then 1384 | value, err, errn = get_link_target_path(filepath, buf) 1385 | return value, err, errn 1386 | else 1387 | value = buf[attr] 1388 | end 1389 | if value == nil then 1390 | error("invalid attribute name '" .. attr .. "'") 1391 | end 1392 | return value 1393 | else 1394 | local tab = (atype == 'table') and attr or {} 1395 | for k, _ in pairs(attr_handlers) do 1396 | tab[k] = buf[k] 1397 | end 1398 | if not follow_symlink then 1399 | tab.target = get_link_target_path(filepath, buf) 1400 | end 1401 | return tab 1402 | end 1403 | end 1404 | 1405 | function _M.attributes(filepath, attr) 1406 | return attributes(filepath, attr, true) 1407 | end 1408 | 1409 | function _M.symlinkattributes(filepath, attr) 1410 | return attributes(filepath, attr, false) 1411 | end 1412 | 1413 | _M.unicode = HAVE_WFINDFIRST 1414 | _M.unicode_errors = false 1415 | --this would error with _M.unicode_errors = true 1416 | --local cad = string.char(0xE0,0x80,0x80)--,0xFD,0xFF) 1417 | 1418 | return _M 1419 | -------------------------------------------------------------------------------- /lfs_spec.lua: -------------------------------------------------------------------------------- 1 | local ffi = require('ffi') 2 | local vanilla_lfs = require('lfs') 3 | local lfs = require('./lfs_ffi') 4 | 5 | 6 | local eq = assert.are.same 7 | local is_nil = assert.is_nil 8 | local is_not_nil = assert.is_not_nil 9 | local is_true = assert.is_true 10 | local has_error = assert.has_error 11 | local posix = ffi.os ~= 'Windows' 12 | 13 | local attr_names = { 14 | 'access', 15 | 'change', 16 | 'dev', 17 | 'gid', 18 | 'ino', 19 | 'mode', 20 | 'modification', 21 | 'nlink', 22 | 'permissions', 23 | 'rdev', 24 | 'size', 25 | 'uid' 26 | } 27 | if posix then 28 | local extra_attrs = {'blksize', 'blocks'} 29 | for i = 1, #extra_attrs do 30 | table.insert(attr_names, extra_attrs[i]) 31 | end 32 | end 33 | 34 | describe('lfs', function() 35 | describe('#attributes', function() 36 | it('without argument', function() 37 | local info = lfs.attributes('.') 38 | eq(vanilla_lfs.attributes('.'), info) 39 | end) 40 | 41 | it('with attribute name', function() 42 | for i = 1, #attr_names do 43 | local attr = attr_names[i] 44 | local info = lfs.attributes('.', attr) 45 | eq(vanilla_lfs.attributes('.', attr), info, 46 | attr..' is not equal') 47 | end 48 | end) 49 | 50 | it('with attributes table', function() 51 | local tab = {"table", "for", "attributes"} 52 | local info = lfs.attributes('.', tab) 53 | eq(vanilla_lfs.attributes('.', tab), info) 54 | end) 55 | 56 | it('with nonexisted file', function() 57 | local info, err = lfs.attributes('nonexisted') 58 | is_nil(info) 59 | eq('No such file or directory', err) 60 | end) 61 | 62 | it('with nonexisted attribute', function() 63 | has_error(function() lfs.attributes('.', 'nonexisted') end, 64 | "invalid attribute name 'nonexisted'") 65 | if not posix then 66 | has_error(function() lfs.attributes('.', 'blocks') end, 67 | "invalid attribute name 'blocks'") 68 | end 69 | end) 70 | end) 71 | 72 | describe('#symlinkattributes', function() 73 | local symlink = 'lfs_ffi.lua.link' 74 | 75 | it('link failed', function() 76 | if posix then 77 | local res, err = lfs.link('xxx', symlink) 78 | is_nil(res) 79 | eq(err, 'No such file or directory') 80 | end 81 | end) 82 | 83 | it('hard link', function() 84 | local _, err = lfs.link('lfs_ffi.lua', symlink) 85 | is_nil(err) 86 | eq(vanilla_lfs.attributes(symlink, 'mode'), 'file') 87 | eq(vanilla_lfs.symlinkattributes(symlink, 'mode'), 'file') 88 | end) 89 | 90 | it('soft link', function() 91 | if posix then 92 | local _, err = lfs.link('lfs_ffi.lua', symlink, true) 93 | is_nil(err) 94 | eq(vanilla_lfs.attributes(symlink, 'mode'), 'file') 95 | eq(vanilla_lfs.symlinkattributes(symlink, 'mode'), 'link') 96 | end 97 | end) 98 | 99 | it('without argument', function() 100 | lfs.link('lfs_ffi.lua', symlink, true) 101 | local info = lfs.symlinkattributes(symlink) 102 | local expected_info = vanilla_lfs.symlinkattributes(symlink) 103 | for k, v in pairs(expected_info) do 104 | eq(v, info[k], k..'is not equal') 105 | end 106 | end) 107 | 108 | it('with attribute name', function() 109 | lfs.link('lfs_ffi.lua', symlink, true) 110 | for i = 1, #attr_names do 111 | local attr = attr_names[i] 112 | local info = lfs.symlinkattributes(symlink, attr) 113 | eq(vanilla_lfs.symlinkattributes(symlink, attr), info, 114 | attr..' is not equal') 115 | end 116 | end) 117 | 118 | it('add target field', function() 119 | if posix then 120 | lfs.link('lfs_ffi.lua', symlink, true) 121 | eq('lfs_ffi.lua', lfs.symlinkattributes(symlink, 'target')) 122 | eq('lfs_ffi.lua', lfs.symlinkattributes(symlink).target) 123 | end 124 | end) 125 | 126 | after_each(function() 127 | os.remove(symlink) 128 | end) 129 | end) 130 | 131 | describe('#setmode', function() 132 | local fh 133 | before_each(function() 134 | fh = io.open('lfs_ffi.lua') 135 | end) 136 | 137 | it('setmode', function() 138 | local ok, mode = lfs.setmode(fh, 'binary') 139 | is_true(ok) 140 | if posix then 141 | -- On posix platform, always return 'binary' 142 | eq('binary', mode) 143 | else 144 | eq( 'text', mode) 145 | local _ 146 | _, mode = lfs.setmode(fh, 'text') 147 | eq('binary', mode) 148 | end 149 | end) 150 | 151 | if not posix then 152 | it('setmode incorrect mode', function() 153 | has_error(function() lfs.setmode(fh, 'bin') end, 'setmode: invalid mode') 154 | end) 155 | 156 | it('setmode incorrect file', function() 157 | has_error(function() lfs.setmode('file', 'binary') end, 'setmode: invalid file') 158 | end) 159 | end 160 | end) 161 | 162 | describe('#dir', function() 163 | it('mkdir', function() 164 | lfs.mkdir('test') 165 | end) 166 | 167 | it('return err if mkdir failed', function() 168 | local res, err = lfs.mkdir('test') 169 | is_nil(res) 170 | eq('File exists', err) 171 | end) 172 | 173 | it('raise error if open dir failed', function() 174 | if posix then 175 | has_error(function() lfs.dir('nonexisted') end, 176 | "cannot open nonexisted : No such file or directory") 177 | else 178 | -- Like vanilla lfs, we only check path's length in Windows 179 | local ok, msg = pcall(function() lfs.dir(('12345'):rep(64)) end) 180 | is_true(not ok) 181 | is_not_nil(msg:find('path too long')) 182 | end 183 | end) 184 | 185 | if posix or os.getenv('CI') ~= 'True' then 186 | it('iterate dir', function() 187 | local _, dir_obj = lfs.dir('test') 188 | local names = {} 189 | while true do 190 | local name = dir_obj:next() 191 | if not name then break end 192 | names[#names + 1] = name 193 | end 194 | table.sort(names) 195 | eq({'.', '..'}, names) 196 | is_true(dir_obj.closed) 197 | end) 198 | 199 | it('iterate dir via iterator', function() 200 | local iter, dir_obj = lfs.dir('test') 201 | local names = {} 202 | while true do 203 | local name = iter(dir_obj) 204 | if not name then break end 205 | names[#names + 1] = name 206 | end 207 | table.sort(names) 208 | eq({'.', '..'}, names) 209 | is_true(dir_obj.closed) 210 | end) 211 | end 212 | 213 | it('close', function() 214 | local _, dir_obj = lfs.dir('.') 215 | dir_obj:close() 216 | has_error(function() dir_obj:next() end, "closed directory") 217 | end) 218 | 219 | it('chdir and currentdir', function() 220 | lfs.chdir('test') 221 | local cur_dir = lfs.currentdir() 222 | lfs.chdir('..') 223 | assert.is_not_nil(cur_dir:find('test$')) 224 | end) 225 | 226 | it('return err if chdir failed', function() 227 | local res, err = lfs.chdir('nonexisted') 228 | is_nil(res) 229 | eq('No such file or directory', err) 230 | end) 231 | 232 | it('rmdir', function() 233 | lfs.rmdir('test') 234 | end) 235 | 236 | it('return err if rmdir failed', function() 237 | local res, err = lfs.rmdir('test') 238 | is_nil(res) 239 | eq('No such file or directory', err) 240 | end) 241 | end) 242 | 243 | describe('#touch', function() 244 | local touched = 'temp' 245 | 246 | before_each(function() 247 | local f = io.open(touched, 'w') 248 | f:write('a') 249 | f:close() 250 | end) 251 | 252 | after_each(function() 253 | os.remove(touched) 254 | end) 255 | 256 | it('touch failed', function() 257 | local _, err = lfs.touch('nonexisted', 1) 258 | eq('No such file or directory', err) 259 | end) 260 | 261 | it('set atime', function() 262 | local _, err = lfs.touch(touched, 1) 263 | is_nil(err) 264 | eq(vanilla_lfs.attributes(touched, 'access'), 1) 265 | end) 266 | 267 | it('set both atime and mtime', function() 268 | local _, err = lfs.touch(touched, 1, 2) 269 | is_nil(err) 270 | eq(vanilla_lfs.attributes(touched, 'access'), 1) 271 | eq(vanilla_lfs.attributes(touched, 'modification'), 2) 272 | end) 273 | end) 274 | 275 | -- Just smoke testing 276 | describe('#lock', function() 277 | local fh 278 | setup(function() 279 | fh = io.open('temp.txt', 'w') 280 | fh:write('1234567890') 281 | fh:close() 282 | end) 283 | 284 | before_each(function() 285 | fh = io.open('temp.txt', 'r+') 286 | end) 287 | 288 | it('lock', function() 289 | local _, err = lfs.lock(fh, 'r', 2, 8) 290 | is_nil(err) 291 | end) 292 | 293 | it('lock exclusively', function() 294 | if posix then 295 | local _, err = lfs.lock(fh, 'w') 296 | is_nil(err) 297 | end 298 | end) 299 | 300 | it('lock: invalid mode', function() 301 | has_error(function() lfs.lock('temp.txt', 'u') end, 'lock: invalid mode') 302 | end) 303 | 304 | it('lock: invalid file', function() 305 | has_error(function() lfs.lock('temp.txt', 'w') end, 'lock: invalid file') 306 | end) 307 | 308 | it('unlock', function() 309 | local _, err = lfs.lock(fh, 'w', 4, 9) 310 | is_nil(err) 311 | if posix then 312 | _, err = lfs.unlock(fh, 3, 11) 313 | is_nil(err) 314 | else 315 | _, err = lfs.unlock(fh, 3, 11) 316 | eq('Permission denied', err) 317 | _, err = lfs.unlock(fh, 4, 9) 318 | is_nil(err) 319 | end 320 | end) 321 | 322 | it('unlock: invalid file', function() 323 | has_error(function() lfs.unlock('temp.txt') end, 'unlock: invalid file') 324 | end) 325 | 326 | after_each(function() 327 | fh:close() 328 | end) 329 | 330 | teardown(function() 331 | os.remove('temp.txt') 332 | end) 333 | end) 334 | 335 | describe('#lock_dir', function() 336 | it('lock_dir', function() 337 | if true then 338 | local _, err = lfs.lock_dir('.') 339 | is_nil(err) 340 | _, err = lfs.lock_dir('.') 341 | assert.is_not_nil(err) 342 | end 343 | -- The old lock should be free during gc 344 | collectgarbage() 345 | 346 | local lock = lfs.lock_dir('.') 347 | lock:free() 348 | local _, err = lfs.lock_dir('.') 349 | is_nil(err) 350 | end) 351 | end) 352 | end) 353 | -------------------------------------------------------------------------------- /lock_unlock.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env luajit 2 | local lfs = require('./lfs_ffi') 3 | 4 | local fh = io.open('temp.txt', 'r+') 5 | 6 | local start = os.clock() 7 | while true do 8 | local ok = lfs.lock(fh, 'w', 2, 7) 9 | if ok then 10 | print('get lock') 11 | break 12 | end 13 | if os.clock() - start > 5 then 14 | print('ERROR: timeout') 15 | return 16 | end 17 | end 18 | 19 | start = os.clock() 20 | while os.clock() - start < 3 do 21 | end 22 | 23 | print('unlock') 24 | local _, err = lfs.unlock(fh) 25 | print(err) 26 | -------------------------------------------------------------------------------- /test_lock.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | local file = io.open('temp.txt', 'w') 4 | file:write('0123456789') 5 | file:close() 6 | 7 | io.popen('lua lock_unlock.lua') 8 | os.execute('lua lock_unlock.lua') 9 | 10 | os.remove('temp.txt') 11 | -------------------------------------------------------------------------------- /test_valgrind.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | -- This file drives valgrind test 4 | -- See .travis.yml for more detail. 5 | require "busted.runner"({ standalone = false }) 6 | -------------------------------------------------------------------------------- /travis.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | ./test_lock.lua > /tmp/lfs 5 | grep 'ERROR' /tmp/lfs && exit 1 6 | if [ "$(uname)" = "Linux" ]; then 7 | sudo apt-get install valgrind 8 | LUA=luajit 9 | test -z "$(which $LUA)" && LUA=$PWD/lua_install/bin/lua 10 | valgrind --error-exitcode=42 --tool=memcheck \ 11 | --gen-suppressions=all --suppressions=valgrind.suppress \ 12 | "$LUA" test_valgrind.lua . 13 | fi 14 | -------------------------------------------------------------------------------- /valgrind.suppress: -------------------------------------------------------------------------------- 1 | { 2 | 3 | Memcheck:Cond 4 | obj:* 5 | } 6 | --------------------------------------------------------------------------------