fix: revert "refactor: mmap asar files (#24470)" (#28137)

This reverts commit 01a2e23194.
This commit is contained in:
Jeremy Rose 2021-03-15 11:42:54 -07:00 committed by GitHub
parent b023b33c05
commit d27ad0d182
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 114 additions and 133 deletions

View file

@ -532,15 +532,17 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
return fs.readFile(realPath, options, callback); return fs.readFile(realPath, options, callback);
} }
const buffer = Buffer.alloc(info.size);
const fd = archive.getFd();
if (!(fd >= 0)) {
const error = createError(AsarError.NOT_FOUND, { asarPath, filePath });
nextTick(callback, [error]);
return;
}
logASARAccess(asarPath, filePath, info.offset); logASARAccess(asarPath, filePath, info.offset);
archive.read(info.offset, info.size).then((buf) => { fs.read(fd, buffer, 0, info.size, info.offset, (error: Error) => {
const buffer = Buffer.from(buf); callback(error, encoding ? buffer.toString(encoding) : buffer);
callback(null, encoding ? buffer.toString(encoding) : buffer);
}, (err) => {
const error: AsarErrorObject = new Error(`EINVAL, ${err.message} while reading ${filePath} in ${asarPath}`);
error.code = 'EINVAL';
error.errno = -22;
callback(error);
}); });
}; };
@ -573,19 +575,13 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
} }
const { encoding } = options; const { encoding } = options;
const buffer = Buffer.alloc(info.size);
const fd = archive.getFd();
if (!(fd >= 0)) throw createError(AsarError.NOT_FOUND, { asarPath, filePath });
logASARAccess(asarPath, filePath, info.offset); logASARAccess(asarPath, filePath, info.offset);
let arrayBuffer: ArrayBuffer; fs.readSync(fd, buffer, 0, info.size, info.offset);
try { return (encoding) ? buffer.toString(encoding) : buffer;
arrayBuffer = archive.readSync(info.offset, info.size);
} catch (err) {
const error: AsarErrorObject = new Error(`EINVAL, ${err.message} while reading ${filePath} in ${asarPath}`);
error.code = 'EINVAL';
error.errno = -22;
throw error;
}
const buffer = Buffer.from(arrayBuffer);
return encoding ? buffer.toString(encoding) : buffer;
}; };
const { readdir } = fs; const { readdir } = fs;
@ -697,17 +693,12 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
return [str, str.length > 0]; return [str, str.length > 0];
} }
const buffer = Buffer.alloc(info.size);
const fd = archive.getFd();
if (!(fd >= 0)) return [];
logASARAccess(asarPath, filePath, info.offset); logASARAccess(asarPath, filePath, info.offset);
let arrayBuffer: ArrayBuffer; fs.readSync(fd, buffer, 0, info.size, info.offset);
try {
arrayBuffer = archive.readSync(info.offset, info.size);
} catch (err) {
const error: AsarErrorObject = new Error(`EINVAL, ${err.message} while reading ${filePath} in ${asarPath}`);
error.code = 'EINVAL';
error.errno = -22;
throw error;
}
const buffer = Buffer.from(arrayBuffer);
const str = buffer.toString('utf8'); const str = buffer.toString('utf8');
return [str, str.length > 0]; return [str, str.length > 0];
}; };

View file

