Files
infocom-systems-design/node_modules/bare-fs/index.js
2025-10-03 22:27:28 +03:00

2333 lines
46 KiB
JavaScript

const FIFO = require('fast-fifo')
const EventEmitter = require('bare-events')
const path = require('bare-path')
const { fileURLToPath } = require('bare-url')
const { Readable, Writable } = require('bare-stream')
const binding = require('./binding')
const constants = require('./lib/constants')
const FileError = require('./lib/errors')
const isWindows = Bare.platform === 'win32'
exports.constants = constants
class FileRequest {
static _free = []
static borrow() {
if (this._free.length > 0) return this._free.pop()
return new FileRequest()
}
static return(req) {
if (this._free.length < 32) this._free.push(req.reset())
else req.destroy()
}
constructor() {
this._reset()
this._handle = binding.requestInit(this, this._onresult)
}
get handle() {
return this._handle
}
retain(value) {
this._retain = value // Tie the lifetime of `value` to the lifetime of `this`
}
reset() {
if (this._handle === null) return this
binding.requestReset(this._handle)
this._reset()
return this
}
destroy() {
if (this._handle === null) return this
binding.requestDestroy(this._handle)
this._reset()
this._handle = null
return this
}
then(resolve, reject) {
return this._promise.then(resolve, reject)
}
return() {
if (this._handle === null) return this
FileRequest.return(this)
return this
}
[Symbol.dispose]() {
this.return()
}
_reset() {
const { promise, resolve, reject } = Promise.withResolvers()
this._promise = promise
this._resolve = resolve
this._reject = reject
this._retain = null
}
_onresult(err, status) {
if (err) this._reject(err)
else this._resolve(status)
}
}
function ok(result, cb) {
if (typeof result === 'function') {
cb = result
result = undefined
}
if (cb) cb(null, result)
else return result
}
function fail(err, cb) {
if (cb) cb(err)
else throw err
}
function done(err, result, cb) {
if (typeof result === 'function') {
cb = result
result = undefined
}
if (err) fail(err, cb)
else return ok(result, cb)
}
async function open(filepath, flags = 'r', mode = 0o666, cb) {
if (typeof flags === 'function') {
cb = flags
flags = 'r'
mode = 0o666
} else if (typeof mode === 'function') {
cb = mode
mode = 0o666
}
if (typeof flags === 'string') flags = toFlags(flags)
if (typeof mode === 'string') mode = toMode(mode)
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
let fd
let err = null
try {
binding.open(req.handle, filepath, flags, mode)
fd = await req
} catch (e) {
err = new FileError(e.message, {
operation: 'open',
code: e.code,
path: filepath
})
}
return done(err, fd, cb)
}
function openSync(filepath, flags = 'r', mode = 0o666) {
if (typeof flags === 'string') flags = toFlags(flags)
if (typeof mode === 'string') mode = toMode(mode)
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
try {
return binding.openSync(req.handle, filepath, flags, mode)
} catch (e) {
throw new FileError(e.message, {
operation: 'open',
code: e.code,
path: filepath
})
}
}
async function close(fd, cb) {
using req = FileRequest.borrow()
let err = null
try {
binding.close(req.handle, fd)
await req
} catch (e) {
err = new FileError(e.message, { operation: 'close', code: e.code, fd })
}
return done(err, cb)
}
function closeSync(fd) {
using req = FileRequest.borrow()
try {
binding.closeSync(req.handle, fd)
} catch (e) {
throw new FileError(e.message, { operation: 'close', code: e.code, fd })
}
}
async function access(filepath, mode = constants.F_OK, cb) {
if (typeof mode === 'function') {
cb = mode
mode = constants.F_OK
}
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
let err = null
try {
binding.access(req.handle, filepath, mode)
await req
} catch (e) {
err = new FileError(e.message, {
operation: 'access',
code: e.code,
path: filepath
})
}
return done(err, cb)
}
function accessSync(filepath, mode = constants.F_OK) {
using req = FileRequest.borrow()
filepath = toNamespacedPath(filepath)
try {
binding.accessSync(req.handle, filepath, mode)
} catch (e) {
throw new FileError(e.message, {
operation: 'access',
code: e.code,
path: filepath
})
}
}
async function exists(filepath, cb) {
let ok = true
try {
await access(filepath)
} catch {
ok = false
}
return done(null, ok, cb)
}
function existsSync(filepath) {
try {
accessSync(filepath)
} catch {
return false
}
return true
}
async function read(
fd,
buffer,
offset = 0,
len = buffer.byteLength - offset,
pos = -1,
cb
) {
if (typeof offset === 'function') {
cb = offset
offset = 0
len = buffer.byteLength
pos = -1
} else if (typeof len === 'function') {
cb = len
len = buffer.byteLength - offset
pos = -1
} else if (typeof pos === 'function') {
cb = pos
pos = -1
}
if (typeof pos !== 'number') pos = -1
using req = FileRequest.borrow()
let bytes
let err = null
try {
binding.read(req.handle, fd, buffer, offset, len, pos)
bytes = await req
} catch (e) {
err = new FileError(e.message, { operation: 'read', code: e.code, fd })
}
return done(err, bytes, cb)
}
function readSync(
fd,
buffer,
offset = 0,
len = buffer.byteLength - offset,
pos = -1
) {
using req = FileRequest.borrow()
try {
return binding.readSync(req.handle, fd, buffer, offset, len, pos)
} catch (e) {
throw new FileError(e.message, { operation: 'read', code: e.code, fd })
}
}
async function readv(fd, buffers, pos = -1, cb) {
if (typeof pos === 'function') {
cb = pos
pos = -1
}
if (typeof pos !== 'number') pos = -1
using req = FileRequest.borrow()
let bytes
let err = null
try {
binding.readv(req.handle, fd, buffers, pos)
bytes = await req
} catch (e) {
err = new FileError(e.message, { operation: 'readv', code: e.code, fd })
}
return done(err, bytes, cb)
}
function readvSync(fd, buffers, pos = -1) {
if (typeof pos !== 'number') pos = -1
using req = FileRequest.borrow()
try {
return binding.readvSync(req.handle, fd, buffers, pos)
} catch (e) {
throw new FileError(e.message, { operation: 'readv', code: e.code, fd })
}
}
async function write(fd, data, offset = 0, len, pos = -1, cb) {
if (typeof data === 'string') {
let encoding = len
cb = pos
pos = offset
if (typeof pos === 'function') {
cb = pos
pos = -1
encoding = 'utf8'
} else if (typeof encoding === 'function') {
cb = encoding
encoding = 'utf8'
}
if (typeof pos === 'string') {
encoding = pos
pos = -1
}
data = Buffer.from(data, encoding)
offset = 0
len = data.byteLength
} else if (typeof offset === 'function') {
cb = offset
offset = 0
len = data.byteLength
pos = -1
} else if (typeof len === 'function') {
cb = len
len = data.byteLength - offset
pos = -1
} else if (typeof pos === 'function') {
cb = pos
pos = -1
}
if (typeof len !== 'number') len = data.byteLength - offset
if (typeof pos !== 'number') pos = -1
using req = FileRequest.borrow()
let bytes
let err = null
try {
binding.write(req.handle, fd, data, offset, len, pos)
bytes = await req
} catch (e) {
err = new FileError(e.message, { operation: 'write', code: e.code, fd })
}
return done(err, bytes, cb)
}
function writeSync(fd, data, offset = 0, len, pos = -1) {
if (typeof data === 'string') {
let encoding = len
pos = offset
if (typeof pos === 'string') {
encoding = pos
pos = -1
}
data = Buffer.from(data, encoding)
offset = 0
len = data.byteLength
}
if (typeof len !== 'number') len = data.byteLength - offset
if (typeof pos !== 'number') pos = -1
using req = FileRequest.borrow()
try {
return binding.writeSync(req.handle, fd, data, offset, len, pos)
} catch (e) {
throw new FileError(e.message, { operation: 'write', code: e.code, fd })
}
}
async function writev(fd, buffers, pos = -1, cb) {
if (typeof pos === 'function') {
cb = pos
pos = -1
}
if (typeof pos !== 'number') pos = -1
using req = FileRequest.borrow()
let bytes
let err = null
try {
binding.writev(req.handle, fd, buffers, pos)
bytes = await req
} catch (e) {
err = new FileError(e.message, { operation: 'writev', code: e.code, fd })
}
return done(err, bytes, cb)
}
function writevSync(fd, buffers, pos = -1) {
if (typeof pos !== 'number') pos = -1
using req = FileRequest.borrow()
try {
return binding.writevSync(req.handle, fd, buffers, pos)
} catch (e) {
throw new FileError(e.message, { operation: 'writev', code: e.code, fd })
}
}
async function stat(filepath, cb) {
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
let st
let err = null
try {
binding.stat(req.handle, filepath)
await req
st = new Stats(...binding.requestResultStat(req.handle))
} catch (e) {
err = new FileError(e.message, {
operation: 'stat',
code: e.code,
path: filepath
})
}
return done(err, st, cb)
}
function statSync(filepath) {
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
try {
binding.statSync(req.handle, filepath)
return new Stats(...binding.requestResultStat(req.handle))
} catch (e) {
throw new FileError(e.message, {
operation: 'stat',
code: e.code,
path: filepath
})
}
}
async function lstat(filepath, cb) {
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
let st
let err = null
try {
binding.lstat(req.handle, filepath)
await req
st = new Stats(...binding.requestResultStat(req.handle))
} catch (e) {
err = new FileError(e.message, {
operation: 'lstat',
code: e.code,
path: filepath
})
}
return done(err, st, cb)
}
function lstatSync(filepath) {
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
try {
binding.lstatSync(req.handle, filepath)
return new Stats(...binding.requestResultStat(req.handle))
} catch (e) {
throw new FileError(e.message, {
operation: 'lstat',
code: e.code,
path: filepath
})
}
}
async function fstat(fd, cb) {
using req = FileRequest.borrow()
let st
let err = null
try {
binding.fstat(req.handle, fd)
await req
st = new Stats(...binding.requestResultStat(req.handle))
} catch (e) {
err = new FileError(e.message, { operation: 'fstat', code: e.code, fd })
}
return done(err, st, cb)
}
function fstatSync(fd) {
using req = FileRequest.borrow()
try {
binding.fstatSync(req.handle, fd)
return new Stats(...binding.requestResultStat(req.handle))
} catch (e) {
throw new FileError(e.message, { operation: 'fstat', code: e.code, fd })
}
}
async function ftruncate(fd, len = 0, cb) {
if (typeof len === 'function') {
cb = len
len = 0
}
if (typeof len !== 'number') len = 0
using req = FileRequest.borrow()
let err = null
try {
binding.ftruncate(req.handle, fd, len)
await req
} catch (e) {
err = new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
}
return done(err, cb)
}
function ftruncateSync(fd, len = 0) {
if (typeof len !== 'number') len = 0
using req = FileRequest.borrow()
try {
binding.ftruncateSync(req.handle, fd, len)
} catch (e) {
throw new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
}
}
async function chmod(filepath, mode, cb) {
if (typeof mode === 'string') mode = toMode(mode)
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
let err = null
try {
binding.chmod(req.handle, filepath, mode)
await req
} catch (e) {
err = new FileError(e.message, {
operation: 'chmod',
code: e.code,
path: filepath
})
}
return done(err, cb)
}
function chmodSync(filepath, mode) {
if (typeof mode === 'string') mode = toMode(mode)
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
try {
binding.chmodSync(req.handle, filepath, mode)
} catch (e) {
throw new FileError(e.message, {
operation: 'chmod',
code: e.code,
path: filepath
})
}
}
async function fchmod(fd, mode, cb) {
if (typeof mode === 'string') mode = toMode(mode)
using req = FileRequest.borrow()
let err = null
try {
binding.fchmod(req.handle, fd, mode)
await req
} catch (e) {
err = new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
}
return done(err, cb)
}
function fchmodSync(fd, mode) {
if (typeof mode === 'string') mode = toMode(mode)
using req = FileRequest.borrow()
try {
binding.fchmodSync(req.handle, fd, mode)
} catch (e) {
throw new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
}
}
async function utimes(filepath, atime, mtime, cb) {
if (typeof atime !== 'number') atime = atime.getTime() / 1000
if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
let err = null
try {
binding.utimes(req.handle, filepath, atime, mtime)
await req
} catch (e) {
err = new FileError(e.message, {
operation: 'utimes',
code: e.code,
path: filepath
})
}
return done(err, cb)
}
function utimesSync(filepath, atime, mtime) {
if (typeof atime !== 'number') atime = atime.getTime() / 1000
if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
try {
binding.utimesSync(req.handle, filepath, atime, mtime)
} catch (e) {
throw new FileError(e.message, {
operation: 'utimes',
code: e.code,
path: filepath
})
}
}
async function mkdir(filepath, opts, cb) {
if (typeof opts === 'function') {
cb = opts
opts = { mode: 0o777 }
}
if (typeof opts === 'number') opts = { mode: opts }
else if (!opts) opts = {}
const mode = typeof opts.mode === 'number' ? opts.mode : 0o777
filepath = toNamespacedPath(filepath)
if (opts.recursive) {
try {
await mkdir(filepath, { mode })
} catch (err) {
if (err.code !== 'ENOENT' && (await stat(filepath)).isDirectory()) {
return ok(cb)
}
while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
const i = filepath.lastIndexOf(path.sep)
if (i <= 0) return fail(err, cb)
await mkdir(filepath.slice(0, i), { mode, recursive: true })
try {
await mkdir(filepath, { mode })
} catch (err) {
if ((await stat(filepath)).isDirectory()) {
return ok(cb)
}
return fail(err, cb)
}
}
return ok(cb)
}
using req = FileRequest.borrow()
let err = null
try {
binding.mkdir(req.handle, filepath, mode)
await req
} catch (e) {
err = new FileError(e.message, {
operation: 'mkdir',
code: e.code,
path: filepath
})
}
return done(err, cb)
}
function mkdirSync(filepath, opts) {
if (typeof opts === 'number') opts = { mode: opts }
else if (!opts) opts = {}
const mode = typeof opts.mode === 'number' ? opts.mode : 0o777
filepath = toNamespacedPath(filepath)
if (opts.recursive) {
try {
mkdirSync(filepath, { mode })
} catch (err) {
if (err.code !== 'ENOENT' && statSync(filepath).isDirectory()) {
return
}
while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
const i = filepath.lastIndexOf(path.sep)
if (i <= 0) throw err
mkdirSync(filepath.slice(0, i), { mode, recursive: true })
try {
mkdirSync(filepath, { mode })
} catch (err) {
if (statSync(filepath).isDirectory()) {
return
}
throw err
}
}
return
}
using req = FileRequest.borrow()
try {
binding.mkdirSync(req.handle, filepath, mode)
} catch (e) {
throw new FileError(e.message, {
operation: 'mkdir',
code: e.code,
path: filepath
})
}
}
async function rmdir(filepath, cb) {
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
let err = null
try {
binding.rmdir(req.handle, filepath)
await req
} catch (e) {
err = new FileError(e.message, {
operation: 'rmdir',
code: e.code,
path: filepath
})
}
return done(err, cb)
}
function rmdirSync(filepath) {
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
try {
binding.rmdirSync(req.handle, filepath)
} catch (e) {
throw new FileError(e.message, {
operation: 'rmdir',
code: e.code,
path: filepath
})
}
}
async function rm(filepath, opts, cb) {
if (typeof opts === 'function') {
cb = opts
opts = {}
}
if (!opts) opts = {}
filepath = toNamespacedPath(filepath)
try {
const st = await lstat(filepath)
if (st.isDirectory()) {
if (opts.recursive) {
try {
await rmdir(filepath)
} catch (err) {
if (err.code !== 'ENOTEMPTY') throw err
const files = await readdir(filepath)
for (const file of files) {
await rm(filepath + path.sep + file, opts)
}
await rmdir(filepath)
}
} else {
fail(
new FileError('is a directory', {
operation: 'rm',
code: 'EISDIR',
path: filepath
}),
cb
)
}
} else {
await unlink(filepath)
}
} catch (err) {
if (err.code !== 'ENOENT' || !opts.force) fail(err, cb)
}
}
function rmSync(filepath, opts) {
if (!opts) opts = {}
filepath = toNamespacedPath(filepath)
try {
const st = lstatSync(filepath)
if (st.isDirectory()) {
if (opts.recursive) {
try {
rmdirSync(filepath)
} catch (err) {
if (err.code !== 'ENOTEMPTY') throw err
const files = readdirSync(filepath)
for (const file of files) {
rmSync(filepath + path.sep + file, opts)
}
rmdirSync(filepath)
}
} else {
throw new FileError('is a directory', {
operation: 'rm',
code: 'EISDIR',
path: filepath
})
}
} else {
unlinkSync(filepath)
}
} catch (err) {
if (err.code !== 'ENOENT' || !opts.force) throw err
}
}
async function unlink(filepath, cb) {
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
let err = null
try {
binding.unlink(req.handle, filepath)
await req
} catch (e) {
err = new FileError(e.message, {
operation: 'unlink',
code: e.code,
path: filepath
})
}
return done(err, cb)
}
function unlinkSync(filepath) {
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
try {
binding.unlinkSync(req.handle, filepath)
} catch (e) {
throw new FileError(e.message, {
operation: 'unlink',
code: e.code,
path: filepath
})
}
}
async function rename(src, dst, cb) {
src = toNamespacedPath(src)
dst = toNamespacedPath(dst)
using req = FileRequest.borrow()
let err = null
try {
binding.rename(req.handle, src, dst)
await req
} catch (e) {
err = new FileError(e.message, {
operation: 'rename',
code: e.code,
path: src,
destination: dst
})
}
return done(err, cb)
}
function renameSync(src, dst) {
src = toNamespacedPath(src)
dst = toNamespacedPath(dst)
using req = FileRequest.borrow()
try {
binding.renameSync(req.handle, src, dst)
} catch (e) {
throw new FileError(e.message, {
operation: 'rename',
code: e.code,
path: src,
destination: dst
})
}
}
async function copyFile(src, dst, mode = 0, cb) {
if (typeof mode === 'function') {
cb = mode
mode = 0
}
src = toNamespacedPath(src)
dst = toNamespacedPath(dst)
using req = FileRequest.borrow()
let err = null
try {
binding.copyfile(req.handle, src, dst, mode)
await req
} catch (e) {
err = new FileError(e.message, {
operation: 'copyfile',
code: e.code,
path: src,
destination: dst
})
}
return done(err, cb)
}
function copyFileSync(src, dst, mode = 0) {
src = toNamespacedPath(src)
dst = toNamespacedPath(dst)
using req = FileRequest.borrow()
try {
binding.copyfileSync(req.handle, src, dst, mode)
} catch (e) {
throw new FileError(e.message, {
operation: 'copyfile',
code: e.code,
path: src,
destination: dst
})
}
}
async function realpath(filepath, opts, cb) {
if (typeof opts === 'function') {
cb = opts
opts = {}
}
if (typeof opts === 'string') opts = { encoding: opts }
else if (!opts) opts = {}
const { encoding = 'utf8' } = opts
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
let res
let err = null
try {
binding.realpath(req.handle, filepath)
await req
res = Buffer.from(binding.requestResultString(req.handle))
if (encoding !== 'buffer') res = res.toString(encoding)
} catch (e) {
err = new FileError(e.message, {
operation: 'realpath',
code: e.code,
path: filepath
})
}
return done(err, res, cb)
}
function realpathSync(filepath, opts) {
if (typeof opts === 'string') opts = { encoding: opts }
else if (!opts) opts = {}
const { encoding = 'utf8' } = opts
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
try {
binding.realpathSync(req.handle, filepath)
let res = Buffer.from(binding.requestResultString(req.handle))
if (encoding !== 'buffer') res = res.toString(encoding)
return res
} catch (e) {
throw new FileError(e.message, {
operation: 'realpath',
code: e.code,
path: filepath
})
}
}
async function readlink(filepath, opts, cb) {
if (typeof opts === 'function') {
cb = opts
opts = {}
}
if (typeof opts === 'string') opts = { encoding: opts }
else if (!opts) opts = {}
const { encoding = 'utf8' } = opts
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
let res
let err = null
try {
binding.readlink(req.handle, filepath)
await req
res = Buffer.from(binding.requestResultString(req.handle))
if (encoding !== 'buffer') res = res.toString(encoding)
} catch (e) {
err = new FileError(e.message, {
operation: 'readlink',
code: e.code,
path: filepath
})
}
return done(err, res, cb)
}
function readlinkSync(filepath, opts) {
if (typeof opts === 'string') opts = { encoding: opts }
else if (!opts) opts = {}
const { encoding = 'utf8' } = opts
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
try {
binding.readlinkSync(req.handle, filepath)
let res = Buffer.from(binding.requestResultString(req.handle))
if (encoding !== 'buffer') res = res.toString(encoding)
return res
} catch (e) {
throw new FileError(e.message, {
operation: 'readlink',
code: e.code,
path: filepath
})
}
}
function normalizeSymlinkTarget(target, type, filepath) {
if (isWindows) {
if (type === 'junction') target = path.resolve(filepath, '..', target)
if (path.isAbsolute(target)) return path.toNamespacedPath(target)
return target.replace(/\//g, path.sep)
}
return target
}
async function symlink(target, filepath, type, cb) {
if (typeof type === 'function') {
cb = type
type = null
}
filepath = toNamespacedPath(filepath)
if (typeof type === 'string') {
switch (type) {
case 'file':
default:
type = 0
break
case 'dir':
type = constants.UV_FS_SYMLINK_DIR
break
case 'junction':
type = constants.UV_FS_SYMLINK_JUNCTION
break
}
} else if (typeof type !== 'number') {
if (isWindows) {
target = path.resolve(filepath, '..', target)
try {
type = (await stat(target)).isDirectory()
? constants.UV_FS_SYMLINK_DIR
: constants.UV_FS_SYMLINK_JUNCTION
} catch {
type = 0
}
} else {
type = 0
}
}
target = normalizeSymlinkTarget(target)
using req = FileRequest.borrow()
let err = null
try {
binding.symlink(req.handle, target, filepath, type)
await req
} catch (e) {
err = new FileError(e.message, {
operation: 'symlink',
code: e.code,
path: target,
destination: filepath
})
}
return done(err, cb)
}
function symlinkSync(target, filepath, type) {
filepath = toNamespacedPath(filepath)
if (typeof type === 'string') {
switch (type) {
case 'file':
default:
type = 0
break
case 'dir':
type = constants.UV_FS_SYMLINK_DIR
break
case 'junction':
type = constants.UV_FS_SYMLINK_JUNCTION
break
}
} else if (typeof type !== 'number') {
if (isWindows) {
target = path.resolve(filepath, '..', target)
try {
type = statSync(target).isDirectory()
? constants.UV_FS_SYMLINK_DIR
: constants.UV_FS_SYMLINK_JUNCTION
} catch {
type = 0
}
} else {
type = 0
}
}
target = normalizeSymlinkTarget(target)
using req = FileRequest.borrow()
try {
binding.symlinkSync(req.handle, target, filepath, type)
} catch (e) {
throw new FileError(e.message, {
operation: 'symlink',
code: e.code,
path: target,
destination: filepath
})
}
}
async function opendir(filepath, opts, cb) {
if (typeof opts === 'function') {
cb = opts
opts = {}
}
if (typeof opts === 'string') opts = { encoding: opts }
else if (!opts) opts = {}
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
let dir
let err = null
try {
binding.opendir(req.handle, filepath)
await req
dir = new Dir(filepath, binding.requestResultDir(req.handle), opts)
} catch (e) {
err = new FileError(e.message, {
operation: 'opendir',
code: e.code,
path: filepath
})
}
return done(err, dir, cb)
}
function opendirSync(filepath, opts) {
if (typeof opts === 'string') opts = { encoding: opts }
else if (!opts) opts = {}
filepath = toNamespacedPath(filepath)
using req = FileRequest.borrow()
try {
binding.opendirSync(req.handle, filepath)
return new Dir(filepath, binding.requestResultDir(req.handle), opts)
} catch (e) {
throw new FileError(e.message, {
operation: 'opendir',
code: e.code,
path: filepath
})
}
}
async function readdir(filepath, opts, cb) {
if (typeof opts === 'function') {
cb = opts
opts = {}
}
if (typeof opts === 'string') opts = { encoding: opts }
else if (!opts) opts = {}
const { withFileTypes = false } = opts
filepath = toNamespacedPath(filepath)
let result = []
let err = null
try {
const dir = await opendir(filepath)
for await (const entry of dir) {
result.push(withFileTypes ? entry : entry.name)
}
} catch (e) {
result = []
err = e
}
return done(err, result, cb)
}
function readdirSync(filepath, opts) {
if (typeof opts === 'string') opts = { encoding: opts }
else if (!opts) opts = {}
const { withFileTypes = false } = opts
filepath = toNamespacedPath(filepath)
const dir = opendirSync(filepath, opts)
const result = []
for (const entry of dir) {
result.push(withFileTypes ? entry : entry.name)
}
return result
}
async function readFile(filepath, opts, cb) {
if (typeof opts === 'function') {
cb = opts
opts = {}
}
if (typeof opts === 'string') opts = { encoding: opts }
else if (!opts) opts = {}
const { encoding = 'buffer' } = opts
let fd = -1
try {
fd = await open(filepath, opts.flag || 'r')
const st = await fstat(fd)
let buffer
let len = 0
if (st.size === 0) {
const buffers = []
while (true) {
buffer = Buffer.allocUnsafe(8192)
const r = await read(fd, buffer)
len += r
if (r === 0) break
buffers.push(buffer.subarray(0, r))
}
buffer = Buffer.concat(buffers)
} else {
buffer = Buffer.allocUnsafe(st.size)
while (true) {
const r = await read(fd, len ? buffer.subarray(len) : buffer)
len += r
if (r === 0 || len === buffer.byteLength) break
}
if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
}
if (encoding !== 'buffer') buffer = buffer.toString(encoding)
return ok(buffer, cb)
} catch (err) {
fail(err, cb)
} finally {
if (fd !== -1) await close(fd)
}
}
function readFileSync(filepath, opts) {
if (typeof opts === 'string') opts = { encoding: opts }
else if (!opts) opts = {}
const { encoding = 'buffer' } = opts
let fd = -1
try {
fd = openSync(filepath, opts.flag || 'r')
const st = fstatSync(fd)
let buffer
let len = 0
if (st.size === 0) {
const buffers = []
while (true) {
buffer = Buffer.allocUnsafe(8192)
const r = readSync(fd, buffer)
len += r
if (r === 0) break
buffers.push(buffer.subarray(0, r))
}
buffer = Buffer.concat(buffers)
} else {
buffer = Buffer.allocUnsafe(st.size)
while (true) {
const r = readSync(fd, len ? buffer.subarray(len) : buffer)
len += r
if (r === 0 || len === buffer.byteLength) break
}
if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
}
if (encoding !== 'buffer') buffer = buffer.toString(encoding)
return buffer
} finally {
if (fd !== -1) closeSync(fd)
}
}
async function writeFile(filepath, data, opts, cb) {
if (typeof opts === 'function') {
cb = opts
opts = {}
}
if (typeof opts === 'string') opts = { encoding: opts }
else if (!opts) opts = {}
if (typeof data === 'string') data = Buffer.from(data, opts.encoding)
let fd = -1
try {
fd = await open(filepath, opts.flag || 'w', opts.mode || 0o666)
let len = 0
while (true) {
len += await write(fd, len ? data.subarray(len) : data)
if (len === data.byteLength) break
}
return ok(cb)
} catch (err) {
fail(err, cb)
} finally {
if (fd !== -1) await close(fd)
}
}
function writeFileSync(filepath, data, opts) {
if (typeof opts === 'string') opts = { encoding: opts }
else if (!opts) opts = {}
if (typeof data === 'string') data = Buffer.from(data, opts.encoding)
let fd = -1
try {
fd = openSync(filepath, opts.flag || 'w', opts.mode || 0o666)
let len = 0
while (true) {
len += writeSync(fd, len ? data.subarray(len) : data)
if (len === data.byteLength) break
}
} finally {
if (fd !== -1) closeSync(fd)
}
}
function appendFile(filepath, data, opts, cb) {
if (typeof opts === 'function') {
cb = opts
opts = {}
}
if (typeof opts === 'string') opts = { encoding: opts }
else if (!opts) opts = {}
if (!opts.flag) opts = { ...opts, flag: 'a' }
return writeFile(filepath, data, opts, cb)
}
function appendFileSync(filepath, data, opts) {
if (typeof opts === 'string') opts = { encoding: opts }
else if (!opts) opts = {}
if (!opts.flag) opts = { ...opts, flag: 'a' }
return writeFileSync(filepath, data, opts)
}
function watch(filepath, opts, cb) {
if (typeof opts === 'function') {
cb = opts
opts = {}
}
if (typeof opts === 'string') opts = { encoding: opts }
else if (!opts) opts = {}
filepath = toNamespacedPath(filepath)
return new Watcher(filepath, opts, cb)
}
class Stats {
constructor(
dev,
mode,
nlink,
uid,
gid,
rdev,
blksize,
ino,
size,
blocks,
atimeMs,
mtimeMs,
ctimeMs,
birthtimeMs
) {
this.dev = dev
this.mode = mode
this.nlink = nlink
this.uid = uid
this.gid = gid
this.rdev = rdev
this.blksize = blksize
this.ino = ino
this.size = size
this.blocks = blocks
this.atimeMs = atimeMs
this.mtimeMs = mtimeMs
this.ctimeMs = ctimeMs
this.birthtimeMs = birthtimeMs
this.atime = new Date(atimeMs)
this.mtime = new Date(mtimeMs)
this.ctime = new Date(ctimeMs)
this.birthtime = new Date(birthtimeMs)
}
isDirectory() {
return (this.mode & constants.S_IFMT) === constants.S_IFDIR
}
isFile() {
return (this.mode & constants.S_IFMT) === constants.S_IFREG
}
isBlockDevice() {
return (this.mode & constants.S_IFMT) === constants.S_IFBLK
}
isCharacterDevice() {
return (this.mode & constants.S_IFCHR) === constants.S_IFCHR
}
isFIFO() {
return (this.mode & constants.S_IFMT) === constants.S_IFIFO
}
isSymbolicLink() {
return (this.mode & constants.S_IFMT) === constants.S_IFLNK
}
isSocket() {
return (this.mode & constants.S_IFMT) === constants.S_IFSOCK
}
}
class Dir {
constructor(path, handle, opts = {}) {
const { encoding = 'utf8', bufferSize = 32 } = opts
this.path = path
this._encoding = encoding
this._capacity = bufferSize
this._buffer = new FIFO()
this._ended = false
this._handle = handle
}
async read(cb) {
if (this._buffer.length) return ok(this._buffer.shift(), cb)
if (this._ended) return ok(null, cb)
using req = FileRequest.borrow()
let entries
let err = null
try {
req.retain(binding.readdir(req.handle, this._handle, this._capacity))
await req
entries = binding.requestResultDirents(req.handle)
} catch (e) {
err = new FileError(e.message, {
operation: 'readdir',
code: e.code,
path: this.path
})
}
if (err) return fail(err, cb)
if (entries.length === 0) {
this._ended = true
return ok(null, cb)
}
for (const entry of entries) {
let name = Buffer.from(entry.name)
if (this._encoding !== 'buffer') name = name.toString(this._encoding)
this._buffer.push(new Dirent(this.path, name, entry.type))
}
return ok(this._buffer.shift(), cb)
}
readSync() {
if (this._buffer.length) return this._buffer.shift()
if (this._ended) return null
using req = FileRequest.borrow()
let entries
try {
req.retain(binding.readdirSync(req.handle, this._handle, this._capacity))
entries = binding.requestResultDirents(req.handle)
} catch (e) {
throw new FileError(e.message, {
operation: 'readdir',
code: e.code,
path: this.path
})
}
if (entries.length === 0) {
this._ended = true
return null
}
for (const entry of entries) {
let name = Buffer.from(entry.name)
if (this._encoding !== 'buffer') name = name.toString(this._encoding)
this._buffer.push(new Dirent(this.path, name, entry.type))
}
return this._buffer.shift()
}
async close(cb) {
using req = FileRequest.borrow()
let err = null
try {
binding.closedir(req.handle, this._handle)
await req
} catch (e) {
err = new FileError(e.message, {
operation: 'closedir',
code: e.code,
path: this.path
})
}
this._handle = null
return done(err, cb)
}
closeSync() {
using req = FileRequest.borrow()
try {
binding.closedirSync(req.handle, this._handle)
} catch (e) {
throw new FileError(e.message, {
operation: 'closedir',
code: e.code,
path: this.path
})
}
this._handle = null
}
[Symbol.dispose]() {
this.closeSync()
}
async [Symbol.asyncDispose]() {
await this.close()
}
*[Symbol.iterator]() {
while (true) {
const entry = this.readSync()
if (entry === null) break
yield entry
}
this.closeSync()
}
async *[Symbol.asyncIterator]() {
while (true) {
const entry = await this.read()
if (entry === null) break
yield entry
}
await this.close()
}
}
class Dirent {
constructor(path, name, type) {
this.path = path
this.name = name
this.type = type
}
isFile() {
return this.type === constants.UV_DIRENT_FILE
}
isDirectory() {
return this.type === constants.UV_DIRENT_DIR
}
isSymbolicLink() {
return this.type === constants.UV_DIRENT_LINK
}
isFIFO() {
return this.type === constants.UV_DIRENT_FIFO
}
isSocket() {
return this.type === constants.UV_DIRENT_SOCKET
}
isCharacterDevice() {
return this.type === constants.UV_DIRENT_CHAR
}
isBlockDevice() {
return this.type === constants.UV_DIRENT_BLOCK
}
}
class FileReadStream extends Readable {
constructor(path, opts = {}) {
const { eagerOpen = true } = opts
super({ eagerOpen, ...opts })
this.path = path
this.fd = typeof opts.fd === 'number' ? opts.fd : -1
this.flags = opts.flags || 'r'
this.mode = opts.mode || 0o666
this._offset = opts.start || 0
this._missing = 0
if (opts.length) {
this._missing = opts.length
} else if (typeof opts.end === 'number') {
this._missing = opts.end - this._offset + 1
} else {
this._missing = -1
}
}
async _open(cb) {
let err
if (this.fd === -1) {
err = null
try {
this.fd = await open(this.path, this.flags, this.mode)
} catch (e) {
err = e
}
if (err) return cb(err)
}
let st
err = null
try {
st = await fstat(this.fd)
} catch (e) {
err = e
}
if (err) return cb(err)
if (this._missing === -1) this._missing = st.size
if (st.size < this._offset) {
this._offset = st.size
this._missing = 0
} else if (st.size < this._offset + this._missing) {
this._missing = st.size - this._offset
}
cb(null)
}
async _read(size) {
if (this._missing <= 0) return this.push(null)
const data = Buffer.allocUnsafe(Math.min(this._missing, size))
let len
let err = null
try {
len = await read(this.fd, data, 0, data.byteLength, this._offset)
} catch (e) {
err = e
}
if (err) return cb(err)
if (len === 0) return this.push(null)
if (this._missing < len) len = this._missing
this.push(data.subarray(0, len))
this._missing -= len
this._offset += len
}
async _destroy(err, cb) {
if (this.fd === -1) return cb(err)
err = null
try {
await close(this.fd)
} catch (e) {
err = e
}
cb(err)
}
}
class FileWriteStream extends Writable {
constructor(path, opts = {}) {
const { eagerOpen = true } = opts
super({ eagerOpen, ...opts })
this.path = path
this.fd = typeof opts.fd === 'number' ? opts.fd : -1
this.flags = opts.flags || 'w'
this.mode = opts.mode || 0o666
}
async _open(cb) {
if (this.fd !== -1) return cb(null)
let err = null
try {
this.fd = await open(this.path, this.flags, this.mode)
} catch (e) {
err = e
}
cb(err)
}
async _writev(batch, cb) {
let err = null
try {
await writev(
this.fd,
batch.map(({ chunk }) => chunk)
)
} catch (e) {
err = e
}
cb(err)
}
async _destroy(err, cb) {
if (this.fd === -1) return cb(err)
err = null
try {
await close(this.fd)
} catch (e) {
err = e
}
cb(err)
}
}
class Watcher extends EventEmitter {
constructor(path, opts, onchange) {
if (typeof opts === 'function') {
onchange = opts
opts = {}
}
if (!opts) opts = {}
const { persistent = true, recursive = false, encoding = 'utf8' } = opts
super()
this._closed = false
this._encoding = encoding
this._handle = binding.watcherInit(
path,
recursive,
this,
this._onevent,
this._onclose
)
if (!persistent) this.unref()
if (onchange) this.on('change', onchange)
}
close() {
if (this._closed) return
this._closed = true
binding.watcherClose(this._handle)
}
ref() {
if (this._handle) binding.watcherRef(this._handle)
return this
}
unref() {
if (this._handle) binding.watcherUnref(this._handle)
return this
}
[Symbol.asyncIterator]() {
const buffer = []
let done = false
let error = null
let next = null
this.on('change', (eventType, filename) => {
if (next) {
next.resolve({ done: false, value: { eventType, filename } })
next = null
} else {
buffer.push({ eventType, filename })
}
})
.on('error', (err) => {
done = true
error = err
if (next) {
next.reject(error)
next = null
}
})
.on('close', () => {
done = true
if (next) {
next.resolve({ done })
next = null
}
})
return {
next: () =>
new Promise((resolve, reject) => {
if (error) return reject(error)
if (buffer.length)
return resolve({ done: false, value: buffer.shift() })
if (done) return resolve({ done })
next = { resolve, reject }
})
}
}
_onevent(err, events, filename) {
if (err) {
this.close()
this.emit('error', err)
} else {
const path =
this._encoding === 'buffer'
? Buffer.from(filename)
: Buffer.from(filename).toString(this._encoding)
if (events & binding.UV_RENAME) {
this.emit('change', 'rename', path)
}
if (events & binding.UV_CHANGE) {
this.emit('change', 'change', path)
}
}
}
_onclose() {
this._handle = null
this.emit('close')
}
}
exports.access = access
exports.appendFile = appendFile
exports.chmod = chmod
// exports.chown = chown TODO
exports.close = close
exports.copyFile = copyFile
exports.exists = exists
exports.fchmod = fchmod
// exports.fchown = fchown TODO
// exports.fdatasync = fdatasync TODO
exports.fstat = fstat
// exports.fsync = fsync TODO
exports.ftruncate = ftruncate
// exports.futimes = futimes TODO
// exports.lchmod = lchmod TODO
// exports.lchown = lchown TODO
// exports.lutimes = lutimes TODO
// exports.link = link TODO
exports.lstat = lstat
exports.mkdir = mkdir
// exports.mkdtemp = mkdtemp TODO
exports.open = open
exports.opendir = opendir
exports.read = read
exports.readFile = readFile
exports.readdir = readdir
exports.readlink = readlink
exports.readv = readv
exports.realpath = realpath
exports.rename = rename
exports.rm = rm
exports.rmdir = rmdir
exports.stat = stat
// exports.statfs = statfs TODO
exports.symlink = symlink
// exports.truncate = truncate TODO
exports.unlink = unlink
exports.utimes = utimes
exports.watch = watch
exports.write = write
exports.writeFile = writeFile
exports.writev = writev
exports.accessSync = accessSync
exports.appendFileSync = appendFileSync
exports.chmodSync = chmodSync
// exports.chownSync = chownSync TODO
exports.closeSync = closeSync
exports.copyFileSync = copyFileSync
exports.existsSync = existsSync
exports.fchmodSync = fchmodSync
// exports.fchownSync = fchownSync TODO
// exports.fdatasyncSync = fdatasyncSync TODO
exports.fstatSync = fstatSync
// exports.fsyncSync = fsyncSync TODO
exports.ftruncateSync = ftruncateSync
// exports.futimesSync = futimesSync TODO
// exports.lchmodSync = lchmodSync TODO
// exports.lchownSync = lchownSync TODO
// exports.lutimesSync = lutimesSync TODO
// exports.linkSync = linkSync TODO
exports.lstatSync = lstatSync
exports.mkdirSync = mkdirSync
// exports.mkdtempSync = mkdtempSync TODO
exports.openSync = openSync
exports.opendirSync = opendirSync
exports.readFileSync = readFileSync
exports.readSync = readSync
exports.readdirSync = readdirSync
exports.readlinkSync = readlinkSync
exports.readvSync = readvSync
exports.realpathSync = realpathSync
exports.renameSync = renameSync
exports.rmSync = rmSync
exports.rmdirSync = rmdirSync
exports.statSync = statSync
// exports.statfsSync = statfsSync TODO
exports.symlinkSync = symlinkSync
// exports.truncateSync = truncateSync TODO
exports.unlinkSync = unlinkSync
exports.utimesSync = utimesSync
exports.writeFileSync = writeFileSync
exports.writeSync = writeSync
exports.writevSync = writevSync
exports.promises = require('./promises')
exports.Stats = Stats
exports.Dir = Dir
exports.Dirent = Dirent
exports.Watcher = Watcher
exports.ReadStream = FileReadStream
exports.createReadStream = function createReadStream(path, opts) {
return new FileReadStream(path, opts)
}
exports.WriteStream = FileWriteStream
exports.createWriteStream = function createWriteStream(path, opts) {
return new FileWriteStream(path, opts)
}
function toNamespacedPath(filepath) {
if (typeof filepath !== 'string') {
if (URL.isURL(filepath)) filepath = fileURLToPath(filepath)
else filepath = filepath.toString()
}
return path.toNamespacedPath(filepath)
}
function toFlags(flags) {
switch (flags) {
case 'r':
return constants.O_RDONLY
case 'rs': // Fall through.
case 'sr':
return constants.O_RDONLY | constants.O_SYNC
case 'r+':
return constants.O_RDWR
case 'rs+': // Fall through.
case 'sr+':
return constants.O_RDWR | constants.O_SYNC
case 'w':
return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY
case 'wx': // Fall through.
case 'xw':
return (
constants.O_TRUNC |
constants.O_CREAT |
constants.O_WRONLY |
constants.O_EXCL
)
case 'w+':
return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR
case 'wx+': // Fall through.
case 'xw+':
return (
constants.O_TRUNC |
constants.O_CREAT |
constants.O_RDWR |
constants.O_EXCL
)
case 'a':
return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY
case 'ax': // Fall through.
case 'xa':
return (
constants.O_APPEND |
constants.O_CREAT |
constants.O_WRONLY |
constants.O_EXCL
)
case 'as': // Fall through.
case 'sa':
return (
constants.O_APPEND |
constants.O_CREAT |
constants.O_WRONLY |
constants.O_SYNC
)
case 'a+':
return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR
case 'ax+': // Fall through.
case 'xa+':
return (
constants.O_APPEND |
constants.O_CREAT |
constants.O_RDWR |
constants.O_EXCL
)
case 'as+': // Fall through.
case 'sa+':
return (
constants.O_APPEND |
constants.O_CREAT |
constants.O_RDWR |
constants.O_SYNC
)
default:
return 0
}
}
function toMode(mode) {
return parseInt(mode, 8)
}