blob: 796123eae47bd7cd7439b653365b5ce26e2cf942 [file] [log] [blame] [edit]
#include "node_sea.h"
#include "debug_utils-inl.h"
#include "env-inl.h"
#include "json_parser.h"
#include "node_external_reference.h"
#include "node_internals.h"
#include "node_union_bytes.h"
// The POSTJECT_SENTINEL_FUSE macro is a string of random characters selected by
// the Node.js project that is present only once in the entire binary. It is
// used by the postject_has_resource() function to efficiently detect if a
// resource has been injected. See
// https://github.com/nodejs/postject/blob/35343439cac8c488f2596d7c4c1dddfec1fddcae/postject-api.h#L42-L45.
#define POSTJECT_SENTINEL_FUSE "NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2"
#include "postject-api.h"
#undef POSTJECT_SENTINEL_FUSE
#include <memory>
#include <string_view>
#include <tuple>
#include <vector>
#if !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)
using node::ExitCode;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::Local;
using v8::Object;
using v8::Value;
namespace node {
namespace sea {
namespace {
// A special number that will appear at the beginning of the single executable
// preparation blobs ready to be injected into the binary. We use this to check
// that the data given to us are intended for building single executable
// applications.
const uint32_t kMagic = 0x143da20;
enum class SeaFlags : uint32_t {
kDefault = 0,
kDisableExperimentalSeaWarning = 1 << 0,
};
SeaFlags operator|(SeaFlags x, SeaFlags y) {
return static_cast<SeaFlags>(static_cast<uint32_t>(x) |
static_cast<uint32_t>(y));
}
SeaFlags operator&(SeaFlags x, SeaFlags y) {
return static_cast<SeaFlags>(static_cast<uint32_t>(x) &
static_cast<uint32_t>(y));
}
SeaFlags operator|=(/* NOLINT (runtime/references) */ SeaFlags& x, SeaFlags y) {
return x = x | y;
}
struct SeaResource {
SeaFlags flags = SeaFlags::kDefault;
std::string_view code;
static constexpr size_t kHeaderSize = sizeof(kMagic) + sizeof(SeaFlags);
};
SeaResource FindSingleExecutableResource() {
CHECK(IsSingleExecutable());
static const SeaResource sea_resource = []() -> SeaResource {
size_t size;
#ifdef __APPLE__
postject_options options;
postject_options_init(&options);
options.macho_segment_name = "NODE_SEA";
const char* code = static_cast<const char*>(
postject_find_resource("NODE_SEA_BLOB", &size, &options));
#else
const char* code = static_cast<const char*>(
postject_find_resource("NODE_SEA_BLOB", &size, nullptr));
#endif
uint32_t first_word = reinterpret_cast<const uint32_t*>(code)[0];
CHECK_EQ(first_word, kMagic);
SeaFlags flags{
reinterpret_cast<const SeaFlags*>(code + sizeof(first_word))[0]};
// TODO(joyeecheung): do more checks here e.g. matching the versions.
return {
flags,
{
code + SeaResource::kHeaderSize,
size - SeaResource::kHeaderSize,
},
};
}();
return sea_resource;
}
} // namespace
std::string_view FindSingleExecutableCode() {
SeaResource sea_resource = FindSingleExecutableResource();
return sea_resource.code;
}
bool IsSingleExecutable() {
return postject_has_resource();
}
void IsExperimentalSeaWarningNeeded(const FunctionCallbackInfo<Value>& args) {
if (!IsSingleExecutable()) {
args.GetReturnValue().Set(false);
return;
}
SeaResource sea_resource = FindSingleExecutableResource();
args.GetReturnValue().Set(!static_cast<bool>(
sea_resource.flags & SeaFlags::kDisableExperimentalSeaWarning));
}
std::tuple<int, char**> FixupArgsForSEA(int argc, char** argv) {
// Repeats argv[0] at position 1 on argv as a replacement for the missing
// entry point file path.
if (IsSingleExecutable()) {
static std::vector<char*> new_argv;
new_argv.reserve(argc + 2);
new_argv.emplace_back(argv[0]);
new_argv.insert(new_argv.end(), argv, argv + argc);
new_argv.emplace_back(nullptr);
argc = new_argv.size() - 1;
argv = new_argv.data();
}
return {argc, argv};
}
namespace {
struct SeaConfig {
std::string main_path;
std::string output_path;
SeaFlags flags = SeaFlags::kDefault;
};
std::optional<SeaConfig> ParseSingleExecutableConfig(
const std::string& config_path) {
std::string config;
int r = ReadFileSync(&config, config_path.c_str());
if (r != 0) {
const char* err = uv_strerror(r);
FPrintF(stderr,
"Cannot read single executable configuration from %s: %s\n",
config_path,
err);
return std::nullopt;
}
SeaConfig result;
JSONParser parser;
if (!parser.Parse(config)) {
FPrintF(stderr, "Cannot parse JSON from %s\n", config_path);
return std::nullopt;
}
result.main_path =
parser.GetTopLevelStringField("main").value_or(std::string());
if (result.main_path.empty()) {
FPrintF(stderr,
"\"main\" field of %s is not a non-empty string\n",
config_path);
return std::nullopt;
}
result.output_path =
parser.GetTopLevelStringField("output").value_or(std::string());
if (result.output_path.empty()) {
FPrintF(stderr,
"\"output\" field of %s is not a non-empty string\n",
config_path);
return std::nullopt;
}
std::optional<bool> disable_experimental_sea_warning =
parser.GetTopLevelBoolField("disableExperimentalSEAWarning");
if (!disable_experimental_sea_warning.has_value()) {
FPrintF(stderr,
"\"disableExperimentalSEAWarning\" field of %s is not a Boolean\n",
config_path);
return std::nullopt;
}
if (disable_experimental_sea_warning.value()) {
result.flags |= SeaFlags::kDisableExperimentalSeaWarning;
}
return result;
}
bool GenerateSingleExecutableBlob(const SeaConfig& config) {
std::string main_script;
// TODO(joyeecheung): unify the file utils.
int r = ReadFileSync(&main_script, config.main_path.c_str());
if (r != 0) {
const char* err = uv_strerror(r);
FPrintF(stderr, "Cannot read main script %s:%s\n", config.main_path, err);
return false;
}
std::vector<char> sink;
// TODO(joyeecheung): reuse the SnapshotSerializerDeserializer for this.
sink.reserve(SeaResource::kHeaderSize + main_script.size());
const char* pos = reinterpret_cast<const char*>(&kMagic);
sink.insert(sink.end(), pos, pos + sizeof(kMagic));
pos = reinterpret_cast<const char*>(&(config.flags));
sink.insert(sink.end(), pos, pos + sizeof(SeaFlags));
sink.insert(
sink.end(), main_script.data(), main_script.data() + main_script.size());
uv_buf_t buf = uv_buf_init(sink.data(), sink.size());
r = WriteFileSync(config.output_path.c_str(), buf);
if (r != 0) {
const char* err = uv_strerror(r);
FPrintF(stderr, "Cannot write output to %s:%s\n", config.output_path, err);
return false;
}
FPrintF(stderr,
"Wrote single executable preparation blob to %s\n",
config.output_path);
return true;
}
} // anonymous namespace
ExitCode BuildSingleExecutableBlob(const std::string& config_path) {
std::optional<SeaConfig> config_opt =
ParseSingleExecutableConfig(config_path);
if (!config_opt.has_value() ||
!GenerateSingleExecutableBlob(config_opt.value())) {
return ExitCode::kGenericUserError;
}
return ExitCode::kNoFailure;
}
void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context,
void* priv) {
SetMethod(context,
target,
"isExperimentalSeaWarningNeeded",
IsExperimentalSeaWarningNeeded);
}
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(IsExperimentalSeaWarningNeeded);
}
} // namespace sea
} // namespace node
NODE_BINDING_CONTEXT_AWARE_INTERNAL(sea, node::sea::Initialize)
NODE_BINDING_EXTERNAL_REFERENCE(sea, node::sea::RegisterExternalReferences)
#endif // !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)