Update man to add the manual guide

This commit is contained in:
Raven Scott
2026-04-03 05:28:49 -04:00
parent 11cac45df6
commit 2d26a0b8d1
174 changed files with 17092 additions and 198 deletions
+1376
View File
File diff suppressed because it is too large Load Diff
+68
View File
@@ -0,0 +1,68 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function run(ctx, argv) {
ctx.console.error(
'chgrp: changing group is not supported on Bare OS (single-user Hyperdrive metadata).'
)
ctx.exitCode = 1
}
+56 -8
View File
@@ -60,23 +60,71 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
function bareChmodSymbolic(curPerm, spec) {
let m = curPerm & 0o777
const parts = spec.split(',')
for (const raw of parts) {
const part = raw.trim()
if (!part) continue
let who = 0
let i = 0
while (i < part.length && 'augo'.includes(part[i])) {
const c = part[i++]
if (c === 'a') who |= 0o777
if (c === 'u') who |= 0o700
if (c === 'g') who |= 0o070
if (c === 'o') who |= 0o007
}
if (!who) who = 0o777
const op = part[i++]
if (!op || !'+-='.includes(op)) continue
const rest = part.slice(i)
let bits = 0
let wantX = false
for (const ch of rest) {
if (ch === 'r') bits |= who & 0o444
if (ch === 'w') bits |= who & 0o222
if (ch === 'x' || ch === 's' || ch === 't') bits |= who & 0o111
if (ch === 'X') wantX = true
}
if (wantX && (m & 0o111)) bits |= who & 0o111
if (op === '=') m = (m & ~who) | bits
else if (op === '+') m |= bits
else if (op === '-') m &= ~bits
}
return m
}
async function run(ctx, argv) {
const modeStr = argv[1]
const files = argv.slice(2)
if (!modeStr || !files.length) {
ctx.console.error('usage: chmod OCTAL_MODE FILE...')
ctx.exitCode = 1
return
}
const mode = Number.parseInt(String(modeStr), 8)
if (!Number.isFinite(mode) || mode < 0) {
ctx.console.error('chmod: invalid mode')
ctx.console.error('usage: chmod MODE FILE...\n MODE is octal (e.g. 644) or symbolic (e.g. u+rw)')
ctx.exitCode = 1
return
}
const sym = !/^[0-7]+$/.test(modeStr)
for (const f of files) {
try {
await ctx.vfs.chmod(f, mode)
let perm
if (sym) {
const st = await ctx.vfs.stat(f)
if (!st) {
ctx.console.error('chmod: ' + f + ': No such file')
ctx.exitCode = 1
continue
}
perm = bareChmodSymbolic(st.mode & 0o777, modeStr)
} else {
perm = Number.parseInt(String(modeStr), 8)
if (!Number.isFinite(perm) || perm < 0) {
ctx.console.error('chmod: invalid mode')
ctx.exitCode = 1
return
}
perm &= 0o777
}
await ctx.vfs.chmod(f, perm)
} catch (e) {
ctx.console.error('chmod: ' + f + ': ' + (e.message || e))
ctx.exitCode = 1
+68
View File
@@ -0,0 +1,68 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function run(ctx, argv) {
ctx.console.error(
'chown: changing file ownership is not supported on Bare OS (single-user Hyperdrive metadata).'
)
ctx.exitCode = 1
}
+146
View File
@@ -0,0 +1,146 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/** POSIX cksum CRC (Open Group / npm `cksum` reference). */
const BARE_CKSUM_TAB = new Uint32Array([
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7,
0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3,
0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef,
0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb,
0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4,
0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08,
0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc,
0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050,
0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1,
0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5,
0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9,
0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd,
0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2,
0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e,
0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a,
0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676,
0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
])
function barePosixCksum(u8) {
let crc = 0
for (let i = 0; i < u8.length; i++) {
crc =
(BARE_CKSUM_TAB[(u8[i] ^ ((crc >>> 24) & 0xff))] ^ (crc << 8)) >>> 0
}
let n = u8.length
while (n > 0) {
crc =
(BARE_CKSUM_TAB[((n & 0xff) ^ ((crc >>> 24) & 0xff))] ^ (crc << 8)) >>> 0
n >>>= 8
}
return (~crc >>> 0)
}
async function run(ctx, argv) {
const files = argv.slice(1).filter((a) => a && !a.startsWith('-'))
async function one(name, u8) {
const sum = barePosixCksum(u8)
ctx.console.log(sum + ' ' + u8.length + ' ' + name)
}
if (!files.length) {
const buf = ctx.b4a.from(bareStdin(ctx))
await one('', buf instanceof Uint8Array ? buf : new Uint8Array(buf))
return
}
for (const f of files) {
const buf = await ctx.vfs.readFile(f)
if (!buf) {
ctx.console.error('cksum: ' + f + ': No such file')
ctx.exitCode = 1
continue
}
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
await one(f, u8)
}
}
+148
View File
@@ -0,0 +1,148 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function copyPath(ctx, from, to, recursive) {
const st = await ctx.vfs.lstat(from)
if (!st) {
ctx.console.error('cp: ' + from + ': No such file')
return false
}
if (st.type === 'symlink') {
const t = await ctx.vfs.readlink(from)
await ctx.vfs.symlink(t, to)
return true
}
if (st.type === 'file') {
const buf = await ctx.vfs.readFile(from)
if (!buf) {
ctx.console.error('cp: cannot read ' + from)
return false
}
await ctx.vfs.writeFile(to, buf)
return true
}
if (st.type === 'directory') {
if (!recursive) {
ctx.console.error('cp: ' + from + ': Is a directory')
return false
}
await ctx.vfs.mkdir(to, { recursive: true })
const names = await ctx.vfs.readdir(from)
for (const n of names) {
if (n === '.bareos_empty') continue
const f = from.replace(/\/+$/, '') + '/' + n
const t = to.replace(/\/+$/, '') + '/' + n
if (!(await copyPath(ctx, f, t, true))) return false
}
return true
}
return false
}
async function run(ctx, argv) {
let recursive = false
const paths = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-R' || a === '-r' || a === '--recursive') {
recursive = true
continue
}
if (a === '--') {
paths.push(...argv.slice(i + 1))
break
}
if (a.startsWith('-')) {
ctx.console.error('cp: unsupported option ' + a)
ctx.exitCode = 1
return
}
paths.push(a)
}
if (paths.length < 2) {
ctx.console.error('usage: cp [-R] SOURCE... DEST')
ctx.exitCode = 1
return
}
const dest = paths.pop()
const sources = paths
let destIsDir = false
try {
const dst = await ctx.vfs.lstat(dest)
destIsDir = !!(dst && dst.type === 'directory')
} catch {
destIsDir = false
}
if (sources.length > 1 && !destIsDir) {
ctx.console.error('cp: target is not a directory')
ctx.exitCode = 1
return
}
for (const src of sources) {
const base = src.replace(/\/+$/, '').split('/').pop() || src
const target =
destIsDir || sources.length > 1
? dest.replace(/\/+$/, '') + '/' + base
: dest
if (!(await copyPath(ctx, src, target, recursive))) ctx.exitCode = 1
}
}
+142
View File
@@ -0,0 +1,142 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function run(ctx, argv) {
let delim = '\t'
let fieldsSpec = ''
const files = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-d') {
delim = argv[++i] || ''
continue
}
if (a === '-f') {
fieldsSpec = argv[++i] || ''
continue
}
if (a === '--') {
files.push(...argv.slice(i + 1))
break
}
if (a.startsWith('-')) {
ctx.console.error('cut: unsupported option ' + a)
ctx.exitCode = 1
return
}
files.push(a)
}
if (!fieldsSpec) {
ctx.console.error('usage: cut -f LIST [-d DELIM] [FILE...]')
ctx.exitCode = 1
return
}
function parseFields(s) {
/** @type {Set<number>} */
const set = new Set()
for (const part of s.split(',')) {
const m = /^(\d+)(?:-(\d+))?$/.exec(part.trim())
if (!m) continue
const a = Number(m[1])
const b = m[2] != null ? Number(m[2]) : a
for (let j = a; j <= b; j++) set.add(j)
}
return set
}
const want = parseFields(fieldsSpec)
if (!want.size) {
ctx.console.error('cut: invalid field list')
ctx.exitCode = 1
return
}
async function cutLines(text) {
const lines = text.split(/\r?\n/)
for (const line of lines) {
if (line === '' && lines.length === 1) continue
const cols =
delim === ''
? [...line]
: line.split(delim === '\\t' ? '\t' : delim)
const out = []
for (const n of [...want].sort((a, b) => a - b)) {
out.push(cols[n - 1] != null ? cols[n - 1] : '')
}
ctx.console.log(out.join(delim === '\\t' ? '\t' : delim))
}
}
if (!files.length) {
await cutLines(bareStdin(ctx))
return
}
for (const f of files) {
const buf = await ctx.vfs.readFile(f)
if (!buf) {
ctx.console.error('cut: ' + f + ': No such file')
ctx.exitCode = 1
continue
}
await cutLines(ctx.b4a.toString(buf))
}
}
+102
View File
@@ -0,0 +1,102 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function duPath(ctx, path, blockSize) {
const st = await ctx.vfs.lstat(path)
if (!st) return 0
if (st.type === 'file' || st.type === 'symlink') {
return Math.ceil((st.size || 0) / blockSize) || 1
}
let total = Math.ceil((st.size || 0) / blockSize) || 1
let names = []
try {
names = await ctx.vfs.readdir(path)
} catch {
return total
}
for (const n of names) {
if (n === '.bareos_empty') continue
const sub = path.replace(/\/+$/, '') + '/' + n
total += await duPath(ctx, sub, blockSize)
}
return total
}
async function run(ctx, argv) {
let blockSize = 512
const paths = []
for (let i = 1; i < argv.length; i++) {
if (argv[i] === '-k') blockSize = 1024
else if (argv[i] !== '--') paths.push(argv[i])
}
const targets = paths.length ? paths : ['.']
for (const p of targets) {
try {
const abs = ctx.vfs.resolveLogical(p)
const kb = await duPath(ctx, abs, blockSize)
ctx.console.log(String(kb) + '\t' + p)
} catch (e) {
ctx.console.error('du: ' + p + ': ' + ((e && e.message) || e))
ctx.exitCode = 1
}
}
}
+131
View File
@@ -0,0 +1,131 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function walk(ctx, dir, nameRe, wantType, maxDepth, curDepth) {
if (maxDepth >= 0 && curDepth > maxDepth) return
let names
try {
names = await ctx.vfs.readdir(dir)
} catch {
return
}
for (const n of names) {
if (n === '.bareos_empty') continue
const path = dir.replace(/\/+$/, '') + '/' + n
let st
try {
st = await ctx.vfs.lstat(path)
} catch {
continue
}
if (!st) continue
if (!nameRe || nameRe.test(n)) {
if (!wantType || st.type === wantType) ctx.console.log(path)
}
if (st.type === 'directory') await walk(ctx, path, nameRe, wantType, maxDepth, curDepth + 1)
}
}
async function run(ctx, argv) {
let maxDepth = -1
/** @type {string | null} */
let nameGlob = null
/** @type {'file' | 'directory' | 'symlink' | null} */
let wantType = null
const rest = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-maxdepth' && argv[i + 1]) {
maxDepth = Number.parseInt(argv[++i], 10)
continue
}
if (a === '-name' && argv[i + 1]) {
nameGlob = argv[++i]
continue
}
if (a === '-type' && argv[i + 1]) {
const t = argv[++i]
if (t === 'f') wantType = 'file'
else if (t === 'd') wantType = 'directory'
else if (t === 'l') wantType = 'symlink'
continue
}
if (a === '--') {
rest.push(...argv.slice(i + 1))
break
}
if (a.startsWith('-')) {
ctx.console.error('find: unsupported ' + a)
ctx.exitCode = 1
return
}
rest.push(a)
}
const root = rest[0] || '.'
let nameRe = null
if (nameGlob) {
const esc = nameGlob.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.')
nameRe = new RegExp('^' + esc + '$')
}
const abs = ctx.vfs.resolveLogical(root)
await walk(ctx, abs, nameRe, wantType, maxDepth, 0)
}
+72
View File
@@ -0,0 +1,72 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function run(ctx, argv) {
const name = argv[1]
if (!name) {
ctx.console.error('usage: getconf VARIABLE_NAME')
ctx.exitCode = 1
return
}
ctx.console.error('getconf: not implemented on Bare OS (no full sysconf path): ' + name)
ctx.exitCode = 1
}
+4 -1
View File
@@ -62,7 +62,10 @@ function barePosixBlocks(size) {
async function run(ctx, argv) {
ctx.console.log(
'Bare OS — default user: guest | builtins: alias, cd, export, exit, login, logout, unalias | /bin: basename cat chmod clear crontab date dirname echo env exit false git grep head hdms help hostname id login logout ls nl pathchk printenv pwd rm savevault seq sleep sort tail test touch true tty uname wc which whoami'
'Bare OS — default user: guest | shell builtins: alias, cd, command, export, exit, login, logout, readonly, type, umask, unalias, unset, : | /bin: awk basename cat chgrp chmod chown cksum clear cp crontab cut date dirname du echo env exit false find getconf grep head hdms help hostname id ln login logout logname ls man mkdir mkfifo mv nl od pathchk printenv printf pwd readlink rm rmdir savevault sed seq sleep sort stat tail tee test time touch tr true tty uname wc which whoami xargs'
)
ctx.console.log(
'Docs: man <command> | man handbook (full handbook, section 7) | man bare-os-shell | man -k <word> | man -l'
)
ctx.console.log(
'Identity: login [--new] <passphrase> | logout [--save] | savevault (encrypt copy of personal drive under /.bare/vault/)'
+99
View File
@@ -0,0 +1,99 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function run(ctx, argv) {
let sym = false
const rest = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-s' || a === '--symbolic') {
sym = true
continue
}
if (a === '--') {
rest.push(...argv.slice(i + 1))
break
}
if (a.startsWith('-')) {
ctx.console.error('ln: unsupported option ' + a)
ctx.exitCode = 1
return
}
rest.push(a)
}
if (!sym) {
ctx.console.error('ln: only symbolic links (-s) are supported on Bare OS')
ctx.exitCode = 1
return
}
if (rest.length !== 2) {
ctx.console.error('usage: ln -s TARGET LINK_NAME')
ctx.exitCode = 1
return
}
try {
await ctx.vfs.symlink(rest[0], rest[1])
} catch (e) {
ctx.console.error('ln: ' + ((e && e.message) || e))
ctx.exitCode = 1
}
}
+66
View File
@@ -0,0 +1,66 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function run(ctx, argv) {
const u = ctx.vfs.env.LOGNAME || ctx.vfs.env.USER || 'guest'
ctx.console.log(u)
}
+440
View File
@@ -0,0 +1,440 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/** Plain-text manual formatter (prepended before src/man.js; no import in /bin/man). */
function bareManParseWidth(env) {
const raw = env && env.MANWIDTH != null ? String(env.MANWIDTH).trim() : ''
const n = raw ? Number.parseInt(raw, 10) : 72
if (!Number.isFinite(n)) return 72
return Math.max(40, Math.min(n, 200))
}
function bareManUseAnsi(ctx) {
const env = ctx.env || {}
if (env.NO_COLOR != null && String(env.NO_COLOR) !== '') return false
const out = ctx.stdout
return Boolean(out && out.isTTY)
}
function bareManBold(s, on) {
if (!on) return s
return '\x1b[1m' + s + '\x1b[0m'
}
function bareManWrap(text, width) {
const words = String(text).replace(/\s+/g, ' ').trim().split(' ')
const lines = []
let cur = ''
for (const w of words) {
const next = cur ? cur + ' ' + w : w
if (next.length <= width) cur = next
else {
if (cur) lines.push(cur)
cur = w.length > width ? w.slice(0, width) : w
while (cur.length > width) {
lines.push(cur.slice(0, width))
cur = cur.slice(width)
}
}
}
if (cur) lines.push(cur)
return lines
}
/** Indent fixed-width command lines; hard-wrap only when longer than width. */
function bareManRenderExampleCode(code, width) {
const indent = ' '
const max = Math.max(20, width - indent.length)
const out = []
for (const line of String(code).split('\n')) {
if (line.length <= max) {
out.push(indent + line)
continue
}
let rest = line
while (rest.length > max) {
out.push(indent + rest.slice(0, max))
rest = rest.slice(max)
}
if (rest) out.push(indent + rest)
}
return out.join('\n')
}
/** Keep newlines; hard-wrap long lines only (for handbook / preformatted text). */
function bareManRenderPreserve(text, width) {
const indent = ' '
const max = Math.max(20, width - indent.length)
const out = []
for (const line of String(text).split('\n')) {
if (line === '') {
out.push('')
continue
}
let rest = line
while (rest.length > max) {
out.push(indent + rest.slice(0, max))
rest = rest.slice(max)
}
out.push(indent + rest)
}
return out.join('\n') + '\n'
}
function bareManFlushBlock(lines, width, prefixFirst, prefixRest) {
const out = []
let first = true
for (const line of lines) {
const wrapped = bareManWrap(line, width - (first ? prefixFirst.length : prefixRest.length))
for (let i = 0; i < wrapped.length; i++) {
const p = i === 0 && first ? prefixFirst : prefixRest
out.push(p + wrapped[i])
first = false
}
}
return out.join('\n') + '\n'
}
function bareManRenderPage(page, ctx, width) {
const ansi = bareManUseAnsi(ctx)
const H = (s) => bareManBold(s, ansi) + '\n'
let s = ''
s += H('NAME')
s += page.name + '(' + page.section + ') - ' + page.title + '\n\n'
s += H('SYNOPSIS')
for (const line of page.synopsis) {
s += ' ' + line + '\n'
}
s += '\n'
s += H('DESCRIPTION')
if (page.descriptionMode === 'preserve') {
s += bareManRenderPreserve(page.description, width)
} else {
s += bareManFlushBlock([page.description], width, '', ' ')
}
if (page.options && page.options.length) {
s += '\n' + H('OPTIONS')
for (const o of page.options) {
const head = o.flag + '\t'
const rest = o.meaning
s += bareManFlushBlock([rest], width, ' ' + head, ' ')
}
}
if (page.examples && page.examples.length) {
s += '\n' + H('EXAMPLES')
s +=
bareManFlushBlock(
[
'tl;dr-style snippets (like cheat.sh). Copy, adapt paths; pipelines are shell-simulated on Bare OS.'
],
width,
'',
' '
) + '\n'
for (const ex of page.examples) {
if (ex.caption) {
s +=
bareManFlushBlock(
['# ' + ex.caption],
width,
'',
' '
) + '\n'
}
s += bareManRenderExampleCode(ex.code, width) + '\n\n'
}
}
if (page.environment && page.environment.length) {
s += '\n' + H('ENVIRONMENT')
for (const e of page.environment) s += ' ' + e + '\n'
}
if (page.files && page.files.length) {
s += '\n' + H('FILES')
for (const f of page.files) s += ' ' + f + '\n'
}
if (page.exitStatus && page.exitStatus.length) {
s += '\n' + H('EXIT STATUS')
for (const e of page.exitStatus) s += ' ' + e + '\n'
}
if (page.diagnostics && page.diagnostics.length) {
s += '\n' + H('DIAGNOSTICS')
for (const d of page.diagnostics) s += ' ' + d + '\n'
}
if (page.builtins && page.builtins.length) {
s += '\n' + H('SHELL BUILTINS')
for (const b of page.builtins) {
s += '\n' + bareManBold(b.name, ansi) + '\n'
if (b.synopsis && b.synopsis.length) {
for (const line of b.synopsis) s += ' ' + line + '\n'
}
s += bareManFlushBlock([b.description], width, ' ', ' ')
if (b.options && b.options.length) {
for (const o of b.options) {
const head = o.flag + '\t'
s += bareManFlushBlock([o.meaning], width, ' ' + head, ' ')
}
}
if (b.examples && b.examples.length) {
s += '\n' + bareManBold(' Examples', ansi) + '\n'
for (const ex of b.examples) {
if (ex.caption) {
s +=
bareManFlushBlock(
['# ' + ex.caption],
width,
' ',
' '
) + '\n'
}
s += bareManRenderExampleCode(ex.code, width) + '\n\n'
}
}
}
}
if (page.bareOsNotes) {
s += '\n' + H('BARE OS NOTES')
s += bareManFlushBlock([page.bareOsNotes], width, '', ' ')
}
if (page.seeAlso && page.seeAlso.length) {
s += '\n' + H('SEE ALSO')
const parts = page.seeAlso.map((r) => r.name + '(' + r.section + ')')
s += ' ' + parts.join(', ') + '\n'
}
if (page.stub) {
s += '\n' + H('STATUS')
s += ' This command is a stub or intentionally limited on Bare OS.\n'
}
return s
}
async function run(ctx, argv) {
const env = ctx.env || {}
const width = bareManParseWidth(env)
const args = argv.slice(1)
function usage() {
ctx.console.error(
'usage: man [-k keyword] [-f name] [-l] [[section] name]\n' +
' Section 1: /bin utilities; section 7: Bare OS handbook (man 7 bare-os-handbook).\n' +
' Data: /share/man/man.json on the system drive.'
)
ctx.exitCode = 2
}
if (args.length === 0) {
usage()
return
}
let mode = 'page'
let kWord = ''
let fName = ''
const rest = []
for (let i = 0; i < args.length; i++) {
const a = args[i]
if (a === '--') {
rest.push(...args.slice(i + 1))
break
}
if (a === '-k' || a === '--apropos') {
mode = 'apropos'
kWord = args[++i] || ''
if (!kWord) {
usage()
return
}
continue
}
if (a === '-f' || a === '--whatis') {
mode = 'whatis'
fName = args[++i] || ''
if (!fName) {
usage()
return
}
continue
}
if (a === '-l' || a === '--list') {
mode = 'list'
continue
}
if (a.startsWith('-')) {
ctx.console.error('man: unknown option: ' + a)
ctx.exitCode = 2
return
}
rest.push(a)
}
const drive = ctx.drive
if (!drive || typeof drive.get !== 'function') {
ctx.console.error('man: no system drive in context')
ctx.exitCode = 1
return
}
let buf
try {
buf = await drive.get('/share/man/man.json', { follow: true })
} catch {
buf = null
}
if (!buf) {
ctx.console.error('man: manual database not found (/share/man/man.json)')
ctx.exitCode = 1
return
}
let db
try {
db = JSON.parse(ctx.b4a.toString(buf))
} catch (e) {
ctx.console.error('man: invalid manual database: ' + (e.message || e))
ctx.exitCode = 1
return
}
if (!db.pages || !db.index) {
ctx.console.error('man: malformed manual database')
ctx.exitCode = 1
return
}
if (mode === 'list') {
const rows = db.pages
.map((p) => ({ n: p.name, s: p.section }))
.sort((a, b) => (a.n !== b.n ? (a.n < b.n ? -1 : 1) : a.s - b.s))
for (const r of rows) ctx.console.log(r.n + '(' + r.s + ')')
return
}
if (mode === 'apropos') {
const needle = kWord.toLowerCase()
const seen = new Set()
const hits = []
if (db.apropos && Array.isArray(db.apropos)) {
for (const row of db.apropos) {
if (typeof row.kw !== 'string') continue
if (!row.kw.includes(needle)) continue
const idx = row.pageRef
if (typeof idx !== 'number' || !db.pages[idx]) continue
if (seen.has(idx)) continue
seen.add(idx)
const p = db.pages[idx]
hits.push(p.name + '(' + p.section + ') - ' + p.title)
}
}
hits.sort()
for (const line of hits) ctx.console.log(line)
return
}
if (mode === 'whatis') {
const key = fName.toLowerCase()
const idx = db.index[key]
if (idx === undefined || !db.pages[idx]) {
ctx.console.error('man: nothing appropriate for ' + fName)
ctx.exitCode = 1
return
}
const p = db.pages[idx]
ctx.console.log(p.name + '(' + p.section + ') - ' + p.title)
return
}
if (rest.length === 0) {
usage()
return
}
/** @type {number | null} */
let sectionExplicit = null
let name = rest[0]
if (rest.length >= 2 && /^[0-9]+$/.test(rest[0])) {
sectionExplicit = Number.parseInt(rest[0], 10)
name = rest[1]
}
if (sectionExplicit !== null && (sectionExplicit < 1 || sectionExplicit > 8)) {
ctx.console.error('man: section must be between 1 and 8')
ctx.exitCode = 2
return
}
const idx = db.index[String(name).toLowerCase()]
if (idx === undefined || !db.pages[idx]) {
ctx.console.error('man: no manual entry for ' + name)
ctx.exitCode = 1
return
}
const page = db.pages[idx]
if (sectionExplicit !== null && page.section !== sectionExplicit) {
ctx.console.error(
'man: no entry for ' + name + ' in section ' + sectionExplicit + ' (see man -l)'
)
ctx.exitCode = 1
return
}
ctx.console.log(bareManRenderPage(page, ctx, width).replace(/\n$/, ''))
}
+96
View File
@@ -0,0 +1,96 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function run(ctx, argv) {
let parents = false
const paths = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-p' || a === '--parents') {
parents = true
continue
}
if (a === '--') {
paths.push(...argv.slice(i + 1))
break
}
if (a.startsWith('-')) {
ctx.console.error('mkdir: unsupported option ' + a)
ctx.exitCode = 1
return
}
paths.push(a)
}
if (!paths.length) {
ctx.console.error('usage: mkdir [-p] DIRECTORY...')
ctx.exitCode = 1
return
}
for (const p of paths) {
try {
await ctx.vfs.mkdir(p, { recursive: parents })
} catch (e) {
ctx.console.error('mkdir: ' + p + ': ' + ((e && e.message) || e))
ctx.exitCode = 1
}
}
}
+66
View File
@@ -0,0 +1,66 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function run(ctx, argv) {
ctx.console.error('mkfifo: FIFOs are not supported in this JavaScript VFS.')
ctx.exitCode = 1
}
+128
View File
@@ -0,0 +1,128 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function mvCopyPath(ctx, from, to, recursive) {
const st = await ctx.vfs.lstat(from)
if (!st) return false
if (st.type === 'symlink') {
await ctx.vfs.symlink(await ctx.vfs.readlink(from), to)
return true
}
if (st.type === 'file') {
const buf = await ctx.vfs.readFile(from)
if (!buf) return false
await ctx.vfs.writeFile(to, buf)
return true
}
if (st.type === 'directory') {
if (!recursive) return false
await ctx.vfs.mkdir(to, { recursive: true })
const names = await ctx.vfs.readdir(from)
for (const n of names) {
if (n === '.bareos_empty') continue
const f = from.replace(/\/+$/, '') + '/' + n
const t = to.replace(/\/+$/, '') + '/' + n
if (!(await mvCopyPath(ctx, f, t, true))) return false
}
return true
}
return false
}
async function run(ctx, argv) {
const paths = argv.slice(1).filter((a) => a && a !== '--')
if (paths.length < 2) {
ctx.console.error('usage: mv SOURCE... DEST')
ctx.exitCode = 1
return
}
const dest = paths.pop()
const sources = paths
let destIsDir = false
try {
const dst = await ctx.vfs.lstat(dest)
destIsDir = !!(dst && dst.type === 'directory')
} catch {
destIsDir = false
}
if (sources.length > 1 && !destIsDir) {
ctx.console.error('mv: target is not a directory')
ctx.exitCode = 1
return
}
for (const src of sources) {
const base = src.replace(/\/+$/, '').split('/').pop() || src
const target =
destIsDir || sources.length > 1
? dest.replace(/\/+$/, '') + '/' + base
: dest
try {
if (!(await mvCopyPath(ctx, src, target, true))) {
throw new Error('cannot copy')
}
await ctx.vfs.rm(src, { recursive: true, force: true })
} catch (e) {
ctx.console.error('mv: ' + src + ': ' + ((e && e.message) || e))
ctx.exitCode = 1
}
}
}
+94
View File
@@ -0,0 +1,94 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
function hexByte(b) {
return b.toString(16).padStart(2, '0')
}
async function run(ctx, argv) {
const paths = argv.slice(1).filter((a) => a && !a.startsWith('-'))
let buf
if (!paths.length) {
buf = ctx.b4a.from(bareStdin(ctx))
} else {
const b = await ctx.vfs.readFile(paths[0])
if (!b) {
ctx.console.error('od: cannot read file')
ctx.exitCode = 1
return
}
buf = b
}
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
let off = 0
while (off < u8.length) {
const chunk = u8.slice(off, off + 16)
const hex = [...chunk].map((x) => hexByte(x)).join(' ')
const asc = [...chunk]
.map((x) => (x >= 32 && x < 127 ? String.fromCharCode(x) : '.'))
.join('')
ctx.console.log(
off.toString(8).padStart(7, '0') + ' ' + hex.padEnd(47, ' ') + ' |' + asc + '|'
)
off += 16
}
}
+108
View File
@@ -0,0 +1,108 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
function barePrintfFormat(fmt, args) {
let ai = 0
let o = ''
for (let i = 0; i < fmt.length; i++) {
if (fmt[i] !== '%') {
o += fmt[i]
continue
}
if (fmt[i + 1] === '%') {
o += '%'
i++
continue
}
let j = i + 1
while (j < fmt.length && /[0-9.#\-+ ]/.test(fmt[j])) j++
const spec = fmt[j] || 's'
const arg = args[ai++]
if (spec === 's') o += String(arg)
else if (spec === 'd' || spec === 'i') o += String(Math.trunc(Number(arg)))
else if (spec === 'u') o += String(Math.trunc(Number(arg)) >>> 0)
else if (spec === 'x') o += (Math.trunc(Number(arg)) >>> 0).toString(16)
else if (spec === 'X') o += (Math.trunc(Number(arg)) >>> 0).toString(16).toUpperCase()
else if (spec === 'o') o += (Math.trunc(Number(arg)) >>> 0).toString(8)
else if (spec === 'c') o += String.fromCharCode(Number(arg) || 0)
else if (spec === 'f') o += String(Number(arg))
else o += String(arg)
i = j
}
return o
}
async function run(ctx, argv) {
if (argv.length < 2) {
ctx.console.error('usage: printf FORMAT [ARG...]')
ctx.exitCode = 1
return
}
const fmt = argv[1]
const args = argv.slice(2)
let unescaped = fmt
unescaped = unescaped.replace(/\\n/g, '\n')
unescaped = unescaped.replace(/\\t/g, '\t')
unescaped = unescaped.replace(/\\r/g, '\r')
unescaped = unescaped.replace(/\\\\/g, '\\')
ctx.console.log(barePrintfFormat(unescaped, args))
}
+82
View File
@@ -0,0 +1,82 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function run(ctx, argv) {
let noNewline = false
const paths = []
for (let i = 1; i < argv.length; i++) {
if (argv[i] === '-n') noNewline = true
else if (argv[i] !== '--') paths.push(argv[i])
}
if (!paths.length) {
ctx.console.error('usage: readlink [-n] FILE')
ctx.exitCode = 1
return
}
try {
const t = await ctx.vfs.readlink(paths[0])
ctx.console.log(noNewline ? t : t)
} catch (e) {
ctx.console.error('readlink: ' + ((e && e.message) || e))
ctx.exitCode = 1
}
}
+78
View File
@@ -0,0 +1,78 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function run(ctx, argv) {
const paths = argv.slice(1).filter((a) => a && a !== '--')
if (!paths.length) {
ctx.console.error('usage: rmdir DIRECTORY...')
ctx.exitCode = 1
return
}
for (const p of paths) {
try {
await ctx.vfs.rmdir(p)
} catch (e) {
ctx.console.error('rmdir: ' + p + ': ' + ((e && e.message) || e))
ctx.exitCode = 1
}
}
}
+857
View File
@@ -0,0 +1,857 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* POSIX-oriented sed engine for Bare OS /bin/sed (no import; concatenated before src/sed.js).
* Covers: -n -e -f -E, addresses (#,$,/re/,n,m,n~s), s///[ngp0-9], y///, d D p P n N,
* h H g G x, b t :, q, r w, =, l, a i c (backslash forms), comments, hold space, line continuations.
*/
/**
* @param {string} delim
* @param {string} script
* @param {number} start
* @returns {{ raw: string, end: number } | null}
*/
function bareSedReadDelimited(delim, script, start) {
if (delim === '\n' || delim === '') return null
let i = start
let out = ''
while (i < script.length) {
const c = script[i]
if (c === '\\' && i + 1 < script.length) {
out += script[i + 1]
i += 2
continue
}
if (c === delim) return { raw: out, end: i + 1 }
out += c
i++
}
return null
}
/**
* @param {string} reStr
* @param {boolean} extended
*/
function bareSedCompileRegex(reStr, extended) {
let flags = extended ? 'u' : 'u'
let body = reStr
if (extended) {
body = body
.replace(/\(\?#[^)]*\)/g, '')
.replace(/\(\?:/g, '(')
.replace(/\+/g, '{1,}')
.replace(/\?/g, '{0,1}')
}
try {
return new RegExp(body, flags)
} catch {
return /$^/
}
}
/**
* @param {string} repl
* @param {string[]} caps
* @param {string} match
* @param {string} line
* @param {number} off
*/
function bareSedApplyReplacement(repl, caps, match, line, off) {
let o = ''
for (let i = 0; i < repl.length; i++) {
const c = repl[i]
if (c === '&') {
o += match
continue
}
if (c === '\\' && i + 1 < repl.length) {
const n = repl[i + 1]
if (n >= '1' && n <= '9') {
o += caps[Number(n)] || ''
i++
continue
}
if (n === '&') {
o += '&'
i++
continue
}
if (n === '\\') {
o += '\\'
i++
continue
}
o += n
i++
continue
}
o += c
}
return o
}
function bareSedListLine(s) {
let o = ''
for (let i = 0; i < s.length; i++) {
const code = s.charCodeAt(i)
if (code === 10) o += '\\n'
else if (code === 9) o += '\\t'
else if (code === 92) o += '\\\\'
else if (code < 32 || code > 126) o += '\\' + code.toString(8).padStart(3, '0')
else o += s[i]
}
return o + '$'
}
/**
* @typedef {{ type: string, [k: string]: unknown }} BareSedCmd
*/
/**
* @param {string} script
* @param {boolean} extended
* @returns {BareSedCmd[]}
*/
function bareSedParseScript(script, extended) {
/** @type {BareSedCmd[]} */
const cmds = []
let i = 0
const len = script.length
function skipWs() {
while (i < len && /[ \t\r]/.test(script[i])) i++
}
function skipCommentLine() {
while (i < len && script[i] !== '\n') i++
if (i < len && script[i] === '\n') i++
}
function readAddr() {
skipWs()
if (i >= len) return null
const c = script[i]
if (c === '#') {
skipCommentLine()
return 'skip'
}
if (c === '0' && script[i + 1] >= '1' && script[i + 1] <= '9') {
/* fall through to number */
} else if (c >= '1' && c <= '9') {
let n = 0
while (i < len && script[i] >= '0' && script[i] <= '9') {
n = n * 10 + (script[i].charCodeAt(0) - 48)
i++
}
return { kind: 'num', n }
}
if (c === '$') {
i++
return { kind: 'last' }
}
if (c === '/' || c === '\\') {
let delim = c
let start = i + 1
if (c === '\\') {
delim = script[i + 1] || '/'
start = i + 2
}
const got = bareSedReadDelimited(delim, script, start)
if (!got) throw new Error('sed: unterminated address regex')
i = got.end
return { kind: 're', rx: bareSedCompileRegex(got.raw, extended) }
}
return null
}
function readAddrPair() {
const a = readAddr()
if (a === 'skip' || a === null) return a
skipWs()
if (i < len && script[i] === ',') {
i++
const b = readAddr()
if (b === 'skip' || b === null) throw new Error('sed: invalid address')
return { kind: 'range', a, b }
}
if (i < len && script[i] === '~') {
i++
let step = 0
while (i < len && script[i] >= '0' && script[i] <= '9') {
step = step * 10 + (script[i].charCodeAt(0) - 48)
i++
}
if (step < 1) step = 1
return { kind: 'step', a, step }
}
return a
}
while (i < len) {
skipWs()
if (i >= len) break
if (script[i] === '#' || (script[i] === '\n' && (i++, false))) {
if (script[i - 1] === '#') skipCommentLine()
else continue
continue
}
if (script[i] === ';') {
i++
continue
}
if (script[i] === '\n') {
i++
continue
}
let neg = false
if (script[i] === '!') {
neg = true
i++
skipWs()
}
const addr1 = readAddrPair()
if (addr1 === 'skip') continue
skipWs()
if (i >= len) break
const ch = script[i]
if (ch === '#') {
skipCommentLine()
continue
}
if (ch === ':') {
i++
let lab = ''
while (i < len && /[A-Za-z0-9_]/.test(script[i])) lab += script[i++]
cmds.push({ type: 'label', name: lab, neg, addr: addr1 })
continue
}
if (ch === 'b' || ch === 't') {
const ty = ch
i++
skipWs()
let lab = ''
while (i < len && /[A-Za-z0-9_]/.test(script[i])) lab += script[i++]
cmds.push({ type: ty, label: lab, neg, addr: addr1 })
continue
}
if (ch === 'r' || ch === 'w') {
const ty = ch
i++
skipWs()
let path = ''
while (i < len && script[i] !== '\n' && script[i] !== ';') path += script[i++]
path = path.replace(/[ \t]+$/, '')
cmds.push({ type: ty === 'r' ? 'readFile' : 'writeFile', path, neg, addr: addr1 })
continue
}
if (ch === 'a' || ch === 'i' || ch === 'c') {
const ty = ch
i++
skipWs()
if (i < len && script[i] === '\\') i++
let text = ''
while (i < len && script[i] !== '\n') text += script[i++]
if (i < len && script[i] === '\n') i++
while (i < len && script[i] === '\\') {
i++
let cont = ''
while (i < len && script[i] !== '\n') cont += script[i++]
text += '\n' + cont
if (i < len && script[i] === '\n') i++
}
cmds.push({ type: ty === 'a' ? 'append' : ty === 'i' ? 'insert' : 'change', text, neg, addr: addr1 })
continue
}
if (ch === 's') {
i++
const delim = script[i++]
const pat = bareSedReadDelimited(delim, script, i)
if (!pat) throw new Error('sed: unterminated s command')
i = pat.end
const rep = bareSedReadDelimited(delim, script, i)
if (!rep) throw new Error('sed: unterminated s replacement')
i = rep.end
/** @type {{ g?: boolean, p?: boolean, n?: number }} */
const fl = {}
while (i < len && /[gpn0-9]/.test(script[i])) {
const f = script[i++]
if (f === 'g') fl.g = true
else if (f === 'p') fl.p = true
else if (f >= '1' && f <= '9') fl.n = Number(f)
}
cmds.push({
type: 'subst',
rx: bareSedCompileRegex(pat.raw, extended),
rep: rep.raw,
flags: fl,
neg,
addr: addr1
})
continue
}
if (ch === 'y') {
i++
const delim = script[i++]
const from = bareSedReadDelimited(delim, script, i)
if (!from) throw new Error('sed: unterminated y')
i = from.end
const to = bareSedReadDelimited(delim, script, i)
if (!to) throw new Error('sed: unterminated y')
i = to.end
if (from.raw.length !== to.raw.length) throw new Error('sed: y strings must be same length')
cmds.push({ type: 'y', from: from.raw, to: to.raw, neg, addr: addr1 })
continue
}
const map = {
d: 'del',
D: 'delFirst',
p: 'print',
P: 'printFirst',
n: 'nextLine',
N: 'appendNext',
h: 'hold',
H: 'holdAppend',
g: 'get',
G: 'getAppend',
x: 'swap',
q: 'quit',
l: 'list',
'=': 'lineNum'
}
const ty = map[ch]
if (ty) {
i++
let count = 1
if (ty === 'quit' && i < len && script[i] >= '0' && script[i] <= '9') {
count = 0
while (i < len && script[i] >= '0' && script[i] <= '9') {
count = count * 10 + (script[i].charCodeAt(0) - 48)
i++
}
}
cmds.push({ type: ty, neg, addr: addr1, quitCode: count })
continue
}
throw new Error('sed: unknown command: ' + ch)
}
return cmds
}
/**
* @param {unknown} addr
* @param {number} lineNo
* @param {number} lastLine
* @param {string} ps
*/
function bareSedAddrSimple(addr, lineNo, lastLine, ps) {
if (addr == null) return true
if (typeof addr === 'object' && addr.kind === 'num') return lineNo === addr.n
if (typeof addr === 'object' && addr.kind === 'last') return lineNo === lastLine
if (typeof addr === 'object' && addr.kind === 're') {
addr.rx.lastIndex = 0
return addr.rx.test(ps)
}
if (typeof addr === 'object' && addr.kind === 'step') {
const an = addr.a
if (typeof an === 'object' && an.kind === 'num') {
return lineNo >= an.n && (lineNo - an.n) % addr.step === 0
}
return false
}
return false
}
/**
* @param {unknown} addr
* @param {number} lineNo
* @param {number} lastLine
* @param {string} ps
* @param {Map<number, { active: boolean }>} rangeStates
* @param {number} cmdIndex
*/
function bareSedAddrMatchFull(addr, lineNo, lastLine, ps, rangeStates, cmdIndex) {
if (addr == null) return true
if (typeof addr === 'object' && addr.kind === 'range') {
const a = addr.a
const b = addr.b
if (typeof a === 'object' && a.kind === 'num' && typeof b === 'object' && b.kind === 'num') {
return lineNo >= a.n && lineNo <= b.n
}
if (typeof a === 'object' && a.kind === 'num' && typeof b === 'object' && b.kind === 'last') {
return lineNo >= a.n && lineNo <= lastLine
}
if (typeof a === 'object' && a.kind === 'last' && typeof b === 'object' && b.kind === 'num') {
return lineNo >= lastLine && lineNo <= b.n
}
if (typeof a === 'object' && a.kind === 're' && typeof b === 'object' && b.kind === 're') {
let st = rangeStates.get(cmdIndex)
if (!st) {
st = { active: false }
rangeStates.set(cmdIndex, st)
}
a.rx.lastIndex = 0
b.rx.lastIndex = 0
const hitA = a.rx.test(ps)
const hitB = b.rx.test(ps)
if (!st.active && hitA) st.active = true
const inRange = st.active
if (st.active && hitB) st.active = false
return inRange
}
if (typeof a === 'object' && a.kind === 'num' && typeof b === 'object' && b.kind === 're') {
let st = rangeStates.get(cmdIndex)
if (!st) {
st = { active: false }
rangeStates.set(cmdIndex, st)
}
if (lineNo === a.n) st.active = true
b.rx.lastIndex = 0
const hitB = b.rx.test(ps)
const inRange = st.active
if (st.active && hitB) st.active = false
return inRange
}
if (typeof a === 'object' && a.kind === 're' && typeof b === 'object' && b.kind === 'num') {
let st = rangeStates.get(cmdIndex)
if (!st) {
st = { active: false }
rangeStates.set(cmdIndex, st)
}
a.rx.lastIndex = 0
if (!st.active && a.rx.test(ps)) st.active = true
const inRange = st.active
if (st.active && lineNo >= b.n) st.active = false
return inRange
}
return false
}
return bareSedAddrSimple(addr, lineNo, lastLine, ps)
}
function bareSedMatchAddr(addr, neg, lineNo, lastLine, ps, rangeStates, cmdIndex) {
const m = bareSedAddrMatchFull(addr, lineNo, lastLine, ps, rangeStates, cmdIndex)
return neg ? !m : m
}
/**
* @param {string[]} lines
* @param {string[]} scripts
* @param {{ silent?: boolean, extended?: boolean, readFile?: (p: string) => string | null, writeFile?: (p: string, chunk: string) => void, lastLineHint?: number }} opts
* @returns {string}
*/
function bareSedRun(lines, scripts, opts) {
const silent = !!opts.silent
const extended = !!opts.extended
const readF = opts.readFile || (() => null)
const writeF = opts.writeFile || (() => {})
const fullScript = scripts.join('\n')
const cmds = bareSedParseScript(fullScript.replace(/\\\n/g, ''), extended)
/** @type {Record<string, number>} */
const labels = {}
for (let ci = 0; ci < cmds.length; ci++) {
if (cmds[ci].type === 'label') labels[/** @type {string} */ (cmds[ci].name)] = ci
}
const lastLine = opts.lastLineHint != null ? opts.lastLineHint : lines.length
/** @type {string[]} */
const out = []
let hold = ''
let quit = 0
let lastSubst = false
/** @type {Map<number, { active: boolean }>} */
const rangeStates = new Map()
function emit(s) {
out.push(s)
}
let lineIdx = 0
while (lineIdx < lines.length && quit === 0) {
let ps = lines[lineIdx]
const lineNo = lineIdx + 1
let autoPrint = !silent
let delLine = false
let nextRead = false
let ci = 0
while (ci < cmds.length && quit === 0) {
const cmd = cmds[ci]
if (cmd.type === 'label') {
ci++
continue
}
const addr = cmd.addr
if (!bareSedMatchAddr(addr, !!cmd.neg, lineNo, lastLine, ps, rangeStates, ci)) {
ci++
continue
}
switch (cmd.type) {
case 'subst': {
lastSubst = false
const rx = /** @type {RegExp} */ (cmd.rx)
const rep = /** @type {string} */ (cmd.rep)
const fl = /** @type {{ g?: boolean, p?: boolean, n?: number }} */ (cmd.flags)
let count = 0
let res = ''
let pos = 0
const g = !!fl.g
const wantN = fl.n != null ? fl.n : g ? Infinity : 1
let replCount = 0
rx.lastIndex = 0
let m
const str = ps
while ((m = rx.exec(str)) && replCount < wantN) {
res += str.slice(pos, m.index)
const caps = m.map((x) => (x == null ? '' : String(x)))
res += bareSedApplyReplacement(rep, caps, m[0], str, m.index)
pos = m.index + m[0].length
count++
replCount++
lastSubst = true
if (!g) break
if (m[0].length === 0) {
rx.lastIndex++
if (rx.lastIndex > str.length) break
}
}
if (count) {
ps = res + str.slice(pos)
if (fl.p) emit(ps + '\n')
}
break
}
case 'y': {
const from = /** @type {string} */ (cmd.from)
const to = /** @type {string} */ (cmd.to)
const map = {}
for (let j = 0; j < from.length; j++) map[from[j]] = to[j]
let ns = ''
for (let j = 0; j < ps.length; j++) ns += map[ps[j]] != null ? map[ps[j]] : ps[j]
ps = ns
break
}
case 'del':
delLine = true
autoPrint = false
break
case 'delFirst': {
const nl = ps.indexOf('\n')
if (nl === -1) {
delLine = true
autoPrint = false
} else ps = ps.slice(nl + 1)
ci = -1
break
}
case 'print':
emit(ps + '\n')
break
case 'printFirst': {
const nl = ps.indexOf('\n')
emit((nl === -1 ? ps : ps.slice(0, nl)) + '\n')
break
}
case 'nextLine':
if (autoPrint && !silent) emit(ps + '\n')
lineIdx++
nextRead = true
ci = cmds.length
break
case 'appendNext':
lineIdx++
if (lineIdx < lines.length) ps += '\n' + lines[lineIdx]
else delLine = true
break
case 'hold':
hold = ps
break
case 'holdAppend':
hold += (hold ? '\n' : '') + ps
break
case 'get':
ps = hold
break
case 'getAppend':
ps += '\n' + hold
break
case 'swap': {
const t = ps
ps = hold
hold = t
break
}
case 'quit':
if (autoPrint && !silent) emit(ps + '\n')
quit = /** @type {number} */ (cmd.quitCode) || 0
break
case 'list':
emit(bareSedListLine(ps) + '\n')
break
case 'lineNum':
emit(String(lineNo) + '\n')
break
case 'readFile': {
const text = readF(/** @type {string} */ (cmd.path))
if (text) emit(text.endsWith('\n') ? text : text + '\n')
break
}
case 'writeFile':
writeF(/** @type {string} */ (cmd.path), ps + '\n')
break
case 'append':
emit(/** @type {string} */ (cmd.text) + '\n')
break
case 'insert':
/* handled as emit before line — approximated by prepending to output before autoPrint */
out.push(/** @type {string} */ (cmd.text) + '\n')
break
case 'change':
autoPrint = false
emit(/** @type {string} */ (cmd.text) + '\n')
delLine = true
break
case 'b': {
const lab = /** @type {string} */ (cmd.label)
if (lab && labels[lab] != null) ci = labels[lab]
break
}
case 't': {
if (lastSubst) {
const lab = /** @type {string} */ (cmd.label)
if (lab && labels[lab] != null) ci = labels[lab]
lastSubst = false
}
break
}
default:
break
}
ci++
if (delLine) break
if (nextRead) break
}
if (quit) break
if (nextRead) continue
if (!delLine && autoPrint) emit(ps + '\n')
lineIdx++
}
return out.join('')
}
async function run(ctx, argv) {
let silent = false
let extended = false
/** @type {string[]} */
const scripts = []
/** @type {string[]} */
const files = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-n' || a === '--quiet' || a === '--silent') {
silent = true
continue
}
if (a === '-E' || a === '-r') {
extended = true
continue
}
if (a === '-e') {
scripts.push(argv[++i] || '')
continue
}
if (a === '-f') {
const path = argv[++i]
if (!path) {
ctx.console.error('sed: -f requires a file')
ctx.exitCode = 1
return
}
const buf = await ctx.vfs.readFile(path)
if (!buf) {
ctx.console.error('sed: cannot read ' + path)
ctx.exitCode = 1
return
}
scripts.push(ctx.b4a.toString(buf))
continue
}
if (a === '--') {
files.push(...argv.slice(i + 1))
break
}
if (a.startsWith('-') && a.length > 1) {
ctx.console.error('sed: unsupported option ' + a)
ctx.exitCode = 1
return
}
scripts.push(a)
files.push(...argv.slice(i + 1))
break
}
if (!scripts.length) {
ctx.console.error('usage: sed [-n] [-E] {-e script | -f file} [file...]')
ctx.exitCode = 1
return
}
function bareSedCollectRPaths(scList) {
const set = new Set()
const s = scList.join('\n')
const m0 = /^\s*r\s+([^\n;]+)/.exec(s)
if (m0) set.add(m0[1].trim())
let i = 0
while (i < s.length) {
const j = s.indexOf('\nr', i)
const k = s.indexOf(';r', i)
let hit = -1
if (j >= 0 && (k < 0 || j <= k)) hit = j + 1
else if (k >= 0) hit = k + 1
if (hit < 0) break
let p = hit + 1
while (p < s.length && /[ \t]/.test(s[p])) p++
let end = p
while (end < s.length && s[end] !== '\n' && s[end] !== ';') end++
const path = s.slice(p, end).trim()
if (path) set.add(path)
i = end
}
return [...set]
}
/** @type {Record<string, string>} */
const readCache = Object.create(null)
for (const rp of bareSedCollectRPaths(scripts)) {
const buf = await ctx.vfs.readFile(rp)
readCache[rp] = buf ? ctx.b4a.toString(buf) : ''
}
/** @type {string[]} */
const lines = []
async function pushFile(path) {
const buf = await ctx.vfs.readFile(path)
if (!buf) {
ctx.console.error('sed: ' + path + ': No such file')
ctx.exitCode = 1
return false
}
const t = ctx.b4a.toString(buf)
const ls = t.split(/\r?\n/)
if (ls.length && ls[ls.length - 1] === '') ls.pop()
lines.push(...ls)
return true
}
if (!files.length) {
const s = bareStdin(ctx)
const ls = s.split(/\r?\n/)
if (ls.length && ls[ls.length - 1] === '') ls.pop()
lines.push(...ls)
} else {
for (const f of files) {
if (!(await pushFile(f))) return
}
}
/** @type {Record<string, string>} */
const wAccum = Object.create(null)
const out = bareSedRun(lines, scripts, {
silent,
extended,
readFile: (p) => readCache[p] ?? null,
writeFile: (p, chunk) => {
wAccum[p] = (wAccum[p] || '') + chunk
},
lastLineHint: lines.length
})
for (const [p, data] of Object.entries(wAccum)) {
try {
const prev = await ctx.vfs.readFile(p)
const merged = prev ? ctx.b4a.concat([prev, ctx.b4a.from(data)]) : ctx.b4a.from(data)
await ctx.vfs.writeFile(p, merged)
} catch (e) {
ctx.console.error('sed: ' + p + ': ' + ((e && e.message) || e))
ctx.exitCode = 1
}
}
const t = out.replace(/\n$/, '')
ctx.console.log(t)
}
+102
View File
@@ -0,0 +1,102 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function run(ctx, argv) {
const paths = argv.slice(1).filter((a) => a && !a.startsWith('-'))
if (!paths.length) {
ctx.console.error('usage: stat FILE...')
ctx.exitCode = 1
return
}
for (const p of paths) {
try {
const st = await ctx.vfs.stat(p)
if (!st) {
ctx.console.error('stat: ' + p + ': No such file')
ctx.exitCode = 1
continue
}
const modeStr = bareFormatModeString(st.mode, st.type)
ctx.console.log(
'File: ' +
p +
'\n' +
'Size: ' +
(st.size || 0) +
'\n' +
'Type: ' +
st.type +
'\n' +
'Mode: ' +
modeStr +
'\n' +
'Uid: ' +
(st.uid ?? 0) +
' Gid: ' +
(st.gid ?? 0)
)
} catch (e) {
ctx.console.error('stat: ' + p + ': ' + ((e && e.message) || e))
ctx.exitCode = 1
}
}
}
+87
View File
@@ -0,0 +1,87 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function run(ctx, argv) {
let append = false
const files = []
for (let i = 1; i < argv.length; i++) {
if (argv[i] === '-a' || argv[i] === '--append') append = true
else if (argv[i] !== '--') files.push(argv[i])
}
const text = bareStdin(ctx)
const data = ctx.b4a.from(text)
for (const f of files) {
try {
if (append) {
const prev = await ctx.vfs.readFile(f)
const merged = prev ? ctx.b4a.concat([prev, data]) : data
await ctx.vfs.writeFile(f, merged)
} else {
await ctx.vfs.writeFile(f, data)
}
} catch (e) {
ctx.console.error('tee: ' + f + ': ' + ((e && e.message) || e))
ctx.exitCode = 1
}
}
ctx.console.log(text.replace(/\n$/, ''))
}
+81
View File
@@ -0,0 +1,81 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function run(ctx, argv) {
const cmd = argv.slice(1)
if (!cmd.length) {
ctx.console.error('usage: time COMMAND [ARG...]')
ctx.exitCode = 1
return
}
const t0 = Date.now()
try {
if (typeof ctx.runBinCommand === 'function') await ctx.runBinCommand(cmd)
else {
ctx.console.error('time: runBinCommand is not available')
ctx.exitCode = 1
}
} finally {
const ms = Date.now() - t0
ctx.console.error('real\t' + (ms / 1000).toFixed(3) + 's')
}
}
+95
View File
@@ -0,0 +1,95 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function run(ctx, argv) {
let del = false
const sets = []
for (let i = 1; i < argv.length; i++) {
if (argv[i] === '-d' || argv[i] === '--delete') del = true
else if (argv[i] !== '--') sets.push(argv[i])
}
if (!sets.length || (!del && sets.length < 2)) {
ctx.console.error('usage: tr [-d] SET1 [SET2]')
ctx.exitCode = 1
return
}
const s = bareStdin(ctx)
if (del) {
const kill = new Set(sets[0].split(''))
let o = ''
for (const ch of s) if (!kill.has(ch)) o += ch
ctx.console.log(o)
return
}
const from = sets[0]
const to = sets[1]
const map = Object.create(null)
const n = Math.max(from.length, to.length)
for (let i = 0; i < n; i++) {
const fc = from[i] || from[from.length - 1]
const tc = to[i] != null ? to[i] : to[to.length - 1] || ''
map[fc] = tc
}
let o = ''
for (const ch of s) o += map[ch] != null ? map[ch] : ch
ctx.console.log(o)
}
+68
View File
@@ -0,0 +1,68 @@
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar =
type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function run(ctx, argv) {
ctx.console.error(
'xargs: running arbitrary commands from /bin/xargs is not supported in Bare OS; use the shell to expand arguments.'
)
ctx.exitCode = 1
}