Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: mmap asar files #24470

Merged
merged 10 commits into from
Aug 4, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 8 additions & 20 deletions lib/common/asar.js
Original file line number Diff line number Diff line change
Expand Up @@ -520,17 +520,12 @@
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);
fs.read(fd, buffer, 0, info.size, info.offset, error => {
callback(error, encoding ? buffer.toString(encoding) : buffer);
archive.read(info.offset, info.size).then((buf) => {
const buffer = Buffer.from(buf);
callback(null, encoding ? buffer.toString(encoding) : buffer);
}, (err) => {
callback(err);
});
};

Expand Down Expand Up @@ -562,13 +557,10 @@
}

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);
fs.readSync(fd, buffer, 0, info.size, info.offset);
return (encoding) ? buffer.toString(encoding) : buffer;
const buffer = Buffer.from(archive.readSync(info.offset, info.size));
zcbenz marked this conversation as resolved.
Show resolved Hide resolved
return encoding ? buffer.toString(encoding) : buffer;
};

const { readdir } = fs;
Expand Down Expand Up @@ -664,12 +656,8 @@
return fs.readFileSync(realPath, { encoding: 'utf8' });
}

const buffer = Buffer.alloc(info.size);
const fd = archive.getFd();
if (!(fd >= 0)) return;

logASARAccess(asarPath, filePath, info.offset);
fs.readSync(fd, buffer, 0, info.size, info.offset);
const buffer = Buffer.from(archive.readSync(info.offset, info.size));
return buffer.toString('utf8');
};

Expand Down
70 changes: 63 additions & 7 deletions shell/common/api/electron_api_asar.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@

#include <vector>

#include "base/numerics/safe_math.h"
#include "shell/common/asar/archive.h"
#include "shell/common/asar/asar_util.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/file_path_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/object_template_builder.h"
#include "shell/common/gin_helper/promise.h"
#include "shell/common/gin_helper/wrappable.h"
#include "shell/common/node_includes.h"
#include "shell/common/node_util.h"

