This commit is contained in:
Raven Scott
2026-04-03 04:23:17 -04:00
parent 98259a7b4a
commit 11cac45df6
86 changed files with 5516 additions and 93 deletions
@@ -79,7 +79,8 @@ function toStats(st) {
}
if (st.type === 'file') return new VfsGitStats('file', Number(st.size) || 0, ts)
if (st.type === 'directory') return new VfsGitStats('directory', 0, ts)
if (st.type === 'symlink') return new VfsGitStats('symlink', 0, ts)
if (st.type === 'symlink')
return new VfsGitStats('symlink', Number(st.size) || 0, ts)
throw err('ENOENT', 'ENOENT')
}
@@ -235,8 +236,10 @@ export function createGitFsFromVfs(vfs) {
return vfs.symlink(target, p)
}
async function chmod(_p, _mode) {
/* no-op: hyperdrive mode not exposed */
async function chmod(p, mode) {
if (typeof vfs.chmod === 'function') {
await vfs.chmod(p, mode)
}
}
const promises = {
@@ -0,0 +1,403 @@
/**
* POSIX-like stat metadata stored in Hyperdrive entry value.metadata.bareOs (JSON).
*/
/** @typedef {{ mode: number, uid: number, gid: number, uname: string, gname: string, mtimeMs: number, ctimeMs: number }} BareOsMeta */
export const S_IFDIR = 0o040000
export const S_IFREG = 0o100000
export const S_IFLNK = 0o120000
const DEFAULT_UMASK = 0o022
/**
* @param {Record<string, string>} env
*/
export function parseUmask(env) {
const raw = env.UMASK
if (raw == null || raw === '') return DEFAULT_UMASK
const s = String(raw).trim()
const m = /^0?([0-7]{1,3})$/.exec(s)
if (m) return Number.parseInt(m[1], 8) & 0o777
return DEFAULT_UMASK
}
/**
* @param {Record<string, string>} env
*/
export function parseUidGid(env) {
const uid = Number.parseInt(String(env.UID ?? '0'), 10)
const gid = Number.parseInt(String(env.GID ?? '0'), 10)
return {
uid: Number.isFinite(uid) ? uid : 0,
gid: Number.isFinite(gid) ? gid : 0
}
}
/**
* @param {Record<string, string>} env
*/
export function identityNames(env) {
const user = env.USER || env.LOGNAME || 'root'
const group = env.GROUP || user
return { user, group }
}
/**
* @param {unknown} value Hyperdrive entry .value
* @returns {BareOsMeta | null}
*/
export function extractBareOs(value) {
if (!value || typeof value !== 'object') return null
const md = /** @type {Record<string, unknown>} */ (value).metadata
if (!md || typeof md !== 'object') return null
const b = /** @type {Record<string, unknown>} */ (md).bareOs
if (!b || typeof b !== 'object') return null
const mode = Number(b.mode)
const uid = Number(b.uid)
const gid = Number(b.gid)
if (!Number.isFinite(mode)) return null
return {
mode: mode & 0xffff,
uid: Number.isFinite(uid) ? uid : 0,
gid: Number.isFinite(gid) ? gid : 0,
uname: typeof b.uname === 'string' ? b.uname : 'root',
gname: typeof b.gname === 'string' ? b.gname : 'root',
mtimeMs:
typeof b.mtimeMs === 'number' && Number.isFinite(b.mtimeMs)
? b.mtimeMs
: Date.now(),
ctimeMs:
typeof b.ctimeMs === 'number' && Number.isFinite(b.ctimeMs)
? b.ctimeMs
: Date.now()
}
}
/**
* @param {Record<string, unknown> | null | undefined} existingMetadata
* @param {BareOsMeta} bareOs
*/
export function mergeEntryMetadata(existingMetadata, bareOs) {
const base =
existingMetadata && typeof existingMetadata === 'object'
? { ...existingMetadata }
: {}
return { ...base, bareOs }
}
/**
* @param {Record<string, string>} env
* @param {{ executable?: boolean }} [opts]
* @returns {BareOsMeta}
*/
export function newBareOsForFile(env, opts = {}) {
const umask = parseUmask(env)
const { uid, gid } = parseUidGid(env)
const { user, group } = identityNames(env)
const now = Date.now()
let mode = S_IFREG | (0o666 & ~umask)
if (opts.executable) mode |= 0o111
return {
mode,
uid,
gid,
uname: user,
gname: group,
mtimeMs: now,
ctimeMs: now
}
}
/**
* @param {BareOsMeta | null} prev
* @param {Record<string, string>} env
* @param {{ executable?: boolean, bumpMtime?: boolean, touchCtime?: boolean, legacyExecutable?: boolean }} [opts]
* @returns {BareOsMeta}
*/
export function mergeBareOsOnWrite(prev, env, opts = {}) {
const now = Date.now()
if (!prev) {
const umask = parseUmask(env)
const { uid, gid } = parseUidGid(env)
const { user, group } = identityNames(env)
const legacyEx = !!opts.legacyExecutable
const wantEx =
opts.executable !== undefined ? opts.executable : legacyEx
let mode = S_IFREG | (0o666 & ~umask)
if (wantEx) mode |= 0o111
return {
mode,
uid,
gid,
uname: user,
gname: group,
mtimeMs: now,
ctimeMs: now
}
}
const executable =
opts.executable !== undefined ? opts.executable : !!(prev.mode & 0o111)
let mode = prev.mode
if (opts.executable !== undefined) {
if (executable) mode |= 0o111
else mode &= ~0o111
}
return {
mode,
uid: prev.uid,
gid: prev.gid,
uname: prev.uname,
gname: prev.gname,
mtimeMs: opts.bumpMtime !== false ? now : prev.mtimeMs,
ctimeMs: opts.touchCtime ? now : prev.ctimeMs
}
}
/**
* @param {Record<string, string>} env
* @returns {BareOsMeta}
*/
export function newBareOsForSymlink(env) {
const { uid, gid } = parseUidGid(env)
const { user, group } = identityNames(env)
const now = Date.now()
return {
mode: S_IFLNK | 0o777,
uid,
gid,
uname: user,
gname: group,
mtimeMs: now,
ctimeMs: now
}
}
/**
* @param {import('hyperdrive').default} personalDrive
* @param {{ drive: import('hyperdrive').default, virtualHomeDir?: boolean, virtualMntRoot?: boolean }} r
*/
export function isPersonalRoute(personalDrive, r) {
if (r.virtualHomeDir || r.virtualMntRoot) return false
return r.drive === personalDrive
}
/**
* @param {string} abs absolute logical path
* @param {boolean} personal
* @param {Record<string, string>} env
* @param {'file' | 'directory' | 'symlink'} type
* @param {{ executable?: boolean, linkname?: string }} [entryHints]
*/
export function synthesizeStat(
abs,
personal,
env,
type,
entryHints = {}
) {
const now = Date.now()
const nlink = 1
if (type === 'symlink') {
const linkname = entryHints.linkname || ''
const enc = new TextEncoder()
const size = enc.encode(linkname).length
const { uid, gid } = personal ? parseUidGid(env) : { uid: 0, gid: 0 }
const { user, group } = personal
? identityNames(env)
: { user: 'root', group: 'root' }
return {
type: 'symlink',
path: abs,
size,
mode: S_IFLNK | 0o777,
uid,
gid,
user,
group,
mtimeMs: now,
ctimeMs: now,
nlink,
linkname
}
}
if (type === 'directory') {
const mode = personal ? S_IFDIR | (0o777 & ~parseUmask(env)) : S_IFDIR | 0o755
const { uid, gid } = personal ? parseUidGid(env) : { uid: 0, gid: 0 }
const { user, group } = personal
? identityNames(env)
: { user: 'root', group: 'root' }
return {
type: 'directory',
path: abs,
size: 0,
mode,
uid,
gid,
user,
group,
mtimeMs: now,
ctimeMs: now,
nlink
}
}
// file
let mode = S_IFREG | 0o644
if (!personal) {
if (abs.startsWith('/bin/') || abs.startsWith('/boot/')) mode = S_IFREG | 0o555
else if (abs.startsWith('/etc/')) mode = S_IFREG | 0o644
else mode = S_IFREG | 0o644
return {
type: 'file',
path: abs,
size: 0,
mode,
uid: 0,
gid: 0,
user: 'root',
group: 'root',
mtimeMs: now,
ctimeMs: now,
nlink
}
}
const umask = parseUmask(env)
let fm = S_IFREG | (0o666 & ~umask)
if (entryHints.executable) fm |= 0o111
const { uid, gid } = parseUidGid(env)
const { user, group } = identityNames(env)
return {
type: 'file',
path: abs,
size: 0,
mode: fm,
uid,
gid,
user,
group,
mtimeMs: now,
ctimeMs: now,
nlink
}
}
/**
* @param {BareOsMeta} bo
* @param {'file' | 'directory' | 'symlink'} type
* @param {number} size
* @param {string} abs
* @param {string} [linkname]
*/
export function statFromBareOs(bo, type, size, abs, linkname) {
const o = {
type,
path: abs,
size,
mode: bo.mode,
uid: bo.uid,
gid: bo.gid,
user: bo.uname,
group: bo.gname,
mtimeMs: bo.mtimeMs,
ctimeMs: bo.ctimeMs,
nlink: 1
}
if (linkname != null) o.linkname = linkname
return o
}
/**
* @param {string} abs
*/
export function isVirtualMountPoint(abs) {
return (
abs === '/' ||
abs === '/home' ||
abs === '/mnt' ||
abs === '/home/' ||
abs === '/mnt/'
)
}
/**
* @param {number} mode
* @param {'file' | 'directory' | 'symlink'} type
*/
export function formatModeString(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]
*/
export function formatLsMtime(mtimeMs, 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(nowMs - 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
*/
export function posixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* @param {{ uid: number, gid: number, mode: number }} st
* @param {number} euid
* @param {number} egid
* @param {'r' | 'w' | 'x'} op
* @param {'file' | 'directory'} kind
*/
export function modeAllows(st, euid, egid, op, kind) {
const mode = st.mode & 0o777
const bit =
op === 'r' ? 0o444 : op === 'w' ? 0o222 : 0o111
let masked = 0
if (euid === st.uid) masked = (mode >> 6) & 7
else if (egid === st.gid) masked = (mode >> 3) & 7
else masked = mode & 7
const need = op === 'r' ? 4 : op === 'w' ? 2 : 1
return (masked & need) === need
}
+312 -64
View File
@@ -1,4 +1,19 @@
import unixPathResolve from 'unix-path-resolve'
import {
extractBareOs,
isPersonalRoute,
isVirtualMountPoint,
mergeBareOsOnWrite,
mergeEntryMetadata,
modeAllows,
newBareOsForSymlink,
parseUidGid,
statFromBareOs,
synthesizeStat,
S_IFDIR,
S_IFLNK,
S_IFREG
} from './vfs-posix-meta.js'
/**
* Unified path view: system Hyperdrive for OS paths, personal Hyperdrive under $HOME,
@@ -139,34 +154,85 @@ export function createVfs(systemDrive, personalDrive, env, mntRef = null) {
return unixPathResolve(base, name)
}
async function lstatFromAbs(abs) {
if (abs === '/mnt' || abs === '/mnt/') {
return { type: 'directory', path: abs }
function dirnameAbs(abs) {
if (!abs || abs === '/') return '/'
const t = abs.replace(/\/$/, '')
const i = t.lastIndexOf('/')
if (i <= 0) return '/'
return t.slice(0, i) || '/'
}
/** @param {string} abs */
function pathPrefixes(abs) {
if (abs === '/') return ['/']
const t = abs.replace(/\/$/, '') || '/'
if (t === '/') return ['/']
const parts = t.split('/').filter(Boolean)
const out = ['/']
let acc = ''
for (const p of parts) {
acc += '/' + p
out.push(acc)
}
const activeSeg = activeHomeBasename()
if (activeSeg && abs === '/home') {
return { type: 'directory', path: abs }
return out
}
/**
* @param {string} abs
* @param {Awaited<ReturnType<typeof route>>} r
* @param {boolean} personal
* @param {Awaited<ReturnType<typeof entryOn>>} e
*/
function statFromEntryValue(abs, r, personal, e) {
const v = e && e.value
if (!v) return null
if (v.linkname) {
const linkname = v.linkname
const enc = new TextEncoder()
const size = enc.encode(String(linkname)).length
const bo = extractBareOs(v)
if (bo) return statFromBareOs(bo, 'symlink', size, abs, linkname)
return synthesizeStat(abs, personal, env, 'symlink', { linkname })
}
const r = route(abs)
if (r.virtualMntRoot) {
return { type: 'directory', path: abs }
}
const { drive, path: p } = r
if (abs === '/' || isHyperdriveRootPath(p)) {
return { type: 'directory', path: abs }
}
const e = await entryOn(drive, p, { follow: false })
if (e && e.value && e.value.linkname) {
return { type: 'symlink', path: abs, linkname: e.value.linkname }
}
if (e && e.value && e.value.blob) {
const bl = e.value.blob
if (v.blob) {
const bl = v.blob
const len =
typeof bl.byteLength === 'number'
? bl.byteLength
: (bl.blockLength ?? 0)
return { type: 'file', size: len, path: abs }
const bo = extractBareOs(v)
const ex = !!v.executable
if (bo) return statFromBareOs(bo, 'file', len, abs)
const s = synthesizeStat(abs, personal, env, 'file', { executable: ex })
s.size = len
return s
}
return null
}
async function lstatFromAbs(abs) {
if (abs === '/mnt' || abs === '/mnt/') {
return { ...synthesizeStat(abs, false, env, 'directory'), path: abs }
}
const activeSeg = activeHomeBasename()
if (activeSeg && abs === '/home') {
return { ...synthesizeStat(abs, false, env, 'directory'), path: abs }
}
if (abs === '/') {
return { ...synthesizeStat(abs, false, env, 'directory'), path: abs }
}
const r = route(abs)
if (r.virtualMntRoot) {
return { ...synthesizeStat(abs, false, env, 'directory'), path: abs }
}
const { drive, path: p } = r
const personal = isPersonalRoute(personalDrive, r)
if (isHyperdriveRootPath(p)) {
return { ...synthesizeStat(abs, personal, env, 'directory'), path: abs }
}
const e = await entryOn(drive, p, { follow: false })
const fromVal = statFromEntryValue(abs, r, personal, e)
if (fromVal) return fromVal
const names = await (async () => {
const out = []
try {
@@ -177,11 +243,146 @@ export function createVfs(systemDrive, personalDrive, env, mntRef = null) {
}
return out
})()
if (names.length) return { type: 'directory', path: abs }
if (e) return { type: 'directory', path: abs }
if (names.length) {
return { ...synthesizeStat(abs, personal, env, 'directory'), path: abs }
}
if (e) return { ...synthesizeStat(abs, personal, env, 'directory'), path: abs }
return null
}
async function statFromAbs(abs) {
if (
abs === '/mnt' ||
abs === '/mnt/' ||
abs === '/home' ||
abs === '/' ||
(activeHomeBasename() && abs === '/home')
) {
return lstatFromAbs(abs)
}
const r0 = route(abs)
if (r0.virtualMntRoot) return lstatFromAbs(abs)
let cur = abs
for (let depth = 0; depth < 16; depth++) {
if (cur === '/' || cur === '/home' || cur === '/mnt') {
return lstatFromAbs(cur)
}
const r = route(cur)
if (r.virtualMntRoot) return lstatFromAbs(cur)
const { drive, path: p } = r
if (isHyperdriveRootPath(p)) return lstatFromAbs(cur)
const e = await entryOn(drive, p, { follow: false })
if (!e || !e.value) return lstatFromAbs(cur)
if (e.value.linkname) {
const parent = dirnameAbs(cur)
cur = unixPathResolve(parent, e.value.linkname)
continue
}
return lstatFromAbs(cur)
}
throw new Error('Too many symlink levels')
}
async function assertTraverseTo(abs, finalOp) {
const { uid: euid, gid: egid } = parseUidGid(env)
const prefixes = pathPrefixes(abs)
for (let i = 0; i < prefixes.length; i++) {
const pre = prefixes[i]
const isLast = i === prefixes.length - 1
if (isVirtualMountPoint(pre)) continue
const st = await lstatFromAbs(pre)
if (!isLast) {
if (!st) throw new Error('ENOENT: ' + abs)
if (st.type !== 'directory') {
throw new Error('Not a directory: ' + pre)
}
if (!modeAllows(st, euid, egid, 'x', 'directory')) {
throw new Error('EACCES: cannot traverse ' + pre)
}
continue
}
// Final path component
if (!st) {
// open(2) on a missing path: callers like touch read then write; readFile returns null.
if (finalOp === 'read') return
throw new Error('ENOENT: ' + abs)
}
if (st.type === 'directory') {
if (finalOp === 'readdir') {
if (!modeAllows(st, euid, egid, 'r', 'directory')) {
throw new Error('EACCES: cannot read directory ' + pre)
}
if (!modeAllows(st, euid, egid, 'x', 'directory')) {
throw new Error('EACCES: cannot access directory ' + pre)
}
} else if (finalOp === 'chdir') {
if (!modeAllows(st, euid, egid, 'x', 'directory')) {
throw new Error('EACCES: permission denied: ' + pre)
}
}
} else if (st.type === 'file' || st.type === 'symlink') {
if (finalOp === 'read') {
if (!modeAllows(st, euid, egid, 'r', 'file')) {
throw new Error('EACCES: cannot read ' + pre)
}
} else if (finalOp === 'write') {
if (!modeAllows(st, euid, egid, 'w', 'file')) {
throw new Error('EACCES: cannot write ' + pre)
}
}
}
}
}
async function assertUnlink(abs) {
const parent = dirnameAbs(abs)
const { uid: euid, gid: egid } = parseUidGid(env)
const prefixes = pathPrefixes(parent)
for (let i = 0; i < prefixes.length; i++) {
const pre = prefixes[i]
const isLast = i === prefixes.length - 1
if (isVirtualMountPoint(pre)) continue
const st = await lstatFromAbs(pre)
if (!st) throw new Error('ENOENT: ' + abs)
if (st.type !== 'directory') throw new Error('Not a directory: ' + pre)
if (!modeAllows(st, euid, egid, 'x', 'directory')) {
throw new Error('EACCES: cannot traverse ' + pre)
}
if (isLast) {
if (!modeAllows(st, euid, egid, 'w', 'directory')) {
throw new Error('EACCES: cannot unlink in ' + pre)
}
}
}
}
async function assertParentWritableForCreate(abs) {
const parent = dirnameAbs(abs)
if (parent === abs) return
const { uid: euid, gid: egid } = parseUidGid(env)
const prefixes = pathPrefixes(parent)
/** @type {{ pre: string, st: Awaited<ReturnType<typeof lstatFromAbs>> } | null} */
let deepest = null
for (const pre of prefixes) {
if (isVirtualMountPoint(pre)) continue
const st = await lstatFromAbs(pre)
if (!st) continue
if (st.type !== 'directory') {
throw new Error('Not a directory: ' + pre)
}
if (!modeAllows(st, euid, egid, 'x', 'directory')) {
throw new Error('EACCES: cannot traverse ' + pre)
}
deepest = { pre, st }
}
if (!deepest) {
throw new Error('ENOENT: ' + parent)
}
if (!modeAllows(deepest.st, euid, egid, 'w', 'directory')) {
throw new Error('EACCES: cannot create in ' + deepest.pre)
}
}
async function readdirFromAbs(abs) {
if (abs === '/mnt' || abs === '/mnt/') {
return [...getMntMap().keys()].sort()
@@ -253,6 +454,7 @@ export function createVfs(systemDrive, personalDrive, env, mntRef = null) {
throw new Error('ENOENT: no such file or directory')
}
if (st.type === 'symlink' || st.type === 'file') {
await assertUnlink(abs)
return delFromAbs(abs)
}
if (st.type === 'directory') {
@@ -286,6 +488,7 @@ export function createVfs(systemDrive, personalDrive, env, mntRef = null) {
if (await isRegularFile(abs)) {
throw new Error('Not a directory: ' + userPath)
}
await assertTraverseTo(abs, 'chdir')
cwd = abs
env.PWD = cwd
},
@@ -296,6 +499,7 @@ export function createVfs(systemDrive, personalDrive, env, mntRef = null) {
if (r.virtualHomeDir || r.virtualMntRoot) return null
const { drive, path: p } = r
if (isHyperdriveRootPath(p)) return null
await assertTraverseTo(abs, 'read')
return drive.get(p, { follow: true })
},
@@ -313,15 +517,27 @@ export function createVfs(systemDrive, personalDrive, env, mntRef = null) {
if (isHyperdriveRootPath(p)) {
throw new Error('Cannot write directory: ' + userPath)
}
return drive.put(p, buf, opts)
}
if (drive !== personalDrive) {
} else if (drive !== personalDrive) {
throw new Error('Read-only path (not under $HOME): ' + userPath)
}
if (isHyperdriveRootPath(p)) {
throw new Error('Cannot write directory: ' + userPath)
}
return drive.put(p, buf, opts)
const existing = await entryOn(drive, p, { follow: false })
const hadBlob = !!(existing?.value?.blob)
if (hadBlob) await assertTraverseTo(abs, 'write')
else await assertParentWritableForCreate(abs)
const value = existing?.value
const prevBare = extractBareOs(value)
const bareOs = mergeBareOsOnWrite(prevBare, env, {
executable: opts.executable,
bumpMtime: true,
legacyExecutable: !!value?.executable
})
const executable =
opts.executable !== undefined ? !!opts.executable : !!value?.executable
const metadata = mergeEntryMetadata(value?.metadata, bareOs)
return drive.put(p, buf, { executable, metadata })
},
async unlink(userPath) {
@@ -338,6 +554,7 @@ export function createVfs(systemDrive, personalDrive, env, mntRef = null) {
if (isHyperdriveRootPath(p)) {
throw new Error('Cannot unlink directory root')
}
await assertUnlink(abs)
return drive.del(p)
}
if (drive !== personalDrive) {
@@ -346,6 +563,7 @@ export function createVfs(systemDrive, personalDrive, env, mntRef = null) {
if (isHyperdriveRootPath(p)) {
throw new Error('Cannot unlink directory root')
}
await assertUnlink(abs)
return drive.del(p)
},
@@ -373,51 +591,77 @@ export function createVfs(systemDrive, personalDrive, env, mntRef = null) {
/** @returns {Promise<string[]>} */
async readdir(userPath) {
return readdirFromAbs(resolveLogical(userPath))
const abs = resolveLogical(userPath)
await assertTraverseTo(abs, 'readdir')
return readdirFromAbs(abs)
},
async stat(userPath) {
const abs = resolveLogical(userPath)
if (abs === '/mnt' || abs === '/mnt/') {
return { type: 'directory', path: abs }
}
const activeSeg = activeHomeBasename()
if (activeSeg && abs === '/home') {
return { type: 'directory', path: abs }
}
return statFromAbs(abs)
},
/**
* Octal mode (e.g. 0o644); applies to files and symlinks with a drive entry.
* @param {string} userPath
* @param {number} modeOctal permission bits + optional type bits (masked)
*/
async chmod(userPath, modeOctal) {
const abs = resolveLogical(userPath)
const r = route(abs)
if (r.virtualMntRoot) {
return { type: 'directory', path: abs }
if (r.virtualHomeDir || r.virtualMntRoot) {
throw new Error('chmod: ' + userPath + ': Operation not supported')
}
if (r.mntReadOnly === true) {
throw new Error('Read-only mount: ' + userPath)
}
const { drive, path: p } = r
if (abs === '/' || isHyperdriveRootPath(p)) {
return { type: 'directory', path: abs }
if (drive !== personalDrive) {
throw new Error('chmod: read-only system path: ' + userPath)
}
const e = await entryOn(drive, p, { follow: true })
if (e && e.value && e.value.blob) {
const bl = e.value.blob
const len =
typeof bl.byteLength === 'number'
? bl.byteLength
: (bl.blockLength ?? 0)
return { type: 'file', size: len, path: abs }
if (isHyperdriveRootPath(p)) {
throw new Error('chmod: invalid path')
}
if (e && e.value && e.value.linkname) {
return { type: 'symlink', path: abs }
const st = await lstatFromAbs(abs)
if (!st) throw new Error('chmod: ' + userPath + ': No such file')
const { uid: euid } = parseUidGid(env)
if (euid !== 0 && euid !== st.uid) {
throw new Error('chmod: ' + userPath + ': Operation not permitted')
}
const names = await (async () => {
const out = []
try {
const stream = drive.readdir(p === '/' ? '/' : p)
for await (const n of stream) out.push(n)
} catch {
/* treat as missing */
}
return out
})()
if (names.length) return { type: 'directory', path: abs }
if (e) return { type: 'directory', path: abs }
return null
const e = await entryOn(drive, p, { follow: false })
if (!e?.value) {
throw new Error('chmod: cannot change inferred directory: ' + userPath)
}
const v = e.value
const perm = modeOctal & 0o777
let typeBits = S_IFREG
if (v.linkname) typeBits = S_IFLNK
else if (!v.blob) typeBits = S_IFDIR
const newMode = typeBits | perm
const prevBare = extractBareOs(v)
const bo = prevBare
? {
...prevBare,
mode: newMode,
mtimeMs: prevBare.mtimeMs,
ctimeMs: prevBare.ctimeMs
}
: {
mode: newMode,
uid: st.uid,
gid: st.gid,
uname: st.user,
gname: st.group,
mtimeMs: st.mtimeMs,
ctimeMs: st.ctimeMs
}
const executable = !!(v.blob && (newMode & 0o111))
await drive.putEntry(p, {
executable,
linkname: v.linkname ?? null,
blob: v.blob ?? null,
metadata: mergeEntryMetadata(v.metadata, bo)
})
},
/**
@@ -461,7 +705,9 @@ export function createVfs(systemDrive, personalDrive, env, mntRef = null) {
if (isHyperdriveRootPath(p)) {
throw new Error('Cannot symlink at directory root')
}
return drive.symlink(p, target)
return drive.symlink(p, target, {
metadata: mergeEntryMetadata(null, newBareOsForSymlink(env))
})
}
if (drive !== personalDrive) {
throw new Error('Read-only path (not under $HOME): ' + userPath)
@@ -469,7 +715,9 @@ export function createVfs(systemDrive, personalDrive, env, mntRef = null) {
if (isHyperdriveRootPath(p)) {
throw new Error('Cannot symlink at directory root')
}
return drive.symlink(p, target)
return drive.symlink(p, target, {
metadata: mergeEntryMetadata(null, newBareOsForSymlink(env))
})
}
}
}
+79
View File
@@ -53,6 +53,8 @@ function testCtx(drive, personal, env) {
HOME: '/home/user',
PATH: '/bin',
USER: 'user',
UID: '1000',
GID: '1000',
PWD: '/home/user',
...env
}
@@ -230,6 +232,83 @@ test('Hyperdrive roundtrips /boot/init.js on Corestore', async (t) => {
rmSync(dir, { recursive: true, force: true })
})
test('vfs lstat personal file includes mode uid user mtime', async (t) => {
const dir = testCorestoreDir('lstatposix')
const store = new Corestore(dir)
const sys = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('lsp'))
await sys.ready()
await personal.ready()
const vfs = createVfs(sys, personal, {
HOME: '/home/user',
PWD: '/home/user',
PATH: '/bin',
USER: 'alice',
UID: '4242',
GID: '4242'
})
await vfs.writeFile('f', b4a.from('x'))
const st = await vfs.lstat('/home/user/f')
t.ok(st)
t.is(st.type, 'file')
t.is(st.user, 'alice')
t.is(st.uid, 4242)
t.ok((st.mode & 0o777) <= 0o777)
t.ok((st.mode & 0o100000) === 0o100000)
t.ok(typeof st.mtimeMs === 'number')
await store.close()
rmSync(dir, { recursive: true, force: true })
})
test('ls -l long listing uses session user and regular file mode', async (t) => {
const lsPath = path.join(__dirname, '../../kernel/bin/ls')
const lsSrc = await readFile(lsPath, 'utf8')
const dir = testCorestoreDir('lslong')
const store = new Corestore(dir)
const drive = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('pll'))
await drive.ready()
await personal.ready()
await drive.put('/bin/ls', b4a.from(lsSrc))
const lines = []
const ctx = testCtx(drive, personal, { USER: 'carol', UID: '9001', GID: '9001' })
ctx.console = {
log: (s) => lines.push(String(s)),
error: (...a) => lines.push(a.join(' '))
}
await ctx.vfs.writeFile('shown.txt', b4a.from(''))
await runBinCommand(ctx, ['ls', '-l', 'shown.txt'])
const longLine = lines.find((l) => l.includes('shown.txt'))
t.ok(longLine)
t.ok(longLine.includes('carol'))
t.ok(/^-rw/.test(longLine), 'expected regular file mode prefix')
await store.close()
rmSync(dir, { recursive: true, force: true })
})
test('vfs readFile missing path in home does not throw (touch pattern)', async (t) => {
const dir = testCorestoreDir('vfsreadmiss')
const store = new Corestore(dir)
const sys = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('pvrm'))
await sys.ready()
await personal.ready()
const vfs = createVfs(sys, personal, {
HOME: '/home/zed',
PWD: '/home/zed',
PATH: '/bin',
USER: 'zed',
UID: '7000',
GID: '7000'
})
const missing = await vfs.readFile('newfile')
t.is(missing, null)
await vfs.writeFile('newfile', b4a.from('ok'))
t.is(b4a.toString(await vfs.readFile('newfile')), 'ok')
await store.close()
rmSync(dir, { recursive: true, force: true })
})
test('vfs routes HOME to personal drive', async (t) => {
const dir = testCorestoreDir('vfs')
const store = new Corestore(dir)
+1
View File
@@ -10,6 +10,7 @@ const seederKernelBin = join(repoRoot, 'packages/bare-os-seeder/kernel/bin')
const commands = [
'basename',
'cat',
'chmod',
'clear',
'crontab',
'date',
+57
View File
@@ -2,3 +2,60 @@
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
}
+23
View File
@@ -0,0 +1,23 @@
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.exitCode = 1
return
}
for (const f of files) {
try {
await ctx.vfs.chmod(f, mode)
} catch (e) {
ctx.console.error('chmod: ' + f + ': ' + (e.message || e))
ctx.exitCode = 1
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
async function run(ctx, argv) {
ctx.console.log(
'Bare OS — default user: guest | builtins: alias, cd, export, exit, login, logout, unalias | /bin: basename cat 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 | 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'
)
ctx.console.log(
'Identity: login [--new] <passphrase> | logout [--save] | savevault (encrypt copy of personal drive under /.bare/vault/)'
+79 -7
View File
@@ -25,23 +25,95 @@ async function run(ctx, argv) {
for (const t of targets) {
if (targets.length > 1) ctx.console.log(t + ':')
let names
/** @type {string | null} */
let singleEntryPath = null
try {
names = await vfs.readdir(t)
const stT = await vfs.lstat(t)
if (stT && stT.type !== 'directory') {
const leaf = t
.replace(/\/+$/, '')
.split('/')
.filter(Boolean)
.pop()
names = [leaf || t]
singleEntryPath = t
} else {
names = await vfs.readdir(t)
}
} catch (e) {
ctx.console.error('ls: cannot access ' + t + ': ' + (e.message || e))
continue
}
// POSIX: hide dotfiles unless -a (. and .. are dot-prefixed too).
if (!showAll) names = names.filter((n) => !n.startsWith('.'))
if (!longFmt) {
ctx.console.log(names.join(' '))
} else {
let totalBlocks = 0
const rows = []
for (const n of names) {
const sub = t === '.' || t === './' ? n : t.replace(/\/$/, '') + '/' + n
const st = await vfs.stat(sub)
const tag = st ? (st.type === 'directory' ? 'd' : '-') : '?'
const sz = st && st.size != null ? String(st.size) : '0'
ctx.console.log(tag + 'rwxr-xr-x 1 user user ' + sz + ' ' + n)
const sub =
singleEntryPath != null
? singleEntryPath
: t === '.' || t === './'
? n
: t.replace(/\/$/, '') + '/' + n
const st = await vfs.lstat(sub)
if (!st) {
rows.push({
modeStr: '?---------',
nlink: '?',
user: '?',
group: '?',
size: 0,
mtimeStr: '?',
name: n,
arrow: ''
})
continue
}
const blocks = barePosixBlocks(st.size || 0)
totalBlocks += blocks
const modeStr = bareFormatModeString(st.mode, st.type)
const nlink = st.nlink != null ? String(st.nlink) : '1'
const user = st.user != null ? st.user : String(st.uid ?? 0)
const group = st.group != null ? st.group : String(st.gid ?? 0)
const size = st.size != null ? String(st.size) : '0'
const mtimeStr = bareFormatLsMtime(
typeof st.mtimeMs === 'number' ? st.mtimeMs : Date.now()
)
let arrow = ''
if (st.type === 'symlink' && st.linkname != null) {
arrow = ' -> ' + st.linkname
}
rows.push({
modeStr,
nlink,
user,
group,
size,
mtimeStr,
name: n,
arrow
})
}
if (singleEntryPath == null) ctx.console.log('total ' + totalBlocks)
for (const r of rows) {
ctx.console.log(
r.modeStr +
' ' +
r.nlink +
' ' +
r.user +
' ' +
r.group +
' ' +
r.size +
' ' +
r.mtimeStr +
' ' +
r.name +
r.arrow
)
}
}
}
@@ -3,6 +3,63 @@ 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 parts = argv.slice(1).filter((a) => a !== '--')
if (!parts.length) {
+57
View File
@@ -3,6 +3,63 @@ 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 vfs = ctx.vfs
const files = argv.slice(1)
+85
View File
@@ -0,0 +1,85 @@
/** 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 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.exitCode = 1
return
}
for (const f of files) {
try {
await ctx.vfs.chmod(f, mode)
} catch (e) {
ctx.console.error('chmod: ' + f + ': ' + (e.message || e))
ctx.exitCode = 1
}
}
}
+57
View File
@@ -3,6 +3,63 @@ 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 seq = '\x1b[H\x1b[2J\x1b[3J'
if (typeof ctx.writeScreen === 'function') {
@@ -3,6 +3,63 @@ 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 vfs = ctx.vfs
const home = vfs.env?.HOME || '/home/guest'
+57
View File
@@ -3,6 +3,63 @@ 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 utc = argv.includes('-u') || argv.includes('--utc')
const d = new Date()
@@ -3,6 +3,63 @@ 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 parts = argv.slice(1).filter((a) => a !== '--')
if (!parts.length) {
+57
View File
@@ -3,6 +3,63 @@ 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 parts = argv.slice(1)
let n = false
+57
View File
@@ -3,6 +3,63 @@ 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 e = ctx.vfs.env
for (const k of Object.keys(e).sort()) {
+57
View File
@@ -3,6 +3,63 @@ 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
}
/** Drive-resident exit: ends the booter session (ctx.requestBooterExit from bare-os-booter). */
async function run(ctx, argv) {
let ec = 0
+57
View File
@@ -3,6 +3,63 @@ 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.exitCode = 1
}
+57
View File
@@ -3,6 +3,63 @@ 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
}
/**
* Subset of POSIX/GNU grep behavior using JavaScript RegExp / string search.
* Not bit-identical to GNU grep (no PCRE, different escaping, UTF-16 strings).
+57
View File
@@ -3,6 +3,63 @@ 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) {
if (typeof ctx.runHdms === 'function') {
await ctx.runHdms(argv)
+57
View File
@@ -3,6 +3,63 @@ 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 vfs = ctx.vfs
let n = 10
+58 -1
View File
@@ -3,9 +3,66 @@ 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.log(
'Bare OS — default user: guest | builtins: alias, cd, export, exit, login, logout, unalias | /bin: basename cat 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 | 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'
)
ctx.console.log(
'Identity: login [--new] <passphrase> | logout [--save] | savevault (encrypt copy of personal drive under /.bare/vault/)'
@@ -3,6 +3,63 @@ 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 h =
globalThis.process?.env?.HOSTNAME ||
+57
View File
@@ -3,6 +3,63 @@ 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 e = ctx.vfs.env
const u = e.USER || e.LOGNAME || 'guest'
+57
View File
@@ -3,6 +3,63 @@ 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 rest = argv.slice(1)
let createNew = false
+57
View File
@@ -3,6 +3,63 @@ 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 save = argv.includes('--save')
const logout = ctx.applyLogout
+136 -7
View File
@@ -3,6 +3,63 @@ 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 vfs = ctx.vfs
let showAll = false
@@ -30,23 +87,95 @@ async function run(ctx, argv) {
for (const t of targets) {
if (targets.length > 1) ctx.console.log(t + ':')
let names
/** @type {string | null} */
let singleEntryPath = null
try {
names = await vfs.readdir(t)
const stT = await vfs.lstat(t)
if (stT && stT.type !== 'directory') {
const leaf = t
.replace(/\/+$/, '')
.split('/')
.filter(Boolean)
.pop()
names = [leaf || t]
singleEntryPath = t
} else {
names = await vfs.readdir(t)
}
} catch (e) {
ctx.console.error('ls: cannot access ' + t + ': ' + (e.message || e))
continue
}
// POSIX: hide dotfiles unless -a (. and .. are dot-prefixed too).
if (!showAll) names = names.filter((n) => !n.startsWith('.'))
if (!longFmt) {
ctx.console.log(names.join(' '))
} else {
let totalBlocks = 0
const rows = []
for (const n of names) {
const sub = t === '.' || t === './' ? n : t.replace(/\/$/, '') + '/' + n
const st = await vfs.stat(sub)
const tag = st ? (st.type === 'directory' ? 'd' : '-') : '?'
const sz = st && st.size != null ? String(st.size) : '0'
ctx.console.log(tag + 'rwxr-xr-x 1 user user ' + sz + ' ' + n)
const sub =
singleEntryPath != null
? singleEntryPath
: t === '.' || t === './'
? n
: t.replace(/\/$/, '') + '/' + n
const st = await vfs.lstat(sub)
if (!st) {
rows.push({
modeStr: '?---------',
nlink: '?',
user: '?',
group: '?',
size: 0,
mtimeStr: '?',
name: n,
arrow: ''
})
continue
}
const blocks = barePosixBlocks(st.size || 0)
totalBlocks += blocks
const modeStr = bareFormatModeString(st.mode, st.type)
const nlink = st.nlink != null ? String(st.nlink) : '1'
const user = st.user != null ? st.user : String(st.uid ?? 0)
const group = st.group != null ? st.group : String(st.gid ?? 0)
const size = st.size != null ? String(st.size) : '0'
const mtimeStr = bareFormatLsMtime(
typeof st.mtimeMs === 'number' ? st.mtimeMs : Date.now()
)
let arrow = ''
if (st.type === 'symlink' && st.linkname != null) {
arrow = ' -> ' + st.linkname
}
rows.push({
modeStr,
nlink,
user,
group,
size,
mtimeStr,
name: n,
arrow
})
}
if (singleEntryPath == null) ctx.console.log('total ' + totalBlocks)
for (const r of rows) {
ctx.console.log(
r.modeStr +
' ' +
r.nlink +
' ' +
r.user +
' ' +
r.group +
' ' +
r.size +
' ' +
r.mtimeStr +
' ' +
r.name +
r.arrow
)
}
}
}
+57
View File
@@ -3,6 +3,63 @@ 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 vfs = ctx.vfs
const files = argv.slice(1).filter((a) => !a.startsWith('-'))
@@ -3,6 +3,63 @@ 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.startsWith('-'))
if (!paths.length) {
@@ -3,6 +3,63 @@ 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 e = ctx.vfs.env
const names = argv.slice(1).filter((a) => !a.startsWith('-'))
+57
View File
@@ -3,6 +3,63 @@ 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.log(ctx.vfs.getcwd())
}
+57
View File
@@ -3,6 +3,63 @@ 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
}
/**
* rm — remove files or directories.
* Flags: -r -R --recursive, -f --force, -- ; bundled e.g. -rf
@@ -3,6 +3,63 @@ 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 save = ctx.saveVault
if (typeof save !== 'function') {
+57
View File
@@ -3,6 +3,63 @@ 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 args = argv.slice(1).filter((a) => !a.startsWith('-'))
let a = 1
+57
View File
@@ -3,6 +3,63 @@ 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 sec = parseFloat(argv[1] || '0')
if (Number.isNaN(sec) || sec < 0) {
+57
View File
@@ -3,6 +3,63 @@ 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 vfs = ctx.vfs
const files = argv.slice(1).filter((a) => !a.startsWith('-'))
+57
View File
@@ -3,6 +3,63 @@ 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 vfs = ctx.vfs
let n = 10
+57
View File
@@ -3,6 +3,63 @@ 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 evalTest(ctx, args) {
if (!args.length) return false
if (args[0] === '!') {
+57
View File
@@ -3,6 +3,63 @@ 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 files = argv.slice(1).filter((a) => !a.startsWith('-'))
if (!files.length) {
+57
View File
@@ -3,6 +3,63 @@ 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.exitCode = 0
}
+57
View File
@@ -3,6 +3,63 @@ 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 tty = globalThis.process?.stdout?.isTTY
if (tty) {
+57
View File
@@ -3,6 +3,63 @@ 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 flagArgs = argv.slice(1).filter((a) => a.startsWith('-') && a !== '--')
const all = argv.includes('-a')
+57
View File
@@ -3,6 +3,63 @@ 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 count(s) {
const lines = (s.match(/\n/g) || []).length
const words = s.trim() ? s.trim().split(/\s+/).length : 0
+57
View File
@@ -3,6 +3,63 @@ 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 joinDirFile(dir, name) {
if (!dir || dir === '.') return name
const d = dir.endsWith('/') ? dir.slice(0, -1) : dir
+57
View File
@@ -3,6 +3,63 @@ 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.log(ctx.vfs.env.USER || ctx.vfs.env.LOGNAME || 'guest')
}