Coverage Report

Created: 2026-01-16 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-partial-input-stream.c
Line
Count
Source
1
/*
2
 * Copyright 2023 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
4.91k
#define G_LOG_DOMAIN "FuPartialInputStream"
8
9
#include "config.h"
10
11
#include "fwupd-codec.h"
12
13
#include "fu-common.h"
14
#include "fu-input-stream.h"
15
#include "fu-partial-input-stream-private.h"
16
17
/**
18
 * FuPartialInputStream:
19
 *
20
 * A input stream that is a slice of another input stream.
21
 *
22
 *       off    sz
23
 *    [xxxxxxxxxxxx]
24
 *       |  0x6  |
25
 *        \      \
26
 *         \      \
27
 *          \      |
28
 *           |     |
29
 *          [xxxxxx]
30
 *
31
 * xxx offset: 2, sz: 6
32
 */
33
34
struct _FuPartialInputStream {
35
  GInputStream parent_instance;
36
  GInputStream *base_stream;
37
  gsize offset;
38
  gsize size;
39
};
40
41
static void
42
fu_partial_input_stream_seekable_iface_init(GSeekableIface *iface);
43
static void
44
fu_partial_input_stream_codec_iface_init(FwupdCodecInterface *iface);
45
46
88.1M
G_DEFINE_TYPE_WITH_CODE(FuPartialInputStream,
47
88.1M
      fu_partial_input_stream,
48
88.1M
      G_TYPE_INPUT_STREAM,
49
88.1M
      G_IMPLEMENT_INTERFACE(G_TYPE_SEEKABLE,
50
88.1M
                fu_partial_input_stream_seekable_iface_init)
51
88.1M
          G_IMPLEMENT_INTERFACE(FWUPD_TYPE_CODEC,
52
88.1M
              fu_partial_input_stream_codec_iface_init))
53
88.1M
54
88.1M
static void
55
88.1M
fu_partial_input_stream_add_string(FwupdCodec *codec, guint idt, GString *str)
56
88.1M
{
57
0
  FuPartialInputStream *self = FU_PARTIAL_INPUT_STREAM(codec);
58
0
  fwupd_codec_string_append_hex(str, idt, "Offset", self->offset);
59
0
  fwupd_codec_string_append_hex(str, idt, "Size", self->size);
60
0
}
61
62
static void
63
fu_partial_input_stream_codec_iface_init(FwupdCodecInterface *iface)
64
29
{
65
29
  iface->add_string = fu_partial_input_stream_add_string;
66
29
}
67
68
static goffset
69
fu_partial_input_stream_tell(GSeekable *seekable)
70
43.6M
{
71
43.6M
  FuPartialInputStream *self = FU_PARTIAL_INPUT_STREAM(seekable);
72
43.6M
  return g_seekable_tell(G_SEEKABLE(self->base_stream)) - self->offset;
73
43.6M
}
74
75
static gboolean
76
fu_partial_input_stream_can_seek(GSeekable *seekable)
77
6.91M
{
78
6.91M
  FuPartialInputStream *self = FU_PARTIAL_INPUT_STREAM(seekable);
79
6.91M
  return g_seekable_can_seek(G_SEEKABLE(self->base_stream));
80
6.91M
}
81
82
static gboolean
83
fu_partial_input_stream_seek(GSeekable *seekable,
84
           goffset offset,
85
           GSeekType type,
86
           GCancellable *cancellable,
87
           GError **error)
