From b1bb7bd8f3c1d78ee9fa4f2142194da83c86101f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Sat, 31 Jan 2015 23:16:29 -0800 Subject: [PATCH 1/6] spec: Test graceful-fs --- spec/asar-spec.coffee | 6 ++++++ spec/package.json | 1 + 2 files changed, 7 insertions(+) diff --git a/spec/asar-spec.coffee b/spec/asar-spec.coffee index 21203c0d4e10..e78caabb09f4 100644 --- a/spec/asar-spec.coffee +++ b/spec/asar-spec.coffee @@ -412,3 +412,9 @@ describe 'asar package', -> file = path.join fixtures, 'asar', 'a.asar' stats = originalFs.statSync file assert stats.isFile() + + describe 'graceful-fs module', -> + it 'recognize asar archvies', -> + gfs = require 'graceful-fs' + p = path.join fixtures, 'asar', 'a.asar', 'link1' + assert.equal gfs.readFileSync(p).toString(), 'file1\n' diff --git a/spec/package.json b/spec/package.json index 46afb4fda3bf..f664d447fb42 100644 --- a/spec/package.json +++ b/spec/package.json @@ -6,6 +6,7 @@ "devDependencies": { "formidable": "1.0.16", + "graceful-fs": "3.0.5", "q": "0.9.7", "mocha": "2.1.0", "runas": "2.x", From 0a393eaa1cc2f566a368d6fcdc473d2246d8c33b Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Sat, 31 Jan 2015 23:31:14 -0800 Subject: [PATCH 2/6] Enable wrap arbitrary fs object --- atom/common/lib/asar.coffee | 408 ++++++++++++++++++------------------ atom/common/lib/init.coffee | 4 +- 2 files changed, 207 insertions(+), 205 deletions(-) diff --git a/atom/common/lib/asar.coffee b/atom/common/lib/asar.coffee index eb56bb98c26d..713b61798872 100644 --- a/atom/common/lib/asar.coffee +++ b/atom/common/lib/asar.coffee @@ -1,6 +1,5 @@ asar = process.atomBinding 'asar' child_process = require 'child_process' -fs = require 'fs' path = require 'path' util = require 'util' @@ -60,203 +59,6 @@ createNotFoundError = (asarPath, filePath) -> error.errno = -2 error -# Override fs APIs. -lstatSync = fs.lstatSync -fs.lstatSync = (p) -> - [isAsar, asarPath, filePath] = splitPath p - return lstatSync p unless isAsar - - archive = getOrCreateArchive asarPath - throw new Error("Invalid package #{asarPath}") unless archive - - stats = archive.stat filePath - throw createNotFoundError(asarPath, filePath) unless stats - - asarStatsToFsStats stats - -lstat = fs.lstat -fs.lstat = (p, callback) -> - [isAsar, asarPath, filePath] = splitPath p - return lstat p, callback unless isAsar - - archive = getOrCreateArchive asarPath - return callback new Error("Invalid package #{asarPath}") unless archive - - stats = getOrCreateArchive(asarPath).stat filePath - return callback createNotFoundError(asarPath, filePath) unless stats - - process.nextTick -> callback null, asarStatsToFsStats stats - -statSync = fs.statSync -fs.statSync = (p) -> - [isAsar, asarPath, filePath] = splitPath p - return statSync p unless isAsar - - # Do not distinguish links for now. - fs.lstatSync p - -stat = fs.stat -fs.stat = (p, callback) -> - [isAsar, asarPath, filePath] = splitPath p - return stat p, callback unless isAsar - - # Do not distinguish links for now. - process.nextTick -> fs.lstat p, callback - -statSyncNoException = fs.statSyncNoException -fs.statSyncNoException = (p) -> - [isAsar, asarPath, filePath] = splitPath p - return statSyncNoException p unless isAsar - - archive = getOrCreateArchive asarPath - return false unless archive - stats = archive.stat filePath - return false unless stats - asarStatsToFsStats stats - -realpathSync = fs.realpathSync -fs.realpathSync = (p) -> - [isAsar, asarPath, filePath] = splitPath p - return realpathSync.apply this, arguments unless isAsar - - archive = getOrCreateArchive asarPath - throw new Error("Invalid package #{asarPath}") unless archive - - real = archive.realpath filePath - throw createNotFoundError(asarPath, filePath) if real is false - - path.join realpathSync(asarPath), real - -realpath = fs.realpath -fs.realpath = (p, cache, callback) -> - [isAsar, asarPath, filePath] = splitPath p - return realpath.apply this, arguments unless isAsar - - if typeof cache is 'function' - callback = cache - cache = undefined - - archive = getOrCreateArchive asarPath - return callback new Error("Invalid package #{asarPath}") unless archive - - real = archive.realpath filePath - return callback createNotFoundError(asarPath, filePath) if real is false - - realpath asarPath, (err, p) -> - return callback err if err - callback null, path.join(p, real) - -exists = fs.exists -fs.exists = (p, callback) -> - [isAsar, asarPath, filePath] = splitPath p - return exists p, callback unless isAsar - - archive = getOrCreateArchive asarPath - return callback new Error("Invalid package #{asarPath}") unless archive - - process.nextTick -> callback archive.stat(filePath) isnt false - -existsSync = fs.existsSync -fs.existsSync = (p) -> - [isAsar, asarPath, filePath] = splitPath p - return existsSync p unless isAsar - - archive = getOrCreateArchive asarPath - return false unless archive - - archive.stat(filePath) isnt false - -open = fs.open -readFile = fs.readFile -fs.readFile = (p, options, callback) -> - [isAsar, asarPath, filePath] = splitPath p - return readFile.apply this, arguments unless isAsar - - if typeof options is 'function' - callback = options - options = undefined - - archive = getOrCreateArchive asarPath - return callback new Error("Invalid package #{asarPath}") unless archive - - info = archive.getFileInfo filePath - return callback createNotFoundError(asarPath, filePath) unless info - - if not options - options = encoding: null, flag: 'r' - else if util.isString options - options = encoding: options, flag: 'r' - else if not util.isObject options - throw new TypeError('Bad arguments') - - flag = options.flag || 'r' - encoding = options.encoding - - buffer = new Buffer(info.size) - open archive.path, flag, (error, fd) -> - return callback error if error - fs.read fd, buffer, 0, info.size, info.offset, (error) -> - fs.close fd, -> - callback error, if encoding then buffer.toString encoding else buffer - -openSync = fs.openSync -readFileSync = fs.readFileSync -fs.readFileSync = (p, options) -> - [isAsar, asarPath, filePath] = splitPath p - return readFileSync.apply this, arguments unless isAsar - - archive = getOrCreateArchive asarPath - throw new Error("Invalid package #{asarPath}") unless archive - - info = archive.getFileInfo filePath - throw createNotFoundError(asarPath, filePath) unless info - - if not options - options = encoding: null, flag: 'r' - else if util.isString options - options = encoding: options, flag: 'r' - else if not util.isObject options - throw new TypeError('Bad arguments') - - flag = options.flag || 'r' - encoding = options.encoding - - buffer = new Buffer(info.size) - fd = openSync archive.path, flag - try - fs.readSync fd, buffer, 0, info.size, info.offset - catch e - throw e - finally - fs.closeSync fd - if encoding then buffer.toString encoding else buffer - -readdir = fs.readdir -fs.readdir = (p, callback) -> - [isAsar, asarPath, filePath] = splitPath p - return readdir.apply this, arguments unless isAsar - - archive = getOrCreateArchive asarPath - return callback new Error("Invalid package #{asarPath}") unless archive - - files = archive.readdir filePath - return callback createNotFoundError(asarPath, filePath) unless files - - process.nextTick -> callback null, files - -readdirSync = fs.readdirSync -fs.readdirSync = (p) -> - [isAsar, asarPath, filePath] = splitPath p - return readdirSync.apply this, arguments unless isAsar - - archive = getOrCreateArchive asarPath - throw new Error("Invalid package #{asarPath}") unless archive - - files = archive.readdir filePath - throw createNotFoundError(asarPath, filePath) unless files - - files - # Override APIs that rely on passing file path instead of content to C++. overrideAPISync = (module, name, arg = 0) -> old = module[name] @@ -293,9 +95,207 @@ overrideAPI = (module, name, arg = 0) -> arguments[arg] = newPath old.apply this, arguments -overrideAPI fs, 'open' -overrideAPI child_process, 'execFile' -overrideAPISync process, 'dlopen', 1 -overrideAPISync require('module')._extensions, '.node', 1 -overrideAPISync fs, 'openSync' -overrideAPISync child_process, 'fork' +# Override fs APIs. +exports.wrapFsWithAsar = (fs) -> + lstatSync = fs.lstatSync + fs.lstatSync = (p) -> + [isAsar, asarPath, filePath] = splitPath p + return lstatSync p unless isAsar + + archive = getOrCreateArchive asarPath + throw new Error("Invalid package #{asarPath}") unless archive + + stats = archive.stat filePath + throw createNotFoundError(asarPath, filePath) unless stats + + asarStatsToFsStats stats + + lstat = fs.lstat + fs.lstat = (p, callback) -> + [isAsar, asarPath, filePath] = splitPath p + return lstat p, callback unless isAsar + + archive = getOrCreateArchive asarPath + return callback new Error("Invalid package #{asarPath}") unless archive + + stats = getOrCreateArchive(asarPath).stat filePath + return callback createNotFoundError(asarPath, filePath) unless stats + + process.nextTick -> callback null, asarStatsToFsStats stats + + statSync = fs.statSync + fs.statSync = (p) -> + [isAsar, asarPath, filePath] = splitPath p + return statSync p unless isAsar + + # Do not distinguish links for now. + fs.lstatSync p + + stat = fs.stat + fs.stat = (p, callback) -> + [isAsar, asarPath, filePath] = splitPath p + return stat p, callback unless isAsar + + # Do not distinguish links for now. + process.nextTick -> fs.lstat p, callback + + statSyncNoException = fs.statSyncNoException + fs.statSyncNoException = (p) -> + [isAsar, asarPath, filePath] = splitPath p + return statSyncNoException p unless isAsar + + archive = getOrCreateArchive asarPath + return false unless archive + stats = archive.stat filePath + return false unless stats + asarStatsToFsStats stats + + realpathSync = fs.realpathSync + fs.realpathSync = (p) -> + [isAsar, asarPath, filePath] = splitPath p + return realpathSync.apply this, arguments unless isAsar + + archive = getOrCreateArchive asarPath + throw new Error("Invalid package #{asarPath}") unless archive + + real = archive.realpath filePath + throw createNotFoundError(asarPath, filePath) if real is false + + path.join realpathSync(asarPath), real + + realpath = fs.realpath + fs.realpath = (p, cache, callback) -> + [isAsar, asarPath, filePath] = splitPath p + return realpath.apply this, arguments unless isAsar + + if typeof cache is 'function' + callback = cache + cache = undefined + + archive = getOrCreateArchive asarPath + return callback new Error("Invalid package #{asarPath}") unless archive + + real = archive.realpath filePath + return callback createNotFoundError(asarPath, filePath) if real is false + + realpath asarPath, (err, p) -> + return callback err if err + callback null, path.join(p, real) + + exists = fs.exists + fs.exists = (p, callback) -> + [isAsar, asarPath, filePath] = splitPath p + return exists p, callback unless isAsar + + archive = getOrCreateArchive asarPath + return callback new Error("Invalid package #{asarPath}") unless archive + + process.nextTick -> callback archive.stat(filePath) isnt false + + existsSync = fs.existsSync + fs.existsSync = (p) -> + [isAsar, asarPath, filePath] = splitPath p + return existsSync p unless isAsar + + archive = getOrCreateArchive asarPath + return false unless archive + + archive.stat(filePath) isnt false + + open = fs.open + readFile = fs.readFile + fs.readFile = (p, options, callback) -> + [isAsar, asarPath, filePath] = splitPath p + return readFile.apply this, arguments unless isAsar + + if typeof options is 'function' + callback = options + options = undefined + + archive = getOrCreateArchive asarPath + return callback new Error("Invalid package #{asarPath}") unless archive + + info = archive.getFileInfo filePath + return callback createNotFoundError(asarPath, filePath) unless info + + if not options + options = encoding: null, flag: 'r' + else if util.isString options + options = encoding: options, flag: 'r' + else if not util.isObject options + throw new TypeError('Bad arguments') + + flag = options.flag || 'r' + encoding = options.encoding + + buffer = new Buffer(info.size) + open archive.path, flag, (error, fd) -> + return callback error if error + fs.read fd, buffer, 0, info.size, info.offset, (error) -> + fs.close fd, -> + callback error, if encoding then buffer.toString encoding else buffer + + openSync = fs.openSync + readFileSync = fs.readFileSync + fs.readFileSync = (p, options) -> + [isAsar, asarPath, filePath] = splitPath p + return readFileSync.apply this, arguments unless isAsar + + archive = getOrCreateArchive asarPath + throw new Error("Invalid package #{asarPath}") unless archive + + info = archive.getFileInfo filePath + throw createNotFoundError(asarPath, filePath) unless info + + if not options + options = encoding: null, flag: 'r' + else if util.isString options + options = encoding: options, flag: 'r' + else if not util.isObject options + throw new TypeError('Bad arguments') + + flag = options.flag || 'r' + encoding = options.encoding + + buffer = new Buffer(info.size) + fd = openSync archive.path, flag + try + fs.readSync fd, buffer, 0, info.size, info.offset + catch e + throw e + finally + fs.closeSync fd + if encoding then buffer.toString encoding else buffer + + readdir = fs.readdir + fs.readdir = (p, callback) -> + [isAsar, asarPath, filePath] = splitPath p + return readdir.apply this, arguments unless isAsar + + archive = getOrCreateArchive asarPath + return callback new Error("Invalid package #{asarPath}") unless archive + + files = archive.readdir filePath + return callback createNotFoundError(asarPath, filePath) unless files + + process.nextTick -> callback null, files + + readdirSync = fs.readdirSync + fs.readdirSync = (p) -> + [isAsar, asarPath, filePath] = splitPath p + return readdirSync.apply this, arguments unless isAsar + + archive = getOrCreateArchive asarPath + throw new Error("Invalid package #{asarPath}") unless archive + + files = archive.readdir filePath + throw createNotFoundError(asarPath, filePath) unless files + + files + + overrideAPI fs, 'open' + overrideAPI child_process, 'execFile' + overrideAPISync process, 'dlopen', 1 + overrideAPISync require('module')._extensions, '.node', 1 + overrideAPISync fs, 'openSync' + overrideAPISync child_process, 'fork' diff --git a/atom/common/lib/init.coffee b/atom/common/lib/init.coffee index b8d67355c05f..2d7fc73b994e 100644 --- a/atom/common/lib/init.coffee +++ b/atom/common/lib/init.coffee @@ -1,4 +1,5 @@ process = global.process +fs = require 'fs' path = require 'path' timers = require 'timers' Module = require 'module' @@ -36,4 +37,5 @@ if process.type is 'browser' global.setInterval = wrapWithActivateUvLoop timers.setInterval # Add support for asar packages. -require './asar' +asar = require './asar' +asar.wrapFsWithAsar fs From afd6f41e08efdb3b0370d0fa5fbb71579df3836b Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Sat, 31 Jan 2015 23:47:47 -0800 Subject: [PATCH 3/6] Redirect process.binding('natives').fs to global fs object --- atom/common/api/lib/original-fs.coffee | 2 +- atom/common/lib/init.coffee | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/atom/common/api/lib/original-fs.coffee b/atom/common/api/lib/original-fs.coffee index 3868168343fc..e4e47f33120b 100644 --- a/atom/common/api/lib/original-fs.coffee +++ b/atom/common/api/lib/original-fs.coffee @@ -2,7 +2,7 @@ vm = require 'vm' # Execute the 'fs.js' and pass the 'exports' to it. source = '(function (exports, require, module, __filename, __dirname) { ' + - process.binding('natives').fs + + process.binding('natives').originalFs + '\n});' fn = vm.runInThisContext source, { filename: 'fs.js' } fn exports, require, module diff --git a/atom/common/lib/init.coffee b/atom/common/lib/init.coffee index 2d7fc73b994e..5594fd8dad5f 100644 --- a/atom/common/lib/init.coffee +++ b/atom/common/lib/init.coffee @@ -39,3 +39,8 @@ if process.type is 'browser' # Add support for asar packages. asar = require './asar' asar.wrapFsWithAsar fs + +# Make graceful-fs work with asar. +source = process.binding 'natives' +source.originalFs = source.fs +source.fs = "module.exports = require('fs');" From 3f8ad3bf7523fc3402b18855c1604fe7c90130d8 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Sat, 31 Jan 2015 23:51:15 -0800 Subject: [PATCH 4/6] spec: graceful-fs should not touch global fs object --- spec/asar-spec.coffee | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/asar-spec.coffee b/spec/asar-spec.coffee index e78caabb09f4..ae0526f9fe1b 100644 --- a/spec/asar-spec.coffee +++ b/spec/asar-spec.coffee @@ -414,7 +414,11 @@ describe 'asar package', -> assert stats.isFile() describe 'graceful-fs module', -> + gfs = require 'graceful-fs' + it 'recognize asar archvies', -> - gfs = require 'graceful-fs' p = path.join fixtures, 'asar', 'a.asar', 'link1' assert.equal gfs.readFileSync(p).toString(), 'file1\n' + + it 'does not touch global fs object', -> + assert.notEqual fs.readdir, gfs.readdir From 0c0a6bd939075c47014496888ef1f096cf6fe05d Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Sun, 1 Feb 2015 14:04:40 -0800 Subject: [PATCH 5/6] Don't touch global fs object in graceful-fs --- atom/common/lib/init.coffee | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/atom/common/lib/init.coffee b/atom/common/lib/init.coffee index 5594fd8dad5f..49b1875725e0 100644 --- a/atom/common/lib/init.coffee +++ b/atom/common/lib/init.coffee @@ -43,4 +43,13 @@ asar.wrapFsWithAsar fs # Make graceful-fs work with asar. source = process.binding 'natives' source.originalFs = source.fs -source.fs = "module.exports = require('fs');" +source.fs = """ + var src = '(function (exports, require, module, __filename, __dirname) { ' + + process.binding('natives').originalFs + + ' });'; + var vm = require('vm'); + var fn = vm.runInThisContext(src, { filename: 'fs.js' }); + fn(exports, require, module); + var asar = require('#{__dirname}/asar'); + asar.wrapFsWithAsar(exports); +""" From 6e469df90e8b7c21c0063c8f063bba0f58a3ef23 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Sun, 1 Feb 2015 17:39:22 -0800 Subject: [PATCH 6/6] Fix string escaping --- atom/common/lib/init.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/common/lib/init.coffee b/atom/common/lib/init.coffee index 49b1875725e0..7a60ed7296c5 100644 --- a/atom/common/lib/init.coffee +++ b/atom/common/lib/init.coffee @@ -50,6 +50,6 @@ source.fs = """ var vm = require('vm'); var fn = vm.runInThisContext(src, { filename: 'fs.js' }); fn(exports, require, module); - var asar = require('#{__dirname}/asar'); + var asar = require(#{JSON.stringify(__dirname)} + '/asar'); asar.wrapFsWithAsar(exports); """