/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuIOChannel" #include "config.h" #include #include #include #include #ifdef HAVE_POLL_H #include #endif #include #include #include "fwupd-error.h" #include "fu-common.h" #include "fu-io-channel.h" /** * FuIOChannel: * * A bidirectional IO channel which can be read from and written to. */ struct _FuIOChannel { GObject parent_instance; gint fd; }; G_DEFINE_TYPE(FuIOChannel, fu_io_channel, G_TYPE_OBJECT) /** * fu_io_channel_unix_get_fd: * @self: a #FuIOChannel * * Gets the file descriptor for the device. * * Returns: fd, or -1 for not open. * * Since: 1.2.2 **/ gint fu_io_channel_unix_get_fd(FuIOChannel *self) { g_return_val_if_fail(FU_IS_IO_CHANNEL(self), -1); return self->fd; } /** * fu_io_channel_shutdown: * @self: a #FuIOChannel * @error: (nullable): optional return location for an error * * Closes the file descriptor for the device. * * Returns: %TRUE if all the FD was closed. * * Since: 1.2.2 **/ gboolean fu_io_channel_shutdown(FuIOChannel *self, GError **error) { g_return_val_if_fail(FU_IS_IO_CHANNEL(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!g_close(self->fd, error)) return FALSE; self->fd = -1; return TRUE; } static gboolean fu_io_channel_flush_input(FuIOChannel *self, GError **error) { GPollFD poll = { .fd = self->fd, .events = G_IO_IN | G_IO_ERR, }; while (g_poll(&poll, 1, 0) > 0) { gchar c; gint r = read(self->fd, &c, 1); if (r < 0 && errno != EINTR) break; } return TRUE; } /** * fu_io_channel_write_bytes: * @self: a #FuIOChannel * @bytes: buffer to write * @timeout_ms: timeout in ms * @flags: channel flags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT * @error: (nullable): optional return location for an error * * Writes bytes to the TTY, that will fail if exceeding @timeout_ms. * * Returns: %TRUE if all the bytes was written * * Since: 1.2.2 **/ gboolean fu_io_channel_write_bytes(FuIOChannel *self, GBytes *bytes, guint timeout_ms, FuIOChannelFlags flags, GError **error) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(bytes, &bufsz); return fu_io_channel_write_raw(self, buf, bufsz, timeout_ms, flags, error); } /** * fu_io_channel_write_byte_array: * @self: a #FuIOChannel * @buf: buffer to write * @timeout_ms: timeout in ms * @flags: channel flags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT * @error: (nullable): optional return location for an error * * Writes bytes to the TTY, that will fail if exceeding @timeout_ms. * * Returns: %TRUE if all the bytes was written * * Since: 1.3.2 **/ gboolean fu_io_channel_write_byte_array(FuIOChannel *self, GByteArray *buf, guint timeout_ms, FuIOChannelFlags flags, GError **error) { return fu_io_channel_write_raw(self, buf->data, buf->len, timeout_ms, flags, error); } /** * fu_io_channel_write_raw: * @self: a #FuIOChannel * @data: buffer to write * @datasz: size of @data * @timeout_ms: timeout in ms * @flags: channel flags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT * @error: (nullable): optional return location for an error * * Writes bytes to the TTY, that will fail if exceeding @timeout_ms. * * Returns: %TRUE if all the bytes was written * * Since: 1.2.2 **/ gboolean fu_io_channel_write_raw(FuIOChannel *self, const guint8 *data, gsize datasz, guint timeout_ms, FuIOChannelFlags flags, GError **error) { gsize idx = 0; g_return_val_if_fail(FU_IS_IO_CHANNEL(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* flush pending reads */ if (flags & FU_IO_CHANNEL_FLAG_FLUSH_INPUT) { if (!fu_io_channel_flush_input(self, error)) return FALSE; } /* blocking IO */ if (flags & FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO) { gssize wrote = write(self->fd, data, datasz); if (wrote != (gssize)datasz) { if (errno == EPROTO) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "failed to write: %s", strerror(errno)); return FALSE; } g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write: " "wrote %" G_GSSIZE_FORMAT " of %" G_GSIZE_FORMAT, wrote, datasz); return FALSE; } return TRUE; } /* nonblocking IO */ while (idx < datasz) { gint rc; GPollFD fds = { .fd = self->fd, .events = G_IO_OUT | G_IO_ERR, }; /* wait for data to be allowed to write without blocking */ rc = g_poll(&fds, 1, (gint)timeout_ms); if (rc == 0) break; if (rc < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to poll %i", self->fd); return FALSE; } /* we can write data */ if (fds.revents & G_IO_OUT) { gssize len = write(self->fd, data + idx, datasz - idx); if (len < 0) { if (errno == EAGAIN) { g_debug("got EAGAIN, trying harder"); continue; } if (errno == EPROTO) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "failed to write: %s", strerror(errno)); return FALSE; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to write %" G_GSIZE_FORMAT " bytes to %i: %s", datasz, self->fd, strerror(errno)); return FALSE; } if (flags & FU_IO_CHANNEL_FLAG_SINGLE_SHOT) break; idx += len; } } return TRUE; } /** * fu_io_channel_read_bytes: * @self: a #FuIOChannel * @max_size: maximum size of the returned blob, or -1 for no limit * @timeout_ms: timeout in ms * @flags: channel flags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT * @error: (nullable): optional return location for an error * * Reads bytes from the TTY, that will fail if exceeding @timeout_ms. * * Returns: a #GBytes, or %NULL for error * * Since: 1.2.2 **/ GBytes * fu_io_channel_read_bytes(FuIOChannel *self, gssize max_size, guint timeout_ms, FuIOChannelFlags flags, GError **error) { GByteArray *buf = fu_io_channel_read_byte_array(self, max_size, timeout_ms, flags, error); if (buf == NULL) return NULL; return g_byte_array_free_to_bytes(buf); } /** * fu_io_channel_read_byte_array: * @self: a #FuIOChannel * @max_size: maximum size of the returned blob, or -1 for no limit * @timeout_ms: timeout in ms * @flags: channel flags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT * @error: (nullable): optional return location for an error * * Reads bytes from the TTY, that will fail if exceeding @timeout_ms. * * Returns: (transfer full): a #GByteArray, or %NULL for error * * Since: 1.3.2 **/ GByteArray * fu_io_channel_read_byte_array(FuIOChannel *self, gssize max_size, guint timeout_ms, FuIOChannelFlags flags, GError **error) { GPollFD fds = { .fd = self->fd, .events = G_IO_IN | G_IO_PRI | G_IO_ERR, }; g_autoptr(GByteArray) buf2 = g_byte_array_new(); g_return_val_if_fail(FU_IS_IO_CHANNEL(self), NULL); /* blocking IO */ if (flags & FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO) { guint8 buf[1024]; gssize len = read(self->fd, buf, sizeof(buf)); if (len < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to read %i: %s", self->fd, strerror(errno)); return NULL; } if (len > 0) g_byte_array_append(buf2, buf, len); return g_steal_pointer(&buf2); } /* nonblocking IO */ while (TRUE) { /* wait for data to appear */ gint rc = g_poll(&fds, 1, (gint)timeout_ms); if (rc == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "timeout"); return NULL; } if (rc < 0) { if (errno == EINTR) continue; g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to poll %i", self->fd); return NULL; } /* we have data to read */ if (fds.revents & G_IO_IN) { guint8 buf[1024]; gssize len = read(self->fd, buf, sizeof(buf)); if (len < 0) { if (errno == EINTR) continue; if (errno == EAGAIN) continue; g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to read %i: %s", self->fd, strerror(errno)); return NULL; } if (len > 0) g_byte_array_append(buf2, buf, len); /* check maximum size */ if (max_size > 0 && buf2->len >= (guint)max_size) break; if (flags & FU_IO_CHANNEL_FLAG_SINGLE_SHOT) break; continue; } if (fds.revents & G_IO_ERR) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "error condition"); return NULL; } if (fds.revents & G_IO_HUP) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "connection hung up"); return NULL; } if (fds.revents & G_IO_NVAL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "invalid request"); return NULL; } } /* no data */ if (buf2->len == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "no data received from device in %ums", timeout_ms); return NULL; } /* return blob */ return g_steal_pointer(&buf2); } /** * fu_io_channel_read_raw: * @self: a #FuIOChannel * @buf: (nullable): optional buffer * @bufsz: size of @buf * @bytes_read: (out) (nullable): data written to @buf * @timeout_ms: timeout in ms * @flags: channel flags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT * @error: (nullable): optional return location for an error * * Reads bytes from the TTY, that will fail if exceeding @timeout_ms. * * Returns: a #GBytes, or %NULL for error * * Since: 1.2.2 **/ gboolean fu_io_channel_read_raw(FuIOChannel *self, guint8 *buf, gsize bufsz, gsize *bytes_read, guint timeout_ms, FuIOChannelFlags flags, GError **error) { const guint8 *tmpbuf = NULL; gsize bytes_read_tmp; g_autoptr(GBytes) tmp = NULL; g_return_val_if_fail(FU_IS_IO_CHANNEL(self), FALSE); tmp = fu_io_channel_read_bytes(self, bufsz, timeout_ms, flags, error); if (tmp == NULL) return FALSE; tmpbuf = g_bytes_get_data(tmp, &bytes_read_tmp); if (tmpbuf != NULL) memcpy(buf, tmpbuf, bytes_read_tmp); if (bytes_read != NULL) *bytes_read = bytes_read_tmp; return TRUE; } static void fu_io_channel_finalize(GObject *object) { FuIOChannel *self = FU_IO_CHANNEL(object); if (self->fd != -1) g_close(self->fd, NULL); G_OBJECT_CLASS(fu_io_channel_parent_class)->finalize(object); } static void fu_io_channel_class_init(FuIOChannelClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_io_channel_finalize; } static void fu_io_channel_init(FuIOChannel *self) { self->fd = -1; } /** * fu_io_channel_unix_new: * @fd: file descriptor * * Creates a new object to write and read from. * * Returns: a #FuIOChannel * * Since: 1.2.2 **/ FuIOChannel * fu_io_channel_unix_new(gint fd) { FuIOChannel *self; self = g_object_new(FU_TYPE_IO_CHANNEL, NULL); self->fd = fd; return FU_IO_CHANNEL(self); } /** * fu_io_channel_new_file: * @filename: device file * @error: (nullable): optional return location for an error * * Creates a new object to write and read from. * * Returns: a #FuIOChannel * * Since: 1.2.2 **/ FuIOChannel * fu_io_channel_new_file(const gchar *filename, GError **error) { #ifdef HAVE_POLL_H gint fd; g_return_val_if_fail(filename != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); fd = g_open(filename, O_RDWR | O_NONBLOCK, S_IRWXU); if (fd < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to open %s", filename); return NULL; } return fu_io_channel_unix_new(fd); #else g_return_val_if_fail(filename != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as is unavailable"); return NULL; #endif }