@ -4,7 +4,6 @@
#include <vector> #include <vector>
#include "base/numerics/safe_math.h"
#include "gin/handle.h" #include "gin/handle.h"
#include "gin/object_template_builder.h" #include "gin/object_template_builder.h"
#include "gin/wrappable.h" #include "gin/wrappable.h"
@ -13,9 +12,6 @@
#include "shell/common/gin_converters/callback_converter.h" #include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/file_path_converter.h" #include "shell/common/gin_converters/file_path_converter.h"
#include "shell/common/gin_helper/dictionary.h" #include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/error_thrower.h"
#include "shell/common/gin_helper/function_template_extensions.h"
#include "shell/common/gin_helper/promise.h"
#include "shell/common/node_includes.h" #include "shell/common/node_includes.h"
#include "shell/common/node_util.h" #include "shell/common/node_util.h"
@ -42,8 +38,7 @@ class Archive : public gin::Wrappable<Archive> {
.SetMethod("readdir", &Archive::Readdir) .SetMethod("readdir", &Archive::Readdir)
.SetMethod("realpath", &Archive::Realpath) .SetMethod("realpath", &Archive::Realpath)
.SetMethod("copyFileOut", &Archive::CopyFileOut) .SetMethod("copyFileOut", &Archive::CopyFileOut)
.SetMethod("read", &Archive::Read) .SetMethod("getFd", &Archive::GetFD);
.SetMethod("readSync", &Archive::ReadSync);
} }
const char* GetTypeName() override { return "Archive"; } const char* GetTypeName() override { return "Archive"; }
@ -109,68 +104,15 @@ class Archive : public gin::Wrappable<Archive> {
return gin::ConvertToV8(isolate, new_path); return gin::ConvertToV8(isolate, new_path);
} }
v8::Local<v8::ArrayBuffer> ReadSync(gin_helper::ErrorThrower thrower, // Return the file descriptor.
uint64_t offset, int GetFD() const {
uint64_t length) { if (!archive_)
base::CheckedNumeric<uint64_t> safe_offset(offset); return -1;
base::CheckedNumeric<uint64_t> safe_end = safe_offset + length; return archive_->GetFD();
if (!safe_end.IsValid() ||
safe_end.ValueOrDie() > archive_->file()->length()) {
thrower.ThrowError("Out of bounds read");
return v8::Local<v8::ArrayBuffer>();
}
auto array_buffer = v8::ArrayBuffer::New(thrower.isolate(), length);
auto backing_store = array_buffer->GetBackingStore();
memcpy(backing_store->Data(), archive_->file()->data() + offset, length);
return array_buffer;
}
v8::Local<v8::Promise> Read(v8::Isolate* isolate,
uint64_t offset,
uint64_t length) {
gin_helper::Promise<v8::Local<v8::ArrayBuffer>> promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle();
base::CheckedNumeric<uint64_t> safe_offset(offset);
base::CheckedNumeric<uint64_t> safe_end = safe_offset + length;
if (!safe_end.IsValid() ||
safe_end.ValueOrDie() > archive_->file()->length()) {
promise.RejectWithErrorMessage("Out of bounds read");
return handle;
}
auto backing_store = v8::ArrayBuffer::NewBackingStore(isolate, length);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&Archive::ReadOnIO, isolate, archive_,
std::move(backing_store), offset, length),
base::BindOnce(&Archive::ResolveReadOnUI, std::move(promise)));
return handle;
} }
private: private:
static std::unique_ptr<v8::BackingStore> ReadOnIO( std::unique_ptr<asar::Archive> archive_;
v8::Isolate* isolate,
std::shared_ptr<asar::Archive> archive,
std::unique_ptr<v8::BackingStore> backing_store,
uint64_t offset,
uint64_t length) {
memcpy(backing_store->Data(), archive->file()->data() + offset, length);
return backing_store;
}
static void ResolveReadOnUI(
gin_helper::Promise<v8::Local<v8::ArrayBuffer>> promise,
std::unique_ptr<v8::BackingStore> backing_store) {
v8::HandleScope scope(promise.isolate());
v8::Context::Scope context_scope(promise.GetContext());
auto array_buffer =
v8::ArrayBuffer::New(promise.isolate(), std::move(backing_store));
promise.Resolve(array_buffer);
}
std::shared_ptr<asar::Archive> archive_;
DISALLOW_COPY_AND_ASSIGN(Archive); DISALLOW_COPY_AND_ASSIGN(Archive);
}; };

View file

