/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* qmi-firmware-update -- Command line tool to update firmware in QMI devices
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Copyright (C) 2016 Bjørn Mork
* Copyright (C) 2016 Zodiac Inflight Innovations
* Copyright (C) 2016-2017 Aleksander Morgado
*/
#include "qfu-image.h"
#include "qfu-enum-types.h"
static void initable_iface_init (GInitableIface *iface);
G_DEFINE_TYPE_EXTENDED (QfuImage, qfu_image, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))
enum {
PROP_0,
PROP_FILE,
PROP_IMAGE_TYPE,
PROP_INPUT_STREAM,
PROP_LAST
};
static GParamSpec *properties[PROP_LAST];
struct _QfuImagePrivate {
QfuImageType image_type;
GFile *file;
GFileInfo *info;
GInputStream *input_stream;
};
/******************************************************************************/
gsize
qfu_image_get_data_chunk_size (QfuImage *self,
guint16 chunk_i)
{
gssize chunk_size = QFU_IMAGE_CHUNK_SIZE;
guint n_chunks;
n_chunks = qfu_image_get_n_data_chunks (self);
if (chunk_i == (n_chunks - 1)) {
chunk_size = qfu_image_get_data_size (self) - (chunk_i * QFU_IMAGE_CHUNK_SIZE);
g_assert (chunk_size > 0);
} else
chunk_size = QFU_IMAGE_CHUNK_SIZE;
return chunk_size;
}
gssize
qfu_image_read_data_chunk (QfuImage *self,
guint16 chunk_i,
guint8 *out_buffer,
gsize out_buffer_size,
GCancellable *cancellable,
GError **error)
{
gssize chunk_size;
guint n_chunks;
goffset chunk_offset;
gssize n_read;
g_return_val_if_fail (QFU_IS_IMAGE (self), -1);
g_debug ("[qfu-image] reading chunk #%u", chunk_i);
n_chunks = qfu_image_get_n_data_chunks (self);
if (chunk_i >= n_chunks) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"invalid chunk index %u", chunk_i);
return -1;
}
/* Last chunk may be shorter than the default */
chunk_size = qfu_image_get_data_chunk_size (self, chunk_i);
g_debug ("[qfu-image] chunk #%u size: %" G_GSSIZE_FORMAT " bytes", chunk_i, chunk_size);
/* Make sure there's enough room */
if (out_buffer_size < chunk_size) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"buffer too small to fit chunk size: %" G_GSIZE_FORMAT, chunk_size);
return -1;
}
/* Compute chunk offset */
chunk_offset = qfu_image_get_header_size (self) + (chunk_i * QFU_IMAGE_CHUNK_SIZE);
g_debug ("[qfu-image] chunk #%u offset: %" G_GOFFSET_FORMAT " bytes", chunk_i, chunk_offset);
/* Seek to the correct place: note that this is likely a noop if already in that offset */
if (!g_seekable_seek (G_SEEKABLE (self->priv->input_stream), chunk_offset, G_SEEK_SET, cancellable, error)) {
g_prefix_error (error, "couldn't seek input stream: ");
return -1;
}
/* Read full chunk */
n_read = g_input_stream_read (self->priv->input_stream,
out_buffer,
chunk_size,
cancellable,
error);
if (n_read < 0) {
g_prefix_error (error, "couldn't read chunk %u", chunk_i);
return -1;
}
if (n_read != chunk_size) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"couldn't read expected chunk %u size: %" G_GSSIZE_FORMAT " (%" G_GSSIZE_FORMAT " bytes read)",
chunk_i, chunk_size, n_read);
return -1;
}
g_debug ("[qfu-image] chunk #%u successfully read", chunk_i);
return chunk_size;
}
/******************************************************************************/
QfuImageType
qfu_image_get_image_type (QfuImage *self)
{
g_return_val_if_fail (QFU_IS_IMAGE (self), QFU_IMAGE_TYPE_UNKNOWN);
return self->priv->image_type;
}
const gchar *
qfu_image_get_display_name (QfuImage *self)
{
g_return_val_if_fail (QFU_IS_IMAGE (self), NULL);
return g_file_info_get_display_name (self->priv->info);
}
goffset
qfu_image_get_size (QfuImage *self)
{
g_return_val_if_fail (QFU_IS_IMAGE (self), 0);
return g_file_info_get_size (self->priv->info);
}
goffset
qfu_image_get_header_size (QfuImage *self)
{
g_return_val_if_fail (QFU_IS_IMAGE (self), 0);
return QFU_IMAGE_GET_CLASS (self)->get_header_size (self);
}
gssize
qfu_image_read_header (QfuImage *self,
guint8 *out_buffer,
gsize out_buffer_size,
GCancellable *cancellable,
GError **error)
{
g_return_val_if_fail (QFU_IS_IMAGE (self), 0);
return (QFU_IMAGE_GET_CLASS (self)->read_header ?
QFU_IMAGE_GET_CLASS (self)->read_header (self, out_buffer, out_buffer_size, cancellable, error) :
0);
}
goffset
qfu_image_get_data_size (QfuImage *self)
{
g_return_val_if_fail (QFU_IS_IMAGE (self), 0);
return QFU_IMAGE_GET_CLASS (self)->get_data_size (self);
}
guint16
qfu_image_get_n_data_chunks (QfuImage *self)
{
goffset data_size;
data_size = qfu_image_get_data_size (self);
g_assert (data_size <= ((goffset) G_MAXUINT16 * (goffset) QFU_IMAGE_CHUNK_SIZE));
return (guint16) (data_size / QFU_IMAGE_CHUNK_SIZE) + !!(data_size % QFU_IMAGE_CHUNK_SIZE);
}
/******************************************************************************/
static goffset
get_header_size (QfuImage *self)
{
return 0;
}
static goffset
get_data_size (QfuImage *self)
{
goffset file_size;
file_size = qfu_image_get_size (self);
/* some image types contain trailing garbage - from gobi-loader */
if (self->priv->image_type == QFU_IMAGE_TYPE_AMSS_MODEM)
file_size -= 8;
return file_size;
}
/******************************************************************************/
static gboolean
initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
QfuImage *self;
self = QFU_IMAGE (initable);
g_assert (self->priv->file);
/* Load file info */
g_debug ("[qfu-image] loading file info...");
self->priv->info = g_file_query_info (self->priv->file,
G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," G_FILE_ATTRIBUTE_STANDARD_SIZE,
G_FILE_QUERY_INFO_NONE,
cancellable,
error);
if (!self->priv->info)
return FALSE;
/* Check minimum file size */
if (qfu_image_get_size (self) < qfu_image_get_header_size (self)) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "image is too short");
return FALSE;
}
/* Open file for reading. Kept open while the input stream reference is valid. */
g_debug ("[qfu-image] opening file for reading...");
self->priv->input_stream = G_INPUT_STREAM (g_file_read (self->priv->file, cancellable, error));
if (!self->priv->input_stream)
return FALSE;
return TRUE;
}
/******************************************************************************/
QfuImage *
qfu_image_new (GFile *file,
QfuImageType image_type,
GCancellable *cancellable,
GError **error)
{
g_return_val_if_fail (G_IS_FILE (file), NULL);
return QFU_IMAGE (g_initable_new (QFU_TYPE_IMAGE,
cancellable,
error,
"file", file,
"image-type", image_type,
NULL));
}
static void
qfu_image_init (QfuImage *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, QFU_TYPE_IMAGE, QfuImagePrivate);
self->priv->image_type = QFU_IMAGE_TYPE_UNKNOWN;
}
static void
set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
QfuImage *self = QFU_IMAGE (object);
switch (prop_id) {
case PROP_FILE:
self->priv->file = g_value_dup_object (value);
break;
case PROP_IMAGE_TYPE:
self->priv->image_type = g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
QfuImage *self = QFU_IMAGE (object);
switch (prop_id) {
case PROP_FILE:
g_value_set_object (value, self->priv->file);
break;
case PROP_IMAGE_TYPE:
g_value_set_enum (value, self->priv->image_type);
break;
case PROP_INPUT_STREAM:
g_value_set_object (value, self->priv->input_stream);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
dispose (GObject *object)
{
QfuImage *self = QFU_IMAGE (object);
g_clear_object (&self->priv->input_stream);
g_clear_object (&self->priv->info);
g_clear_object (&self->priv->file);
G_OBJECT_CLASS (qfu_image_parent_class)->dispose (object);
}
static void
initable_iface_init (GInitableIface *iface)
{
iface->init = initable_init;
}
static void
qfu_image_class_init (QfuImageClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (object_class, sizeof (QfuImagePrivate));
object_class->dispose = dispose;
object_class->get_property = get_property;
object_class->set_property = set_property;
klass->get_header_size = get_header_size;
klass->get_data_size = get_data_size;
properties[PROP_FILE] =
g_param_spec_object ("file",
"File",
"File object",
G_TYPE_FILE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_property (object_class, PROP_FILE, properties[PROP_FILE]);
properties[PROP_IMAGE_TYPE] =
g_param_spec_enum ("image-type",
"Image type",
"Type of firmware image",
QFU_TYPE_IMAGE_TYPE,
QFU_IMAGE_TYPE_UNKNOWN,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_property (object_class, PROP_IMAGE_TYPE, properties[PROP_IMAGE_TYPE]);
properties[PROP_INPUT_STREAM] =
g_param_spec_object ("input-stream",
"Input stream",
"Input stream object",
G_TYPE_FILE_INPUT_STREAM,
G_PARAM_READABLE);
g_object_class_install_property (object_class, PROP_INPUT_STREAM, properties[PROP_INPUT_STREAM]);
}