aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/lfs_ext.lua17
-rw-r--r--test/test.lua24
2 files changed, 35 insertions, 6 deletions
diff --git a/core/lfs_ext.lua b/core/lfs_ext.lua
index c24ee2e9..5bbe609a 100644
--- a/core/lfs_ext.lua
+++ b/core/lfs_ext.lua
@@ -19,9 +19,14 @@ module('lfs')]]
lfs.default_filter = {--[[Extensions]]'!.a','!.bmp','!.bz2','!.class','!.dll','!.exe','!.gif','!.gz','!.jar','!.jpeg','!.jpg','!.o','!.pdf','!.png','!.so','!.tar','!.tgz','!.tif','!.tiff','!.xz','!.zip',--[[Directories]]'!/%.bzr$','!/%.git$','!/%.hg$','!/%.svn$','!/_FOSSIL_$','!/node_modules$'}
-- Documentation is in `lfs.walk()`.
+-- @param seen Utility table that holds directories seen. If there is a
+-- duplicate, stop walking down that path (it's probably a recursive symlink).
-- @param level Utility value indicating the directory level this function is
-- at.
-local function walk(dir, filter, n, include_dirs, level)
+local function walk(dir, filter, n, include_dirs, seen, level)
+ local sep = not WIN32 and '/' or '\\'
+ local os_dir = not WIN32 and dir or dir:gsub('/', sep)
+ seen[os_dir], seen[os_dir .. sep] = true, true
for basename in lfs.dir(dir) do
if basename:find('^%.%.?$') then goto continue end -- ignore . and ..
local filename = dir .. (dir ~= '/' and '/' or '') .. basename
@@ -42,14 +47,14 @@ local function walk(dir, filter, n, include_dirs, level)
include = include or (not patt:find('^!') and filename:find(patt))
end
if not include then goto continue end
- local sep = not WIN32 and '/' or '\\'
local os_filename = not WIN32 and filename or filename:gsub('/', sep)
if mode == 'file' then
coroutine.yield(os_filename)
- elseif mode == 'directory' then
+ elseif mode == 'directory' and
+ not seen[lfs.symlinkattributes(filename, 'target')] then
if include_dirs then coroutine.yield(os_filename .. sep) end
if n and (level or 0) >= n then goto continue end
- walk(filename, filter, n, include_dirs, (level or 0) + 1)
+ walk(filename, filter, n, include_dirs, seen, (level or 0) + 1)
end
::continue::
end
@@ -77,7 +82,7 @@ end
-- @see filter
-- @name walk
function lfs.walk(dir, filter, n, include_dirs)
- assert_type(dir, 'string', 1)
+ dir = assert_type(dir, 'string', 1):match('^(.-)[/\\]?$')
if not assert_type(filter, 'string/table/nil', 2) then
filter = lfs.default_filter
end
@@ -104,7 +109,7 @@ function lfs.walk(dir, filter, n, include_dirs)
end
end
local co = coroutine.create(
- function() walk(dir, processed_filter, n, include_dirs) end)
+ function() walk(dir, processed_filter, n, include_dirs, {}) end)
return function() return select(2, coroutine.resume(co)) end
end
diff --git a/test/test.lua b/test/test.lua
index ee4cbd0f..b1b4ee74 100644
--- a/test/test.lua
+++ b/test/test.lua
@@ -720,6 +720,30 @@ function test_lfs_ext_walk_win32()
_G.WIN32 = win32 -- reset just in case
end
+function test_lfs_ext_walk_symlinks()
+ local dir = os.tmpname()
+ os.remove(dir)
+ lfs.mkdir(dir)
+ lfs.mkdir(dir .. '/1')
+ io.open(dir .. '/1/foo', 'w'):close()
+ lfs.mkdir(dir .. '/1/bar')
+ io.open(dir .. '/1/bar/baz', 'w'):close()
+ lfs.link(dir .. '/1/', dir .. '/1/bar/quux', true) -- trailing '/' on purpose
+ lfs.mkdir(dir .. '/2')
+ io.open(dir .. '/2/foobar', 'w'):close()
+ lfs.link(dir .. '/2/foobar', dir .. '/2/foobaz', true)
+ lfs.link(dir .. '/2', dir .. '/1/2', true)
+ local files = {}
+ for filename in lfs.walk(dir .. '/1/') do -- trailing '/' on purpose
+ files[#files + 1] = filename
+ end
+ table.sort(files)
+ local expected_files = {dir .. '/1/foo', dir .. '/1/bar/baz', dir .. '/1/2/foobar', dir .. '/1/2/foobaz'}
+ table.sort(expected_files)
+ assert_equal(files, expected_files)
+ os.execute('rm -r ' .. dir)
+end
+
function test_lfs_ext_abs_path()
assert_equal(lfs.abspath('bar', '/foo'), '/foo/bar')
assert_equal(lfs.abspath('./bar', '/foo'), '/foo/bar')