| // Copyright 2023 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 <filesystem> |
| #include <type_traits> |
| #include <vector> |
| |
| #include "error.h" |
| #include "fuchsia_controller.h" |
| #include "macros.h" |
| #include "mod.h" |
| #include "src/developer/ffx/lib/fuchsia-controller/cpp/python/py_header.h" |
| #include "src/developer/ffx/lib/fuchsia-controller/cpp/raii/py_wrapper.h" |
| |
| extern struct PyModuleDef fuchsia_controller_internal; |
| |
| namespace { |
| |
| constexpr PyMethodDef SENTINEL = {nullptr, nullptr, 0, nullptr}; |
| |
| /// Enum of python-mapped C++ types |
| enum PythonTypeID : uint8_t { |
| kHandleTypeID = 0, |
| kChannelTypeID = 1, |
| kContextTypeID = 2, |
| kSocketTypeID = 3, |
| kIsolateDirTypeID = 4, |
| kEventTypeID = 5, |
| }; |
| |
| const std::vector<const char *> TypeStrings = {"Handle", "Channel", "Context", |
| "Socket", "IsolateDir", "Event"}; |
| |
| void SetDowncastError(PyObject *obj, const char *expected) { |
| py::Object repr(PyObject_Repr(obj)); |
| PyErr_Format(PyExc_TypeError, "Failed casting \"%s\", expected %s", |
| PyUnicode_AsUTF8AndSize(repr.get(), nullptr), expected); |
| } |
| |
| void SetUnknownIdError(PythonTypeID id, const char *expected) { |
| PyErr_Format(PyExc_TypeError, "Unable to cast from type %s to \"%s\"", TypeStrings[id], expected); |
| } |
| |
| std::pair<std::unique_ptr<ffx_config_t[]>, Py_ssize_t> build_config(PyObject *config, |
| const char *target) { |
| static const char TYPE_ERROR[] = "`config` must be a dictionary of string key/value pairs"; |
| if (!PyDict_Check(config)) { |
| PyErr_SetString(PyExc_TypeError, TYPE_ERROR); |
| return std::make_pair(nullptr, 0); |
| } |
| PyObject *maybe_target = PyDict_GetItem(config, PyUnicode_FromString("target.default")); |
| if (target && maybe_target) { |
| PyErr_Format( |
| PyExc_RuntimeError, |
| "Context `target` parameter set to '%s', but " |
| "config also contains 'target.default' value set to '%s'. You must only specify one", |
| target, PyUnicode_AsUTF8AndSize(maybe_target, nullptr)); |
| return std::make_pair(nullptr, 0); |
| } |
| Py_ssize_t config_len = PyDict_Size(config); |
| if (config_len < 0) { |
| return std::make_pair(nullptr, 0); |
| } |
| std::unique_ptr<ffx_config_t[]> ffx_config; |
| if (target) { |
| config_len++; |
| } |
| ffx_config = std::make_unique<ffx_config_t[]>(config_len); |
| |
| PyObject *py_key = nullptr; |
| PyObject *py_value = nullptr; |
| Py_ssize_t pos = 0; |
| // `pos` is not used for iterating in ffx_config because it is an internal |
| // iterator for a sparse map, so does not always increment by one. |
| for (Py_ssize_t i = 0; PyDict_Next(config, &pos, &py_key, &py_value); ++i) { |
| if (!PyUnicode_Check(py_key)) { |
| PyErr_SetString(PyExc_TypeError, TYPE_ERROR); |
| return std::make_pair(nullptr, 0); |
| } |
| const char *key = PyUnicode_AsUTF8AndSize(py_key, nullptr); |
| if (key == nullptr) { |
| return std::make_pair(nullptr, 0); |
| } |
| if (!PyUnicode_Check(py_value)) { |
| PyErr_SetString(PyExc_TypeError, TYPE_ERROR); |
| return std::make_pair(nullptr, 0); |
| } |
| const char *value = PyUnicode_AsUTF8AndSize(py_value, nullptr); |
| if (value == nullptr) { |
| return std::make_pair(nullptr, 0); |
| } |
| ffx_config[i] = { |
| .key = key, |
| .value = value, |
| }; |
| } |
| if (target) { |
| ffx_config[config_len - 1] = { |
| .key = "target.default", |
| .value = target, |
| }; |
| } |
| return std::make_pair(std::move(ffx_config), config_len); |
| } |
| |
| /// Base class for Python objects to inherit from. |
| class PythonObject { |
| public: |
| explicit PythonObject(PythonTypeID type_id) : type_id_(type_id) {} |
| template <typename T> |
| |
| /// Dynamically casts into a T if this is an instance of T. |
| /// Returns nullptr on failure. |
| T *as() { |
| if (type_id_ != T::type_id) { |
| return nullptr; |
| } |
| return static_cast<T *>(this); |
| } |
| |
| /// Converts this PythonObject to a PyObject. |
| /// The returned PyObject then assumes ownership of this |
| /// PythonObject and will automatically free this object |
| /// when the corresponding PyObject is garbage collected |
| /// or its reference count hits zero. |
| PyObject *IntoPyObject(); |
| |
| /// Gets the type of this object. |
| PythonTypeID GetTypeID() { return type_id_; } |
| |
| virtual ~PythonObject() = default; |
| |
| private: |
| PythonTypeID type_id_; |
| bool converted_ = false; |
| }; |
| |
| /// Generic untyped zx_handle_t. |
| class PythonHandle : public PythonObject { |
| public: |
| explicit PythonHandle(zx_handle_t handle) : PythonObject(kHandleTypeID), handle_(handle) {} |
| static constexpr PythonTypeID type_id = kHandleTypeID; |
| |
| zx_handle_t handle() const { return handle_; } |
| |
| zx_handle_t take() { |
| auto ret = handle_; |
| handle_ = 0; |
| return ret; |
| } |
| |
| ~PythonHandle() override { |
| if (handle_) { |
| ffx_close_handle(handle_); |
| } |
| } |
| |
| private: |
| zx_handle_t handle_; |
| }; |
| |
| class PythonContext : public PythonObject { |
| public: |
| explicit PythonContext(ffx_env_context_t *context) |
| : PythonObject(kContextTypeID), context_(context) {} |
| static constexpr PythonTypeID type_id = kContextTypeID; |
| |
| ffx_env_context_t *context() { return context_; } |
| |
| ~PythonContext() { destroy_ffx_env_context(context_); } |
| |
| private: |
| ffx_env_context_t *context_; |
| }; |
| |
| class PythonChannel : public PythonObject { |
| public: |
| explicit PythonChannel(zx_handle_t channel) : PythonObject(kChannelTypeID), channel_(channel) {} |
| static constexpr PythonTypeID type_id = kChannelTypeID; |
| |
| zx_handle_t channel() const { return channel_; } |
| |
| uint64_t take() { |
| auto ret = channel_; |
| channel_ = 0; |
| return ret; |
| } |
| |
| ~PythonChannel() { |
| if (channel_) { |
| ffx_close_handle(channel_); |
| channel_ = 0; |
| } |
| } |
| |
| private: |
| zx_handle_t channel_; |
| }; |
| |
| class PythonSocket : public PythonObject { |
| public: |
| explicit PythonSocket(zx_handle_t handle) : PythonObject(kSocketTypeID), handle_(handle) {} |
| |
| static constexpr PythonTypeID type_id = kSocketTypeID; |
| |
| zx_handle_t take() { |
| auto handle = handle_; |
| handle_ = 0; |
| return handle; |
| } |
| |
| zx_handle_t handle() const { return handle_; } |
| |
| ~PythonSocket() { |
| if (handle() != 0) { |
| ffx_close_handle(handle()); |
| handle_ = 0; |
| } |
| } |
| |
| private: |
| zx_handle_t handle_; |
| }; |
| |
| class PythonEvent : public PythonObject { |
| public: |
| explicit PythonEvent(zx_handle_t handle) : PythonObject(kEventTypeID), handle_(handle) {} |
| |
| static constexpr PythonTypeID type_id = kEventTypeID; |
| zx_handle_t take() { |
| auto handle = handle_; |
| handle_ = 0; |
| return handle; |
| } |
| |
| zx_handle_t handle() const { return handle_; } |
| |
| ~PythonEvent() override { |
| if (handle() != 0) { |
| ffx_close_handle(handle()); |
| handle_ = 0; |
| } |
| } |
| |
| private: |
| zx_handle_t handle_; |
| }; |
| |
| class IsolateDir : public PythonObject { |
| public: |
| explicit IsolateDir(std::filesystem::path path) |
| : PythonObject(kIsolateDirTypeID), directory_(std::move(path)) {} |
| |
| /// Attempts to create an IsolateDir. |
| /// Sets the Python error string and returns nullptr on failure. |
| static IsolateDir *Create(const char *dir_cstr) { |
| std::filesystem::path directory; |
| const char *error_format_str = "Error when creating isolate directory %s: %s"; |
| if (dir_cstr == nullptr) { |
| std::filesystem::path tmp_dir_path = std::filesystem::temp_directory_path() / "fctemp.XXXXXX"; |
| |
| // Guarantee the temporary directory is created. |
| // mkdtemp modifies its parameter in-place, so use it to create a |
| // filesystem::path before it goes out of scope |
| std::string tmp_dir_str = tmp_dir_path.string(); |
| if (mkdtemp(tmp_dir_str.data()) == nullptr) { |
| const char *error_str = strerror(errno); |
| PyErr_Format(PyExc_IOError, error_format_str, tmp_dir_str.c_str(), error_str); |
| return nullptr; |
| } |
| directory = std::filesystem::path(tmp_dir_str); |
| } else { |
| std::filesystem::path tmp_path = std::filesystem::path(dir_cstr); |
| |
| // Guarantee the directory is created. |
| std::error_code err; |
| if (!std::filesystem::create_directory(tmp_path, err)) { |
| // If |err| is falsey this indicates success, although `create_directory` |
| // might return false. This can occur when the directory already exists, |
| // so creating it succeeds even though there was an "error". |
| // |
| // Don't raise a python error in this case. |
| if (err) { |
| PyErr_Format(PyExc_IOError, error_format_str, tmp_path.c_str(), err.message().c_str()); |
| return nullptr; |
| } |
| } |
| directory = std::move(tmp_path); |
| } |
| return new IsolateDir(std::move(directory)); |
| } |
| |
| static constexpr PythonTypeID type_id = kIsolateDirTypeID; |
| |
| std::string directory() const { return directory_.string(); } |
| |
| ~IsolateDir() { std::filesystem::remove_all(directory_); } |
| |
| private: |
| std::filesystem::path directory_; |
| }; |
| |
| extern PyTypeObject *InternalHandleType; |
| |
| IGNORE_EXTRA_SC |
| using InternalHandle = struct { |
| PyObject_HEAD; |
| PythonObject *handle; |
| }; |
| |
| // Python C ABI requires that this is POD. |
| static_assert(std::is_pod_v<InternalHandle>); |
| |
| PyObject *PythonObject::IntoPyObject() { |
| if (converted_) { |
| fprintf(stderr, "IntoPyObject can only be called once."); |
| abort(); |
| } |
| converted_ = true; |
| auto handle = PyObject_New(InternalHandle, InternalHandleType); |
| handle->handle = this; |
| return reinterpret_cast<PyObject *>(handle); |
| } |
| |
| /// Constructs a T then converts it into a PyObject, and returns |
| /// the corresponding PyObject. |
| template <typename T, typename... Constructor> |
| PyObject *MakePyObject(Constructor... constructor) { |
| return (new T(constructor...))->IntoPyObject(); |
| } |
| |
| /// Takes the value out of a PythonHandle and converts it to |
| /// the specified type. The caller is responsible for ensuring |
| /// this conversion is safe. |
| /// For the conversion to be safe, the handle must be a PythonHandle, |
| /// and C++ must not reference the PythonHandle outside of this function. |
| /// The returned object is owned by the Python runtime, and the old |
| /// Handle is free'd after this call completes. |
| template <typename T> |
| T *HandleCast(InternalHandle *handle) { |
| auto newobj = new T(handle->handle->as<PythonHandle>()->take()); |
| delete handle->handle; |
| handle->handle = newobj; |
| return newobj; |
| } |
| |
| /// Specializations for Channels and Sockets |
| /// which Python expects handles to be implicitly convertible into. |
| PythonChannel *DowncastChannel(PyObject *object) { |
| if (object->ob_type != InternalHandleType) { |
| SetDowncastError(object, "channel"); |
| return nullptr; |
| } |
| auto internal_handle = reinterpret_cast<InternalHandle *>(object); |
| auto maybe_object = internal_handle->handle->as<PythonChannel>(); |
| if (maybe_object) { |
| return maybe_object; |
| } |
| auto id = internal_handle->handle->GetTypeID(); |
| if (id == kHandleTypeID) { |
| return HandleCast<PythonChannel>(internal_handle); |
| } |
| SetUnknownIdError(id, "channel"); |
| return nullptr; |
| } |
| |
| PythonSocket *DowncastSocket(PyObject *object) { |
| if (object->ob_type != InternalHandleType) { |
| SetDowncastError(object, "socket"); |
| return nullptr; |
| } |
| auto internal_handle = reinterpret_cast<InternalHandle *>(object); |
| auto maybe_object = internal_handle->handle->as<PythonSocket>(); |
| if (maybe_object) { |
| return maybe_object; |
| } |
| auto id = internal_handle->handle->GetTypeID(); |
| if (id == kHandleTypeID) { |
| return HandleCast<PythonSocket>(internal_handle); |
| } |
| SetUnknownIdError(id, "socket"); |
| return nullptr; |
| } |
| |
| PythonEvent *DowncastEvent(PyObject *object) { |
| if (object->ob_type != InternalHandleType) { |
| SetDowncastError(object, "event"); |
| return nullptr; |
| } |
| auto internal_handle = reinterpret_cast<InternalHandle *>(object); |
| auto maybe_object = internal_handle->handle->as<PythonEvent>(); |
| if (maybe_object) { |
| return maybe_object; |
| } |
| auto id = internal_handle->handle->GetTypeID(); |
| if (id == kEventTypeID) { |
| return HandleCast<PythonEvent>(internal_handle); |
| } |
| SetUnknownIdError(id, "event"); |
| return nullptr; |
| } |
| |
| /// Dynamically downcasts a PyObject to a C++ type. |
| /// Python retains ownership of the object. |
| template <typename T> |
| T *DowncastPyObject(PyObject *object) { |
| if (object->ob_type != InternalHandleType) { |
| // Object is not a valid handle. |
| return nullptr; |
| } |
| auto internal_handle = reinterpret_cast<InternalHandle *>(object); |
| return internal_handle->handle->as<T>(); |
| } |
| |
| PyObject *handle_from_int(PyObject *self, PyObject *args) { |
| unsigned int handle = 0; |
| if (!PyArg_ParseTuple(args, "I", &handle)) { |
| return nullptr; |
| } |
| return MakePyObject<PythonHandle>(handle); |
| } |
| |
| PyObject *handle_as_int(PyObject *self, PyObject *args) { |
| PyObject *object; |
| if (!PyArg_ParseTuple(args, "O", &object)) { |
| return nullptr; |
| } |
| // Get C++ object from Python object |
| auto handle = DowncastPyObject<PythonHandle>(object); |
| if (handle == nullptr) { |
| return nullptr; |
| } |
| return PyLong_FromUnsignedLongLong(handle->handle()); |
| } |
| |
| PyObject *handle_take(PyObject *self, PyObject *args) { |
| PyObject *object; |
| if (!PyArg_ParseTuple(args, "O", &object)) { |
| return nullptr; |
| } |
| auto handle = DowncastPyObject<PythonHandle>(object); |
| if (handle == nullptr) { |
| return nullptr; |
| } |
| return PyLong_FromUnsignedLongLong(handle->take()); |
| } |
| |
| void object_dealloc(InternalHandle *self) { |
| delete self->handle; |
| PyObject_Free(self); |
| } |
| |
| PyObject *handle_create(PyObject *self, PyObject *args) { |
| uint64_t value = 0; |
| if (!PyArg_ParseTuple(args, "K", &value)) { |
| return nullptr; |
| } |
| if (value == 0) { |
| return nullptr; |
| } |
| return reinterpret_cast<PyObject *>(PyObject_New(InternalHandle, InternalHandleType)); |
| } |
| |
| PyObject *context_create(PyObject *self, PyObject *args) { |
| PyObject *config = nullptr; |
| const char *isolate = nullptr; |
| const char *target = nullptr; |
| if (!PyArg_ParseTuple(args, "Osz", &config, &isolate, &target)) { |
| return nullptr; |
| } |
| std::unique_ptr<ffx_config_t[]> ffx_config; |
| Py_ssize_t config_len = 0; |
| // Convert the borrowed reference into an shared reference to avoid double-free. |
| Py_INCREF(config); |
| if (!config || config == Py_None) { |
| Py_XDECREF(config); |
| config = PyDict_New(); |
| } |
| auto pair = build_config(config, target); |
| if (pair.first == nullptr) { |
| return nullptr; |
| } |
| ffx_config = std::move(pair.first); |
| config_len = pair.second; |
| ffx_env_context_t *env_context; |
| if (create_ffx_env_context(&env_context, mod::get_module_state()->ctx, ffx_config.get(), |
| config_len, isolate) != ZX_OK) { |
| mod::dump_python_err(); |
| Py_XDECREF(config); |
| return nullptr; |
| } |
| Py_XDECREF(config); |
| return MakePyObject<PythonContext>(env_context); |
| } |
| |
| PyObject *context_connect_daemon_protocol(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| const char *protocol = nullptr; |
| if (!PyArg_ParseTuple(args, "Os", &obj, &protocol)) { |
| return nullptr; |
| } |
| auto context = DowncastPyObject<PythonContext>(obj); |
| if (!context) { |
| return nullptr; |
| } |
| zx_handle_t handle; |
| zx_status_t status = ffx_connect_daemon_protocol(context->context(), protocol, &handle); |
| if (status != ZX_OK) { |
| mod::dump_python_err(); |
| return nullptr; |
| } |
| return MakePyObject<PythonChannel>(handle); |
| } |
| |
| PyObject *context_connect_target_proxy(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| if (!PyArg_ParseTuple(args, "O", &obj)) { |
| return nullptr; |
| } |
| auto context = DowncastPyObject<PythonContext>(obj); |
| if (!context) { |
| return nullptr; |
| } |
| zx_handle_t handle; |
| zx_status_t status = ffx_connect_target_proxy(context->context(), &handle); |
| if (status != ZX_OK) { |
| mod::dump_python_err(); |
| return nullptr; |
| } |
| return MakePyObject<PythonChannel>(handle); |
| } |
| |
| PyObject *context_connect_remote_control_proxy(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| if (!PyArg_ParseTuple(args, "O", &obj)) { |
| return nullptr; |
| } |
| auto context = DowncastPyObject<PythonContext>(obj); |
| if (!context) { |
| return nullptr; |
| } |
| zx_handle_t handle; |
| zx_status_t status = ffx_connect_remote_control_proxy(context->context(), &handle); |
| if (status != ZX_OK) { |
| mod::dump_python_err(); |
| return nullptr; |
| } |
| return MakePyObject<PythonChannel>(handle); |
| } |
| |
| PyObject *context_connect_device_proxy(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| const char *moniker = nullptr; |
| const char *capability = nullptr; |
| if (!PyArg_ParseTuple(args, "Oss", &obj, &moniker, &capability)) { |
| return nullptr; |
| } |
| auto context = DowncastPyObject<PythonContext>(obj); |
| if (!context) { |
| return nullptr; |
| } |
| zx_handle_t handle; |
| zx_status_t status = ffx_connect_device_proxy(context->context(), moniker, capability, &handle); |
| if (status != ZX_OK) { |
| mod::dump_python_err(); |
| return nullptr; |
| } |
| return MakePyObject<PythonChannel>(handle); |
| } |
| |
| PyObject *context_config_get_string(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| Py_ssize_t key_len; |
| const char *key = nullptr; |
| if (!PyArg_ParseTuple(args, "Oz#", &obj, &key, &key_len)) { |
| return nullptr; |
| } |
| auto context = DowncastPyObject<PythonContext>(obj); |
| if (!context) { |
| return nullptr; |
| } |
| uint64_t buf_size = 4096; |
| char buf[buf_size]; |
| if (zx_status_t res = ffx_config_get_string(context->context(), key, |
| static_cast<uint64_t>(key_len), buf, &buf_size); |
| res != ZX_OK) { |
| switch (res) { |
| case ZX_ERR_BUFFER_TOO_SMALL: |
| PyErr_SetString(PyExc_BufferError, "config key larger than 4096 characters"); |
| break; |
| case ZX_ERR_NOT_FOUND: |
| Py_RETURN_NONE; |
| default: |
| mod::dump_python_err(); |
| } |
| return nullptr; |
| } |
| return PyUnicode_FromStringAndSize(buf, static_cast<Py_ssize_t>(buf_size)); |
| } |
| |
| PyObject *connect_handle_notifier(PyObject *self, PyObject *Py_UNUSED(arg)) { |
| auto descriptor = ffx_connect_handle_notifier(mod::get_module_state()->ctx); |
| if (descriptor <= 0) { |
| mod::dump_python_err(); |
| return nullptr; |
| } |
| return PyLong_FromLong(descriptor); |
| } |
| |
| PyObject *context_target_add(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| const char *target = nullptr; |
| bool wait = false; |
| if (!PyArg_ParseTuple(args, "Osb", &obj, &target, &wait)) { |
| return nullptr; |
| } |
| auto context = DowncastPyObject<PythonContext>(obj); |
| if (!context) { |
| return nullptr; |
| } |
| zx_status_t status = ffx_target_add(context->context(), target, wait); |
| if (status != ZX_OK) { |
| switch (status) { |
| case ZX_ERR_INVALID_ARGS: |
| PyErr_Format(PyExc_ValueError, "invalid address passed as target: '%s'", target); |
| break; |
| default: |
| mod::dump_python_err(); |
| } |
| return nullptr; |
| } |
| Py_RETURN_NONE; |
| } |
| |
| PyObject *context_target_wait(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| double seconds = 0; |
| if (!PyArg_ParseTuple(args, "Od", &obj, &seconds)) { |
| return nullptr; |
| } |
| auto context = DowncastPyObject<PythonContext>(obj); |
| if (!context) { |
| return nullptr; |
| } |
| zx_status_t status = ffx_target_wait(context->context(), seconds); |
| if (status != ZX_OK) { |
| mod::dump_python_err(); |
| return nullptr; |
| } |
| Py_RETURN_NONE; |
| } |
| |
| PyObject *channel_write(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| PyObject *bytes = nullptr; |
| PyObject *handles = nullptr; |
| if (!PyArg_ParseTuple(args, "OOO", &obj, &bytes, &handles)) { |
| return nullptr; |
| } |
| auto channel = DowncastChannel(obj); |
| if (!channel) { |
| return nullptr; |
| } |
| constexpr size_t max_handle_count = 64; |
| zx_handle_disposition_t c_handles[max_handle_count]; |
| |
| Py_buffer view; |
| if (PyObject_GetBuffer(handles, &view, PyBUF_CONTIG_RO) < 0) { |
| PyErr_SetString(PyExc_TypeError, "Expected a buffer."); |
| return nullptr; |
| } |
| size_t handles_len = view.len / sizeof(zx_handle_disposition_t); |
| if (static_cast<size_t>(view.len) > sizeof(c_handles)) { |
| PyErr_SetString(PyExc_TypeError, "Only 64 handles are allowed in a FIDL message."); |
| PyBuffer_Release(&view); |
| return nullptr; |
| } |
| // We need the memcpy because Python doesn't guarantee that objects are aligned to 4 bytes. |
| // We could have UB on the Rust side if we don't properly align this to 4 bytes during the |
| // channel_write_etc call. |
| memcpy(c_handles, view.buf, view.len); |
| PyBuffer_Release(&view); |
| if (PyObject_GetBuffer(bytes, &view, PyBUF_CONTIG_RO) < 0) { |
| PyErr_SetString(PyExc_TypeError, "Expected a buffer."); |
| return nullptr; |
| } |
| zx_status_t status = |
| ffx_channel_write_etc(mod::get_module_state()->ctx, channel->channel(), |
| static_cast<const char *>(view.buf), view.len, c_handles, handles_len); |
| if (status != ZX_OK) { |
| PyErr_SetObject(reinterpret_cast<PyObject *>(error::ZxStatusType), PyLong_FromLong(status)); |
| PyBuffer_Release(&view); |
| return nullptr; |
| } |
| PyBuffer_Release(&view); |
| Py_RETURN_NONE; |
| } |
| |
| PyObject *channel_read(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| if (!PyArg_ParseTuple(args, "O", &obj)) { |
| return nullptr; |
| } |
| auto channel = DowncastChannel(obj); |
| if (!channel) { |
| return nullptr; |
| } |
| // This is the max FIDL message size; |
| static constexpr uint64_t c_buf_len = 65536; |
| static char c_buf[c_buf_len] = {}; |
| static constexpr uint64_t handles_len = 64; |
| static zx_handle_t handles[handles_len] = {}; |
| static uint64_t actual_bytes_count = 0; |
| static uint64_t actual_handles_count = 0; |
| |
| auto status = ffx_channel_read(mod::get_module_state()->ctx, channel->channel(), c_buf, c_buf_len, |
| handles, handles_len, &actual_bytes_count, &actual_handles_count); |
| if (status != ZX_OK) { |
| // Lint suppressed because we are asserting on the sizeof(long), |
| // not int64. |
| static_assert(sizeof(long) >= sizeof(status)); // NOLINT |
| PyErr_SetObject(reinterpret_cast<PyObject *>(error::ZxStatusType), PyLong_FromLong(status)); |
| return nullptr; |
| } |
| auto res = py::Object(PyTuple_New(2)); |
| if (res == nullptr) { |
| return nullptr; |
| } |
| auto buf = py::Object(PyByteArray_FromStringAndSize(const_cast<const char *>(c_buf), |
| static_cast<Py_ssize_t>(actual_bytes_count))); |
| if (buf == nullptr) { |
| return nullptr; |
| } |
| auto handles_list = py::Object(PyList_New(static_cast<Py_ssize_t>(actual_handles_count))); |
| if (handles_list == nullptr) { |
| return nullptr; |
| } |
| PyTuple_SetItem(res.get(), 0, buf.take()); |
| for (uint64_t i = 0; i < actual_handles_count; ++i) { |
| zx_handle_t handle = handles[i]; |
| auto handle_obj = MakePyObject<PythonHandle>(handle); |
| PyList_SetItem(handles_list.get(), static_cast<Py_ssize_t>(i), handle_obj); |
| } |
| PyTuple_SetItem(res.get(), 1, handles_list.take()); |
| return res.take(); |
| } |
| |
| PyObject *socket_write(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| PyObject *data = nullptr; |
| if (!PyArg_ParseTuple(args, "OO", &obj, &data)) { |
| return nullptr; |
| } |
| auto socket = DowncastSocket(obj); |
| if (!socket) { |
| return nullptr; |
| } |
| Py_buffer view; |
| if (PyObject_GetBuffer(data, &view, PyBUF_CONTIG_RO) < 0) { |
| PyErr_SetString(PyExc_TypeError, "Expected a buffer."); |
| return nullptr; |
| } |
| auto status = ffx_socket_write(mod::get_module_state()->ctx, socket->handle(), |
| static_cast<const char *>(view.buf), view.len); |
| PyBuffer_Release(&view); |
| if (status != ZX_OK) { |
| // Lint suppressed because we are asserting on the sizeof(long), |
| // not int64. |
| static_assert(sizeof(long) >= sizeof(status)); // NOLINT |
| PyErr_SetObject(reinterpret_cast<PyObject *>(error::ZxStatusType), PyLong_FromLong(status)); |
| return nullptr; |
| } |
| Py_RETURN_NONE; |
| } |
| |
| PyObject *socket_read(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| if (!PyArg_ParseTuple(args, "O", &obj)) { |
| return nullptr; |
| } |
| auto socket = DowncastSocket(obj); |
| if (!socket) { |
| return nullptr; |
| } |
| constexpr static uint64_t c_buf_len = 65536; |
| static char c_buf[c_buf_len] = {}; |
| uint64_t actual_bytes_count = 0; |
| auto status = ffx_socket_read(mod::get_module_state()->ctx, socket->handle(), c_buf, |
| sizeof(c_buf), &actual_bytes_count); |
| if (status != ZX_OK) { |
| // Lint suppressed because we are asserting on the sizeof(long), |
| // not int64. |
| static_assert(sizeof(long) >= sizeof(status)); // NOLINT |
| PyErr_SetObject(reinterpret_cast<PyObject *>(error::ZxStatusType), PyLong_FromLong(status)); |
| return nullptr; |
| } |
| return PyByteArray_FromStringAndSize(c_buf, static_cast<ssize_t>(actual_bytes_count)); |
| } |
| |
| PyObject *socket_as_int(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| if (!PyArg_ParseTuple(args, "O", &obj)) { |
| return nullptr; |
| } |
| auto socket = DowncastSocket(obj); |
| if (!socket) { |
| return nullptr; |
| } |
| return PyLong_FromUnsignedLongLong(socket->handle()); |
| } |
| |
| PyObject *socket_take(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| if (!PyArg_ParseTuple(args, "O", &obj)) { |
| return nullptr; |
| } |
| auto socket = DowncastSocket(obj); |
| if (!socket) { |
| return nullptr; |
| } |
| auto handle = socket->take(); |
| return PyLong_FromUnsignedLongLong(handle); |
| } |
| |
| template <typename... Args> |
| PyObject *MakePythonTuple(Args... args) { |
| PyObject *tuple = PyTuple_New(sizeof...(Args)); |
| if (!tuple) { |
| return nullptr; |
| } |
| PyObject *items[] = {args...}; |
| for (size_t i = 0; i < sizeof...(Args); i++) { |
| PyTuple_SetItem(tuple, i, items[i]); |
| } |
| return tuple; |
| } |
| |
| PyObject *socket_create(PyObject *self, PyObject *args) { |
| unsigned int options; |
| if (!PyArg_ParseTuple(args, "I", &options)) { |
| return nullptr; |
| } |
| zx_handle_t socket1 = ZX_HANDLE_INVALID; |
| zx_handle_t socket2 = ZX_HANDLE_INVALID; |
| auto status = ffx_socket_create(mod::get_module_state()->ctx, options, &socket1, &socket2); |
| if (status != ZX_OK) { |
| // Lint suppressed because we are asserting on the sizeof(long), |
| // not int64. |
| static_assert(sizeof(long) >= sizeof(status)); // NOLINT |
| PyErr_SetObject(reinterpret_cast<PyObject *>(error::ZxStatusType), PyLong_FromLong(status)); |
| return nullptr; |
| } |
| return MakePythonTuple(MakePyObject<PythonSocket>(socket1), MakePyObject<PythonSocket>(socket2)); |
| } |
| |
| PyObject *channel_as_int(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| if (!PyArg_ParseTuple(args, "O", &obj)) { |
| return nullptr; |
| } |
| auto channel = DowncastChannel(obj); |
| if (!channel) { |
| return nullptr; |
| } |
| return PyLong_FromUnsignedLongLong(channel->channel()); |
| } |
| |
| PyObject *channel_take(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| if (!PyArg_ParseTuple(args, "O", &obj)) { |
| return nullptr; |
| } |
| auto channel = DowncastChannel(obj); |
| if (!channel) { |
| return nullptr; |
| } |
| auto handle = channel->take(); |
| return PyLong_FromUnsignedLongLong(handle); |
| } |
| |
| PyObject *channel_create(PyObject *self, PyObject *args) { |
| zx_handle_t hdl0; |
| zx_handle_t hdl1; |
| ffx_channel_create(mod::get_module_state()->ctx, 0, &hdl0, &hdl1); |
| py::Object tuple(PyTuple_New(2)); |
| if (tuple == nullptr) { |
| return nullptr; |
| } |
| PyTuple_SetItem(tuple.get(), 0, MakePyObject<PythonChannel>(hdl0)); |
| PyTuple_SetItem(tuple.get(), 1, MakePyObject<PythonChannel>(hdl1)); |
| return tuple.take(); |
| } |
| |
| PyObject *channel_from_int(PyObject *self, PyObject *args) { |
| unsigned int handle = 0; |
| if (!PyArg_ParseTuple(args, "I", &handle)) { |
| return nullptr; |
| } |
| return MakePyObject<PythonChannel>(handle); |
| } |
| |
| PyObject *event_from_int(PyObject *self, PyObject *args) { |
| unsigned int handle = 0; |
| if (!PyArg_ParseTuple(args, "I", &handle)) { |
| return nullptr; |
| } |
| return MakePyObject<PythonEvent>(handle); |
| } |
| |
| PyObject *event_signal_peer(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| unsigned int clear_mask; |
| unsigned int set_mask; |
| if (!PyArg_ParseTuple(args, "OII", &obj, &clear_mask, &set_mask)) { |
| return nullptr; |
| } |
| auto event = DowncastPyObject<PythonEvent>(obj); |
| if (!event) { |
| return nullptr; |
| } |
| zx_status_t status = |
| ffx_object_signal_peer(mod::get_module_state()->ctx, event->handle(), clear_mask, set_mask); |
| if (status != ZX_OK) { |
| PyErr_SetObject(reinterpret_cast<PyObject *>(error::ZxStatusType), PyLong_FromLong(status)); |
| return nullptr; |
| } |
| Py_RETURN_NONE; |
| } |
| |
| PyObject *event_as_int(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| if (!PyArg_ParseTuple(args, "O", &obj)) { |
| return nullptr; |
| } |
| auto event = DowncastEvent(obj); |
| if (!event) { |
| return nullptr; |
| } |
| return PyLong_FromUnsignedLongLong(event->handle()); |
| } |
| |
| PyObject *event_take(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| if (!PyArg_ParseTuple(args, "O", &obj)) { |
| return nullptr; |
| } |
| auto event = DowncastEvent(obj); |
| if (!event) { |
| return nullptr; |
| } |
| auto handle = event->take(); |
| return PyLong_FromUnsignedLongLong(handle); |
| } |
| |
| PyObject *event_create(PyObject *self, PyObject *args) { |
| zx_handle_t hdl; |
| auto status = ffx_event_create(mod::get_module_state()->ctx, 0, &hdl); |
| if (status != ZX_OK) { |
| PyErr_SetObject(reinterpret_cast<PyObject *>(error::ZxStatusType), PyLong_FromLong(status)); |
| return nullptr; |
| } |
| return MakePyObject<PythonEvent>(hdl); |
| } |
| |
| PyObject *event_create_pair(PyObject *self, PyObject *args) { |
| zx_handle_t hdl0; |
| zx_handle_t hdl1; |
| auto status = ffx_eventpair_create(mod::get_module_state()->ctx, 0, &hdl0, &hdl1); |
| if (status != ZX_OK) { |
| PyErr_SetObject(reinterpret_cast<PyObject *>(error::ZxStatusType), PyLong_FromLong(status)); |
| return nullptr; |
| } |
| py::Object tuple(PyTuple_New(2)); |
| if (tuple == nullptr) { |
| return nullptr; |
| } |
| PyTuple_SetItem(tuple.get(), 0, MakePyObject<PythonEvent>(hdl0)); |
| PyTuple_SetItem(tuple.get(), 1, MakePyObject<PythonEvent>(hdl1)); |
| return tuple.take(); |
| } |
| |
| PyObject *socket_from_int(PyObject *self, PyObject *args) { |
| unsigned int handle = 0; |
| if (!PyArg_ParseTuple(args, "I", &handle)) { |
| return nullptr; |
| } |
| return MakePyObject<PythonSocket>(handle); |
| } |
| |
| PyObject *isolate_dir_create(PyObject *self, PyObject *args) { |
| const char *maybe_cstr = nullptr; |
| if (!PyArg_ParseTuple(args, "z", &maybe_cstr)) { |
| return nullptr; |
| } |
| auto maybe_directory = IsolateDir::Create(maybe_cstr); |
| // No need to set the error, as IsolateDir::Create does that for us based |
| // on the error returned from the C++ standard library. |
| if (!maybe_directory) { |
| return nullptr; |
| } |
| return maybe_directory->IntoPyObject(); |
| } |
| |
| PyObject *isolate_dir_get_path(PyObject *self, PyObject *args) { |
| PyObject *obj = nullptr; |
| if (!PyArg_ParseTuple(args, "O", &obj)) { |
| return nullptr; |
| } |
| auto directory = DowncastPyObject<IsolateDir>(obj); |
| if (!directory) { |
| return nullptr; |
| } |
| return PyUnicode_FromString(directory->directory().c_str()); |
| } |
| |
| PyType_Slot InternalHandleType_slots[] = { |
| {Py_tp_dealloc, reinterpret_cast<void *>(object_dealloc)}, |
| {Py_tp_doc, reinterpret_cast<void *>( |
| const_cast<char *>("Internal handle used by fuchsia controller for FFI."))}, |
| {0, nullptr}, |
| }; |
| |
| PyType_Spec InternalHandleType_Spec = { |
| .name = "fuchsia_controller_internal.InternalHandle", |
| .basicsize = sizeof(InternalHandle), |
| .itemsize = 0, |
| .slots = InternalHandleType_slots, |
| }; |
| |
| PyTypeObject *InternalHandleType = nullptr; |
| |
| PyMethodDef FuchsiaControllerMethods[] = { |
| // v2 methods for handle |
| {"handle_from_int", reinterpret_cast<PyCFunction>(handle_from_int), METH_VARARGS, nullptr}, |
| {"handle_as_int", reinterpret_cast<PyCFunction>(handle_as_int), METH_VARARGS, nullptr}, |
| {"handle_take", reinterpret_cast<PyCFunction>(handle_take), METH_VARARGS, nullptr}, |
| {"handle_create", reinterpret_cast<PyCFunction>(handle_create), METH_VARARGS, nullptr}, |
| |
| // v2 methods for Context |
| {"context_create", reinterpret_cast<PyCFunction>(context_create), METH_VARARGS, nullptr}, |
| {"context_connect_daemon_protocol", |
| reinterpret_cast<PyCFunction>(context_connect_daemon_protocol), METH_VARARGS, nullptr}, |
| {"context_connect_target_proxy", reinterpret_cast<PyCFunction>(context_connect_target_proxy), |
| METH_VARARGS, nullptr}, |
| {"context_connect_device_proxy", reinterpret_cast<PyCFunction>(context_connect_device_proxy), |
| METH_VARARGS, nullptr}, |
| {"context_connect_remote_control_proxy", |
| reinterpret_cast<PyCFunction>(context_connect_remote_control_proxy), METH_VARARGS, nullptr}, |
| {"context_target_add", reinterpret_cast<PyCFunction>(context_target_add), METH_VARARGS, |
| nullptr}, |
| {"context_target_wait", reinterpret_cast<PyCFunction>(context_target_wait), METH_VARARGS, |
| nullptr}, |
| {"context_config_get_string", reinterpret_cast<PyCFunction>(context_config_get_string), |
| METH_VARARGS, nullptr}, |
| |
| // v2 methods for channel |
| {"channel_read", reinterpret_cast<PyCFunction>(channel_read), METH_VARARGS, nullptr}, |
| {"channel_write", reinterpret_cast<PyCFunction>(channel_write), METH_VARARGS, nullptr}, |
| {"channel_as_int", reinterpret_cast<PyCFunction>(channel_as_int), METH_VARARGS, nullptr}, |
| {"channel_take", reinterpret_cast<PyCFunction>(channel_take), METH_VARARGS, nullptr}, |
| {"channel_create", reinterpret_cast<PyCFunction>(channel_create), METH_NOARGS, nullptr}, |
| {"channel_from_int", reinterpret_cast<PyCFunction>(channel_from_int), METH_VARARGS, nullptr}, |
| |
| // v2 methods for socket |
| {"socket_read", reinterpret_cast<PyCFunction>(socket_read), METH_VARARGS, nullptr}, |
| {"socket_write", reinterpret_cast<PyCFunction>(socket_write), METH_VARARGS, nullptr}, |
| {"socket_as_int", reinterpret_cast<PyCFunction>(socket_as_int), METH_VARARGS, nullptr}, |
| {"socket_take", reinterpret_cast<PyCFunction>(socket_take), METH_VARARGS, nullptr}, |
| {"socket_create", reinterpret_cast<PyCFunction>(socket_create), METH_VARARGS, nullptr}, |
| {"connect_handle_notifier", reinterpret_cast<PyCFunction>(connect_handle_notifier), |
| METH_VARARGS, nullptr}, |
| {"socket_from_int", reinterpret_cast<PyCFunction>(socket_from_int), METH_VARARGS, nullptr}, |
| |
| // event methods |
| {"event_from_int", reinterpret_cast<PyCFunction>(event_from_int), METH_VARARGS, nullptr}, |
| {"event_as_int", reinterpret_cast<PyCFunction>(event_as_int), METH_VARARGS, nullptr}, |
| {"event_take", reinterpret_cast<PyCFunction>(event_take), METH_VARARGS, nullptr}, |
| {"event_signal_peer", reinterpret_cast<PyCFunction>(event_signal_peer), METH_VARARGS, nullptr}, |
| {"event_create", reinterpret_cast<PyCFunction>(event_create), METH_NOARGS, nullptr}, |
| {"event_create_pair", reinterpret_cast<PyCFunction>(event_create_pair), METH_NOARGS, nullptr}, |
| |
| // v2 methods for IsolateDir (create, get name) |
| {"isolate_dir_create", reinterpret_cast<PyCFunction>(isolate_dir_create), METH_VARARGS, |
| nullptr}, |
| {"isolate_dir_get_path", reinterpret_cast<PyCFunction>(isolate_dir_get_path), METH_VARARGS, |
| nullptr}, |
| |
| SENTINEL, |
| }; |
| |
| int FuchsiaControllerModule_clear(PyObject *m) { |
| auto state = reinterpret_cast<mod::FuchsiaControllerState *>(PyModule_GetState(m)); |
| destroy_ffx_lib_context(state->ctx); |
| return 0; |
| } |
| |
| int InternalHandleTypeInit() { |
| return mod::GenericTypeInit(&InternalHandleType, &InternalHandleType_Spec); |
| } |
| |
| PyMODINIT_FUNC __attribute__((visibility("default"))) PyInit_fuchsia_controller_internal() { |
| if (InternalHandleTypeInit() < 0) { |
| return nullptr; |
| } |
| auto m = py::Object(PyModule_Create(&fuchsia_controller_internal)); |
| if (m == nullptr) { |
| return nullptr; |
| } |
| auto zx_status_type = error::ZxStatusType_Create(); |
| if (zx_status_type == nullptr) { |
| return nullptr; |
| } |
| auto state = reinterpret_cast<mod::FuchsiaControllerState *>(PyModule_GetState(m.get())); |
| create_ffx_lib_context(&state->ctx, state->ERR_SCRATCH, mod::ERR_SCRATCH_LEN); |
| if (PyModule_AddObject(m.get(), "ZxStatus", PyObjCast(zx_status_type)) < 0) { |
| Py_DECREF(zx_status_type); |
| return nullptr; |
| } |
| if (PyModule_AddObject(m.get(), "InternalHandle", PyObjCast(InternalHandleType)) < 0) { |
| return nullptr; |
| } |
| return m.take(); |
| } |
| |
| } // namespace |
| |
| DES_MIX struct PyModuleDef fuchsia_controller_internal = { |
| PyModuleDef_HEAD_INIT, |
| .m_name = "fuchsia_controller_internal", |
| .m_doc = nullptr, |
| .m_size = sizeof(mod::FuchsiaControllerState), |
| .m_methods = FuchsiaControllerMethods, |
| .m_clear = FuchsiaControllerModule_clear, |
| }; |