88
6.91M
{
89
6.91M
  FuPartialInputStream *self = FU_PARTIAL_INPUT_STREAM(seekable);
90
91
6.91M
  g_return_val_if_fail(FU_IS_PARTIAL_INPUT_STREAM(self), FALSE);
92
6.91M
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
93
94
6.91M
  if (type == G_SEEK_CUR) {
95
0
    goffset pos = g_seekable_tell(G_SEEKABLE(self->base_stream));
96
0
    return g_seekable_seek(G_SEEKABLE(self->base_stream),
97
0
               self->offset + pos + offset,
98
0
               G_SEEK_SET,
99
0
               cancellable,
100
0
               error);
101
0
  }
102
6.91M
  if (type == G_SEEK_END) {
103
1.53M
    return g_seekable_seek(G_SEEKABLE(self->base_stream),
104
1.53M
               self->offset + self->size + offset,
105
1.53M
               G_SEEK_SET,
106
1.53M
               cancellable,
107
1.53M
               error);
108
1.53M
  }
109
5.37M
  return g_seekable_seek(G_SEEKABLE(self->base_stream),
110
5.37M
             self->offset + offset,
111
5.37M
             G_SEEK_SET,
112
5.37M
             cancellable,
113
5.37M
             error);
114
6.91M
}
115
116
static gboolean
117
fu_partial_input_stream_can_truncate(GSeekable *seekable)
118
0
{
119
0
  return FALSE;
120
0
}
121
122
static gboolean
123
fu_partial_input_stream_truncate(GSeekable *seekable,
124
         goffset offset,
125
         GCancellable *cancellable,
126
         GError **error)
127
0
{
128
0
  g_set_error_literal(error,
129
0
          FWUPD_ERROR,
130
0
          FWUPD_ERROR_NOT_SUPPORTED,
131
0
          "cannot truncate FuPartialInputStream");
132
0
  return FALSE;
133
0
}
134
135
static void
136
fu_partial_input_stream_seekable_iface_init(GSeekableIface *iface)
137
29
{
138
29
  iface->tell = fu_partial_input_stream_tell;
139
29
  iface->can_seek = fu_partial_input_stream_can_seek;
140
29
  iface->seek = fu_partial_input_stream_seek;
141
29
  iface->can_truncate = fu_partial_input_stream_can_truncate;
142
29
  iface->truncate_fn = fu_partial_input_stream_truncate;
143
29
}
144
145
/**
146
 * fu_partial_input_stream_new:
147
 * @stream: a base #GInputStream
148
 * @offset: offset into @stream
149
 * @size: size of @stream in bytes, or %G_MAXSIZE for the "rest" of the stream
150
 * @error: (nullable): optional return location for an error
151
 *
152
 * Creates a partial input stream where content is read from the donor stream.
153
 *
154
 * Returns: (transfer full): a #FuPartialInputStream, or %NULL on error
155
 *
156
 * Since: 2.0.0
157
 **/
158
GInputStream *
159
fu_partial_input_stream_new(GInputStream *stream, gsize offset, gsize size, GError **error)
160
1.90M
{
161
1.90M
  gsize base_sz = 0;
162
1.90M
  g_autoptr(FuPartialInputStream) self = g_object_new(FU_TYPE_PARTIAL_INPUT_STREAM, NULL);
163
164
1.90M
  g_return_val_if_fail(G_IS_INPUT_STREAM(stream), NULL);
165
1.90M
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
166
167
1.90M
  self->base_stream = g_object_ref(stream);
168
1.90M
  self->offset = offset;
169
170
  /* sanity check */
171
1.90M
  if (!fu_input_stream_size(stream, &base_sz, error)) {
172
0
    g_prefix_error_literal(error, "failed to get size: ");
173
0
    return NULL;
174
0
  }
175
1.90M
  if (size == G_MAXSIZE) {
176
3.76k
    if (offset > base_sz) {
177
173
      g_set_error(error,
178
173
            FWUPD_ERROR,
179
173
            FWUPD_ERROR_INVALID_DATA,
180
173
            "base stream was 0x%x bytes in size "
181
173
            "and tried to create partial stream @0x%x",
182
173
            (guint)base_sz,
183
173
            (guint)offset);
184
173
      return NULL;
185
173
    }
186
3.59k
    self->size = base_sz - offset;
187
1.90M
  } else {
188
1.90M
    if (fu_size_checked_add(offset, size) > base_sz) {
189
7.07k
      g_set_error(error,
190
7.07k
            FWUPD_ERROR,
191
7.07k
            FWUPD_ERROR_INVALID_DATA,
192
7.07k
            "base stream was 0x%x bytes in size, and tried to create "
193
7.07k
            "partial stream @0x%x of 0x%x bytes",
194
7.07k
            (guint)base_sz,
195
7.07k
            (guint)offset,
196
7.07k
            (guint)size);
197
7.07k
      return NULL;
198
7.07k
    }
199
1.89M
    self->size = size;
200
1.89M
  }
201
202
  /* success */
203
1.89M
  return G_INPUT_STREAM(g_steal_pointer(&self));
204
1.90M
}
205
206
/**
207
 * fu_partial_input_stream_get_offset:
208
 * @self: a #FuPartialInputStream
209
 *
210
 * Gets the offset of the stream.
211
 *
212
 * Returns: integer
213
 *
214
 * Since: 2.0.0
215
 **/
