Skip to content

Commit e93265b

Browse files
Christophe DelordChristophe Delord
authored andcommitted
tar.lua: symbolic links support
1 parent ef29c6f commit e93265b

File tree

3 files changed

+97
-15
lines changed

3 files changed

+97
-15
lines changed

doc/tar.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
local tar = require "tar"
55
```
66

7-
The `tar` module can read and write tar archives. Only files and
8-
directories are supported.
7+
The `tar` module can read and write tar archives. Only files,
8+
directories and symbolic links are supported.
99

1010
``` lua
1111
tar.tar(files, [xform])

libluax/tar/tar.lua

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ local tar = require "tar"
2828
```
2929
3030
The `tar` module can read and write tar archives.
31-
Only files and directories are supported.
31+
Only files, directories and symbolic links are supported.
3232
@@@]]
3333

3434
-- https://fr.wikipedia.org/wiki/Tar_%28informatique%29
@@ -51,6 +51,7 @@ end
5151

5252
local file_type = F{
5353
file = "0",
54+
link = "2",
5455
directory = "5",
5556
}
5657

@@ -59,6 +60,7 @@ rev_file_type["\0"] = rev_file_type["0"]
5960

6061
local default_mode = {
6162
file = tonumber("644", 8),
63+
link = tonumber("777", 8),
6264
directory = tonumber("755", 8),
6365
}
6466

@@ -77,20 +79,22 @@ local function header(st, xform)
7779
local name = xform(clean_path(st.name))
7880
if name == nil then return Discarded end
7981
if #name > 100 then return nil, name..": filename too long" end
80-
if st.size >= 8*1024^3 then return nil, st.name..": file too big" end
82+
if st.type=="file" and st.size >= 8*1024^3 then return nil, st.name..": file too big" end
8183
local ftype = file_type[st.type]
8284
if not ftype then return nil, st.name..": wrong file type" end
85+
if st.type=="link" and not st.link then return nil, st.name..": missing link name" end
86+
if st.link and #st.link > 100 then return nil, link..": filename too long" end
8387
local header1 = pack("c100c8c8c8c12c12",
8488
name,
8589
format("%07o", st.mode or default_mode[st.type] or "0"),
8690
"",
8791
"",
88-
format("%011o", st.size),
92+
format("%011o", st.size or 0),
8993
format("%011o", st.mtime)
9094
)
9195
local header2 = pack("c1c100c6c2c32c32c8c8c155c12",
9296
ftype,
93-
"",
97+
st.link or "",
9498
"", "", "", "", "", "", "", ""
9599
)
96100
local checksum = format("%07o", sum(bytes(header1)) + sum(bytes(header2)) + 32*8)
@@ -102,7 +106,7 @@ local function end_of_archive()
102106
end
103107

104108
local function parse(archive, i)
105-
local name, mode, _, _, size, mtime, checksum, ftype = unpack("c100c8c8c8c12c12c8c1", archive, i)
109+
local name, mode, _, _, size, mtime, checksum, ftype, link = unpack("c100c8c8c8c12c12c8c1c100", archive, i)
106110
if not checksum then return nil, "Corrupted archive" end
107111
local function cut(s) return s:match "^[^\0]*" end
108112
if sum(bytes(archive:sub(i, i+148-1))) + sum(bytes(archive:sub(i+156, i+512-1))) + 32*8 ~= tonumber(cut(checksum), 8) then
@@ -116,6 +120,7 @@ local function parse(archive, i)
116120
size = tonumber(cut(size), 8),
117121
mtime = tonumber(cut(mtime), 8),
118122
type = ftype,
123+
link = ftype=="link" and cut(link) or nil,
119124
}
120125
end
121126

@@ -176,6 +181,20 @@ function tar.tar(files, xform)
176181
return true
177182
end
178183

184+
local function add_link(st)
185+
local xformed_name = xform(st.name)
186+
if xformed_name == nil then return true end
187+
if done(xformed_name) then return true end
188+
local ok, err = add_dir(xformed_name:dirname(), st)
189+
if not ok then return nil, err end
190+
local hd
191+
hd, err = header(st, xform)
192+
if hd == Discarded then return true end
193+
if not hd then return nil, err end
194+
chunks[#chunks+1] = hd
195+
return true
196+
end
197+
179198
local function add_real_dir(path)
180199
if done(path) then return true end
181200
if path:dirname() == path then return true end
@@ -211,13 +230,32 @@ function tar.tar(files, xform)
211230
return true
212231
end
213232

233+
local function add_real_link(st)
234+
local xformed_name = xform(st.name)
235+
if xformed_name == nil then return true end
236+
if done(xformed_name) then return true end
237+
local linkst, sterr = fs.stat(st.name)
238+
if not linkst then return nil, sterr end
239+
local ok, err = add_real_dir(st.name:dirname())
240+
if not ok then return nil, err end
241+
local hd
242+
st.link = stlink.name
243+
hd, err = header(st, xform)
244+
if hd == Discarded then return true end
245+
if not hd then return nil, err end
246+
chunks[#chunks+1] = hd
247+
return true
248+
end
249+
214250
for _, file in ipairs(files) do
215251

216252
if type(file) == "string" then
217253
local st, err = fs.stat(file)
218254
if not st then return nil, err end
219255
if st.type == "file" then
220256
add_real_file(st)
257+
elseif st.type == "link" then
258+
add_real_link(st)
221259
elseif st.type == "directory" then
222260
add_real_dir(st.name)
223261
for _, name in ipairs(fs.ls(st.name/"**")) do
@@ -241,22 +279,40 @@ function tar.tar(files, xform)
241279
if file.content then
242280
st.content = file.content
243281
st.size = #file.content
282+
elseif file.link then
283+
st.type = "link"
284+
st.link = file.link
244285
else
245-
st0, err = fs.stat(file.name)
286+
if sys.os == "windows" then
287+
st0, err = fs.stat(file.name)
288+
else
289+
st0, err = fs.lstat(file.name)
290+
end
246291
if not st0 then return nil, err end
247-
local content
248-
content, err = fs.read_bin(file.name)
249-
if not content then return nil, err end
250-
st.size = st0.size
251-
st.content = content
292+
if st0.type == "link" then
293+
local linkst, linkerr = fs.stat(file.name)
294+
if not linkst then return nil, linkerr end
295+
st.type = "link"
296+
st.link = linkst.name
297+
else
298+
local content
299+
content, err = fs.read_bin(file.name)
300+
if not content then return nil, err end
301+
st.size = st0.size
302+
st.content = content
303+
end
252304
end
253305
if file.mtime then
254306
st.mtime = file.mtime
255307
else
256308
st.mtime = st0 and st0.mtime or os.time()
257309
end
258310
local ok
259-
ok, err = add_file(st)
311+
if st.type == "link" then
312+
ok, err = add_link(st)
313+
else
314+
ok, err = add_file(st)
315+
end
260316
if not ok then return nil, err end
261317

262318
end
@@ -293,7 +349,7 @@ function tar.untar(archive, xform)
293349
if st.type == "file" then
294350
st.content = archive:sub(i+512, i+512+st.size-1)
295351
i = i + 512 + st.size + pad(st.size)
296-
elseif st.type == "directory" then
352+
elseif st.type == "link" or st.type == "directory" then
297353
i = i + 512
298354
else
299355
return nil, st.type..": file type not supported"

tests/luax-tests/tar_test.lua

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ return function()
7474
local files = {
7575
{ name="foo/bar/baz.txt", content="baz content", mtime=1739568630 },
7676
{ name="hello.txt", content="Hello, World!", mtime=1739568630+60 },
77+
{ name="baz/baz.link", link="foo/bar/baz.txt", mtime=1739568630+60*2 },
7778
}
7879
local archive = tar.tar(files)
7980
if sys.os == "linux" then
@@ -86,6 +87,8 @@ drwxr-xr-x 0/0 0 2025-02-14 22:30 foo
8687
drwxr-xr-x 0/0 0 2025-02-14 22:30 foo/bar
8788
-rw-r--r-- 0/0 11 2025-02-14 22:30 foo/bar/baz.txt
8889
-rw-r--r-- 0/0 13 2025-02-14 22:31 hello.txt
90+
drwxr-xr-x 0/0 0 2025-02-14 22:32 baz
91+
lrwxrwxrwx 0/0 0 2025-02-14 22:32 baz/baz.link -> foo/bar/baz.txt
8992
]])
9093
end
9194

@@ -122,6 +125,21 @@ drwxr-xr-x 0/0 0 2025-02-14 22:30 foo/bar
122125
size = 13,
123126
content = "Hello, World!",
124127
},
128+
{
129+
name = "baz",
130+
type = "directory",
131+
mode = tonumber("755", 8),
132+
mtime = 1739568750,
133+
size = 0,
134+
},
135+
{
136+
name = "baz/baz.link",
137+
type="link",
138+
mode = tonumber("777", 8),
139+
mtime = 1739568750,
140+
size = 0,
141+
link = "foo/bar/baz.txt",
142+
},
125143
})
126144
end
127145

@@ -143,6 +161,14 @@ drwxr-xr-x 0/0 0 2025-02-14 22:30 foo/bar
143161
size = 11,
144162
content = "baz content",
145163
},
164+
{
165+
name = "baz.link",
166+
type="link",
167+
mode = tonumber("777", 8),
168+
mtime = 1739568750,
169+
size = 0,
170+
link = "foo/bar/baz.txt",
171+
}
146172
})
147173
end
148174

0 commit comments

Comments
 (0)