silk.compress.tar.

Read and write tar archives held in memory.

When to use#

Use a TarReader to unpack a .tar file, or a .tar.gz file after you decode it with silk.compress.inflate. Open the archive with TarReader.open and call TarReader.next until it returns none. Use a TarWriter to make one: add members with TarWriter.file and TarWriter.directory, then take the bytes with TarWriter.finish, and compress them with silk.compress.deflate for a .tar.gz. This module does not compress or decompress.

Details#

The reader understands ustar, GNU, and pax archives: the ustar prefix field, GNU long names, pax path records, and base-256 sizes. It does not allocate, and every member it returns borrows the archive. Each header's checksum is checked. A member's path is checked before the member is returned: an absolute path, a backslash, a NUL, or a .. segment refuses the archive, so a caller that joins a path to a directory and creates no links never writes outside it. Empty and . segments are allowed, because tar -C dir . writes paths such as ./include/x.h.

The writer writes ustar headers with fixed metadata: modification time 0, owner and group 0 with no names, and mode 644, or 755 for an executable file or a directory. A path longer than 100 bytes goes in the ustar prefix field when it splits at a /, else in a pax path record. The same members in the same order give the same bytes on every host, so a checksum of the archive names its content.

Errors#

Examples#

silk
import silk.compress.tar {TarError, TarKind, TarMember, TarReader}
import silk.core.option {Option}
import silk.core.result {Result}

// Counts the regular files of `archive`, or answers -1 when it is not a valid tar archive.
fn files(archive: &[u8]) -> i32 {
  let mut reader = TarReader.open(archive)
  let mut count = 0
  while true {
    match TarReader.next(&mut reader) {
      Result<Option<TarMember>, TarError>.Success {value} => match move value {
        Option<TarMember>.Some {value: member} => { if member.kind == TarKind.File { count = count + 1 } }
        Option<TarMember>.None => { return count }
      }
      Result<Option<TarMember>, TarError>.Failure {..} => { return -1 }
    }
  }
  return count
}

pub fn main() -> i32 {
  let empty: [u8; 1024] = [0; 1024]
  return files(&empty)
}

Write an archive and read it back#

silk
import silk.collections.bytes {Bytes}
import silk.compress.tar {TarError, TarMember, TarReader, TarWriter}
import silk.core.effect {Effect}
import silk.core.option {Option}
import silk.core.result {Result}
import silk.mem.allocator {Allocator, OutOfMemoryError}

effect fn archive() -> Bytes ! TarError | OutOfMemoryError ? &mut Allocator {
  let mut writer = TarWriter.make()
  run TarWriter.directory(&mut writer, b"app/bin")
  run TarWriter.file(&mut writer, b"app/bin/run", b"#!/bin/sh\n", true)
  run TarWriter.file(&mut writer, b"app/README", b"hello\n", false)
  return run TarWriter.finish(move writer)
}

pub effect fn main() -> i32 ! TarError | OutOfMemoryError {
  let mut allocator = Allocator.systemAllocatorProvider()
  let bytes = run archive() |> Effect.provideMut<Allocator>(&mut allocator)
  let mut reader = TarReader.open(Bytes.asSlice(&bytes))
  let mut members = 0
  while true {
    match TarReader.next(&mut reader) {
      Result<Option<TarMember>, TarError>.Success {value} => match move value {
        Option<TarMember>.Some {..} => { members = members + 1 }
        Option<TarMember>.None => { break }
      }
      Result<Option<TarMember>, TarError>.Failure {..} => { return 1 }
    }
  }
  return if members == 3 { 0 } else { 2 }
}

Import as TarReader with import silk.compress.tar { TarReader }.

Public declarations: 5.

TarError#

silk
pub enum TarError

The reason a tar archive was refused.

Truncated#

silk
Truncated

The archive ends inside a header or inside a member's content.

BadHeader#

silk
BadHeader

A header's checksum does not match its bytes, or a number field holds something other than an octal or base-256 number.

UnsafePath#

silk
UnsafePath

A member path is absolute, holds a backslash or a NUL, or has a .. segment.

Implementation TarError#

silk
impl TarError

Associated function describe#

silk
pub fn describe(error: TarError) -> string<'static>

Returns a short sentence that describes error, such as "the tar archive is truncated".

Implementation Debug for TarError#

silk
impl Debug for TarError

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant, such as Truncated.

TarKind#

silk
pub enum TarKind

What a tar member is.

File#

silk
File

A regular file. Its bytes are the member's content.

Directory#