216
gsize
217
fu_partial_input_stream_get_offset(FuPartialInputStream *self)
218
0
{
219
0
  g_return_val_if_fail(FU_IS_PARTIAL_INPUT_STREAM(self), G_MAXSIZE);
220
0
  return self->offset;
221
0
}
222
223
/**
224
 * fu_partial_input_stream_get_size:
225
 * @self: a #FuPartialInputStream
226
 *
227
 * Gets the offset of the stream.
228
 *
229
 * Returns: integer
230
 *
231
 * Since: 2.0.0
232
 **/
233
gsize
234
fu_partial_input_stream_get_size(FuPartialInputStream *self)
235
1.61M
{
236
1.61M
  g_return_val_if_fail(FU_IS_PARTIAL_INPUT_STREAM(self), G_MAXSIZE);
237
1.61M
  return self->size;
238
1.61M
}
239
240
static gssize
241
fu_partial_input_stream_read(GInputStream *stream,
242
           void *buffer,
243
           gsize count,
244
           GCancellable *cancellable,
245
           GError **error)
246
8.00M
{
247
8.00M
  FuPartialInputStream *self = FU_PARTIAL_INPUT_STREAM(stream);
248
8.00M
  g_return_val_if_fail(FU_IS_PARTIAL_INPUT_STREAM(self), -1);
249
8.00M
  g_return_val_if_fail(error == NULL || *error == NULL, -1);
250
8.00M
  if (self->size < (gsize)g_seekable_tell(G_SEEKABLE(stream))) {
251
4.91k
    g_warning("base stream is outside seekable range");
252
4.91k
    return 0;
253
4.91k
  }
254
7.99M
  count = MIN(count, self->size - g_seekable_tell(G_SEEKABLE(stream)));
255
7.99M
  return g_input_stream_read(self->base_stream, buffer, count, cancellable, error);
256
8.00M
}
257
258
static void
259
fu_partial_input_stream_finalize(GObject *object)
260
1.90M
{
261
1.90M
  FuPartialInputStream *self = FU_PARTIAL_INPUT_STREAM(object);
262
1.90M
  if (self->base_stream != NULL)
263
1.90M
    g_object_unref(self->base_stream);
264
1.90M
  G_OBJECT_CLASS(fu_partial_input_stream_parent_class)->finalize(object);
265
1.90M
}
266
267
static void
268
fu_partial_input_stream_class_init(FuPartialInputStreamClass *klass)
269
29
{
270
29
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
271
29
  GInputStreamClass *istream_class = G_INPUT_STREAM_CLASS(klass);
272
29
  istream_class->read_fn = fu_partial_input_stream_read;
273
29
  object_class->finalize = fu_partial_input_stream_finalize;
274
29
}
275
276
static void
277
fu_partial_input_stream_init(FuPartialInputStream *self)
278
1.90M
{
279
1.90M
}