@ -117,51 +117,78 @@ bool FillFileInfoWithNode(Archive::FileInfo* info,
} // namespace } // namespace
Archive::Archive(const base::FilePath& path) : path_(path) { Archive::Archive(const base::FilePath& path)
: path_(path), file_(base::File::FILE_OK) {
base::ThreadRestrictions::ScopedAllowIO allow_io; base::ThreadRestrictions::ScopedAllowIO allow_io;
if (base::PathExists(path_) && !file_.Initialize(path_)) { file_.Initialize(path_, base::File::FLAG_OPEN | base::File::FLAG_READ);
LOG(ERROR) << "Failed to open ASAR archive at '" << path_.value() << "'"; #if defined(OS_WIN)
} fd_ = _open_osfhandle(reinterpret_cast<intptr_t>(file_.GetPlatformFile()), 0);
#elif defined(OS_POSIX)
fd_ = file_.GetPlatformFile();
#endif
} }
Archive::~Archive() {} Archive::~Archive() {
#if defined(OS_WIN)
if (fd_ != -1) {
_close(fd_);
// Don't close the handle since we already closed the fd.
file_.TakePlatformFile();
}
#endif
base::ThreadRestrictions::ScopedAllowIO allow_io;
file_.Close();
}
bool Archive::Init() { bool Archive::Init() {
if (!file_.IsValid()) { if (!file_.IsValid()) {
if (file_.error_details() != base::File::FILE_ERROR_NOT_FOUND) {
LOG(WARNING) << "Opening " << path_.value() << ": "
<< base::File::ErrorToString(file_.error_details());
}
return false; return false;
} }
if (file_.length() < 8) { std::vector<char> buf;
LOG(ERROR) << "Malformed ASAR file at '" << path_.value() int len;
<< "' (too short)";
buf.resize(8);
{
base::ThreadRestrictions::ScopedAllowIO allow_io;
len = file_.ReadAtCurrentPos(buf.data(), buf.size());
}
if (len != static_cast<int>(buf.size())) {
PLOG(ERROR) << "Failed to read header size from " << path_.value();
return false; return false;
} }
uint32_t size; uint32_t size;
base::PickleIterator size_pickle( if (!base::PickleIterator(base::Pickle(buf.data(), buf.size()))
base::Pickle(reinterpret_cast<const char*>(file_.data()), 8)); .ReadUInt32(&size)) {
if (!size_pickle.ReadUInt32(&size)) { LOG(ERROR) << "Failed to parse header size from " << path_.value();
LOG(ERROR) << "Failed to read header size at '" << path_.value() << "'";
return false; return false;
} }
if (file_.length() - 8 < size) { buf.resize(size);
LOG(ERROR) << "Malformed ASAR file at '" << path_.value() {
<< "' (incorrect header)"; base::ThreadRestrictions::ScopedAllowIO allow_io;
len = file_.ReadAtCurrentPos(buf.data(), buf.size());
}
if (len != static_cast<int>(buf.size())) {
PLOG(ERROR) << "Failed to read header from " << path_.value();
return false; return false;
} }
base::PickleIterator header_pickle(
base::Pickle(reinterpret_cast<const char*>(file_.data() + 8), size));
std::string header; std::string header;
if (!header_pickle.ReadString(&header)) { if (!base::PickleIterator(base::Pickle(buf.data(), buf.size()))
LOG(ERROR) << "Failed to read header string at '" << path_.value() << "'"; .ReadString(&header)) {
LOG(ERROR) << "Failed to parse header from " << path_.value();
return false; return false;
} }
base::Optional<base::Value> value = base::JSONReader::Read(header); base::Optional<base::Value> value = base::JSONReader::Read(header);
if (!value || !value->is_dict()) { if (!value || !value->is_dict()) {
LOG(ERROR) << "Header was not valid JSON at '" << path_.value() << "'"; LOG(ERROR) << "Failed to parse header";
return false; return false;
} }
@ -264,24 +291,11 @@ bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) {
return true; return true;
} }
base::CheckedNumeric<uint64_t> safe_offset(info.offset);
auto safe_end = safe_offset + info.size;
if (!safe_end.IsValid() || safe_end.ValueOrDie() > file_.length())
return false;
auto temp_file = std::make_unique<ScopedTemporaryFile>(); auto temp_file = std::make_unique<ScopedTemporaryFile>();
base::FilePath::StringType ext = path.Extension(); base::FilePath::StringType ext = path.Extension();
if (!temp_file->Init(ext)) if (!temp_file->InitFromFile(&file_, ext, info.offset, info.size))
return false; return false;
base::File dest(temp_file->path(),
base::File::FLAG_OPEN | base::File::FLAG_WRITE);
if (!dest.IsValid())
return false;
dest.WriteAtCurrentPos(
reinterpret_cast<const char*>(file_.data() + info.offset), info.size);
#if defined(OS_POSIX) #if defined(OS_POSIX)
if (info.executable) { if (info.executable) {
// chmod a+x temp_file; // chmod a+x temp_file;
@ -294,4 +308,8 @@ bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) {
return true; return true;
} }
int Archive::GetFD() const {
return fd_;
}
} // namespace asar } // namespace asar

