Task 637: .SB File Format
Task 637: .SB File Format
File Format Specifications for .SB
The .SB file format is used for Slax Bundles, which are essentially SquashFS compressed read-only filesystem images. SquashFS is a binary format optimized for compression, random access, and embedding in limited environments like live USB distributions (e.g., Slax Linux). The format starts with a fixed-size superblock (96 bytes) that describes the entire filesystem's metadata, compression settings, and locations of other sections (e.g., inode table, directory table). All integers are stored in little-endian byte order. The archive is padded to a multiple of 4KB or 1KB for compatibility.
- List of all the properties of this file format intrinsic to its file system:
- magic: u32, must be 0x73717368 ("hsqs" in ASCII).
- inode_count: u32, number of inodes in the filesystem.
- mod_time: u32, last modification time (seconds since Unix epoch, unsigned).
- block_size: u32, size of data blocks (power of 2, 4KB to 1MB).
- frag_count: u32, number of fragment table entries.
- compressor: u16, compressor ID (1=GZIP, 2=LZMA, 3=LZO, 4=XZ, 5=LZ4, 6=ZSTD).
- block_log: u16, log2 of block_size.
- flags: u16, bitfield for options (e.g., 0x0001=inodes uncompressed, 0x0400=compressor options present).
- id_count: u16, number of UID/GID entries.
- version_major: u16, major version (must be 4).
- version_minor: u16, minor version (must be 0).
- root_inode: u64, reference to root directory inode.
- bytes_used: u64, total bytes used by the filesystem.
- id_table: u64, offset to UID/GID lookup table.
- xattr_table: u64, offset to extended attributes table (0xFFFFFFFFFFFFFFFF if absent).
- inode_table: u64, offset to inode table.
- dir_table: u64, offset to directory table.
- frag_table: u64, offset to fragment table (0xFFFFFFFFFFFFFFFF if absent).
- export_table: u64, offset to export table (0xFFFFFFFFFFFFFFFF if absent).
If the flags indicate compressor options are present (flags & 0x0400), additional compressor-specific properties follow the superblock in an uncompressed metadata block (e.g., compression level for GZIP). Other sections (data blocks, metadata) follow based on offsets, but the core intrinsic properties are the superblock fields above.
- Two direct download links for files of format .SB:
- https://ftp.cvut.cz/slax/Slax-old/Slax-7.x/latest-version-unpacked/slax/01-core.sb
- https://ftp.cvut.cz/slax/Slax-old/Slax-7.x/latest-version-unpacked/slax/02-xorg.sb
- Ghost blog embedded HTML JavaScript for drag n drop .SB file to dump properties:
Drag and Drop .SB File to Dump Properties
- Python class for opening, decoding, reading, writing, and printing .SB properties:
import struct
import os
class SBFile:
def __init__(self, filepath):
self.filepath = filepath
self.properties = {}
self._read()
def _read(self):
with open(self.filepath, 'rb') as f:
data = f.read(96)
if len(data) < 96:
raise ValueError("File too small for .SB superblock")
(
self.properties['magic'],
self.properties['inode_count'],
self.properties['mod_time'],
self.properties['block_size'],
self.properties['frag_count'],
self.properties['compressor'],
self.properties['block_log'],
self.properties['flags'],
self.properties['id_count'],
self.properties['version_major'],
self.properties['version_minor'],
self.properties['root_inode'],
self.properties['bytes_used'],
self.properties['id_table'],
self.properties['xattr_table'],
self.properties['inode_table'],
self.properties['dir_table'],
self.properties['frag_table'],
self.properties['export_table']
) = struct.unpack('<IIIIIIHHHHIHQQQQQQQ', data) # Note: Q for u64 in little-endian
def print_properties(self):
for key, value in self.properties.items():
print(f"{key}: {value}")
def write(self, new_filepath=None):
if new_filepath is None:
new_filepath = self.filepath
with open(self.filepath, 'rb') as f_orig:
original_data = f_orig.read()
superblock = struct.pack('<IIIIIIHHHHIHQQQQQQQ',
self.properties['magic'],
self.properties['inode_count'],
self.properties['mod_time'],
self.properties['block_size'],
self.properties['frag_count'],
self.properties['compressor'],
self.properties['block_log'],
self.properties['flags'],
self.properties['id_count'],
self.properties['version_major'],
self.properties['version_minor'],
self.properties['root_inode'],
self.properties['bytes_used'],
self.properties['id_table'],
self.properties['xattr_table'],
self.properties['inode_table'],
self.properties['dir_table'],
self.properties['frag_table'],
self.properties['export_table'])
with open(new_filepath, 'wb') as f:
f.write(superblock + original_data[96:]) # Overwrite superblock, keep rest
# Example usage:
# sb = SBFile('example.sb')
# sb.print_properties()
# sb.properties['mod_time'] = 1234567890 # Modify a property
# sb.write()
- Java class for opening, decoding, reading, writing, and printing .SB properties:
import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.nio.file.*;
public class SBFile {
private String filepath;
private long magic, inodeCount, modTime, blockSize, fragCount, compressor, blockLog, flags, idCount, versionMajor, versionMinor, rootInode, bytesUsed, idTable, xattrTable, inodeTable, dirTable, fragTable, exportTable;
public SBFile(String filepath) throws IOException {
this.filepath = filepath;
read();
}
private void read() throws IOException {
try (RandomAccessFile raf = new RandomAccessFile(filepath, "r")) {
ByteBuffer buffer = ByteBuffer.allocate(96).order(ByteOrder.LITTLE_ENDIAN);
raf.getChannel().read(buffer);
buffer.flip();
magic = buffer.getInt() & 0xFFFFFFFFL;
inodeCount = buffer.getInt() & 0xFFFFFFFFL;
modTime = buffer.getInt() & 0xFFFFFFFFL;
blockSize = buffer.getInt() & 0xFFFFFFFFL;
fragCount = buffer.getInt() & 0xFFFFFFFFL;
compressor = buffer.getShort() & 0xFFFFL;
blockLog = buffer.getShort() & 0xFFFFL;
flags = buffer.getShort() & 0xFFFFL;
idCount = buffer.getShort() & 0xFFFFL;
versionMajor = buffer.getShort() & 0xFFFFL;
versionMinor = buffer.getShort() & 0xFFFFL;
rootInode = buffer.getLong();
bytesUsed = buffer.getLong();
idTable = buffer.getLong();
xattrTable = buffer.getLong();
inodeTable = buffer.getLong();
dirTable = buffer.getLong();
fragTable = buffer.getLong();
exportTable = buffer.getLong();
}
}
public void printProperties() {
System.out.println("magic: " + magic);
System.out.println("inode_count: " + inodeCount);
System.out.println("mod_time: " + modTime);
System.out.println("block_size: " + blockSize);
System.out.println("frag_count: " + fragCount);
System.out.println("compressor: " + compressor);
System.out.println("block_log: " + blockLog);
System.out.println("flags: " + flags);
System.out.println("id_count: " + idCount);
System.out.println("version_major: " + versionMajor);
System.out.println("version_minor: " + versionMinor);
System.out.println("root_inode: " + rootInode);
System.out.println("bytes_used: " + bytesUsed);
System.out.println("id_table: " + idTable);
System.out.println("xattr_table: " + xattrTable);
System.out.println("inode_table: " + inodeTable);
System.out.println("dir_table: " + dirTable);
System.out.println("frag_table: " + fragTable);
System.out.println("export_table: " + exportTable);
}
public void write(String newFilepath) throws IOException {
if (newFilepath == null) newFilepath = filepath;
ByteBuffer buffer = ByteBuffer.allocate(96).order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt((int) magic);
buffer.putInt((int) inodeCount);
buffer.putInt((int) modTime);
buffer.putInt((int) blockSize);
buffer.putInt((int) fragCount);
buffer.putShort((short) compressor);
buffer.putShort((short) blockLog);
buffer.putShort((short) flags);
buffer.putShort((short) idCount);
buffer.putShort((short) versionMajor);
buffer.putShort((short) versionMinor);
buffer.putLong(rootInode);
buffer.putLong(bytesUsed);
buffer.putLong(idTable);
buffer.putLong(xattrTable);
buffer.putLong(inodeTable);
buffer.putLong(dirTable);
buffer.putLong(fragTable);
buffer.putLong(exportTable);
buffer.flip();
Files.copy(Paths.get(filepath), Paths.get(newFilepath), StandardCopyOption.REPLACE_EXISTING);
try (FileChannel channel = new RandomAccessFile(newFilepath, "rw").getChannel()) {
channel.write(buffer, 0);
}
}
// Getters and setters for properties can be added as needed
// e.g., public void setModTime(long modTime) { this.modTime = modTime; }
// Example usage:
// public static void main(String[] args) throws IOException {
// SBFile sb = new SBFile("example.sb");
// sb.printProperties();
// sb.setModTime(1234567890L);
// sb.write(null);
// }
}
- JavaScript class for opening, decoding, reading, writing, and printing .SB properties (Node.js compatible):
const fs = require('fs');
class SBFile {
constructor(filepath) {
this.filepath = filepath;
this.properties = {};
this._read();
}
_read() {
const data = fs.readFileSync(this.filepath);
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
this.properties.magic = view.getUint32(0, true);
this.properties.inode_count = view.getUint32(4, true);
this.properties.mod_time = view.getUint32(8, true);
this.properties.block_size = view.getUint32(12, true);
this.properties.frag_count = view.getUint32(16, true);
this.properties.compressor = view.getUint16(20, true);
this.properties.block_log = view.getUint16(22, true);
this.properties.flags = view.getUint16(24, true);
this.properties.id_count = view.getUint16(26, true);
this.properties.version_major = view.getUint16(28, true);
this.properties.version_minor = view.getUint16(30, true);
this.properties.root_inode = view.getBigUint64(32, true);
this.properties.bytes_used = view.getBigUint64(40, true);
this.properties.id_table = view.getBigUint64(48, true);
this.properties.xattr_table = view.getBigUint64(56, true);
this.properties.inode_table = view.getBigUint64(64, true);
this.properties.dir_table = view.getBigUint64(72, true);
this.properties.frag_table = view.getBigUint64(80, true);
this.properties.export_table = view.getBigUint64(88, true);
}
printProperties() {
for (const [key, value] of Object.entries(this.properties)) {
console.log(`${key}: ${value}`);
}
}
write(newFilepath = this.filepath) {
const originalData = fs.readFileSync(this.filepath);
const buffer = new ArrayBuffer(96);
const view = new DataView(buffer);
view.setUint32(0, this.properties.magic, true);
view.setUint32(4, this.properties.inode_count, true);
view.setUint32(8, this.properties.mod_time, true);
view.setUint32(12, this.properties.block_size, true);
view.setUint32(16, this.properties.frag_count, true);
view.setUint16(20, this.properties.compressor, true);
view.setUint16(22, this.properties.block_log, true);
view.setUint16(24, this.properties.flags, true);
view.setUint16(26, this.properties.id_count, true);
view.setUint16(28, this.properties.version_major, true);
view.setUint16(30, this.properties.version_minor, true);
view.setBigUint64(32, this.properties.root_inode, true);
view.setBigUint64(40, this.properties.bytes_used, true);
view.setBigUint64(48, this.properties.id_table, true);
view.setBigUint64(56, this.properties.xattr_table, true);
view.setBigUint64(64, this.properties.inode_table, true);
view.setBigUint64(72, this.properties.dir_table, true);
view.setBigUint64(80, this.properties.frag_table, true);
view.setBigUint64(88, this.properties.export_table, true);
const newData = Buffer.concat([Buffer.from(buffer), originalData.slice(96)]);
fs.writeFileSync(newFilepath, newData);
}
}
// Example usage:
// const sb = new SBFile('example.sb');
// sb.printProperties();
// sb.properties.mod_time = 1234567890;
// sb.write();
- C "class" (using struct and functions) for opening, decoding, reading, writing, and printing .SB properties:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <endian.h> // For leXXtoh if needed, but assuming little-endian host for simplicity
typedef struct {
uint32_t magic;
uint32_t inode_count;
uint32_t mod_time;
uint32_t block_size;
uint32_t frag_count;
uint16_t compressor;
uint16_t block_log;
uint16_t flags;
uint16_t id_count;
uint16_t version_major;
uint16_t version_minor;
uint64_t root_inode;
uint64_t bytes_used;
uint64_t id_table;
uint64_t xattr_table;
uint64_t inode_table;
uint64_t dir_table;
uint64_t frag_table;
uint64_t export_table;
char *filepath;
} SBFile;
SBFile* sbfile_open(const char *filepath) {
SBFile *sb = malloc(sizeof(SBFile));
if (!sb) return NULL;
sb->filepath = strdup(filepath);
FILE *f = fopen(filepath, "rb");
if (!f) {
free(sb);
return NULL;
}
uint8_t data[96];
if (fread(data, 1, 96, f) != 96) {
fclose(f);
free(sb);
return NULL;
}
fclose(f);
memcpy(&sb->magic, data + 0, 4);
memcpy(&sb->inode_count, data + 4, 4);
memcpy(&sb->mod_time, data + 8, 4);
memcpy(&sb->block_size, data + 12, 4);
memcpy(&sb->frag_count, data + 16, 4);
memcpy(&sb->compressor, data + 20, 2);
memcpy(&sb->block_log, data + 22, 2);
memcpy(&sb->flags, data + 24, 2);
memcpy(&sb->id_count, data + 26, 2);
memcpy(&sb->version_major, data + 28, 2);
memcpy(&sb->version_minor, data + 30, 2);
memcpy(&sb->root_inode, data + 32, 8);
memcpy(&sb->bytes_used, data + 40, 8);
memcpy(&sb->id_table, data + 48, 8);
memcpy(&sb->xattr_table, data + 56, 8);
memcpy(&sb->inode_table, data + 64, 8);
memcpy(&sb->dir_table, data + 72, 8);
memcpy(&sb->frag_table, data + 80, 8);
memcpy(&sb->export_table, data + 88, 8);
// Assuming host is little-endian; otherwise, use le32toh, etc.
return sb;
}
void sbfile_print_properties(SBFile *sb) {
if (!sb) return;
printf("magic: %u\n", sb->magic);
printf("inode_count: %u\n", sb->inode_count);
printf("mod_time: %u\n", sb->mod_time);
printf("block_size: %u\n", sb->block_size);
printf("frag_count: %u\n", sb->frag_count);
printf("compressor: %hu\n", sb->compressor);
printf("block_log: %hu\n", sb->block_log);
printf("flags: %hu\n", sb->flags);
printf("id_count: %hu\n", sb->id_count);
printf("version_major: %hu\n", sb->version_major);
printf("version_minor: %hu\n", sb->version_minor);
printf("root_inode: %llu\n", (unsigned long long)sb->root_inode);
printf("bytes_used: %llu\n", (unsigned long long)sb->bytes_used);
printf("id_table: %llu\n", (unsigned long long)sb->id_table);
printf("xattr_table: %llu\n", (unsigned long long)sb->xattr_table);
printf("inode_table: %llu\n", (unsigned long long)sb->inode_table);
printf("dir_table: %llu\n", (unsigned long long)sb->dir_table);
printf("frag_table: %llu\n", (unsigned long long)sb->frag_table);
printf("export_table: %llu\n", (unsigned long long)sb->export_table);
}
void sbfile_write(SBFile *sb, const char *new_filepath) {
if (!sb) return;
if (!new_filepath) new_filepath = sb->filepath;
FILE *f_orig = fopen(sb->filepath, "rb");
if (!f_orig) return;
fseek(f_orig, 0, SEEK_END);
long size = ftell(f_orig);
fseek(f_orig, 0, SEEK_SET);
uint8_t *original_data = malloc(size);
fread(original_data, 1, size, f_orig);
fclose(f_orig);
uint8_t superblock[96] = {0};
memcpy(superblock + 0, &sb->magic, 4);
memcpy(superblock + 4, &sb->inode_count, 4);
memcpy(superblock + 8, &sb->mod_time, 4);
memcpy(superblock + 12, &sb->block_size, 4);
memcpy(superblock + 16, &sb->frag_count, 4);
memcpy(superblock + 20, &sb->compressor, 2);
memcpy(superblock + 22, &sb->block_log, 2);
memcpy(superblock + 24, &sb->flags, 2);
memcpy(superblock + 26, &sb->id_count, 2);
memcpy(superblock + 28, &sb->version_major, 2);
memcpy(superblock + 30, &sb->version_minor, 2);
memcpy(superblock + 32, &sb->root_inode, 8);
memcpy(superblock + 40, &sb->bytes_used, 8);
memcpy(superblock + 48, &sb->id_table, 8);
memcpy(superblock + 56, &sb->xattr_table, 8);
memcpy(superblock + 64, &sb->inode_table, 8);
memcpy(superblock + 72, &sb->dir_table, 8);
memcpy(superblock + 80, &sb->frag_table, 8);
memcpy(superblock + 88, &sb->export_table, 8);
FILE *f = fopen(new_filepath, "wb");
if (!f) {
free(original_data);
return;
}
fwrite(superblock, 1, 96, f);
fwrite(original_data + 96, 1, size - 96, f);
fclose(f);
free(original_data);
}
void sbfile_close(SBFile *sb) {
if (sb) {
free(sb->filepath);
free(sb);
}
}
// Example usage:
// int main() {
// SBFile *sb = sbfile_open("example.sb");
// if (sb) {
// sbfile_print_properties(sb);
// sb->mod_time = 1234567890;
// sbfile_write(sb, NULL);
// sbfile_close(sb);
// }
// return 0;
// }