util-tar.lua /size: 10 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['util-tar'] = {
2    version   = 1.001,
3    comment   = "companion to luat-lib.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9local type, tonumber = type, tonumber
10local gsub, escapedpattern = string.gsub, string.escapedpattern
11local nameonly, dirname, makedirs = file.nameonly, file.dirname, dir.makedirs
12local savedata = io.savedata
13local newreader = io.newreader
14
15local report = logs.reporter("tar")
16
17local types = {
18    ["0"]  = "file",
19    ["\0"] = "regular",
20    ["1"]  = "link",
21    ["2"]  = "symbolic",     -- reserved
22    ["3"]  = "character",
23    ["4"]  = "block",
24    ["5"]  = "directory",
25    ["6"]  = "fifo",
26    ["7"]  = "continuation", -- reserved
27    ["x"]  = "extended",     -- header
28}
29
30local function asstring(str)
31    return str and gsub(str,"[\0 ]+$","") or nil
32end
33
34local function asnumber(str)
35    str = gsub(str,"\0$","")
36    return tonumber(str,8)
37end
38
39local function opentar(whatever,filename)
40    local f = newreader(filename,whatever)
41    if f then
42        f.metadata = {
43            nofpaths = 0,
44            noffiles = 0,
45            noflinks = 0,
46            nofbytes = 0,
47        }
48        return f
49    end
50end
51
52local function readheader(t)
53    -- checksum
54    local p = t:getposition()
55    local h = t:readbytetable(512)
56    t:setposition(p)
57    for i=149,156 do -- nasty, one less
58        h[i] = 0
59    end
60    local c = 256
61    for i=1,512 do
62        c = c + h[i]
63    end
64    --
65    local header = {
66        name     = asstring(t:readstring(100)),    --   0
67        mode     = asnumber(t:readstring(  8)), -- 100 -- when we write: 0775 octal
68        uid      = asnumber(t:readstring(  8)), -- 108
69        gid      = asnumber(t:readstring(  8)), -- 116
70        size     = asnumber(t:readstring( 12)), -- 124
71        mtime    = asnumber(t:readstring( 12)), -- 136
72        checksum = asnumber(t:readstring(  6)), -- 148 -- actually 6 with space and \0
73        dummy    =          t:skip        (2) ,
74        typeflag =          t:readstring(  1) , -- 156
75        linkname = asstring(t:readstring(100)), -- 157
76     -- magic    = asstring(t:readstring(  6)), -- 257 -- ustar\0
77     -- version  =                         2    -- 263
78     -- uname    =                        32    -- 265
79     -- gname    =                        32    -- 297
80     -- devmajor =                         8    -- 329
81     -- devminor =                         8    -- 337
82     -- prefix   =                       155    -- 345
83        padding  =          t:skip      (255) , -- 500
84    }
85    local typeflag = header.typeflag
86    if typeflag then
87        header.filetype = types[typeflag]
88        if c == header.checksum then
89            return header
90        end
91    end
92end
93
94local readers = {
95
96    directory = function(t,h)
97        local metadata = t.metadata
98        local filename = h.name
99        if metadata.verbose then
100            report("%8s   %s","",filename)
101        end
102        metadata.nofpaths = metadata.nofpaths + 1
103        return true
104    end,
105
106    file = function(t,h)
107        local metadata = t.metadata
108        local filename = h.name
109        local filesize = h.size
110        local pathname = dirname(filename)
111        if metadata.verbose then
112            report("% 8i : %s",filesize,filename)
113        end
114        if makedirs(pathname) then
115            savedata(filename,t:readstring(filesize))
116        else
117            t.skip(filesize)
118        end
119        local position = t:getposition()
120        local target   = position + (512 - position % 512) % 512
121        t:setposition(target)
122        metadata.noffiles = metadata.noffiles + 1
123        metadata.nofbytes = metadata.nofbytes + filesize
124        return true
125    end,
126
127    symbolic = function(t,h)
128        local metadata = t.metadata
129        local filename = h.name
130        local linkname = h.linkname
131        if metadata.verbose then
132            report("%8s   %s => %s","",linkname,filename)
133        end
134        metadata.noflinks = metadata.noflinks + 1
135        return true
136    end,
137
138}
139
140local skippers = {
141
142    directory = function(t,h)
143        return true
144    end,
145
146    file = function(t,h)
147        local filesize   = h.size
148        local fileoffset = t:getposition()
149        local position   = filesize + fileoffset
150        local target     = position + (512 - position % 512) % 512
151        t:setposition(target)
152        return fileoffset
153    end,
154
155    symbolic = function(t,h)
156        return true
157    end,
158
159}
160
161local writers = {
162    -- nothing here (yet)
163}
164
165local function saveheader(t,h)
166    local filetype = h.filetype
167    local reader   = readers[filetype]
168    if reader then
169        return filetype, reader(t,h)
170    else
171        report("no reader for %s",filetype)
172    end
173end
174
175local function skipheader(t,h)
176    local filetype = h.filetype
177    local skipper  = skippers[filetype]
178    if skipper then
179        return filetype, skipper(t,h)
180    else
181        report("no skipper for %s",filetype)
182    end
183end
184
185local function unpacktar(whatever,filename,verbose)
186    local t = opentar(whatever,filename)
187    if t then
188        local metadata = t.metadata
189        statistics.starttiming(metadata)
190        if verbose then
191            if whatever == "string" then
192                report("unpacking: %i bytes",#filename)
193            else
194                report("unpacking: %s",filename)
195            end
196            report("")
197            metadata.verbose = verbose
198        end
199        while true do
200            local h = readheader(t)
201            if not h then
202                break
203            else
204                local filetype, saved = saveheader(t,h)
205                if not saved then
206                    break
207                end
208            end
209        end
210        statistics.stoptiming(metadata)
211        metadata.runtime = statistics.elapsed(metadata)
212        if verbose then
213            report("")
214            report("number of paths : %i",metadata.nofpaths)
215            report("number of files : %i",metadata.noffiles)
216            report("number of links : %i",metadata.noflinks)
217            report("number of bytes : %i",metadata.nofbytes)
218            report("")
219            report("runtime needed  : %s",statistics.elapsedseconds(metadata))
220            report("")
221        end
222        t.close()
223        return metadata
224    end
225end
226
227local function listtar(whatever,filename,onlyfiles)
228    local t = opentar(whatever,filename)
229    if t then
230        local list, n = { }, 0
231        while true do
232            local h = readheader(t)
233            if not h then
234                break
235            else
236                local filetype, offset = skipheader(t,h)
237                if not offset then
238                    break
239                elseif filetype == "file" then
240                    n = n + 1 ; list[n] = { filetype, h.name, h.size }
241                elseif filetype == "link" then
242                    n = n + 1 ; list[n] = { filetype, h.name, h.linkfile }
243                elseif not onlyfiles then
244                    n = n + 1 ; list[n] = { filetype, h.name }
245                end
246            end
247        end
248        t.close()
249        -- can be an option
250        table.sort(list,function(a,b) return a[2] < b[2] end)
251        return list
252    end
253end
254
255local function hashtar(whatever,filename,strip)
256    local t = opentar(whatever,filename)
257    if t then
258        local list = { }
259        if strip then
260            strip = "^" .. escapedpattern(nameonly(nameonly(strip))) .. "/"
261        end
262        while true do
263            local h = readheader(t)
264            if not h then
265                break
266            else
267                local filetype, offset = skipheader(t,h)
268                if not offset then
269                    break
270                else
271                    local name = h.name
272                    if strip then
273                        name = gsub(name,strip,"")
274                    end
275                    if filetype == "file" then
276                        list[name] = { offset, h.size }
277                    elseif filetype == "link" then
278                        list[name] = h.linkname
279                    end
280                end
281            end
282        end
283        t.close()
284        return list
285    end
286end
287
288-- weak table ?
289
290local function fetchtar(whatever,archive,filename,list)
291    if not list then
292        list = hashtar(whatever,archive)
293    end
294    if list then
295        local what = list[filename]
296        if type(what) == "string" then
297            what = list[what] -- a link
298        end
299        if what then
300            local t = opentar(whatever,archive)
301            if t then
302                t:setposition(what[1])
303                return t:readstring(what[2])
304            end
305        end
306    end
307end
308
309local function packtar(whatever,filename,verbose)
310    report("packing will be implemented when we need it")
311end
312
313local tar = {
314    files = {
315        unpack = function(...) return unpacktar("file",  ...) end,
316        pack   = function(...) return packtar  ("file",  ...) end,
317        list   = function(...) return listtar  ("file",  ...) end,
318        hash   = function(...) return hashtar  ("file",  ...) end,
319        fetch  = function(...) return fetchtar ("file",  ...) end,
320    },
321    strings = {
322        unpack = function(...) return unpacktar("string",...) end,
323        pack   = function(...) return packtar  ("string",...) end,
324        list   = function(...) return listtar  ("string",...) end,
325        hash   = function(...) return hashtar  ("string",...) end,
326        fetch  = function(...) return fetchtar ("string",...) end,
327    },
328    streams = {
329        unpack = function(...) return unpacktar("stream",...) end,
330        pack   = function(...) return packtar  ("stream",...) end,
331        list   = function(...) return listtar  ("stream",...) end,
332        hash   = function(...) return hashtar  ("stream",...) end,
333        fetch  = function(...) return fetchtar ("stream",...) end,
334    },
335}
336
337utilities.tar = tar
338
339-- tar.files  .unpack("e:/luatex/luametatex-source.tar",true)
340-- tar.streams.unpack("e:/luatex/luametatex-source.tar",true)
341-- tar.strings.unpack(io.loaddata("e:/luatex/luametatex-source.tar"),true)
342
343-- inspect(tar.files  .unpack("e:/luatex/luametatex-source.tar"))
344-- inspect(tar.streams.unpack("e:/luatex/luametatex-source.tar"))
345-- inspect(tar.strings.unpack(io.loaddata("e:/luatex/luametatex-source.tar")))
346
347-- inspect(tar.files  .list("e:/luatex/luametatex-source.tar",true))
348-- inspect(tar.streams.list("e:/luatex/luametatex-source.tar",true))
349-- inspect(tar.strings.list(io.loaddata("e:/luatex/luametatex-source.tar"),true))
350
351-- local c = os.clock()
352-- local l = tar.files.hash("e:/luatex/luametatex-source.tar")
353-- for i=1,500 do
354--     local s = tar.files.fetch("e:/luatex/luametatex-source.tar", "luametatex-source/source/tex/texbuildpage.c", l)
355--     local s = tar.files.fetch( "e:/luatex/luametatex-source.tar","luametatex-source/source/lua/lmtlibrary.c",   l)
356-- end
357-- print(os.clock()-c)
358
359return tar
360