| // 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 <Python.h> | |
| #include <filesystem> | |
| #include <type_traits> | |
| #include <vector> | |
| #include <fuchsia_controller_abi/utils.h> | |
| #include "error.h" | |
| #include "fuchsia_controller.h" | |
| #include "macros.h" | |
| #include "mod.h" | |
| extern struct PyModuleDef fuchsia_controller_internal; | |
| namespace { | |
| namespace fc = fuchsia_controller; | |
| 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) { | |
| fc::abi::utils::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); | |
| } | |
| /// 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() != 0) { | |
| auto state = mod::get_module_state(); | |
| if (state == nullptr) { | |
| return; | |
| } | |
| fc_status_t status; | |
| Py_BEGIN_ALLOW_THREADS; | |
| status = ffx_close_handle(state->ctx, handle_); | |
| Py_END_ALLOW_THREADS; | |
| if (status != FC_OK) { | |
| mod::set_python_exception(status); | |
| return; | |
| } | |
| handle_ = 0; | |
| } | |
| } | |
| 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 handle() const { return channel_; } | |
| uint64_t take() { | |
| auto ret = channel_; | |
| channel_ = 0; | |
| return ret; | |
| } | |
| ~PythonChannel() override { | |
| if (handle() != 0) { | |
| auto state = mod::get_module_state(); | |
| if (state == nullptr) { | |
| return; | |
| } | |
| fc_status_t status; | |
| Py_BEGIN_ALLOW_THREADS; | |
| status = ffx_close_handle(state->ctx, channel_); | |
| Py_END_ALLOW_THREADS; | |
| if (status != FC_OK) { | |
| mod::set_python_exception(status); | |
| return; | |
| } | |
| 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() override { | |
| if (handle() != 0) { | |
| auto state = mod::get_module_state(); | |
| if (state == nullptr) { | |
| return; | |
| } | |
| fc_status_t status; | |
| Py_BEGIN_ALLOW_THREADS; | |
| status = ffx_close_handle(state->ctx, handle_); | |
| Py_END_ALLOW_THREADS; | |
| if (status != FC_OK) { | |
| mod::set_python_exception(status); | |
| return; | |
| } | |
| 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) { | |
| auto state = mod::get_module_state(); | |
| if (state == nullptr) { | |
| return; | |
| } | |
| fc_status_t status; | |
| Py_BEGIN_ALLOW_THREADS; | |
| status = ffx_close_handle(state->ctx, handle()); | |
| Py_END_ALLOW_THREADS; | |
| if (status != FC_OK) { | |
| mod::set_python_exception(status); | |
| return; | |
| } | |
| 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 trivially copyable. | |
| static_assert(std::is_trivially_copyable_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()); | |
| } | |
| template <typename T> | |
| PyObject *handle_koid_helper(PyObject *self, PyObject *args) { | |
| PyObject *object; | |
| if (!PyArg_ParseTuple(args, "O", &object)) { | |
| return nullptr; | |
| } | |
| // Get C++ object from Python object | |
| auto handle = DowncastPyObject<T>(object); | |
| if (handle == nullptr) { | |
| return nullptr; | |
| } | |
| zx_koid_t out; | |
| zx_status_t status; | |
| auto ctx = mod::get_module_state()->ctx; | |
| auto koid_handle = handle->handle(); | |
| Py_BEGIN_ALLOW_THREADS; | |
| status = ffx_handle_get_koid(ctx, koid_handle, &out); | |
| Py_END_ALLOW_THREADS; | |
| if (status != FC_OK) { | |
| mod::set_python_exception(status); | |
| return nullptr; | |
| } | |
| return PyLong_FromUnsignedLongLong(out); | |
| } | |
| 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) { | |
| const char *config_json = nullptr; | |
| const char *isolate = nullptr; | |
| if (!PyArg_ParseTuple(args, "zs", &config_json, &isolate)) { | |
| return nullptr; | |
| } | |
| ffx_env_context_t *env_context; | |
| fc_status_t status = | |
| create_ffx_env_context(&env_context, mod::get_module_state()->ctx, config_json, isolate); | |
| if (status != FC_OK) { | |
| mod::set_python_exception(status); | |
| return nullptr; | |
| } | |
| return MakePyObject<PythonContext>(env_context); | |
| } | |
| 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; | |
| auto ctx = context->context(); | |
| Py_BEGIN_ALLOW_THREADS; | |
| status = ffx_connect_remote_control_proxy(ctx, &handle); | |
| Py_END_ALLOW_THREADS; | |
| if (status != FC_OK) { | |
| mod::set_python_exception(status); | |
| 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; | |
| auto ctx = context->context(); | |
| Py_BEGIN_ALLOW_THREADS; | |
| status = ffx_connect_device_proxy(ctx, moniker, capability, &handle); | |
| Py_END_ALLOW_THREADS; | |
| if (status != FC_OK) { | |
| mod::set_python_exception(status); | |
| 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; | |
| } | |
| const uint64_t kMaxBufSize = 4096; | |
| uint64_t buf_size = kMaxBufSize; | |
| char buf[kMaxBufSize]; | |
| if (zx_status_t res = ffx_config_get_string(context->context(), key, | |
| static_cast<uint64_t>(key_len), buf, &buf_size); | |
| res != FC_OK) { | |
| switch (res) { | |
| case FC_ERR_BUFFER_TOO_SMALL: | |
| PyErr_Format(PyExc_BufferError, "config key larger than %" PRIu64 " characters", | |
| kMaxBufSize); | |
| break; | |
| case FC_ERR_NOT_FOUND: | |
| Py_RETURN_NONE; | |
| default: | |
| mod::set_python_exception(res); | |
| } | |
| 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::set_python_exception(FC_ERR_INTERNAL); | |
| return nullptr; | |
| } | |
| return PyLong_FromLong(descriptor); | |
| } | |
| PyObject *context_target_wait(PyObject *self, PyObject *args) { | |
| PyObject *obj = nullptr; | |
| uint64_t timeout_seconds = 0; | |
| bool offline = false; | |
| // The python docs state "K" converts to "unsigned long long" so static assert they're matching | |
| // the expected ABI. | |
| // | |
| // https://docs.python.org/3/c-api/arg.html#numbers | |
| // | |
| // TODO(https://fxbug.dev/346628306): Consider moving this assertion, and possibly others like it, | |
| // to the top of this file. There may also be another approach that uses types like | |
| // ctypes.c_uint64. | |
| static_assert(sizeof(uint64_t) == sizeof(unsigned long long)); // NOLINT | |
| if (!PyArg_ParseTuple(args, "OKb", &obj, &timeout_seconds, &offline)) { | |
| return nullptr; | |
| } | |
| auto context = DowncastPyObject<PythonContext>(obj); | |
| if (!context) { | |
| return nullptr; | |
| } | |
| zx_status_t status; | |
| auto ctx = context->context(); | |
| Py_BEGIN_ALLOW_THREADS; | |
| status = ffx_target_wait(ctx, timeout_seconds, offline); | |
| Py_END_ALLOW_THREADS; | |
| if (status != FC_OK) { | |
| mod::set_python_exception(status); | |
| return nullptr; | |
| } | |
| Py_RETURN_NONE; | |
| } | |
| PyObject *channel_write(PyObject *self, PyObject *args) { | |
| PyObject *obj = nullptr; | |
| PyObject *buffer = nullptr; | |
| PyObject *handles = nullptr; | |
| if (!PyArg_ParseTuple(args, "OOO", &obj, &buffer, &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(buffer, &view, PyBUF_CONTIG_RO) < 0) { | |
| PyErr_SetString(PyExc_TypeError, "Expected a buffer."); | |
| return nullptr; | |
| } | |
| zx_status_t status; | |
| auto buf = static_cast<const char *>(view.buf); | |
| auto buf_len = view.len; | |
| auto ctx = mod::get_module_state()->ctx; | |
| auto ch = channel->handle(); | |
| Py_BEGIN_ALLOW_THREADS; | |
| status = ffx_channel_write_etc(ctx, ch, buf, buf_len, c_handles, handles_len); | |
| Py_END_ALLOW_THREADS; | |
| if (status != FC_OK) { | |
| PyBuffer_Release(&view); | |
| mod::set_python_exception(status); | |
| 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; | |
| char c_buf[c_buf_len]; | |
| static constexpr uint64_t handles_len = 64; | |
| zx_handle_t handles[handles_len]; | |
| uint64_t actual_bytes_count = 0; | |
| uint64_t actual_handles_count = 0; | |
| fc_status_t status; | |
| auto ctx = mod::get_module_state()->ctx; | |
| auto ch = channel->handle(); | |
| // This does not release the GIL as this does not block. See src/commands.rs | |
| // and src/fdomain.rs for more details. The reason this does not block is: | |
| // | |
| // -- We do not attempt to take ahold of fdomain_client (from the env context) | |
| // -- We only ever poll on the rust Future<> for the channel reads, so the | |
| // only time we return is when the read is ready to receive data. | |
| // | |
| // For any cases where we could potentially block, FC_ERR_SHOULD_WAIT is | |
| // returned and we register a waker that will notify the main Python thread | |
| // via a unix pipe that there is an error or data available on this channel. | |
| // | |
| // All this also applies to socket reads. | |
| // | |
| // Currently, this function, socket reading, and signal polling are the main | |
| // async functions implemented. | |
| status = ffx_channel_read(ctx, ch, c_buf, c_buf_len, handles, handles_len, &actual_bytes_count, | |
| &actual_handles_count); | |
| if (status != FC_OK) { | |
| mod::set_python_exception(status); | |
| return nullptr; | |
| } | |
| auto res = fc::abi::utils::Object(PyTuple_New(2)); | |
| if (res == nullptr) { | |
| return nullptr; | |
| } | |
| auto buf = fc::abi::utils::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 = | |
| fc::abi::utils::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 *buffer = nullptr; | |
| if (!PyArg_ParseTuple(args, "OO", &obj, &buffer)) { | |
| return nullptr; | |
| } | |
| auto socket = DowncastSocket(obj); | |
| if (!socket) { | |
| return nullptr; | |
| } | |
| Py_buffer view; | |
| if (PyObject_GetBuffer(buffer, &view, PyBUF_CONTIG_RO) < 0) { | |
| PyErr_SetString(PyExc_TypeError, "Expected a buffer."); | |
| return nullptr; | |
| } | |
| fc_status_t status; | |
| auto ctx = mod::get_module_state()->ctx; | |
| auto sock = socket->handle(); | |
| auto buf = static_cast<const char *>(view.buf); | |
| auto buf_len = view.len; | |
| Py_BEGIN_ALLOW_THREADS; | |
| status = ffx_socket_write(ctx, sock, buf, buf_len); | |
| Py_END_ALLOW_THREADS; | |
| PyBuffer_Release(&view); | |
| if (status != FC_OK) { | |
| mod::set_python_exception(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; | |
| char c_buf[c_buf_len]; | |
| uint64_t actual_bytes_count = 0; | |
| fc_status_t status; | |
| auto ctx = mod::get_module_state()->ctx; | |
| auto sock = socket->handle(); | |
| // This does not block: see the usage of `ffx_channel_read` for comments | |
| // explaining why. | |
| status = ffx_socket_read(ctx, sock, c_buf, sizeof(c_buf), &actual_bytes_count); | |
| if (status != FC_OK) { | |
| mod::set_python_exception(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; | |
| PyObject *obj = nullptr; | |
| if (!PyArg_ParseTuple(args, "OI", &obj, &options)) { | |
| return nullptr; | |
| } | |
| zx_handle_t socket1 = ZX_HANDLE_INVALID; | |
| zx_handle_t socket2 = ZX_HANDLE_INVALID; | |
| auto context = DowncastPyObject<PythonContext>(obj); | |
| if (!context) { | |
| return nullptr; | |
| } | |
| fc_status_t status; | |
| auto ctx = context->context(); | |
| Py_BEGIN_ALLOW_THREADS; | |
| status = ffx_socket_create(ctx, options, &socket1, &socket2); | |
| Py_END_ALLOW_THREADS; | |
| if (status != FC_OK) { | |
| mod::set_python_exception(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->handle()); | |
| } | |
| 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; | |
| PyObject *obj = nullptr; | |
| if (!PyArg_ParseTuple(args, "O", &obj)) { | |
| return nullptr; | |
| } | |
| auto context = DowncastPyObject<PythonContext>(obj); | |
| if (context == nullptr) { | |
| return nullptr; | |
| } | |
| fc_status_t status; | |
| auto ctx = context->context(); | |
| Py_BEGIN_ALLOW_THREADS; | |
| status = ffx_channel_create(ctx, 0, &hdl0, &hdl1); | |
| Py_END_ALLOW_THREADS; | |
| if (status != FC_OK) { | |
| mod::set_python_exception(status); | |
| return nullptr; | |
| } | |
| fc::abi::utils::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; | |
| auto ctx = mod::get_module_state()->ctx; | |
| Py_BEGIN_ALLOW_THREADS; | |
| status = ffx_object_signal_peer(ctx, event->handle(), clear_mask, set_mask); | |
| Py_END_ALLOW_THREADS; | |
| if (status != FC_OK) { | |
| mod::set_python_exception(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; | |
| PyObject *obj = nullptr; | |
| if (!PyArg_ParseTuple(args, "O", &obj)) { | |
| return nullptr; | |
| } | |
| auto context = DowncastPyObject<PythonContext>(obj); | |
| if (context == nullptr) { | |
| return nullptr; | |
| } | |
| fc_status_t status; | |
| auto ctx = context->context(); | |
| Py_BEGIN_ALLOW_THREADS; | |
| status = ffx_event_create(ctx, 0, &hdl); | |
| Py_END_ALLOW_THREADS; | |
| if (status != FC_OK) { | |
| mod::set_python_exception(status); | |
| return nullptr; | |
| } | |
| return MakePyObject<PythonEvent>(hdl); | |
| } | |
| PyObject *event_create_pair(PyObject *self, PyObject *args) { | |
| zx_handle_t hdl0; | |
| zx_handle_t hdl1; | |
| PyObject *obj = nullptr; | |
| if (!PyArg_ParseTuple(args, "O", &obj)) { | |
| return nullptr; | |
| } | |
| auto context = DowncastPyObject<PythonContext>(obj); | |
| if (context == nullptr) { | |
| return nullptr; | |
| } | |
| fc_status_t status; | |
| auto ctx = context->context(); | |
| Py_BEGIN_ALLOW_THREADS; | |
| status = ffx_eventpair_create(ctx, 0, &hdl0, &hdl1); | |
| Py_END_ALLOW_THREADS; | |
| if (status != FC_OK) { | |
| mod::set_python_exception(status); | |
| return nullptr; | |
| } | |
| fc::abi::utils::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 *block_forever_test_function(PyObject *self, PyObject *args) { | |
| auto state = mod::get_module_state(); | |
| if (state == nullptr) { | |
| return nullptr; | |
| } | |
| fc_status_t status; | |
| Py_BEGIN_ALLOW_THREADS; | |
| status = _block_forever(state->ctx); | |
| Py_END_ALLOW_THREADS; | |
| if (status != FC_OK) { | |
| mod::set_python_exception(status); | |
| return nullptr; | |
| } | |
| Py_RETURN_NONE; | |
| } | |
| 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}, | |
| {"handle_koid", reinterpret_cast<PyCFunction>(handle_koid_helper<PythonHandle>), METH_VARARGS, | |
| nullptr}, | |
| // v2 methods for Context | |
| {"context_create", reinterpret_cast<PyCFunction>(context_create), 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_wait", reinterpret_cast<PyCFunction>(context_target_wait), METH_VARARGS, | |
| nullptr}, | |
| {"context_config_get_string", reinterpret_cast<PyCFunction>(context_config_get_string), | |
| METH_VARARGS, nullptr}, | |
| {"channel_create", reinterpret_cast<PyCFunction>(channel_create), METH_VARARGS, nullptr}, | |
| {"socket_create", reinterpret_cast<PyCFunction>(socket_create), METH_VARARGS, nullptr}, | |
| {"event_create", reinterpret_cast<PyCFunction>(event_create), METH_VARARGS, nullptr}, | |
| {"event_create_pair", reinterpret_cast<PyCFunction>(event_create_pair), 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_from_int", reinterpret_cast<PyCFunction>(channel_from_int), METH_VARARGS, nullptr}, | |
| {"channel_koid", reinterpret_cast<PyCFunction>(handle_koid_helper<PythonChannel>), 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}, | |
| {"connect_handle_notifier", reinterpret_cast<PyCFunction>(connect_handle_notifier), | |
| METH_VARARGS, nullptr}, | |
| {"socket_from_int", reinterpret_cast<PyCFunction>(socket_from_int), METH_VARARGS, nullptr}, | |
| {"socket_koid", reinterpret_cast<PyCFunction>(handle_koid_helper<PythonSocket>), 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_koid", reinterpret_cast<PyCFunction>(handle_koid_helper<PythonEvent>), METH_VARARGS, | |
| 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}, | |
| // Testing. | |
| {"_block_forever", reinterpret_cast<PyCFunction>(block_forever_test_function), METH_NOARGS, | |
| 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 PyInit_libfuchsia_controller_internal() { | |
| if (InternalHandleTypeInit() < 0) { | |
| return nullptr; | |
| } | |
| auto m = fc::abi::utils::Object(PyModule_Create(&fuchsia_controller_internal)); | |
| if (m == nullptr) { | |
| return nullptr; | |
| } | |
| auto fc_status_type = error::FcTransportStatusType_Create(); | |
| if (fc_status_type == nullptr) { | |
| return nullptr; | |
| } | |
| if (PyModule_AddObject(m.get(), "FcTransportStatus", | |
| reinterpret_cast<PyObject *>(fc_status_type)) < 0) { | |
| Py_DECREF(fc_status_type); | |
| 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); | |
| if (PyModule_AddObject(m.get(), "ZxStatus", reinterpret_cast<PyObject *>(zx_status_type)) < 0) { | |
| Py_DECREF(zx_status_type); | |
| return nullptr; | |
| } | |
| if (PyModule_AddObject(m.get(), "InternalHandle", | |
| reinterpret_cast<PyObject *>(InternalHandleType)) < 0) { | |
| return nullptr; | |
| } | |
| return m.take(); | |
| } | |
| } // namespace | |
| struct PyModuleDef fuchsia_controller_internal = { | |
| .m_base = PyModuleDef_HEAD_INIT, | |
| .m_name = "fuchsia_controller_internal", | |
| .m_doc = nullptr, | |
| .m_size = sizeof(mod::FuchsiaControllerState), | |
| .m_methods = FuchsiaControllerMethods, | |
| .m_clear = FuchsiaControllerModule_clear, | |
| }; |