Skip to content

Commit

Permalink
Fix fs.promises with asar
Browse files Browse the repository at this point in the history
  • Loading branch information
zcbenz committed Mar 24, 2023
1 parent 054aef2 commit 0712a5d
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 53 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jobs:
if: matrix.arch == 'x64' || matrix.arch == 'ia32'
env:
DISPLAY: ':99.0'
run: out/Release/yode test.js
run: out/Release/yode test/main.js

- name: Upload Binary Files
uses: actions/upload-artifact@v3
Expand Down
24 changes: 24 additions & 0 deletions deps/asar.js

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions deps/fs-extra.js

Large diffs are not rendered by default.

Binary file removed fixtures/exit_123.asar
Binary file not shown.
Binary file removed fixtures/print_filename.asar
Binary file not shown.
107 changes: 71 additions & 36 deletions src/asar_monkey_patch.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const childProcess = require('child_process')
const path = require('path')
const util = require('util')

// The root dir of asar archive.
const rootDir = path._makeLong(path.join(execPath, 'asar'))
Expand Down Expand Up @@ -171,6 +172,8 @@ exports.wrapFsWithAsar = function(fs) {
})
}

fs.promises.lstat = util.promisify(fs.lstat)

const {statSync} = fs
fs.statSync = function(p) {
const [isAsar] = splitPath(p)
Expand All @@ -191,57 +194,76 @@ exports.wrapFsWithAsar = function(fs) {
})
}

const {realpathSync} = fs
fs.realpathSync = function(p) {
const [isAsar, filePath] = splitPath(p)
if (!isAsar)
return realpathSync.apply(this, arguments)
const info = process.asarArchive.getFileInfo(filePath)
if (!info)
return notFoundError(filePath)
const real = process.asarArchive.realpath(info)
if (info.unpacked)
return real
else
return path.join(realpathSync(execPath), 'asar', real)
}
fs.promises.stat = util.promisify(fs.lstat)

const {realpath} = fs
fs.realpath = function(p, cache, callback) {
const [isAsar, filePath] = splitPath(p)
if (!isAsar)
return realpath.apply(this, arguments)
if (typeof cache === 'function') {
callback = cache
cache = undefined
const wrapRealpathSync = function(func) {
return function(p) {
const [isAsar, filePath] = splitPath(p)
if (!isAsar)
return func.apply(this, arguments)
const info = process.asarArchive.getFileInfo(filePath)
if (!info)
return notFoundError(filePath)
const real = process.asarArchive.realpath(info)
if (info.unpacked)
return real
else
return path.join(func(execPath), 'asar', real)
}
const info = process.asarArchive.getFileInfo(filePath)
if (!info)
return notFoundError(filePath, callback)
const real = process.asarArchive.realpath(info)
if (info.unpacked) {
callback(null, real)
} else {
realpath(execPath, function(err, p) {
if (err)
return callback(err)
return callback(null, path.join(p, 'asar', real))
})
}

const {realpathSync} = fs
fs.realpathSync = wrapRealpathSync(realpathSync)
fs.realpathSync.native = wrapRealpathSync(realpathSync.native);

const wrapRealpath = function(func) {
return function(p, cache, callback) {
const [isAsar, filePath] = splitPath(p)
if (!isAsar)
return func.apply(this, arguments)
if (typeof cache === 'function') {
callback = cache
cache = undefined
}
const info = process.asarArchive.getFileInfo(filePath)
if (!info)
return notFoundError(filePath, callback)
const real = process.asarArchive.realpath(info)
if (info.unpacked) {
callback(null, real)
} else {
func(execPath, function(err, p) {
if (err)
return callback(err)
return callback(null, path.join(p, 'asar', real))
})
}
}
}

const {realpath} = fs
fs.realpath = wrapRealpath(realpath)
fs.realpath.native = wrapRealpath(realpath.native)

fs.promises.realpath = util.promisify(fs.realpath.native)

const {exists} = fs
fs.exists = function(p, callback) {
const [isAsar, filePath] = splitPath(p)
if (!isAsar)
return exists(p, callback)
process.nextTick(function() {
// Disabled due to false positive in StandardJS
// eslint-disable-next-line standard/no-callback-literal
callback(process.asarArchive.stat(filePath) !== false)
})
}

fs.exists[util.promisify.custom] = function(p) {
const [isAsar, filePath] = splitPath(p)
if (!isAsar)
return exists[util.promisify.custom](p)
return Promise.resolve(process.asarArchive.stat(filePath) !== false)
}

const {existsSync} = fs
fs.existsSync = function(p) {
const [isAsar, filePath] = splitPath(p)
Expand Down Expand Up @@ -276,6 +298,8 @@ exports.wrapFsWithAsar = function(fs) {
})
}

fs.promises.access = util.promisify(fs.access)

const {accessSync} = fs
fs.accessSync = function(p, mode) {
const [isAsar, filePath] = splitPath(p)
Expand Down Expand Up @@ -345,6 +369,15 @@ exports.wrapFsWithAsar = function(fs) {
})
}

const {readFilePromise} = fs.promises
fs.promises.readFile = function(p, options) {
const [isAsar, filePath] = splitPath(p)
if (!isAsar)
return readFilePromise.apply(this, arguments)

return util.promisify(fs.readFile)(p,options)
}

const {readFileSync} = fs
fs.readFileSync = function(p, options) {
const [isAsar, filePath] = splitPath(p)
Expand Down Expand Up @@ -398,6 +431,8 @@ exports.wrapFsWithAsar = function(fs) {
})
}

