all data is big-endian on disk. arena layout: ArenaPart (first at offset PartBlank = 256kB in the disk file) magic[4] 0xA9E4A5E7 version[4] 3 blockSize[4] arenaBase[4] offset of first ArenaHead structure in the disk file the ArenaMap starts at the first block at offset >= PartBlank+512 bytes. it is a sequence of text lines /* * amap: n '\n' amapelem * n * n: u32int * amapelem: name '\t' astart '\t' asize '\n' * astart, asize: u64int */ the astart and astop are byte offsets in the disk file. they are the offsets to the ArenaHead and the end of the Arena block. ArenaHead [base points here in the C code] size bytes Clumps ClumpInfo blocks Arena Arena magic[4] 0xF2A14EAD version[4] 4 name[64] clumps[4] cclumps[4] ctime[4] wtime[4] used[8] uncsize[8] sealed[1] optional score[20] once sealed, the sha1 hash of every block from the ArenaHead to the Arena is checksummed, as though the final score in Arena were the zeroScore. strangely, the tail of the Arena block (the last one) is not included in the checksum (i.e., the unused data after the score). clumpMax = blocksize/ClumpInfoSize = blocksize/25 dirsize = ((clumps/clumpMax)+1) * blocksize want used+dirsize <= size want cclumps <= clumps want uncsize+clumps*ClumpSize+blocksize < used want ctime <= wtime clump info is stored packed into blocks in order. clump info moves forward through a block but the blocks themselves move backwards. so if cm=clumpMax and there are two blocks worth of clumpinfo, the blocks look like; [cm..2*cm-1] [0..cm-1] [Arena] with the blocks pushed right up against the Arena trailer. ArenaHead magic[4] 0xD15C4EAD version[4] = Arena.version name[64] blockSize[4] size[8] Clump magic[4] 0xD15CB10C (0 for an unused clump) type[1] size[2] uncsize[2] score[20] encoding[1] raw=1, compress=2 creator[4] time[4] ClumpInfo type[1] size[2] uncsize[2] score[20] the arenas are mapped into a single address space corresponding to the index that brings them together. if each arena has 100M bytes excluding the headers and there are 4 arenas, then there's 400M of index address space between them. index address space starts at 1M instead of 0, so the index addresses assigned to the first arena are 1M up to 101M, then 101M to 201M, etc. of course, the assignment of addresses has nothing to do with the index, but that's what they're called. the index is split into index sections, which are put on different disks to get parallelism of disk heads. each index section holds some number of hash buckets, each in its own disk block. collectively the index sections hold ix->buckets between them. the top 32-bits of the score is used to assign scores to buckets. div = ceil(2³² / ix->buckets) is the amount of 32-bit score space per bucket. to look up a block, take the top 32 bits of score and divide by div to get the bucket number. then look through the index section headers to figure out which index section has that bucket. then load that block from the index section. it's an IBucket. the IBucket has ib.n IEntry structures in it, sorted by score and then by type. do the lookup and get an IEntry. the ia.addr will be a logical address that you then use to get the ISect magic[4] 0xD15C5EC7 version[4] name[64] index[64] blockSize[4] blockBase[4] address in partition where bucket blocks start blocks[4] start[4] stop[4] stop - start <= blocks, but not necessarily == IEntry score[20] wtime[4] train[2] ia.addr[8] index address (see note above) ia.size[2] size of uncompressed block data ia.type[1] ia.blocks[1] number of blocks of clump on disk IBucket n[2] next[4] not sure; either 0 or inside [start,stop) for the ISect data[n*IEntrySize] final piece: all the disk partitions start with PartBlank=256kB of unused disk (presumably to avoid problems with boot sectors and layout tables and the like). actually the last 8k of the 256k (that is, at offset 248kB) can hold a venti config file to help during bootstrap of the venti file server.