Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env node

import { open } from 'node:fs/promises';
import { DataSource } from './src/data-source.js';
import { ElfFile } from './src/elf.js';

const filePath = process.argv[2];
if (!filePath) {
console.error('Usage: elfy <path-to-elf-file>');
process.exit(1);
}

const fileHandle = await open(filePath, 'r');
try {
Comment on lines +13 to +14
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open(filePath, 'r') is outside any error handling, so a missing/unreadable file will throw an unhandled rejection and print a stack trace. Catch the error and print a concise message (e.g., include err.message) with a non-zero exit code.

Copilot uses AI. Check for mistakes.
const dataSource: DataSource = {
async read(offset: number, length: number): Promise<Uint8Array> {
const buffer = new Uint8Array(length);
const { bytesRead } = await fileHandle.read(buffer, 0, length, offset);
return buffer.slice(0, bytesRead);
},
};

const elf = new ElfFile(dataSource);
const { success, section } = await elf.tryReadSection('.note.gnu.build-id');
if (!success || !section) {
console.error('Could not find .note.gnu.build-id section');
process.exit(1);
}

const uuid = Array.from(section.slice(16, 36))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
Comment on lines +30 to +32
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The build-id extraction is hard-coded to section.slice(16, 36) (20 bytes). ELF build IDs can have different descriptor sizes, so this can truncate or misread IDs. Parse the note header to determine the descriptor offset/length (or at least derive the end from the note’s descsz) instead of assuming 20 bytes.

Copilot uses AI. Check for mistakes.

console.log(uuid);
Comment on lines +30 to +34
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name uuid is misleading here: the GNU build-id is not necessarily a UUID (often it’s a SHA-1-like hex string). Consider renaming to something like buildIdHex/buildId to match what is printed.

Suggested change
const uuid = Array.from(section.slice(16, 36))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
console.log(uuid);
const buildId = Array.from(section.slice(16, 36))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
console.log(buildId);

Copilot uses AI. Check for mistakes.
} finally {
await fileHandle.close();
}
Comment on lines +7 to +37
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid calling process.exit(1) inside this try block: it can terminate the process before the finally runs, so the file handle may not be closed. Prefer setting process.exitCode = 1 and returning from a main() function (or otherwise ensuring cleanup happens before exiting).

Suggested change
const filePath = process.argv[2];
if (!filePath) {
console.error('Usage: elfy <path-to-elf-file>');
process.exit(1);
}
const fileHandle = await open(filePath, 'r');
try {
const dataSource: DataSource = {
async read(offset: number, length: number): Promise<Uint8Array> {
const buffer = new Uint8Array(length);
const { bytesRead } = await fileHandle.read(buffer, 0, length, offset);
return buffer.slice(0, bytesRead);
},
};
const elf = new ElfFile(dataSource);
const { success, section } = await elf.tryReadSection('.note.gnu.build-id');
if (!success || !section) {
console.error('Could not find .note.gnu.build-id section');
process.exit(1);
}
const uuid = Array.from(section.slice(16, 36))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
console.log(uuid);
} finally {
await fileHandle.close();
}
async function main(): Promise<void> {
const filePath = process.argv[2];
if (!filePath) {
console.error('Usage: elfy <path-to-elf-file>');
process.exitCode = 1;
return;
}
const fileHandle = await open(filePath, 'r');
try {
const dataSource: DataSource = {
async read(offset: number, length: number): Promise<Uint8Array> {
const buffer = new Uint8Array(length);
const { bytesRead } = await fileHandle.read(buffer, 0, length, offset);
return buffer.slice(0, bytesRead);
},
};
const elf = new ElfFile(dataSource);
const { success, section } = await elf.tryReadSection('.note.gnu.build-id');
if (!success || !section) {
console.error('Could not find .note.gnu.build-id section');
process.exitCode = 1;
return;
}
const uuid = Array.from(section.slice(16, 36))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
console.log(uuid);
} finally {
await fileHandle.close();
}
}
await main();

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +37
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR introduces a new user-facing CLI entrypoint, but there are no automated tests covering CLI argument handling and output (e.g., missing path, missing section, successful extraction). Consider adding a vitest spec that runs the CLI (or refactors the logic into a testable function) to prevent regressions.

Suggested change
const filePath = process.argv[2];
if (!filePath) {
console.error('Usage: elfy <path-to-elf-file>');
process.exit(1);
}
const fileHandle = await open(filePath, 'r');
try {
const dataSource: DataSource = {
async read(offset: number, length: number): Promise<Uint8Array> {
const buffer = new Uint8Array(length);
const { bytesRead } = await fileHandle.read(buffer, 0, length, offset);
return buffer.slice(0, bytesRead);
},
};
const elf = new ElfFile(dataSource);
const { success, section } = await elf.tryReadSection('.note.gnu.build-id');
if (!success || !section) {
console.error('Could not find .note.gnu.build-id section');
process.exit(1);
}
const uuid = Array.from(section.slice(16, 36))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
console.log(uuid);
} finally {
await fileHandle.close();
}
type CliIo = {
stdout?: (message: string) => void;
stderr?: (message: string) => void;
};
export async function runCli(args: string[], io: CliIo = {}): Promise<number> {
const stdout = io.stdout ?? console.log;
const stderr = io.stderr ?? console.error;
const filePath = args[0];
if (!filePath) {
stderr('Usage: elfy <path-to-elf-file>');
return 1;
}
const fileHandle = await open(filePath, 'r');
try {
const dataSource: DataSource = {
async read(offset: number, length: number): Promise<Uint8Array> {
const buffer = new Uint8Array(length);
const { bytesRead } = await fileHandle.read(buffer, 0, length, offset);
return buffer.slice(0, bytesRead);
},
};
const elf = new ElfFile(dataSource);
const { success, section } = await elf.tryReadSection('.note.gnu.build-id');
if (!success || !section) {
stderr('Could not find .note.gnu.build-id section');
return 1;
}
const uuid = Array.from(section.slice(16, 36))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
stdout(uuid);
return 0;
} finally {
await fileHandle.close();
}
}
const exitCode = await runCli(process.argv.slice(2));
if (exitCode !== 0) {
process.exit(exitCode);
}

Copilot uses AI. Check for mistakes.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
],
"author": "@bobbyg603",
"license": "MIT",
"bin": {
"elfy": "dist/cli.js"
},
"files": [
"dist"
],
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["index.ts", "src/**/*.ts"],
"include": ["index.ts", "cli.ts", "src/**/*.ts"],
"exclude": ["node_modules", "dist", "spec"]
}
Loading