Files
tsvm/assets/disk0/tvdos/include/fs.mjs
2026-05-15 23:36:14 +09:00

1130 lines
39 KiB
JavaScript

/*
* fs.mjs — NodeJS-compatible Filesystem module for TVDOS
*
* Wraps the TVDOS Kernel filesystem interface (`files.open()` and the
* TVDOSFileDescriptor methods exposed by TVDOS.SYS) with the NodeJS
* `fs` API surface. Both synchronous (`*Sync`) and callback-style
* asynchronous variants are provided, plus a `promises` namespace.
*
* The TSVM has no real concurrency, so all the "async" calls execute
* synchronously and then invoke the callback (or resolve the promise)
* immediately.
*
* Path handling
* -------------
* Paths must be one of:
* * Drive-prefixed absolute path: "A:/foo/bar" or "A:\\foo\\bar"
* * Special device path: "$:/CON", "$:/NUL", "$:/TMP/...", ...
* * Reserved single name: "CON", "NUL", "RND", ...
* * Relative / drive-less: resolved through the active shell
* (`_G.shell.resolvePathInput`) when
* available; otherwise prefixed with
* "A:" (the boot drive).
*
* Buffers
* -------
* TVDOS does not ship NodeJS Buffer. The functions return / accept
* `Uint8Array` instances when binary data is expected, and JavaScript
* strings when an encoding ("utf8" / "utf-8" / "binary" / "ascii") is
* supplied. `Uint8Array` instances also gain a `.toString(encoding)`
* shim when produced inside this module.
*
* Usage
* -----
* let fs = require("A:/tvdos/include/fs.mjs")
* let txt = fs.readFileSync("A:/etc/motd", "utf8")
* fs.writeFileSync("A:/tmp/hello.txt", "hi", "utf8")
* fs.readdirSync("A:/").forEach(println)
*/
///////////////////////////////////////////////////////////////////////////////
// Constants (NodeJS fs.constants subset)
///////////////////////////////////////////////////////////////////////////////
const F_OK = 0
const X_OK = 1
const W_OK = 2
const R_OK = 4
const O_RDONLY = 0
const O_WRONLY = 1
const O_RDWR = 2
const O_CREAT = 0o100
const O_EXCL = 0o200
const O_TRUNC = 0o1000
const O_APPEND = 0o2000
const S_IFMT = 0o170000
const S_IFREG = 0o100000
const S_IFDIR = 0o040000
const S_IFCHR = 0o020000
const S_IFBLK = 0o060000
const S_IFIFO = 0o010000
const S_IFLNK = 0o120000
const S_IFSOCK = 0o140000
const constants = {
F_OK, X_OK, W_OK, R_OK,
O_RDONLY, O_WRONLY, O_RDWR,
O_CREAT, O_EXCL, O_TRUNC, O_APPEND,
S_IFMT, S_IFREG, S_IFDIR, S_IFCHR, S_IFBLK, S_IFIFO, S_IFLNK, S_IFSOCK,
}
///////////////////////////////////////////////////////////////////////////////
// Internal helpers
///////////////////////////////////////////////////////////////////////////////
function _makeError(code, syscall, path, msg) {
let m = msg || (code + (path ? ", " + syscall + " '" + path + "'" : ""))
let e = new Error(m)
e.code = code
e.errno = -1
e.syscall = syscall
if (path !== undefined) e.path = path
return e
}
function _enoent(syscall, path) { return _makeError("ENOENT", syscall, path, "ENOENT: no such file or directory, " + syscall + " '" + path + "'") }
function _eexist(syscall, path) { return _makeError("EEXIST", syscall, path, "EEXIST: file already exists, " + syscall + " '" + path + "'") }
function _eisdir(syscall, path) { return _makeError("EISDIR", syscall, path, "EISDIR: illegal operation on a directory, " + syscall + " '" + path + "'") }
function _enotdir(syscall, path) { return _makeError("ENOTDIR", syscall, path, "ENOTDIR: not a directory, " + syscall + " '" + path + "'") }
function _ebadf(syscall) { return _makeError("EBADF", syscall, undefined, "EBADF: bad file descriptor, " + syscall) }
function _isReservedName(name) {
return (typeof files !== 'undefined') && files.reservedNames.includes(String(name).toUpperCase())
}
function _currentDriveLetter() {
if (typeof _G !== 'undefined' && _G.shell) {
if (typeof _G.shell.getCurrentDrive === 'function')
return _G.shell.getCurrentDrive()
if (_G.shell.CURRENT_DRIVE !== undefined)
return _G.shell.CURRENT_DRIVE
}
return "A"
}
/** Normalise a path for files.open(). */
function _normalisePath(path) {
if (path === undefined || path === null)
throw new TypeError("path is required")
if (typeof path !== 'string')
throw new TypeError("path must be a string")
if (path.length === 0)
throw new Error("path is empty")
// Reserved single name (CON, NUL, RND, ...)
if (_isReservedName(path)) return path.toUpperCase()
// Special device path: $:/something or $DEVxxx/...
if (path[0] === '$') return path
// Drive-prefixed absolute path
if (path.length >= 2 && path[1] === ':') return path
// Try the active shell first
if (typeof _G !== 'undefined' && _G.shell &&
typeof _G.shell.resolvePathInput === 'function') {
try {
let r = _G.shell.resolvePathInput(path)
if (r && r.full) return r.full
} catch (_) { /* fall through */ }
}
// Fallback: assume an absolute path on the current drive
let d = _currentDriveLetter()
if (path[0] === '/' || path[0] === '\\') return d + ":" + path
return d + ":/" + path
}
/** Open and return a TVDOSFileDescriptor; throws on bad arguments. */
function _fd(path) {
return files.open(_normalisePath(path))
}
function _bytesToString(bytes) {
let s = ''
let len = bytes.length
for (let i = 0; i < len; i++) {
let b = bytes[i]
s += String.fromCharCode((b < 0) ? (256 + b) : (b & 0xff))
}
return s
}
function _stringToU8(str) {
let buf = new Uint8Array(str.length)
for (let i = 0; i < str.length; i++) {
buf[i] = str.charCodeAt(i) & 0xff
}
return _attachToString(buf)
}
function _arrayToU8(arr) {
let buf = new Uint8Array(arr.length)
for (let i = 0; i < arr.length; i++) {
let b = arr[i]
buf[i] = (b < 0) ? (256 + b) & 0xff : b & 0xff
}
return _attachToString(buf)
}
/** Add a NodeJS-Buffer-like .toString(encoding) shim onto a Uint8Array. */
function _attachToString(u8) {
if (u8.__tvdosFsToString) return u8
Object.defineProperty(u8, 'toString', {
value: function (encoding, start, end) {
let s = (start === undefined) ? 0 : (start | 0)
let e = (end === undefined) ? this.length : (end | 0)
if (s < 0) s = 0
if (e > this.length) e = this.length
if (e < s) e = s
let enc = (encoding || 'utf8').toLowerCase()
if (enc === 'hex') {
let out = ''
for (let i = s; i < e; i++) out += (this[i] < 16 ? '0' : '') + this[i].toString(16)
return out
}
if (enc === 'base64') {
let raw = ''
for (let i = s; i < e; i++) raw += String.fromCharCode(this[i])
if (typeof base64 !== 'undefined' && base64.encode) return base64.encode(raw)
return raw
}
// utf8/utf-8/binary/latin1/ascii — TSVM strings are all 8-bit clean
let s2 = ''
for (let i = s; i < e; i++) s2 += String.fromCharCode(this[i])
return s2
},
enumerable: false, configurable: true, writable: true,
})
Object.defineProperty(u8, '__tvdosFsToString', {
value: true, enumerable: false,
})
return u8
}
/** Resolve `data` argument to a Uint8Array regardless of the input shape. */
function _toBytes(data, encoding) {
if (data === undefined || data === null)
throw new TypeError("data is required")
if (data instanceof Uint8Array) return data
if (data instanceof Int8Array) return _arrayToU8(data)
if (Array.isArray(data)) return _arrayToU8(data)
if (typeof data === 'string') return _stringToU8(data)
// Buffer-like with .length and indexed access
if (typeof data.length === 'number') return _arrayToU8(data)
throw new TypeError("Unsupported data type")
}
/** Parse NodeJS open()-style flags string into capability flags. */
function _parseFlags(flags) {
if (flags === undefined || flags === null) flags = 'r'
if (typeof flags === 'number') {
return {
read: ((flags & 3) === O_RDONLY) || ((flags & 3) === O_RDWR),
write: ((flags & 3) === O_WRONLY) || ((flags & 3) === O_RDWR),
create: (flags & O_CREAT) !== 0,
exclusive: (flags & O_EXCL) !== 0,
truncate: (flags & O_TRUNC) !== 0,
append: (flags & O_APPEND) !== 0,
raw: flags,
}
}
let f = String(flags)
let r = { read: false, write: false, create: false, exclusive: false, truncate: false, append: false, raw: f }
switch (f) {
case 'r': r.read = true; break
case 'r+': r.read = true; r.write = true; break
case 'rs': // deprecated alias
case 'sr': r.read = true; break
case 'rs+':
case 'sr+': r.read = true; r.write = true; break
case 'w': r.write = true; r.create = true; r.truncate = true; break
case 'wx':
case 'xw': r.write = true; r.create = true; r.truncate = true; r.exclusive = true; break
case 'w+': r.read = true; r.write = true; r.create = true; r.truncate = true; break
case 'wx+':
case 'xw+': r.read = true; r.write = true; r.create = true; r.truncate = true; r.exclusive = true; break
case 'a': r.write = true; r.create = true; r.append = true; break
case 'ax':
case 'xa': r.write = true; r.create = true; r.append = true; r.exclusive = true; break
case 'a+': r.read = true; r.write = true; r.create = true; r.append = true; break
case 'ax+':
case 'xa+': r.read = true; r.write = true; r.create = true; r.append = true; r.exclusive = true; break
default: throw new TypeError("Unknown file open flag: " + flags)
}
return r
}
///////////////////////////////////////////////////////////////////////////////
// Stats / Dirent
///////////////////////////////////////////////////////////////////////////////
function _now() {
let ms = (typeof sys !== 'undefined' && sys.currentTimeInMills) ? sys.currentTimeInMills() : 0
return new Date(ms)
}
class Stats {
constructor(fd) {
let isDir = false
let size = 0
try {
isDir = fd.isDirectory
} catch (_) { /* device files may misbehave */ }
if (!isDir) {
try { size = fd.size } catch (_) { size = 0 }
}
this.dev = 0
this.ino = 0
this.mode = isDir ? (S_IFDIR | 0o755) : (S_IFREG | 0o644)
this.nlink = 1
this.uid = 0
this.gid = 0
this.rdev = 0
this.size = size
this.blksize = 4096
this.blocks = (size + 511) >> 9
let t = _now()
this.atime = t
this.mtime = t
this.ctime = t
this.birthtime = t
let tms = t.getTime()
this.atimeMs = tms
this.mtimeMs = tms
this.ctimeMs = tms
this.birthtimeMs = tms
this._isDir = isDir
}
isFile() { return !this._isDir }
isDirectory() { return this._isDir }
isBlockDevice() { return false }
isCharacterDevice(){ return false }
isSymbolicLink() { return false }
isFIFO() { return false }
isSocket() { return false }
}
class Dirent {
constructor(name, parentPath, isDir) {
this.name = name
this.parentPath = parentPath
this.path = parentPath
this._isDir = !!isDir
}
isFile() { return !this._isDir }
isDirectory() { return this._isDir }
isBlockDevice() { return false }
isCharacterDevice(){ return false }
isSymbolicLink() { return false }
isFIFO() { return false }
isSocket() { return false }
}
///////////////////////////////////////////////////////////////////////////////
// File descriptor table (TVDOS does not expose integer fds, so we synthesise)
///////////////////////////////////////////////////////////////////////////////
let _fdCounter = 3 // reserve 0/1/2 for stdio
const _fdTable = {}
function _allocFd(entry) {
let id = _fdCounter++
_fdTable[id] = entry
return id
}
function _getFd(fd, syscall) {
if (typeof fd !== 'number' || _fdTable[fd] === undefined)
throw _ebadf(syscall || 'read')
return _fdTable[fd]
}
///////////////////////////////////////////////////////////////////////////////
// Synchronous API
///////////////////////////////////////////////////////////////////////////////
function existsSync(path) {
try {
return _fd(path).exists
} catch (_) {
return false
}
}
function accessSync(path, mode) {
let fd = _fd(path)
if (!fd.exists) throw _enoent('access', path)
// TVDOS has no permission bits — every existing file is read/write/exec.
return undefined
}
function statSync(path, options) {
let fd = _fd(path)
let throwIfNoEntry = !options || options.throwIfNoEntry !== false
if (!fd.exists) {
if (throwIfNoEntry) throw _enoent('stat', path)
return undefined
}
return new Stats(fd)
}
function lstatSync(path, options) {
// TVDOS has no symlinks, so lstat == stat.
return statSync(path, options)
}
function fstatSync(fd, options) {
let entry = _getFd(fd, 'fstat')
return new Stats(entry.descriptor)
}
function readFileSync(path, options) {
let encoding = null
if (typeof options === 'string') encoding = options
else if (options && options.encoding) encoding = options.encoding
// fd path
if (typeof path === 'number') {
let entry = _getFd(path, 'read')
let bytes = entry.buffer
if (encoding) return _attachToString(bytes).toString(encoding)
return _attachToString(new Uint8Array(bytes))
}
let fd = _fd(path)
if (!fd.exists) throw _enoent('open', path)
if (fd.isDirectory) throw _eisdir('read', path)
if (encoding) {
// sread() is a single Kernel round-trip and returns a JS string.
let s = fd.sread()
try { fd.close() } catch (_) {}
// The string is already 8-bit-clean; for utf8/utf-8/binary/ascii we
// return it as-is. Hex/base64 require the byte view.
let enc = encoding.toLowerCase()
if (enc === 'utf8' || enc === 'utf-8' || enc === 'binary' || enc === 'latin1' || enc === 'ascii')
return s
return _attachToString(_stringToU8(s)).toString(encoding)
}
let bytes = fd.bread()
try { fd.close() } catch (_) {}
return _arrayToU8(bytes)
}
function writeFileSync(path, data, options) {
let encoding = 'utf8'
let flag = 'w'
if (typeof options === 'string') {
encoding = options
} else if (options) {
if (options.encoding) encoding = options.encoding
if (options.flag) flag = options.flag
}
// fd path
if (typeof path === 'number') {
let entry = _getFd(path, 'write')
if (typeof data === 'string') {
entry.descriptor.swrite(data)
} else {
let u8 = _toBytes(data, encoding)
entry.descriptor.bwrite(Array.from(u8))
}
try { entry.descriptor.flush() } catch (_) {}
return undefined
}
let fd = _fd(path)
let parsed = _parseFlags(flag)
if (parsed.exclusive && fd.exists)
throw _eexist('open', path)
if (!fd.exists) fd.mkFile()
if (parsed.append && fd.exists && !parsed.truncate) {
if (typeof data === 'string') {
fd.sappend(data)
} else {
let u8 = _toBytes(data, encoding)
fd.bappend(Array.from(u8))
}
} else {
if (typeof data === 'string') {
fd.swrite(data)
} else {
let u8 = _toBytes(data, encoding)
fd.bwrite(Array.from(u8))
}
}
try { fd.flush() } catch (_) {}
try { fd.close() } catch (_) {}
return undefined
}
function appendFileSync(path, data, options) {
let encoding = 'utf8'
if (typeof options === 'string') encoding = options
else if (options && options.encoding) encoding = options.encoding
if (typeof path === 'number') {
let entry = _getFd(path, 'write')
if (typeof data === 'string') entry.descriptor.sappend(data)
else entry.descriptor.bappend(Array.from(_toBytes(data, encoding)))
try { entry.descriptor.flush() } catch (_) {}
return undefined
}
let fd = _fd(path)
if (!fd.exists) fd.mkFile()
if (typeof data === 'string') fd.sappend(data)
else fd.bappend(Array.from(_toBytes(data, encoding)))
try { fd.flush() } catch (_) {}
try { fd.close() } catch (_) {}
return undefined
}
function unlinkSync(path) {
let fd = _fd(path)
if (!fd.exists) throw _enoent('unlink', path)
if (fd.isDirectory) throw _eisdir('unlink', path)
let r = fd.remove()
if (r !== 0 && r !== true && r !== undefined)
throw _makeError('EIO', 'unlink', path, "removing failed with status " + r)
return undefined
}
function rmdirSync(path, options) {
let recursive = !!(options && options.recursive)
let fd = _fd(path)
if (!fd.exists) throw _enoent('rmdir', path)
if (!fd.isDirectory) throw _enotdir('rmdir', path)
if (recursive) {
_rmRecursive(fd)
} else {
let r = fd.remove()
if (r !== 0 && r !== true && r !== undefined)
throw _makeError('ENOTEMPTY', 'rmdir', path, "directory not empty or removal failed (" + r + ")")
}
return undefined
}
function rmSync(path, options) {
let recursive = !!(options && options.recursive)
let force = !!(options && options.force)
let fd
try { fd = _fd(path) }
catch (e) {
if (force) return undefined
throw e
}
if (!fd.exists) {
if (force) return undefined
throw _enoent('stat', path)
}
if (fd.isDirectory) {
if (!recursive)
throw _makeError('EISDIR', 'unlink', path, "EISDIR: is a directory, use { recursive: true } to remove")
_rmRecursive(fd)
} else {
fd.remove()
}
return undefined
}
function _rmRecursive(fd) {
if (fd.isDirectory) {
let kids = fd.list() || []
for (let i = 0; i < kids.length; i++) {
let n = kids[i].name
if (n === '.' || n === '..' || n === '') continue
_rmRecursive(kids[i])
}
}
fd.remove()
}
function mkdirSync(path, options) {
let recursive = false
if (typeof options === 'object' && options) {
if (options.recursive !== undefined) recursive = !!options.recursive
}
let np = _normalisePath(path)
let fd = files.open(np)
if (fd.exists) {
if (recursive) return undefined
throw _eexist('mkdir', path)
}
if (recursive) {
// Walk parents and create missing ones first. fd.parentPath does not
// include the drive letter, so reattach it.
let parent = files.open(fd.driveLetter + ":" + fd.parentPath)
if (!parent.exists && parent.path && parent.path !== "\\" && parent.path !== "")
mkdirSync(parent.fullPath, { recursive: true })
let ok = fd.mkDir()
if (!ok) throw _makeError('EIO', 'mkdir', path, "mkdir failed")
return fd.fullPath
}
let ok = fd.mkDir()
if (!ok) throw _makeError('EIO', 'mkdir', path, "mkdir failed")
return undefined
}
function readdirSync(path, options) {
let withFileTypes = !!(options && options.withFileTypes)
let fd = _fd(path)
if (!fd.exists) throw _enoent('scandir', path)
if (!fd.isDirectory) throw _enotdir('scandir', path)
let kids = fd.list() || []
let parentFull = fd.fullPath
let result = []
for (let i = 0; i < kids.length; i++) {
let name = kids[i].name
if (name === '.' || name === '..' || name === '') continue
if (withFileTypes) {
result.push(new Dirent(name, parentFull, kids[i].isDirectory))
} else {
result.push(name)
}
}
return result
}
function renameSync(oldPath, newPath) {
let src = _fd(oldPath)
if (!src.exists) throw _enoent('rename', oldPath)
if (src.isDirectory)
throw _makeError('EPERM', 'rename', oldPath, "EPERM: rename of directories is not supported")
let dst = _fd(newPath)
if (dst.exists && dst.isDirectory)
throw _eisdir('rename', newPath)
let bytes = src.bread()
if (!dst.exists) dst.mkFile()
dst.bwrite(bytes)
try { dst.flush() } catch (_) {}
try { dst.close() } catch (_) {}
src.remove()
return undefined
}
function copyFileSync(src, dest, mode) {
let s = _fd(src)
if (!s.exists) throw _enoent('copyfile', src)
if (s.isDirectory) throw _eisdir('copyfile', src)
let d = _fd(dest)
// mode: COPYFILE_EXCL = 1
if ((mode | 0) & 1) {
if (d.exists) throw _eexist('copyfile', dest)
}
if (d.isDirectory) throw _eisdir('copyfile', dest)
if (!d.exists) d.mkFile()
d.bwrite(s.bread())
try { d.flush() } catch (_) {}
try { d.close() } catch (_) {}
try { s.close() } catch (_) {}
return undefined
}
function cpSync(src, dest, options) {
let recursive = !!(options && options.recursive)
let force = (options && options.force === false) ? false : true
let s = _fd(src)
if (!s.exists) throw _enoent('cp', src)
if (s.isDirectory) {
if (!recursive)
throw _makeError('EISDIR', 'cp', src, "EISDIR: source is a directory, use { recursive: true }")
_cpDir(s, _fd(dest), force)
} else {
copyFileSync(src, dest, force ? 0 : 1)
}
return undefined
}
function _cpDir(srcFd, destFd, force) {
if (!destFd.exists) destFd.mkDir()
let kids = srcFd.list() || []
for (let i = 0; i < kids.length; i++) {
let n = kids[i].name
if (n === '.' || n === '..' || n === '') continue
let child = kids[i]
let childDest = files.open(destFd.fullPath + "/" + n)
if (child.isDirectory) {
_cpDir(child, childDest, force)
} else {
if (childDest.exists && !force) continue
if (!childDest.exists) childDest.mkFile()
childDest.bwrite(child.bread())
try { childDest.flush() } catch (_) {}
try { childDest.close() } catch (_) {}
}
}
}
function realpathSync(path, options) {
let fd = _fd(path)
if (!fd.exists) throw _enoent('realpath', path)
return fd.fullPath
}
function truncateSync(path, len) {
if (len === undefined) len = 0
if (len < 0) len = 0
let fd = _fd(path)
if (!fd.exists) throw _enoent('open', path)
if (fd.isDirectory) throw _eisdir('truncate', path)
let bytes = fd.bread()
let cur = bytes.length
let out
if (len <= cur) {
out = bytes.slice(0, len)
} else {
out = new Array(len)
for (let i = 0; i < cur; i++) out[i] = bytes[i]
for (let i = cur; i < len; i++) out[i] = 0
}
fd.bwrite(out)
try { fd.flush() } catch (_) {}
try { fd.close() } catch (_) {}
return undefined
}
function ftruncateSync(fd, len) {
let entry = _getFd(fd, 'ftruncate')
return truncateSync(entry.descriptor.fullPath, len)
}
function utimesSync(path, atime, mtime) {
// TVDOS does not record timestamps; treat as a touch().
let fd = _fd(path)
if (!fd.exists) throw _enoent('utime', path)
fd.touch()
return undefined
}
function futimesSync(fd, atime, mtime) {
let entry = _getFd(fd, 'futimes')
entry.descriptor.touch()
return undefined
}
function lutimesSync(path, atime, mtime) { return utimesSync(path, atime, mtime) }
function chmodSync(path, mode) { /* no perm bits in TVDOS */ return undefined }
function lchmodSync(path, mode) { return undefined }
function fchmodSync(fd, mode) { _getFd(fd, 'fchmod'); return undefined }
function chownSync(path, uid, gid) { return undefined }
function lchownSync(path, uid, gid) { return undefined }
function fchownSync(fd, uid, gid) { _getFd(fd, 'fchown'); return undefined }
function readlinkSync(path, options) {
// No symlinks in TVDOS — every existing node is its own target.
let fd = _fd(path)
if (!fd.exists) throw _enoent('readlink', path)
return fd.fullPath
}
function symlinkSync(target, path, type) { throw _makeError('ENOSYS', 'symlink', path, "ENOSYS: symlinks not supported on TVDOS") }
function linkSync(existingPath, newPath) { return copyFileSync(existingPath, newPath, 0) }
///////////////////////////////////////////////////////////////////////////////
// open / close / read / write (fd-style)
///////////////////////////////////////////////////////////////////////////////
function openSync(path, flags, mode) {
let parsed = _parseFlags(flags)
let fd = _fd(path)
if (parsed.exclusive && fd.exists)
throw _eexist('open', path)
if (!fd.exists) {
if (!parsed.create && !parsed.write)
throw _enoent('open', path)
if (parsed.create || parsed.write)
fd.mkFile()
}
// Read existing contents (truncate -> empty).
let buffer
if (parsed.truncate) {
buffer = []
} else {
try { buffer = fd.bread() } catch (_) { buffer = [] }
}
let position = parsed.append ? buffer.length : 0
let entry = {
descriptor: fd,
flags: parsed,
position: position,
buffer: buffer,
dirty: parsed.truncate,
path: fd.fullPath,
}
return _allocFd(entry)
}
function closeSync(fd) {
let entry = _getFd(fd, 'close')
if (entry.dirty) {
entry.descriptor.bwrite(entry.buffer)
try { entry.descriptor.flush() } catch (_) {}
}
try { entry.descriptor.close() } catch (_) {}
delete _fdTable[fd]
return undefined
}
/**
* readSync(fd, buffer, offset, length, position)
* buffer Uint8Array / Int8Array / Array
* offset where in `buffer` to start writing
* length bytes to read
* position position in the file, or null for current
*/
function readSync(fd, buffer, offset, length, position) {
let entry = _getFd(fd, 'read')
if (!entry.flags.read) throw _ebadf('read')
if (typeof offset === 'object' && offset !== null) {
// readSync(fd, buffer, options)
let opts = offset
offset = opts.offset === undefined ? 0 : opts.offset
length = opts.length === undefined ? (buffer.length - offset) : opts.length
position = opts.position === undefined ? null : opts.position
}
if (offset === undefined) offset = 0
if (length === undefined) length = buffer.length - offset
if (position === null || position === undefined) position = entry.position
let src = entry.buffer
let avail = src.length - position
if (avail < 0) avail = 0
let n = Math.min(length, avail)
for (let i = 0; i < n; i++) {
let b = src[position + i]
buffer[offset + i] = (b < 0) ? (256 + b) & 0xff : b & 0xff
}
if (arguments.length < 5 || position === entry.position)
entry.position += n
return n
}
/**
* writeSync(fd, buffer, offset, length, position)
* writeSync(fd, string [, position [, encoding]])
*/
function writeSync(fd, buffer, offset, length, position) {
let entry = _getFd(fd, 'write')
if (!entry.flags.write) throw _ebadf('write')
let bytes
let writeLen
let writePos
if (typeof buffer === 'string') {
let str = buffer
let pos = (typeof offset === 'number') ? offset : null
bytes = []
for (let i = 0; i < str.length; i++) bytes.push(str.charCodeAt(i) & 0xff)
writeLen = bytes.length
writePos = (pos === null || pos === undefined) ? entry.position : pos
} else {
let off = (offset === undefined) ? 0 : offset
let len = (length === undefined) ? (buffer.length - off) : length
let pos = (position === undefined || position === null) ? entry.position : position
bytes = []
for (let i = 0; i < len; i++) {
let b = buffer[off + i]
bytes.push((b < 0) ? (256 + b) & 0xff : b & 0xff)
}
writeLen = len
writePos = pos
}
if (entry.flags.append) writePos = entry.buffer.length
let buf = entry.buffer
let needed = writePos + writeLen
if (needed > buf.length) {
// grow
let grown = new Array(needed)
for (let i = 0; i < buf.length; i++) grown[i] = buf[i]
for (let i = buf.length; i < writePos; i++) grown[i] = 0
buf = grown
}
for (let i = 0; i < writeLen; i++) buf[writePos + i] = bytes[i]
entry.buffer = buf
entry.dirty = true
if (position === undefined || position === null || entry.flags.append) {
entry.position = writePos + writeLen
}
return writeLen
}
function fsyncSync(fd) {
let entry = _getFd(fd, 'fsync')
if (entry.dirty) {
entry.descriptor.bwrite(entry.buffer)
entry.dirty = false
}
try { entry.descriptor.flush() } catch (_) {}
return undefined
}
function fdatasyncSync(fd) { return fsyncSync(fd) }
///////////////////////////////////////////////////////////////////////////////
// Asynchronous (callback) wrappers
///////////////////////////////////////////////////////////////////////////////
function _maybeAsync(syncFn, args, hasCallback) {
let cb = hasCallback ? args[args.length - 1] : undefined
if (typeof cb !== 'function') {
// no callback supplied — silently swallow result; matches old node behaviour
try { syncFn.apply(null, args) } catch (_) {}
return
}
let realArgs = Array.prototype.slice.call(args, 0, args.length - 1)
try {
let result = syncFn.apply(null, realArgs)
cb(null, result)
} catch (e) {
cb(e)
}
}
function exists(path, callback) {
if (typeof callback !== 'function') return
callback(existsSync(path))
}
function access(path, mode, callback) {
if (typeof mode === 'function') { callback = mode; mode = F_OK }
try { accessSync(path, mode); callback(null) } catch (e) { callback(e) }
}
function stat(path, options, callback) {
if (typeof options === 'function') { callback = options; options = undefined }
try { callback(null, statSync(path, options)) } catch (e) { callback(e) }
}
function lstat(path, options, callback) {
if (typeof options === 'function') { callback = options; options = undefined }
try { callback(null, lstatSync(path, options)) } catch (e) { callback(e) }
}
function fstat(fd, options, callback) {
if (typeof options === 'function') { callback = options; options = undefined }
try { callback(null, fstatSync(fd, options)) } catch (e) { callback(e) }
}
function readFile(path, options, callback) {
if (typeof options === 'function') { callback = options; options = undefined }
try { callback(null, readFileSync(path, options)) } catch (e) { callback(e) }
}
function writeFile(path, data, options, callback) {
if (typeof options === 'function') { callback = options; options = undefined }
try { writeFileSync(path, data, options); callback(null) } catch (e) { callback(e) }
}
function appendFile(path, data, options, callback) {
if (typeof options === 'function') { callback = options; options = undefined }
try { appendFileSync(path, data, options); callback(null) } catch (e) { callback(e) }
}
function unlink(path, callback) { try { unlinkSync(path); callback(null) } catch (e) { callback(e) } }
function rmdir(path, options, callback) {
if (typeof options === 'function') { callback = options; options = undefined }
try { rmdirSync(path, options); callback(null) } catch (e) { callback(e) }
}
function rm(path, options, callback) {
if (typeof options === 'function') { callback = options; options = undefined }
try { rmSync(path, options); callback(null) } catch (e) { callback(e) }
}
function mkdir(path, options, callback) {
if (typeof options === 'function') { callback = options; options = undefined }
try { callback(null, mkdirSync(path, options)) } catch (e) { callback(e) }
}
function readdir(path, options, callback) {
if (typeof options === 'function') { callback = options; options = undefined }
try { callback(null, readdirSync(path, options)) } catch (e) { callback(e) }
}
function rename(o, n, callback) { try { renameSync(o, n); callback(null) } catch (e) { callback(e) } }
function copyFile(s, d, mode, callback) {
if (typeof mode === 'function') { callback = mode; mode = 0 }
try { copyFileSync(s, d, mode); callback(null) } catch (e) { callback(e) }
}
function cp(s, d, options, callback) {
if (typeof options === 'function') { callback = options; options = undefined }
try { cpSync(s, d, options); callback(null) } catch (e) { callback(e) }
}
function realpath(path, options, callback) {
if (typeof options === 'function') { callback = options; options = undefined }
try { callback(null, realpathSync(path, options)) } catch (e) { callback(e) }
}
function truncate(path, len, callback) {
if (typeof len === 'function') { callback = len; len = 0 }
try { truncateSync(path, len); callback(null) } catch (e) { callback(e) }
}
function ftruncate(fd, len, callback) {
if (typeof len === 'function') { callback = len; len = 0 }
try { ftruncateSync(fd, len); callback(null) } catch (e) { callback(e) }
}
function utimes(path, a, m, callback) { try { utimesSync(path, a, m); callback(null) } catch (e) { callback(e) } }
function futimes(fd, a, m, callback) { try { futimesSync(fd, a, m); callback(null) } catch (e) { callback(e) } }
function lutimes(path, a, m, callback) { try { lutimesSync(path, a, m); callback(null) } catch (e) { callback(e) } }
function chmod(path, m, callback) { try { chmodSync(path, m); callback(null) } catch (e) { callback(e) } }
function lchmod(path, m, callback) { try { lchmodSync(path, m); callback(null) } catch (e) { callback(e) } }
function fchmod(fd, m, callback) { try { fchmodSync(fd, m); callback(null) } catch (e) { callback(e) } }
function chown(path, u, g, callback) { try { chownSync(path, u, g); callback(null) } catch (e) { callback(e) } }
function lchown(path, u, g, callback) { try { lchownSync(path, u, g); callback(null) } catch (e) { callback(e) } }
function fchown(fd, u, g, callback) { try { fchownSync(fd, u, g); callback(null) } catch (e) { callback(e) } }
function readlink(path, options, callback) {
if (typeof options === 'function') { callback = options; options = undefined }
try { callback(null, readlinkSync(path, options)) } catch (e) { callback(e) }
}
function symlink(target, path, type, callback) {
if (typeof type === 'function') { callback = type; type = undefined }
try { symlinkSync(target, path, type); callback(null) } catch (e) { callback(e) }
}
function link(existingPath, newPath, callback) {
try { linkSync(existingPath, newPath); callback(null) } catch (e) { callback(e) }
}
function open(path, flags, mode, callback) {
if (typeof flags === 'function') { callback = flags; flags = 'r'; mode = 0o666 }
else if (typeof mode === 'function') { callback = mode; mode = 0o666 }
try { callback(null, openSync(path, flags, mode)) } catch (e) { callback(e) }
}
function close(fd, callback) {
try { closeSync(fd); if (callback) callback(null) } catch (e) { if (callback) callback(e) }
}
function read(fd, buffer, offset, length, position, callback) {
// Variadic — collapse to (fd, buffer, offset, length, position, cb).
if (typeof offset === 'function') { callback = offset; offset = undefined; length = undefined; position = undefined }
else if (typeof length === 'function') { callback = length; length = undefined; position = undefined }
else if (typeof position === 'function') { callback = position; position = undefined }
try {
let n = readSync(fd, buffer, offset, length, position)
callback(null, n, buffer)
} catch (e) { callback(e) }
}
function write(fd, buffer, offset, length, position, callback) {
if (typeof offset === 'function') { callback = offset; offset = undefined; length = undefined; position = undefined }
else if (typeof length === 'function') { callback = length; length = undefined; position = undefined }
else if (typeof position === 'function') { callback = position; position = undefined }
try {
let n = writeSync(fd, buffer, offset, length, position)
callback(null, n, buffer)
} catch (e) { callback(e) }
}
function fsync(fd, callback) { try { fsyncSync(fd); callback(null) } catch (e) { callback(e) } }
function fdatasync(fd, callback) { try { fdatasyncSync(fd); callback(null) } catch (e) { callback(e) } }
///////////////////////////////////////////////////////////////////////////////
// Promise API (only when the host JS engine supplies Promise)
///////////////////////////////////////////////////////////////////////////////
let promises = undefined
if (typeof Promise !== 'undefined') {
let _wrap = function (syncFn) {
return function () {
let args = arguments
return new Promise(function (resolve, reject) {
try { resolve(syncFn.apply(null, args)) }
catch (e) { reject(e) }
})
}
}
promises = {
access: _wrap(accessSync),
appendFile: _wrap(appendFileSync),
chmod: _wrap(chmodSync),
chown: _wrap(chownSync),
copyFile: _wrap(copyFileSync),
cp: _wrap(cpSync),
lchmod: _wrap(lchmodSync),
lchown: _wrap(lchownSync),
link: _wrap(linkSync),
lstat: _wrap(lstatSync),
lutimes: _wrap(lutimesSync),
mkdir: _wrap(mkdirSync),
readdir: _wrap(readdirSync),
readFile: _wrap(readFileSync),
readlink: _wrap(readlinkSync),
realpath: _wrap(realpathSync),
rename: _wrap(renameSync),
rm: _wrap(rmSync),
rmdir: _wrap(rmdirSync),
stat: _wrap(statSync),
symlink: _wrap(symlinkSync),
truncate: _wrap(truncateSync),
unlink: _wrap(unlinkSync),
utimes: _wrap(utimesSync),
writeFile: _wrap(writeFileSync),
}
}
///////////////////////////////////////////////////////////////////////////////
// Module exports
///////////////////////////////////////////////////////////////////////////////
exports = {
// constants
constants,
F_OK, X_OK, W_OK, R_OK,
O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_TRUNC, O_APPEND,
S_IFMT, S_IFREG, S_IFDIR, S_IFCHR, S_IFBLK, S_IFIFO, S_IFLNK, S_IFSOCK,
// classes
Stats, Dirent,
// sync
accessSync, existsSync, statSync, lstatSync, fstatSync,
readFileSync, writeFileSync, appendFileSync,
unlinkSync, rmdirSync, rmSync, mkdirSync, readdirSync,
renameSync, copyFileSync, cpSync,
realpathSync,
truncateSync, ftruncateSync,
utimesSync, futimesSync, lutimesSync,
chmodSync, lchmodSync, fchmodSync,
chownSync, lchownSync, fchownSync,
readlinkSync, symlinkSync, linkSync,
openSync, closeSync, readSync, writeSync,
fsyncSync, fdatasyncSync,
// async (callback)
access, exists, stat, lstat, fstat,
readFile, writeFile, appendFile,
unlink, rmdir, rm, mkdir, readdir,
rename, copyFile, cp,
realpath,
truncate, ftruncate,
utimes, futimes, lutimes,
chmod, lchmod, fchmod,
chown, lchown, fchown,
readlink, symlink, link,
open, close, read, write,
fsync, fdatasync,
// promise namespace (undefined if Promise is not available)
promises,
}