This commit is contained in:
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
const BARE_P2P_SUITE_PREFIX = '[bare-p2p-v1]'
|
||||
const BARE_P2P_SUITE_MAX_HISTORY = 2000
|
||||
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
const BARE_P2P_SUITE_PREFIX = '[bare-p2p-v1]'
|
||||
const BARE_P2P_SUITE_MAX_HISTORY = 2000
|
||||
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
if (argv[i] === '-h' || argv[i] === '--help') {
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* POSIX-oriented awk interpreter for Bare OS (no import; concatenated before src/awk.js).
|
||||
* Supports: BEGIN/END, /re/, line patterns, {}, print/printf, if/else, while, for(;;), for(x in a),
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Bubble + cow rendering for baresay (no imports; prepended before src/baresay.js).
|
||||
*/
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/** ANSI helpers for /bin/edit (preamble; no import in src). */
|
||||
|
||||
const EDIT_ANSI_RESET = '\x1b[0m'
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
const B32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
|
||||
function bareB32EncodeBytes(u8) {
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* base64 — encode or decode Base64 (RFC 4648).
|
||||
* Uses btoa/atob or Buffer when available; otherwise a small inline encoder.
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let all = false
|
||||
/** @type {string | null} */
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
const B32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
|
||||
function bareHexEncode(u8) {
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/** ANSI helpers for /bin/edit (preamble; no import in src). */
|
||||
|
||||
const EDIT_ANSI_RESET = '\x1b[0m'
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
function bareCatOut(ctx, s) {
|
||||
if (bareOsEmitRaw(ctx, s)) return
|
||||
ctx.console.log(s)
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/** ANSI helpers for /bin/edit (preamble; no import in src). */
|
||||
|
||||
const EDIT_ANSI_RESET = '\x1b[0m'
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
function bareChmodSymbolic(curPerm, spec) {
|
||||
let m = curPerm & 0o777
|
||||
const parts = spec.split(',')
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/** POSIX cksum CRC (Open Group / npm `cksum` reference). */
|
||||
const BARE_CKSUM_TAB = new Uint32Array([
|
||||
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
for (const a of argv.slice(1)) {
|
||||
if (a === '--help' || a === '-h') {
|
||||
@@ -324,7 +450,7 @@ async function run(ctx, argv) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Keep in sync with packages/bare-os-booter/lib/terminal-escapes.js XTERM_CLEAR_SCROLLBACK_AND_VIEWPORT */
|
||||
/* Keep in sync with packages/bare-os-booter/lib/cli/terminal-escapes.js XTERM_CLEAR_SCROLLBACK_AND_VIEWPORT */
|
||||
const seq = '\x1b[H\x1b[3J\x1b[2J\x1b[0m'
|
||||
const term = String(ctx.vfs?.env?.TERM || globalThis.process?.env?.TERM || '')
|
||||
if (term === 'dumb') return
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const rest = argv.slice(1)
|
||||
const silent = rest.includes('-s') || rest.includes('--silent')
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function readLines(ctx, path) {
|
||||
const b4 = ctx.b4a
|
||||
if (path === '-') return bareStdin(ctx).split('\n')
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'corestorectl'
|
||||
const parsed = bareP2pParseCommonFlags(argv.slice(1))
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function copyPath(ctx, from, to, recursive, followSymlink, opts) {
|
||||
const preserve = opts && opts.preserveTime
|
||||
const update = opts && opts.update
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const vfs = ctx.vfs
|
||||
const home = vfs.env?.HOME || '/home/guest'
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'ctxbaredoctor'
|
||||
const parsed = bareP2pParseCommonFlags(argv.slice(1))
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* `/bin/curl` parity entry: normally the booter delegates to `curl-cli.js` first.
|
||||
* When this script runs (e.g. `BARE_OS_DELEGATE_ALLOW` excludes curl), it calls
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let delimChar = '\t'
|
||||
let fieldsSpec = ''
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* date — print current date/time; optional +FORMAT (strftime-like subset).
|
||||
*/
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
function parseSize(v) {
|
||||
const s = String(v || '').trim().toLowerCase()
|
||||
const m = /^(\d+)([kmg]?)$/.exec(s)
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let human = false
|
||||
const paths = []
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'dhtctl'
|
||||
const parsed = bareP2pParseCommonFlags(argv.slice(1))
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
const BARE_P2P_SUITE_PREFIX = '[bare-p2p-v1]'
|
||||
const BARE_P2P_SUITE_MAX_HISTORY = 2000
|
||||
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/** ANSI helpers for /bin/edit (preamble; no import in src). */
|
||||
|
||||
const EDIT_ANSI_RESET = '\x1b[0m'
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal diff(1) for Bare OS: compares two text files (VFS paths).
|
||||
* Supports -q (quiet, exit status only), -s (report when identical), -u (single-hunk unified).
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
if (typeof ctx.runBinCommand !== 'function') {
|
||||
ctx.console.error('dir: runBinCommand not available')
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* GNU-style LS_COLORS string + dircolors(5) database parsing and file classification.
|
||||
* Consumed by bare-os-booter (ESM import). Prepended into /bin/ls and /bin/dircolors by bare-os-coreutils build (no import there).
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
function dirnameEmit(ctx, out, nullSep) {
|
||||
if (!nullSep) {
|
||||
ctx.console.log(out)
|
||||
|
||||
@@ -311,31 +311,157 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared QVAC chat catalog + REST /models helpers (preamble for agent and discord-bot).
|
||||
*/
|
||||
|
||||
/** @type {{ id: string, family: string, label: string, tools: boolean, ramGb: number, profile?: string }[]} */
|
||||
/** @type {{ id: string, family: string, label: string, tools: boolean, ramGb: number, ctxSize: number, profile?: string }[]} */
|
||||
var BARE_AGENT_QVAC_CHAT_MODELS = [
|
||||
{ id: 'QWEN3_600M_INST_Q4', family: 'qwen3', label: 'Qwen3 0.6B Instruct Q4', tools: true, ramGb: 4, profile: 'lite' },
|
||||
{ id: 'QWEN3_1_7B_INST_Q4', family: 'qwen3', label: 'Qwen3 1.7B Instruct Q4', tools: true, ramGb: 8, profile: 'recommended' },
|
||||
{ id: 'QWEN3_4B_INST_Q4_K_M', family: 'qwen3', label: 'Qwen3 4B Instruct Q4_K_M', tools: true, ramGb: 16, profile: 'strong' },
|
||||
{ id: 'QWEN3_4B_Q4_K_M', family: 'qwen3', label: 'Qwen3 4B Q4_K_M', tools: true, ramGb: 16 },
|
||||
{ id: 'QWEN3_8B_INST_Q4_K_M', family: 'qwen3', label: 'Qwen3 8B Instruct Q4_K_M', tools: true, ramGb: 24 },
|
||||
{ id: 'LLAMA_TOOL_CALLING_1B_INST_Q4_K', family: 'llama', label: 'Llama 3.2 1B tool-calling', tools: true, ramGb: 6, profile: 'tool-tiny' },
|
||||
{ id: 'LLAMA_3_2_1B_INST_Q4_0', family: 'llama', label: 'Llama 3.2 1B Instruct Q4_0', tools: true, ramGb: 6 },
|
||||
{ id: 'SMOLLM2_360M_INST_Q8', family: 'smol', label: 'SmolLM2 360M Instruct Q8', tools: false, ramGb: 3 },
|
||||
{ id: 'GPT_OSS_20B_INST_Q4_K_M', family: 'gpt-oss', label: 'GPT-OSS 20B Instruct Q4_K_M', tools: true, ramGb: 24 },
|
||||
{ id: 'GEMMA4_2B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 2B multimodal Q4', tools: true, ramGb: 8 },
|
||||
{ id: 'GEMMA4_2B_MULTIMODAL_Q6_K', family: 'gemma', label: 'Gemma 4 2B multimodal Q6', tools: true, ramGb: 10 },
|
||||
{ id: 'GEMMA4_4B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 4B multimodal Q4', tools: true, ramGb: 16 },
|
||||
{ id: 'GEMMA4_31B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 31B multimodal Q4', tools: true, ramGb: 48 },
|
||||
{ id: 'QWEN3VL_2B_MULTIMODAL_Q4_K', family: 'qwen3', label: 'Qwen3-VL 2B multimodal Q4', tools: true, ramGb: 10 },
|
||||
{ id: 'QWEN3_5_2B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 2B multimodal Q4', tools: true, ramGb: 10 },
|
||||
{ id: 'QWEN3_5_4B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 4B multimodal Q4', tools: true, ramGb: 16 },
|
||||
{ id: 'QWEN3_5_0_8B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 8B multimodal Q4', tools: true, ramGb: 24 },
|
||||
{ id: 'QWEN3_5_9B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 9B multimodal Q4', tools: true, ramGb: 28 },
|
||||
{ id: 'QWEN3_6_27B_MULTIMODAL_Q4_K_XL', family: 'large', label: 'Qwen3.6 27B multimodal Q4', tools: true, ramGb: 48 }
|
||||
{ id: 'QWEN3_600M_INST_Q4', family: 'qwen3', label: 'Qwen3 0.6B Instruct Q4', tools: true, ramGb: 4, profile: 'lite', ctxSize: 32768 },
|
||||
{ id: 'QWEN3_1_7B_INST_Q4', family: 'qwen3', label: 'Qwen3 1.7B Instruct Q4', tools: true, ramGb: 8, profile: 'recommended', ctxSize: 32768 },
|
||||
{ id: 'QWEN3_4B_INST_Q4_K_M', family: 'qwen3', label: 'Qwen3 4B Instruct Q4_K_M', tools: true, ramGb: 16, profile: 'strong', ctxSize: 32768 },
|
||||
{ id: 'QWEN3_4B_Q4_K_M', family: 'qwen3', label: 'Qwen3 4B Q4_K_M', tools: true, ramGb: 16, ctxSize: 32768 },
|
||||
{ id: 'QWEN3_8B_INST_Q4_K_M', family: 'qwen3', label: 'Qwen3 8B Instruct Q4_K_M', tools: true, ramGb: 24, ctxSize: 32768 },
|
||||
{ id: 'LLAMA_TOOL_CALLING_1B_INST_Q4_K', family: 'llama', label: 'Llama 3.2 1B tool-calling', tools: true, ramGb: 6, profile: 'tool-tiny', ctxSize: 131072 },
|
||||
{ id: 'LLAMA_3_2_1B_INST_Q4_0', family: 'llama', label: 'Llama 3.2 1B Instruct Q4_0', tools: true, ramGb: 6, ctxSize: 131072 },
|
||||
{ id: 'SMOLLM2_360M_INST_Q8', family: 'smol', label: 'SmolLM2 360M Instruct Q8', tools: false, ramGb: 3, ctxSize: 8192 },
|
||||
{ id: 'GPT_OSS_20B_INST_Q4_K_M', family: 'gpt-oss', label: 'GPT-OSS 20B Instruct Q4_K_M', tools: true, ramGb: 24, ctxSize: 131072 },
|
||||
{ id: 'GEMMA4_2B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 2B multimodal Q4', tools: true, ramGb: 8, ctxSize: 131072 },
|
||||
{ id: 'GEMMA4_2B_MULTIMODAL_Q6_K', family: 'gemma', label: 'Gemma 4 2B multimodal Q6', tools: true, ramGb: 10, ctxSize: 131072 },
|
||||
{ id: 'GEMMA4_4B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 4B multimodal Q4', tools: true, ramGb: 16, ctxSize: 131072 },
|
||||
{ id: 'GEMMA4_31B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 31B multimodal Q4', tools: true, ramGb: 48, ctxSize: 262144 },
|
||||
{ id: 'QWEN3VL_2B_MULTIMODAL_Q4_K', family: 'qwen3', label: 'Qwen3-VL 2B multimodal Q4', tools: true, ramGb: 10, ctxSize: 131072 },
|
||||
{ id: 'QWEN3_5_2B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 2B multimodal Q4', tools: true, ramGb: 10, ctxSize: 262144 },
|
||||
{ id: 'QWEN3_5_4B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 4B multimodal Q4', tools: true, ramGb: 16, ctxSize: 262144 },
|
||||
{ id: 'QWEN3_5_0_8B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 0.8B multimodal Q4', tools: true, ramGb: 8, ctxSize: 262144 },
|
||||
{ id: 'QWEN3_5_9B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 9B multimodal Q4', tools: true, ramGb: 28, ctxSize: 262144 },
|
||||
{ id: 'QWEN3_6_27B_MULTIMODAL_Q4_K_XL', family: 'large', label: 'Qwen3.6 27B multimodal Q4', tools: true, ramGb: 48, ctxSize: 262144 }
|
||||
]
|
||||
|
||||
/** @type {Record<string, string[]>} */
|
||||
@@ -437,6 +563,72 @@ function bareAgentParseOpenAiModels(json) {
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep an explicit QVAC model id (Discord / `--model` / picker).
|
||||
* Profile defaults only apply when no model is set.
|
||||
* @param {Record<string, unknown>} config
|
||||
* @param {{ chatModel?: string }} [profile]
|
||||
* @returns {Record<string, unknown>}
|
||||
*/
|
||||
function bareAgentHonorExplicitQvacModel(config, profile) {
|
||||
const out = config && typeof config === 'object' ? config : {}
|
||||
const explicit = String(out.qvac_model || out.model || '').trim()
|
||||
if (explicit) {
|
||||
out.qvac_model = explicit
|
||||
out.model = explicit
|
||||
if (typeof bareAgentQvacSyncSettingsForModel === 'function') {
|
||||
bareAgentQvacSyncSettingsForModel(out, explicit)
|
||||
} else {
|
||||
const card =
|
||||
typeof bareAgentQvacFindChatModel === 'function'
|
||||
? bareAgentQvacFindChatModel(explicit)
|
||||
: null
|
||||
if (card && card.profile) out.qvac_profile = card.profile
|
||||
}
|
||||
return out
|
||||
}
|
||||
const fallback = String((profile && profile.chatModel) || '').trim()
|
||||
if (fallback) {
|
||||
out.qvac_model = fallback
|
||||
out.model = fallback
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist a live model switch for the current backend.
|
||||
* @param {Record<string, unknown>} config
|
||||
* @param {string} modelId
|
||||
* @returns {Record<string, unknown>}
|
||||
*/
|
||||
function bareAgentApplyLiveModel(config, modelId) {
|
||||
const out = { ...(config && typeof config === 'object' ? config : {}) }
|
||||
const id = String(modelId || '').trim()
|
||||
if (!id) return out
|
||||
let backend = 'qvac'
|
||||
if (typeof bareAgentResolveBackend === 'function') {
|
||||
backend = bareAgentResolveBackend(out)
|
||||
} else {
|
||||
const b = String(out.backend || '').trim().toLowerCase()
|
||||
const p = String(out.provider || '').trim().toLowerCase()
|
||||
if (b === 'rest' || b === 'openai' || b === 'http') backend = 'rest'
|
||||
else if (b === 'qvac') backend = 'qvac'
|
||||
else if (p === 'groq' || p === 'xai' || p === 'openai' || p === 'custom') backend = 'rest'
|
||||
else if (out.rest_api_key && String(out.rest_api_key).trim()) backend = 'rest'
|
||||
}
|
||||
if (backend === 'qvac') {
|
||||
out.backend = 'qvac'
|
||||
out.provider = 'qvac'
|
||||
out.qvac_model = id
|
||||
out.model = id
|
||||
out.qvac_ctx_size = 0
|
||||
return bareAgentHonorExplicitQvacModel(out, null)
|
||||
}
|
||||
out.backend = 'rest'
|
||||
out.model = id
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [provider]
|
||||
*/
|
||||
@@ -3344,17 +3536,16 @@ async function discordCmdHandleBare(ctx, sub) {
|
||||
title: 'Commands',
|
||||
desc: 'Running as **' + user + '**. Open **Menu** for destinations, or use a slash command.',
|
||||
fields: [
|
||||
discordField('/bare', 'Session — status, whoami, hostname, date, uptime, motd, uname'),
|
||||
discordField('/sys', 'Host — disk, memory, processes, env, doctor, features, limits'),
|
||||
discordField('/svc', 'Services — list, start, stop, restart, logs'),
|
||||
discordField('/fs', 'Read-only VFS — ls, cat, stat, head'),
|
||||
discordField('/net', 'Swarm — peers, summary'),
|
||||
discordField('/panel', 'Control panel (also Menu on every reply)'),
|
||||
discordField('/bare', 'Session — ping, about, help, status, whoami, hostname, date, uptime, motd, uname, presence'),
|
||||
discordField('/sys', 'Host — disk, memory, processes, env, doctor, features, limits, peers, swarm'),
|
||||
discordField('/svc', 'Services — list, start, stop, restart, logs, journal'),
|
||||
discordField(
|
||||
'/files /edit /create /upload /open-file',
|
||||
'Browse, edit, wget in, or attach a VFS file out (CDN, kept)'
|
||||
'/files',
|
||||
'VFS — browse, ls, cat, stat, head, edit, create, upload, open-file (CDN, kept)'
|
||||
),
|
||||
discordField('/settings', 'Live session knobs (theme, shell, Discord, agent, aliases)'),
|
||||
discordField('/r /journal /say /man', 'Full shell (cwd + autocomplete), logs, speak, man pages'),
|
||||
discordField('/r /say /man', 'Full shell (cwd + autocomplete), speak, man pages'),
|
||||
discordField('/plugins', 'List, reload, enable/disable ~/.discord/plugins'),
|
||||
discordField('/hdms', 'Hyperdrives — list, create, add, invite, pair, /mnt'),
|
||||
discordField('/holesail', 'Tunnels — list, add, edit, start/stop, enable, service'),
|
||||
@@ -3362,8 +3553,7 @@ async function discordCmdHandleBare(ctx, sub) {
|
||||
discordField(
|
||||
'DM this bot',
|
||||
'Whitelisted users: send a normal message in a Direct Message to talk to the agent (same ~/.agent history). help / reset / stop work as text.'
|
||||
),
|
||||
discordField('/status', 'Bot presence — playing / watching / listening / competing')
|
||||
)
|
||||
]
|
||||
})
|
||||
)
|
||||
@@ -4493,7 +4683,7 @@ async function discordCmdHandleOpenFile(ctx, interaction, rawPath) {
|
||||
const given = String(rawPath || '').trim()
|
||||
if (!given) {
|
||||
return {
|
||||
text: 'Give `/open-file` a VFS path (e.g. `~/notes.txt`). The bot attaches it to this channel (Discord CDN; not auto-deleted).',
|
||||
text: 'Give `/files open-file` a VFS path (e.g. `~/notes.txt`). The bot attaches it to this channel (Discord CDN; not auto-deleted).',
|
||||
ephemeral: true
|
||||
}
|
||||
}
|
||||
@@ -7675,27 +7865,16 @@ function discordBuildSlashCommands(dj, ctx) {
|
||||
const finish = function () {
|
||||
const B2 = dj.SlashCommandBuilder
|
||||
const bare = new B2().setName('bare').setDescription('Bare OS session (logged-in user)')
|
||||
const sys = new B2().setName('sys').setDescription('Bare OS system snapshots')
|
||||
const svc = new B2().setName('svc').setDescription('systemctl units')
|
||||
const fsCmd = new B2().setName('fs').setDescription('Read-only VFS')
|
||||
const net = new B2().setName('net').setDescription('Swarm / network')
|
||||
const sys = new B2().setName('sys').setDescription('Bare OS system snapshots and swarm')
|
||||
const svc = new B2().setName('svc').setDescription('systemctl units and journal')
|
||||
const man = new B2().setName('man').setDescription('Look up a man page')
|
||||
const say = new B2().setName('say').setDescription('Speak as Bare OS (or open a compose form)')
|
||||
const run = new B2()
|
||||
.setName('r')
|
||||
.setDescription('Run a Bare OS shell command (cwd, pipes, cd, history)')
|
||||
const journal = new B2()
|
||||
.setName('journal')
|
||||
.setDescription('Tail a unit or system log')
|
||||
const edit = new B2()
|
||||
.setName('edit')
|
||||
.setDescription('Edit a text file in a Discord modal (home or /tmp)')
|
||||
const create = new B2()
|
||||
.setName('create')
|
||||
.setDescription('Create a new text file (pick a path, then enter contents)')
|
||||
const files = new B2()
|
||||
.setName('files')
|
||||
.setDescription('Browse and manage files (list, open, mkdir, rename, delete)')
|
||||
.setDescription('VFS: browse, read, edit, create, upload, attach')
|
||||
const settings = new B2()
|
||||
.setName('settings')
|
||||
.setDescription('Live session settings (theme, shell, discord, agent, aliases)')
|
||||
@@ -7705,16 +7884,6 @@ function discordBuildSlashCommands(dj, ctx) {
|
||||
const panel = new B2()
|
||||
.setName('panel')
|
||||
.setDescription('Interactive Bare OS control panel')
|
||||
const ping = new B2().setName('ping').setDescription('Reply pong')
|
||||
const status = new B2()
|
||||
.setName('status')
|
||||
.setDescription('Bot presence in the member list (playing / watching / …)')
|
||||
const upload = new B2()
|
||||
.setName('upload')
|
||||
.setDescription('Upload an attachment; wget saves it in the /r cwd or path')
|
||||
const openFile = new B2()
|
||||
.setName('open-file')
|
||||
.setDescription('Send a VFS file as a Discord attachment (kept, not auto-deleted)')
|
||||
const hdms = new B2()
|
||||
.setName('hdms')
|
||||
.setDescription('Hyperdrive management (list, create, add, invite, pair)')
|
||||
@@ -7735,7 +7904,31 @@ function discordBuildSlashCommands(dj, ctx) {
|
||||
['date', 'Clock'],
|
||||
['uptime', '/proc/uptime'],
|
||||
['motd', '/etc/motd'],
|
||||
['uname', 'uname -a style']
|
||||
['uname', 'uname -a style'],
|
||||
['presence', 'Bot presence in the member list', function (s) {
|
||||
discordCmdOpt(s, 'activity', 'playing, watching, listening, competing, custom, streaming', false, false, {
|
||||
choices: [
|
||||
['Playing', 'playing'],
|
||||
['Watching', 'watching'],
|
||||
['Listening', 'listening'],
|
||||
['Competing', 'competing'],
|
||||
['Custom', 'custom'],
|
||||
['Streaming', 'streaming']
|
||||
]
|
||||
})
|
||||
discordCmdOpt(s, 'name', 'Activity text shown in the member list', false, false, {
|
||||
min: 1,
|
||||
max: 128
|
||||
})
|
||||
discordCmdOpt(s, 'state', 'online, idle, dnd, or invisible', false, false, {
|
||||
choices: [
|
||||
['Online', 'online'],
|
||||
['Idle', 'idle'],
|
||||
['Do not disturb', 'dnd'],
|
||||
['Invisible', 'invisible']
|
||||
]
|
||||
})
|
||||
}]
|
||||
]) &&
|
||||
discordCmdAddSubs(sys, [
|
||||
['df', 'Disk / host resources'],
|
||||
@@ -7744,7 +7937,10 @@ function discordBuildSlashCommands(dj, ctx) {
|
||||
['env', 'Redacted environment'],
|
||||
['doctor', 'Security / debug posture'],
|
||||
['features', '/proc/bare_os_features'],
|
||||
['rlimits', 'Resource limits']
|
||||
['rlimits', 'Resource limits'],
|
||||
['peers', 'Swarm peers'],
|
||||
['swarm', 'Swarm snapshot'],
|
||||
['summary', 'net_summary']
|
||||
]) &&
|
||||
discordCmdAddSubs(svc, [
|
||||
['list', 'systemctl list'],
|
||||
@@ -7762,9 +7958,15 @@ function discordBuildSlashCommands(dj, ctx) {
|
||||
}],
|
||||
['logs', 'Unit logs', function (s) {
|
||||
discordCmdOpt(s, 'unit', 'Unit name', true, true)
|
||||
}],
|
||||
['journal', 'Tail a unit or system log', function (s) {
|
||||
discordCmdOpt(s, 'unit', 'Unit name', false, true)
|
||||
}]
|
||||
]) &&
|
||||
discordCmdAddSubs(fsCmd, [
|
||||
discordCmdAddSubs(files, [
|
||||
['browse', 'Interactive file manager', function (s) {
|
||||
discordCmdOpt(s, 'path', 'Directory to open', false, true)
|
||||
}],
|
||||
['ls', 'List a directory', function (s) {
|
||||
discordCmdOpt(s, 'path', 'VFS path', false, true)
|
||||
}],
|
||||
@@ -7777,13 +7979,21 @@ function discordBuildSlashCommands(dj, ctx) {
|
||||
['head', 'First lines of a file', function (s) {
|
||||
discordCmdOpt(s, 'path', 'VFS path', true, true)
|
||||
discordCmdOpt(s, 'lines', 'Line count', false, false)
|
||||
}],
|
||||
['edit', 'Edit a text file in a Discord modal', function (s) {
|
||||
discordCmdOpt(s, 'path', 'File under ~ or /tmp (omit to pick)', false, true)
|
||||
}],
|
||||
['create', 'Create a new text file', function (s) {
|
||||
discordCmdOpt(s, 'path', 'New file under ~ or /tmp (omit to pick)', false, true)
|
||||
}],
|
||||
['upload', 'Upload an attachment; wget saves it in the /r cwd or path', function (s) {
|
||||
discordCmdAtt(s, 'file', 'File to upload (Discord CDN, then wget into the VFS)', true)
|
||||
discordCmdOpt(s, 'path', 'VFS dest (file or dir; default: /r cwd + name)', false, true)
|
||||
}],
|
||||
['open-file', 'Send a VFS file as a Discord attachment (kept)', function (s) {
|
||||
discordCmdOpt(s, 'path', 'VFS file to attach (uploaded to the Discord CDN)', true, true)
|
||||
}]
|
||||
]) &&
|
||||
discordCmdAddSubs(net, [
|
||||
['peers', 'Swarm peers'],
|
||||
['swarm', 'Swarm snapshot'],
|
||||
['summary', 'net_summary']
|
||||
]) &&
|
||||
discordCmdAddSubs(plugins, [
|
||||
['list', 'Loaded plugins'],
|
||||
['reload', 'Rescan ~/.discord/plugins'],
|
||||
@@ -7920,41 +8130,13 @@ function discordBuildSlashCommands(dj, ctx) {
|
||||
['stop', 'Abort the in-flight turn']
|
||||
])
|
||||
if (!ok) {
|
||||
return [ping.toJSON()]
|
||||
const ping = new B2().setName('ping').setDescription('Reply pong')
|
||||
return [discordStampUserInstallCommand(ping.toJSON(), ctx)]
|
||||
}
|
||||
discordCmdOpt(man, 'page', 'Command name (e.g. uname)', true, true)
|
||||
discordCmdOpt(say, 'text', 'Text to box (omit to open a form)', false, false)
|
||||
discordCmdOpt(run, 'cmd', 'Shell line (cd, pipes, &&, redirects, $VAR)', true, true)
|
||||
discordCmdOpt(journal, 'unit', 'Unit name', false, true)
|
||||
discordCmdOpt(edit, 'path', 'File under ~ or /tmp (omit to pick)', false, true)
|
||||
discordCmdOpt(create, 'path', 'New file under ~ or /tmp (omit to pick)', false, true)
|
||||
discordCmdOpt(files, 'path', 'Directory to open', false, true)
|
||||
discordCmdAtt(upload, 'file', 'File to upload (Discord CDN, then wget into the VFS)', true)
|
||||
discordCmdOpt(upload, 'path', 'VFS dest (file or dir; default: /r cwd + name)', false, true)
|
||||
discordCmdOpt(openFile, 'path', 'VFS file to attach (uploaded to the Discord CDN)', true, true)
|
||||
discordCmdOpt(status, 'activity', 'playing, watching, listening, competing, custom, streaming', false, false, {
|
||||
choices: [
|
||||
['Playing', 'playing'],
|
||||
['Watching', 'watching'],
|
||||
['Listening', 'listening'],
|
||||
['Competing', 'competing'],
|
||||
['Custom', 'custom'],
|
||||
['Streaming', 'streaming']
|
||||
]
|
||||
})
|
||||
discordCmdOpt(status, 'name', 'Activity text shown in the member list', false, false, {
|
||||
min: 1,
|
||||
max: 128
|
||||
})
|
||||
discordCmdOpt(status, 'state', 'online, idle, dnd, or invisible', false, false, {
|
||||
choices: [
|
||||
['Online', 'online'],
|
||||
['Idle', 'idle'],
|
||||
['Do not disturb', 'dnd'],
|
||||
['Invisible', 'invisible']
|
||||
]
|
||||
})
|
||||
const stock = [bare, sys, svc, fsCmd, net, man, say, run, journal, edit, create, files, settings, plugins, panel, ping, status, upload, openFile, hdms, holesail, agent].map(
|
||||
const stock = [bare, sys, svc, files, man, say, run, settings, plugins, panel, hdms, holesail, agent].map(
|
||||
function (c) {
|
||||
return discordStampUserInstallCommand(c.toJSON(), ctx)
|
||||
}
|
||||
@@ -8012,15 +8194,40 @@ function discordShowModal(interaction, spec) {
|
||||
return discordShowForm(interaction, spec)
|
||||
}
|
||||
|
||||
function discordCmdHandleEdit(opt) {
|
||||
const path = opt('path')
|
||||
if (!path) return { modal: 'edit:path' }
|
||||
return { edit: path }
|
||||
}
|
||||
|
||||
function discordCmdHandleCreate(opt) {
|
||||
const path = opt('path')
|
||||
if (!path) return { createPick: true }
|
||||
return { create: path }
|
||||
}
|
||||
|
||||
async function discordRouteCommand(ctx, name, sub, opt) {
|
||||
if (name === 'panel') return discordPanel(ctx)
|
||||
if (name === 'ping' || (name === 'bare' && (sub === 'ping' || !sub))) {
|
||||
return discordCmdHandleBare(ctx, 'ping')
|
||||
}
|
||||
if (name === 'bare') return discordCmdHandleBare(ctx, sub)
|
||||
if (name === 'sys') return discordCmdHandleSys(ctx, sub)
|
||||
if (name === 'svc') return discordCmdHandleSvc(ctx, sub, opt('unit'))
|
||||
if (name === 'fs') return discordCmdHandleFs(ctx, sub, opt('path'), opt('lines'))
|
||||
if (name === 'bare') {
|
||||
if (sub === 'presence') return discordCmdHandlePresence(ctx, opt)
|
||||
return discordCmdHandleBare(ctx, sub)
|
||||
}
|
||||
if (name === 'sys') {
|
||||
if (sub === 'peers' || sub === 'swarm' || sub === 'summary') {
|
||||
return discordCmdHandleNet(ctx, sub)
|
||||
}
|
||||
return discordCmdHandleSys(ctx, sub)
|
||||
}
|
||||
if (name === 'svc') {
|
||||
if (sub === 'journal') return discordCmdHandleJournal(ctx, opt('unit'))
|
||||
return discordCmdHandleSvc(ctx, sub, opt('unit'))
|
||||
}
|
||||
if (name === 'fs' || (name === 'files' && (sub === 'ls' || sub === 'cat' || sub === 'stat' || sub === 'head'))) {
|
||||
return discordCmdHandleFs(ctx, sub, opt('path'), opt('lines'))
|
||||
}
|
||||
if (name === 'net') return discordCmdHandleNet(ctx, sub)
|
||||
if (name === 'man') return discordCmdHandleMan(ctx, opt('page'))
|
||||
if (name === 'say') {
|
||||
@@ -8035,16 +8242,8 @@ async function discordRouteCommand(ctx, name, sub, opt) {
|
||||
}
|
||||
if (name === 'r' || name === 'run') return { run: opt('cmd') || '' }
|
||||
if (name === 'journal') return discordCmdHandleJournal(ctx, opt('unit'))
|
||||
if (name === 'edit') {
|
||||
const path = opt('path')
|
||||
if (!path) return { modal: 'edit:path' }
|
||||
return { edit: path }
|
||||
}
|
||||
if (name === 'create') {
|
||||
const path = opt('path')
|
||||
if (!path) return { createPick: true }
|
||||
return { create: path }
|
||||
}
|
||||
if (name === 'edit' || (name === 'files' && sub === 'edit')) return discordCmdHandleEdit(opt)
|
||||
if (name === 'create' || (name === 'files' && sub === 'create')) return discordCmdHandleCreate(opt)
|
||||
if (name === 'files' || name === 'browse') {
|
||||
return { files: opt('path') || '' }
|
||||
}
|
||||
@@ -8110,7 +8309,7 @@ async function discordEditSaveFromModal(ctx, interaction) {
|
||||
const rec = discordEditGet(interaction)
|
||||
if (!rec || !rec.path) {
|
||||
return {
|
||||
text: 'Edit session expired (15 minutes). Run `/edit` again.',
|
||||
text: 'Edit session expired (15 minutes). Run `/files edit` again.',
|
||||
ephemeral: true
|
||||
}
|
||||
}
|
||||
@@ -8134,7 +8333,7 @@ async function discordEditSaveFromModal(ctx, interaction) {
|
||||
const body = parts.join('')
|
||||
if (rec.created && (await discordFileExists(ctx, p))) {
|
||||
return {
|
||||
text: '`' + p + '` already exists. Use `/edit` to change it.',
|
||||
text: '`' + p + '` already exists. Use `/files edit` to change it.',
|
||||
ephemeral: true
|
||||
}
|
||||
}
|
||||
@@ -10080,12 +10279,24 @@ async function discordDispatchAutocomplete(ctx, interaction) {
|
||||
else if (fname === 'cmd') choices = await discordSuggestRun(ctx, interaction, q)
|
||||
else if (fname === 'path') {
|
||||
const cmd = String(interaction.commandName || '')
|
||||
choices =
|
||||
cmd === 'edit' || cmd === 'create' || cmd === 'upload'
|
||||
? discordSuggestEditPaths(ctx, q)
|
||||
: cmd === 'hdms'
|
||||
? await discordSuggestHdmsLabels(ctx, q)
|
||||
: discordSuggestPaths(ctx, q)
|
||||
let sub = ''
|
||||
try {
|
||||
if (interaction.options && typeof interaction.options.getSubcommand === 'function') {
|
||||
sub = String(interaction.options.getSubcommand(false) || '')
|
||||
}
|
||||
} catch {
|
||||
sub = ''
|
||||
}
|
||||
const writePath =
|
||||
cmd === 'edit' ||
|
||||
cmd === 'create' ||
|
||||
cmd === 'upload' ||
|
||||
(cmd === 'files' && (sub === 'edit' || sub === 'create' || sub === 'upload'))
|
||||
choices = writePath
|
||||
? discordSuggestEditPaths(ctx, q)
|
||||
: cmd === 'hdms'
|
||||
? await discordSuggestHdmsLabels(ctx, q)
|
||||
: discordSuggestPaths(ctx, q)
|
||||
} else if (fname === 'label' && String(interaction.commandName || '') === 'hdms') {
|
||||
choices = await discordSuggestHdmsLabels(ctx, q)
|
||||
} else if (fname === 'id' && String(interaction.commandName || '') === 'holesail') {
|
||||
@@ -10924,7 +11135,7 @@ function discordDmHelpResult() {
|
||||
discordField('reset / !reset / !new', 'Clear chat history (keeps config)'),
|
||||
discordField('stop / !stop', 'Abort the in-flight turn'),
|
||||
discordField('ping', 'Pong'),
|
||||
discordField('/agent /status', 'Slash commands still work in this DM'),
|
||||
discordField('/agent /bare presence', 'Slash commands still work in this DM'),
|
||||
discordField(
|
||||
'Agent reach-out',
|
||||
'The agent can DM you first on this channel (`discord_send_message`). Same whitelist.'
|
||||
@@ -11091,9 +11302,12 @@ async function discordDispatchInteraction(ctx, interaction) {
|
||||
await discordPluginsEnsure(ctx)
|
||||
if (name === 'plugins') {
|
||||
result = await discordPluginsAdmin(ctx, interaction, sub, opt)
|
||||
} else if (name === 'upload') {
|
||||
} else if (name === 'upload' || (name === 'files' && sub === 'upload')) {
|
||||
result = await discordCmdHandleUpload(ctx, interaction, opt('path'))
|
||||
} else if (name === 'open-file') {
|
||||
} else if (
|
||||
name === 'open-file' ||
|
||||
(name === 'files' && (sub === 'open-file' || sub === 'attach'))
|
||||
) {
|
||||
result = await discordCmdHandleOpenFile(ctx, interaction, opt('path'))
|
||||
} else if (name === 'hdms') {
|
||||
result = await discordCmdHandleHdms(ctx, interaction, sub, opt)
|
||||
@@ -11214,6 +11428,7 @@ var bareOsDiscordCommands = {
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = bareOsDiscordCommands
|
||||
}
|
||||
|
||||
/**
|
||||
* Stock Discord ping-pong bot via ctx.bare.discordJS (vendored bare-discord-js).
|
||||
* Token: --token, DISCORD_TOKEN, --env PATH, DISCORD_ENV_FILE, ~/.discord.env.
|
||||
@@ -11382,10 +11597,10 @@ function discordUsage(argv0) {
|
||||
'host DISCORD_ENV_FILE, ~/.discord/.env, ~/.discord.env).\n' +
|
||||
'Ctrl+C stops the foreground bot. Initd unit bare-os-discord appears in\n' +
|
||||
'systemctl only when ~/.discord/.env exists with DISCORD_TOKEN=.\n' +
|
||||
'Slash commands: /panel /files /edit /create /upload /open-file /hdms /holesail /agent /status /bare /sys /svc /fs /net /man /say /r /journal /ping.\n' +
|
||||
'Slash commands: /panel /bare /sys /svc /files /man /say /r /settings /plugins /hdms /holesail /agent.\n' +
|
||||
'Commands run as the unlocked session user. Options use Discord autocomplete.\n' +
|
||||
'Whitelisted users can DM the bot to talk to /bin/agent (same ~/.agent session).\n' +
|
||||
'/status sets playing / watching / listening / competing in the member list.\n' +
|
||||
'/bare presence sets playing / watching / listening / competing in the member list.\n' +
|
||||
'Channel "ping" still replies pong when Message Content Intent is enabled.\n' +
|
||||
'User-installable (profile app) is on by default so operators can use slash\n' +
|
||||
'commands in DMs and any server. DISCORD_ID_WHITELIST is always enforced on\n' +
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function duBytes(ctx, path, followSymlinks, seen) {
|
||||
const mark = bareOsNormPath(bareOsResolvePath(ctx, path))
|
||||
const vis = seen || new Set()
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const parts = argv.slice(1)
|
||||
let n = false
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/** ANSI helpers for /bin/edit (preamble; no import in src). */
|
||||
|
||||
const EDIT_ANSI_RESET = '\x1b[0m'
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse leading NAME=value assignments; first non-assignment starts the utility argv.
|
||||
* @param {string[]} rest
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/** Drive-resident exit: ends the booter session (ctx.requestBooterExit from bare-os-booter). */
|
||||
async function run(ctx, argv) {
|
||||
let ec = 0
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} tabArg
|
||||
* @returns {number[]}
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const tokens = argv.slice(1)
|
||||
if (!tokens.length || tokens[0] === '--help' || tokens[0] === '-h') {
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
function factorOne(n) {
|
||||
const out = []
|
||||
let x = n
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* curDepth is distance from the search root directory (0 at the initial path).
|
||||
* Printed paths use gnu-like depth curDepth + 1 (immediate children of the root are depth 1).
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let width = 75
|
||||
const paths = []
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let width = 80
|
||||
const paths = []
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* getfacl — show POSIX-style ACL text for a VFS path.
|
||||
* When PATH.bare_acl exists, prints that file; otherwise synthesizes user/group/other
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const sub = argv[1]
|
||||
if (sub === 'help' || sub === '--help' || sub === '-h' || sub === undefined) {
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Subset of POSIX/GNU grep behavior using JavaScript RegExp / string search.
|
||||
* Not bit-identical to GNU grep (no PCRE, different escaping, UTF-16 strings).
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
if (argv[i] === '-h' || argv[i] === '--help') {
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
if (argv[1] === 'ls') argv = [argv[0], 'list', ...argv.slice(2)]
|
||||
if (typeof ctx.runHdms === 'function') {
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* head — first lines or bytes of files.
|
||||
*/
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
var BARE_OS_HELP_BIN_SPACED = "agent appctl appstore arch awk baresay baretop base32 base64 basename basenc btop bundlebee cat chat chgrp chmod chown cksum clear cmp comm corestorectl cp crontab ctxbaredoctor curl cut date dd df dhtctl dhtscan dhttop diff dir dircolors dirname discord-bot du echo edit env exit expand expr factor false find fmt fold getconf getfacl git git-pear grep groups hdms head help holepunch-view holesail hostid hostname hrpc hypershell-board iconv id init-discord install irc join journalctl jq kernel-boot-diff kernel-doctor kernel-explain kernel-fsck kernel-home-snapshot kernel-manifest-validate kernel-preflight kernel-triage kill link ln locale logger login logname logout ls man md5sum meshdrop mkdir mkfifo mktemp mount mv nano nice nl nohup nproc numfmt od oidc-publish openssl openssl p2ping p2ptrace paste patch pathcap-verify pathchk pear pear-runtime-matrix peerctl peerdiscover peernote pkg-swarm-index pr printenv printf procstat ps pwd readlink realpath rev rm rmdir routeview savevault say sed seq setfacl sh sha1sum sha224sum sha256sum sha384sum sha512sum shuf sidecar sleep sort split ssh ssh-keygen ssh-keygen sshd sshd stat sum summon swarmdoctor swarmmap swarmtop sync systemctl tac tail tar tar taskmesh tee telnet test theme time timeout touch tr true truncate trustctl tsort tty tui ulimit umount uname unexpand uniq unlink uptime users vdir wc wget which who whoami whois xargs xattr yes"
|
||||
async function run(ctx, argv) {
|
||||
ctx.console.log(
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/** ANSI helpers for /bin/edit (preamble; no import in src). */
|
||||
|
||||
const EDIT_ANSI_RESET = '\x1b[0m'
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/* BARE_OS_BIN_API 1.0.0 — delegates to booter holesail-cli when ctx exposes bareOsRunHolesailCli. */
|
||||
async function run(ctx, argv) {
|
||||
if (typeof ctx.bareOsRunHolesailCli === 'function') {
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
if (argv[i] === '-h' || argv[i] === '--help') {
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const env = bareOsEnv(ctx)
|
||||
let h = env.HOSTNAME || env.COMPUTERNAME || 'bare-os'
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
const BARE_P2P_SUITE_PREFIX = '[bare-p2p-v1]'
|
||||
const BARE_P2P_SUITE_MAX_HISTORY = 2000
|
||||
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal iconv(1): UTF-8 pass-through only (Bare userland text is UTF-8).
|
||||
*/
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const e = ctx.vfs.env
|
||||
const u = e.USER || e.LOGNAME || 'guest'
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Interactive Discord bot setup: ~/.discord/.env + hello-world plugin.
|
||||
*/
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let mode = null
|
||||
const rest = []
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/** IRC message codec + casemap (modern.ircdocs.horse). No import. */
|
||||
|
||||
var BARE_IRC_MAX_LINE = 512
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function readLines(ctx, path) {
|
||||
const b4 = ctx.b4a
|
||||
if (path === '-') return bareStdin(ctx).split('\n')
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
// Bare OS: vendored from @sscots/[email protected] (https://github.com/sscots/jqjs). ISC License. Top-level compile() and prettyPrint(); not the C jq binary (https://github.com/jqlang/jq).
|
||||
// Triage: treat bug reports as jqjs semantic gaps; prefer golden tests before expanding builtins. Large JSON is bounded by host memory and stdin string caps (see handbook §3).
|
||||
// jqjs - jq JSON query language in JavaScript
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function readUtf8(ctx, path) {
|
||||
const vfs = ctx.vfs
|
||||
if (!vfs || typeof vfs.readFile !== 'function') {
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function readProcJson(ctx, p) {
|
||||
const vfs = ctx.vfs
|
||||
if (!vfs || typeof vfs.readFile !== 'function') return null
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/** Map common failure tokens to documentation anchors (offline-friendly). */
|
||||
var KERNEL_EXPLAIN_MAP = {
|
||||
boot_policy: 'docs/reference/kernel-extensions.md — BARE_OS_BOOT_POLICY',
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const vfs = ctx.vfs
|
||||
const b4 = ctx.b4a
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk a home directory on the VFS and emit a bounded manifest (no archive bytes).
|
||||
*/
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const { drive, b4a, console } = ctx
|
||||
if (!drive || typeof drive.get !== 'function' || !b4a) {
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function readProcJson(ctx, p) {
|
||||
const vfs = ctx.vfs
|
||||
if (!vfs || typeof vfs.readFile !== 'function') return null
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let json = false
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve job spec %n / %% to synthetic PID (4100+job.id) for ctx.bareOsSendSignal.
|
||||
* Matches fg/wait job selection: %% is latest non-done job.
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* POSIX `link` utility — delegates to `ctx.bareOsSyscall('link', …)` (copy-on-Hyperdrive semantics).
|
||||
*/
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
function bareParentLogical(p) {
|
||||
const t = p.replace(/\/+$/, '') || '/'
|
||||
if (t === '/') return '/'
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const args = argv.slice(1)
|
||||
if (!args.length || args.includes('-a')) {
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
function parseLoggerArgv(argv) {
|
||||
let tag = 'logger'
|
||||
let prio = 'user.notice'
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Plain-stream masked line input (same pattern as agent --config / bareAgentPromptSetupLine).
|
||||
* Used by /bin/login so the Fish REPL is suspended and stdin/stdout are the TTY streams.
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const u = ctx.vfs.env.LOGNAME || ctx.vfs.env.USER || 'guest'
|
||||
ctx.console.log(u)
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const save = argv.includes('--save')
|
||||
const logout = ctx.applyLogout
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* GNU-style LS_COLORS string + dircolors(5) database parsing and file classification.
|
||||
* Consumed by bare-os-booter (ESM import). Prepended into /bin/ls and /bin/dircolors by bare-os-coreutils build (no import there).
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/** Plain-text manual formatter (prepended before src/man.js; no import in /bin/man). */
|
||||
|
||||
const BARE_MAN_WIDTH_MIN = 40
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
const BARE_P2P_SUITE_PREFIX = '[bare-p2p-v1]'
|
||||
const BARE_P2P_SUITE_MAX_HISTORY = 2000
|
||||
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
function parseModeOctal(s) {
|
||||
const t = String(s).trim()
|
||||
const n = Number.parseInt(t, 8)
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const path = argv[1]
|
||||
if (!path) {
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let wantDir = false
|
||||
/** @type {string | null} */
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function printMountTable(ctx) {
|
||||
try {
|
||||
const buf = await ctx.vfs.readFile('/proc/mounts')
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Move/rename via copy + delete. Hyperdrive has no single-key rename across paths, so
|
||||
* directory trees and cross-location moves are duplicated then removed. A single regular
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/** ANSI helpers for /bin/edit (preamble; no import in src). */
|
||||
|
||||
const EDIT_ANSI_RESET = '\x1b[0m'
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let inc = 10
|
||||
let i = 1
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const vfs = ctx.vfs
|
||||
let bodyType = 'a'
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a command; SIGHUP is not delivered in Bare OS (single process). Output may be redirected.
|
||||
*/
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
function fmtIec(n, si) {
|
||||
const base = si ? 1000 : 1024
|
||||
const units = si
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
function hexByte(b) {
|
||||
return b.toString(16).padStart(2, '0')
|
||||
}
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Thin helper for OIDC-style token exchange workflows (Holepunch oidc-publishing patterns).
|
||||
* Uses ctx.httpFetch when present and host HTTP policy allows the issuer URL.
|
||||
|
||||
@@ -311,6 +311,132 @@ function bareOsHexEncode(u8) {
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string[]} argv
|
||||
* @param {{
|
||||
* cmd: string,
|
||||
* help?: string,
|
||||
* digest: (u8: Uint8Array) => string | Promise<string>
|
||||
* }} opts
|
||||
*/
|
||||
async function bareChecksumRun(ctx, argv, opts) {
|
||||
const cmd = String(opts.cmd || 'checksum')
|
||||
const help =
|
||||
opts.help ||
|
||||
'usage: ' +
|
||||
cmd +
|
||||
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
|
||||
const digest = opts.digest
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(help)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error(cmd + ': unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await digest(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error(cmd + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error(cmd + ': ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
|
||||
* @param {string} algo
|
||||
* @param {string} missingMsg
|
||||
* @returns {(u8: Uint8Array) => Promise<string>}
|
||||
*/
|
||||
function bareSubtleHexDigest(algo, missingMsg) {
|
||||
return async function (u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error(missingMsg)
|
||||
}
|
||||
const hash = await subtle.digest(algo, u8)
|
||||
return bareOsHexEncode(new Uint8Array(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner/group name → uid/gid used by chown / chgrp.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseGroupSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return null
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
const g = (env.GROUP || env.USER || 'guest').trim()
|
||||
if (s === 'root') return 0
|
||||
if (s === 'guest' || s === 'nobody') return 65534
|
||||
if (g && s === g) {
|
||||
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
|
||||
return Number.isFinite(gid) ? gid : 65534
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* User name / numeric uid used by chown.
|
||||
* @param {string} spec
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {{ uid: number | null, gid: number | null }}
|
||||
*/
|
||||
function parseIdSpec(spec, env) {
|
||||
const s = String(spec).trim()
|
||||
if (!s) return { uid: null, gid: null }
|
||||
if (/^\d+$/.test(s)) {
|
||||
const n = Number.parseInt(s, 10)
|
||||
return { uid: Number.isFinite(n) ? n : null, gid: null }
|
||||
}
|
||||
const u = (env.USER || env.LOGNAME || '').trim()
|
||||
if (s === 'root') return { uid: 0, gid: null }
|
||||
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
|
||||
if (u && s === u) {
|
||||
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
|
||||
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
|
||||
}
|
||||
return { uid: null, gid: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenSSL CLI compatibility via booter `openssl-cli.js` (bare-crypto).
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user