Update man to add the manual guide
This commit is contained in:
+1376
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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/)'
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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$/, ''))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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$/, ''))
|
||||
}
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user