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; }