silk
Directory

A directory. Its name has no trailing /, and it has no content.

silk
SymbolicLink

A symbolic link to link, which the reader does not check. A target longer than the 100-byte header field comes back cut to that field.

silk
HardLink

A hard link to the earlier member link, which the reader does not check. A target longer than the 100-byte header field comes back cut to that field.

Other#

silk
Other

A device, a FIFO, or a type this module does not know.

TarMember#

silk
pub struct TarMember<'a>

One member of a tar archive: its checked path, its kind, and its content.

Details#

The member's path is prefix/name, name alone when prefix is empty, or prefix alone when name is empty, as ustar writes a long directory path. A GNU long name or a pax path record comes back as name with an empty prefix. The slices borrow the archive given to TarReader.open.

Field prefix#

silk
pub prefix: &'a [u8]

The ustar prefix field, or empty.

Field name#

silk
pub name: &'a [u8]

The rest of the path, without the trailing / a directory may carry.

Field kind#

silk
pub kind: TarKind

Whether the member is a file, a directory, a link, or something else.

Field executable#

silk
pub executable: bool

Whether the member is a file whose mode has an execute permission bit.

silk
pub link: &'a [u8]

The target of a symbolic or hard link from the header's 100-byte link field, or empty. GNU long link names and pax linkpath records are not read.

Field content#

silk
pub content: &'a [u8]

The bytes of a regular file. It is empty for a directory or a link.

TarReader#

silk
pub struct TarReader<'a>

A position in a tar archive held in memory.

When to use#

Make one with TarReader.open and call TarReader.next until it returns none.

Details#

The reader does not allocate and borrows the archive. It stops at the first all-zero block or at the end of the bytes. After a failure, discard the reader.

Implementation TarReader<'a>#

silk
impl<'a> TarReader<'a>

Associated function open#

silk
pub fn open(archive: &'a [u8]) -> TarReader<'a>

Returns a reader at the start of archive.

Method next#

silk
pub fn next(self: &mut TarReader<'a>) -> Result<Option<TarMember<'a>>, TarError>

Returns the next member in archive order, or none at the end-of-archive blocks or the end of the bytes. GNU long-name and pax headers are read as part of the member they name.

Errors#

  • TarError with Truncated when the archive ends inside a header or a member, BadHeader when a checksum or number is invalid, and UnsafePath when the member's path is absolute, holds a backslash or a NUL, or has a .. segment.

TarWriter#

silk
pub struct TarWriter

A tar archive being written in memory, one member at a time.

When to use#

Make one with TarWriter.make, add members with TarWriter.file and TarWriter.directory in the order the archive should list them, and take the archive with TarWriter.finish.

Details#

The writer writes ustar headers with fixed metadata: modification time zero, owner and group zero with empty names, and mode 644 for a file, 755 for an executable file or a directory. The same members in the same order therefore always give the same bytes, on any host. A path longer than the 100-byte name field is split into the ustar prefix and name at a / when it fits, and is otherwise written in a pax path record. A file of 8 GiB or more records its size in base-256. TarReader reads every archive the writer makes.

Implementation TarWriter#

silk
impl TarWriter

Associated function make#

silk
pub fn make() -> TarWriter

Returns a writer with no members.

Method file#

silk
pub effect fn file(self: &mut Self, path: &[u8], content: &[u8], executable: bool) -> ()
    ! TarError | OutOfMemoryError
    ? &mut Allocator

Adds the regular file path holding content, with mode 755 when executable and 644 otherwise.

Errors#

  • TarError with UnsafePath when path is empty, absolute, ends in /, or holds a backslash, a NUL, or a .. segment. Nothing is added.

  • OutOfMemoryError when the archive cannot grow.

Method directory#

silk
pub effect fn directory(self: &mut Self, path: &[u8]) -> ()
    ! TarError | OutOfMemoryError
    ? &mut Allocator

Adds the directory path, with mode 755. Write it without a trailing /.

Errors#

  • TarError with UnsafePath when path is empty, absolute, ends in /, or holds a backslash, a NUL, or a .. segment. Nothing is added.

  • OutOfMemoryError when the archive cannot grow.

Method finish#

silk
pub effect fn finish(self: TarWriter) -> Bytes
    ! OutOfMemoryError
    ? &mut Allocator

Ends the archive with its two zero blocks and returns its bytes.

Errors#

  • OutOfMemoryError when the archive cannot grow.

Category: Data formats.

Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2, x86_64-pc-windows-msvc, wasm64-unknown-unknown.

View the source of this page