luaforwindows/files/lua/tar.lua

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