namespace {

class Archive : public gin_helper::Wrappable<Archive> {
Expand All @@ -37,7 +40,8 @@ class Archive : public gin_helper::Wrappable<Archive> {
.SetMethod("readdir", &Archive::Readdir)
.SetMethod("realpath", &Archive::Realpath)
.SetMethod("copyFileOut", &Archive::CopyFileOut)
.SetMethod("getFd", &Archive::GetFD);
.SetMethod("read", &Archive::Read)
.SetMethod("readSync", &Archive::ReadSync);
}

protected:
Expand Down Expand Up @@ -103,15 +107,67 @@ class Archive : public gin_helper::Wrappable<Archive> {
return gin::ConvertToV8(isolate, new_path);
}

// Return the file descriptor.
int GetFD() const {
if (!archive_)
return -1;
return archive_->GetFD();
v8::Local<v8::ArrayBuffer> ReadSync(gin_helper::ErrorThrower thrower,
uint64_t offset,
uint64_t length) {
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()) {
thrower.ThrowError("Out of bounds read requested in ASAR");
nornagon marked this conversation as resolved.
Show resolved Hide resolved
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(gin_helper::ErrorThrower thrower,
uint64_t offset,
uint64_t length) {
gin_helper::Promise<v8::Local<v8::ArrayBuffer>> promise(thrower.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()) {
thrower.ThrowError("Out of bounds read requested in ASAR");
nornagon marked this conversation as resolved.
Show resolved Hide resolved
return v8::Local<v8::Promise>();
}

base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&Archive::ReadOnIO, thrower.isolate(), archive_, offset,
length),
base::BindOnce(&Archive::ResolveReadOnUI, std::move(promise)));

return handle;
}

private:
std::unique_ptr<asar::Archive> archive_;
static std::unique_ptr<v8::BackingStore> ReadOnIO(
v8::Isolate* isolate,
std::shared_ptr<asar::Archive> archive,
uint64_t offset,
uint64_t length) {
auto backing_store = v8::ArrayBuffer::NewBackingStore(isolate, length);
nornagon marked this conversation as resolved.
Show resolved Hide resolved
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);
};
Expand Down
86 changes: 34 additions & 52 deletions shell/common/asar/archive.cc
Original file line number Diff line number Diff line change
Expand Up @@ -117,78 +117,51 @@ bool FillFileInfoWithNode(Archive::FileInfo* info,

} // namespace

Archive::Archive(const base::FilePath& path)
: path_(path), file_(base::File::FILE_OK) {
Archive::Archive(const base::FilePath& path) : path_(path) {
base::ThreadRestrictions::ScopedAllowIO allow_io;
file_.Initialize(path_, base::File::FLAG_OPEN | base::File::FLAG_READ);
#if defined(OS_WIN)
fd_ = _open_osfhandle(reinterpret_cast<intptr_t>(file_.GetPlatformFile()), 0);
#elif defined(OS_POSIX)
fd_ = file_.GetPlatformFile();
#endif
}

Archive::~Archive() {
#if defined(OS_WIN)
if (fd_ != -1) {
_close(fd_);
// Don't close the handle since we already closed the fd.
file_.TakePlatformFile();
if (!file_.Initialize(path_)) {
LOG(ERROR) << "Failed to open ASAR archive at '" << path_.value() << "'";
}
#endif
base::ThreadRestrictions::ScopedAllowIO allow_io;
file_.Close();
}

Archive::~Archive() {}

bool Archive::Init() {
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;
}

std::vector<char> buf;
int len;

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();
if (file_.length() < 8) {
LOG(ERROR) << "Malformed ASAR file at '" << path_.value()
<< "' (too short)";
return false;
}

uint32_t size;
if (!base::PickleIterator(base::Pickle(buf.data(), buf.size()))
.ReadUInt32(&size)) {
LOG(ERROR) << "Failed to parse header size from " << path_.value();
base::PickleIterator size_pickle(
base::Pickle(reinterpret_cast<const char*>(file_.data()), 8));
if (!size_pickle.ReadUInt32(&size)) {
LOG(ERROR) << "Failed to read header size at '" << path_.value() << "'";
return false;
}

buf.resize(size);
{
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();
if (file_.length() - 8 < size) {
LOG(ERROR) << "Malformed ASAR file at '" << path_.value()
<< "' (incorrect header)";
return false;
}

base::PickleIterator header_pickle(
base::Pickle(reinterpret_cast<const char*>(file_.data() + 8), size));
std::string header;
if (!base::PickleIterator(base::Pickle(buf.data(), buf.size()))
.ReadString(&header)) {
LOG(ERROR) << "Failed to parse header from " << path_.value();
if (!header_pickle.ReadString(&header)) {
LOG(ERROR) << "Failed to read header string at '" << path_.value() << "'";
return false;
}

base::Optional<base::Value> value = base::JSONReader::Read(header);
if (!value || !value->is_dict()) {
LOG(ERROR) << "Failed to parse header";
LOG(ERROR) << "Header was not valid JSON at '" << path_.value() << "'";
return false;
}

Expand Down Expand Up @@ -291,11 +264,24 @@ bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) {
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>();
base::FilePath::StringType ext = path.Extension();
if (!temp_file->InitFromFile(&file_, ext, info.offset, info.size))
if (!temp_file->Init(ext))
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 (info.executable) {
// chmod a+x temp_file;
Expand All @@ -308,8 +294,4 @@ bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) {
return true;
}

int Archive::GetFD() const {
return fd_;
}

} // namespace asar
8 changes: 3 additions & 5 deletions shell/common/asar/archive.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/memory_mapped_file.h"

namespace base {
class DictionaryValue;
Expand Down Expand Up @@ -61,16 +62,13 @@ class Archive {
// For unpacked file, this method will return its real path.
bool CopyFileOut(const base::FilePath& path, base::FilePath* out);

// Returns the file's fd.
int GetFD() const;

base::MemoryMappedFile* file() { return &file_; }
base::FilePath path() const { return path_; }
base::DictionaryValue* header() const { return header_.get(); }

private:
base::FilePath path_;
base::File file_;
int fd_ = -1;
base::MemoryMappedFile file_;
uint32_t header_size_ = 0;
std::unique_ptr<base::DictionaryValue> header_;

Expand Down
23 changes: 0 additions & 23 deletions shell/common/asar/scoped_temporary_file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,27 +48,4 @@ bool ScopedTemporaryFile::Init(const base::FilePath::StringType& ext) {
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
6 changes: 0 additions & 6 deletions shell/common/asar/scoped_temporary_file.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,6 @@ class ScopedTemporaryFile {
// Init an empty temporary file with a certain extension.
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_; }

private:
Expand Down