263 lines
8.3 KiB
Lua
Executable File
263 lines
8.3 KiB
Lua
Executable File
-----------------------------------------------------------------------------
|
|
-- tar - Lua Tape ARchive module
|
|
--
|
|
-- Code for managing tar files
|
|
--
|
|
-- Version 0.1 (21 AUG 2007)
|
|
--
|
|
-- Author: Judge Maygarden (jmaygarden@computer.org)
|
|
--
|
|
-- Copyright (c) 2007 Judge Maygarden
|
|
-----------------------------------------------------------------------------
|
|
|
|
local public = {}
|
|
local private = {}
|
|
local TarFile = {}
|
|
local TarInternalFile = {}
|
|
tar = public
|
|
TarFile.__index = TarFile
|
|
TarInternalFile.__index = TarInternalFile
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Closes file
|
|
-----------------------------------------------------------------------------
|
|
function TarInternalFile:close()
|
|
end
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- UNSUPPORTED: Saves any written data to file
|
|
-----------------------------------------------------------------------------
|
|
function TarInternalFile:flush()
|
|
error('Tar file output flushing is unsupported.')
|
|
end
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Returns an iterator function that returns a new line from the file
|
|
-- @return String repressenting a line from the file without the newline
|
|
-----------------------------------------------------------------------------
|
|
function TarInternalFile:lines()
|
|
return function(s, var)
|
|
return self:read("*l")
|
|
end, self, nil
|
|
end
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Reads the file according to the given formats as follows:
|
|
-- "*n": reads a number
|
|
-- "*a": reads the whole file
|
|
-- "*l" reads the next line
|
|
-- number: reads a string with up to this number of characters
|
|
-- @param variadic list of formats (default "*l")
|
|
-- @return list of strings (or numbers) or nil if end of file or format error
|
|
-----------------------------------------------------------------------------
|
|
function TarInternalFile:read(...)
|
|
local eof = self.offset + self.size
|
|
if self.pointer >= eof then return nil end -- eof
|
|
local t = {}
|
|
self.archive.file:seek("set", self.pointer)
|
|
for i, v in ipairs{...} do
|
|
local s
|
|
if "*a" == v then
|
|
s = self.archive.file:read(eof - self.pointer)
|
|
elseif "*l" == v or "*n" == v or "number" == type(v) then
|
|
s = self.archive.file:read(v)
|
|
else
|
|
error('bad argument %d to %s (%s)', i
|
|
'TarInternalFile:read', 'invalid format')
|
|
return nil
|
|
end
|
|
local last = self.pointer
|
|
self.pointer = self.archive.file:seek()
|
|
if s and self.pointer < eof then
|
|
table.insert(t, s)
|
|
elseif s then
|
|
table.insert(t, string.sub(s, 1, eof - last - 1))
|
|
self.pointer = self.offset + self.size
|
|
break
|
|
else
|
|
break
|
|
end
|
|
end
|
|
return unpack(t)
|
|
end
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Sets and gets the file position measured from the given base as follows:
|
|
-- "set": base is position 0 (beginning of the file)
|
|
-- "cur": base is current position
|
|
-- "end": base is end of file
|
|
-- @param base position (default "cur")
|
|
-- @param offset in bytes (default 0)
|
|
-- @return final position
|
|
-----------------------------------------------------------------------------
|
|
function TarInternalFile:seek(whence, offset)
|
|
local whence = whence or "cur"
|
|
local offset = offset or 0
|
|
local eof = self.offset + self.size
|
|
local final
|
|
local err
|
|
|
|
if "cur" == whence then
|
|
final, err = self.archive.file:seek("set",
|
|
offset + self.pointer) - self.offset
|
|
|
|
elseif "set" == whence then
|
|
final, err = self.archive.file:seek("set",
|
|
offset + self.offset) - self.offset
|
|
|
|
elseif "end" == whence then
|
|
final, err = self.archive.file:seek("set",
|
|
offset + eof - 1) - self.offset
|
|
|
|
else
|
|
final, err = self.archive.file:seek(whence, offset)
|
|
end
|
|
|
|
if not final then
|
|
return nil, err
|
|
elseif final < 0 then
|
|
final = 0
|
|
elseif final > self.size then
|
|
final = self.size
|
|
end
|
|
self.pointer = final + self.offset
|
|
|
|
return final
|
|
end
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- UNSUPPORTED: Sets the buffering mode for an output file
|
|
-- @param "no" - no buffering, "full" - full buffering, "line" - line buffering
|
|
-- @param size of the buffer for "full" and "line" modes
|
|
-----------------------------------------------------------------------------
|
|
function TarInternalFile:setvbuf(mode, size)
|
|
error('Tar file output buffering is unsupported.')
|
|
end
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- UNSUPPORTED: Writes the value of each of its arguments to the file
|
|
-- @param string or numbers values only
|
|
-----------------------------------------------------------------------------
|
|
function TarInternalFile:write(...)
|
|
error('Tar file writes are unsupported.')
|
|
end
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Opens a file inside the tar file archive
|
|
-- @param pathname of a file inside the tar file
|
|
-- @return file handle or nil if the file is not found
|
|
-----------------------------------------------------------------------------
|
|
function TarFile:open(filename)
|
|
if not self.list[filename] then return nil end
|
|
local file = {}
|
|
file.archive = self
|
|
file.filename = filename
|
|
file.offset = self.list[filename].offset
|
|
file.pointer = file.offset
|
|
file.size = self.list[filename].size
|
|
setmetatable(file, TarInternalFile)
|
|
return file
|
|
end
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Returns an iterator over all files in the tar archive
|
|
-- @return interator function, tar file handle
|
|
-----------------------------------------------------------------------------
|
|
function TarFile:files()
|
|
return next, self.list, nil
|
|
end
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Data for parsing tar header fields
|
|
-----------------------------------------------------------------------------
|
|
private.HEADER_DATA = {
|
|
-- { field, offset, size, octal }
|
|
{ "name", 0, 100 },
|
|
{ "mode", 100, 8, true },
|
|
{ "uid", 108, 8, true },
|
|
{ "gid", 116, 8, true },
|
|
{ "size", 124, 12, true },
|
|
{ "mtime", 136, 12, true },
|
|
{ "chksum", 148, 8, true },
|
|
{ "typeflag", 156, 1, true },
|
|
{ "linkname", 157, 100 },
|
|
{ "magic", 257, 6 },
|
|
{ "version", 263, 2 },
|
|
{ "uname", 265, 32 },
|
|
{ "gname", 297, 32 },
|
|
{ "devmajor", 329, 8, true },
|
|
{ "devminor", 337, 8, true },
|
|
{ "prefix", 345, 155 },
|
|
}
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Converts an octal string into a number
|
|
-- @param string of octal digits
|
|
-- @return number or nil if an invalid string was passed
|
|
-----------------------------------------------------------------------------
|
|
private.octal = function(s)
|
|
if string.match(s, "[^0-7]") then return nil end
|
|
local n, m = 0, 0
|
|
for i = string.len(s), 1, -1 do
|
|
n = n + (string.byte(s, i) - 48) * 8 ^ m
|
|
m = m + 1
|
|
end
|
|
return n
|
|
end
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Decodes a tar file header
|
|
-- @param 512-byte string containing a tar file header block
|
|
-- @return table containg the header fields
|
|
-----------------------------------------------------------------------------
|
|
private.decode = function(header)
|
|
local t = {}
|
|
for _, field in ipairs(private.HEADER_DATA) do
|
|
local s = string.sub(header, field[2] + 1, field[2] + field[3])
|
|
local s = string.match(s, "[^%z]*")
|
|
if field[4] then
|
|
t[field[1]] = private.octal(s)
|
|
else
|
|
t[field[1]] = s
|
|
end
|
|
end
|
|
if 0 < string.len(t.prefix) then
|
|
t.pathname = t.prefix..'/'..t.name
|
|
else
|
|
t.pathname = t.name
|
|
end
|
|
return t
|
|
end
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Opens a tar archive by attaching to the provided Lua file handle.
|
|
-- Ownership of the file handle is transferred to the returned table and
|
|
-- should no longer be referenced by the user.
|
|
-- @param open file handle
|
|
-- @return tar file handle
|
|
-----------------------------------------------------------------------------
|
|
public.open = function(file)
|
|
assert(file, 'invalid file handle')
|
|
local archive = {}
|
|
archive.file = file
|
|
archive.list = {}
|
|
local p = file:seek("set")
|
|
while true do
|
|
local block = file:read(512)
|
|
if not block or not string.match(block, "[^%z]") then break end
|
|
local header = private.decode(block)
|
|
if 0 == header.typeflag then
|
|
archive.list[header.pathname] = {
|
|
["offset"] = file:seek(),
|
|
["size"] = header.size
|
|
}
|
|
end
|
|
local p = file:seek("cur", 512 * math.ceil(header.size / 512))
|
|
end
|
|
setmetatable(archive, TarFile)
|
|
return archive
|
|
end
|
|
|
|
return tar
|
|
|