update
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -10,6 +10,7 @@ const seederKernelBin = join(repoRoot, 'packages/bare-os-seeder/kernel/bin')
|
||||
const commands = [
|
||||
'basename',
|
||||
'cat',
|
||||
'chmod',
|
||||
'clear',
|
||||
'crontab',
|
||||
'date',
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,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/)'
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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('-'))
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 vfs = ctx.vfs
|
||||
let n = 10
|
||||
|
||||
@@ -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] === '!') {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user