Fixed dd /dev/zero truncation in packages/bare-os-coreutils/src/dd.js
if=/dev/zero with finite count*bs now produces requested size directly (not limited by VFS 64KiB pseudo-file). Raw stdout now uses bareOsEmitRaw, improving pipeline correctness. Fixed pipeline capture path for FIFO-relevant commands echo and cat now emit via bareOsEmitRaw (packages/bare-os-coreutils/src/echo.js, packages/bare-os-coreutils/src/cat.js). Shell pipeline now injects bareOsBinWrite into child contexts when output is captured, so raw writes are captured (packages/bare-os-booter/lib/shell.js). Improved xattr behavior in packages/bare-os-coreutils/src/xattr.js Added -p/--print NAME. -w now logs written attribute name for visible success feedback. Roundtrip behaviors preserved with sidecar metadata format. Improved ACL UX in: packages/bare-os-coreutils/src/getfacl.js Explicit source header for sidecar and mode fallback (non-compact mode). packages/bare-os-coreutils/src/setfacl.js Added -m/--modify ENTRY in addition to stdin blob and -b. Implemented dynamic ulimit output in packages/bare-os-coreutils/src/ulimit.js Reads /proc/bare_os/rlimits.json when available. Supports -a, -n, -Sn, -Hn. Added shuf -e support in packages/bare-os-coreutils/src/shuf.js. Added split byte-suffix parsing (k/K/m/M/g/G) and attached -bSIZE support in packages/bare-os-coreutils/src/split.js.
This commit is contained in:
@@ -1839,6 +1839,19 @@ function bareOsPipelineChildCtx(ctx, env, stdinText, bareOsStdoutCaptured) {
|
||||
return o
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert raw binary chunks into shell pipeline capture text.
|
||||
* Pipeline transport is text-based, so bytes are mapped 1:1 via char codes.
|
||||
* @param {string | Uint8Array} chunk
|
||||
*/
|
||||
function bareOsPipelineRawChunkToText(chunk) {
|
||||
if (typeof chunk === 'string') return chunk
|
||||
if (!(chunk instanceof Uint8Array)) return String(chunk)
|
||||
let s = ''
|
||||
for (let i = 0; i < chunk.length; i++) s += String.fromCharCode(chunk[i])
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional timeout guard for potentially stalled pipeline stages.
|
||||
* @param {Record<string, string>} env
|
||||
@@ -2120,6 +2133,16 @@ async function execParsedPipeline(ctx, pipeline) {
|
||||
}
|
||||
checkCaptureSize(outChunks)
|
||||
}
|
||||
/** @param {string | Uint8Array} chunk */
|
||||
const pushOutRaw = (chunk) => {
|
||||
const text = bareOsPipelineRawChunkToText(chunk)
|
||||
outChunks.push(text)
|
||||
const st = ctx.bareOsSessionStats
|
||||
if (st && typeof st.pipelineBytesTotal === 'number') {
|
||||
st.pipelineBytesTotal += text.length
|
||||
}
|
||||
checkCaptureSize(outChunks)
|
||||
}
|
||||
const pushErr = (...args) => {
|
||||
const line = args.map(String).join(' ') + '\n'
|
||||
errChunks.push(line)
|
||||
@@ -2489,6 +2512,7 @@ async function execParsedPipeline(ctx, pipeline) {
|
||||
}
|
||||
} else {
|
||||
const childCtx = bareOsPipelineChildCtx(ctx, env, stdinText, capOut)
|
||||
if (capOut) childCtx.bareOsBinWrite = pushOutRaw
|
||||
await runWithShellPipelineStageTimeout(
|
||||
env,
|
||||
() => runBinCommand(childCtx, cargs),
|
||||
@@ -2793,6 +2817,7 @@ async function execParsedPipeline(ctx, pipeline) {
|
||||
}
|
||||
if (testArgv) {
|
||||
const childCtx = bareOsPipelineChildCtx(ctx, env, stdinText, capOut)
|
||||
if (capOut) childCtx.bareOsBinWrite = pushOutRaw
|
||||
try {
|
||||
await runWithShellPipelineStageTimeout(
|
||||
env,
|
||||
@@ -2824,6 +2849,7 @@ async function execParsedPipeline(ctx, pipeline) {
|
||||
}
|
||||
} else {
|
||||
const childCtx = bareOsPipelineChildCtx(ctx, env, stdinText, capOut)
|
||||
if (capOut) childCtx.bareOsBinWrite = pushOutRaw
|
||||
try {
|
||||
await runWithShellPipelineStageTimeout(
|
||||
env,
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
function bareCatOut(ctx, s) {
|
||||
const w = globalThis.process?.stdout?.write
|
||||
if (typeof w === 'function') {
|
||||
w.call(globalThis.process.stdout, s)
|
||||
} else {
|
||||
ctx.console.log(s)
|
||||
}
|
||||
if (bareOsEmitRaw(ctx, s)) return
|
||||
ctx.console.log(s)
|
||||
}
|
||||
|
||||
/** @param {string} line @param {{ showTabs: boolean, showEnds: boolean, showNonprinting: boolean }} o */
|
||||
|
||||
@@ -111,32 +111,49 @@ async function run(ctx, argv) {
|
||||
}
|
||||
}
|
||||
|
||||
let src
|
||||
if (opt.if) {
|
||||
src = await ctx.vfs.readFile(opt.if)
|
||||
if (!src) {
|
||||
ctx.console.error('dd: ' + opt.if + ': No such file or directory')
|
||||
const finiteCount = opt.count == null ? null : opt.count
|
||||
const wantedBytes =
|
||||
finiteCount == null ? null : Math.max(0, finiteCount * opt.bs)
|
||||
const skipBytes = Math.max(0, opt.skip * opt.bs)
|
||||
let chunk
|
||||
if (opt.if === '/dev/zero' && wantedBytes != null) {
|
||||
chunk = ctx.b4a.alloc(wantedBytes)
|
||||
} else if (opt.if === '/dev/urandom' && wantedBytes != null) {
|
||||
const raw = await ctx.vfs.readFile('/dev/urandom')
|
||||
if (!raw) {
|
||||
ctx.console.error('dd: /dev/urandom: unavailable')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const seed = raw instanceof Uint8Array ? raw : ctx.b4a.from(raw)
|
||||
chunk = ctx.b4a.alloc(wantedBytes)
|
||||
for (let i = 0; i < wantedBytes; i++) chunk[i] = seed[i % seed.length]
|
||||
} else {
|
||||
src = ctx.b4a.from(bareStdin(ctx), 'utf8')
|
||||
let src
|
||||
if (opt.if) {
|
||||
src = await ctx.vfs.readFile(opt.if)
|
||||
if (!src) {
|
||||
ctx.console.error('dd: ' + opt.if + ': No such file or directory')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
} else {
|
||||
src = ctx.b4a.from(bareStdin(ctx), 'utf8')
|
||||
}
|
||||
const input = src instanceof Uint8Array ? src : ctx.b4a.from(src)
|
||||
const start = Math.min(skipBytes, input.byteLength)
|
||||
const end =
|
||||
wantedBytes == null
|
||||
? input.byteLength
|
||||
: Math.min(input.byteLength, start + wantedBytes)
|
||||
chunk = input.subarray(start, end)
|
||||
}
|
||||
const input = src instanceof Uint8Array ? src : ctx.b4a.from(src)
|
||||
const start = Math.min(opt.skip * opt.bs, input.byteLength)
|
||||
const end =
|
||||
opt.count == null
|
||||
? input.byteLength
|
||||
: Math.min(input.byteLength, start + opt.count * opt.bs)
|
||||
let chunk = input.subarray(start, end)
|
||||
if (opt.conv.includes('sync')) {
|
||||
chunk = padToBlock(chunk, opt.bs, ctx.b4a)
|
||||
}
|
||||
|
||||
if (!opt.of) {
|
||||
const w = globalThis.process?.stdout?.write
|
||||
if (typeof w === 'function') w.call(globalThis.process.stdout, chunk)
|
||||
else ctx.console.log(ctx.b4a.toString(chunk))
|
||||
if (!bareOsEmitRaw(ctx, chunk)) ctx.console.log(ctx.b4a.toString(chunk))
|
||||
if (opt.status !== 'none') {
|
||||
ctx.console.error(`${chunk.byteLength} bytes copied`)
|
||||
}
|
||||
|
||||
@@ -6,10 +6,7 @@ async function run(ctx, argv) {
|
||||
parts.shift()
|
||||
}
|
||||
const s = parts.join(' ')
|
||||
const w = globalThis.process?.stdout?.write
|
||||
if (typeof w === 'function') {
|
||||
w.call(globalThis.process.stdout, s + (n ? '' : '\n'))
|
||||
} else {
|
||||
ctx.console.log(n ? s : s)
|
||||
}
|
||||
const out = s + (n ? '' : '\n')
|
||||
if (bareOsEmitRaw(ctx, out)) return
|
||||
ctx.console.log(s)
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ async function run(ctx, argv) {
|
||||
const raw = await ctx.vfs.readFile(side)
|
||||
if (raw && raw.byteLength) {
|
||||
const text = ctx.b4a.toString(raw)
|
||||
if (!compact) ctx.console.log('# source: ' + side)
|
||||
ctx.console.log(text.replace(/\n$/, ''))
|
||||
return
|
||||
}
|
||||
@@ -67,6 +68,7 @@ async function run(ctx, argv) {
|
||||
'# file: ' + path,
|
||||
'# owner: synthetic',
|
||||
'# group: synthetic',
|
||||
'# source: mode-fallback',
|
||||
'user::' + u,
|
||||
'group::' + g,
|
||||
'other::' + o
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
async function run(ctx, argv) {
|
||||
let clear = false
|
||||
/** @type {string[]} */
|
||||
const modify = []
|
||||
/** @type {string[]} */
|
||||
const files = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
@@ -13,9 +15,20 @@ async function run(ctx, argv) {
|
||||
clear = true
|
||||
continue
|
||||
}
|
||||
if (a === '-m' || a === '--modify') {
|
||||
const spec = argv[i + 1]
|
||||
if (!spec) {
|
||||
ctx.console.error('setfacl: -m requires ACL entry')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
modify.push(spec)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if (a === '--help' || a === '-h') {
|
||||
ctx.console.log(
|
||||
'usage: setfacl [-b] PATH\nWrites ACL text from stdin when not clearing.'
|
||||
'usage: setfacl [-b] [-m ENTRY] PATH\nWrites ACL text from stdin when not clearing.'
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -48,14 +61,43 @@ async function run(ctx, argv) {
|
||||
}
|
||||
return
|
||||
}
|
||||
const text = bareStdin(ctx)
|
||||
if (!text || !String(text).trim()) {
|
||||
ctx.console.error('setfacl: ACL text required on stdin')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
let body = ''
|
||||
if (modify.length) {
|
||||
const prev = await ctx.vfs.readFile(side)
|
||||
const lines = prev
|
||||
? ctx.b4a
|
||||
.toString(prev)
|
||||
.split(/\r?\n/)
|
||||
.map((x) => x.trim())
|
||||
.filter(Boolean)
|
||||
: []
|
||||
const byKey = new Map()
|
||||
for (const ln of lines) {
|
||||
const k = ln.split(':', 2).join(':')
|
||||
byKey.set(k, ln)
|
||||
}
|
||||
for (const m of modify) {
|
||||
const spec = String(m).trim()
|
||||
if (!spec.includes(':')) {
|
||||
ctx.console.error('setfacl: invalid ACL entry ' + spec)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const key = spec.split(':', 2).join(':')
|
||||
byKey.set(key, spec)
|
||||
}
|
||||
body = [...byKey.values()].join('\n')
|
||||
} else {
|
||||
const text = bareStdin(ctx)
|
||||
if (!text || !String(text).trim()) {
|
||||
ctx.console.error('setfacl: ACL text required on stdin')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
body = String(text)
|
||||
}
|
||||
const body = String(text).endsWith('\n') ? String(text) : String(text) + '\n'
|
||||
await ctx.vfs.writeFile(side, ctx.b4a.from(body))
|
||||
const finalBody = body.endsWith('\n') ? body : body + '\n'
|
||||
await ctx.vfs.writeFile(side, ctx.b4a.from(finalBody))
|
||||
} catch (e) {
|
||||
ctx.console.error('setfacl: ' + (e && e.message ? e.message : String(e)))
|
||||
ctx.exitCode = 1
|
||||
|
||||
@@ -28,6 +28,7 @@ function seededRandom(seed) {
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const paths = []
|
||||
let echoMode = false
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
@@ -36,6 +37,10 @@ async function run(ctx, argv) {
|
||||
)
|
||||
return
|
||||
}
|
||||
if (a === '-e') {
|
||||
echoMode = true
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('shuf: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
@@ -50,21 +55,32 @@ async function run(ctx, argv) {
|
||||
const seed = String(ctx.vfs?.env?.BARE_OS_SHUF_SEED || '').trim()
|
||||
const rand = seed ? seededRandom(seed) : Math.random
|
||||
const lines = []
|
||||
for (const p of paths) {
|
||||
const L = await readLines(ctx, p)
|
||||
if (L == null) {
|
||||
ctx.console.error('shuf: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const trim = L.length && L[L.length - 1] === '' ? L.slice(0, -1) : L
|
||||
for (const ln of trim) {
|
||||
if (echoMode) {
|
||||
for (const p of paths) {
|
||||
if (lines.length >= cap) {
|
||||
ctx.console.error('shuf: input exceeds line cap')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
lines.push(ln)
|
||||
lines.push(p)
|
||||
}
|
||||
} else {
|
||||
for (const p of paths) {
|
||||
const L = await readLines(ctx, p)
|
||||
if (L == null) {
|
||||
ctx.console.error('shuf: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const trim = L.length && L[L.length - 1] === '' ? L.slice(0, -1) : L
|
||||
for (const ln of trim) {
|
||||
if (lines.length >= cap) {
|
||||
ctx.console.error('shuf: input exceeds line cap')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
lines.push(ln)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = lines.length - 1; i > 0; i--) {
|
||||
|
||||
@@ -22,6 +22,23 @@ function splitSuffix(prefix, i) {
|
||||
return prefix + s
|
||||
}
|
||||
|
||||
function parseByteSize(v) {
|
||||
const s = String(v || '').trim()
|
||||
const m = /^(\d+)([kKmMgG]?)$/.exec(s)
|
||||
if (!m) return null
|
||||
const n = Number.parseInt(m[1], 10)
|
||||
if (!Number.isFinite(n) || n < 1) return null
|
||||
const mul =
|
||||
m[2] === 'k' || m[2] === 'K'
|
||||
? 1024
|
||||
: m[2] === 'm' || m[2] === 'M'
|
||||
? 1024 * 1024
|
||||
: m[2] === 'g' || m[2] === 'G'
|
||||
? 1024 * 1024 * 1024
|
||||
: 1
|
||||
return n * mul
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let lineCount = 1000
|
||||
let byteCount = null
|
||||
@@ -47,7 +64,13 @@ async function run(ctx, argv) {
|
||||
continue
|
||||
}
|
||||
if (a === '-b' && argv[i + 1]) {
|
||||
byteCount = parseInt(argv[++i], 10)
|
||||
byteCount = parseByteSize(argv[++i])
|
||||
lineCount = null
|
||||
if (!Number.isFinite(byteCount) || byteCount < 1) byteCount = 512
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-b') && a.length > 2) {
|
||||
byteCount = parseByteSize(a.slice(2))
|
||||
lineCount = null
|
||||
if (!Number.isFinite(byteCount) || byteCount < 1) byteCount = 512
|
||||
continue
|
||||
|
||||
@@ -11,18 +11,54 @@ const LIMITS = [
|
||||
['virtual memory', '-v', 'unlimited']
|
||||
]
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
*/
|
||||
async function readRuntimeLimits(ctx) {
|
||||
try {
|
||||
const b = await ctx.vfs.readFile('/proc/bare_os/rlimits.json')
|
||||
if (!b) return null
|
||||
const j = JSON.parse(ctx.b4a.toString(b))
|
||||
return j && typeof j === 'object' ? j : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const args = argv.slice(1).filter((a) => a !== '--')
|
||||
const rt = await readRuntimeLimits(ctx)
|
||||
const nofileSoft =
|
||||
String(
|
||||
rt?.softNoFile ??
|
||||
rt?.soft_nofile ??
|
||||
rt?.nofileSoft ??
|
||||
rt?.nofile_soft ??
|
||||
256
|
||||
) || '256'
|
||||
const nofileHard =
|
||||
String(
|
||||
rt?.hardNoFile ??
|
||||
rt?.hard_nofile ??
|
||||
rt?.nofileHard ??
|
||||
rt?.nofile_hard ??
|
||||
nofileSoft
|
||||
) || nofileSoft
|
||||
if (args.length === 0 || (args.length === 1 && args[0] === '-a')) {
|
||||
for (const [label, flag, val] of LIMITS) {
|
||||
ctx.console.log(`${label} (${flag}) ${val}`)
|
||||
const outVal = flag === '-n' ? nofileSoft : val
|
||||
ctx.console.log(`${label} (${flag}) ${outVal}`)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (args.length === 1 && args[0] === '-n') {
|
||||
ctx.console.log('256')
|
||||
if (args.length === 1 && (args[0] === '-n' || args[0] === '-Sn')) {
|
||||
ctx.console.log(nofileSoft)
|
||||
return
|
||||
}
|
||||
ctx.console.error('ulimit: only -a and -n are supported in Bare OS')
|
||||
if (args.length === 1 && args[0] === '-Hn') {
|
||||
ctx.console.log(nofileHard)
|
||||
return
|
||||
}
|
||||
ctx.console.error('ulimit: only -a, -n, -Sn, and -Hn are supported in Bare OS')
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
@@ -68,6 +68,8 @@ async function run(ctx, argv) {
|
||||
let delName = null
|
||||
/** @type {{ name: string, val: string } | null} */
|
||||
let writePair = null
|
||||
/** @type {string | null} */
|
||||
let printName = null
|
||||
/** @type {string[]} */
|
||||
const rest = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
@@ -88,6 +90,17 @@ async function run(ctx, argv) {
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
if (a === '-p' || a === '--print') {
|
||||
const name = argv[i + 1]
|
||||
if (!name) {
|
||||
ctx.console.error('xattr: -p requires NAME')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
printName = name
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if (a === '-d' || a === '--delete') {
|
||||
const name = argv[i + 1]
|
||||
if (!name) {
|
||||
@@ -100,7 +113,7 @@ async function run(ctx, argv) {
|
||||
continue
|
||||
}
|
||||
if (a === '--help' || a === '-h') {
|
||||
ctx.console.log('usage: xattr [-l] [-w NAME VALUE] [-d NAME] PATH')
|
||||
ctx.console.log('usage: xattr [-l] [-p NAME] [-w NAME VALUE] [-d NAME] PATH')
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
@@ -111,7 +124,7 @@ async function run(ctx, argv) {
|
||||
rest.push(a)
|
||||
}
|
||||
if (rest.length !== 1) {
|
||||
ctx.console.error('usage: xattr [-l] [-w NAME VALUE] [-d NAME] PATH')
|
||||
ctx.console.error('usage: xattr [-l] [-p NAME] [-w NAME VALUE] [-d NAME] PATH')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
@@ -132,6 +145,16 @@ async function run(ctx, argv) {
|
||||
if (writePair) {
|
||||
obj[writePair.name] = xattrB64Encode(writePair.val)
|
||||
await writeXattrMap(ctx, path, obj)
|
||||
ctx.console.log(writePair.name)
|
||||
return
|
||||
}
|
||||
if (printName) {
|
||||
if (!Object.prototype.hasOwnProperty.call(obj, printName)) {
|
||||
ctx.console.error('xattr: ' + printName + ': No such xattr')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.log(xattrB64Decode(obj[printName]))
|
||||
return
|
||||
}
|
||||
const keys = Object.keys(obj).sort()
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import test from 'brittle'
|
||||
import b4a from 'b4a'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
|
||||
|
||||
async function loadBin(name) {
|
||||
const runtime = await readFile(path.join(__dirname, '../lib/runtime.js'), 'utf8')
|
||||
const body = await readFile(path.join(__dirname, `../src/${name}.js`), 'utf8')
|
||||
return new AsyncFunction(
|
||||
'ctx',
|
||||
'argv',
|
||||
`${runtime}\n${body}\nif (typeof run === 'function') return await run(ctx, argv)\n`
|
||||
)
|
||||
}
|
||||
|
||||
test('dd honors large /dev/zero count*bs output', async (t) => {
|
||||
const run = await loadBin('dd')
|
||||
/** @type {number[]} */
|
||||
const rawChunks = []
|
||||
const ctx = {
|
||||
b4a,
|
||||
exitCode: 0,
|
||||
shellStdin: '',
|
||||
bareOsBinWrite: (u8) => rawChunks.push(u8.byteLength),
|
||||
console: { log: () => {}, error: () => {} },
|
||||
vfs: {
|
||||
async readFile(p) {
|
||||
if (p === '/dev/zero') return new Uint8Array(65536)
|
||||
return null
|
||||
},
|
||||
async writeFile() {}
|
||||
}
|
||||
}
|
||||
await run(ctx, ['dd', 'if=/dev/zero', 'bs=1M', 'count=2', 'status=none'])
|
||||
t.is(ctx.exitCode, 0)
|
||||
t.is(rawChunks.reduce((a, b) => a + b, 0), 2 * 1024 * 1024)
|
||||
})
|
||||
|
||||
test('shuf -e shuffles operands directly', async (t) => {
|
||||
const run = await loadBin('shuf')
|
||||
/** @type {string[]} */
|
||||
const logs = []
|
||||
const ctx = {
|
||||
b4a,
|
||||
exitCode: 0,
|
||||
shellStdin: '',
|
||||
console: { log: (m) => logs.push(String(m)), error: () => {} },
|
||||
vfs: {
|
||||
env: { BARE_OS_SHUF_SEED: 'seed' },
|
||||
async readFile() {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
await run(ctx, ['shuf', '-e', 'a', 'b', 'c'])
|
||||
t.is(ctx.exitCode, 0)
|
||||
t.is(logs.length, 3)
|
||||
t.alike([...logs].sort(), ['a', 'b', 'c'])
|
||||
})
|
||||
|
||||
test('split accepts -b1k suffix', async (t) => {
|
||||
const run = await loadBin('split')
|
||||
/** @type {Record<string, Uint8Array>} */
|
||||
const out = {}
|
||||
const input = b4a.alloc(2050, 120)
|
||||
const ctx = {
|
||||
b4a,
|
||||
exitCode: 0,
|
||||
shellStdin: '',
|
||||
console: { log: () => {}, error: () => {} },
|
||||
vfs: {
|
||||
env: {},
|
||||
async readFile(p) {
|
||||
return p === '/in' ? input : out[p] || null
|
||||
},
|
||||
async writeFile(p, body) {
|
||||
out[p] = body instanceof Uint8Array ? body : b4a.from(body)
|
||||
}
|
||||
}
|
||||
}
|
||||
await run(ctx, ['split', '-b1k', '/in', '/tmp/x'])
|
||||
t.is(ctx.exitCode, 0)
|
||||
t.is(out['/tmp/xaa'].byteLength, 1024)
|
||||
t.is(out['/tmp/xab'].byteLength, 1024)
|
||||
t.is(out['/tmp/xac'].byteLength, 2)
|
||||
})
|
||||
|
||||
test('ulimit -Sn reads runtime soft nofile when present', async (t) => {
|
||||
const run = await loadBin('ulimit')
|
||||
/** @type {string[]} */
|
||||
const logs = []
|
||||
const ctx = {
|
||||
b4a,
|
||||
exitCode: 0,
|
||||
shellStdin: '',
|
||||
console: { log: (m) => logs.push(String(m)), error: () => {} },
|
||||
vfs: {
|
||||
async readFile(p) {
|
||||
if (p !== '/proc/bare_os/rlimits.json') return null
|
||||
return b4a.from(JSON.stringify({ soft_nofile: 4096, hard_nofile: 8192 }))
|
||||
}
|
||||
}
|
||||
}
|
||||
await run(ctx, ['ulimit', '-Sn'])
|
||||
t.is(ctx.exitCode, 0)
|
||||
t.is(logs[0], '4096')
|
||||
})
|
||||
@@ -93,7 +93,7 @@ test('getfacl synthesizes mode triples and reads sidecar', async (t) => {
|
||||
const ctxB = createCtx(store)
|
||||
await runB(ctxB, ['getfacl', '/b'])
|
||||
t.is(ctxB.exitCode, 0)
|
||||
t.ok(ctxB._logs[0].includes('user::rw-'))
|
||||
t.ok(ctxB._logs.join('\n').includes('user::rw-'))
|
||||
})
|
||||
|
||||
test('getfacl documents mask-style sidecars for VFS enforcement', async (t) => {
|
||||
@@ -116,7 +116,7 @@ test('getfacl documents mask-style sidecars for VFS enforcement', async (t) => {
|
||||
t.ok(ctx._logs.join('\n').includes('other::---'))
|
||||
})
|
||||
|
||||
test('setfacl -b and stdin write', async (t) => {
|
||||
test('setfacl -b, -m and stdin write', async (t) => {
|
||||
const store = {
|
||||
files: {
|
||||
'/x': {
|
||||
@@ -141,6 +141,12 @@ test('setfacl -b and stdin write', async (t) => {
|
||||
await runSet(ctx2, ['setfacl', '/x'])
|
||||
t.is(ctx2.exitCode, 0)
|
||||
t.is(b4a.toString(store.files['/x.bare_acl'].body), 'user::r--\n')
|
||||
|
||||
const runMod = await loadBin('setfacl')
|
||||
const ctx3 = createCtx(store)
|
||||
await runMod(ctx3, ['setfacl', '-m', 'group::r-x', '/x'])
|
||||
t.is(ctx3.exitCode, 0)
|
||||
t.ok(b4a.toString(store.files['/x.bare_acl'].body).includes('group::r-x'))
|
||||
})
|
||||
|
||||
test('xattr -w -l -d round trip', async (t) => {
|
||||
@@ -156,6 +162,7 @@ test('xattr -w -l -d round trip', async (t) => {
|
||||
const ctxW = createCtx(store)
|
||||
await runW(ctxW, ['xattr', '-w', 'user.t', 'hi', '/f'])
|
||||
t.is(ctxW.exitCode, 0)
|
||||
t.is(ctxW._logs.join('\n'), 'user.t')
|
||||
|
||||
const runL = await loadBin('xattr')
|
||||
const ctxL = createCtx(store)
|
||||
@@ -163,6 +170,12 @@ test('xattr -w -l -d round trip', async (t) => {
|
||||
t.is(ctxL.exitCode, 0)
|
||||
t.is(ctxL._logs.join('\n'), 'user.t: hi')
|
||||
|
||||
const runP = await loadBin('xattr')
|
||||
const ctxP = createCtx(store)
|
||||
await runP(ctxP, ['xattr', '-p', 'user.t', '/f'])
|
||||
t.is(ctxP.exitCode, 0)
|
||||
t.is(ctxP._logs.join('\n'), 'hi')
|
||||
|
||||
const runD = await loadBin('xattr')
|
||||
const ctxD = createCtx(store)
|
||||
await runD(ctxD, ['xattr', '-d', 'user.t', '/f'])
|
||||
|
||||
@@ -88,12 +88,8 @@ function bareOsEmitRaw(ctx, chunk) {
|
||||
}
|
||||
|
||||
function bareCatOut(ctx, s) {
|
||||
const w = globalThis.process?.stdout?.write
|
||||
if (typeof w === 'function') {
|
||||
w.call(globalThis.process.stdout, s)
|
||||
} else {
|
||||
ctx.console.log(s)
|
||||
}
|
||||
if (bareOsEmitRaw(ctx, s)) return
|
||||
ctx.console.log(s)
|
||||
}
|
||||
|
||||
/** @param {string} line @param {{ showTabs: boolean, showEnds: boolean, showNonprinting: boolean }} o */
|
||||
|
||||
@@ -200,32 +200,49 @@ async function run(ctx, argv) {
|
||||
}
|
||||
}
|
||||
|
||||
let src
|
||||
if (opt.if) {
|
||||
src = await ctx.vfs.readFile(opt.if)
|
||||
if (!src) {
|
||||
ctx.console.error('dd: ' + opt.if + ': No such file or directory')
|
||||
const finiteCount = opt.count == null ? null : opt.count
|
||||
const wantedBytes =
|
||||
finiteCount == null ? null : Math.max(0, finiteCount * opt.bs)
|
||||
const skipBytes = Math.max(0, opt.skip * opt.bs)
|
||||
let chunk
|
||||
if (opt.if === '/dev/zero' && wantedBytes != null) {
|
||||
chunk = ctx.b4a.alloc(wantedBytes)
|
||||
} else if (opt.if === '/dev/urandom' && wantedBytes != null) {
|
||||
const raw = await ctx.vfs.readFile('/dev/urandom')
|
||||
if (!raw) {
|
||||
ctx.console.error('dd: /dev/urandom: unavailable')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const seed = raw instanceof Uint8Array ? raw : ctx.b4a.from(raw)
|
||||
chunk = ctx.b4a.alloc(wantedBytes)
|
||||
for (let i = 0; i < wantedBytes; i++) chunk[i] = seed[i % seed.length]
|
||||
} else {
|
||||
src = ctx.b4a.from(bareStdin(ctx), 'utf8')
|
||||
let src
|
||||
if (opt.if) {
|
||||
src = await ctx.vfs.readFile(opt.if)
|
||||
if (!src) {
|
||||
ctx.console.error('dd: ' + opt.if + ': No such file or directory')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
} else {
|
||||
src = ctx.b4a.from(bareStdin(ctx), 'utf8')
|
||||
}
|
||||
const input = src instanceof Uint8Array ? src : ctx.b4a.from(src)
|
||||
const start = Math.min(skipBytes, input.byteLength)
|
||||
const end =
|
||||
wantedBytes == null
|
||||
? input.byteLength
|
||||
: Math.min(input.byteLength, start + wantedBytes)
|
||||
chunk = input.subarray(start, end)
|
||||
}
|
||||
const input = src instanceof Uint8Array ? src : ctx.b4a.from(src)
|
||||
const start = Math.min(opt.skip * opt.bs, input.byteLength)
|
||||
const end =
|
||||
opt.count == null
|
||||
? input.byteLength
|
||||
: Math.min(input.byteLength, start + opt.count * opt.bs)
|
||||
let chunk = input.subarray(start, end)
|
||||
if (opt.conv.includes('sync')) {
|
||||
chunk = padToBlock(chunk, opt.bs, ctx.b4a)
|
||||
}
|
||||
|
||||
if (!opt.of) {
|
||||
const w = globalThis.process?.stdout?.write
|
||||
if (typeof w === 'function') w.call(globalThis.process.stdout, chunk)
|
||||
else ctx.console.log(ctx.b4a.toString(chunk))
|
||||
if (!bareOsEmitRaw(ctx, chunk)) ctx.console.log(ctx.b4a.toString(chunk))
|
||||
if (opt.status !== 'none') {
|
||||
ctx.console.error(`${chunk.byteLength} bytes copied`)
|
||||
}
|
||||
|
||||
@@ -95,10 +95,7 @@ async function run(ctx, argv) {
|
||||
parts.shift()
|
||||
}
|
||||
const s = parts.join(' ')
|
||||
const w = globalThis.process?.stdout?.write
|
||||
if (typeof w === 'function') {
|
||||
w.call(globalThis.process.stdout, s + (n ? '' : '\n'))
|
||||
} else {
|
||||
ctx.console.log(n ? s : s)
|
||||
}
|
||||
const out = s + (n ? '' : '\n')
|
||||
if (bareOsEmitRaw(ctx, out)) return
|
||||
ctx.console.log(s)
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@ async function run(ctx, argv) {
|
||||
const raw = await ctx.vfs.readFile(side)
|
||||
if (raw && raw.byteLength) {
|
||||
const text = ctx.b4a.toString(raw)
|
||||
if (!compact) ctx.console.log('# source: ' + side)
|
||||
ctx.console.log(text.replace(/\n$/, ''))
|
||||
return
|
||||
}
|
||||
@@ -156,6 +157,7 @@ async function run(ctx, argv) {
|
||||
'# file: ' + path,
|
||||
'# owner: synthetic',
|
||||
'# group: synthetic',
|
||||
'# source: mode-fallback',
|
||||
'user::' + u,
|
||||
'group::' + g,
|
||||
'other::' + o
|
||||
|
||||
@@ -95,6 +95,8 @@ function bareOsEmitRaw(ctx, chunk) {
|
||||
async function run(ctx, argv) {
|
||||
let clear = false
|
||||
/** @type {string[]} */
|
||||
const modify = []
|
||||
/** @type {string[]} */
|
||||
const files = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
@@ -102,9 +104,20 @@ async function run(ctx, argv) {
|
||||
clear = true
|
||||
continue
|
||||
}
|
||||
if (a === '-m' || a === '--modify') {
|
||||
const spec = argv[i + 1]
|
||||
if (!spec) {
|
||||
ctx.console.error('setfacl: -m requires ACL entry')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
modify.push(spec)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if (a === '--help' || a === '-h') {
|
||||
ctx.console.log(
|
||||
'usage: setfacl [-b] PATH\nWrites ACL text from stdin when not clearing.'
|
||||
'usage: setfacl [-b] [-m ENTRY] PATH\nWrites ACL text from stdin when not clearing.'
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -137,14 +150,43 @@ async function run(ctx, argv) {
|
||||
}
|
||||
return
|
||||
}
|
||||
const text = bareStdin(ctx)
|
||||
if (!text || !String(text).trim()) {
|
||||
ctx.console.error('setfacl: ACL text required on stdin')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
let body = ''
|
||||
if (modify.length) {
|
||||
const prev = await ctx.vfs.readFile(side)
|
||||
const lines = prev
|
||||
? ctx.b4a
|
||||
.toString(prev)
|
||||
.split(/\r?\n/)
|
||||
.map((x) => x.trim())
|
||||
.filter(Boolean)
|
||||
: []
|
||||
const byKey = new Map()
|
||||
for (const ln of lines) {
|
||||
const k = ln.split(':', 2).join(':')
|
||||
byKey.set(k, ln)
|
||||
}
|
||||
for (const m of modify) {
|
||||
const spec = String(m).trim()
|
||||
if (!spec.includes(':')) {
|
||||
ctx.console.error('setfacl: invalid ACL entry ' + spec)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const key = spec.split(':', 2).join(':')
|
||||
byKey.set(key, spec)
|
||||
}
|
||||
body = [...byKey.values()].join('\n')
|
||||
} else {
|
||||
const text = bareStdin(ctx)
|
||||
if (!text || !String(text).trim()) {
|
||||
ctx.console.error('setfacl: ACL text required on stdin')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
body = String(text)
|
||||
}
|
||||
const body = String(text).endsWith('\n') ? String(text) : String(text) + '\n'
|
||||
await ctx.vfs.writeFile(side, ctx.b4a.from(body))
|
||||
const finalBody = body.endsWith('\n') ? body : body + '\n'
|
||||
await ctx.vfs.writeFile(side, ctx.b4a.from(finalBody))
|
||||
} catch (e) {
|
||||
ctx.console.error('setfacl: ' + (e && e.message ? e.message : String(e)))
|
||||
ctx.exitCode = 1
|
||||
|
||||
@@ -117,6 +117,7 @@ function seededRandom(seed) {
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const paths = []
|
||||
let echoMode = false
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
@@ -125,6 +126,10 @@ async function run(ctx, argv) {
|
||||
)
|
||||
return
|
||||
}
|
||||
if (a === '-e') {
|
||||
echoMode = true
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('shuf: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
@@ -139,21 +144,32 @@ async function run(ctx, argv) {
|
||||
const seed = String(ctx.vfs?.env?.BARE_OS_SHUF_SEED || '').trim()
|
||||
const rand = seed ? seededRandom(seed) : Math.random
|
||||
const lines = []
|
||||
for (const p of paths) {
|
||||
const L = await readLines(ctx, p)
|
||||
if (L == null) {
|
||||
ctx.console.error('shuf: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const trim = L.length && L[L.length - 1] === '' ? L.slice(0, -1) : L
|
||||
for (const ln of trim) {
|
||||
if (echoMode) {
|
||||
for (const p of paths) {
|
||||
if (lines.length >= cap) {
|
||||
ctx.console.error('shuf: input exceeds line cap')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
lines.push(ln)
|
||||
lines.push(p)
|
||||
}
|
||||
} else {
|
||||
for (const p of paths) {
|
||||
const L = await readLines(ctx, p)
|
||||
if (L == null) {
|
||||
ctx.console.error('shuf: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const trim = L.length && L[L.length - 1] === '' ? L.slice(0, -1) : L
|
||||
for (const ln of trim) {
|
||||
if (lines.length >= cap) {
|
||||
ctx.console.error('shuf: input exceeds line cap')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
lines.push(ln)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = lines.length - 1; i > 0; i--) {
|
||||
|
||||
@@ -111,6 +111,23 @@ function splitSuffix(prefix, i) {
|
||||
return prefix + s
|
||||
}
|
||||
|
||||
function parseByteSize(v) {
|
||||
const s = String(v || '').trim()
|
||||
const m = /^(\d+)([kKmMgG]?)$/.exec(s)
|
||||
if (!m) return null
|
||||
const n = Number.parseInt(m[1], 10)
|
||||
if (!Number.isFinite(n) || n < 1) return null
|
||||
const mul =
|
||||
m[2] === 'k' || m[2] === 'K'
|
||||
? 1024
|
||||
: m[2] === 'm' || m[2] === 'M'
|
||||
? 1024 * 1024
|
||||
: m[2] === 'g' || m[2] === 'G'
|
||||
? 1024 * 1024 * 1024
|
||||
: 1
|
||||
return n * mul
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let lineCount = 1000
|
||||
let byteCount = null
|
||||
@@ -136,7 +153,13 @@ async function run(ctx, argv) {
|
||||
continue
|
||||
}
|
||||
if (a === '-b' && argv[i + 1]) {
|
||||
byteCount = parseInt(argv[++i], 10)
|
||||
byteCount = parseByteSize(argv[++i])
|
||||
lineCount = null
|
||||
if (!Number.isFinite(byteCount) || byteCount < 1) byteCount = 512
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-b') && a.length > 2) {
|
||||
byteCount = parseByteSize(a.slice(2))
|
||||
lineCount = null
|
||||
if (!Number.isFinite(byteCount) || byteCount < 1) byteCount = 512
|
||||
continue
|
||||
|
||||
@@ -100,18 +100,54 @@ const LIMITS = [
|
||||
['virtual memory', '-v', 'unlimited']
|
||||
]
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
*/
|
||||
async function readRuntimeLimits(ctx) {
|
||||
try {
|
||||
const b = await ctx.vfs.readFile('/proc/bare_os/rlimits.json')
|
||||
if (!b) return null
|
||||
const j = JSON.parse(ctx.b4a.toString(b))
|
||||
return j && typeof j === 'object' ? j : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const args = argv.slice(1).filter((a) => a !== '--')
|
||||
const rt = await readRuntimeLimits(ctx)
|
||||
const nofileSoft =
|
||||
String(
|
||||
rt?.softNoFile ??
|
||||
rt?.soft_nofile ??
|
||||
rt?.nofileSoft ??
|
||||
rt?.nofile_soft ??
|
||||
256
|
||||
) || '256'
|
||||
const nofileHard =
|
||||
String(
|
||||
rt?.hardNoFile ??
|
||||
rt?.hard_nofile ??
|
||||
rt?.nofileHard ??
|
||||
rt?.nofile_hard ??
|
||||
nofileSoft
|
||||
) || nofileSoft
|
||||
if (args.length === 0 || (args.length === 1 && args[0] === '-a')) {
|
||||
for (const [label, flag, val] of LIMITS) {
|
||||
ctx.console.log(`${label} (${flag}) ${val}`)
|
||||
const outVal = flag === '-n' ? nofileSoft : val
|
||||
ctx.console.log(`${label} (${flag}) ${outVal}`)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (args.length === 1 && args[0] === '-n') {
|
||||
ctx.console.log('256')
|
||||
if (args.length === 1 && (args[0] === '-n' || args[0] === '-Sn')) {
|
||||
ctx.console.log(nofileSoft)
|
||||
return
|
||||
}
|
||||
ctx.console.error('ulimit: only -a and -n are supported in Bare OS')
|
||||
if (args.length === 1 && args[0] === '-Hn') {
|
||||
ctx.console.log(nofileHard)
|
||||
return
|
||||
}
|
||||
ctx.console.error('ulimit: only -a, -n, -Sn, and -Hn are supported in Bare OS')
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
@@ -157,6 +157,8 @@ async function run(ctx, argv) {
|
||||
let delName = null
|
||||
/** @type {{ name: string, val: string } | null} */
|
||||
let writePair = null
|
||||
/** @type {string | null} */
|
||||
let printName = null
|
||||
/** @type {string[]} */
|
||||
const rest = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
@@ -177,6 +179,17 @@ async function run(ctx, argv) {
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
if (a === '-p' || a === '--print') {
|
||||
const name = argv[i + 1]
|
||||
if (!name) {
|
||||
ctx.console.error('xattr: -p requires NAME')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
printName = name
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if (a === '-d' || a === '--delete') {
|
||||
const name = argv[i + 1]
|
||||
if (!name) {
|
||||
@@ -189,7 +202,7 @@ async function run(ctx, argv) {
|
||||
continue
|
||||
}
|
||||
if (a === '--help' || a === '-h') {
|
||||
ctx.console.log('usage: xattr [-l] [-w NAME VALUE] [-d NAME] PATH')
|
||||
ctx.console.log('usage: xattr [-l] [-p NAME] [-w NAME VALUE] [-d NAME] PATH')
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
@@ -200,7 +213,7 @@ async function run(ctx, argv) {
|
||||
rest.push(a)
|
||||
}
|
||||
if (rest.length !== 1) {
|
||||
ctx.console.error('usage: xattr [-l] [-w NAME VALUE] [-d NAME] PATH')
|
||||
ctx.console.error('usage: xattr [-l] [-p NAME] [-w NAME VALUE] [-d NAME] PATH')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
@@ -221,6 +234,16 @@ async function run(ctx, argv) {
|
||||
if (writePair) {
|
||||
obj[writePair.name] = xattrB64Encode(writePair.val)
|
||||
await writeXattrMap(ctx, path, obj)
|
||||
ctx.console.log(writePair.name)
|
||||
return
|
||||
}
|
||||
if (printName) {
|
||||
if (!Object.prototype.hasOwnProperty.call(obj, printName)) {
|
||||
ctx.console.error('xattr: ' + printName + ': No such xattr')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.log(xattrB64Decode(obj[printName]))
|
||||
return
|
||||
}
|
||||
const keys = Object.keys(obj).sort()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema": 2,
|
||||
"profileId": "bare-os-posix-like",
|
||||
"generatedAt": "2026-04-27T12:05:37.921Z",
|
||||
"generatedAt": "2026-04-27T12:14:28.388Z",
|
||||
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
|
||||
"commandIndex": [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"atMs": 1777291537920,
|
||||
"atMs": 1777292068387,
|
||||
"commands": [
|
||||
"agent",
|
||||
"appctl",
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user