/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 | } |