This commit is contained in:
Raven Scott
2026-04-04 18:41:34 -04:00
parent 7cf1a8d1c5
commit d75f4e7383
29 changed files with 1219 additions and 469 deletions
@@ -148,9 +148,13 @@ export const MAN_EXTRA_PAGES = [
* Keep sorted; see build.mjs.
*/
export const HELP_DELEGATED_COMMANDS = [
'bundlebee',
'git',
'hrpc',
'journalctl',
'openssl',
'pear-runtime-matrix',
'sidecar',
'ssh-keygen',
'systemctl',
'tar'
@@ -3,25 +3,21 @@
"section": 1,
"title": "change file group ownership",
"synopsis": [
"chgrp [OPTION]... [OPERAND]..."
"chgrp [OPTION]... GROUP FILE..."
],
"description": "Changing group ownership is not supported on Bare OS: Hyperdrive metadata is single-session oriented.",
"description": "Updates group metadata on the personal Hyperdrive for writable paths (same scope as chown). GROUP may be a numeric gid or root/guest/nobody/current GROUP name.",
"options": [],
"keywords": [
"chgrp",
"bare-os",
"coreutils",
"stub"
"metadata"
],
"stub": true,
"diagnostics": [
"chgrp: changing group is not supported on Bare OS"
],
"bareOsNotes": "Single-user identity; gid fields exist for display only.",
"bareOsNotes": "Uses vfs.chown with preserved uid; see chown(1) and vfs.js.",
"examples": [
{
"caption": "not supported — use identity model",
"code": "# chgrp is a stub; group is display metadata only"
"caption": "set group by name",
"code": "chgrp guest shared.conf"
}
]
}
+10 -10
View File
@@ -3,25 +3,25 @@
"section": 1,
"title": "change file owner and group",
"synopsis": [
"chown [OPTION]... [OPERAND]..."
"chown [OPTION]... OWNER[:GROUP] FILE..."
],
"description": "Changing file owner is not supported on Bare OS (single-user Hyperdrive metadata).",
"description": "Updates uid/gid metadata on the personal Hyperdrive for paths under the active session home, $HOME, /tmp, and /var/log. System paths are read-only. Non-root may only chgrp on files they own; root (euid 0) may set any owner.",
"options": [],
"keywords": [
"chown",
"bare-os",
"coreutils",
"stub"
"metadata"
],
"stub": true,
"diagnostics": [
"chown: changing owner is not supported on Bare OS"
],
"bareOsNotes": "Use identity login/logout instead of POSIX ownership changes.",
"bareOsNotes": "OWNER/GROUP may be numeric ids or root/guest/nobody/current USER/GROUP. See packages/bare-os-booter/lib/vfs.js chown.",
"examples": [
{
"caption": "not supported",
"code": "# chown stub — see man identity / login"
"caption": "change group only",
"code": "chown :guest file.txt"
},
{
"caption": "numeric owner and group",
"code": "chown 1000:1000 notes.txt"
}
]
}
@@ -3,19 +3,28 @@
"section": 1,
"title": "kernel-home-snapshot",
"synopsis": [
"kernel-home-snapshot [--json]"
"kernel-home-snapshot [--json] [--max N] [--depth D] [DIR]"
],
"description": "Placeholder for future personal-drive snapshot export.",
"description": "Walks a directory on the VFS and emits a bounded JSON manifest (paths, kinds, sizes). No archive bytes are produced — use for inventory, backup planning, and diagnostics.",
"options": [
{
"flag": "--json",
"meaning": "JSON status."
"meaning": "Print JSON manifest (default human summary also available)."
},
{
"flag": "--max N",
"meaning": "Cap number of file entries (default 8000)."
},
{
"flag": "--depth D",
"meaning": "Maximum directory depth (default 12)."
}
],
"keywords": [
"kernel-home-snapshot",
"bare-os",
"backup"
"backup",
"manifest"
],
"stub": true
"bareOsNotes": "Schema field schema:2 in emitted JSON; see packages/bare-os-coreutils/src/kernel-home-snapshot.js."
}
@@ -11,7 +11,7 @@ import { COREUTILS_COMMANDS, MAN_EXTRA_PAGES } from '../lib/commands.mjs'
const __dirname = dirname(fileURLToPath(import.meta.url))
const pagesDir = join(__dirname, '../man/pages')
const STUB = new Set(['chgrp', 'chown'])
const STUB = new Set()
const POSIX_TITLE = {
awk: 'pattern scanning and processing language',
@@ -113,10 +113,7 @@ EXAMPLES.cat = [
{ caption: 'here-string via echo pipe', code: 'echo hello | cat' }
]
EXAMPLES.chgrp = [
{
caption: 'not supported — use identity model',
code: '# chgrp is a stub; group is display metadata only'
}
{ caption: 'set group by name', code: 'chgrp guest shared.conf' }
]
EXAMPLES.chmod = [
{ caption: 'octal', code: 'chmod 644 ~/.profile' },
@@ -128,7 +125,8 @@ EXAMPLES.chmod = [
{ caption: 'all read, owner write', code: 'chmod a+r,u+w shared.txt' }
]
EXAMPLES.chown = [
{ caption: 'not supported', code: '# chown stub — see man identity / login' }
{ caption: 'group only', code: 'chown :guest file.txt' },
{ caption: 'numeric ids', code: 'chown 1000:1000 notes.txt' }
]
EXAMPLES.cksum = [
{ caption: 'checksum file', code: 'cksum iso.img' },
@@ -357,15 +355,15 @@ const EXTRA = {}
EXTRA.chgrp = {
description:
'Changing group ownership is not supported on Bare OS: Hyperdrive metadata is single-session oriented.',
diagnostics: ['chgrp: changing group is not supported on Bare OS'],
bareOsNotes: 'Single-user identity; gid fields exist for display only.'
'Updates group metadata on the personal Hyperdrive for writable paths (session home, $HOME, /tmp, /var/log).',
bareOsNotes:
'Uses vfs.chown with preserved uid; system image paths are read-only. See packages/bare-os-booter/lib/vfs.js.'
}
EXTRA.chown = {
description:
'Changing file owner is not supported on Bare OS (single-user Hyperdrive metadata).',
diagnostics: ['chown: changing owner is not supported on Bare OS'],
bareOsNotes: 'Use identity login/logout instead of POSIX ownership changes.'
'Updates uid/gid metadata on the personal Hyperdrive where the booter allows writes; euid 0 may set any owner.',
bareOsNotes:
'OWNER/GROUP may be numeric or root/guest/nobody/current user. See vfs chown and identity env UID/GID.'
}
EXTRA.xargs = {
description:
+43 -1
View File
@@ -2,6 +2,48 @@
* rm — remove files or directories.
* Flags: -r -R --recursive, -f --force, -d --dir, -- ; bundled e.g. -rf
*/
/**
* @param {Record<string, unknown>} vfs
* @param {string} p logical path
* @param {boolean} force
*/
async function rmTreeManual(vfs, p, force) {
/** @type {{ type?: string } | null} */
let st = null
try {
st = await vfs.lstat(p)
} catch (e) {
if (!force) throw e
return
}
if (!st) {
if (!force) throw new Error('No such file')
return
}
if (st.type === 'directory') {
/** @type {string[]} */
const names = []
try {
for await (const n of vfs.readdir(p)) names.push(n)
} catch (e) {
if (!force) throw e
return
}
for (const n of names) {
const child =
p.replace(/\/+$/, '') === ''
? '/' + n
: p.replace(/\/$/, '') + '/' + n
await rmTreeManual(vfs, child, force)
}
if (typeof vfs.rmdir === 'function') await vfs.rmdir(p)
else await vfs.unlink(p)
return
}
await vfs.unlink(p)
}
async function run(ctx, argv) {
const vfs = ctx.vfs
let recursive = false
@@ -56,7 +98,7 @@ async function run(ctx, argv) {
vfs && typeof vfs.rm === 'function'
? (p) => vfs.rm(p, { recursive, force })
: async (p) => {
if (recursive) throw new Error('recursive rm not supported')
if (recursive) return rmTreeManual(vfs, p, force)
return vfs.unlink(p)
}
for (const f of files) {
+46 -16
View File
@@ -1,27 +1,51 @@
/**
* @param {string} raw
* @param {number} curLen
* @returns {number | null}
*/
function resolveTruncateSize(raw, curLen) {
const s = String(raw).trim()
if (!s) return null
if (/%$/.test(s)) {
const inner = s.startsWith('+') || s.startsWith('-') ? s.slice(1) : s
const pct = Number.parseFloat(inner.replace(/%$/, ''))
if (!Number.isFinite(pct)) return null
const delta = Math.floor((curLen * pct) / 100)
if (s.startsWith('+')) return Math.max(0, curLen + delta)
if (s.startsWith('-')) return Math.max(0, curLen - delta)
return Math.max(0, Math.floor((curLen * pct) / 100))
}
if (s.startsWith('+')) {
const n = Number.parseInt(s.slice(1), 10)
if (!Number.isFinite(n)) return null
return Math.max(0, curLen + n)
}
if (s.startsWith('-')) {
const n = Number.parseInt(s.slice(1), 10)
if (!Number.isFinite(n)) return null
return Math.max(0, curLen - n)
}
const abs = Number.parseInt(s, 10)
if (!Number.isFinite(abs) || abs < 0) return null
return abs
}
async function run(ctx, argv) {
let size = null
/** @type {string | null} */
let sizeSpec = null
const paths = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-h' || a === '--help') {
ctx.console.log(
'usage: truncate -s SIZE FILE\nSet file length to SIZE bytes (padded with zeros if growing).'
'usage: truncate -s SIZE FILE\n' +
'SIZE: bytes, +N, -N, +N%, -N%, or N% (of current length).\n' +
'Sets file length (padded with zeros if growing).'
)
return
}
if ((a === '-s' || a === '--size') && argv[i + 1]) {
const raw = argv[++i]
if (raw.startsWith('+') || raw.startsWith('-')) {
ctx.console.error('truncate: relative sizes not supported')
ctx.exitCode = 1
return
}
size = parseInt(raw, 10)
if (!Number.isFinite(size) || size < 0) {
ctx.console.error('truncate: invalid size')
ctx.exitCode = 1
return
}
sizeSpec = argv[++i]
continue
}
if (a.startsWith('-')) {
@@ -31,7 +55,7 @@ async function run(ctx, argv) {
}
paths.push(a)
}
if (size == null || paths.length !== 1) {
if (!sizeSpec || paths.length !== 1) {
ctx.console.error('usage: truncate -s SIZE FILE')
ctx.exitCode = 1
return
@@ -42,7 +66,13 @@ async function run(ctx, argv) {
const b = await ctx.vfs.readFile(file)
if (b) cur = b instanceof Uint8Array ? b : new Uint8Array(b)
} catch {
/* new file */
/* new file — relative sizes use curLen 0 */
}
const size = resolveTruncateSize(sizeSpec, cur.length)
if (size == null) {
ctx.console.error('truncate: invalid size: ' + sizeSpec)
ctx.exitCode = 1
return
}
const out = new Uint8Array(size)
out.set(cur.subarray(0, Math.min(cur.length, size)))