This commit is contained in:
Raven Scott
2026-04-03 17:56:46 -04:00
parent a7e08e093c
commit ec236f9cc7
28 changed files with 1052 additions and 97 deletions
+1 -1
View File
@@ -39,7 +39,7 @@ Or `node packages/bare-os-coreutils/build.mjs`.
**`sed`** / **`awk`** use large interpreters in **`lib/sed-engine.js`** and **`lib/awk-engine.js`** — capable, but not guaranteed to match every POSIX or GNU edge case.
**Stubs** (**`xargs`**, **`getconf`**, **`chown`**, **`chgrp`**, **`mkfifo`**) print a clear error and exit non-zero.
**Stubs** (**`chown`**, **`chgrp`**, **`mkfifo`**) print a clear error and exit non-zero. **`getconf`** and **`xargs`** implement a documented Bare-specific subset (see `src/getconf.js`, `src/xargs.js`).
See [handbook §6 — Kernel and `/bin`](../../handbook/06-kernel-and-binaries.md), [handbook §9 — POSIX alignment](../../handbook/09-posix-utilities-shell-and-vfs.md), and [handbook §10 — `man` and online help](../../handbook/10-manpages-and-online-help.md).
@@ -3,22 +3,32 @@
"section": 1,
"title": "get configuration values",
"synopsis": [
"getconf [OPTION]... [OPERAND]..."
"getconf [-a] system_var"
],
"description": "Prints a fixed subset of POSIX-style limit names and values for Bare OS. There is no host sysconf path; constants match the documented JavaScript/VFS environment.",
"options": [
{
"flag": "-a",
"meaning": "Write every known variable (each name on one line, value on the next)"
}
],
"description": "Host sysconf-style values are not exposed. The command prints an error.",
"options": [],
"keywords": [
"getconf",
"limits",
"PATH_MAX",
"POSIX",
"bare-os",
"coreutils",
"stub"
"coreutils"
],
"stub": true,
"bareOsNotes": "Stub only; no kernel sysconf surface.",
"bareOsNotes": "Subset only; unknown names fail with exit status 1. See src/getconf.js for the name table.",
"examples": [
{
"caption": "stub",
"code": "# getconf PATH_MAX — not available on Bare OS"
"caption": "path length limit",
"code": "getconf PATH_MAX"
},
{
"caption": "list known names and values",
"code": "getconf -a"
}
]
}
@@ -3,21 +3,37 @@
"section": 1,
"title": "construct argument lists and invoke utility",
"synopsis": [
"xargs [OPTION]... [OPERAND]..."
"xargs [-0] [-n maxargs] [--] [utility [argument ...]]"
],
"description": "Reads stdin, splits into words (or null-terminated records with -0), and invokes the utility via ctx.runBinCommand in batches. Enforces fixed limits on stdin size, token count, arguments per run, and total invocations.",
"options": [
{
"flag": "-0, --null",
"meaning": "Input items are separated by null bytes instead of whitespace"
},
{
"flag": "-n maxargs, --max-args maxargs",
"meaning": "Use at most maxargs arguments from stdin per utility invocation (capped at 128)"
}
],
"description": "xargs does not spawn arbitrary /bin utilities on Bare OS. Use shell word splitting or pipelines.",
"options": [],
"keywords": [
"xargs",
"arguments",
"bare-os",
"coreutils",
"stub"
"coreutils"
],
"stub": true,
"bareOsNotes": "No process fork model; see handbook ch.9.",
"bareOsNotes": "No host fork; subset of POSIX/GNU xargs. See src/xargs.js for numeric limits.",
"examples": [
{
"caption": "workaround: shell word split",
"caption": "pass lines as arguments",
"code": "printf 'a\\nb\\n' | xargs echo"
},
{
"caption": "one argument per run",
"code": "printf 'a\\nb\\n' | xargs -n1 echo"
},
{
"caption": "workaround for complex scripts",
"code": "# for f in *.txt; do grep -l foo $f; done"
}
]
@@ -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', 'xargs', 'getconf', 'mkfifo'])
const STUB = new Set(['chgrp', 'chown', 'mkfifo'])
const POSIX_TITLE = {
awk: 'pattern scanning and processing language',
@@ -184,7 +184,8 @@ EXAMPLES.find = [
{ caption: 'OR names', code: 'find . \\( -name "*.c" -o -name "*.h" \\)' }
]
EXAMPLES.getconf = [
{ caption: 'stub', code: '# getconf PATH_MAX — not available on Bare OS' }
{ caption: 'path length limit', code: 'getconf PATH_MAX' },
{ caption: 'list known names and values', code: 'getconf -a' }
]
EXAMPLES.grep = [
{ caption: 'recursive feel (grep each file)', code: 'grep -n error *.log' },
@@ -327,7 +328,15 @@ EXAMPLES.which = [{ caption: 'resolve on PATH', code: 'which ls' }]
EXAMPLES.whoami = [{ caption: 'effective user', code: 'whoami' }]
EXAMPLES.xargs = [
{
caption: 'workaround: shell word split',
caption: 'pass lines as arguments',
code: "printf 'a\\nb\\n' | xargs echo"
},
{
caption: 'one argument per run',
code: "printf 'a\\nb\\n' | xargs -n1 echo"
},
{
caption: 'workaround for complex scripts',
code: '# for f in *.txt; do grep -l foo $f; done'
}
]
@@ -349,13 +358,25 @@ EXTRA.chown = {
}
EXTRA.xargs = {
description:
'xargs does not spawn arbitrary /bin utilities on Bare OS. Use shell word splitting or pipelines.',
bareOsNotes: 'No process fork model; see handbook ch.9.'
'Reads stdin into argument batches and runs **`ctx.runBinCommand`** (same as the shell). Enforces stdin size, token count, batch size, and invocation limits for safety.',
options: [
{ flag: '-0, --null', meaning: 'Input items are null-terminated, not whitespace-separated' },
{
flag: '-n, --max-args',
meaning: 'Up to N arguments per utility invocation (capped at 128)'
}
],
bareOsNotes:
'No host process spawn; not full POSIX xargs (no -I, -P, etc.). See src/xargs.js for limits.'
}
EXTRA.getconf = {
description:
'Host sysconf-style values are not exposed. The command prints an error.',
bareOsNotes: 'Stub only; no kernel sysconf surface.'
'Prints a fixed subset of configuration limits for Bare OS (JavaScript runtime and VFS). There is no host sysconf(3); values are documented constants, not live kernel queries.',
options: [
{ flag: '-a', meaning: 'Write all known variables (name then value per pair)' }
],
bareOsNotes:
'Unknown variable names exit with status 1. Not a full Issue 7 getconf implementation.'
}
EXTRA.mkfifo = {
description:
+58 -3
View File
@@ -1,10 +1,65 @@
/**
* Subset of POSIX.1-2017 getconf — fixed values for Bare OS (no host sysconf).
* Unknown names exit with status 1 (matches common getconf for invalid var).
*/
const CONF = {
PATH_MAX: '4096',
NAME_MAX: '255',
/** POSIX minimum for ARG_MAX; Bare uses a conservative cap for runBinCommand argv. */
_POSIX_ARG_MAX: '4096',
ARG_MAX: '262144',
LINE_MAX: '2048',
/** POSIX.1-2008 */
_POSIX_VERSION: '200809',
_POSIX2_VERSION: '200809',
NGROUPS_MAX: '32',
OPEN_MAX: '256',
STREAM_MAX: '256',
TZNAME_MAX: '32',
_POSIX_CHOWN_RESTRICTED: '1',
_POSIX_NO_TRUNC: '1',
_POSIX_VDISABLE: '0',
_POSIX_JOB_CONTROL: '0',
_POSIX_SAVED_IDS: '0',
/** Bare shell line length (reasonable REPL limit, not a hard kernel cap). */
BARE_OS_INPUT_LINE_MAX: '8192'
}
async function run(ctx, argv) {
const name = argv[1]
const args = argv.slice(1).filter((a) => a !== '--')
let dumpAll = false
let name = null
for (const a of args) {
if (a === '-a') dumpAll = true
else if (!a.startsWith('-')) name = a
else {
ctx.console.error('getconf: unknown option: ' + a)
ctx.exitCode = 1
return
}
}
if (dumpAll) {
for (const k of Object.keys(CONF).sort()) {
ctx.console.log(k + '\n' + CONF[k])
}
ctx.exitCode = 0
return
}
if (!name) {
ctx.console.error('usage: getconf VARIABLE_NAME')
ctx.console.error('usage: getconf [-a] system_var')
ctx.exitCode = 1
return
}
ctx.console.error('getconf: not implemented on Bare OS (no full sysconf path): ' + name)
if (Object.prototype.hasOwnProperty.call(CONF, name)) {
ctx.console.log(CONF[name])
ctx.exitCode = 0
return
}
ctx.console.error('getconf: ' + name + ': unknown variable')
ctx.exitCode = 1
}
+116 -4
View File
@@ -1,6 +1,118 @@
/**
* Bounded xargs for Bare OS: invokes ctx.runBinCommand only (no host spawn).
* Limits: stdin 256KiB, 4096 whitespace/null tokens, 128 args per invocation,
* 64 invocations per run. Exceeding limits is a fatal error (exit 125).
*/
const MAX_STDIN = 256 * 1024
const MAX_TOKENS = 4096
const MAX_PER_INVOCATION = 128
const MAX_INVOCATIONS = 64
async function run(ctx, argv) {
ctx.console.error(
'xargs: running arbitrary commands from /bin/xargs is not supported in Bare OS; use the shell to expand arguments.'
)
ctx.exitCode = 1
if (typeof ctx.runBinCommand !== 'function') {
ctx.console.error(
'xargs: ctx.runBinCommand is not available (requires a Bare OS booter session)'
)
ctx.exitCode = 1
return
}
const args = argv.slice(1)
let nullSep = false
let maxBatch = MAX_PER_INVOCATION
let i = 0
while (i < args.length && args[i].startsWith('-')) {
const a = args[i]
if (a === '--') {
i++
break
}
if (a === '-0' || a === '--null') {
nullSep = true
i++
continue
}
if (a === '-n' || a === '--max-args') {
const n = args[i + 1]
if (n == null || !/^\d+$/.test(n) || Number(n) < 1) {
ctx.console.error('xargs: -n requires a positive integer')
ctx.exitCode = 1
return
}
maxBatch = Math.min(Number(n), MAX_PER_INVOCATION)
i += 2
continue
}
if (a.startsWith('-n') && a.length > 2 && /^\d+$/.test(a.slice(2))) {
const n = Number(a.slice(2))
maxBatch = Math.min(n, MAX_PER_INVOCATION)
i++
continue
}
ctx.console.error('xargs: unsupported option: ' + a)
ctx.console.error(
'xargs: Bare OS supports: -0/--null, -n N (max ' +
MAX_PER_INVOCATION +
' per run)'
)
ctx.exitCode = 1
return
}
/** @type {string[]} */
let cmd = args.slice(i)
if (cmd.length === 0) cmd = ['echo']
let text = bareStdin(ctx) || ''
if (text.length > MAX_STDIN) {
ctx.console.error(
'xargs: stdin exceeds ' + MAX_STDIN + ' bytes (Bare OS limit)'
)
ctx.exitCode = 125
return
}
/** @type {string[]} */
let pieces
if (nullSep) {
pieces = text.split('\0').filter((s) => s.length > 0)
} else {
pieces = text.trim() === '' ? [] : text.trim().split(/\s+/).filter(Boolean)
}
if (pieces.length > MAX_TOKENS) {
ctx.console.error(
'xargs: too many arguments (' + pieces.length + ' > ' + MAX_TOKENS + ')'
)
ctx.exitCode = 125
return
}
const runOne = async (batch) => {
await ctx.runBinCommand(cmd.concat(batch))
}
if (pieces.length === 0) {
await runOne([])
return
}
let invocations = 0
for (let o = 0; o < pieces.length; o += maxBatch) {
if (++invocations > MAX_INVOCATIONS) {
ctx.console.error(
'xargs: exceeded ' + MAX_INVOCATIONS + ' invocations (Bare OS limit)'
)
ctx.exitCode = 125
return
}
const batch = pieces.slice(o, o + maxBatch)
await runOne(batch)
const ec = Number(ctx.exitCode) || 0
if (ec !== 0) {
ctx.exitCode = ec
return
}
}
}