electron/atom/common/lib/asar.coffee

395 lines
12 KiB
CoffeeScript
Raw Normal View History

asar = process.binding 'atom_common_asar'
child_process = require 'child_process'
2014-09-24 07:06:36 +00:00
path = require 'path'
2014-09-24 08:24:22 +00:00
util = require 'util'
2014-09-24 07:06:36 +00:00
2016-01-12 02:03:02 +00:00
### Cache asar archive objects. ###
cachedArchives = {}
getOrCreateArchive = (p) ->
archive = cachedArchives[p]
return archive if archive?
archive = asar.createArchive p
return false unless archive
cachedArchives[p] = archive
2016-01-12 02:03:02 +00:00
### Clean cache on quit. ###
process.on 'exit', ->
archive.destroy() for own p, archive of cachedArchives
2016-01-12 02:03:02 +00:00
### Separate asar package's path from full path. ###
2014-09-24 07:06:36 +00:00
splitPath = (p) ->
2016-01-12 02:03:02 +00:00
### shortcut to disable asar. ###
return [false] if process.noAsar
2014-09-25 14:18:40 +00:00
return [false] if typeof p isnt 'string'
return [true, p, ''] if p.substr(-5) is '.asar'
2015-06-17 07:52:29 +00:00
p = path.normalize p
2014-09-25 14:18:40 +00:00
index = p.lastIndexOf ".asar#{path.sep}"
return [false] if index is -1
[true, p.substr(0, index + 5), p.substr(index + 6)]
2014-09-24 07:06:36 +00:00
2016-01-12 02:03:02 +00:00
### Convert asar archive's Stats object to fs's Stats object. ###
nextInode = 0
uid = if process.getuid? then process.getuid() else 0
gid = if process.getgid? then process.getgid() else 0
2014-10-02 17:50:37 +00:00
fakeTime = new Date()
2014-09-24 07:06:36 +00:00
asarStatsToFsStats = (stats) ->
{
dev: 1,
ino: ++nextInode,
mode: 33188,
nlink: 1,
uid: uid,
gid: gid,
rdev: 0,
2014-10-02 17:50:37 +00:00
atime: stats.atime || fakeTime,
birthtime: stats.birthtime || fakeTime,
mtime: stats.mtime || fakeTime,
ctime: stats.ctime || fakeTime,
2014-10-01 16:39:51 +00:00
size: stats.size,
2014-09-24 07:06:36 +00:00
isFile: -> stats.isFile
isDirectory: -> stats.isDirectory
isSymbolicLink: -> stats.isLink
isBlockDevice: -> false
isCharacterDevice: -> false
isFIFO: -> false
isSocket: -> false
}
2016-01-12 02:03:02 +00:00
### Create a ENOENT error. ###
notFoundError = (asarPath, filePath, callback) ->
2014-09-24 07:38:07 +00:00
error = new Error("ENOENT, #{filePath} not found in #{asarPath}")
error.code = "ENOENT"
error.errno = -2
unless typeof callback is 'function'
throw error
process.nextTick -> callback error
2016-01-12 02:03:02 +00:00
### Create a ENOTDIR error. ###
notDirError = (callback) ->
error = new Error('ENOTDIR, not a directory')
error.code = 'ENOTDIR'
error.errno = -20
unless typeof callback is 'function'
throw error
process.nextTick -> callback error
2016-01-12 02:03:02 +00:00
### Create invalid archive error. ###
invalidArchiveError = (asarPath, callback) ->
error = new Error("Invalid package #{asarPath}")
unless typeof callback is 'function'
throw error
process.nextTick -> callback error
2014-09-24 07:38:07 +00:00
2016-01-12 02:03:02 +00:00
### Override APIs that rely on passing file path instead of content to C++. ###
2015-02-01 07:31:14 +00:00
overrideAPISync = (module, name, arg = 0) ->
old = module[name]
module[name] = ->
p = arguments[arg]
[isAsar, asarPath, filePath] = splitPath p
return old.apply this, arguments unless isAsar
2014-09-24 07:06:36 +00:00
2015-02-01 07:31:14 +00:00
archive = getOrCreateArchive asarPath
invalidArchiveError asarPath unless archive
2014-09-24 08:24:22 +00:00
2015-02-01 07:31:14 +00:00
newPath = archive.copyFileOut filePath
notFoundError asarPath, filePath unless newPath
2014-09-24 08:24:22 +00:00
2015-02-01 07:31:14 +00:00
arguments[arg] = newPath
old.apply this, arguments
2014-09-24 08:24:22 +00:00
2015-02-01 07:31:14 +00:00
overrideAPI = (module, name, arg = 0) ->
old = module[name]
module[name] = ->
p = arguments[arg]
[isAsar, asarPath, filePath] = splitPath p
return old.apply this, arguments unless isAsar
2014-09-24 08:24:22 +00:00
2015-02-01 07:31:14 +00:00
callback = arguments[arguments.length - 1]
return overrideAPISync module, name, arg unless typeof callback is 'function'
2014-09-24 07:06:36 +00:00
2015-02-01 07:31:14 +00:00
archive = getOrCreateArchive asarPath
return invalidArchiveError asarPath, callback unless archive
2014-09-24 07:38:07 +00:00
2015-02-01 07:31:14 +00:00
newPath = archive.copyFileOut filePath
return notFoundError asarPath, filePath, callback unless newPath
2014-09-30 06:53:41 +00:00
2015-02-01 07:31:14 +00:00
arguments[arg] = newPath
old.apply this, arguments
2014-09-30 06:53:41 +00:00
2016-01-12 02:03:02 +00:00
### Override fs APIs. ###
2015-02-01 07:31:14 +00:00
exports.wrapFsWithAsar = (fs) ->
lstatSync = fs.lstatSync
fs.lstatSync = (p) ->
[isAsar, asarPath, filePath] = splitPath p
return lstatSync p unless isAsar
2014-09-30 06:53:41 +00:00
2015-02-01 07:31:14 +00:00
archive = getOrCreateArchive asarPath
invalidArchiveError asarPath unless archive
2014-09-30 06:53:41 +00:00
2015-02-01 07:31:14 +00:00
stats = archive.stat filePath
notFoundError asarPath, filePath unless stats
2014-09-30 06:57:49 +00:00
2015-02-01 07:31:14 +00:00
asarStatsToFsStats stats
2014-09-30 06:57:49 +00:00
2015-02-01 07:31:14 +00:00
lstat = fs.lstat
fs.lstat = (p, callback) ->
[isAsar, asarPath, filePath] = splitPath p
return lstat p, callback unless isAsar
2014-09-30 06:57:49 +00:00
2015-02-01 07:31:14 +00:00
archive = getOrCreateArchive asarPath
return invalidArchiveError asarPath, callback unless archive
2014-09-30 06:57:49 +00:00
2015-02-01 07:31:14 +00:00
stats = getOrCreateArchive(asarPath).stat filePath
return notFoundError asarPath, filePath, callback unless stats
2014-09-30 06:57:49 +00:00
2015-02-01 07:31:14 +00:00
process.nextTick -> callback null, asarStatsToFsStats stats
2014-09-24 07:38:07 +00:00
2015-02-01 07:31:14 +00:00
statSync = fs.statSync
fs.statSync = (p) ->
[isAsar, asarPath, filePath] = splitPath p
return statSync p unless isAsar
2014-09-24 07:38:07 +00:00
2016-01-12 02:03:02 +00:00
### Do not distinguish links for now. ###
2015-02-01 07:31:14 +00:00
fs.lstatSync p
2014-09-24 07:38:07 +00:00
2015-02-01 07:31:14 +00:00
stat = fs.stat
fs.stat = (p, callback) ->
[isAsar, asarPath, filePath] = splitPath p
return stat p, callback unless isAsar
2014-09-24 07:38:07 +00:00
2016-01-12 02:03:02 +00:00
### Do not distinguish links for now. ###
2015-02-01 07:31:14 +00:00
process.nextTick -> fs.lstat p, callback
2014-09-24 07:38:07 +00:00
2015-02-01 07:31:14 +00:00
statSyncNoException = fs.statSyncNoException
fs.statSyncNoException = (p) ->
[isAsar, asarPath, filePath] = splitPath p
return statSyncNoException p unless isAsar
2014-09-24 07:38:07 +00:00
2015-02-01 07:31:14 +00:00
archive = getOrCreateArchive asarPath
return false unless archive
stats = archive.stat filePath
return false unless stats
asarStatsToFsStats stats
2014-09-24 07:38:07 +00:00
2015-02-01 07:31:14 +00:00
realpathSync = fs.realpathSync
fs.realpathSync = (p) ->
[isAsar, asarPath, filePath] = splitPath p
return realpathSync.apply this, arguments unless isAsar
2014-09-28 15:36:12 +00:00
2015-02-01 07:31:14 +00:00
archive = getOrCreateArchive asarPath
invalidArchiveError asarPath unless archive
2014-09-24 07:38:07 +00:00
2015-02-01 07:31:14 +00:00
real = archive.realpath filePath
notFoundError asarPath, filePath if real is false
2014-09-24 07:38:07 +00:00
2015-02-01 07:31:14 +00:00
path.join realpathSync(asarPath), real
2014-09-24 08:24:22 +00:00
2015-02-01 07:31:14 +00:00
realpath = fs.realpath
fs.realpath = (p, cache, callback) ->
[isAsar, asarPath, filePath] = splitPath p
return realpath.apply this, arguments unless isAsar
2014-09-24 08:24:22 +00:00
2015-02-01 07:31:14 +00:00
if typeof cache is 'function'
callback = cache
cache = undefined
2014-09-24 07:38:07 +00:00
2015-02-01 07:31:14 +00:00
archive = getOrCreateArchive asarPath
return invalidArchiveError asarPath, callback unless archive
2014-09-24 07:38:07 +00:00
2015-02-01 07:31:14 +00:00
real = archive.realpath filePath
if real is false
return notFoundError asarPath, filePath, callback
2014-09-24 07:38:07 +00:00
2015-02-01 07:31:14 +00:00
realpath asarPath, (err, p) ->
return callback err if err
callback null, path.join(p, real)
2014-09-24 07:38:07 +00:00
2015-02-01 07:31:14 +00:00
exists = fs.exists
fs.exists = (p, callback) ->
[isAsar, asarPath, filePath] = splitPath p
return exists p, callback unless isAsar
2014-09-24 08:24:22 +00:00
2015-02-01 07:31:14 +00:00
archive = getOrCreateArchive asarPath
return invalidArchiveError asarPath, callback unless archive
2014-09-24 08:24:22 +00:00
2015-02-01 07:31:14 +00:00
process.nextTick -> callback archive.stat(filePath) isnt false
2014-09-24 10:44:00 +00:00
2015-02-01 07:31:14 +00:00
existsSync = fs.existsSync
fs.existsSync = (p) ->
[isAsar, asarPath, filePath] = splitPath p
return existsSync p unless isAsar
2014-09-24 10:44:00 +00:00
2015-02-01 07:31:14 +00:00
archive = getOrCreateArchive asarPath
return false unless archive
2014-09-24 10:44:00 +00:00
2015-02-01 07:31:14 +00:00
archive.stat(filePath) isnt false
2014-09-24 10:44:00 +00:00
2015-02-01 07:31:14 +00:00
open = fs.open
readFile = fs.readFile
fs.readFile = (p, options, callback) ->
[isAsar, asarPath, filePath] = splitPath p
return readFile.apply this, arguments unless isAsar
2014-09-24 10:44:00 +00:00
2015-02-01 07:31:14 +00:00
if typeof options is 'function'
callback = options
options = undefined
2014-09-24 10:44:00 +00:00
2015-02-01 07:31:14 +00:00
archive = getOrCreateArchive asarPath
return invalidArchiveError asarPath, callback unless archive
2014-09-24 10:44:00 +00:00
2015-02-01 07:31:14 +00:00
info = archive.getFileInfo filePath
return notFoundError asarPath, filePath, callback unless info
if info.size is 0
return process.nextTick -> callback null, new Buffer(0)
2015-02-01 07:31:14 +00:00
if info.unpacked
realPath = archive.copyFileOut filePath
return fs.readFile realPath, options, callback
2015-02-01 07:31:14 +00:00
if not options
2015-05-11 03:10:50 +00:00
options = encoding: null
2015-02-01 07:31:14 +00:00
else if util.isString options
2015-05-11 03:10:50 +00:00
options = encoding: options
2015-02-01 07:31:14 +00:00
else if not util.isObject options
throw new TypeError('Bad arguments')
encoding = options.encoding
2015-03-21 11:20:52 +00:00
buffer = new Buffer(info.size)
2015-05-11 03:10:50 +00:00
fd = archive.getFd()
return notFoundError asarPath, filePath, callback unless fd >= 0
fs.read fd, buffer, 0, info.size, info.offset, (error) ->
callback error, if encoding then buffer.toString encoding else buffer
2015-02-01 07:31:14 +00:00
openSync = fs.openSync
readFileSync = fs.readFileSync
2015-10-30 09:30:08 +00:00
fs.readFileSync = (p, opts) ->
2016-01-12 02:03:02 +00:00
### this allows v8 to optimize this function ###
options = opts
2015-02-01 07:31:14 +00:00
[isAsar, asarPath, filePath] = splitPath p
return readFileSync.apply this, arguments unless isAsar
2014-09-24 10:44:00 +00:00
2015-02-01 07:31:14 +00:00
archive = getOrCreateArchive asarPath
invalidArchiveError asarPath unless archive
2015-02-01 07:31:14 +00:00
info = archive.getFileInfo filePath
notFoundError asarPath, filePath unless info
if info.size is 0
return if options then '' else new Buffer(0)
2015-02-01 07:31:14 +00:00
if info.unpacked
realPath = archive.copyFileOut filePath
return fs.readFileSync realPath, options
2015-02-01 07:31:14 +00:00
if not options
2015-05-11 03:10:50 +00:00
options = encoding: null
2015-02-01 07:31:14 +00:00
else if util.isString options
2015-05-11 03:10:50 +00:00
options = encoding: options
2015-02-01 07:31:14 +00:00
else if not util.isObject options
throw new TypeError('Bad arguments')
encoding = options.encoding
2015-03-21 11:20:52 +00:00
buffer = new Buffer(info.size)
2015-05-11 03:10:50 +00:00
fd = archive.getFd()
notFoundError asarPath, filePath unless fd >= 0
fs.readSync fd, buffer, 0, info.size, info.offset
2015-03-21 11:20:52 +00:00
if encoding then buffer.toString encoding else buffer
2015-02-01 07:31:14 +00:00
readdir = fs.readdir
fs.readdir = (p, callback) ->
[isAsar, asarPath, filePath] = splitPath p
2015-02-01 07:31:14 +00:00
return readdir.apply this, arguments unless isAsar
archive = getOrCreateArchive asarPath
return invalidArchiveError asarPath, callback unless archive
2015-02-01 07:31:14 +00:00
files = archive.readdir filePath
return notFoundError asarPath, filePath, callback unless files
2015-02-01 07:31:14 +00:00
process.nextTick -> callback null, files
2015-02-01 07:31:14 +00:00
readdirSync = fs.readdirSync
fs.readdirSync = (p) ->
2014-09-29 08:28:51 +00:00
[isAsar, asarPath, filePath] = splitPath p
2015-02-01 07:31:14 +00:00
return readdirSync.apply this, arguments unless isAsar
2014-09-29 08:28:51 +00:00
archive = getOrCreateArchive asarPath
invalidArchiveError asarPath unless archive
2014-09-29 08:28:51 +00:00
2015-02-01 07:31:14 +00:00
files = archive.readdir filePath
notFoundError asarPath, filePath unless files
2014-09-29 08:28:51 +00:00
2015-02-01 07:31:14 +00:00
files
2014-09-29 08:28:51 +00:00
internalModuleReadFile = process.binding('fs').internalModuleReadFile
process.binding('fs').internalModuleReadFile = (p) ->
[isAsar, asarPath, filePath] = splitPath p
return internalModuleReadFile p unless isAsar
archive = getOrCreateArchive asarPath
return undefined unless archive
info = archive.getFileInfo filePath
return undefined unless info
return '' if info.size is 0
if info.unpacked
realPath = archive.copyFileOut filePath
return fs.readFileSync realPath, encoding: 'utf8'
buffer = new Buffer(info.size)
fd = archive.getFd()
2015-09-19 18:17:23 +00:00
return undefined unless fd >= 0
fs.readSync fd, buffer, 0, info.size, info.offset
buffer.toString 'utf8'
internalModuleStat = process.binding('fs').internalModuleStat
process.binding('fs').internalModuleStat = (p) ->
[isAsar, asarPath, filePath] = splitPath p
return internalModuleStat p unless isAsar
archive = getOrCreateArchive asarPath
2016-01-12 02:03:02 +00:00
### -ENOENT ###
return -34 unless archive
stats = archive.stat filePath
2016-01-12 02:03:02 +00:00
### -ENOENT ###
return -34 unless stats
if stats.isDirectory then return 1 else return 0
2016-01-12 02:03:02 +00:00
###
Calling mkdir for directory inside asar archive should throw ENOTDIR
error, but on Windows it throws ENOENT.
This is to work around the recursive looping bug of mkdirp since it is
widely used.
###
if process.platform is 'win32'
mkdir = fs.mkdir
fs.mkdir = (p, mode, callback) ->
callback = mode if typeof mode is 'function'
[isAsar, asarPath, filePath] = splitPath p
return notDirError callback if isAsar and filePath.length
mkdir p, mode, callback
mkdirSync = fs.mkdirSync
fs.mkdirSync = (p, mode) ->
[isAsar, asarPath, filePath] = splitPath p
notDirError() if isAsar and filePath.length
mkdirSync p, mode
2015-02-01 07:31:14 +00:00
overrideAPI fs, 'open'
overrideAPI child_process, 'execFile'
overrideAPISync process, 'dlopen', 1
overrideAPISync require('module')._extensions, '.node', 1
overrideAPISync fs, 'openSync'
overrideAPISync child_process, 'execFileSync'