View file

@ -11,7 +11,6 @@
#include "base/files/file.h" #include "base/files/file.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/files/memory_mapped_file.h"
namespace base { namespace base {
class DictionaryValue; class DictionaryValue;
@ -62,13 +61,16 @@ class Archive {
// For unpacked file, this method will return its real path. // For unpacked file, this method will return its real path.
bool CopyFileOut(const base::FilePath& path, base::FilePath* out); bool CopyFileOut(const base::FilePath& path, base::FilePath* out);
base::MemoryMappedFile* file() { return &file_; } // Returns the file's fd.
int GetFD() const;
base::FilePath path() const { return path_; } base::FilePath path() const { return path_; }
base::DictionaryValue* header() const { return header_.get(); } base::DictionaryValue* header() const { return header_.get(); }
private: private:
base::FilePath path_; base::FilePath path_;
base::MemoryMappedFile file_; base::File file_;
int fd_ = -1;
uint32_t header_size_ = 0; uint32_t header_size_ = 0;
std::unique_ptr<base::DictionaryValue> header_; std::unique_ptr<base::DictionaryValue> header_;

View file

@ -48,4 +48,27 @@ bool ScopedTemporaryFile::Init(const base::FilePath::StringType& ext) {
return true; return true;
} }
bool ScopedTemporaryFile::InitFromFile(base::File* src,
const base::FilePath::StringType& ext,
uint64_t offset,
uint64_t size) {
if (!src->IsValid())
return false;
if (!Init(ext))
return false;
std::vector<char> buf(size);
int len = src->Read(offset, buf.data(), buf.size());
if (len != static_cast<int>(size))
return false;
base::File dest(path_, base::File::FLAG_OPEN | base::File::FLAG_WRITE);
if (!dest.IsValid())
return false;
return dest.WriteAtCurrentPos(buf.data(), buf.size()) ==
static_cast<int>(size);
}
} // namespace asar } // namespace asar

View file

@ -27,6 +27,12 @@ class ScopedTemporaryFile {
// Init an empty temporary file with a certain extension. // Init an empty temporary file with a certain extension.
bool Init(const base::FilePath::StringType& ext); bool Init(const base::FilePath::StringType& ext);
// Init an temporary file and fill it with content of |path|.
bool InitFromFile(base::File* src,
const base::FilePath::StringType& ext,
uint64_t offset,
uint64_t size);
base::FilePath path() const { return path_; } base::FilePath path() const { return path_; }
private: private:

View file

@ -76,8 +76,7 @@ declare namespace NodeJS {
readdir(path: string): string[] | false; readdir(path: string): string[] | false;
realpath(path: string): string | false; realpath(path: string): string | false;
copyFileOut(path: string): string | false; copyFileOut(path: string): string | false;
read(offset: number, size: number): Promise<ArrayBuffer>; getFd(): number | -1;
readSync(offset: number, size: number): ArrayBuffer;
} }
interface AsarBinding { interface AsarBinding {