| // 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 "mod.h" |
| |
| #include <fidl/fuchsia.fdomain/cpp/fidl.h> |
| #include <fidl/fuchsia.fdomain/cpp/natural_ostream.h> |
| #include <lib/stdcompat/span.h> |
| |
| #include <sstream> |
| |
| #include <fuchsia_controller_abi/utils.h> |
| |
| #include "error.h" |
| |
| // Defined in fuchsia_controller_py.cc |
| extern struct PyModuleDef fuchsia_controller_internal; |
| |
| namespace fc = fuchsia_controller; |
| |
| namespace { |
| |
| // The purpose of this wrapper type is to allow for more descriptive error messages for zx_status_t |
| // types. Currently the formatter cannot differentiate between this type and a regular integer, so |
| // things like ZX_ERR_PEER_CLOSED would print as "-24" without this wrapper. So far the surface area |
| // that needs to be handled is relatively small. Only the `TargetError` variant of |
| // `fuchsia.fdomain.Error` needs to be handled, but in order to ensure all formatting works |
| // correctly, we also need to wrap other types in this formatter, deconstruct them, and wrap their |
| // inner errors when encountered. |
| // |
| // It does have a decent amount of boilerplate, but that's the price we're currently paying to have |
| // more clarity around error messages. |
| template <typename T> |
| struct DescriptiveFormatter { |
| const T &error; |
| explicit DescriptiveFormatter(const T &err) : error(err) {} |
| }; |
| } // namespace |
| |
| namespace fidl::ostream { |
| template <> |
| struct Formatter<DescriptiveFormatter<fuchsia_fdomain::Error>> { |
| static std::ostream &Format(std::ostream &os, |
| const DescriptiveFormatter<fuchsia_fdomain::Error> &f) { |
| auto err = f.error; |
| switch (err.Which()) { |
| case fuchsia_fdomain::Error::Tag::kTargetError: |
| os << "fuchsia_fdomain::Error::target_error(" |
| << error::zx_status_get_string(err.target_error().value()) << ")"; |
| break; |
| case fuchsia_fdomain::Error::Tag::kBadHandleId: |
| case fuchsia_fdomain::Error::Tag::kNewHandleIdOutOfRange: |
| case fuchsia_fdomain::Error::Tag::kNewHandleIdReused: |
| case fuchsia_fdomain::Error::Tag::kWrongHandleType: |
| case fuchsia_fdomain::Error::Tag::kStreamingReadInProgress: |
| case fuchsia_fdomain::Error::Tag::kNoReadInProgress: |
| case fuchsia_fdomain::Error::Tag::kWroteToSelf: |
| case fuchsia_fdomain::Error::Tag::kClosedDuringRead: |
| case fuchsia_fdomain::Error::Tag::kSignalsUnknown: |
| case fuchsia_fdomain::Error::Tag::kRightsUnknown: |
| case fuchsia_fdomain::Error::Tag::kSocketDispositionUnknown: |
| case fuchsia_fdomain::Error::Tag::kSocketTypeUnknown: |
| os << fidl::ostream::Formatted(err); |
| break; |
| default: |
| os << "unhandled variant. This is a bug: " << fidl::ostream::Formatted(err); |
| break; |
| } |
| return os; |
| } |
| }; |
| |
| template <> |
| struct Formatter<DescriptiveFormatter<fuchsia_fdomain::WriteChannelError>> { |
| static std::ostream &Format(std::ostream &os, |
| const DescriptiveFormatter<fuchsia_fdomain::WriteChannelError> &f) { |
| auto err = f.error; |
| os << "fuchsia_fdomain::WriteChannelError::"; |
| switch (err.Which()) { |
| case fuchsia_fdomain::WriteChannelError::Tag::kError: |
| os << "error(" << fidl::ostream::Formatted(DescriptiveFormatter(err.error().value())) |
| << ")"; |
| break; |
| case fuchsia_fdomain::WriteChannelError::Tag::kOpErrors: |
| os << "op_errors(["; |
| if (err.op_errors().has_value()) { |
| const auto &errors = err.op_errors().value(); |
| for (auto iter = errors.cbegin(); iter != errors.cend(); iter++) { |
| if (iter->has_value()) { |
| os << fidl::ostream::Formatted(DescriptiveFormatter(iter->value())); |
| } else { |
| os << "null"; |
| } |
| if (iter != errors.cend()) { |
| os << ", "; |
| } |
| } |
| } |
| os << "])"; |
| break; |
| } |
| return os; |
| } |
| }; |
| |
| template <> |
| struct Formatter<DescriptiveFormatter<fuchsia_fdomain::WriteSocketError>> { |
| static std::ostream &Format(std::ostream &os, |
| const DescriptiveFormatter<fuchsia_fdomain::WriteSocketError> &f) { |
| auto err = f.error; |
| os << "fuchsia_fdomain::WriteSocketError { error: " |
| << fidl::ostream::Formatted(DescriptiveFormatter(err.error())) |
| << ", wrote: " << fidl::ostream::Formatted(err.wrote()) << " }"; |
| return os; |
| } |
| }; |
| } // namespace fidl::ostream |
| |
| namespace { |
| // Takes a PyTuple and sets the decode error (if we encountered one). |
| template <typename T, typename V> |
| PyObject *get_decode_error(const fit::result<T, V> &decode_res) { |
| std::ostringstream ss; |
| ss << "Unable to decode underlying FIDL error from buffer (this is a bug). Code: " |
| << decode_res.error_value(); |
| auto str = ss.str(); |
| return PyUnicode_FromStringAndSize(str.data(), static_cast<Py_ssize_t>(str.size())); |
| } |
| |
| // Decodes a FIDL error from the scratch memory and turns it into a Python exception. |
| template <typename T> |
| PyObject *decode_wire_error_type(mod::FuchsiaControllerState *state, char *err_buf, |
| uint64_t err_len) { |
| fit::result decode_res = |
| fidl::Unpersist<T>(cpp20::span(reinterpret_cast<uint8_t *>(err_buf), err_len)); |
| if (decode_res.is_error()) { |
| return get_decode_error(decode_res); |
| } |
| // For the time being there's not any existing code that handles the various |
| // kinds of errors that this could turn into. We're going to turn it into a |
| // somewhat readable string, and depending on use-cases we can add |
| // easier-to-debug information later. Ideally in the future we can leverage |
| // Python bindings and simply use some kind of `unpersist` function for that |
| // instead. |
| DescriptiveFormatter descriptive_formatter(decode_res.value()); |
| auto fostream = fidl::ostream::Formatted(descriptive_formatter); |
| std::ostringstream ss; |
| ss << fostream; |
| auto output = ss.str(); |
| return PyUnicode_FromStringAndSize(output.data(), static_cast<Py_ssize_t>(output.size())); |
| } |
| |
| void set_fdomain_exception(mod::FuchsiaControllerState *state, fc_status_t err, char *err_buf, |
| uint64_t err_len) { |
| auto tuple = fc::abi::utils::Object(PyTuple_New(2)); |
| if (tuple == nullptr) { |
| std::ostringstream ss; |
| ss << "Failed to allocate Tuple in %s" << __func__; |
| auto out = ss.str(); |
| PyErr_SetString(PyExc_RuntimeError, out.c_str()); |
| return; |
| } |
| PyTuple_SetItem(tuple.get(), 0, PyLong_FromLong(err)); |
| PyObject *err_message = nullptr; |
| switch (err) { |
| case FC_ERR_SOCKET_WRITE: |
| err_message = |
| decode_wire_error_type<::fuchsia_fdomain::WriteSocketError>(state, err_buf, err_len); |
| break; |
| case FC_ERR_CHANNEL_WRITE: { |
| err_message = |
| decode_wire_error_type<::fuchsia_fdomain::WriteChannelError>(state, err_buf, err_len); |
| break; |
| } |
| case FC_ERR_FDOMAIN: { |
| err_message = decode_wire_error_type<::fuchsia_fdomain::Error>(state, err_buf, err_len); |
| break; |
| } |
| default: |
| std::ostringstream ss; |
| // It's a little awkward, but in the event that the caller just sent something wrong we |
| // should let the user know they've run into a bug. This would only happen if |
| // "set_python_exception" was written incorrectly. |
| ss << "Received unrecognized fc_status_t error (" << err << ") in " << __func__ |
| << ". This is a bug"; |
| auto out = ss.str(); |
| PyErr_SetString(PyExc_RuntimeError, out.c_str()); |
| break; |
| } |
| if (err_message != nullptr) { |
| PyTuple_SetItem(tuple.get(), 1, err_message); |
| PyErr_SetObject(reinterpret_cast<PyObject *>(error::FcTransportStatusType), tuple.get()); |
| } |
| } |
| } // namespace |
| |
| namespace mod { |
| FuchsiaControllerState *get_module_state() { |
| auto mod = PyState_FindModule(&fuchsia_controller_internal); |
| if (mod == nullptr) { |
| return nullptr; |
| } |
| return reinterpret_cast<FuchsiaControllerState *>(PyModule_GetState(mod)); |
| } |
| |
| // Returns new reference. |
| namespace { |
| |
| // Convenience RAII error state handler. |
| class LastError { |
| public: |
| explicit LastError(mod::FuchsiaControllerState *state) : state_(state) { |
| ffx_get_last_error(state_->ctx, &err_buf_, &err_len_, &err_type_); |
| } |
| |
| ~LastError() { |
| if (err_buf_ != nullptr) { |
| ffx_free_error_buffer(err_buf_, err_len_); |
| } |
| } |
| |
| char *err_buf() { return err_buf_; } |
| uint64_t err_len() const { return err_len_; } |
| fc_err_type_t err_type() const { return err_type_; } |
| |
| bool is_fidl() { return err_buf_ != nullptr && err_type_ == FC_ERR_TYPE_FIDL; } |
| bool is_string() { return err_buf_ != nullptr && err_type_ == FC_ERR_TYPE_STRING; } |
| |
| fc::abi::utils::Object to_string() { |
| if (!is_string()) { |
| PyErr_SetString(PyExc_RuntimeError, "Internal error: expected string error from buffer"); |
| return fc::abi::utils::Object::null(); |
| } |
| auto str_obj = PyUnicode_FromStringAndSize(err_buf_, static_cast<Py_ssize_t>(err_len_)); |
| // If this is null this will return an internal error about allocating the string. |
| if (str_obj == nullptr) { |
| PyErr_Clear(); |
| PyErr_SetString(PyExc_RuntimeError, |
| "Internal error: unable to allocate error string from buffer. This is a bug"); |
| return fc::abi::utils::Object::null(); |
| } |
| return fc::abi::utils::Object(str_obj); |
| } |
| |
| void set_fdomain_exception(fc_status_t err) { |
| if (!is_fidl()) { |
| PyErr_SetString(PyExc_RuntimeError, "Internal error: expected FIDL error from buffer"); |
| return; |
| } |
| ::set_fdomain_exception(state_, err, err_buf_, err_len_); |
| } |
| |
| private: |
| mod::FuchsiaControllerState *state_; |
| char *err_buf_ = nullptr; |
| uint64_t err_len_ = 0; |
| fc_err_type_t err_type_ = FC_ERR_TYPE_NONE; |
| }; |
| |
| } // namespace |
| |
| void set_python_exception(fc_status_t err) { |
| LastError last_err(get_module_state()); |
| switch (err) { |
| // If for some reason this is passed, some bug has been introduced, as there |
| // is no reason to return `FC_OK`. |
| case FC_OK: |
| PyErr_SetString(PyExc_RuntimeError, |
| "FC_OK was passed to raise an exception. This is likely a bug"); |
| break; |
| // None of these cases contain any internal structs, so setting the error |
| // code is sufficient. |
| case FC_ERR_INVALID_ARGS: |
| case FC_ERR_NOT_SUPPORTED: |
| case FC_ERR_NOT_FOUND: |
| case FC_ERR_BUFFER_TOO_SMALL: |
| case FC_ERR_SHOULD_WAIT: |
| case FC_ERR_PROTOCOL_OBJECT_TYPE_INCOMPATIBLE: |
| case FC_ERR_PROTOCOL_RIGHTS_INCOMPATIBLE: |
| case FC_ERR_PROTOCOL_STREAM_EVENT_INCOMPATIBLE: |
| case FC_ERR_CONNECTION_MISMATCH: |
| case FC_ERR_STREAMING_ABORTED: { |
| auto exception_err = fc::abi::utils::Object(PyLong_FromLong(err)); |
| PyErr_SetObject(reinterpret_cast<PyObject *>(error::FcTransportStatusType), exception_err.get()); |
| break; |
| } |
| // These errors are only surfaced from internal failures, which means the |
| // Rust error that created this exception is surfaced. |
| case FC_ERR_PROTOCOL: |
| case FC_ERR_TRANSPORT: |
| case FC_ERR_INTERNAL: { |
| auto str_obj = last_err.to_string(); |
| if (str_obj == nullptr) { |
| return; |
| } |
| switch (err) { |
| case FC_ERR_TRANSPORT: |
| case FC_ERR_PROTOCOL: { |
| auto tuple = fc::abi::utils::Object(PyTuple_New(2)); |
| if (tuple == nullptr) { |
| std::ostringstream ss; |
| ss << "Failed to allocate Tuple in %s" << __func__; |
| auto out = ss.str(); |
| PyErr_SetString(PyExc_RuntimeError, out.c_str()); |
| break; |
| } |
| PyTuple_SetItem(tuple.get(), 0, PyLong_FromLong(err)); |
| // Important: use `take` here because this steals the reference. |
| // PyErr_SetObject increases the refcount so only requires `get`. |
| PyTuple_SetItem(tuple.get(), 1, str_obj.take()); |
| PyErr_SetObject(reinterpret_cast<PyObject *>(error::FcTransportStatusType), tuple.get()); |
| break; |
| } |
| default: |
| PyErr_SetObject(PyExc_RuntimeError, str_obj.get()); |
| break; |
| } |
| break; |
| } |
| case FC_ERR_SOCKET_WRITE: |
| case FC_ERR_CHANNEL_WRITE: |
| case FC_ERR_FDOMAIN: { |
| last_err.set_fdomain_exception(err); |
| break; |
| } |
| case FC_ERR_INTERRUPTED: { |
| PyErr_SetNone(PyExc_KeyboardInterrupt); |
| break; |
| } |
| default: { |
| std::ostringstream ss; |
| ss << "Received unrecognized fc_status_t (" << err << ")"; |
| auto out = ss.str(); |
| PyErr_SetString(PyExc_RuntimeError, out.c_str()); |
| break; |
| } |
| } |
| } |
| |
| } // namespace mod |