| // Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "entd/entd.h" |
| |
| #include <stdio.h> |
| |
| #include <iostream> |
| |
| #include <base/file_util.h> |
| #include <event.h> |
| #include <glib-object.h> |
| #include <v8.h> |
| |
| #include "cros/chromeos_cros_api.h" |
| #include "entd/browser.h" |
| #include "entd/callback_server.h" |
| #include "entd/flimflam.h" |
| #include "entd/http.h" |
| #include "entd/crypto_openssl.h" |
| #include "entd/crypto_pkcs11.h" |
| #include "entd/scriptable.h" |
| #include "entd/syslog.h" |
| #include "entd/tpm.h" |
| #include "entd/utils.h" |
| |
| namespace entd { |
| |
| using std::string; |
| static const char kOpenSSLConfigName[] = "entd"; |
| |
| void dispatch_OnTimeout(int fd, short flags, void* arg); |
| |
| inline struct timeval CreateTimeoutInMs(uint64_t milliseconds) { |
| struct timeval tv = { milliseconds / 1000, milliseconds % 1000 * 1000 }; |
| return tv; |
| } |
| |
| static const char kDefaultLsbRelease[] = "/etc/lsb-release"; |
| |
| // Class to represent a pending timeout event. |
| class Timeout { |
| public: |
| Timeout() |
| : entd_(NULL), event_(NULL) { |
| } |
| |
| virtual ~Timeout() { |
| v8_value_.Dispose(); |
| delete event_; |
| } |
| |
| virtual bool Initialize(Entd* entd, v8::Handle<v8::Value> v8_value, |
| uint32_t interval) { |
| entd_ = entd; |
| v8_value_ = v8::Persistent<v8::Value>::New(v8_value); |
| |
| // Least significant half (on 32bit x86 :P) of the address of the timeout |
| // data, shifted left 16 bytes and or'd with the timeout sequence |
| // should give us unique timeout handles that are difficult to guess. |
| handle_ = (((0xFFFF & reinterpret_cast<size_t>(this)) << 16) | |
| ++Timeout::sequence); |
| |
| struct timeval tv = CreateTimeoutInMs(interval); |
| event_ = new struct event; |
| event_set(event_, 0, 0, &dispatch_OnTimeout, this); |
| event_add(event_, &tv); |
| |
| return true; |
| } |
| |
| virtual uint32_t GetHandle() const { |
| return handle_; |
| } |
| |
| virtual v8::Local<v8::Value> GetValue() const { |
| return v8::Local<v8::Value>::New(v8_value_); |
| } |
| |
| virtual Entd* GetEntd() const { |
| return entd_; |
| } |
| |
| virtual void CancelEvent() { |
| event_del(event_); |
| } |
| |
| private: |
| // Count of timeouts created so far, used to ensure globally unique handles. |
| static uint32_t sequence; |
| |
| // Pointer to the daemon instance. |
| Entd* entd_; |
| // Integer handle used to identify this timeout to JavaScript. |
| uint32_t handle_; |
| // JS value to execute when the timeout fires. |
| v8::Persistent<v8::Value> v8_value_; |
| // libevent structure for this timeout event. |
| struct event* event_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Timeout); |
| }; |
| |
| uint32_t Timeout::sequence = 0; |
| |
| // Class to handle calling native timeouts. Implemented using libevent. |
| class NativeTimeout |
| { |
| public: |
| NativeTimeout(Entd::NativeTimeoutCallback cb, void* data, |
| uint32_t interval_ms) |
| : callback_(cb), data_(data), |
| interval_(interval_ms * 1000), |
| handle_(0), event_(NULL) { |
| } |
| |
| ~NativeTimeout() { |
| event_del(event_); |
| delete event_; |
| } |
| |
| void Initialize() { |
| handle_ = s_handle_counter_++; |
| event_ = new struct event; |
| event_set(event_, 0, 0, &EventCallback, this); |
| Start(); |
| } |
| |
| // Calling Start on a running timeout will simply reset the timer |
| void Start() { |
| struct timeval tv = CreateTimeoutInMs(interval_); |
| event_add(event_, &tv); |
| } |
| |
| uint32_t GetHandle() { return handle_; } |
| |
| private: |
| // Calls the set callback. If the callback returns 'true', start it again. |
| static void EventCallback(int fd, short flags, void* arg) { |
| NativeTimeout* timeout = reinterpret_cast<NativeTimeout*>(arg); |
| bool res = timeout->callback_(timeout->data_); |
| if (res) { |
| timeout->Start(); |
| } |
| } |
| |
| // Callback data |
| Entd::NativeTimeoutCallback callback_; |
| void* data_; |
| |
| uint32_t interval_; |
| |
| // Handle identifier (simple integer incrementing from 1) |
| uint32_t handle_; |
| |
| // libevent data |
| struct event* event_; |
| |
| // Global handle counter |
| static uint32_t s_handle_counter_; |
| |
| DISALLOW_COPY_AND_ASSIGN(NativeTimeout); |
| }; |
| |
| uint32_t NativeTimeout::s_handle_counter_ = 1; |
| |
| // Called by libevent when a signal is detected. |
| void dispatch_OnSignal(int fd, short flags, void* arg) { |
| Entd* entd = reinterpret_cast<Entd*>(arg); |
| entd->OnSignal(fd); |
| } |
| |
| // Called by libevent when a timeout fires. |
| void dispatch_OnTimeout(int fd, short flags, void* arg) { |
| Timeout* timeout = reinterpret_cast<Timeout*>(arg); |
| // LOG(INFO) << "Fire timeout: " << timeout->GetHandle(); |
| timeout->GetEntd()->OnTimeout(*timeout); |
| delete timeout; |
| } |
| |
| // Called by v8 for entd.setTimeout(). |
| v8::Handle<v8::Value> dispatch_SetTimeout(const v8::Arguments& args) { |
| Entd* entd = Entd::Unwrap(args.This()); |
| if (!entd) |
| return v8::Undefined(); |
| |
| if (args.Length() < 2) { |
| ThrowException(v8::String::New("Not enough parameters")); |
| return v8::Undefined(); |
| } |
| |
| if (!(args[0]->IsString() || args[0]->IsFunction())) { |
| ThrowException(v8::String::New( |
| "First argument must be string or function")); |
| return v8::Undefined(); |
| } |
| |
| return v8::Uint32::New(entd->SetTimeout(args[0], args[1]->Uint32Value())); |
| } |
| |
| // Called by v8 for entd.clearTimeout(). |
| v8::Handle<v8::Value> dispatch_ClearTimeout(const v8::Arguments& args) { |
| Entd* entd = Entd::Unwrap(args.This()); |
| if (!entd) |
| return v8::Undefined(); |
| |
| if (args.Length() < 1) { |
| ThrowException(v8::String::New("Not enough parameters")); |
| return v8::Undefined(); |
| } |
| |
| Timeout* timeout = entd->ClearTimeout(args[0]->Uint32Value()); |
| if (!timeout) |
| return v8::False(); |
| |
| delete timeout; |
| return v8::True(); |
| } |
| |
| // Called by v8 for entd.scheduleShutdown(). |
| v8::Handle<v8::Value> dispatch_ScheduleShutdown(const v8::Arguments& args) { |
| Entd* entd = Entd::Unwrap(args.This()); |
| if (!entd) |
| return v8::Undefined(); |
| |
| uint32_t code = 0; |
| if (args.Length() > 0) |
| code = args[0]->Uint32Value(); |
| |
| uint32_t interval = 0; |
| if (args.Length() > 1) |
| interval = args[1]->Uint32Value(); |
| |
| entd->ScheduleShutdown(code, interval); |
| |
| return v8::True(); |
| } |
| |
| // Called by v8 when someone trys to read from entd.hostname |
| v8::Handle<v8::Value> dispatch_GetHostname(v8::Local<v8::String> name, |
| const v8::AccessorInfo& info) { |
| Entd* entd = Entd::Unwrap(info.Holder()); |
| return v8::String::New(entd->GetHostname().c_str()); |
| } |
| |
| // Called by v8 when someone trys to assign to entd.hostname |
| void dispatch_SetHostname(v8::Local<v8::String> name, |
| v8::Local<v8::Value> value, |
| const v8::AccessorInfo& info) { |
| Entd* entd = Entd::Unwrap(info.Holder()); |
| if (!entd->SetHostname(*v8::String::Utf8Value(value))) |
| utils::ThrowV8Exception("Invalid hostname"); |
| } |
| |
| // Called by v8 for the global object's print() method. |
| v8::Handle<v8::Value> dispatch_Print(const v8::Arguments& args) { |
| for (int i = 0; i < args.Length(); ++i) { |
| v8::Handle<v8::Value> arg = args[i]; |
| v8::String::Utf8Value value(arg); |
| std::cout << *value; |
| } |
| |
| std::cout.flush(); |
| return v8::Undefined(); |
| } |
| |
| // Called by v8 for the global object's print() method. |
| v8::Handle<v8::Value> dispatch_PrintLn(const v8::Arguments& args) { |
| for (int i = 0; i < args.Length(); ++i) { |
| v8::Handle<v8::Value> arg = args[i]; |
| v8::String::Utf8Value value(arg); |
| std::cout << *value; |
| } |
| std::cout << "\n"; |
| std::cout.flush(); |
| return v8::Undefined(); |
| } |
| |
| // Called by v8 for the global object's readFromFile() method. |
| v8::Handle<v8::Value> dispatch_ReadFromFile(const v8::Arguments& args) { |
| if (args.Length() != 1) { |
| utils::ThrowV8Exception("Missing argument: filePath"); |
| return v8::False(); |
| } |
| std::string file = utils::ValueAsUtf8String(args[0]); |
| FilePath filepath = FilePath(file); |
| std::string filestr; |
| v8::Handle<v8::String> result; |
| if (file_util::ReadFileToString(filepath, &filestr)) |
| result = v8::String::New(filestr.c_str(), filestr.size()); |
| if (result.IsEmpty()) |
| utils::ThrowV8Exception(std::string("Unable to read file: ") + file); |
| return result; |
| } |
| |
| // Called by v8 for the global object's writeToFile() method. |
| v8::Handle<v8::Value> dispatch_WriteToFile(const v8::Arguments& args) { |
| if (args.Length() != 2) { |
| utils::ThrowV8Exception("Insufficient arguments."); |
| return v8::False(); |
| } |
| std::string s = utils::ValueAsUtf8String(args[0]); |
| std::string file = utils::ValueAsUtf8String(args[1]); |
| int slen = s.length(); |
| int res = -1; |
| if (slen > 0) { |
| res = file_util::WriteFile(FilePath(file), s.c_str(), slen); |
| } |
| if (res < 0 || res != slen) { |
| utils::ThrowV8Exception(std::string("Unable to write file: ") + file); |
| return v8::False(); |
| } |
| return v8::True(); |
| } |
| |
| // Called by v8 for the global object's GC() method. |
| v8::Handle<v8::Value> dispatch_GC(const v8::Arguments& args) { |
| v8::V8::LowMemoryNotification(); |
| return v8::Undefined(); |
| } |
| |
| Entd::Entd() |
| : lsb_release_filename_(kDefaultLsbRelease), |
| callback_server_(NULL), |
| flimflam_(new Flimflam()), |
| syslog_(new Syslog()) { |
| ::g_type_init(); |
| } |
| |
| Entd::~Entd() { |
| // Clean up any pending timeouts. |
| for (unsigned int i = 0; i < timeout_list_.size(); ++i) { |
| Timeout* timeout = timeout_list_[i]; |
| timeout->CancelEvent(); |
| delete timeout; |
| } |
| CleanupTemplate(); |
| context_.Dispose(); |
| } |
| |
| bool Entd::allow_dirty_exit = false; |
| bool Entd::allow_file_io = false; |
| bool Entd::libcros_loaded = false; |
| std::string Entd::libcros_location = "/opt/google/chrome/chromeos/libcros.so"; |
| |
| // Bind "setTimeout" and "clearTimeout" to the entd template object. |
| void Entd::SetTemplateBindings(v8::Handle<v8::ObjectTemplate> template_object) { |
| template_object->Set(v8::String::NewSymbol("setTimeout"), |
| v8::FunctionTemplate::New(dispatch_SetTimeout), |
| v8::ReadOnly); |
| template_object->Set(v8::String::NewSymbol("clearTimeout"), |
| v8::FunctionTemplate::New(dispatch_ClearTimeout), |
| v8::ReadOnly); |
| template_object->Set(v8::String::NewSymbol("scheduleShutdown"), |
| v8::FunctionTemplate::New(dispatch_ScheduleShutdown), |
| v8::ReadOnly); |
| |
| template_object->SetAccessor(v8::String::NewSymbol("hostname"), |
| dispatch_GetHostname, |
| dispatch_SetHostname); |
| } |
| |
| bool Entd::Initialize() { |
| if (!context_.IsEmpty()) { |
| LOG(ERROR) << "Entd initialized twice."; |
| return false; |
| } |
| |
| event_init(); |
| LOG(INFO) << "Initialized libevent " << event_get_version(); |
| |
| std::string errmsg; |
| Entd::libcros_loaded = chromeos::LoadLibcros( |
| Entd::libcros_location.c_str(), errmsg); |
| if (!Entd::libcros_loaded) |
| LOG(WARNING) << "Problem loading chromeos shared object: " << errmsg; |
| |
| return true; |
| } |
| |
| v8::Handle<v8::Object> Entd::ConstructEntd() { |
| v8::HandleScope handle_scope; |
| |
| // Build the entd object. |
| if (!JSObjectWrapper<Entd>::Initialize()) { |
| LOG(ERROR) << "Error initializing entd"; |
| exit(1); |
| } |
| v8::Handle<v8::Object> entd = obj(); |
| |
| entd->Set(v8::String::NewSymbol("isLibcrosLoaded"), |
| v8::Boolean::New(Entd::libcros_loaded)); |
| |
| // Build the flimflam object. |
| if (!flimflam_->Initialize()) { |
| LOG(ERROR) << "Error initializing entd.flimflam"; |
| exit(1); |
| } |
| entd->Set(v8::String::NewSymbol("flimflam"), |
| flimflam_->obj(), v8::ReadOnly); |
| |
| // Build the http object. |
| Http::Reference http = Http::New(); |
| if (http.IsEmpty() || !http->Initialize(this, NULL)) { |
| LOG(ERROR) << "Error initializing entd.http"; |
| exit(1); |
| } |
| entd->Set(v8::String::NewSymbol("http"), |
| http.js_object(), v8::ReadOnly); |
| |
| // Build the syslog object. |
| if (!syslog_->Initialize()) { |
| LOG(ERROR) << "Error initializing entd.syslog"; |
| exit(1); |
| } |
| entd->Set(v8::String::NewSymbol("syslog"), |
| syslog_->obj(), v8::ReadOnly); |
| |
| // Build the callbackServer object. |
| callback_server_.reset(new CallbackServer(this)); |
| if (!callback_server_->Initialize()) { |
| LOG(ERROR) << "Error initializing entd.callback_server"; |
| exit(1); |
| } |
| entd->Set(v8::String::NewSymbol("callbackServer"), |
| callback_server_->obj(), v8::ReadOnly); |
| |
| // Build the entd.crypto object. |
| v8::Handle<v8::Object> crypto_obj = v8::Object::New(); |
| entd->Set(v8::String::NewSymbol("crypto"), crypto_obj); |
| |
| // Hook up the entd.crypto.Pkcs11 constructor. |
| crypto_obj->Set(v8::String::NewSymbol("Pkcs11"), |
| crypto::Pkcs11::constructor_template()->GetFunction()); |
| |
| // Hook up the entd.crypto.OpenSSL constructor. |
| crypto_obj->Set(v8::String::NewSymbol("OpenSSL"), |
| crypto::OpenSSL::constructor_template()->GetFunction()); |
| |
| Browser::Reference browser = Browser::New(); |
| if (!browser->Initialize(this)) { |
| LOG(ERROR) << "Error initializing entd.browser"; |
| exit(1); |
| } |
| |
| entd->Set(v8::String::NewSymbol("Browser"), |
| Browser::constructor_template()->GetFunction()); |
| entd->Set(v8::String::NewSymbol("browser"), browser->js_object()); |
| |
| Tpm::Reference tpm = Tpm::New(); |
| if (!tpm->Initialize()) { |
| LOG(ERROR) << "Error initializing entd.tpm"; |
| exit(1); |
| } |
| |
| entd->Set(v8::String::NewSymbol("Tpm"), |
| Tpm::constructor_template()->GetFunction()); |
| entd->Set(v8::String::NewSymbol("tpm"), tpm->js_object()); |
| |
| return handle_scope.Close(entd); |
| } |
| |
| bool Entd::StartScriptingEnvironment() { |
| v8::HandleScope handle_scope; |
| |
| // Create the global object. |
| v8::Handle<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New(); |
| global_template->Set(v8::String::NewSymbol("print"), |
| v8::FunctionTemplate::New(dispatch_Print), |
| v8::ReadOnly); |
| global_template->Set(v8::String::NewSymbol("println"), |
| v8::FunctionTemplate::New(dispatch_PrintLn), |
| v8::ReadOnly); |
| global_template->Set(v8::String::NewSymbol("GC"), |
| v8::FunctionTemplate::New(dispatch_GC), |
| v8::ReadOnly); |
| |
| if (allow_file_io) { |
| global_template->Set(v8::String::NewSymbol("readFromFile"), |
| v8::FunctionTemplate::New(dispatch_ReadFromFile), |
| v8::ReadOnly); |
| global_template->Set(v8::String::NewSymbol("writeToFile"), |
| v8::FunctionTemplate::New(dispatch_WriteToFile), |
| v8::ReadOnly); |
| } |
| context_ = v8::Context::New(NULL, global_template); |
| |
| v8::Context::Scope context_scope(context_); |
| |
| v8::Handle<v8::Object> global = context_->Global(); |
| |
| // Construct the global entd object. |
| v8::Handle<v8::Object> entd = ConstructEntd(); |
| |
| // Set the "entd" property of the global object. |
| global->Set(v8::String::NewSymbol("entd"), entd, v8::ReadOnly); |
| |
| // Temporary storage for the generic values used below. |
| v8::Handle<v8::Value> value; |
| |
| // Set entd.username and entd.hostname. |
| entd->Set(v8::String::NewSymbol("username"), |
| v8::String::New(username_.c_str()), v8::ReadOnly); |
| |
| // Set entd.lsbRelease to the contents the lsb_release file. |
| std::string lsb_file_contents; |
| if (!file_util::ReadFileToString(FilePath(lsb_release_filename_), |
| &lsb_file_contents)) { |
| LOG(ERROR) << "Could not read " << lsb_release_filename_; |
| return false; |
| } |
| entd->Set(v8::String::NewSymbol("lsbRelease"), |
| v8::String::New(lsb_file_contents.c_str()), v8::ReadOnly); |
| |
| size_t at_pos = username_.find("@"); |
| if (at_pos == std::string::npos || at_pos == username_.length() - 1) { |
| LOG(ERROR) << "Can't determine hostname from username: " << username_; |
| return false; |
| } |
| |
| if (!SetHostname(username_.substr(at_pos + 1))) { |
| LOG(ERROR) << "Unable to set host name."; |
| return false; |
| } |
| |
| // Load manifest file, if one was provided. |
| v8::Handle<v8::Object> manifest; |
| |
| if (manifest_filename_.empty()) { |
| LOG(WARNING) << "No manifest file."; |
| } else { |
| if (!JsonParseFromFile(manifest_filename_, &value) || !value->IsObject()) { |
| LOG(ERROR) << "Error loading manifest file: " << manifest_filename_; |
| return false; |
| } |
| |
| manifest = v8::Handle<v8::Object>::Cast(value); |
| } |
| |
| // Load utility file, if one was provided. |
| if (!utility_filename_.empty()) { |
| LOG(INFO) << "Executing utility file: " << utility_filename_; |
| if (!ExecuteFile(utility_filename_, &value)) { |
| LOG(ERROR) << "Error in utility file."; |
| return false; |
| } |
| |
| if (entd->Has(v8::String::NewSymbol("verifyManifest"))) { |
| v8::TryCatch try_catch; |
| |
| v8::Handle<v8::Value> arg; |
| if (manifest.IsEmpty()) { |
| arg = v8::Null(); |
| } else { |
| arg = manifest; |
| } |
| |
| value = utils::CallV8Function(entd, "verifyManifest", 1, &arg); |
| if (try_catch.HasCaught()) { |
| // Failed due to V8 exception |
| utils::ReportV8Exception(&try_catch); |
| return false; |
| } else if (value.IsEmpty()) { |
| // Failed for other reasons |
| return false; |
| } |
| |
| if (!value->IsBoolean() || value->BooleanValue() != true) { |
| LOG(ERROR) << "Utility file vetoed the extension manifest."; |
| return false; |
| } |
| } |
| } |
| |
| // Load the policy file. |
| if (policy_filename_.empty()) { |
| LOG(ERROR) << "No policy file."; |
| return false; |
| } |
| |
| if (!ExecuteFile(policy_filename_, &value)) { |
| LOG(ERROR) << "Error in policy file."; |
| return false; |
| } |
| |
| LOG(INFO) << "Policy loaded."; |
| |
| if (!crypto::OpenSSL::StaticInitialize(kOpenSSLConfigName)) { |
| LOG(ERROR) << "Failed to initialize OpenSSL."; |
| return false; |
| } |
| |
| // Fire the onLoad event. |
| if (entd->Has(v8::String::NewSymbol("onLoad"))) { |
| v8::TryCatch try_catch; |
| |
| v8::Handle<v8::Value> arg; |
| if (manifest.IsEmpty()) { |
| arg = v8::Null(); |
| } else { |
| arg = manifest; |
| } |
| |
| value = utils::CallV8Function(entd, "onLoad", 1, &arg); |
| if (try_catch.HasCaught()) { |
| // Failed due to V8 exception |
| utils::ReportV8Exception(&try_catch); |
| return false; |
| } else if (value.IsEmpty()) { |
| // Failed for other reasons |
| LOG(INFO) << "Policy onLoad failed for mysterious reasons."; |
| return false; |
| } |
| |
| LOG(INFO) << "Policy onLoad complete."; |
| } |
| |
| return true; |
| } |
| |
| bool Entd::StopScriptingEnvironment() { |
| // Temporary storage for the a generic value used in this function. |
| v8::Handle<v8::Value> value; |
| |
| value = context_->Global()->Get(v8::String::NewSymbol("entd")); |
| if (value.IsEmpty() || !value->IsObject()) { |
| LOG(ERROR) << "Global entd missing or not an object"; |
| return false; |
| } |
| |
| v8::Handle<v8::Object> entd = v8::Handle<v8::Object>::Cast(value); |
| |
| if (entd->Has(v8::String::NewSymbol("onUnload"))) { |
| v8::TryCatch try_catch; |
| |
| value = utils::CallV8Function(entd, "onUnload", 0, NULL); |
| if (try_catch.HasCaught()) { |
| // Failed due to V8 exception |
| utils::ReportV8Exception(&try_catch); |
| return false; |
| } else if (value.IsEmpty()) { |
| // Failed for other reasons |
| return false; |
| } |
| } |
| |
| crypto::Pkcs11::Finalize(); |
| |
| // Unload OpenSSL modules. |
| crypto::OpenSSL::StaticFinalize(); |
| |
| return true; |
| } |
| |
| // static |
| bool Entd::LibcrosLoadedOrThrow() { |
| if (!Entd::libcros_loaded) { |
| utils::ThrowV8Exception("Library not loaded: libcros"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // See comments in main.cc about the return values. |
| uint32_t Entd::Run() { |
| exit_code_ = 0; |
| |
| if (!StartScriptingEnvironment()) |
| return 1; |
| |
| struct event ev_SIGHUP; |
| struct event ev_SIGINT; |
| struct event ev_SIGTERM; |
| |
| if (!Entd::allow_dirty_exit) { |
| event_set(&ev_SIGHUP, SIGHUP, EV_SIGNAL | EV_PERSIST, &dispatch_OnSignal, |
| reinterpret_cast<void*>(this)); |
| event_add(&ev_SIGHUP, NULL); |
| event_set(&ev_SIGINT, SIGINT, EV_SIGNAL | EV_PERSIST, &dispatch_OnSignal, |
| reinterpret_cast<void*>(this)); |
| event_add(&ev_SIGINT, NULL); |
| event_set(&ev_SIGTERM, SIGTERM, EV_SIGNAL | EV_PERSIST, &dispatch_OnSignal, |
| reinterpret_cast<void*>(this)); |
| event_add(&ev_SIGTERM, NULL); |
| } |
| |
| // Set up the handle and context scope for JavaScript run from event handlers, |
| // or from StopScriptingEnvironment(). |
| v8::HandleScope handle_scope; |
| v8::Context::Scope context_scope(context_); |
| |
| event_loop(0); |
| |
| if (!Entd::allow_dirty_exit) { |
| event_del(&ev_SIGHUP); |
| event_del(&ev_SIGINT); |
| event_del(&ev_SIGTERM); |
| } |
| |
| if (!StopScriptingEnvironment()) { |
| if (exit_code_ == 2) { |
| // The policy was asking for a restart. We failed to shut down for some |
| // reason, but would still like to honor the restart. |
| return 3; |
| } |
| |
| if (exit_code_ < 2) { |
| // We weren't going to restart anyway. |
| return 1; |
| } |
| } |
| |
| return exit_code_; |
| } |
| |
| void Entd::OnSignal(int signal) { |
| LOG(INFO) << "Responding to signal: " << signal; |
| event_loopbreak(); |
| } |
| |
| uint32_t Entd::SetTimeout(v8::Handle<v8::Value> value, uint32_t interval) { |
| Timeout* timeout = new Timeout(); |
| if (!timeout->Initialize(this, value, interval)) |
| return 0; |
| |
| timeout_list_.push_back(timeout); |
| return timeout->GetHandle(); |
| } |
| |
| Timeout* Entd::ClearTimeout(uint32_t timeout_handle) { |
| for (unsigned int i = 0; i < timeout_list_.size(); ++i) { |
| Timeout* timeout = timeout_list_[i]; |
| if (timeout->GetHandle() == timeout_handle) { |
| // LOG(INFO) << "Remove timeout handle: " << timeout_handle; |
| timeout->CancelEvent(); |
| timeout_list_.erase(timeout_list_.begin() + i); |
| return timeout; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| void Entd::ScheduleShutdown(uint32_t code, uint32_t interval) { |
| exit_code_ = code; |
| struct timeval tv = CreateTimeoutInMs(interval); |
| event_loopexit(&tv); |
| } |
| |
| void Entd::OnTimeout(const Timeout& timeout) { |
| v8::Context::Scope context_scope(context_); |
| v8::HandleScope handle_scope; |
| |
| uint32_t timeout_handle = timeout.GetHandle(); |
| // LOG(INFO) << "OnTimeout: " << timeout_handle; |
| |
| // Remove this from our active timeouts list first, so any attempt to |
| // call clearTimeout from script doesn't cause trouble. If we can't clear |
| // the timeout for some reason then something strange is going on. A |
| // timeout should only get removed from the list of timeouts by firing |
| // (this case) or through a ClearTimeout, which should prevent it from |
| // firing. |
| if (!ClearTimeout(timeout_handle)) |
| LOG(ERROR) << "Failed to clear the current timeout, something is fishy."; |
| |
| v8::Local<v8::Value> value = timeout.GetValue(); |
| if (value->IsString()) { |
| v8::Handle<v8::String> script = v8::Handle<v8::String>::Cast(value); |
| v8::Handle<v8::Value> result; |
| char filename[20]; |
| snprintf(&filename[0], sizeof(filename), "(timeout %u)", timeout_handle); |
| ExecuteString(script, filename, &result); |
| |
| } else if (value->IsFunction()) { |
| v8::Handle<v8::Function> func = v8::Handle<v8::Function>::Cast(value); |
| func->Call(context_->Global(), 0, NULL); |
| } |
| } |
| |
| // Initialize and start a timeout |
| uint32_t Entd::SetNativeTimeout(NativeTimeoutCallback cb, void* data, |
| uint32_t interval_ms) { |
| NativeTimeout* timeout = new NativeTimeout(cb, data, interval_ms); |
| uint32_t handle = timeout->GetHandle(); |
| native_timeouts_[handle] = timeout; |
| timeout->Initialize(); |
| return handle; |
| } |
| |
| // Start an existing timeout (resets timer if already started) |
| // Returns false if timeout doesn't exist |
| bool Entd::StartNativeTimeout(uint32_t handle) { |
| std::map<uint32_t, NativeTimeout*>::iterator iter = |
| native_timeouts_.find(handle); |
| if (iter != native_timeouts_.end()) { |
| iter->second->Start(); |
| return true; |
| } |
| return false; |
| } |
| |
| // Cancel and delete an existing timeout |
| // Returns false if timeout doesn't exist |
| bool Entd::ClearNativeTimeout(uint32_t handle) { |
| std::map<uint32_t, NativeTimeout*>::iterator iter = |
| native_timeouts_.find(handle); |
| if (iter != native_timeouts_.end()) { |
| delete iter->second; |
| native_timeouts_.erase(iter); |
| return true; |
| } |
| return false; |
| } |
| |
| bool Entd::CheckHostname(const string& new_hostname) { |
| if (hostname_.empty()) |
| return utils::CheckHostnameCharset(new_hostname); |
| |
| size_t new_len = new_hostname.length(); |
| size_t current_len = hostname_.length(); |
| |
| if (new_len < current_len) { |
| // New hostname is shorter than the existing hostname, can't possibly |
| // be right. |
| return false; |
| } |
| |
| size_t starts_at = new_len - current_len; |
| |
| if (new_hostname.compare(starts_at, current_len, hostname_) != 0) { |
| // New host doesn't match existing one. |
| return false; |
| } |
| |
| if (starts_at == 0) { |
| // New host and old host are exactly the same. |
| return true; |
| } |
| |
| // Otherwise, the new hostname must more specific than the existing one. |
| |
| if (starts_at < 2) { |
| // It takes at least two characters to be more specific (eg: add "a." to |
| // the existing hostname.) |
| return false; |
| } |
| |
| if (new_hostname[starts_at - 1] != '.') |
| return false; |
| |
| return utils::CheckHostnameCharset(new_hostname); |
| } |
| |
| bool Entd::JsonParseFromFile(const string& filename, |
| v8::Handle<v8::Value>* result) { |
| if (!result) { |
| LOG(ERROR) << "Null pointer passed for result"; |
| return false; |
| } |
| |
| v8::Handle<v8::String> source = utils::ReadFile(filename); |
| if (source.IsEmpty()) { |
| LOG(ERROR) << "Error reading json file: " << filename; |
| return false; |
| } |
| |
| return JsonParse(source, result); |
| } |
| |
| bool Entd::JsonParse(v8::Handle<v8::String> source, |
| v8::Handle<v8::Value>* result) { |
| return CallJson("parse", source, result); |
| } |
| |
| bool Entd::JsonStringify(v8::Handle<v8::Value> source, |
| v8::Handle<v8::Value>* result) { |
| return CallJson("stringify", source, result); |
| } |
| |
| bool Entd::CallJson(string method_name, v8::Handle<v8::Value> arg, |
| v8::Handle<v8::Value>* result) { |
| if (!result) { |
| LOG(ERROR) << "Null pointer passed for result"; |
| return false; |
| } |
| |
| v8::Handle<v8::Value> value = |
| context_->Global()->Get(v8::String::NewSymbol("JSON")); |
| if (value.IsEmpty() || !value->IsObject()) { |
| LOG(ERROR) << "Global JSON property missing or not an object."; |
| return false; |
| } |
| |
| v8::Handle<v8::Object> json = v8::Handle<v8::Object>::Cast(value); |
| |
| value = json->Get(v8::String::NewSymbol(method_name.c_str())); |
| if (value.IsEmpty() || !value->IsFunction()) { |
| LOG(ERROR) << "JSON method missing or not a function: " << method_name; |
| return false; |
| } |
| |
| v8::TryCatch try_catch; |
| v8::Handle<v8::Value> call_result = |
| utils::CallV8Function(json, method_name, 1, &arg); |
| if (try_catch.HasCaught()) { |
| // Failed due to V8 exception |
| utils::ReportV8Exception(&try_catch); |
| return false; |
| } else if (call_result.IsEmpty()) { |
| // Failed for other reasons |
| return false; |
| } |
| |
| *result = call_result; |
| |
| return true; |
| } |
| |
| bool Entd::ExecuteFile(const std::string& filename, |
| v8::Handle<v8::Value>* result) { |
| v8::HandleScope handle_scope; |
| v8::Handle<v8::String> source = utils::ReadFile(filename); |
| if (source.IsEmpty()) { |
| PLOG(WARNING) << "Error reading source file: " << filename; |
| return false; |
| } |
| |
| return ExecuteString(source, filename, result); |
| } |
| |
| bool Entd::ExecuteString(const v8::Handle<v8::String>& source, |
| const std::string& filename, |
| v8::Handle<v8::Value>* result) { |
| v8::HandleScope handle_scope; |
| v8::TryCatch try_catch; |
| |
| v8::Handle<v8::Script> script = |
| v8::Script::Compile(source, v8::String::New(filename.c_str())); |
| |
| if (script.IsEmpty()) { |
| utils::ReportV8Exception(&try_catch); |
| return false; |
| } |
| |
| *result = script->Run(); |
| if (!result || result->IsEmpty()) { |
| utils::ReportV8Exception(&try_catch); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| } // namespace entd |