blob: f381e7d49ec708aa5a9267f8b0efdbe5d668c455 [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 "zx.h"
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <iostream>
#include <iterator>
#include <memory>
#include <string>
#include "src/lib/fidl_codec/printer.h"
#include "third_party/quickjs/quickjs-libc.h"
#include "third_party/quickjs/quickjs.h"
// This file contains bindings that allow JavaScript code to invoke syscalls.
namespace shell::zx {
// Converts a zx_status_t into a JavaScript error and throws it.
JSValue ZxStatusToError(JSContext *ctx, zx_status_t status) {
if (status == ZX_OK) {
return JS_UNDEFINED;
}
JSValue obj = JS_NewError(ctx);
JS_DefinePropertyValueStr(ctx, obj, "message", JS_NewString(ctx, zx_status_get_string(status)),
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
JS_DefinePropertyValueStr(ctx, obj, "status", JS_NewInt32(ctx, status),
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
return JS_Throw(ctx, obj);
}
JSClassID handle_class_id_;
JSClassDef handle_class_ = {
.class_name = "Handle",
.finalizer = nullptr,
};
zx_handle_disposition_t HandleFromJsval(JSValue val) {
auto opaque = reinterpret_cast<JSFuchsiaHandle *>(JS_GetOpaque(val, handle_class_id_));
zx_handle_disposition_t handle;
handle.operation = fidl_codec::kNoHandleDisposition;
handle.handle = opaque->handle;
handle.type = opaque->type;
handle.rights = ZX_RIGHT_NONE;
handle.result = ZX_OK;
return handle;
}
JSValue HandleCreate(JSContext *ctx, zx_handle_t handle, zx_obj_type_t type) {
JSValue obj = JS_NewObjectClass(ctx, handle_class_id_);
if (JS_IsException(obj)) {
return obj;
}
auto s = reinterpret_cast<JSFuchsiaHandle *>(js_mallocz(ctx, sizeof(JSFuchsiaHandle)));
if (!s) {
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
s->handle = handle;
s->type = type;
JS_SetOpaque(obj, s);
return obj;
}
JSValue HandleClose(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc != 1) {
return JS_ThrowSyntaxError(ctx, "Wrong number of arguments to zx.close(), was %d, expected 1",
argc);
}
auto h = reinterpret_cast<JSFuchsiaHandle *>(JS_GetOpaque2(ctx, argv[0], handle_class_id_));
if (!h) {
return JS_EXCEPTION;
}
stop_waiting_for_zx_handle(JS_GetRuntime(ctx), h, -1);
zx_handle_close(h->handle);
h->handle = ZX_HANDLE_INVALID;
return JS_UNDEFINED;
}
JSValue ObjectWaitAsync(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc != 3) {
return JS_ThrowSyntaxError(ctx, "Wrong number of arguments to zx.close(), was %d, expected 3",
argc);
}
auto h = reinterpret_cast<JSFuchsiaHandle *>(JS_GetOpaque2(ctx, argv[0], handle_class_id_));
if (!h) {
return JS_EXCEPTION;
}
uint32_t signals;
if (JS_ToUint32(ctx, &signals, argv[1])) {
return JS_EXCEPTION;
}
if (!JS_IsFunction(ctx, argv[2])) {
return JS_ThrowTypeError(ctx, "Expected a function");
}
if (zx_object_get_info(h->handle, ZX_INFO_HANDLE_VALID, NULL, 0, NULL, NULL) ==
ZX_ERR_BAD_HANDLE) {
return JS_ThrowTypeError(ctx, "Invalid handle");
}
wait_for_zx_handle(ctx, h, signals, &argv[2]);
return JS_UNDEFINED;
}
JSValue ChannelCreate(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
zx_handle_t out0, out1;
zx_status_t status = zx_channel_create(0, &out0, &out1);
if (status != ZX_OK) {
return ZxStatusToError(ctx, status);
}
JSValue handles = JS_NewArray(ctx);
JS_SetPropertyUint32(ctx, handles, 0, HandleCreate(ctx, out0, ZX_OBJ_TYPE_CHANNEL));
JS_SetPropertyUint32(ctx, handles, 1, HandleCreate(ctx, out1, ZX_OBJ_TYPE_CHANNEL));
return handles;
}
// This gets passed a Handle object.
// TODO(jeremymanson): Support flags
JSValue ChannelRead(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc != 1) {
return JS_ThrowSyntaxError(
ctx, "Wrong number of arguments to zx.channelRead(), was %d, expected 1", argc);
}
auto h = reinterpret_cast<JSFuchsiaHandle *>(JS_GetOpaque2(ctx, argv[0], handle_class_id_));
if (!h) {
return JS_EXCEPTION;
}
uint32_t num_bytes;
uint8_t bytes[ZX_CHANNEL_MAX_MSG_BYTES];
uint32_t num_handles;
zx_handle_info_t handles[ZX_CHANNEL_MAX_MSG_HANDLES];
zx_status_t status = zx_channel_read_etc(h->handle, 0, bytes, handles, std::size(bytes),
std::size(handles), &num_bytes, &num_handles);
if (status != ZX_OK) {
return ZxStatusToError(ctx, status);
}
JSValue bytes_buffer = JS_NewArrayBufferCopy(ctx, bytes, num_bytes);
JSValue handles_array = JS_NewArray(ctx);
for (uint32_t i = 0; i < num_handles; i++) {
JS_SetPropertyUint32(ctx, handles_array, i,
HandleCreate(ctx, handles[i].handle, handles[i].type));
}
// TODO(jeremymanson): We can do better than an array here.
JSValue ret = JS_NewArray(ctx);
JS_SetPropertyUint32(ctx, ret, 0, bytes_buffer);
JS_SetPropertyUint32(ctx, ret, 1, handles_array);
return ret;
}
// Takes a Handle, an array of bytes, and an array of Handles.
// TODO(jeremymanson): Should this be an array of zx.Objects?
JSValue ChannelWrite(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc != 3) {
return JS_ThrowSyntaxError(ctx, "Wrong number of arguments to zx.write(), was %d, expected 3",
argc);
}
auto h = reinterpret_cast<JSFuchsiaHandle *>(JS_GetOpaque2(ctx, argv[0], handle_class_id_));
if (!h) {
return JS_EXCEPTION;
}
size_t num_bytes;
uint8_t *bytes = JS_GetArrayBuffer(ctx, &num_bytes, argv[1]);
if (!bytes) {
return JS_ThrowTypeError(ctx, "Expected an ArrayBuffer");
}
if (num_bytes > ZX_CHANNEL_MAX_MSG_BYTES) {
return JS_ThrowRangeError(ctx, "Message length exceeds %d bytes", ZX_CHANNEL_MAX_MSG_BYTES);
}
zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES];
uint32_t num_handles;
if (!JS_IsArray(ctx, argv[2])) {
return JS_ThrowTypeError(ctx, "Expected an Array");
}
JSValue num_handles_value = JS_GetPropertyStr(ctx, argv[2], "length");
if (JS_IsException(num_handles_value)) {
return num_handles_value;
}
if (JS_ToUint32(ctx, &num_handles, num_handles_value)) {
return JS_EXCEPTION;
}
if (num_handles > ZX_CHANNEL_MAX_MSG_HANDLES) {
return JS_ThrowRangeError(ctx, "Message handle count exceeds %d", ZX_CHANNEL_MAX_MSG_HANDLES);
}
for (uint32_t i = 0; i < num_handles; i++) {
JSValue item = JS_GetPropertyUint32(ctx, argv[2], i);
auto ih = reinterpret_cast<JSFuchsiaHandle *>(JS_GetOpaque2(ctx, item, handle_class_id_));
if (!ih) {
return JS_ThrowTypeError(ctx, "Expected a handle at index %d into handle array", i);
}
handles[i] = ih->handle;
ih->handle = ZX_HANDLE_INVALID;
}
zx_status_t status = zx_channel_write(h->handle, 0, bytes, num_bytes, handles, num_handles);
return ZxStatusToError(ctx, status);
}
JSValue Duplicate(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc != 2) {
return JS_ThrowSyntaxError(ctx, "Wrong number of arguments to zx.write(), was %d, expected 2",
argc);
}
auto h = reinterpret_cast<JSFuchsiaHandle *>(JS_GetOpaque2(ctx, argv[0], handle_class_id_));
if (!h) {
return JS_EXCEPTION;
}
uint32_t right_mask;
if (JS_ToUint32(ctx, &right_mask, argv[1])) {
return JS_EXCEPTION;
}
zx_handle_t out;
zx_status_t status = zx_handle_duplicate(h->handle, right_mask, &out);
if (status != ZX_OK) {
return ZxStatusToError(ctx, status);
}
return HandleCreate(ctx, out, h->type);
}
// Converts a number to a Handle object, where number is a zx_handle_t that the code
// got from somewhere.
// argv[0] is the Number.
static JSValue HandleFromInt(JSContext *ctx, JSValueConst /*this_val*/, int argc,
JSValueConst *argv) {
if (argc != 1) {
return JS_EXCEPTION;
}
zx_handle_t handle;
if (JS_ToInt32(ctx, reinterpret_cast<int32_t *>(&handle), argv[0])) {
return JS_EXCEPTION;
}
zx_info_handle_basic_t basic;
if (zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &basic, sizeof(basic), nullptr, nullptr) !=
ZX_OK) {
return JS_ThrowTypeError(ctx, "Invalid handle");
}
return HandleCreate(ctx, handle, basic.type);
}
// Calls zx_object_get_child.
// argv[0] is the handle
// argv[1] is the koid for which you want the handle.
// argv[2] is the rights you have on the child handle.
// Returns the handle of the child.
JSValue GetChild(JSContext *ctx, JSValueConst /*this_val*/, int argc, JSValueConst *argv) {
if (argc != 3) {
return JS_ThrowSyntaxError(ctx, "Bad arguments to zx.getChild()");
}
static_assert(sizeof(zx_rights_t) == sizeof(uint32_t));
uint64_t koid;
zx_rights_t rights;
zx_handle_disposition_t handle_disposition = HandleFromJsval(argv[0]);
JS_ToInt64(ctx, reinterpret_cast<int64_t *>(&koid), argv[1]);
JS_ToInt32(ctx, reinterpret_cast<int32_t *>(&rights), argv[2]);
zx_handle_t out;
zx_status_t status = zx_object_get_child(handle_disposition.handle, koid, rights, &out);
if (status != ZX_OK) {
return ZxStatusToError(ctx, status);
}
zx_info_handle_basic_t basic;
if (zx_object_get_info(out, ZX_INFO_HANDLE_BASIC, &basic, sizeof(basic), nullptr, nullptr) !=
ZX_OK) {
return JS_ThrowTypeError(ctx, "Invalid handle");
}
return HandleCreate(ctx, out, basic.type);
}
// Provides a generic interface for dealing with the output of zx_object_get_info, that
// can be specialized for the kind of info we're getting.
class GetInfoController {
public:
GetInfoController(JSContext *ctx, uint32_t topic) : ctx_(ctx), topic_(topic) {}
virtual ~GetInfoController() = default;
// Creates a buffer for the output of zx_object_get_info. |size| is a hint, and may be
// ignored if you know better. However, if it isn't enough room, we'll loop until it is.
virtual void *SetBuffer(size_t size) = 0;
// Gets the current buffer size.
virtual size_t BufferSize() = 0;
// Gets a reference to the buffer.
virtual void *GetBuffer() = 0;
// Converts the contents of the buffer into a JSValue (usually with the same fields as the struct)
virtual JSValue GetValues(size_t actual) = 0;
// Gets the controller for the given |topic|, where topic is the same as the topic of the
// zx_object_get_info.
static GetInfoController *GetCorrectController(JSContext *ctx, uint32_t topic);
protected:
JSContext *ctx_;
uint32_t topic_;
};
// GetInfoController for zx_koid_t[]
class KoidInfoController : public GetInfoController {
public:
KoidInfoController(JSContext *ctx, uint32_t topic) : GetInfoController(ctx, topic) {}
void *SetBuffer(size_t size) override {
auto *arr = new zx_koid_t[size];
buffer_.reset(arr);
size_ = size;
return reinterpret_cast<void *>(arr);
}
size_t BufferSize() override { return size_; }
void *GetBuffer() override { return buffer_.get(); }
JSValue GetValues(size_t actual) override {
JSValue arr = JS_NewArray(ctx_);
for (size_t i = 0; i < actual; i++) {
JS_SetPropertyUint32(ctx_, arr, i, JS_NewInt64(ctx_, buffer_.get()[i]));
}
return arr;
}
private:
std::unique_ptr<zx_koid_t[]> buffer_;
size_t size_;
};
// GetInfoController for zx_info_handle_basic_t.
class BasicInfoController : public GetInfoController {
public:
BasicInfoController(JSContext *ctx, uint32_t topic) : GetInfoController(ctx, topic) {}
size_t BufferSize() override { return sizeof(basic_info_); }
void *GetBuffer() override { return &basic_info_; }
void *SetBuffer(size_t /*size*/) override { return GetBuffer(); }
JSValue GetValues(size_t /*actual*/) override {
JSValue object = JS_NewObject(ctx_);
if (JS_SetPropertyStr(ctx_, object, "koid", JS_NewInt32(ctx_, basic_info_.koid)) != 1) {
return JS_ThrowInternalError(ctx_, "Unable to set koid");
}
if (JS_SetPropertyStr(ctx_, object, "rights", JS_NewInt32(ctx_, basic_info_.rights)) != 1) {
return JS_ThrowInternalError(ctx_, "Unable to set rights");
}
if (JS_SetPropertyStr(ctx_, object, "type", JS_NewInt32(ctx_, basic_info_.type)) != 1) {
return JS_ThrowInternalError(ctx_, "Unable to set type");
}
if (JS_SetPropertyStr(ctx_, object, "related_koid",
JS_NewInt32(ctx_, basic_info_.related_koid)) != 1) {
return JS_ThrowInternalError(ctx_, "Unable to set related_koid");
}
return object;
}
private:
zx_info_handle_basic_t basic_info_;
};
GetInfoController *GetInfoController::GetCorrectController(JSContext *ctx, uint32_t topic) {
switch (topic) {
case ZX_INFO_JOB_CHILDREN:
case ZX_INFO_JOB_PROCESSES:
case ZX_INFO_PROCESS_THREADS:
return new KoidInfoController(ctx, topic);
case ZX_INFO_HANDLE_VALID:
return nullptr;
case ZX_INFO_HANDLE_BASIC:
return new BasicInfoController(ctx, topic);
case ZX_INFO_PROCESS:
case ZX_INFO_VMAR:
case ZX_INFO_THREAD:
case ZX_INFO_THREAD_EXCEPTION_REPORT:
case ZX_INFO_TASK_STATS:
case ZX_INFO_PROCESS_MAPS:
case ZX_INFO_PROCESS_VMOS:
case ZX_INFO_THREAD_STATS:
case ZX_INFO_CPU_STATS:
case ZX_INFO_KMEM_STATS:
case ZX_INFO_RESOURCE:
case ZX_INFO_HANDLE_COUNT:
case ZX_INFO_BTI:
case ZX_INFO_PROCESS_HANDLE_STATS:
case ZX_INFO_SOCKET:
case ZX_INFO_VMO:
default:
return nullptr;
}
}
// Calls zx_object_get_info, and returns a JSValue that looks like the struct returned
// by that call.
// argv[0] is a handle, argv[1] is the topic
JSValue ObjectGetInfo(JSContext *ctx, JSValueConst /*this_val*/, int argc, JSValueConst *argv) {
if (argc != 2) {
return JS_ThrowSyntaxError(ctx, "Bad arguments to zx.objectGetInfo");
}
zx_handle_disposition_t handle_disposition = HandleFromJsval(argv[0]);
uint32_t topic;
if (JS_ToUint32(ctx, &topic, argv[1])) {
return JS_ThrowSyntaxError(ctx, "Bad topic for zx.objectGetInfo");
}
GetInfoController *c = GetInfoController::GetCorrectController(ctx, topic);
if (c == nullptr) {
std::string msg = "zx.objectGetInfo topic " + std::to_string(topic) + " not implemented";
return JS_ThrowSyntaxError(ctx, "%s", msg.c_str());
}
std::unique_ptr<GetInfoController> controller;
controller.reset(c);
std::unique_ptr<void *> buffer;
size_t actual;
size_t avail;
constexpr size_t kMaxAttempts = 5;
size_t attempt = 0;
// 16 seems like a nice round number.
size_t buffer_size = 16;
do {
attempt++;
void *buffer = controller->SetBuffer(buffer_size);
zx_status_t status = zx_object_get_info(handle_disposition.handle, topic, buffer,
controller->BufferSize(), &actual, &avail);
if (status != ZX_OK) {
return ZxStatusToError(ctx, status);
}
buffer_size *= 2;
} while (actual < avail && attempt < kMaxAttempts);
return controller->GetValues(actual);
}
// argv[0] is a handle
// argv[1] is a legal property for zx_object_get_property.
JSValue ObjectGetProperty(JSContext *ctx, JSValueConst /*this_val*/, int argc, JSValueConst *argv) {
if (argc != 2) {
return JS_ThrowSyntaxError(ctx, "Bad arguments to zx.objectGetProperty");
}
zx_handle_disposition_t handle_disposition = HandleFromJsval(argv[0]);
uint32_t property;
if (JS_ToUint32(ctx, &property, argv[1])) {
return JS_ThrowSyntaxError(ctx, "Bad property for zx.objectGetProperty");
}
if (property != ZX_PROP_NAME) {
return JS_ThrowInternalError(ctx, "Operation %d not supported on zx.objectGetProperty",
property);
}
char name[ZX_MAX_NAME_LEN];
zx_status_t status =
zx_object_get_property(handle_disposition.handle, property, name, sizeof(name));
if (status != ZX_OK) {
return ZxStatusToError(ctx, status);
}
return JS_NewString(ctx, name);
}
JSValue ProcessSelf(JSContext *ctx, JSValueConst /*this_val*/, int /*argc*/,
JSValueConst * /*argv*/) {
zx_handle_t self = zx_process_self();
return HandleCreate(ctx, self, ZX_OBJ_TYPE_PROCESS);
}
JSValue JobDefault(JSContext *ctx, JSValueConst /*this_val*/, int /*argc*/,
JSValueConst * /*argv*/) {
zx_handle_t d = zx_job_default();
return HandleCreate(ctx, d, ZX_OBJ_TYPE_JOB);
}
// Calls zx_task_kill
// argv[0] is a handle to the task to kill
JSValue Kill(JSContext *ctx, JSValueConst /*this_val*/, int argc, JSValueConst *argv) {
if (argc != 1) {
return JS_ThrowSyntaxError(ctx, "Bad arguments to zx.kill");
}
zx_handle_disposition_t handle_disposition = HandleFromJsval(argv[0]);
zx_status_t status = zx_task_kill(handle_disposition.handle);
if (status != ZX_OK) {
return ZxStatusToError(ctx, status);
}
return JS_NewInt32(ctx, 0);
}
#define FLAG(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE)
#define FLAG_64(x) JS_PROP_INT64_DEF(#x, x, JS_PROP_CONFIGURABLE)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wc99-designator"
const JSCFunctionListEntry funcs_[] = {
/* Fuchsia handle operations */
JS_CFUNC_DEF("channelCreate", 0, ChannelCreate),
JS_CFUNC_DEF("channelRead", 0, ChannelRead),
JS_CFUNC_DEF("channelWrite", 0, ChannelWrite),
JS_CFUNC_DEF("handleClose", 0, HandleClose),
JS_CFUNC_DEF("objectWaitAsync", 0, ObjectWaitAsync),
JS_CFUNC_DEF("duplicate", 0, Duplicate),
JS_CFUNC_DEF("getChild", 0, GetChild),
JS_CFUNC_DEF("handleFromInt", 0, HandleFromInt),
JS_CFUNC_DEF("getObjectInfo", 2, ObjectGetInfo),
JS_CFUNC_DEF("getObjectProperty", 2, ObjectGetProperty),
JS_CFUNC_DEF("jobDefault", 2, JobDefault),
JS_CFUNC_DEF("processSelf", 2, ProcessSelf),
JS_CFUNC_DEF("kill", 1, Kill),
/* Handle signal constants */
FLAG(ZX_CHANNEL_READABLE),
FLAG(ZX_CHANNEL_WRITABLE),
FLAG(ZX_CHANNEL_PEER_CLOSED),
/* zx_object_get_info flags */
FLAG(ZX_INFO_NONE),
FLAG(ZX_INFO_HANDLE_VALID),
FLAG(ZX_INFO_HANDLE_BASIC),
FLAG(ZX_INFO_PROCESS),
FLAG(ZX_INFO_PROCESS_THREADS),
FLAG(ZX_INFO_VMAR),
FLAG(ZX_INFO_JOB_CHILDREN),
FLAG(ZX_INFO_JOB_PROCESSES),
FLAG(ZX_INFO_THREAD),
FLAG(ZX_INFO_THREAD_EXCEPTION_REPORT),
FLAG(ZX_INFO_TASK_STATS),
FLAG(ZX_INFO_PROCESS_MAPS),
FLAG(ZX_INFO_PROCESS_VMOS),
FLAG(ZX_INFO_THREAD_STATS),
FLAG(ZX_INFO_CPU_STATS),
FLAG(ZX_INFO_KMEM_STATS),
FLAG(ZX_INFO_RESOURCE),
FLAG(ZX_INFO_HANDLE_COUNT),
FLAG(ZX_INFO_BTI),
FLAG(ZX_INFO_PROCESS_HANDLE_STATS),
FLAG(ZX_INFO_SOCKET),
FLAG(ZX_INFO_VMO),
/* zx_object_get_property flags */
FLAG(ZX_PROP_NAME),
FLAG_64(ZX_RIGHT_SAME_RIGHTS),
};
#pragma GCC diagnostic pop
namespace {
int ZxRunOnInit(JSContext *ctx, JSModuleDef *m) {
JS_NewClassID(&handle_class_id_);
JS_NewClass(JS_GetRuntime(ctx), handle_class_id_, &handle_class_);
return JS_SetModuleExportList(ctx, m, funcs_, std::size(funcs_));
}
} // namespace
JSModuleDef *ZxModuleInit(JSContext *ctx, const char *module_name) {
JSModuleDef *m = JS_NewCModule(ctx, module_name, ZxRunOnInit);
if (!m) {
return nullptr;
}
JS_AddModuleExportList(ctx, m, funcs_, std::size(funcs_));
return m;
}
} // namespace shell::zx