power_play/src/tar/tar.c
2025-12-03 20:41:03 -06:00

135 lines
4.2 KiB
C

Readonly TAR_Entry TAR_nil_entry = ZI;
u64 TAR_U64FromOctString(String str)
{
u64 n = 0;
for (u64 i = 0; i < str.len; ++i)
{
n *= 8;
n += (u64)(str.text[i]) - '0';
}
return n;
}
/* `prefix` will be prepended to all file names in the archive
*
* NOTE: The resulting archive merely points into the supplied tar data, no
* copying is done. Accessing the archive assumes that the data string is still valid.
*/
TAR_Archive TAR_ArchiveFromString(Arena *arena, String data, String prefix)
{
TAR_Archive archive = ZI;
BB_Buff bb = BitbuffFromString(data);
BB_Reader br = BB_ReaderFromBuffNoDebug(&bb);
u64 num_files = 0;
while (BB_NumBytesRemaining(&br) > 1024)
{
TAR_Header header = ZI;
BB_ReadBytes(&br, StringFromStruct(&header));
if (!MatchString(StringFromArray(header.ustar_indicator), Lit("ustar\0")))
{
/* Invalid header */
Assert(0);
continue;
}
if (header.file_name_prefix[0] != 0)
{
/* Header file name prefix not supported */
Assert(0);
continue;
}
String file_size_oct_str = { .len = 11, .text = header.file_size };
u64 file_size = TAR_U64FromOctString(file_size_oct_str);
u8 *file_data_ptr = BB_ReadBytesRaw(&br, file_size);
if (!file_data_ptr)
{
file_size = 0;
}
String file_data = STRING(file_size, file_data_ptr);
/* Skip sector padding */
u64 remaining = (512 - (file_size % 512)) % 512;
BB_SeekBytes(&br, remaining);
b32 is_dir = header.file_type == TAR_FileKind_Directory;
if (!is_dir && header.file_type != TAR_FileKind_File)
{
/* Unsupported type */
Assert(header.file_type == TAR_FileKind_PaxHeaderX ||
header.file_type == TAR_FileKind_PaxHeaderG);
continue;
}
String file_name_cstr = StringFromCstrNoLimit((char *)header.file_name);
if (file_name_cstr.len >= 2)
{
/* Chop off './' prefix */
file_name_cstr.len -= 2;
file_name_cstr.text += 2;
}
String file_name = CatString(arena, prefix, file_name_cstr);
TAR_Entry *entry = PushStruct(arena, TAR_Entry);
entry->valid = 1;
entry->is_dir = is_dir;
entry->file_name = file_name;
entry->data = file_data;
entry->next = archive.head;
archive.head = entry;
++num_files;
}
/* Build lookup table */
archive.lookup = InitDict(arena, (u64)((f64)num_files * TAR_ArchiveLookupTableCapacityFactor));
for (TAR_Entry *entry = archive.head; entry; entry = entry->next)
{
u64 hash = HashFnv64(Fnv64Basis, entry->file_name);
SetDictValue(arena, archive.lookup, hash, (u64)entry);
}
/* Build hierarchy */
/* NOTE: This is a separate pass because tar entry order is not guaranteed
* (IE file entries may be encountered before their parent directory entry) */
for (TAR_Entry *entry = archive.head; entry; entry = entry->next)
{
/* Enter into hierarchy */
if (!entry->is_dir)
{
/* Find parent entry */
TAR_Entry *parent_entry = 0;
for (String parent_dir_name = entry->file_name; parent_dir_name.len > 0; --parent_dir_name.len)
{
if (parent_dir_name.text[parent_dir_name.len - 1] == '/')
{
u64 hash = HashFnv64(Fnv64Basis, parent_dir_name);
parent_entry = (TAR_Entry *)DictValueFromHash(archive.lookup, hash);
break;
}
}
/* Insert child into parent's list */
if (parent_entry)
{
entry->next_child = parent_entry->next_child;
parent_entry->next_child = entry;
}
}
}
return archive;
}
Readonly Global TAR_Entry g_nil_tar_entry = ZI;
TAR_Entry *TAR_EntryFromName(TAR_Archive *archive, String name)
{
u64 hash = HashFnv64(Fnv64Basis, name);
TAR_Entry *lookup = (TAR_Entry *)DictValueFromHash(archive->lookup, hash);
return lookup ? lookup : &g_nil_tar_entry;
}