Task 009: .A File Format
Task 009: .A File Format
File Format Specifications for the .A File Format
The .A file format refers to the ar archive format, commonly used for static libraries on Unix-like systems. It is a binary archive format that combines multiple files into a single file, typically with the extension .a (case-insensitive, though lowercase is conventional). The format has not been formally standardized, leading to variants such as common, BSD, GNU/System V, and others. The specifications below focus on the common variant, as it forms the basis for most implementations and is widely used in static libraries and Debian packages (.deb files, which are ar archives).
1. List of All Properties of This File Format Intrinsic to Its File System
The intrinsic properties of the ar file format (common variant) are as follows:
- Magic Signature: The archive begins with the 8-byte ASCII string
!<arch>\n(hex: 21 3C 61 72 63 68 3E 0A), serving as the file identifier. - Header Size per Member: Each archived file (member) is preceded by a fixed 60-byte header.
- Header Fields:
- File Name (Identifier): 16 bytes, ASCII, right-padded with spaces if shorter than 16 characters; trailing spaces are ignored.
- Modification Timestamp: 12 bytes, decimal ASCII representation of seconds since the Unix epoch (January 1, 1970).
- User ID (UID): 6 bytes, decimal ASCII representation.
- Group ID (GID): 6 bytes, decimal ASCII representation.
- File Mode: 8 bytes, octal ASCII representation of UNIX-style permissions (e.g., 100644 for -rw-r--r--).
- File Size: 10 bytes, decimal ASCII representation of the member's data size in bytes (excluding padding).
- Terminator: 2 bytes, fixed as backtick followed by newline (
\`` followed by\n`, hex: 60 0A). - Data Alignment and Padding: Member data follows the header immediately; if the data size is odd, it is padded with a single newline (
\n) to ensure even-byte alignment. The padding is not included in the size field. - Encoding: All header fields are encoded in ASCII; numeric fields are space-padded on the left if necessary.
- Structure: The archive is a sequence of the magic signature followed by zero or more member header-data pairs; there is no explicit end marker— the file ends at EOF.
- Textual Compatibility: Headers use printable ASCII and line feeds, allowing an archive of text files to appear as a text file itself.
- Variants Considerations: While the common variant limits file names to 16 characters, variants like BSD and GNU support longer names through extensions (e.g., #1/length prefix in BSD or // index file in GNU). Symbol tables for linking (e.g., / for GNU) may be included as special members.
| Property | Description | Size (Bytes) | Format |
|---|---|---|---|
| Magic Signature | File identifier | 8 | ASCII !<arch>\n |
| File Name | Member identifier | 16 | ASCII, space-padded |
| Modification Timestamp | Unix epoch seconds | 12 | Decimal ASCII, space-padded |
| User ID | Owner UID | 6 | Decimal ASCII, space-padded |
| Group ID | Owner GID | 6 | Decimal ASCII, space-padded |
| File Mode | Permissions | 8 | Octal ASCII, space-padded |
| File Size | Data length | 10 | Decimal ASCII, space-padded |
| Terminator | Header end | 2 | Binary \ \n` |
2. Two Direct Download Links for Files of Format .A
Two direct download links for example .a files (static libraries in ar format) are provided below:
- https://raw.githubusercontent.com/eerimoq/arm-toolchain-windows/master/arm-none-eabi-gcc/4.8.3-2014q1/arm-none-eabi/lib/armv7-ar/thumb/softfp/libm.a
- https://raw.githubusercontent.com/LilyGO/TTGO_LED_Matrix/master/hardware/tools/avr/avr/lib/avrtiny/libm.a
These are prebuilt static math libraries (libm.a) from open-source repositories, suitable for testing the format.
3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .A File Dump
The following is a standalone HTML page with embedded JavaScript that allows users to drag and drop a .A file. Upon dropping, it parses the file using the common ar format specifications and dumps the properties (magic signature and per-member headers) to the screen. It uses the FileReader API for client-side processing.
4. Python Class for .A File Handling
The following Python class can open, decode, read, write, and print the properties of a .A file based on the common variant. It supports reading an existing file, printing properties to console, and writing a new archive from provided members (files with metadata).
import struct
import time
import os
class AFileHandler:
MAGIC = b'!<arch>\n'
def __init__(self, filepath=None):
self.filepath = filepath
self.members = [] # List of dicts: {'name': str, 'data': bytes, 'timestamp': int, 'uid': int, 'gid': int, 'mode': int}
def read(self):
if not self.filepath:
raise ValueError("Filepath not provided.")
with open(self.filepath, 'rb') as f:
data = f.read()
offset = 0
magic = data[offset:offset+8]
if magic != self.MAGIC:
raise ValueError("Invalid .A file signature.")
offset += 8
while offset < len(data):
if offset + 60 > len(data):
break
header = data[offset:offset+60]
name = header[0:16].decode('ascii').rstrip()
timestamp = int(header[16:28].decode('ascii').strip())
uid = int(header[28:34].decode('ascii').strip())
gid = int(header[34:40].decode('ascii').strip())
mode = int(header[40:48].decode('ascii').strip(), 8)
size = int(header[48:58].decode('ascii').strip())
terminator = header[58:60]
if terminator != b'`\n':
raise ValueError("Invalid header terminator.")
member_data = data[offset+60:offset+60+size]
self.members.append({
'name': name,
'data': member_data,
'timestamp': timestamp,
'uid': uid,
'gid': gid,
'mode': mode
})
offset += 60 + size
if size % 2 != 0:
offset += 1 # Skip padding
def print_properties(self):
print("Magic Signature: !<arch>\\n")
for member in self.members:
print(f"Member: {member['name']}")
print(f" Timestamp: {member['timestamp']} ({time.ctime(member['timestamp'])})")
print(f" UID: {member['uid']}")
print(f" GID: {member['gid']}")
print(f" Mode: {oct(member['mode'])}")
print(f" Size: {len(member['data'])} bytes")
print()
def write(self, output_path):
with open(output_path, 'wb') as f:
f.write(self.MAGIC)
for member in self.members:
name = member['name'].ljust(16).encode('ascii')
timestamp = str(member['timestamp']).rjust(12).encode('ascii')
uid = str(member['uid']).rjust(6).encode('ascii')
gid = str(member['gid']).rjust(6).encode('ascii')
mode = oct(member['mode'])[2:].rjust(8).encode('ascii')
size = str(len(member['data'])).rjust(10).encode('ascii')
terminator = b'`\n'
header = name + timestamp + uid + gid + mode + size + terminator
f.write(header)
f.write(member['data'])
if len(member['data']) % 2 != 0:
f.write(b'\n')
# Example usage:
# handler = AFileHandler('example.a')
# handler.read()
# handler.print_properties()
# handler.members.append({'name': 'newfile.txt', 'data': b'content', 'timestamp': int(time.time()), 'uid': os.getuid(), 'gid': os.getgid(), 'mode': 0o644})
# handler.write('new.a')
5. Java Class for .A File Handling
The following Java class performs similar operations: opening, decoding, reading, writing, and printing properties to the console.
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
public class AFileHandler {
private static final byte[] MAGIC = "!<arch>\n".getBytes();
private String filepath;
private List<Member> members = new ArrayList<>();
static class Member {
String name;
byte[] data;
long timestamp;
int uid;
int gid;
int mode;
}
public AFileHandler(String filepath) {
this.filepath = filepath;
}
public void read() throws IOException {
byte[] data = Files.readAllBytes(Paths.get(filepath));
int offset = 0;
byte[] magic = new byte[8];
System.arraycopy(data, offset, magic, 0, 8);
if (!new String(magic).equals(new String(MAGIC))) {
throw new IOException("Invalid .A file signature.");
}
offset += 8;
while (offset < data.length) {
if (offset + 60 > data.length) break;
byte[] header = new byte[60];
System.arraycopy(data, offset, header, 0, 60);
String name = new String(header, 0, 16).trim();
long timestamp = Long.parseLong(new String(header, 16, 12).trim());
int uid = Integer.parseInt(new String(header, 28, 6).trim());
int gid = Integer.parseInt(new String(header, 34, 6).trim());
int mode = Integer.parseInt(new String(header, 40, 8).trim(), 8);
int size = Integer.parseInt(new String(header, 48, 10).trim());
byte[] terminator = new byte[2];
System.arraycopy(header, 58, terminator, 0, 2);
if (terminator[0] != 0x60 || terminator[1] != 0x0A) {
throw new IOException("Invalid header terminator.");
}
byte[] memberData = new byte[size];
System.arraycopy(data, offset + 60, memberData, 0, size);
Member member = new Member();
member.name = name;
member.data = memberData;
member.timestamp = timestamp;
member.uid = uid;
member.gid = gid;
member.mode = mode;
members.add(member);
offset += 60 + size;
if (size % 2 != 0) offset += 1;
}
}
public void printProperties() {
System.out.println("Magic Signature: !<arch>\\n");
for (Member member : members) {
System.out.println("Member: " + member.name);
System.out.println(" Timestamp: " + member.timestamp + " (" + new java.util.Date(member.timestamp * 1000) + ")");
System.out.println(" UID: " + member.uid);
System.out.println(" GID: " + member.gid);
System.out.println(" Mode: " + Integer.toOctalString(member.mode));
System.out.println(" Size: " + member.data.length + " bytes");
System.out.println();
}
}
public void write(String outputPath) throws IOException {
try (FileOutputStream fos = new FileOutputStream(outputPath)) {
fos.write(MAGIC);
for (Member member : members) {
String name = String.format("%-16s", member.name);
String timestamp = String.format("%12d", member.timestamp);
String uid = String.format("%6d", member.uid);
String gid = String.format("%6d", member.gid);
String mode = String.format("%8o", member.mode);
String size = String.format("%10d", member.data.length);
byte[] header = (name + timestamp + uid + gid + mode + size + "`\n").getBytes();
fos.write(header);
fos.write(member.data);
if (member.data.length % 2 != 0) {
fos.write('\n');
}
}
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// AFileHandler handler = new AFileHandler("example.a");
// handler.read();
// handler.printProperties();
// Member newMember = new Member();
// newMember.name = "newfile.txt";
// newMember.data = "content".getBytes();
// newMember.timestamp = System.currentTimeMillis() / 1000;
// newMember.uid = 501;
// newMember.gid = 20;
// newMember.mode = 0644;
// handler.members.add(newMember);
// handler.write("new.a");
// }
}
6. JavaScript Class for .A File Handling
The following JavaScript class can be used in a Node.js environment (requires 'fs' module) to open, decode, read, write, and print properties to the console.
const fs = require('fs');
class AFileHandler {
static MAGIC = Buffer.from('!<arch>\n');
constructor(filepath = null) {
this.filepath = filepath;
this.members = []; // Array of objects: {name, data, timestamp, uid, gid, mode}
}
read() {
if (!this.filepath) throw new Error('Filepath not provided.');
const data = fs.readFileSync(this.filepath);
let offset = 0;
const magic = data.slice(offset, offset + 8);
if (!magic.equals(AFileHandler.MAGIC)) throw new Error('Invalid .A file signature.');
offset += 8;
while (offset < data.length) {
if (offset + 60 > data.length) break;
const header = data.slice(offset, offset + 60);
const name = header.slice(0, 16).toString('ascii').trim();
const timestamp = parseInt(header.slice(16, 28).toString('ascii').trim(), 10);
const uid = parseInt(header.slice(28, 34).toString('ascii').trim(), 10);
const gid = parseInt(header.slice(34, 40).toString('ascii').trim(), 10);
const mode = parseInt(header.slice(40, 48).toString('ascii').trim(), 8);
const size = parseInt(header.slice(48, 58).toString('ascii').trim(), 10);
const terminator = header.slice(58, 60);
if (terminator[0] !== 0x60 || terminator[1] !== 0x0A) throw new Error('Invalid header terminator.');
const memberData = data.slice(offset + 60, offset + 60 + size);
this.members.push({name, data: memberData, timestamp, uid, gid, mode});
offset += 60 + size;
if (size % 2 !== 0) offset += 1;
}
}
printProperties() {
console.log('Magic Signature: !<arch>\\n');
this.members.forEach(member => {
console.log(`Member: ${member.name}`);
console.log(` Timestamp: ${member.timestamp} (${new Date(member.timestamp * 1000).toString()})`);
console.log(` UID: ${member.uid}`);
console.log(` GID: ${member.gid}`);
console.log(` Mode: 0${member.mode.toString(8)}`);
console.log(` Size: ${member.data.length} bytes`);
console.log('');
});
}
write(outputPath) {
let buffer = Buffer.from(AFileHandler.MAGIC);
this.members.forEach(member => {
const name = member.name.padEnd(16);
const timestamp = member.timestamp.toString().padStart(12);
const uid = member.uid.toString().padStart(6);
const gid = member.gid.toString().padStart(6);
const mode = member.mode.toString(8).padStart(8);
const size = member.data.length.toString().padStart(10);
const header = Buffer.from(`${name}${timestamp}${uid}${gid}${mode}${size}\`\n`);
buffer = Buffer.concat([buffer, header, member.data]);
if (member.data.length % 2 !== 0) {
buffer = Buffer.concat([buffer, Buffer.from('\n')]);
}
});
fs.writeFileSync(outputPath, buffer);
}
}
// Example usage:
// const handler = new AFileHandler('example.a');
// handler.read();
// handler.printProperties();
// handler.members.push({name: 'newfile.txt', data: Buffer.from('content'), timestamp: Math.floor(Date.now() / 1000), uid: 501, gid: 20, mode: 0o644});
// handler.write('new.a');
7. C Class for .A File Handling
In C, classes are not native, so the following uses a struct with associated functions to achieve similar functionality: opening, decoding, reading, writing, and printing properties to the console.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define MAGIC "!<arch>\n"
#define HEADER_SIZE 60
typedef struct {
char *name;
char *data;
size_t data_size;
long timestamp;
int uid;
int gid;
mode_t mode;
} Member;
typedef struct {
char *filepath;
Member *members;
size_t member_count;
} AFileHandler;
void init_a_file_handler(AFileHandler *handler, const char *filepath) {
handler->filepath = strdup(filepath);
handler->members = NULL;
handler->member_count = 0;
}
void free_a_file_handler(AFileHandler *handler) {
free(handler->filepath);
for (size_t i = 0; i < handler->member_count; i++) {
free(handler->members[i].name);
free(handler->members[i].data);
}
free(handler->members);
}
int read_a_file(AFileHandler *handler) {
FILE *f = fopen(handler->filepath, "rb");
if (!f) return -1;
fseek(f, 0, SEEK_END);
long file_size = ftell(f);
fseek(f, 0, SEEK_SET);
char *data = malloc(file_size);
fread(data, 1, file_size, f);
fclose(f);
long offset = 0;
if (strncmp(data + offset, MAGIC, 8) != 0) {
free(data);
return -1;
}
offset += 8;
while (offset < file_size) {
if (offset + HEADER_SIZE > file_size) break;
char header[HEADER_SIZE];
memcpy(header, data + offset, HEADER_SIZE);
char name_str[17] = {0};
strncpy(name_str, header, 16);
char *name = strdup(strtok(name_str, " "));
long timestamp = atol(strndup(header + 16, 12));
int uid = atoi(strndup(header + 28, 6));
int gid = atoi(strndup(header + 34, 6));
mode_t mode = strtol(strndup(header + 40, 8), NULL, 8);
size_t size = atol(strndup(header + 48, 10));
if (header[58] != '`' || header[59] != '\n') {
free(name);
free(data);
return -1;
}
char *member_data = malloc(size);
memcpy(member_data, data + offset + HEADER_SIZE, size);
handler->members = realloc(handler->members, sizeof(Member) * (handler->member_count + 1));
handler->members[handler->member_count].name = name;
handler->members[handler->member_count].data = member_data;
handler->members[handler->member_count].data_size = size;
handler->members[handler->member_count].timestamp = timestamp;
handler->members[handler->member_count].uid = uid;
handler->members[handler->member_count].gid = gid;
handler->members[handler->member_count].mode = mode;
handler->member_count++;
offset += HEADER_SIZE + size;
if (size % 2 != 0) offset += 1;
}
free(data);
return 0;
}
void print_properties(const AFileHandler *handler) {
printf("Magic Signature: !<arch>\\n\n");
for (size_t i = 0; i < handler->member_count; i++) {
Member m = handler->members[i];
struct tm *tm = localtime(&m.timestamp);
char time_buf[32];
strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm);
printf("Member: %s\n", m.name);
printf(" Timestamp: %ld (%s)\n", m.timestamp, time_buf);
printf(" UID: %d\n", m.uid);
printf(" GID: %d\n", m.gid);
printf(" Mode: %o\n", m.mode);
printf(" Size: %zu bytes\n\n", m.data_size);
}
}
int write_a_file(const AFileHandler *handler, const char *output_path) {
FILE *f = fopen(output_path, "wb");
if (!f) return -1;
fwrite(MAGIC, 1, 8, f);
for (size_t i = 0; i < handler->member_count; i++) {
Member m = handler->members[i];
char header[HEADER_SIZE];
snprintf(header, sizeof(header), "%-16s%12ld%6d%6d%8o%10zu`\n", m.name, m.timestamp, m.uid, m.gid, m.mode, m.data_size);
fwrite(header, 1, HEADER_SIZE, f);
fwrite(m.data, 1, m.data_size, f);
if (m.data_size % 2 != 0) {
fputc('\n', f);
}
}
fclose(f);
return 0;
}
// Example usage:
// int main() {
// AFileHandler handler;
// init_a_file_handler(&handler, "example.a");
// if (read_a_file(&handler) == 0) {
// print_properties(&handler);
// }
// // Add a new member
// Member new_member;
// new_member.name = strdup("newfile.txt");
// new_member.data = strdup("content");
// new_member.data_size = strlen(new_member.data);
// new_member.timestamp = time(NULL);
// new_member.uid = getuid();
// new_member.gid = getgid();
// new_member.mode = 0644;
// handler.members = realloc(handler.members, sizeof(Member) * (handler.member_count + 1));
// handler.members[handler.member_count] = new_member;
// handler.member_count++;
// write_a_file(&handler, "new.a");
// free_a_file_handler(&handler);
// return 0;
// }