Skip to content

libarchive integration with Lua so you can read and write tarballs from Lua.

Notifications You must be signed in to change notification settings

brimworks/lua-archive

Repository files navigation

**********************************************************************
* Author  : Brian Maher <maherb at brimworks dot com>
* Library : lua_archive - Lua 5.1 interface to libarchive
*
* The MIT License
* 
* Copyright (c) 2009 Brian Maher
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
**********************************************************************

To use this library, you need libarchive, get it here:
     http://code.google.com/p/libarchive/

To build this library, you need CMake, get it here:
    http://www.cmake.org/cmake/resources/software.html

Loading the library:

    If you built the library as a loadable package
        [local] archive = require 'archive'

    If you compiled the package statically into your application, call
    the function "luaopen_archive(L)". It will create a table with the
    archive functions and leave it on the stack.

-- archive functions --

major, minor, patch = archive.version()

    returns numeric major, minor, and patch versions from the archive
    library.

write = archive.write {
    compression     = "gzip",
    format          = "pax-restricted",
    options         = "compression-level=9,sha1",
    block_size      = 10240,
    last_block_size = 1,
    writer          = function(archive_write, string)
        if ( nil == string ) then
           fh:close()
        else
           fh:write(string)
           return #string
        end
    end,
}

    Generate an archive.  All parameters are optional except for the
    writer parameter.  The writer is called with nil when EOF is
    reached, otherwise it is called with a string to be written, and
    it is currently a requirement to return the number of bytes
    written (libarchive has this API, so I preserved it).  The
    archive_write is the instance of the "archive{write}" object
    requesting to write data.

    Returns an "archive{write}" object with these functions that are used to
    create your archive:

    write:header(archive_entry)

        Append a new file entry to the archive.  archive_entry must be
        an "archive{entry}" object (see  below for how to create one).

    write:data(string)

        Append the file contents for the last file entry created.

    write:close()

       Be sure to clean-up the resources and close the underlying file
       handle.  Due to the nature of the underlying implementation, it
       will take two rounds of GC to autmoatically collect an object
       that was not closed.

read = archive.read {
    reader = function(archive_read)
        return fh:read(10000)
    end,
    -- TODO: document other options.
}

    Reads an archive.  All parameters are optional except for the
    reader function that returns nil on EOF, otherwise it returns the
    bytes read from the archive file.  The archive_read parameter is
    the instance of the "archive{read}" object that is requesting to
    read some bytes.

    Returns an "archive{read}" object with these functions used to
    read the archive:

    archive_header = read:next_header()

        Reads the next header entry from the archive, or nil if there
        are no more files in the archive.

    string = read:data()

        Reads a buffer of data from the file for which we just got the
        header for (must call next_header() at least once before
        calling this).  Returns nil if there is no more data to read
        from this file entry (but there may still be more file
        entries!).

    read:close()

       Be sure to clean-up the resources and close the underlying file
       handle.  Due to the nature of the underlying implementation, it
       will take two rounds of GC to autmoatically collect an object
       that was not closed.

entry = archive.entry {
    sourcepath = <string>,
    pathname = <string>,
    mode = <number>,
    dev = <number>,
    ino = <number>,
    nlink = <number>,
    uid = <number>,
    uname = <string>,
    gid = <number>,
    gname = <string>,
    atime = { <number>, <number> },
    mtime = { <number>, <number> },
    ctime = { <number>, <number> },
    birthtime = { <number>, <number> },
    size = <number>,
    fflags = <string>,
}

    Create a new archive entry.  If passed in a sourcepath, then all
    relevant fields will be initialized with the results of lstat().
    Each field is then iterated over, calling the appropriate set
    method mentioned below.

    Returns an "archive{entry}" object with the following methods:

    path = entry:sourcepath()
    entry:sourcepath(path)

        Get/set the 'sourcepath', note that this field is not saved to
        the archive and is not even used by libarchive, but exists as
        a convenience.  Also note that the special logic of
        initializing from the resuls of lstat() only applies to the
        constructor and does not apply to the setter here.

    path = entry:pathname()
    entry:pathname(path)

         Get/set the 'pathname'.  You pretty much always want this to
         be a relative path... otherwise people get mad.

    number = entry:mode()
    entry:mode(number)

         Get/set the 'mode' as a raw number.  A certain part of this
         field is the file type and another part is the permissions.
         TODO: provide better support for manipulating the mode?

    number = entry:dev()
    number = entry:rdev()
    entry:dev(number)
    entry:rdev(number)

        Get/set the 'dev' or 'rdev' field (so far, I've not seen
        'rdev' field be preserved... so I'm not sure exactly what it
        does).

    number = entry:ino()
    entry:ino(number)

        Get/set the 'ino' field.  This is used to uniquely identify a
        file on a given device (if ino + dev is the same, they must be
        the same file).

    number = entry:nlink()
    entry:nlink(number)

        Get/set the number of hardlinks.

    number = entry:uid()
    entry:uid(number)

        Get/set the user identifier that owns this file.

    string = entry:uname()
    entry:uname(string)

        Get/set the user name that owns this file.  Note that it is a
        good idea to resolve the uid to a uname when archiving in case
        the system on which you read the archive does not have a
        matching uid.  This resolution is not autmoatically done with
        the sourcepath constructor field, but this might be done
        automatically in the future?

    number = entry:gid()
    entry:gid(number)

        Get/set the group identifier that owns this file.

    string = entry:gname()
    entry:gname(string)

        Get/set the group name that owns this file.  Again, it is a
        good idea to resolve gid to gname.

    (seconds, microseconds) = entry:atime()
    entry:atime(seconds, microseconds)
    (seconds, microseconds) = entry:mtime()
    entry:mtime(seconds, microseconds)
    (seconds, microseconds) = entry:ctime()
    entry:ctime(seconds, microseconds)
    (seconds, microseconds) = entry:birthtime()
    entry:birthtime(seconds, microseconds)

        Get/set the "access"/"modified"/"creation"/"birth" time of the
        file.  The microseconds field is optional and may be nil.

    number = entry:size()
    entry:size(number)

        Get/set the size of the file represented by the entry.

    string = entry:fflags()
    entry:fflags(string)

        Get/set the "fflags", these are special attributes of the
        file (like archive,dump,nosappnd).

######################################################################
# TODO: disk API!  Way to easily traverse the filesystem?

write = archive.write_disk {
    options = "secure-nodotdot,secure-symlinks,no-overwrite",    
}

    Generate on-disk representation of an archive, by handling the
    various events when parsing an archive.

    write:header(archive.entry)

        Create a file on disk for this archive entry.

    write:data("data to append to archive entry")

        Append data to the last file header created on disk.

    write:close()

       Be sure to clean-up the resources and close the underlying file
       handle.

read = archive.disk_read {
    -- TODO: Various options...
    events = {
        header = function(events, archive.entry),
        data   = function(events, string_data),
        close  = function(events), -- Called on EOF.
    }
}

    Read files and file entries from disk.    

    read("path/on/disk/to/file.txt" [, "location/in/archive/file.txt"])

    read(nil) -- For EOF

        Call it with the location of a file on disk and trigger the
        header and data events.  Call with nil to trigger the close
        event.

        You may optionally specify the location of the entry as you
        want it stored in an archive.  Helpful if you are using
        absolute paths, but want the archive to store relative paths.

About

libarchive integration with Lua so you can read and write tarballs from Lua.

Resources

Stars

Watchers

Forks

Packages

No packages published