fs.promises.readdir = util.promisify(fs.readdir)

const {readdirSync} = fs
fs.readdirSync = function(p) {
const [isAsar, filePath] = splitPath(p)
Expand Down
1 change: 1 addition & 0 deletions test/asar_exit_123/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
process.exit(123)
Binary file not shown.
6 changes: 6 additions & 0 deletions test/asar_fs_promise/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const fs = require('node:fs/promises');

(async function() {
process.stdout.write((await fs.readFile(__filename)).toString());
process.exit(0)
})()
2 changes: 2 additions & 0 deletions test/asar_print_filename/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
console.log(__filename)
process.exit(0)
48 changes: 32 additions & 16 deletions test.js → test/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@

// Run the tests defined in this file.
if (require.main == module) {
const Mocha = require('./deps/mocha')
const Mocha = require('../deps/mocha')
const mocha = new Mocha
mocha.ui('bdd').reporter('tap')
for (let member in require.cache) // make require('test.js') work
delete require.cache[member]
mocha.addFile('test.js')
mocha.addFile('test/main.js')
mocha.run((failures) => process.exit(failures))
return
}

const assert = require('assert')
const path = require('path')
const fs = require('fs')
const asar = require('../deps/asar')

describe('property', function() {
describe('process.versions.yode', function() {
Expand Down Expand Up @@ -58,6 +59,11 @@ describe('node', function() {
})
})

it('fetch should work', async () => {
const res = await fetch('https://google.com')
assert.equal(res.status, 200)
})

it('fork should work', function(done) {
const p = path.join(require('os').tmpdir(), 'yode-fork.js')
fs.writeFileSync(p, "process.send('ok')")
Expand Down Expand Up @@ -92,26 +98,32 @@ describe('node', function() {
})
})

it('start with asar', function() {
const result = packageAndRun('exit_123')
it('start with asar', async () => {
const result = await packageAndRun('exit_123')
assert.equal(result.status, 123)
})

it('start with asar with output', function() {
const result = packageAndRun('print_filename')
it('start with asar with output', async () => {
const result = await packageAndRun('print_filename')
assert.equal(result.status, 0)
const p = path.join(__dirname, `print_filename_${path.basename(process.execPath)}`, 'asar', 'index.js')
const p = path.join(__dirname, '..', `print_filename_${path.basename(process.execPath)}`, 'asar', 'index.js')
assert.equal(result.stdout.toString().trim(), p)
})

it('start with asar with fs', function() {
const result = packageAndRun('print_self')
it('start with asar with async fs', async () => {
const result = await packageAndRun('fs_async')
assert.equal(result.status, 0)
assert.ok(result.stdout.toString().includes('fs.readFile(__filename'))
})

it('start with asar with promise fs', async () => {
const result = await packageAndRun('fs_promise')
assert.equal(result.status, 0)
assert.ok(result.stdout.toString().includes('fs.readFile(__filename'))
})

it('start with asar with offset', function() {
const result = packageAndRun('print_self', changeOffset)
it('start with asar with offset', async () => {
const result = await packageAndRun('fs_async', changeOffset)
assert.equal(result.status, 0)
assert.ok(result.stdout.toString().includes('fs.readFile(__filename'))
})
Expand All @@ -121,18 +133,22 @@ describe('node', function() {
})
})

function packageAndRun(asar, modifyBinary = null) {
const a = path.join(__dirname, 'fixtures', asar + '.asar')
const p = path.join(__dirname, `${path.basename(asar, '.asar')}_${path.basename(process.execPath)}`)
async function packageAndRun(filename, modifyBinary = null) {
const a = path.join(__dirname, '..', filename + '.asar')
await asar.createPackage(path.join(__dirname, 'asar_' + filename), a)
const p = path.join(__dirname, '..', `${filename}_${path.basename(process.execPath)}`)
fs.writeFileSync(p, fs.readFileSync(process.execPath))
if (modifyBinary)
modifyBinary(p)
fs.appendFileSync(p, fs.readFileSync(a))
appendMeta(p, a)
fs.chmodSync(p, 0o755)
const result = require('child_process').spawnSync(p)
if (result.status !== null)
fs.unlinkSync(p) // will be left for debugging if failed to run
if (result.status !== null) {
// Will be left for debugging if failed to run.
fs.unlinkSync(a)
fs.unlinkSync(p)
}
return result
}

Expand Down

0 comments on commit 0712a5d

Please sign in to comment.